db: make it possible to mark coins back as unspent

This commit is contained in:
Antoine Poinsot 2023-11-16 17:26:21 +01:00
parent c30bc8cd18
commit f78e831c39
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
3 changed files with 67 additions and 10 deletions

View File

@ -109,6 +109,9 @@ pub trait DatabaseConnection {
/// Mark a set of coins as being spent by a specified txid of a pending transaction.
fn spend_coins(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]);
/// Mark a set of coins as not being spent anymore.
fn unspend_coins(&mut self, outpoints: &[bitcoin::OutPoint]);
/// Mark a set of coins as spent by a specified txid at a specified block time.
fn confirm_spend(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, i32, u32)]);
@ -232,6 +235,10 @@ impl DatabaseConnection for SqliteConn {
self.spend_coins(outpoints)
}
fn unspend_coins(&mut self, outpoints: &[bitcoin::OutPoint]) {
self.unspend_coins(outpoints)
}
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, i32, u32)]) {
self.confirm_spend(outpoints)
}

View File

@ -509,6 +509,27 @@ impl SqliteConn {
.expect("Database must be available")
}
/// Mark a set of coins as not being spent.
pub fn unspend_coins<'a>(
&mut self,
outpoints: impl IntoIterator<Item = &'a bitcoin::OutPoint>,
) {
db_exec(&mut self.conn, |db_tx| {
for outpoint in outpoints {
db_tx.execute(
"UPDATE coins SET spend_txid = NULL, spend_block_height = NULL, spend_block_time = NULL WHERE txid = ?1 AND vout = ?2",
rusqlite::params![
outpoint.txid[..].to_vec(),
outpoint.vout,
],
)?;
}
Ok(())
})
.expect("Database must be available")
}
/// Mark the Spend transaction of a given set of coins as being confirmed at a given
/// block.
pub fn confirm_spend<'a>(
@ -1350,18 +1371,27 @@ CREATE TABLE spend_transactions (
coin_a.outpoint,
bitcoin::Txid::from_slice(&[0; 32][..]).unwrap(),
)]);
let coins_map: HashMap<bitcoin::OutPoint, DbCoin> = conn
.coins(&[], &[])
let coin = conn
.coins(&[], &[coin_a.outpoint])
.into_iter()
.map(|c| (c.outpoint, c))
.collect();
assert!(coins_map
.get(&coin_a.outpoint)
.unwrap()
.spend_txid
.is_some());
.next()
.unwrap();
assert!(coin.spend_txid.is_some());
// We will see it as 'spending'
// We can unspend it, if the spend transaction gets double spent.
conn.unspend_coins(&[coin_a.outpoint]);
let coin = conn
.coins(&[], &[coin_a.outpoint])
.into_iter()
.next()
.unwrap();
assert!(coin.spend_txid.is_none());
// Spend it back. We will see it as 'spending'
conn.spend_coins(&[(
coin_a.outpoint,
bitcoin::Txid::from_slice(&[0; 32][..]).unwrap(),
)]);
let outpoints: HashSet<bitcoin::OutPoint> = conn
.list_spending_coins()
.into_iter()
@ -1406,6 +1436,16 @@ CREATE TABLE spend_transactions (
assert_eq!(coin.spend_block.as_ref().unwrap().time, time);
assert_eq!(coin.spend_block.unwrap().height, height);
// If we unspend it all spend info will be wiped.
conn.unspend_coins(&[coin_a.outpoint]);
let coin = conn
.coins(&[], &[coin_a.outpoint])
.into_iter()
.next()
.unwrap();
assert!(coin.spend_txid.is_none());
assert!(coin.spend_block.is_none());
// Add an immature coin. As all coins it's first registered as unconfirmed (even though
// it's not).
let coin_imma = Coin {

View File

@ -278,6 +278,16 @@ impl DatabaseConnection for DummyDatabase {
}
}
fn unspend_coins<'a>(&mut self, outpoints: &[bitcoin::OutPoint]) {
for op in outpoints {
let mut db = self.db.write().unwrap();
let spent = &mut db.coins.get_mut(op).unwrap();
assert!(spent.spend_txid.is_some());
spent.spend_txid = None;
spent.spend_block = None;
}
}
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, i32, u32)]) {
for (op, spend_txid, height, time) in outpoints {
let mut db = self.db.write().unwrap();