db: record whether a coin was received on a change address

So we know what descriptor to use when spending it.
This commit is contained in:
Antoine Poinsot 2022-10-24 10:41:12 +02:00
parent 9b04a55147
commit 58a0e57c59
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
6 changed files with 36 additions and 11 deletions

View File

@ -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,

View File

@ -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,
},

View File

@ -49,10 +49,11 @@ pub trait DatabaseConnection {
fn increment_derivation_index(&mut self, secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>);
/// 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<bip32::ChildNumber>;
) -> Option<(bip32::ChildNumber, bool)>;
/// Get all our coins, past or present, spent or not.
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin>;
@ -154,9 +155,9 @@ impl DatabaseConnection for SqliteConn {
fn derivation_index_by_address(
&mut self,
address: &bitcoin::Address,
) -> Option<bip32::ChildNumber> {
) -> 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<u32>,
pub amount: bitcoin::Amount,
pub derivation_index: bip32::ChildNumber,
pub is_change: bool,
pub spend_txid: Option<bitcoin::Txid>,
pub spend_block: Option<SpendBlock>,
}
@ -227,6 +229,7 @@ impl std::convert::From<DbCoin> for Coin {
block_time,
amount,
derivation_index,
is_change,
spend_txid,
spend_block,
..
@ -237,6 +240,7 @@ impl std::convert::From<DbCoin> for Coin {
block_time,
amount,
derivation_index,
is_change,
spend_txid,
spend_block: spend_block.map(SpendBlock::from),
}

View File

@ -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",

View File

@ -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<u32>,
pub amount: bitcoin::Amount,
pub derivation_index: bip32::ChildNumber,
pub is_change: bool,
pub spend_txid: Option<bitcoin::Txid>,
pub spend_block: Option<DbSpendBlock>,
}
@ -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<Vec<u8>> = row.get(8)?;
let spend_txid: Option<Vec<u8>> = row.get(9)?;
let spend_txid =
spend_txid.map(|txid| encode::deserialize(&txid).expect("We only store valid txids"));
let spend_height: Option<i32> = row.get(9)?;
let spend_time: Option<u32> = row.get(10)?;
let spend_height: Option<i32> = row.get(10)?;
let spend_time: Option<u32> = 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,
})

View File

@ -187,7 +187,10 @@ impl DatabaseConnection for DummyDbConn {
}
}
fn derivation_index_by_address(&mut self, _: &bitcoin::Address) -> Option<bip32::ChildNumber> {
fn derivation_index_by_address(
&mut self,
_: &bitcoin::Address,
) -> Option<(bip32::ChildNumber, bool)> {
None
}