From 4cb6d2dc690d243616b32535774a0f959ff99bc2 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Mon, 3 Apr 2023 09:23:37 +0100 Subject: [PATCH] lib: couple block height and time --- src/bitcoin/poller/looper.rs | 5 +-- src/commands/mod.rs | 37 +++++++-------- src/database/mod.rs | 28 ++++++++---- src/database/sqlite/mod.rs | 84 ++++++++++++++++++----------------- src/database/sqlite/schema.rs | 21 ++++++--- src/testutils.rs | 13 +++--- 6 files changed, 104 insertions(+), 84 deletions(-) diff --git a/src/bitcoin/poller/looper.rs b/src/bitcoin/poller/looper.rs index 99ec8a3d..04038f2d 100644 --- a/src/bitcoin/poller/looper.rs +++ b/src/bitcoin/poller/looper.rs @@ -60,8 +60,7 @@ fn update_coins( amount, derivation_index, is_change, - block_height: None, - block_time: None, + block_info: None, spend_txid: None, spend_block: None, }; @@ -85,7 +84,7 @@ fn update_coins( .values() .chain(received.iter()) .filter_map(|coin| { - if coin.block_height.is_none() { + if coin.block_info.is_none() { Some(coin.outpoint) } else { None diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 379141f8..be7324e7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -293,7 +293,7 @@ impl DaemonControl { let Coin { amount, outpoint, - block_height, + block_info, spend_txid, spend_block, .. @@ -302,6 +302,7 @@ impl DaemonControl { txid, height: spend_block.map(|b| b.height), }); + let block_height = block_info.map(|b| b.height); ListCoinsEntry { amount, outpoint, @@ -712,8 +713,8 @@ impl DaemonControl { .into_iter() .filter(|(_, c)| { // We are interested in coins available at the *next* block - c.block_height - .map(|h| current_height + 1 >= h + timelock) + c.block_info + .map(|b| current_height + 1 >= b.height + timelock) .unwrap_or(false) }); @@ -867,7 +868,11 @@ pub struct CreateRecoveryResult { #[cfg(test)] mod tests { use super::*; - use crate::{bitcoin::Block, database::SpendBlock, testutils::*}; + use crate::{ + bitcoin::Block, + database::{BlockInfo, SpendBlock}, + testutils::*, + }; use bitcoin::{ blockdata::transaction::{TxIn, TxOut}, @@ -959,8 +964,7 @@ mod tests { let mut db_conn = control.db().lock().unwrap().connection(); db_conn.new_unspent_coins(&[Coin { outpoint: dummy_op, - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(100_000), derivation_index: bip32::ChildNumber::from(13), is_change: false, @@ -1065,8 +1069,7 @@ mod tests { }; db_conn.new_unspent_coins(&[Coin { outpoint: dummy_op_dup, - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(400_000), derivation_index: bip32::ChildNumber::from(42), is_change: false, @@ -1112,8 +1115,7 @@ mod tests { db_conn.new_unspent_coins(&[ Coin { outpoint: dummy_op_a, - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(100_000), derivation_index: bip32::ChildNumber::from(13), is_change: false, @@ -1122,8 +1124,7 @@ mod tests { }, Coin { outpoint: dummy_op_b, - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(115_680), derivation_index: bip32::ChildNumber::from(34), is_change: false, @@ -1294,8 +1295,7 @@ mod tests { txid: deposit1.txid(), vout: 0, }, - block_time: Some(1), - block_height: Some(1), + block_info: Some(BlockInfo { height: 1, time: 1 }), spend_block: Some(SpendBlock { time: 3, height: 3 }), derivation_index: ChildNumber::from(0), amount: bitcoin::Amount::from_sat(100_000_000), @@ -1308,8 +1308,7 @@ mod tests { txid: deposit2.txid(), vout: 0, }, - block_time: Some(2), - block_height: Some(2), + block_info: Some(BlockInfo { height: 2, time: 2 }), spend_block: None, derivation_index: ChildNumber::from(1), amount: bitcoin::Amount::from_sat(2000), @@ -1319,8 +1318,7 @@ mod tests { Coin { is_change: true, outpoint: OutPoint::new(spend_tx.txid(), 1), - block_time: Some(3), - block_height: Some(3), + block_info: Some(BlockInfo { height: 3, time: 3 }), spend_block: None, derivation_index: ChildNumber::from(2), amount: bitcoin::Amount::from_sat(100_000_000 - 4000 - 1000), @@ -1333,8 +1331,7 @@ mod tests { txid: deposit3.txid(), vout: 0, }, - block_time: Some(4), - block_height: Some(4), + block_info: Some(BlockInfo { height: 4, time: 4 }), spend_block: None, derivation_index: ChildNumber::from(3), amount: bitcoin::Amount::from_sat(3000), diff --git a/src/database/mod.rs b/src/database/mod.rs index 04953c36..070ed7e7 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -6,7 +6,7 @@ pub mod sqlite; use crate::{ bitcoin::BlockChainTip, database::sqlite::{ - schema::{DbCoin, DbSpendBlock, DbTip}, + schema::{DbBlockInfo, DbCoin, DbSpendBlock, DbTip}, SqliteConn, SqliteDb, }, }; @@ -276,11 +276,25 @@ impl From for SpendBlock { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BlockInfo { + pub height: i32, + pub time: u32, +} + +impl From for BlockInfo { + fn from(b: DbBlockInfo) -> BlockInfo { + BlockInfo { + height: b.height, + time: b.time, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Coin { pub outpoint: bitcoin::OutPoint, - pub block_height: Option, - pub block_time: Option, + pub block_info: Option, pub amount: bitcoin::Amount, pub derivation_index: bip32::ChildNumber, pub is_change: bool, @@ -292,8 +306,7 @@ impl std::convert::From for Coin { fn from(db_coin: DbCoin) -> Coin { let DbCoin { outpoint, - block_height, - block_time, + block_info, amount, derivation_index, is_change, @@ -303,8 +316,7 @@ impl std::convert::From for Coin { } = db_coin; Coin { outpoint, - block_height, - block_time, + block_info: block_info.map(BlockInfo::from), amount, derivation_index, is_change, @@ -316,7 +328,7 @@ impl std::convert::From for Coin { impl Coin { pub fn is_confirmed(&self) -> bool { - self.block_height.is_some() + self.block_info.is_some() } pub fn is_spent(&self) -> bool { diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index dedd79e9..1c979ad2 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -585,7 +585,7 @@ impl SqliteConn { #[cfg(test)] mod tests { use super::*; - use crate::database::SpendBlock; + use crate::database::{BlockInfo, DbBlockInfo, SpendBlock}; use crate::testutils::*; use std::{ collections::{HashMap, HashSet}, @@ -709,8 +709,7 @@ mod tests { "6f0dc85a369b44458eba3a1f0ea5b5935d563afb6994f70f5b0094e05be1676c:1", ) .unwrap(), - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(), is_change: false, @@ -742,8 +741,7 @@ mod tests { "61db3e276b095e5b05f1849dd6bfffb4e7e5ec1c4a4210099b98fce01571936f:12", ) .unwrap(), - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(1111), derivation_index: bip32::ChildNumber::from_normal_idx(103).unwrap(), is_change: true, @@ -780,10 +778,8 @@ mod tests { let time = 174500; conn.confirm_coins(&[(coin_a.outpoint, height, time)]); let coins = conn.coins(CoinType::All); - assert_eq!(coins[0].block_height, Some(height)); - assert_eq!(coins[0].block_time, Some(time)); - assert!(coins[1].block_height.is_none()); - assert!(coins[1].block_time.is_none()); + assert_eq!(coins[0].block_info, Some(DbBlockInfo { height, time })); + assert!(coins[1].block_info.is_none()); // Now if we spend one, it'll be marked as such. conn.spend_coins(&[( @@ -969,8 +965,7 @@ mod tests { "6f0dc85a369b44458eba3a1f0ea5b5935d563afb6994f70f5b0094e05be1676c:1", ) .unwrap(), - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(), is_change: false, @@ -982,8 +977,10 @@ mod tests { "c449539458c60bee6c0d8905ba1dadb20b9187b82045d306a408b894cea492b0:2", ) .unwrap(), - block_height: Some(101_095), - block_time: Some(1_111_899), + block_info: Some(BlockInfo { + height: 101_095, + time: 1_111_899, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(100).unwrap(), is_change: false, @@ -995,8 +992,10 @@ mod tests { "f0801fd9ca8bca0624c230ab422b2e2c4c8dc995e4e1dbc6412510959cce1e4f:3", ) .unwrap(), - block_height: Some(101_099), - block_time: Some(1_121_899), + block_info: Some(BlockInfo { + height: 101_099, + time: 1_121_899, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(1000).unwrap(), is_change: false, @@ -1016,8 +1015,10 @@ mod tests { "19f56e65069f0a7a3bfb00c6a7085cc0669e03e91befeca1ee9891c9e737b2fb:4", ) .unwrap(), - block_height: Some(101_100), - block_time: Some(1_131_899), + block_info: Some(BlockInfo { + height: 101_100, + time: 1_131_899, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10000).unwrap(), is_change: false, @@ -1029,8 +1030,10 @@ mod tests { "ed6c8f1af9325f84de521e785e7ddfd33dc28c9ada4d687dcd3850100bde54e9:5", ) .unwrap(), - block_height: Some(101_102), - block_time: Some(1_134_899), + block_info: Some(BlockInfo { + height: 101_102, + time: 1_134_899, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(100000).unwrap(), is_change: false, @@ -1050,10 +1053,7 @@ mod tests { conn.confirm_coins( &coins .iter() - .filter_map(|c| { - c.block_height - .map(|b| (c.outpoint, b, c.block_time.unwrap())) - }) + .filter_map(|c| c.block_info.map(|b| (c.outpoint, b.height, b.time))) .collect::>(), ); conn.confirm_spend( @@ -1115,13 +1115,11 @@ mod tests { assert_eq!(db_coins[&coins[2].outpoint], coin); // The fourth one got its own confirmation info wiped let mut coin = coins[3]; - coin.block_height = None; - coin.block_time = None; + coin.block_info = None; assert_eq!(db_coins[&coins[3].outpoint], coin); // The fourth one got both is own confirmation and spend confirmation info wiped let mut coin = coins[4]; - coin.block_height = None; - coin.block_time = None; + coin.block_info = None; coin.spend_block = None; assert_eq!(db_coins[&coins[4].outpoint], coin); } @@ -1180,8 +1178,7 @@ mod tests { "6f0dc85a369b44458eba3a1f0ea5b5935d563afb6994f70f5b0094e05be1676c:1", ) .unwrap(), - block_height: None, - block_time: None, + block_info: None, amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(), is_change: false, @@ -1193,8 +1190,10 @@ mod tests { "c449539458c60bee6c0d8905ba1dadb20b9187b82045d306a408b894cea492b0:2", ) .unwrap(), - block_height: Some(101_095), - block_time: Some(1_121_000), + block_info: Some(BlockInfo { + height: 101_095, + time: 1_121_000, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(100).unwrap(), is_change: false, @@ -1206,8 +1205,10 @@ mod tests { "f0801fd9ca8bca0624c230ab422b2e2c4c8dc995e4e1dbc6412510959cce1e4f:3", ) .unwrap(), - block_height: Some(101_099), - block_time: Some(1_122_000), + block_info: Some(BlockInfo { + height: 101_099, + time: 1_122_000, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(1000).unwrap(), is_change: false, @@ -1227,8 +1228,10 @@ mod tests { "19f56e65069f0a7a3bfb00c6a7085cc0669e03e91befeca1ee9891c9e737b2fb:4", ) .unwrap(), - block_height: Some(101_100), - block_time: Some(1_124_000), + block_info: Some(BlockInfo { + height: 101_100, + time: 1_124_000, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(10000).unwrap(), is_change: false, @@ -1240,8 +1243,10 @@ mod tests { "ed6c8f1af9325f84de521e785e7ddfd33dc28c9ada4d687dcd3850100bde54e9:5", ) .unwrap(), - block_height: Some(101_102), - block_time: Some(1_125_000), + block_info: Some(BlockInfo { + height: 101_102, + time: 1_125_000, + }), amount: bitcoin::Amount::from_sat(98765), derivation_index: bip32::ChildNumber::from_normal_idx(100000).unwrap(), is_change: false, @@ -1261,10 +1266,7 @@ mod tests { conn.confirm_coins( &coins .iter() - .filter_map(|c| { - c.block_height - .map(|b| (c.outpoint, b, c.block_time.unwrap())) - }) + .filter_map(|c| c.block_info.map(|b| (c.outpoint, b.height, b.time))) .collect::>(), ); conn.confirm_spend( diff --git a/src/database/sqlite/schema.rs b/src/database/sqlite/schema.rs index 65ed1670..01a4c8c9 100644 --- a/src/database/sqlite/schema.rs +++ b/src/database/sqlite/schema.rs @@ -153,13 +153,18 @@ pub struct DbSpendBlock { pub time: u32, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DbBlockInfo { + pub height: i32, + pub time: u32, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DbCoin { pub id: i64, pub wallet_id: i64, pub outpoint: bitcoin::OutPoint, - pub block_height: Option, - pub block_time: Option, + pub block_info: Option, pub amount: bitcoin::Amount, pub derivation_index: bip32::ChildNumber, pub is_change: bool, @@ -174,8 +179,13 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin { let id = row.get(0)?; let wallet_id = row.get(1)?; - let block_height = row.get(2)?; - let block_time = row.get(3)?; + let block_height: Option = row.get(2)?; + let block_time: Option = row.get(3)?; + assert_eq!(block_height.is_none(), block_time.is_none()); + let block_info = block_height.map(|height| DbBlockInfo { + height, + time: block_time.expect("Must be there if height is"), + }); let txid: Vec = row.get(4)?; let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids"); let vout = row.get(5)?; @@ -202,8 +212,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin { id, wallet_id, outpoint, - block_height, - block_time, + block_info, amount, derivation_index, is_change, diff --git a/src/testutils.rs b/src/testutils.rs index 54d2ae6e..82cedd85 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -1,7 +1,7 @@ use crate::{ bitcoin::{BitcoinInterface, Block, BlockChainTip, UTxO}, config::{BitcoinConfig, Config}, - database::{Coin, CoinType, DatabaseConnection, DatabaseInterface, SpendBlock}, + database::{BlockInfo, Coin, CoinType, DatabaseConnection, DatabaseInterface, SpendBlock}, descriptors, DaemonHandle, }; @@ -233,10 +233,11 @@ impl DatabaseConnection for DummyDatabase { for (op, height, time) in outpoints { let mut db = self.db.write().unwrap(); let coin = &mut db.coins.get_mut(op).unwrap(); - assert!(coin.block_height.is_none()); - assert!(coin.block_time.is_none()); - coin.block_height = Some(*height); - coin.block_time = Some(*time); + assert!(coin.block_info.is_none()); + coin.block_info = Some(BlockInfo { + height: *height, + time: *time, + }); } } @@ -335,7 +336,7 @@ impl DatabaseConnection for DummyDatabase { // Get txid and block time of every transactions that happened between start and end // timestamps. for coin in coins.values() { - if let Some(time) = coin.block_time { + if let Some(time) = coin.block_info.map(|b| b.time) { if time >= start && time <= end { let row = (coin.outpoint.txid, time); if !txids_and_time.contains(&row) {