From 58a0e57c59bf7b4f451580917f701e95e45d1af6 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Mon, 24 Oct 2022 10:41:12 +0200 Subject: [PATCH] db: record whether a coin was received on a change address So we know what descriptor to use when spending it. --- src/bitcoin/poller/looper.rs | 5 ++++- src/commands/mod.rs | 3 +++ src/database/mod.rs | 10 +++++++--- src/database/sqlite/mod.rs | 14 +++++++++++--- src/database/sqlite/schema.rs | 10 +++++++--- src/testutils.rs | 5 ++++- 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/bitcoin/poller/looper.rs b/src/bitcoin/poller/looper.rs index 93ea97df..7d39124c 100644 --- a/src/bitcoin/poller/looper.rs +++ b/src/bitcoin/poller/looper.rs @@ -35,7 +35,9 @@ fn update_coins( // Start by fetching newly received coins. let mut received = Vec::new(); for utxo in bit.received_coins(previous_tip, descs) { - if let Some(derivation_index) = db_conn.derivation_index_by_address(&utxo.address) { + if let Some((derivation_index, is_change)) = + db_conn.derivation_index_by_address(&utxo.address) + { if !curr_coins.contains_key(&utxo.outpoint) { let UTxO { outpoint, amount, .. @@ -44,6 +46,7 @@ fn update_coins( outpoint, amount, derivation_index, + is_change, block_height: None, block_time: None, spend_txid: None, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6c04752d..92d5e19d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -627,6 +627,7 @@ mod tests { block_time: None, amount: bitcoin::Amount::from_sat(100_000), derivation_index: bip32::ChildNumber::from(13), + is_change: false, spend_txid: None, spend_block: None, }]); @@ -721,6 +722,7 @@ mod tests { block_time: None, amount: bitcoin::Amount::from_sat(100_000), derivation_index: bip32::ChildNumber::from(13), + is_change: false, spend_txid: None, spend_block: None, }, @@ -730,6 +732,7 @@ mod tests { block_time: None, amount: bitcoin::Amount::from_sat(115_680), derivation_index: bip32::ChildNumber::from(34), + is_change: false, spend_txid: None, spend_block: None, }, diff --git a/src/database/mod.rs b/src/database/mod.rs index c9e2d88b..6097248e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -49,10 +49,11 @@ pub trait DatabaseConnection { fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1); + /// Get the derivation index for this address, as well as whether this address is change. fn derivation_index_by_address( &mut self, address: &bitcoin::Address, - ) -> Option; + ) -> Option<(bip32::ChildNumber, bool)>; /// Get all our coins, past or present, spent or not. fn coins(&mut self) -> HashMap; @@ -154,9 +155,9 @@ impl DatabaseConnection for SqliteConn { fn derivation_index_by_address( &mut self, address: &bitcoin::Address, - ) -> Option { + ) -> Option<(bip32::ChildNumber, bool)> { self.db_address(address) - .map(|db_addr| db_addr.derivation_index) + .map(|db_addr| (db_addr.derivation_index, address == &db_addr.change_address)) } fn coins_by_outpoints( @@ -215,6 +216,7 @@ pub struct Coin { pub block_time: Option, pub amount: bitcoin::Amount, pub derivation_index: bip32::ChildNumber, + pub is_change: bool, pub spend_txid: Option, pub spend_block: Option, } @@ -227,6 +229,7 @@ impl std::convert::From for Coin { block_time, amount, derivation_index, + is_change, spend_txid, spend_block, .. @@ -237,6 +240,7 @@ impl std::convert::From for Coin { block_time, amount, derivation_index, + is_change, spend_txid, spend_block: spend_block.map(SpendBlock::from), } diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index 50c92f4d..d6b14d30 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -288,14 +288,15 @@ impl SqliteConn { for coin in coins { let deriv_index: u32 = coin.derivation_index.into(); db_tx.execute( - "INSERT INTO coins (wallet_id, txid, vout, amount_sat, derivation_index) \ - VALUES (?1, ?2, ?3, ?4, ?5)", + "INSERT INTO coins (wallet_id, txid, vout, amount_sat, derivation_index, is_change) \ + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", rusqlite::params![ WALLET_ID, coin.outpoint.txid.to_vec(), coin.outpoint.vout, coin.amount.to_sat(), deriv_index, + coin.is_change, ], )?; } @@ -607,6 +608,7 @@ mod tests { block_time: None, amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(), + is_change: false, spend_txid: None, spend_block: None, }; @@ -618,7 +620,7 @@ mod tests { assert_eq!(coins.len(), 1); assert_eq!(coins[0].outpoint, coin_a.outpoint); - // Add a second one, we'll get both. + // Add a second one (this one is change), we'll get both. let coin_b = Coin { outpoint: bitcoin::OutPoint::from_str( "61db3e276b095e5b05f1849dd6bfffb4e7e5ec1c4a4210099b98fce01571936f:12", @@ -628,6 +630,7 @@ mod tests { block_time: None, amount: bitcoin::Amount::from_sat(1111), derivation_index: bip32::ChildNumber::from_normal_idx(103).unwrap(), + is_change: true, spend_txid: None, spend_block: None, }; @@ -802,6 +805,7 @@ mod tests { block_time: None, amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(), + is_change: false, spend_txid: None, spend_block: None, }, @@ -814,6 +818,7 @@ mod tests { block_time: Some(1_111_899), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(100).unwrap(), + is_change: false, spend_txid: None, spend_block: None, }, @@ -826,6 +831,7 @@ mod tests { block_time: Some(1_121_899), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(1000).unwrap(), + is_change: false, spend_txid: Some( bitcoin::Txid::from_str( "0c62a990d20d54429e70859292e82374ba6b1b951a3ab60f26bb65fee5724ff7", @@ -846,6 +852,7 @@ mod tests { block_time: Some(1_131_899), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10000).unwrap(), + is_change: false, spend_txid: None, spend_block: None, }, @@ -858,6 +865,7 @@ mod tests { block_time: Some(1_134_899), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(100000).unwrap(), + is_change: false, spend_txid: Some( bitcoin::Txid::from_str( "7477017f992cdc7ba08acafb77cb3b5bc0f42ac340d3e1e1da0785bdda20d5f6", diff --git a/src/database/sqlite/schema.rs b/src/database/sqlite/schema.rs index 87c55bb5..1f729d41 100644 --- a/src/database/sqlite/schema.rs +++ b/src/database/sqlite/schema.rs @@ -44,6 +44,7 @@ CREATE TABLE coins ( vout INTEGER NOT NULL, amount_sat INTEGER NOT NULL, derivation_index INTEGER NOT NULL, + is_change BOOLEAN NOT NULL CHECK (is_change IN (0,1)), spend_txid BLOB, spend_block_height INTEGER, spend_block_time INTEGER, @@ -146,6 +147,7 @@ pub struct DbCoin { pub block_time: Option, pub amount: bitcoin::Amount, pub derivation_index: bip32::ChildNumber, + pub is_change: bool, pub spend_txid: Option, pub spend_block: Option, } @@ -168,12 +170,13 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin { let amount = bitcoin::Amount::from_sat(amount); let der_idx: u32 = row.get(7)?; let derivation_index = bip32::ChildNumber::from(der_idx); + let is_change: bool = row.get(8)?; - let spend_txid: Option> = row.get(8)?; + let spend_txid: Option> = row.get(9)?; let spend_txid = spend_txid.map(|txid| encode::deserialize(&txid).expect("We only store valid txids")); - let spend_height: Option = row.get(9)?; - let spend_time: Option = row.get(10)?; + let spend_height: Option = row.get(10)?; + let spend_time: Option = row.get(11)?; assert_eq!(spend_height.is_none(), spend_time.is_none()); let spend_block = spend_height.map(|height| DbSpendBlock { height, @@ -188,6 +191,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin { block_time, amount, derivation_index, + is_change, spend_txid, spend_block, }) diff --git a/src/testutils.rs b/src/testutils.rs index 78fe9fd2..ea13d1d2 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -187,7 +187,10 @@ impl DatabaseConnection for DummyDbConn { } } - fn derivation_index_by_address(&mut self, _: &bitcoin::Address) -> Option { + fn derivation_index_by_address( + &mut self, + _: &bitcoin::Address, + ) -> Option<(bip32::ChildNumber, bool)> { None }