From 2892edf94bf733f8baaa9bd7a71c238f1a9e6890 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Sat, 23 Oct 2021 14:51:42 +0200 Subject: [PATCH 1/9] [db] Add the `last_sync_time` database entry This will be used to store the height and timestamp after every sync. --- src/database/any.rs | 15 ++++++++++ src/database/keyvalue.rs | 28 ++++++++++++++++++ src/database/memory.rs | 29 +++++++++++++++++++ src/database/mod.rs | 26 +++++++++++++++++ src/database/sqlite.rs | 61 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+) diff --git a/src/database/any.rs b/src/database/any.rs index 5186452cf..ba06e79ae 100644 --- a/src/database/any.rs +++ b/src/database/any.rs @@ -144,6 +144,9 @@ impl BatchOperations for AnyDatabase { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value) } + fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error> { + impl_inner_method!(AnyDatabase, self, set_last_sync_time, last_sync_time) + } fn del_script_pubkey_from_path( &mut self, @@ -180,6 +183,9 @@ impl BatchOperations for AnyDatabase { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, del_last_index, keychain) } + fn del_last_sync_time(&mut self) -> Result, Error> { + impl_inner_method!(AnyDatabase, self, del_last_sync_time) + } } impl Database for AnyDatabase { @@ -241,6 +247,9 @@ impl Database for AnyDatabase { fn get_last_index(&self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, get_last_index, keychain) } + fn get_last_sync_time(&self) -> Result, Error> { + impl_inner_method!(AnyDatabase, self, get_last_sync_time) + } fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { impl_inner_method!(AnyDatabase, self, increment_last_index, keychain) @@ -272,6 +281,9 @@ impl BatchOperations for AnyBatch { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyBatch, self, set_last_index, keychain, value) } + fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error> { + impl_inner_method!(AnyBatch, self, set_last_sync_time, last_sync_time) + } fn del_script_pubkey_from_path( &mut self, @@ -302,6 +314,9 @@ impl BatchOperations for AnyBatch { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyBatch, self, del_last_index, keychain) } + fn del_last_sync_time(&mut self) -> Result, Error> { + impl_inner_method!(AnyBatch, self, del_last_sync_time) + } } impl BatchDatabase for AnyDatabase { diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index 2da92f228..b856b5c5e 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -82,6 +82,13 @@ macro_rules! impl_batch_operations { Ok(()) } + fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + let key = MapKey::LastSyncTime.as_map_key(); + self.insert(key, serde_json::to_vec(&ct)?)$($after_insert)*; + + Ok(()) + } + fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result, Error> { let key = MapKey::Path((Some(keychain), Some(path))).as_map_key(); let res = self.remove(key); @@ -168,6 +175,14 @@ macro_rules! impl_batch_operations { } } } + + fn del_last_sync_time(&mut self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + let res = self.remove(key); + let res = $process_delete!(res); + + Ok(res.map(|b| serde_json::from_slice(&b)).transpose()?) + } } } @@ -342,6 +357,14 @@ impl Database for Tree { .transpose() } + fn get_last_sync_time(&self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + Ok(self + .get(key)? + .map(|b| serde_json::from_slice(&b)) + .transpose()?) + } + // inserts 0 if not present fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { let key = MapKey::LastIndex(keychain).as_map_key(); @@ -470,4 +493,9 @@ mod test { fn test_last_index() { crate::database::test::test_last_index(get_tree()); } + + #[test] + fn test_last_sync_time() { + crate::database::test::test_last_sync_time(get_tree()); + } } diff --git a/src/database/memory.rs b/src/database/memory.rs index 78cc031d4..21f00ed94 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -33,6 +33,7 @@ use crate::types::*; // transactions t -> tx details // deriv indexes c{i,e} -> u32 // descriptor checksum d{i,e} -> vec +// last sync time l -> { height, timestamp } pub(crate) enum MapKey<'a> { Path((Option, Option)), @@ -41,6 +42,7 @@ pub(crate) enum MapKey<'a> { RawTx(Option<&'a Txid>), Transaction(Option<&'a Txid>), LastIndex(KeychainKind), + LastSyncTime, DescriptorChecksum(KeychainKind), } @@ -59,6 +61,7 @@ impl MapKey<'_> { MapKey::RawTx(_) => b"r".to_vec(), MapKey::Transaction(_) => b"t".to_vec(), MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(), + MapKey::LastSyncTime => b"l".to_vec(), MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(), } } @@ -180,6 +183,12 @@ impl BatchOperations for MemoryDatabase { Ok(()) } + fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + let key = MapKey::LastSyncTime.as_map_key(); + self.map.insert(key, Box::new(ct)); + + Ok(()) + } fn del_script_pubkey_from_path( &mut self, @@ -270,6 +279,13 @@ impl BatchOperations for MemoryDatabase { Some(b) => Ok(Some(*b.downcast_ref().unwrap())), } } + fn del_last_sync_time(&mut self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + let res = self.map.remove(&key); + self.deleted_keys.push(key); + + Ok(res.map(|b| b.downcast_ref().cloned().unwrap())) + } } impl Database for MemoryDatabase { @@ -407,6 +423,14 @@ impl Database for MemoryDatabase { Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap())) } + fn get_last_sync_time(&self) -> Result, Error> { + let key = MapKey::LastSyncTime.as_map_key(); + Ok(self + .map + .get(&key) + .map(|b| b.downcast_ref().cloned().unwrap())) + } + // inserts 0 if not present fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { let key = MapKey::LastIndex(keychain).as_map_key(); @@ -590,4 +614,9 @@ mod test { fn test_last_index() { crate::database::test::test_last_index(get_tree()); } + + #[test] + fn test_last_sync_time() { + crate::database::test::test_last_sync_time(get_tree()); + } } diff --git a/src/database/mod.rs b/src/database/mod.rs index 4a3936f55..310dd1929 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -64,6 +64,8 @@ pub trait BatchOperations { fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>; /// Store the last derivation index for a given keychain. fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>; + /// Store the sync time in terms of block height and timestamp + fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error>; /// Delete a script_pubkey given the keychain and its child number. fn del_script_pubkey_from_path( @@ -89,6 +91,10 @@ pub trait BatchOperations { ) -> Result, Error>; /// Delete the last derivation index for a keychain. fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error>; + /// Reset the last sync time to `None` + /// + /// Returns the removed value + fn del_last_sync_time(&mut self) -> Result, Error>; } /// Trait for reading data from a database @@ -134,6 +140,8 @@ pub trait Database: BatchOperations { fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result, Error>; /// Return the last defivation index for a keychain. fn get_last_index(&self, keychain: KeychainKind) -> Result, Error>; + /// Return the last sync time, if present + fn get_last_sync_time(&self) -> Result, Error>; /// Increment the last derivation index for a keychain and return it /// @@ -377,5 +385,23 @@ pub mod test { ); } + pub fn test_last_sync_time(mut tree: D) { + assert!(tree.get_last_sync_time().unwrap().is_none()); + + tree.set_last_sync_time(ConfirmationTime { + height: 100, + timestamp: 1000, + }) + .unwrap(); + + let extracted = tree.get_last_sync_time().unwrap(); + assert!(extracted.is_some()); + assert_eq!(extracted.as_ref().unwrap().height, 100); + assert_eq!(extracted.as_ref().unwrap().timestamp, 1000); + + tree.del_last_sync_time().unwrap(); + assert!(tree.get_last_sync_time().unwrap().is_none()); + } + // TODO: more tests... } diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 0dbaed44e..813a8f9c8 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -35,6 +35,7 @@ static MIGRATIONS: &[&str] = &[ "CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);", "CREATE TABLE checksums (keychain TEXT, checksum BLOB);", "CREATE INDEX idx_checksums_keychain ON checksums(keychain);", + "CREATE TABLE last_sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);" ]; /// Sqlite database stored on filesystem @@ -205,6 +206,19 @@ impl SqliteDatabase { Ok(()) } + fn update_last_sync_time(&self, ct: ConfirmationTime) -> Result { + let mut statement = self.connection.prepare_cached( + "INSERT INTO last_sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0", + )?; + + statement.execute(named_params! { + ":height": ct.height, + ":timestamp": ct.timestamp, + })?; + + Ok(self.connection.last_insert_rowid()) + } + fn select_script_pubkeys(&self) -> Result, Error> { let mut statement = self .connection @@ -487,6 +501,20 @@ impl SqliteDatabase { } } + fn select_last_sync_time(&self) -> Result, Error> { + let mut statement = self + .connection + .prepare_cached("SELECT height, timestamp FROM last_sync_time WHERE id = 0")?; + let mut rows = statement.query_map([], |row| { + Ok(ConfirmationTime { + height: row.get(0)?, + timestamp: row.get(1)?, + }) + })?; + + Ok(rows.next().transpose()?) + } + fn select_checksum_by_keychain(&self, keychain: String) -> Result>, Error> { let mut statement = self .connection @@ -563,6 +591,14 @@ impl SqliteDatabase { Ok(()) } + + fn delete_last_sync_time(&self) -> Result<(), Error> { + let mut statement = self + .connection + .prepare_cached("DELETE FROM last_sync_time WHERE id = 0")?; + statement.execute([])?; + Ok(()) + } } impl BatchOperations for SqliteDatabase { @@ -622,6 +658,11 @@ impl BatchOperations for SqliteDatabase { Ok(()) } + fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + self.update_last_sync_time(ct)?; + Ok(()) + } + fn del_script_pubkey_from_path( &mut self, keychain: KeychainKind, @@ -707,6 +748,17 @@ impl BatchOperations for SqliteDatabase { None => Ok(None), } } + + fn del_last_sync_time(&mut self) -> Result, Error> { + match self.select_last_sync_time()? { + Some(value) => { + self.delete_last_sync_time()?; + + Ok(Some(value)) + } + None => Ok(None), + } + } } impl Database for SqliteDatabase { @@ -818,6 +870,10 @@ impl Database for SqliteDatabase { Ok(value) } + fn get_last_sync_time(&self) -> Result, Error> { + self.select_last_sync_time() + } + fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { let keychain_string = serde_json::to_string(&keychain)?; match self.get_last_index(keychain)? { @@ -965,4 +1021,9 @@ pub mod test { fn test_last_index() { crate::database::test::test_last_index(get_database()); } + + #[test] + fn test_last_sync_time() { + crate::database::test::test_last_sync_time(get_database()); + } } From 12e51b3c06fcd2b68d419ddda7a4d995865d4ce7 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Sat, 23 Oct 2021 15:00:09 +0200 Subject: [PATCH 2/9] [wallet] Expose an immutable reference to a wallet's database --- src/wallet/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b8d1196d9..b811662b9 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1447,6 +1447,11 @@ where Ok(()) } + + /// Return an immutable reference to the internal database + pub fn database(&self) -> impl std::ops::Deref + '_ { + self.database.borrow() + } } impl Wallet From 7c06f52a07946f9f767d5abb87c06c70cd145ac2 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Sat, 23 Oct 2021 15:25:49 +0200 Subject: [PATCH 3/9] [wallet] Store the block height and timestamp after syncing Closes #455 --- src/testutils/blockchain_tests.rs | 9 +++++++++ src/wallet/mod.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index b44a97182..7174bf58c 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -394,6 +394,9 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_sync_simple() { + use std::ops::Deref; + use crate::database::Database; + let (wallet, descriptors, mut test_client) = init_single_sig(); let tx = testutils! { @@ -402,7 +405,13 @@ macro_rules! bdk_blockchain_tests { println!("{:?}", tx); let txid = test_client.receive(tx); + // the RPC blockchain needs to call `sync()` during initialization to import the + // addresses (see `init_single_sig()`), so we skip this assertion + #[cfg(not(feature = "test-rpc"))] + assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none"); + wallet.sync(noop_progress(), None).unwrap(); + assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated"); assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind"); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b811662b9..2244f9272 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1554,6 +1554,15 @@ where } } + let last_sync_time = ConfirmationTime { + height: maybe_await!(self.client.get_height())?, + timestamp: time::get_timestamp(), + }; + debug!("Saving `last_sync_time` = {:?}", last_sync_time); + self.database + .borrow_mut() + .set_last_sync_time(last_sync_time)?; + Ok(()) } From 3e5bb077ac0b87ffd60f008869e2f47763b59745 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 26 Oct 2021 14:38:55 +0200 Subject: [PATCH 4/9] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 757baaa34..12ed347fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39. - Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced. +- Update the `Database` trait to store the last sync timestamp and block height ## [v0.13.0] - [v0.12.0] @@ -391,4 +392,4 @@ final transaction is created by calling `finish` on the builder. [v0.10.0]: https://github.com/bitcoindevkit/bdk/compare/v0.9.0...v0.10.0 [v0.11.0]: https://github.com/bitcoindevkit/bdk/compare/v0.10.0...v0.11.0 [v0.12.0]: https://github.com/bitcoindevkit/bdk/compare/v0.11.0...v0.12.0 -[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0 \ No newline at end of file +[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0 From 2c773293335a7f03c370c6d15298241a13e483ae Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Wed, 3 Nov 2021 16:05:30 +0000 Subject: [PATCH 5/9] Rename `ConfirmationTime` to `BlockTime` --- CHANGELOG.md | 1 + src/blockchain/compact_filters/mod.rs | 4 ++-- src/blockchain/rpc.rs | 6 +++--- src/blockchain/utils.rs | 6 +++--- src/database/any.rs | 10 +++++----- src/database/keyvalue.rs | 6 +++--- src/database/memory.rs | 8 ++++---- src/database/mod.rs | 10 +++++----- src/database/sqlite.rs | 18 +++++++++--------- src/types.rs | 18 ++++++++++++------ src/wallet/export.rs | 4 ++-- src/wallet/mod.rs | 4 ++-- 12 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12ed347fa..48b0bca3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39. - Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced. - Update the `Database` trait to store the last sync timestamp and block height +- Rename `ConfirmationTime` to `BlockTime` ## [v0.13.0] - [v0.12.0] diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index b513d378f..c2e2b8eac 100644 --- a/src/blockchain/compact_filters/mod.rs +++ b/src/blockchain/compact_filters/mod.rs @@ -71,7 +71,7 @@ use super::{Blockchain, Capability, ConfigurableBlockchain, Progress}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::error::Error; use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; -use crate::{ConfirmationTime, FeeRate}; +use crate::{BlockTime, FeeRate}; use peer::*; use store::*; @@ -206,7 +206,7 @@ impl CompactFiltersBlockchain { transaction: Some(tx.clone()), received: incoming, sent: outgoing, - confirmation_time: ConfirmationTime::new(height, timestamp), + confirmation_time: BlockTime::new(height, timestamp), verified: height.is_some(), fee: Some(inputs_sum.saturating_sub(outputs_sum)), }; diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index d4a5beca1..4cd22943d 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -37,7 +37,7 @@ use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress use crate::database::{BatchDatabase, DatabaseUtils}; use crate::descriptor::{get_checksum, IntoWalletDescriptor}; use crate::wallet::utils::SecpCtx; -use crate::{ConfirmationTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; +use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; use bitcoincore_rpc::json::{ GetAddressInfoResultLabel, ImportMultiOptions, ImportMultiRequest, ImportMultiRequestScriptPubkey, ImportMultiRescanSince, @@ -230,7 +230,7 @@ impl Blockchain for RpcBlockchain { list_txs_ids.insert(txid); if let Some(mut known_tx) = known_txs.get_mut(&txid) { let confirmation_time = - ConfirmationTime::new(tx_result.info.blockheight, tx_result.info.blocktime); + BlockTime::new(tx_result.info.blockheight, tx_result.info.blocktime); if confirmation_time != known_tx.confirmation_time { // reorg may change tx height debug!( @@ -266,7 +266,7 @@ impl Blockchain for RpcBlockchain { let td = TransactionDetails { transaction: Some(tx), txid: tx_result.info.txid, - confirmation_time: ConfirmationTime::new( + confirmation_time: BlockTime::new( tx_result.info.blockheight, tx_result.info.blocktime, ), diff --git a/src/blockchain/utils.rs b/src/blockchain/utils.rs index 7385b1db8..760cfe6f9 100644 --- a/src/blockchain/utils.rs +++ b/src/blockchain/utils.rs @@ -21,7 +21,7 @@ use bitcoin::{BlockHeader, OutPoint, Script, Transaction, Txid}; use super::*; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::error::Error; -use crate::types::{ConfirmationTime, KeychainKind, LocalUtxo, TransactionDetails}; +use crate::types::{BlockTime, KeychainKind, LocalUtxo, TransactionDetails}; use crate::wallet::time::Instant; use crate::wallet::utils::ChunksIterator; @@ -151,7 +151,7 @@ pub trait ElectrumLikeSync { // check if tx height matches, otherwise updates it. timestamp is not in the if clause // because we are not asking headers for confirmed tx we know about if tx_details.confirmation_time.as_ref().map(|c| c.height) != height { - let confirmation_time = ConfirmationTime::new(height, timestamp); + let confirmation_time = BlockTime::new(height, timestamp); let mut new_tx_details = tx_details.clone(); new_tx_details.confirmation_time = confirmation_time; batch.set_tx(&new_tx_details)?; @@ -359,7 +359,7 @@ fn save_transaction_details_and_utxos( transaction: Some(tx), received: incoming, sent: outgoing, - confirmation_time: ConfirmationTime::new(height, timestamp), + confirmation_time: BlockTime::new(height, timestamp), fee: Some(inputs_sum.saturating_sub(outputs_sum)), /* if the tx is a coinbase, fees would be negative */ verified: height.is_some(), }; diff --git a/src/database/any.rs b/src/database/any.rs index ba06e79ae..a608daca3 100644 --- a/src/database/any.rs +++ b/src/database/any.rs @@ -144,7 +144,7 @@ impl BatchOperations for AnyDatabase { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value) } - fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error> { + fn set_last_sync_time(&mut self, last_sync_time: BlockTime) -> Result<(), Error> { impl_inner_method!(AnyDatabase, self, set_last_sync_time, last_sync_time) } @@ -183,7 +183,7 @@ impl BatchOperations for AnyDatabase { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, del_last_index, keychain) } - fn del_last_sync_time(&mut self) -> Result, Error> { + fn del_last_sync_time(&mut self) -> Result, Error> { impl_inner_method!(AnyDatabase, self, del_last_sync_time) } } @@ -247,7 +247,7 @@ impl Database for AnyDatabase { fn get_last_index(&self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, get_last_index, keychain) } - fn get_last_sync_time(&self) -> Result, Error> { + fn get_last_sync_time(&self) -> Result, Error> { impl_inner_method!(AnyDatabase, self, get_last_sync_time) } @@ -281,7 +281,7 @@ impl BatchOperations for AnyBatch { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyBatch, self, set_last_index, keychain, value) } - fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error> { + fn set_last_sync_time(&mut self, last_sync_time: BlockTime) -> Result<(), Error> { impl_inner_method!(AnyBatch, self, set_last_sync_time, last_sync_time) } @@ -314,7 +314,7 @@ impl BatchOperations for AnyBatch { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyBatch, self, del_last_index, keychain) } - fn del_last_sync_time(&mut self) -> Result, Error> { + fn del_last_sync_time(&mut self) -> Result, Error> { impl_inner_method!(AnyBatch, self, del_last_sync_time) } } diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index b856b5c5e..976c3856b 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -82,7 +82,7 @@ macro_rules! impl_batch_operations { Ok(()) } - fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + fn set_last_sync_time(&mut self, ct: BlockTime) -> Result<(), Error> { let key = MapKey::LastSyncTime.as_map_key(); self.insert(key, serde_json::to_vec(&ct)?)$($after_insert)*; @@ -176,7 +176,7 @@ macro_rules! impl_batch_operations { } } - fn del_last_sync_time(&mut self) -> Result, Error> { + fn del_last_sync_time(&mut self) -> Result, Error> { let key = MapKey::LastSyncTime.as_map_key(); let res = self.remove(key); let res = $process_delete!(res); @@ -357,7 +357,7 @@ impl Database for Tree { .transpose() } - fn get_last_sync_time(&self) -> Result, Error> { + fn get_last_sync_time(&self) -> Result, Error> { let key = MapKey::LastSyncTime.as_map_key(); Ok(self .get(key)? diff --git a/src/database/memory.rs b/src/database/memory.rs index 21f00ed94..35b3283a4 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -183,7 +183,7 @@ impl BatchOperations for MemoryDatabase { Ok(()) } - fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + fn set_last_sync_time(&mut self, ct: BlockTime) -> Result<(), Error> { let key = MapKey::LastSyncTime.as_map_key(); self.map.insert(key, Box::new(ct)); @@ -279,7 +279,7 @@ impl BatchOperations for MemoryDatabase { Some(b) => Ok(Some(*b.downcast_ref().unwrap())), } } - fn del_last_sync_time(&mut self) -> Result, Error> { + fn del_last_sync_time(&mut self) -> Result, Error> { let key = MapKey::LastSyncTime.as_map_key(); let res = self.map.remove(&key); self.deleted_keys.push(key); @@ -423,7 +423,7 @@ impl Database for MemoryDatabase { Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap())) } - fn get_last_sync_time(&self) -> Result, Error> { + fn get_last_sync_time(&self) -> Result, Error> { let key = MapKey::LastSyncTime.as_map_key(); Ok(self .map @@ -505,7 +505,7 @@ macro_rules! populate_test_db { let txid = tx.txid(); let confirmation_time = tx_meta .min_confirmations - .map(|conf| $crate::ConfirmationTime { + .map(|conf| $crate::BlockTime { height: current_height.unwrap().checked_sub(conf as u32).unwrap(), timestamp: 0, }); diff --git a/src/database/mod.rs b/src/database/mod.rs index 310dd1929..a73d45b9a 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -65,7 +65,7 @@ pub trait BatchOperations { /// Store the last derivation index for a given keychain. fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>; /// Store the sync time in terms of block height and timestamp - fn set_last_sync_time(&mut self, last_sync_time: ConfirmationTime) -> Result<(), Error>; + fn set_last_sync_time(&mut self, last_sync_time: BlockTime) -> Result<(), Error>; /// Delete a script_pubkey given the keychain and its child number. fn del_script_pubkey_from_path( @@ -94,7 +94,7 @@ pub trait BatchOperations { /// Reset the last sync time to `None` /// /// Returns the removed value - fn del_last_sync_time(&mut self) -> Result, Error>; + fn del_last_sync_time(&mut self) -> Result, Error>; } /// Trait for reading data from a database @@ -141,7 +141,7 @@ pub trait Database: BatchOperations { /// Return the last defivation index for a keychain. fn get_last_index(&self, keychain: KeychainKind) -> Result, Error>; /// Return the last sync time, if present - fn get_last_sync_time(&self) -> Result, Error>; + fn get_last_sync_time(&self) -> Result, Error>; /// Increment the last derivation index for a keychain and return it /// @@ -333,7 +333,7 @@ pub mod test { received: 1337, sent: 420420, fee: Some(140), - confirmation_time: Some(ConfirmationTime { + confirmation_time: Some(BlockTime { timestamp: 123456, height: 1000, }), @@ -388,7 +388,7 @@ pub mod test { pub fn test_last_sync_time(mut tree: D) { assert!(tree.get_last_sync_time().unwrap().is_none()); - tree.set_last_sync_time(ConfirmationTime { + tree.set_last_sync_time(BlockTime { height: 100, timestamp: 1000, }) diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 813a8f9c8..6394f7447 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -206,7 +206,7 @@ impl SqliteDatabase { Ok(()) } - fn update_last_sync_time(&self, ct: ConfirmationTime) -> Result { + fn update_last_sync_time(&self, ct: BlockTime) -> Result { let mut statement = self.connection.prepare_cached( "INSERT INTO last_sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0", )?; @@ -389,7 +389,7 @@ impl SqliteDatabase { }; let confirmation_time = match (height, timestamp) { - (Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }), + (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }), _ => None, }; @@ -423,7 +423,7 @@ impl SqliteDatabase { let verified: bool = row.get(6)?; let confirmation_time = match (height, timestamp) { - (Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }), + (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }), _ => None, }; @@ -466,7 +466,7 @@ impl SqliteDatabase { }; let confirmation_time = match (height, timestamp) { - (Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }), + (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }), _ => None, }; @@ -501,12 +501,12 @@ impl SqliteDatabase { } } - fn select_last_sync_time(&self) -> Result, Error> { + fn select_last_sync_time(&self) -> Result, Error> { let mut statement = self .connection .prepare_cached("SELECT height, timestamp FROM last_sync_time WHERE id = 0")?; let mut rows = statement.query_map([], |row| { - Ok(ConfirmationTime { + Ok(BlockTime { height: row.get(0)?, timestamp: row.get(1)?, }) @@ -658,7 +658,7 @@ impl BatchOperations for SqliteDatabase { Ok(()) } - fn set_last_sync_time(&mut self, ct: ConfirmationTime) -> Result<(), Error> { + fn set_last_sync_time(&mut self, ct: BlockTime) -> Result<(), Error> { self.update_last_sync_time(ct)?; Ok(()) } @@ -749,7 +749,7 @@ impl BatchOperations for SqliteDatabase { } } - fn del_last_sync_time(&mut self) -> Result, Error> { + fn del_last_sync_time(&mut self) -> Result, Error> { match self.select_last_sync_time()? { Some(value) => { self.delete_last_sync_time()?; @@ -870,7 +870,7 @@ impl Database for SqliteDatabase { Ok(value) } - fn get_last_sync_time(&self) -> Result, Error> { + fn get_last_sync_time(&self) -> Result, Error> { self.select_last_sync_time() } diff --git a/src/types.rs b/src/types.rs index 3e4d8edf4..ac4a2228f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -210,7 +210,7 @@ pub struct TransactionDetails { pub fee: Option, /// If the transaction is confirmed, contains height and timestamp of the block containing the /// transaction, unconfirmed transaction contains `None`. - pub confirmation_time: Option, + pub confirmation_time: Option, /// Whether the tx has been verified against the consensus rules /// /// Confirmed txs are considered "verified" by default, while unconfirmed txs are checked to @@ -222,20 +222,26 @@ pub struct TransactionDetails { pub verified: bool, } -/// Block height and timestamp of the block containing the confirmed transaction +/// Block height and timestamp of a block #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] -pub struct ConfirmationTime { +pub struct BlockTime { /// confirmation block height pub height: u32, /// confirmation block timestamp pub timestamp: u64, } -impl ConfirmationTime { - /// Returns `Some` `ConfirmationTime` if both `height` and `timestamp` are `Some` +/// **DEPRECATED**: Confirmation time of a transaction +/// +/// The structure has been renamed to `BlockTime` +#[deprecated(note = "This structure has been renamed to `BlockTime`")] +pub type ConfirmationTime = BlockTime; + +impl BlockTime { + /// Returns `Some` `BlockTime` if both `height` and `timestamp` are `Some` pub fn new(height: Option, timestamp: Option) -> Option { match (height, timestamp) { - (Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }), + (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }), _ => None, } } diff --git a/src/wallet/export.rs b/src/wallet/export.rs index 047e93a1b..e39d178ea 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -212,7 +212,7 @@ mod test { use crate::database::{memory::MemoryDatabase, BatchOperations}; use crate::types::TransactionDetails; use crate::wallet::Wallet; - use crate::ConfirmationTime; + use crate::BlockTime; fn get_test_db() -> MemoryDatabase { let mut db = MemoryDatabase::new(); @@ -226,7 +226,7 @@ mod test { received: 100_000, sent: 0, fee: Some(500), - confirmation_time: Some(ConfirmationTime { + confirmation_time: Some(BlockTime { timestamp: 12345678, height: 5000, }), diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 2244f9272..5ae46e992 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1554,7 +1554,7 @@ where } } - let last_sync_time = ConfirmationTime { + let last_sync_time = BlockTime { height: maybe_await!(self.client.get_height())?, timestamp: time::get_timestamp(), }; @@ -2792,7 +2792,7 @@ pub(crate) mod test { let txid = tx.txid(); // skip saving the utxos, we know they can't be used anyways details.transaction = Some(tx); - details.confirmation_time = Some(ConfirmationTime { + details.confirmation_time = Some(BlockTime { timestamp: 12345678, height: 42, }); From 5830226216218f2b3ce89206c58fb24c88ef8c9a Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 4 Nov 2021 15:38:38 +0000 Subject: [PATCH 6/9] [database] Wrap `BlockTime` in another struct to allow adding more fields in the future --- src/database/any.rs | 20 +++++++------- src/database/keyvalue.rs | 20 +++++++------- src/database/memory.rs | 34 ++++++++++++------------ src/database/mod.rs | 45 ++++++++++++++++++++------------ src/database/sqlite.rs | 56 +++++++++++++++++++++------------------- src/wallet/mod.rs | 16 ++++++------ 6 files changed, 103 insertions(+), 88 deletions(-) diff --git a/src/database/any.rs b/src/database/any.rs index a608daca3..8b626e4b7 100644 --- a/src/database/any.rs +++ b/src/database/any.rs @@ -144,8 +144,8 @@ impl BatchOperations for AnyDatabase { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value) } - fn set_last_sync_time(&mut self, last_sync_time: BlockTime) -> Result<(), Error> { - impl_inner_method!(AnyDatabase, self, set_last_sync_time, last_sync_time) + fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> { + impl_inner_method!(AnyDatabase, self, set_sync_time, sync_time) } fn del_script_pubkey_from_path( @@ -183,8 +183,8 @@ impl BatchOperations for AnyDatabase { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, del_last_index, keychain) } - fn del_last_sync_time(&mut self) -> Result, Error> { - impl_inner_method!(AnyDatabase, self, del_last_sync_time) + fn del_sync_time(&mut self) -> Result, Error> { + impl_inner_method!(AnyDatabase, self, del_sync_time) } } @@ -247,8 +247,8 @@ impl Database for AnyDatabase { fn get_last_index(&self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyDatabase, self, get_last_index, keychain) } - fn get_last_sync_time(&self) -> Result, Error> { - impl_inner_method!(AnyDatabase, self, get_last_sync_time) + fn get_sync_time(&self) -> Result, Error> { + impl_inner_method!(AnyDatabase, self, get_sync_time) } fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { @@ -281,8 +281,8 @@ impl BatchOperations for AnyBatch { fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> { impl_inner_method!(AnyBatch, self, set_last_index, keychain, value) } - fn set_last_sync_time(&mut self, last_sync_time: BlockTime) -> Result<(), Error> { - impl_inner_method!(AnyBatch, self, set_last_sync_time, last_sync_time) + fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> { + impl_inner_method!(AnyBatch, self, set_sync_time, sync_time) } fn del_script_pubkey_from_path( @@ -314,8 +314,8 @@ impl BatchOperations for AnyBatch { fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error> { impl_inner_method!(AnyBatch, self, del_last_index, keychain) } - fn del_last_sync_time(&mut self) -> Result, Error> { - impl_inner_method!(AnyBatch, self, del_last_sync_time) + fn del_sync_time(&mut self) -> Result, Error> { + impl_inner_method!(AnyBatch, self, del_sync_time) } } diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index 976c3856b..07499e9f1 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -18,7 +18,7 @@ use bitcoin::hash_types::Txid; use bitcoin::{OutPoint, Script, Transaction}; use crate::database::memory::MapKey; -use crate::database::{BatchDatabase, BatchOperations, Database}; +use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime}; use crate::error::Error; use crate::types::*; @@ -82,9 +82,9 @@ macro_rules! impl_batch_operations { Ok(()) } - fn set_last_sync_time(&mut self, ct: BlockTime) -> Result<(), Error> { - let key = MapKey::LastSyncTime.as_map_key(); - self.insert(key, serde_json::to_vec(&ct)?)$($after_insert)*; + fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> { + let key = MapKey::SyncTime.as_map_key(); + self.insert(key, serde_json::to_vec(&data)?)$($after_insert)*; Ok(()) } @@ -176,8 +176,8 @@ macro_rules! impl_batch_operations { } } - fn del_last_sync_time(&mut self) -> Result, Error> { - let key = MapKey::LastSyncTime.as_map_key(); + fn del_sync_time(&mut self) -> Result, Error> { + let key = MapKey::SyncTime.as_map_key(); let res = self.remove(key); let res = $process_delete!(res); @@ -357,8 +357,8 @@ impl Database for Tree { .transpose() } - fn get_last_sync_time(&self) -> Result, Error> { - let key = MapKey::LastSyncTime.as_map_key(); + fn get_sync_time(&self) -> Result, Error> { + let key = MapKey::SyncTime.as_map_key(); Ok(self .get(key)? .map(|b| serde_json::from_slice(&b)) @@ -495,7 +495,7 @@ mod test { } #[test] - fn test_last_sync_time() { - crate::database::test::test_last_sync_time(get_tree()); + fn test_sync_time() { + crate::database::test::test_sync_time(get_tree()); } } diff --git a/src/database/memory.rs b/src/database/memory.rs index 35b3283a4..e828dc9df 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -22,7 +22,7 @@ use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::hash_types::Txid; use bitcoin::{OutPoint, Script, Transaction}; -use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database}; +use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database, SyncTime}; use crate::error::Error; use crate::types::*; @@ -42,7 +42,7 @@ pub(crate) enum MapKey<'a> { RawTx(Option<&'a Txid>), Transaction(Option<&'a Txid>), LastIndex(KeychainKind), - LastSyncTime, + SyncTime, DescriptorChecksum(KeychainKind), } @@ -61,7 +61,7 @@ impl MapKey<'_> { MapKey::RawTx(_) => b"r".to_vec(), MapKey::Transaction(_) => b"t".to_vec(), MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(), - MapKey::LastSyncTime => b"l".to_vec(), + MapKey::SyncTime => b"l".to_vec(), MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(), } } @@ -183,9 +183,9 @@ impl BatchOperations for MemoryDatabase { Ok(()) } - fn set_last_sync_time(&mut self, ct: BlockTime) -> Result<(), Error> { - let key = MapKey::LastSyncTime.as_map_key(); - self.map.insert(key, Box::new(ct)); + fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> { + let key = MapKey::SyncTime.as_map_key(); + self.map.insert(key, Box::new(data)); Ok(()) } @@ -279,8 +279,8 @@ impl BatchOperations for MemoryDatabase { Some(b) => Ok(Some(*b.downcast_ref().unwrap())), } } - fn del_last_sync_time(&mut self) -> Result, Error> { - let key = MapKey::LastSyncTime.as_map_key(); + fn del_sync_time(&mut self) -> Result, Error> { + let key = MapKey::SyncTime.as_map_key(); let res = self.map.remove(&key); self.deleted_keys.push(key); @@ -423,8 +423,8 @@ impl Database for MemoryDatabase { Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap())) } - fn get_last_sync_time(&self) -> Result, Error> { - let key = MapKey::LastSyncTime.as_map_key(); + fn get_sync_time(&self) -> Result, Error> { + let key = MapKey::SyncTime.as_map_key(); Ok(self .map .get(&key) @@ -503,12 +503,10 @@ macro_rules! populate_test_db { }; let txid = tx.txid(); - let confirmation_time = tx_meta - .min_confirmations - .map(|conf| $crate::BlockTime { - height: current_height.unwrap().checked_sub(conf as u32).unwrap(), - timestamp: 0, - }); + let confirmation_time = tx_meta.min_confirmations.map(|conf| $crate::BlockTime { + height: current_height.unwrap().checked_sub(conf as u32).unwrap(), + timestamp: 0, + }); let tx_details = $crate::TransactionDetails { transaction: Some(tx.clone()), @@ -616,7 +614,7 @@ mod test { } #[test] - fn test_last_sync_time() { - crate::database::test::test_last_sync_time(get_tree()); + fn test_sync_time() { + crate::database::test::test_sync_time(get_tree()); } } diff --git a/src/database/mod.rs b/src/database/mod.rs index a73d45b9a..e160b7422 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -24,6 +24,8 @@ //! //! [`Wallet`]: crate::wallet::Wallet +use serde::{Deserialize, Serialize}; + use bitcoin::hash_types::Txid; use bitcoin::{OutPoint, Script, Transaction, TxOut}; @@ -44,6 +46,15 @@ pub use sqlite::SqliteDatabase; pub mod memory; pub use memory::MemoryDatabase; +/// Blockchain state at the time of syncing +/// +/// Contains only the block time and height at the moment +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SyncTime { + /// Block timestamp and height at the time of sync + pub block_time: BlockTime, +} + /// Trait for operations that can be batched /// /// This trait defines the list of operations that must be implemented on the [`Database`] type and @@ -64,8 +75,8 @@ pub trait BatchOperations { fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>; /// Store the last derivation index for a given keychain. fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>; - /// Store the sync time in terms of block height and timestamp - fn set_last_sync_time(&mut self, last_sync_time: BlockTime) -> Result<(), Error>; + /// Store the sync time + fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error>; /// Delete a script_pubkey given the keychain and its child number. fn del_script_pubkey_from_path( @@ -91,10 +102,10 @@ pub trait BatchOperations { ) -> Result, Error>; /// Delete the last derivation index for a keychain. fn del_last_index(&mut self, keychain: KeychainKind) -> Result, Error>; - /// Reset the last sync time to `None` + /// Reset the sync time to `None` /// /// Returns the removed value - fn del_last_sync_time(&mut self) -> Result, Error>; + fn del_sync_time(&mut self) -> Result, Error>; } /// Trait for reading data from a database @@ -140,8 +151,8 @@ pub trait Database: BatchOperations { fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result, Error>; /// Return the last defivation index for a keychain. fn get_last_index(&self, keychain: KeychainKind) -> Result, Error>; - /// Return the last sync time, if present - fn get_last_sync_time(&self) -> Result, Error>; + /// Return the sync time, if present + fn get_sync_time(&self) -> Result, Error>; /// Increment the last derivation index for a keychain and return it /// @@ -385,22 +396,24 @@ pub mod test { ); } - pub fn test_last_sync_time(mut tree: D) { - assert!(tree.get_last_sync_time().unwrap().is_none()); + pub fn test_sync_time(mut tree: D) { + assert!(tree.get_sync_time().unwrap().is_none()); - tree.set_last_sync_time(BlockTime { - height: 100, - timestamp: 1000, + tree.set_sync_time(SyncTime { + block_time: BlockTime { + height: 100, + timestamp: 1000, + }, }) .unwrap(); - let extracted = tree.get_last_sync_time().unwrap(); + let extracted = tree.get_sync_time().unwrap(); assert!(extracted.is_some()); - assert_eq!(extracted.as_ref().unwrap().height, 100); - assert_eq!(extracted.as_ref().unwrap().timestamp, 1000); + assert_eq!(extracted.as_ref().unwrap().block_time.height, 100); + assert_eq!(extracted.as_ref().unwrap().block_time.timestamp, 1000); - tree.del_last_sync_time().unwrap(); - assert!(tree.get_last_sync_time().unwrap().is_none()); + tree.del_sync_time().unwrap(); + assert!(tree.get_sync_time().unwrap().is_none()); } // TODO: more tests... diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index 6394f7447..597ef6548 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -13,7 +13,7 @@ use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::hash_types::Txid; use bitcoin::{OutPoint, Script, Transaction, TxOut}; -use crate::database::{BatchDatabase, BatchOperations, Database}; +use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime}; use crate::error::Error; use crate::types::*; @@ -35,7 +35,7 @@ static MIGRATIONS: &[&str] = &[ "CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);", "CREATE TABLE checksums (keychain TEXT, checksum BLOB);", "CREATE INDEX idx_checksums_keychain ON checksums(keychain);", - "CREATE TABLE last_sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);" + "CREATE TABLE sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);" ]; /// Sqlite database stored on filesystem @@ -206,14 +206,14 @@ impl SqliteDatabase { Ok(()) } - fn update_last_sync_time(&self, ct: BlockTime) -> Result { + fn update_sync_time(&self, data: SyncTime) -> Result { let mut statement = self.connection.prepare_cached( - "INSERT INTO last_sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0", + "INSERT INTO sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0", )?; statement.execute(named_params! { - ":height": ct.height, - ":timestamp": ct.timestamp, + ":height": data.block_time.height, + ":timestamp": data.block_time.timestamp, })?; Ok(self.connection.last_insert_rowid()) @@ -501,18 +501,22 @@ impl SqliteDatabase { } } - fn select_last_sync_time(&self) -> Result, Error> { + fn select_sync_time(&self) -> Result, Error> { let mut statement = self .connection - .prepare_cached("SELECT height, timestamp FROM last_sync_time WHERE id = 0")?; - let mut rows = statement.query_map([], |row| { - Ok(BlockTime { - height: row.get(0)?, - timestamp: row.get(1)?, - }) - })?; + .prepare_cached("SELECT height, timestamp FROM sync_time WHERE id = 0")?; + let mut rows = statement.query([])?; - Ok(rows.next().transpose()?) + if let Some(row) = rows.next()? { + Ok(Some(SyncTime { + block_time: BlockTime { + height: row.get(0)?, + timestamp: row.get(1)?, + }, + })) + } else { + Ok(None) + } } fn select_checksum_by_keychain(&self, keychain: String) -> Result>, Error> { @@ -592,10 +596,10 @@ impl SqliteDatabase { Ok(()) } - fn delete_last_sync_time(&self) -> Result<(), Error> { + fn delete_sync_time(&self) -> Result<(), Error> { let mut statement = self .connection - .prepare_cached("DELETE FROM last_sync_time WHERE id = 0")?; + .prepare_cached("DELETE FROM sync_time WHERE id = 0")?; statement.execute([])?; Ok(()) } @@ -658,8 +662,8 @@ impl BatchOperations for SqliteDatabase { Ok(()) } - fn set_last_sync_time(&mut self, ct: BlockTime) -> Result<(), Error> { - self.update_last_sync_time(ct)?; + fn set_sync_time(&mut self, ct: SyncTime) -> Result<(), Error> { + self.update_sync_time(ct)?; Ok(()) } @@ -749,10 +753,10 @@ impl BatchOperations for SqliteDatabase { } } - fn del_last_sync_time(&mut self) -> Result, Error> { - match self.select_last_sync_time()? { + fn del_sync_time(&mut self) -> Result, Error> { + match self.select_sync_time()? { Some(value) => { - self.delete_last_sync_time()?; + self.delete_sync_time()?; Ok(Some(value)) } @@ -870,8 +874,8 @@ impl Database for SqliteDatabase { Ok(value) } - fn get_last_sync_time(&self) -> Result, Error> { - self.select_last_sync_time() + fn get_sync_time(&self) -> Result, Error> { + self.select_sync_time() } fn increment_last_index(&mut self, keychain: KeychainKind) -> Result { @@ -1023,7 +1027,7 @@ pub mod test { } #[test] - fn test_last_sync_time() { - crate::database::test::test_last_sync_time(get_database()); + fn test_sync_time() { + crate::database::test::test_sync_time(get_database()); } } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 5ae46e992..d5a6cb30e 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -57,7 +57,7 @@ use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LI use crate::blockchain::{Blockchain, Progress}; use crate::database::memory::MemoryDatabase; -use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; +use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; use crate::descriptor::derived::AsDerived; use crate::descriptor::policy::BuildSatisfaction; use crate::descriptor::{ @@ -1554,14 +1554,14 @@ where } } - let last_sync_time = BlockTime { - height: maybe_await!(self.client.get_height())?, - timestamp: time::get_timestamp(), + let sync_time = SyncTime { + block_time: BlockTime { + height: maybe_await!(self.client.get_height())?, + timestamp: time::get_timestamp(), + }, }; - debug!("Saving `last_sync_time` = {:?}", last_sync_time); - self.database - .borrow_mut() - .set_last_sync_time(last_sync_time)?; + debug!("Saving `sync_time` = {:?}", sync_time); + self.database.borrow_mut().set_sync_time(sync_time)?; Ok(()) } From 54f61d17f202a582d690a942f3ef285ed03de0ad Mon Sep 17 00:00:00 2001 From: Sandipan Dey Date: Mon, 13 Sep 2021 21:59:20 +0530 Subject: [PATCH 7/9] Added a wallet unit test to send to a Bech32m address --- Cargo.toml | 4 ++++ src/blockchain/rpc.rs | 4 ++-- src/wallet/mod.rs | 11 +++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b289f1949..701960d01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,10 @@ test-rpc = ["rpc", "electrsd/electrs_0_8_10", "test-blockchains"] test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "test-blockchains"] test-md-docs = ["electrum"] +[patch.crates-io] +core-rpc = { git="https://github.com/sandipndev/rust-bitcoincore-rpc", branch="bech32m-support" } +bitcoind = { git="https://github.com/sandipndev/bitcoind", branch="create-wallet-updates" } + [dev-dependencies] lazy_static = "1.4" env_logger = "0.7" diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index 4cd22943d..d1706b6ff 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -370,7 +370,7 @@ impl ConfigurableBlockchain for RpcBlockchain { client.load_wallet(&wallet_name)?; debug!("wallet loaded {:?}", wallet_name); } else { - client.create_wallet(&wallet_name, Some(true), None, None, None)?; + client.create_wallet(&wallet_name, Some(true), None, None, None, None)?; debug!("wallet created {:?}", wallet_name); } } @@ -445,7 +445,7 @@ where } /// return the wallets available in default wallet directory -//TODO use bitcoincore_rpc method when PR #179 lands +//TODO use core_rpc method when PR #179 lands fn list_wallet_dir(client: &Client) -> Result, Error> { #[derive(Deserialize)] struct Name { diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index d5a6cb30e..832626e31 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -3994,4 +3994,15 @@ pub(crate) mod test { } ); } + + #[test] + fn test_sending_to_bip350_bech32m_address() { + let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); + let addr = + Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c") + .unwrap(); + let mut builder = wallet.build_tx(); + builder.add_recipient(addr.script_pubkey(), 45_000); + builder.finish().unwrap(); + } } From 632422a3ab3462802cbb1f1e0154eb879b14ef20 Mon Sep 17 00:00:00 2001 From: Sandipan Dey Date: Mon, 13 Sep 2021 22:01:44 +0530 Subject: [PATCH 8/9] Added wallet blockchain test to send to Bech32m address --- src/testutils/blockchain_tests.rs | 51 +++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 7174bf58c..0582e93b3 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -145,9 +145,7 @@ impl TestClient { let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap(); let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap(); - - let monitor_script = - tx.vout[0].script_pub_key.addresses.as_ref().unwrap()[0].script_pubkey(); + let monitor_script = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap(); self.wait_for_tx(new_txid, &monitor_script); debug!("Bumped {}, new txid {}", txid, new_txid); @@ -911,6 +909,53 @@ macro_rules! bdk_blockchain_tests { wallet.sync(noop_progress(), None).unwrap(); assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase"); } + + #[test] + fn test_send_to_bech32m_addr() { + use std::str::FromStr; + use core_rpc::RpcApi; + use core_rpc::core_rpc_json::ImportDescriptorRequest; + + let (wallet, descriptors, mut test_client) = init_single_sig(); + + let wallet_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/0/*)#y283ssmn"; + let change_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/1/*)#47zsd9tt"; + + test_client.bitcoind.client + .import_descriptors( + vec![ + ImportDescriptorRequest::new(wallet_descriptor, false), + ImportDescriptorRequest::new(change_descriptor, false), + ] + ).unwrap(); + + let node_addr = test_client.get_node_address(Some(core_rpc::core_rpc_json::AddressType::Bech32m)); + assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap()); + + let new_bech32m_addr = test_client.get_new_address(Some("test bech32m"), Some(core_rpc::core_rpc_json::AddressType::Bech32m)).unwrap(); + assert_eq!(new_bech32m_addr, bitcoin::Address::from_str("bcrt1pxa4h86c5gc8x65un8nz546wy7hqxv7wljrv5sxukayh3xwnw23fs80jdf9").unwrap()); + + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); + + wallet.sync(noop_progress(), None).unwrap(); + assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); + + let mut builder = wallet.build_tx(); + builder.add_recipient(node_addr.script_pubkey(), 25_000); + let (mut psbt, details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + let tx = psbt.extract_tx(); + println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); + wallet.broadcast(tx).unwrap(); + wallet.sync(noop_progress(), None).unwrap(); + assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send"); + + assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); + assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents"); + } } }; From afa1ab4ff8dba441d30bc745eca2d634fbd7f017 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 11 Nov 2021 13:44:34 -0800 Subject: [PATCH 9/9] Fix blockchain_tests::test_send_to_bech32m_addr Now works with latest released versions of rust-bitcoincore-rpc and bitcoind. Once these crates are updated to support creating descriptor wallets and add importdescriptors and bech32m support this test will need to be updated. --- Cargo.toml | 6 +-- src/blockchain/rpc.rs | 4 +- src/testutils/blockchain_tests.rs | 83 ++++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 701960d01..e4ebf4ec2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,15 +90,11 @@ test-rpc = ["rpc", "electrsd/electrs_0_8_10", "test-blockchains"] test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "test-blockchains"] test-md-docs = ["electrum"] -[patch.crates-io] -core-rpc = { git="https://github.com/sandipndev/rust-bitcoincore-rpc", branch="bech32m-support" } -bitcoind = { git="https://github.com/sandipndev/bitcoind", branch="create-wallet-updates" } - [dev-dependencies] lazy_static = "1.4" env_logger = "0.7" clap = "2.33" -electrsd = { version= "0.12", features = ["trigger", "bitcoind_0_21_1"] } +electrsd = { version= "0.12", features = ["trigger", "bitcoind_22_0"] } [[example]] name = "address_validator" diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index d1706b6ff..4cd22943d 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -370,7 +370,7 @@ impl ConfigurableBlockchain for RpcBlockchain { client.load_wallet(&wallet_name)?; debug!("wallet loaded {:?}", wallet_name); } else { - client.create_wallet(&wallet_name, Some(true), None, None, None, None)?; + client.create_wallet(&wallet_name, Some(true), None, None, None)?; debug!("wallet created {:?}", wallet_name); } } @@ -445,7 +445,7 @@ where } /// return the wallets available in default wallet directory -//TODO use core_rpc method when PR #179 lands +//TODO use bitcoincore_rpc method when PR #179 lands fn list_wallet_dir(client: &Client) -> Result, Error> { #[derive(Deserialize)] struct Name { diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 0582e93b3..eec08c1d1 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -913,48 +913,97 @@ macro_rules! bdk_blockchain_tests { #[test] fn test_send_to_bech32m_addr() { use std::str::FromStr; - use core_rpc::RpcApi; - use core_rpc::core_rpc_json::ImportDescriptorRequest; + use serde; + use serde_json; + use serde::Serialize; + use bitcoincore_rpc::jsonrpc::serde_json::Value; + use bitcoincore_rpc::{Auth, Client, RpcApi}; let (wallet, descriptors, mut test_client) = init_single_sig(); + // TODO remove once rust-bitcoincore-rpc with PR 199 released + // https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199 + /// Import Descriptor Request + #[derive(Serialize, Clone, PartialEq, Eq, Debug)] + pub struct ImportDescriptorRequest { + pub active: bool, + #[serde(rename = "desc")] + pub descriptor: String, + pub range: [i64; 2], + pub next_index: i64, + pub timestamp: String, + pub internal: bool, + } + + // TODO remove once rust-bitcoincore-rpc with PR 199 released + impl ImportDescriptorRequest { + /// Create a new Import Descriptor request providing just the descriptor and internal flags + pub fn new(descriptor: &str, internal: bool) -> Self { + ImportDescriptorRequest { + descriptor: descriptor.to_string(), + internal, + active: true, + range: [0, 100], + next_index: 0, + timestamp: "now".to_string(), + } + } + } + + // 1. Create and add descriptors to a test bitcoind node taproot wallet + + // TODO replace once rust-bitcoincore-rpc with PR 174 released + // https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/174 + let _createwallet_result: Value = test_client.bitcoind.client.call("createwallet", &["taproot_wallet".into(),false.into(),true.into(),serde_json::to_value("").unwrap(), false.into(), true.into()]).unwrap(); + + // TODO replace once bitcoind released with support for rust-bitcoincore-rpc PR 174 + let taproot_wallet_client = Client::new(&test_client.bitcoind.rpc_url_with_wallet("taproot_wallet"), Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone())).unwrap(); + let wallet_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/0/*)#y283ssmn"; let change_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/1/*)#47zsd9tt"; - test_client.bitcoind.client - .import_descriptors( - vec![ + let tr_descriptors = vec![ ImportDescriptorRequest::new(wallet_descriptor, false), ImportDescriptorRequest::new(change_descriptor, false), - ] - ).unwrap(); + ]; + + // TODO replace once rust-bitcoincore-rpc with PR 199 released + let _import_result: Value = taproot_wallet_client.call("importdescriptors", &[serde_json::to_value(tr_descriptors).unwrap()]).unwrap(); + + // 2. Get a new bech32m address from test bitcoind node taproot wallet - let node_addr = test_client.get_node_address(Some(core_rpc::core_rpc_json::AddressType::Bech32m)); + // TODO replace once rust-bitcoincore-rpc with PR 199 released + let node_addr: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap(); assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap()); - let new_bech32m_addr = test_client.get_new_address(Some("test bech32m"), Some(core_rpc::core_rpc_json::AddressType::Bech32m)).unwrap(); - assert_eq!(new_bech32m_addr, bitcoin::Address::from_str("bcrt1pxa4h86c5gc8x65un8nz546wy7hqxv7wljrv5sxukayh3xwnw23fs80jdf9").unwrap()); + // 3. Send 50_000 sats from test bitcoind node to test BDK wallet test_client.receive(testutils! { @tx ( (@external descriptors, 0) => 50_000 ) }); wallet.sync(noop_progress(), None).unwrap(); - assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); + assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance"); + + // 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet let mut builder = wallet.build_tx(); builder.add_recipient(node_addr.script_pubkey(), 25_000); let (mut psbt, details) = builder.finish().unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); + assert!(finalized, "wallet cannot finalize transaction"); let tx = psbt.extract_tx(); - println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); - wallet.broadcast(tx).unwrap(); + wallet.broadcast(&tx).unwrap(); wallet.sync(noop_progress(), None).unwrap(); - assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send"); + assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send"); + assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs"); + assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents"); + test_client.generate(1, None); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); - assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents"); + // 5. Verify 25_000 sats are received by test bitcoind node taproot wallet + + let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap(); + assert_eq!(taproot_balance.as_sat(), 25_000, "node has incorrect taproot wallet balance"); } } };