From 338e38bc0f781a8c1e36d82ef34d1dfe99c02e8b Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Wed, 31 Aug 2022 18:10:37 +0530 Subject: [PATCH 1/5] Accumulate test helpers into a single place - Few test helpers and macros are scattered around `database/memory.rs` and `wallet/mod.rs`. These are collected in a single place `testutils/helpers.rs`. - the `populate_test_db` macro is changed into a function. Internal logic should remain same. - A new `run_tests_with_init` macro is added in `testutils.helpers.rs`, which can run database tests given an initializer function. Co-authored-by: SanthoshAnguluri Co-authored-by: saikishore222 --- src/database/memory.rs | 120 ----------------------- src/testutils/helpers.rs | 201 +++++++++++++++++++++++++++++++++++++++ src/testutils/mod.rs | 1 + src/wallet/mod.rs | 41 -------- 4 files changed, 202 insertions(+), 161 deletions(-) create mode 100644 src/testutils/helpers.rs diff --git a/src/database/memory.rs b/src/database/memory.rs index 691e7eb16..08748d022 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -475,126 +475,6 @@ impl ConfigurableDatabase for MemoryDatabase { } } -#[macro_export] -#[doc(hidden)] -/// Artificially insert a tx in the database, as if we had found it with a `sync`. This is a hidden -/// macro and not a `[cfg(test)]` function so it can be called within the context of doctests which -/// don't have `test` set. -macro_rules! populate_test_db { - ($db:expr, $tx_meta:expr, $current_height:expr$(,)?) => {{ - $crate::populate_test_db!($db, $tx_meta, $current_height, (@coinbase false)) - }}; - ($db:expr, $tx_meta:expr, $current_height:expr, (@coinbase $is_coinbase:expr)$(,)?) => {{ - use std::str::FromStr; - use $crate::database::SyncTime; - use $crate::database::{BatchOperations, Database}; - let mut db = $db; - let tx_meta = $tx_meta; - let current_height: Option = $current_height; - let mut input = vec![$crate::bitcoin::TxIn::default()]; - if !$is_coinbase { - input[0].previous_output.vout = 0; - } - let tx = $crate::bitcoin::Transaction { - version: 1, - lock_time: 0, - input, - output: tx_meta - .output - .iter() - .map(|out_meta| $crate::bitcoin::TxOut { - value: out_meta.value, - script_pubkey: $crate::bitcoin::Address::from_str(&out_meta.to_address) - .unwrap() - .script_pubkey(), - }) - .collect(), - }; - - let txid = tx.txid(); - // Set Confirmation time only if current height is provided. - // panics if `tx_meta.min_confirmation` is Some, and current_height is None. - let confirmation_time = tx_meta - .min_confirmations - .and_then(|v| if v == 0 { None } else { Some(v) }) - .map(|conf| $crate::BlockTime { - height: current_height.expect("Current height is needed for testing transaction with min-confirmation values").checked_sub(conf as u32).unwrap() + 1, - timestamp: 0, - }); - - // Set the database sync_time. - // Check if the current_height is less than already known sync height, apply the max - // If any of them is None, the other will be applied instead. - // If both are None, this will not be set. - if let Some(height) = db.get_sync_time().unwrap() - .map(|sync_time| sync_time.block_time.height) - .max(current_height) { - let sync_time = SyncTime { - block_time: BlockTime { - height, - timestamp: 0 - } - }; - db.set_sync_time(sync_time).unwrap(); - } - - let tx_details = $crate::TransactionDetails { - transaction: Some(tx.clone()), - txid, - fee: Some(0), - received: 0, - sent: 0, - confirmation_time, - }; - - db.set_tx(&tx_details).unwrap(); - for (vout, out) in tx.output.iter().enumerate() { - db.set_utxo(&$crate::LocalUtxo { - txout: out.clone(), - outpoint: $crate::bitcoin::OutPoint { - txid, - vout: vout as u32, - }, - keychain: $crate::KeychainKind::External, - is_spent: false, - }) - .unwrap(); - } - - txid - }}; -} - -#[macro_export] -#[doc(hidden)] -/// Macro for getting a wallet for use in a doctest -macro_rules! doctest_wallet { - () => {{ - use $crate::bitcoin::Network; - use $crate::database::MemoryDatabase; - use $crate::testutils; - let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; - let descriptors = testutils!(@descriptors (descriptor) (descriptor)); - - let mut db = MemoryDatabase::new(); - let txid = populate_test_db!( - &mut db, - testutils! { - @tx ( (@external descriptors, 0) => 500_000 ) (@confirmations 1) - }, - Some(100), - ); - - $crate::Wallet::new( - &descriptors.0, - descriptors.1.as_ref(), - Network::Regtest, - db - ) - .unwrap() - }} -} - #[cfg(test)] mod test { use super::MemoryDatabase; diff --git a/src/testutils/helpers.rs b/src/testutils/helpers.rs new file mode 100644 index 000000000..7d13883af --- /dev/null +++ b/src/testutils/helpers.rs @@ -0,0 +1,201 @@ +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. +#![allow(missing_docs)] +#![allow(unused)] + +use std::str::FromStr; + +use bitcoin::{Address, Network, OutPoint, Transaction, TxIn, TxOut, Txid}; + +use crate::{ + database::{AnyDatabase, BatchDatabase, BatchOperations, Database, MemoryDatabase, SyncTime}, + testutils, BlockTime, KeychainKind, LocalUtxo, TransactionDetails, Wallet, +}; + +use super::TestIncomingTx; + +#[doc(hidden)] +/// Populate a test database with a `TestIncomingTx`, as if we had found the tx with a `sync`. +/// This is a hidden function, only useful for `Database` unit testing. +/// Setting current_height to None, creates an unsynced database. +pub fn populate_test_db( + db: &mut impl Database, + tx_meta: TestIncomingTx, + current_height: Option, + is_coinbase: bool, +) -> Txid { + let mut input = vec![bitcoin::TxIn::default()]; + if !is_coinbase { + input[0].previous_output.vout = 0; + } + let tx = bitcoin::Transaction { + version: 1, + lock_time: 0, + input, + output: tx_meta + .output + .iter() + .map(|out_meta| bitcoin::TxOut { + value: out_meta.value, + script_pubkey: bitcoin::Address::from_str(&out_meta.to_address) + .unwrap() + .script_pubkey(), + }) + .collect(), + }; + + let txid = tx.txid(); + // Set Confirmation time only if current height is provided. + // panics if `tx_meta.min_confirmation` is Some, and current_height is None. + let confirmation_time = tx_meta + .min_confirmations + .and_then(|v| if v == 0 { None } else { Some(v) }) + .map(|conf| BlockTime { + height: current_height + .expect( + "Current height is needed for testing transaction with min-confirmation values", + ) + .checked_sub(conf as u32) + .unwrap() + + 1, + timestamp: 0, + }); + + // Set the database sync_time. + // Check if the current_height is less than already known sync height, apply the max + // If any of them is None, the other will be applied instead. + // If both are None, this will not be set. + if let Some(height) = db + .get_sync_time() + .unwrap() + .map(|sync_time| sync_time.block_time.height) + .max(current_height) + { + let sync_time = SyncTime { + block_time: BlockTime { + height, + timestamp: 0, + }, + }; + db.set_sync_time(sync_time).unwrap(); + } + + let tx_details = TransactionDetails { + transaction: Some(tx.clone()), + txid, + fee: Some(0), + received: 0, + sent: 0, + confirmation_time, + }; + + db.set_tx(&tx_details).unwrap(); + for (vout, out) in tx.output.iter().enumerate() { + db.set_utxo(&LocalUtxo { + txout: out.clone(), + outpoint: bitcoin::OutPoint { + txid, + vout: vout as u32, + }, + keychain: KeychainKind::External, + is_spent: false, + }) + .unwrap(); + } + txid +} + +#[doc(hidden)] +#[cfg(test)] +/// Return a fake wallet that appears to be funded for testing. +pub(crate) fn get_funded_wallet( + descriptor: &str, +) -> (Wallet, (String, Option), bitcoin::Txid) { + let descriptors = testutils!(@descriptors (descriptor)); + let wallet = Wallet::new( + &descriptors.0, + None, + Network::Regtest, + AnyDatabase::Memory(MemoryDatabase::new()), + ) + .unwrap(); + + let funding_address_kix = 0; + + let tx_meta = testutils! { + @tx ( (@external descriptors, funding_address_kix) => 50_000 ) (@confirmations 1) + }; + + wallet + .database_mut() + .set_script_pubkey( + &bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address) + .unwrap() + .script_pubkey(), + KeychainKind::External, + funding_address_kix, + ) + .unwrap(); + wallet + .database_mut() + .set_last_index(KeychainKind::External, funding_address_kix) + .unwrap(); + + let txid = populate_test_db(&mut *wallet.database_mut(), tx_meta, Some(100), false); + + (wallet, descriptors, txid) +} + +#[macro_export] +#[doc(hidden)] +macro_rules! run_tests_with_init { +(@init $fn_name:ident(), @tests ( $($x:tt) , + $(,)? )) => { + $( + #[test] + fn $x() + { + $crate::database::test::$x($fn_name()); + } + )+ + }; +} + +#[macro_export] +#[doc(hidden)] +/// Macro for getting a wallet for use in a doctest +macro_rules! doctest_wallet { + () => {{ + use $crate::testutils::helpers::populate_test_db; + use $crate::bitcoin::Network; + use $crate::database::MemoryDatabase; + use $crate::testutils; + let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; + let descriptors = testutils!(@descriptors (descriptor) (descriptor)); + + let mut db = MemoryDatabase::new(); + populate_test_db( + &mut db, + testutils! { + @tx ( (@external descriptors, 0) => 500_000 ) (@confirmations 1) + }, + Some(100), + false + ); + + $crate::Wallet::new( + &descriptors.0, + descriptors.1.as_ref(), + Network::Regtest, + db + ) + .unwrap() + }} +} diff --git a/src/testutils/mod.rs b/src/testutils/mod.rs index 82949ecc1..62040bf28 100644 --- a/src/testutils/mod.rs +++ b/src/testutils/mod.rs @@ -17,6 +17,7 @@ pub mod blockchain_tests; #[cfg(test)] #[cfg(feature = "test-blockchains")] pub mod configurable_blockchain_tests; +pub mod helpers; use bitcoin::{Address, Txid}; diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 2e3d9fdff..6753f496e 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1878,47 +1878,6 @@ where Ok(wallet_name) } -/// Return a fake wallet that appears to be funded for testing. -pub fn get_funded_wallet( - descriptor: &str, -) -> (Wallet, (String, Option), bitcoin::Txid) { - let descriptors = testutils!(@descriptors (descriptor)); - let wallet = Wallet::new( - &descriptors.0, - None, - Network::Regtest, - AnyDatabase::Memory(MemoryDatabase::new()), - ) - .unwrap(); - - let funding_address_kix = 0; - - let tx_meta = testutils! { - @tx ( (@external descriptors, funding_address_kix) => 50_000 ) (@confirmations 1) - }; - - wallet - .database - .borrow_mut() - .set_script_pubkey( - &bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address) - .unwrap() - .script_pubkey(), - KeychainKind::External, - funding_address_kix, - ) - .unwrap(); - wallet - .database - .borrow_mut() - .set_last_index(KeychainKind::External, funding_address_kix) - .unwrap(); - - let txid = crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, Some(100)); - - (wallet, descriptors, txid) -} - #[cfg(test)] pub(crate) mod test { use bitcoin::{util::psbt, Network}; From 35964da419e9bb287441b41abe09b520eba6a3e2 Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Wed, 31 Aug 2022 18:20:27 +0530 Subject: [PATCH 2/5] Apply new database test macro for testing - in `keyvalue.rs`, `memory.rs`, `sqlite.rs` use the `run_tests_with_init` macro to run database tests. - in `wallet/mod.rs` update all the test calls from macro to function for `populate_test_db`. - some import fixes in `address_validator.rs` and `psbt/mod.rs`. Co-authored-by: SanthoshAnguluri Co-authored-by: saikishore222 --- src/database/keyvalue.rs | 108 +++++++----------------------- src/database/memory.rs | 108 +++++++----------------------- src/database/sqlite.rs | 113 +++++++------------------------- src/psbt/mod.rs | 3 +- src/wallet/address_validator.rs | 3 +- src/wallet/mod.rs | 108 ++++++++++++++++-------------- 6 files changed, 133 insertions(+), 310 deletions(-) diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index f586ebeba..21e3841d2 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -410,6 +410,8 @@ mod test { use sled::{Db, Tree}; + use crate::run_tests_with_init; + static mut COUNT: usize = 0; lazy_static! { @@ -448,88 +450,26 @@ mod test { } } - #[test] - fn test_script_pubkey() { - crate::database::test::test_script_pubkey(get_tree()); - } - - #[test] - fn test_batch_script_pubkey() { - crate::database::test::test_batch_script_pubkey(get_tree()); - } - - #[test] - fn test_iter_script_pubkey() { - crate::database::test::test_iter_script_pubkey(get_tree()); - } - - #[test] - fn test_del_script_pubkey() { - crate::database::test::test_del_script_pubkey(get_tree()); - } - - #[test] - fn test_utxo() { - crate::database::test::test_utxo(get_tree()); - } - - #[test] - fn test_raw_tx() { - crate::database::test::test_raw_tx(get_tree()); - } - - #[test] - fn test_tx() { - crate::database::test::test_tx(get_tree()); - } - - #[test] - fn test_last_index() { - crate::database::test::test_last_index(get_tree()); - } - - #[test] - fn test_sync_time() { - crate::database::test::test_sync_time(get_tree()); - } - - #[test] - fn test_iter_raw_txs() { - crate::database::test::test_iter_raw_txs(get_tree()); - } - - #[test] - fn test_del_path_from_script_pubkey() { - crate::database::test::test_del_path_from_script_pubkey(get_tree()); - } - - #[test] - fn test_iter_script_pubkeys() { - crate::database::test::test_iter_script_pubkeys(get_tree()); - } - - #[test] - fn test_del_utxo() { - crate::database::test::test_del_utxo(get_tree()); - } - - #[test] - fn test_del_raw_tx() { - crate::database::test::test_del_raw_tx(get_tree()); - } - - #[test] - fn test_del_tx() { - crate::database::test::test_del_tx(get_tree()); - } - - #[test] - fn test_del_last_index() { - crate::database::test::test_del_last_index(get_tree()); - } - - #[test] - fn test_check_descriptor_checksum() { - crate::database::test::test_check_descriptor_checksum(get_tree()); - } + run_tests_with_init![ + @init get_tree(), + @tests( + test_script_pubkey, + test_batch_script_pubkey, + test_iter_script_pubkey, + test_del_script_pubkey, + test_utxo, + test_raw_tx, + test_tx, + test_last_index, + test_sync_time, + test_iter_raw_txs, + test_del_path_from_script_pubkey, + test_iter_script_pubkeys, + test_del_utxo, + test_del_raw_tx, + test_del_tx, + test_del_last_index, + test_check_descriptor_checksum + ) + ]; } diff --git a/src/database/memory.rs b/src/database/memory.rs index 08748d022..761e3c16d 100644 --- a/src/database/memory.rs +++ b/src/database/memory.rs @@ -477,94 +477,34 @@ impl ConfigurableDatabase for MemoryDatabase { #[cfg(test)] mod test { + use crate::run_tests_with_init; + use super::MemoryDatabase; fn get_tree() -> MemoryDatabase { MemoryDatabase::new() } - #[test] - fn test_script_pubkey() { - crate::database::test::test_script_pubkey(get_tree()); - } - - #[test] - fn test_batch_script_pubkey() { - crate::database::test::test_batch_script_pubkey(get_tree()); - } - - #[test] - fn test_iter_script_pubkey() { - crate::database::test::test_iter_script_pubkey(get_tree()); - } - - #[test] - fn test_del_script_pubkey() { - crate::database::test::test_del_script_pubkey(get_tree()); - } - - #[test] - fn test_utxo() { - crate::database::test::test_utxo(get_tree()); - } - - #[test] - fn test_raw_tx() { - crate::database::test::test_raw_tx(get_tree()); - } - - #[test] - fn test_tx() { - crate::database::test::test_tx(get_tree()); - } - - #[test] - fn test_last_index() { - crate::database::test::test_last_index(get_tree()); - } - - #[test] - fn test_sync_time() { - crate::database::test::test_sync_time(get_tree()); - } - - #[test] - fn test_iter_raw_txs() { - crate::database::test::test_iter_raw_txs(get_tree()); - } - - #[test] - fn test_del_path_from_script_pubkey() { - crate::database::test::test_del_path_from_script_pubkey(get_tree()); - } - - #[test] - fn test_iter_script_pubkeys() { - crate::database::test::test_iter_script_pubkeys(get_tree()); - } - - #[test] - fn test_del_utxo() { - crate::database::test::test_del_utxo(get_tree()); - } - - #[test] - fn test_del_raw_tx() { - crate::database::test::test_del_raw_tx(get_tree()); - } - - #[test] - fn test_del_tx() { - crate::database::test::test_del_tx(get_tree()); - } - - #[test] - fn test_del_last_index() { - crate::database::test::test_del_last_index(get_tree()); - } - - #[test] - fn test_check_descriptor_checksum() { - crate::database::test::test_check_descriptor_checksum(get_tree()); - } + run_tests_with_init![ + @init get_tree(), + @tests( + test_script_pubkey, + test_batch_script_pubkey, + test_iter_script_pubkey, + test_del_script_pubkey, + test_utxo, + test_raw_tx, + test_tx, + test_last_index, + test_sync_time, + test_iter_raw_txs, + test_del_path_from_script_pubkey, + test_iter_script_pubkeys, + test_del_utxo, + test_del_raw_tx, + test_del_tx, + test_del_last_index, + test_check_descriptor_checksum + ) + ]; } diff --git a/src/database/sqlite.rs b/src/database/sqlite.rs index a8061984f..ee37bd47d 100644 --- a/src/database/sqlite.rs +++ b/src/database/sqlite.rs @@ -997,7 +997,7 @@ pub fn migrate(conn: &mut Connection) -> Result<(), Error> { #[cfg(test)] pub mod test { - use crate::database::SqliteDatabase; + use crate::{database::SqliteDatabase, run_tests_with_init}; use std::time::{SystemTime, UNIX_EPOCH}; fn get_database() -> SqliteDatabase { @@ -1007,93 +1007,26 @@ pub mod test { SqliteDatabase::new(String::from(dir.to_str().unwrap())) } - #[test] - fn test_script_pubkey() { - crate::database::test::test_script_pubkey(get_database()); - } - - #[test] - fn test_batch_script_pubkey() { - crate::database::test::test_batch_script_pubkey(get_database()); - } - - #[test] - fn test_iter_script_pubkey() { - crate::database::test::test_iter_script_pubkey(get_database()); - } - - #[test] - fn test_del_script_pubkey() { - crate::database::test::test_del_script_pubkey(get_database()); - } - - #[test] - fn test_utxo() { - crate::database::test::test_utxo(get_database()); - } - - #[test] - fn test_raw_tx() { - crate::database::test::test_raw_tx(get_database()); - } - - #[test] - fn test_tx() { - crate::database::test::test_tx(get_database()); - } - - #[test] - fn test_last_index() { - crate::database::test::test_last_index(get_database()); - } - - #[test] - fn test_sync_time() { - crate::database::test::test_sync_time(get_database()); - } - - #[test] - fn test_txs() { - crate::database::test::test_list_transaction(get_database()); - } - - #[test] - fn test_iter_raw_txs() { - crate::database::test::test_iter_raw_txs(get_database()); - } - - #[test] - fn test_del_path_from_script_pubkey() { - crate::database::test::test_del_path_from_script_pubkey(get_database()); - } - - #[test] - fn test_iter_script_pubkeys() { - crate::database::test::test_iter_script_pubkeys(get_database()); - } - - #[test] - fn test_del_utxo() { - crate::database::test::test_del_utxo(get_database()); - } - - #[test] - fn test_del_raw_tx() { - crate::database::test::test_del_raw_tx(get_database()); - } - - #[test] - fn test_del_tx() { - crate::database::test::test_del_tx(get_database()); - } - - #[test] - fn test_del_last_index() { - crate::database::test::test_del_last_index(get_database()); - } - - #[test] - fn test_check_descriptor_checksum() { - crate::database::test::test_check_descriptor_checksum(get_database()); - } + run_tests_with_init![ + @init get_database(), + @tests( + test_script_pubkey, + test_batch_script_pubkey, + test_iter_script_pubkey, + test_del_script_pubkey, + test_utxo, + test_raw_tx, + test_tx, + test_last_index, + test_sync_time, + test_iter_raw_txs, + test_del_path_from_script_pubkey, + test_iter_script_pubkeys, + test_del_utxo, + test_del_raw_tx, + test_del_tx, + test_del_last_index, + test_check_descriptor_checksum + ) + ]; } diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index f06b5297c..7fa2c7e90 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -75,9 +75,10 @@ impl PsbtUtils for Psbt { mod test { use crate::bitcoin::TxIn; use crate::psbt::Psbt; + use crate::testutils::helpers::get_funded_wallet; + use crate::wallet::test::get_test_wpkh; use crate::wallet::AddressIndex; use crate::wallet::AddressIndex::New; - use crate::wallet::{get_funded_wallet, test::get_test_wpkh}; use crate::{psbt, FeeRate, SignOptions}; use std::str::FromStr; diff --git a/src/wallet/address_validator.rs b/src/wallet/address_validator.rs index eaac582ce..c921b11d0 100644 --- a/src/wallet/address_validator.rs +++ b/src/wallet/address_validator.rs @@ -116,8 +116,9 @@ mod test { use std::sync::Arc; use super::*; + use crate::testutils::helpers::get_funded_wallet; + use crate::wallet::test::get_test_wpkh; use crate::wallet::AddressIndex::New; - use crate::wallet::{get_funded_wallet, test::get_test_wpkh}; #[derive(Debug)] struct TestValidator; diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 6753f496e..1f53be12f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -18,7 +18,6 @@ use std::collections::HashMap; use std::collections::{BTreeMap, HashSet}; use std::fmt; use std::ops::{Deref, DerefMut}; -use std::str::FromStr; use std::sync::Arc; use bitcoin::secp256k1::Secp256k1; @@ -62,8 +61,7 @@ use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx}; use crate::blockchain::{GetHeight, NoopProgress, Progress, WalletSync}; -use crate::database::memory::MemoryDatabase; -use crate::database::{AnyDatabase, BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; +use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime}; use crate::descriptor::derived::AsDerived; use crate::descriptor::policy::BuildSatisfaction; use crate::descriptor::{ @@ -74,7 +72,6 @@ use crate::descriptor::{ use crate::error::Error; use crate::psbt::PsbtUtils; use crate::signer::SignerError; -use crate::testutils; use crate::types::*; use crate::wallet::coin_selection::Excess::{Change, NoChange}; @@ -1755,6 +1752,12 @@ where self.database.borrow() } + #[cfg(test)] + /// Return an mutable reference to the internal database + pub(crate) fn database_mut(&self) -> impl std::ops::DerefMut + '_ { + self.database.borrow_mut() + } + /// Sync the internal database with the blockchain #[maybe_async] pub fn sync( @@ -1881,12 +1884,14 @@ where #[cfg(test)] pub(crate) mod test { use bitcoin::{util::psbt, Network}; + use std::str::FromStr; - use crate::database::Database; + use crate::database::{AnyDatabase, Database, MemoryDatabase}; use crate::types::KeychainKind; use super::*; use crate::signer::{SignOptions, SignerError}; + use crate::testutils::helpers::{get_funded_wallet, populate_test_db}; use crate::wallet::AddressIndex::{LastUnused, New, Peek, Reset}; // The satisfaction size of a P2WPKH is 112 WU = @@ -2184,7 +2189,7 @@ pub(crate) mod test { }; // Add the transaction to our db, but do not sync the db. - crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, None); + populate_test_db(&mut *wallet.database_mut(), tx_meta, None, false); let addr = wallet.get_address(New).unwrap(); let mut builder = wallet.build_tx(); @@ -2391,7 +2396,7 @@ pub(crate) mod test { // Add the transaction to our db, but do not sync the db. Unsynced db // should trigger the default sequence value for a new transaction as 0xFFFFFFFF - crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, None); + populate_test_db(&mut *wallet.database_mut(), tx_meta, None, false); let addr = wallet.get_address(New).unwrap(); let mut builder = wallet.build_tx(); @@ -2829,10 +2834,11 @@ pub(crate) mod test { #[test] fn test_create_tx_add_utxo() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - let small_output_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let small_output_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -2858,10 +2864,11 @@ pub(crate) mod test { #[should_panic(expected = "InsufficientFunds")] fn test_create_tx_manually_selected_insufficient() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - let small_output_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let small_output_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -2905,7 +2912,7 @@ pub(crate) mod test { // Add the transaction to our db, but do not sync the db. Unsynced db // should trigger the default sequence value for a new transaction as 0xFFFFFFFF - crate::populate_test_db!(wallet.database.borrow_mut(), tx_meta, None); + populate_test_db(&mut *wallet.database_mut(), tx_meta, None, false); let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); let root_id = external_policy.id; @@ -3544,10 +3551,11 @@ pub(crate) mod test { fn test_bump_fee_drain_wallet() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); // receive an extra tx so that our wallet has two utxos. - let incoming_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let incoming_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let outpoint = OutPoint { txid: incoming_txid, @@ -3601,10 +3609,11 @@ pub(crate) mod test { // told the wallet it's not allowed to add more inputs AND it can't reduce the value of the // existing output. In other words, bump_fee + manually_selected_only is always an error // unless you've also set "allow_shrinking" OR there is a change output. - let incoming_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let incoming_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let outpoint = OutPoint { txid: incoming_txid, @@ -3647,10 +3656,11 @@ pub(crate) mod test { #[test] fn test_bump_fee_add_input() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -3710,10 +3720,11 @@ pub(crate) mod test { #[test] fn test_bump_fee_absolute_add_input() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -3773,10 +3784,11 @@ pub(crate) mod test { #[test] fn test_bump_fee_no_change_add_input_and_change() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - let incoming_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let incoming_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); // initially make a tx without change by using `drain_to` @@ -3850,10 +3862,11 @@ pub(crate) mod test { #[test] fn test_bump_fee_add_input_change_dust() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -3927,10 +3940,11 @@ pub(crate) mod test { #[test] fn test_bump_fee_force_add_input() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - let incoming_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let incoming_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -3998,10 +4012,11 @@ pub(crate) mod test { #[test] fn test_bump_fee_absolute_force_add_input() { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); - let incoming_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let incoming_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); @@ -4085,10 +4100,11 @@ pub(crate) mod test { let (psbt, mut original_details) = builder.finish().unwrap(); // Now we receive one transaction with 0 confirmations. We won't be able to use that for // fee bumping, as it's still unconfirmed! - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database.borrow_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 0)), Some(100), + false, ); let mut tx = psbt.extract_tx(); let txid = tx.txid(); @@ -4123,10 +4139,11 @@ pub(crate) mod test { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); // We receive a tx with 0 confirmations, which will be used as an input // in the drain tx. - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 0)), Some(100), + false, ); let mut builder = wallet.build_tx(); builder @@ -4172,10 +4189,11 @@ pub(crate) mod test { let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh()); let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap(); let fee_rate = FeeRate::from_sat_per_vb(2.01); - let incoming_txid = crate::populate_test_db!( - wallet.database.borrow_mut(), + let incoming_txid = populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 8859 ) (@confirmations 1)), Some(100), + false, ); let mut builder = wallet.build_tx(); @@ -4492,10 +4510,11 @@ pub(crate) mod test { ); // use the above address - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(100), + false, ); assert_eq!( @@ -5329,23 +5348,12 @@ pub(crate) mod test { let confirmation_time = 5; - crate::populate_test_db!( - wallet.database.borrow_mut(), + populate_test_db( + &mut *wallet.database_mut(), testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)), Some(confirmation_time), - (@coinbase true) + true, ); - let sync_time = SyncTime { - block_time: BlockTime { - height: confirmation_time, - timestamp: 0, - }, - }; - wallet - .database - .borrow_mut() - .set_sync_time(sync_time) - .unwrap(); let not_yet_mature_time = confirmation_time + COINBASE_MATURITY - 1; let maturity_time = confirmation_time + COINBASE_MATURITY; From 037d84d1ea1cfd8e33bba8196349ca5ae76eed4b Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Wed, 31 Aug 2022 18:24:35 +0530 Subject: [PATCH 3/5] Change Blockchain tests from macro to function Similar to the database `run_test_with_init` another `make_blockchain_test` macro is added, which will run the blockchain tests given a initilizer function. These test functions are taken out of the macro and are placed as their own public functions. A doc comment explaining how to use the tests externally from bdk is added before the `blockchain_tests::test` module. Co-authored-by: SanthoshAnguluri Co-authored-by: saikishore222 --- src/testutils/blockchain_tests.rs | 2411 ++++++++++++++++++----------- 1 file changed, 1490 insertions(+), 921 deletions(-) diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index a3d7c2b17..a5a9921af 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -4,7 +4,7 @@ use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::sha256d; use bitcoin::{Address, Amount, Script, Transaction, Txid, Witness}; pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType; -pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi}; +pub use bitcoincore_rpc::{Client as RpcClient, RpcApi}; use core::str::FromStr; use electrsd::bitcoind::BitcoinD; use electrsd::{bitcoind, ElectrsD}; @@ -358,1112 +358,1681 @@ where } } -/// This macro runs blockchain tests against a `Blockchain` implementation. It requires access to a -/// Bitcoin core wallet via RPC. At the moment you have to dig into the code yourself and look at -/// the setup required to run the tests yourself. -#[macro_export] -macro_rules! bdk_blockchain_tests { - ( - fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => { - #[cfg(test)] - mod bdk_blockchain_tests { - use $crate::bitcoin::{Transaction, Network}; - use $crate::testutils::blockchain_tests::TestClient; - use $crate::blockchain::Blockchain; - use $crate::database::MemoryDatabase; - use $crate::types::KeychainKind; - use $crate::wallet::AddressIndex; - use $crate::{Wallet, FeeRate, SyncOptions}; - use $crate::testutils; - - use super::*; - - #[allow(unused_variables)] - fn get_blockchain(test_client: &TestClient) -> $blockchain { - $( let $test_client = test_client; )? - $block - } +#[cfg(test)] +/// This test module is intended to be used externally from bdk library, if the user +/// wants to test their custom [`Blockchain`] implementation. +/// +/// Test functions can be used using the `make_blockchain_tests` macro as below +/// ``` +/// use bdk::make_blockchain_tests; +/// +/// pub fn init_blockchain(test_client: &TestClient) -> { +/// .. // Define your blockchain from a `TestClient` +/// } +/// +/// make_blockchain_tests![ +/// init_blockchain, +/// tests( +/// test_sync_simple, +/// test_sync_stop_gap_20, +/// test_sync_before_and_after_receive, +/// test_sync_multiple_outputs_same_tx, +/// ... // List of test case names you wanna run +/// ) +/// ] +/// } +/// ``` +/// +/// This will replicate the bdk::blockchain test cases for other custom Blockchain backends. +pub mod test { + use super::*; + use crate::bitcoin::{Network, Transaction}; + use crate::blockchain::Blockchain; + use crate::database::MemoryDatabase; + use crate::testutils::blockchain_tests::TestClient; + use crate::types::KeychainKind; + use crate::wallet::AddressIndex; + use crate::{testutils, wallet}; + use crate::{FeeRate, SyncOptions, Wallet}; + + fn get_wallet_from_descriptors( + descriptors: &(String, Option), + ) -> Wallet { + Wallet::new( + &descriptors.0.to_string(), + descriptors.1.as_ref(), + Network::Regtest, + MemoryDatabase::new(), + ) + .unwrap() + } - fn get_wallet_from_descriptors(descriptors: &(String, Option)) -> Wallet { - Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap() - } + #[allow(dead_code)] + enum WalletType { + WpkhSingleSig, + TaprootKeySpend, + TaprootScriptSpend, + TaprootScriptSpend2, + TaprootScriptSpend3, + } - #[allow(dead_code)] - enum WalletType { - WpkhSingleSig, - TaprootKeySpend, - TaprootScriptSpend, - TaprootScriptSpend2, - TaprootScriptSpend3, - } + fn init_wallet( + ty: WalletType, + init_blockchain: &dyn Fn(&TestClient) -> B, + ) -> ( + Wallet, + B, + (String, Option), + TestClient, + ) + where + B: Blockchain, + { + let _ = env_logger::try_init(); + + let descriptors = match ty { + WalletType::WpkhSingleSig => testutils! { + @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) + }, + WalletType::TaprootKeySpend => testutils! { + @descriptors ( "tr(Alice)" ) ( "tr(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) + }, + WalletType::TaprootScriptSpend => testutils! { + @descriptors ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( @keys ( "Key" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Script" => (@generate_xprv "/0/*", "/1/*") ) ) + }, + WalletType::TaprootScriptSpend2 => testutils! { + @descriptors ( "tr(Alice,pk(Bob))" ) ( "tr(Alice,pk(Bob))" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*") ) ) + }, + WalletType::TaprootScriptSpend3 => testutils! { + @descriptors ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*"), "Carol" => (@generate_xprv "/0/*", "/1/*") ) ) + }, + }; - fn init_wallet(ty: WalletType) -> (Wallet, $blockchain, (String, Option), TestClient) { - let _ = env_logger::try_init(); - - let descriptors = match ty { - WalletType::WpkhSingleSig => testutils! { - @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) - }, - WalletType::TaprootKeySpend => testutils! { - @descriptors ( "tr(Alice)" ) ( "tr(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) - }, - WalletType::TaprootScriptSpend => testutils! { - @descriptors ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( @keys ( "Key" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Script" => (@generate_xprv "/0/*", "/1/*") ) ) - }, - WalletType::TaprootScriptSpend2 => testutils! { - @descriptors ( "tr(Alice,pk(Bob))" ) ( "tr(Alice,pk(Bob))" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*") ) ) - }, - WalletType::TaprootScriptSpend3 => testutils! { - @descriptors ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*"), "Carol" => (@generate_xprv "/0/*", "/1/*") ) ) - }, - }; - - let test_client = TestClient::default(); - let blockchain = get_blockchain(&test_client); - let wallet = get_wallet_from_descriptors(&descriptors); - - // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool - #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))] - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - - (wallet, blockchain, descriptors, test_client) - } + let test_client = TestClient::default(); + let blockchain = init_blockchain(&test_client); + let wallet = get_wallet_from_descriptors(&descriptors); - fn init_single_sig() -> (Wallet, $blockchain, (String, Option), TestClient) { - init_wallet(WalletType::WpkhSingleSig) - } + // rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool + #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))] + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - #[test] - fn test_sync_simple() { - use std::ops::Deref; - use crate::database::Database; + (wallet, blockchain, descriptors, test_client) + } - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + fn init_single_sig( + init_blockchain: &dyn Fn(&TestClient) -> B, + ) -> ( + Wallet, + B, + (String, Option), + TestClient, + ) + where + B: Blockchain, + { + init_wallet(WalletType::WpkhSingleSig, init_blockchain) + } - let tx = testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }; - println!("{:?}", tx); - let txid = test_client.receive(tx); + pub fn test_sync_simple(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + use crate::database::Database; - // the RPC blockchain needs to call `sync()` during initialization to import the - // addresses (see `init_single_sig()`), so we skip this assertion - #[cfg(not(any(feature = "test-rpc", feature = "test-rpc-legacy")))] - assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none"); + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated"); + let tx = testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }; + println!("{:?}", tx); + let txid = test_client.receive(tx); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); - assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind"); + // the RPC blockchain needs to call `sync()` during initialization to import the + // addresses (see `init_single_sig()`), so we skip this assertion + #[cfg(not(any(feature = "test-rpc", feature = "test-rpc-legacy")))] + assert!( + wallet.database().deref().get_sync_time().unwrap().is_none(), + "initial sync_time not none" + ); - let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; - assert_eq!(list_tx_item.txid, txid, "incorrect txid"); - assert_eq!(list_tx_item.received, 50_000, "incorrect received"); - assert_eq!(list_tx_item.sent, 0, "incorrect sent"); - assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation time"); - } + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert!( + wallet.database().deref().get_sync_time().unwrap().is_some(), + "sync_time hasn't been updated" + ); - #[test] - fn test_sync_stop_gap_20() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_unspent().unwrap()[0].keychain, + KeychainKind::External, + "incorrect keychain kind" + ); - test_client.receive(testutils! { - @tx ( (@external descriptors, 5) => 50_000 ) - }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 25) => 50_000 ) - }); + let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; + assert_eq!(list_tx_item.txid, txid, "incorrect txid"); + assert_eq!(list_tx_item.received, 50_000, "incorrect received"); + assert_eq!(list_tx_item.sent, 0, "incorrect sent"); + assert_eq!( + list_tx_item.confirmation_time, None, + "incorrect confirmation time" + ); + } - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + pub fn test_sync_stop_gap_20(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 100_000, "incorrect balance"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); - } + test_client.receive(testutils! { + @tx ( (@external descriptors, 5) => 50_000 ) + }); + test_client.receive(testutils! { + @tx ( (@external descriptors, 25) => 50_000 ) + }); - #[test] - fn test_sync_before_and_after_receive() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_total(), 0); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 100_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 2, + "incorrect number of txs" + ); + } - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + pub fn test_sync_before_and_after_receive(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(wallet.get_balance().unwrap().get_total(), 0); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 1, + "incorrect number of txs" + ); + } - assert_eq!(wallet.get_balance().unwrap().confirmed, 100_000, "incorrect balance"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); - } + pub fn test_sync_multiple_outputs_same_tx(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - #[test] - fn test_sync_multiple_outputs_same_tx() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + let txid = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 ) + }); - let txid = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 105_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 1, + "incorrect number of txs" + ); + assert_eq!( + wallet.list_unspent().unwrap().len(), + 3, + "incorrect number of unspents" + ); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 105_000, "incorrect balance"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); - assert_eq!(wallet.list_unspent().unwrap().len(), 3, "incorrect number of unspents"); + let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; + assert_eq!(list_tx_item.txid, txid, "incorrect txid"); + assert_eq!(list_tx_item.received, 105_000, "incorrect received"); + assert_eq!(list_tx_item.sent, 0, "incorrect sent"); + assert_eq!( + list_tx_item.confirmation_time, None, + "incorrect confirmation_time" + ); + } - let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; - assert_eq!(list_tx_item.txid, txid, "incorrect txid"); - assert_eq!(list_tx_item.received, 105_000, "incorrect received"); - assert_eq!(list_tx_item.sent, 0, "incorrect sent"); - assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation_time"); - } + pub fn test_sync_receive_multi(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - #[test] - fn test_sync_receive_multi() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); + test_client.receive(testutils! { + @tx ( (@external descriptors, 5) => 25_000 ) + }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 5) => 25_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 75_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 2, + "incorrect number of txs" + ); + assert_eq!( + wallet.list_unspent().unwrap().len(), + 2, + "incorrect number of unspent" + ); + } - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 75_000, "incorrect balance"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs"); - assert_eq!(wallet.list_unspent().unwrap().len(), 2, "incorrect number of unspent"); - } + pub fn test_sync_address_reuse(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - #[test] - fn test_sync_address_reuse() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 25_000 ) + }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 25_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 75_000, "incorrect balance"); - } + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 75_000, + "incorrect balance" + ); + } - #[test] - fn test_sync_receive_rbf_replaced() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + pub fn test_sync_receive_rbf_replaced(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); - let txid = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true ) - }); + let txid = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true ) + }); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); - assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent"); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 1, + "incorrect number of txs" + ); + assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent"); + + let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; + assert_eq!(list_tx_item.txid, txid, "incorrect txid"); + assert_eq!(list_tx_item.received, 50_000, "incorrect received"); + assert_eq!(list_tx_item.sent, 0, "incorrect sent"); + assert_eq!( + list_tx_item.confirmation_time, None, + "incorrect confirmation_time" + ); - let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; - assert_eq!(list_tx_item.txid, txid, "incorrect txid"); - assert_eq!(list_tx_item.received, 50_000, "incorrect received"); - assert_eq!(list_tx_item.sent, 0, "incorrect sent"); - assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation_time"); + let new_txid = test_client.bump_fee(&txid); - let new_txid = test_client.bump_fee(&txid); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance after bump" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 1, + "incorrect number of txs after bump" + ); + assert_eq!( + wallet.list_unspent().unwrap().len(), + 1, + "incorrect unspent after bump" + ); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance after bump"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump"); - assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect unspent after bump"); + let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; + assert_eq!(list_tx_item.txid, new_txid, "incorrect txid after bump"); + assert_eq!( + list_tx_item.received, 50_000, + "incorrect received after bump" + ); + assert_eq!(list_tx_item.sent, 0, "incorrect sent after bump"); + assert_eq!( + list_tx_item.confirmation_time, None, + "incorrect height after bump" + ); + } - let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; - assert_eq!(list_tx_item.txid, new_txid, "incorrect txid after bump"); - assert_eq!(list_tx_item.received, 50_000, "incorrect received after bump"); - assert_eq!(list_tx_item.sent, 0, "incorrect sent after bump"); - assert_eq!(list_tx_item.confirmation_time, None, "incorrect height after bump"); - } + // FIXME: I would like this to be cfg_attr(not(feature = "test-esplora"), ignore) but it + // doesn't work for some reason. + #[cfg(not(feature = "test-esplora"))] + pub fn test_sync_reorg_block(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + + let txid = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true ) + }); - // FIXME: I would like this to be cfg_attr(not(feature = "test-esplora"), ignore) but it - // doesn't work for some reason. - #[cfg(not(feature = "esplora"))] - #[test] - fn test_sync_reorg_block() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - let txid = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true ) - }); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000, + "incorrect balance" + ); + assert_eq!( + wallet.list_transactions(false).unwrap().len(), + 1, + "incorrect number of txs" + ); + assert_eq!( + wallet.list_unspent().unwrap().len(), + 1, + "incorrect number of unspents" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance"); - assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs"); - assert_eq!(wallet.list_unspent().unwrap().len(), 1, "incorrect number of unspents"); + let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; + assert_eq!(list_tx_item.txid, txid, "incorrect txid"); + assert!( + list_tx_item.confirmation_time.is_some(), + "incorrect confirmation_time" + ); - let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; - assert_eq!(list_tx_item.txid, txid, "incorrect txid"); - assert!(list_tx_item.confirmation_time.is_some(), "incorrect confirmation_time"); + // Invalidate 1 block + test_client.invalidate(1); - // Invalidate 1 block - test_client.invalidate(1); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance after invalidate" + ); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance after invalidate"); + let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; + assert_eq!(list_tx_item.txid, txid, "incorrect txid after invalidate"); + assert_eq!( + list_tx_item.confirmation_time, None, + "incorrect confirmation time after invalidate" + ); + } - let list_tx_item = &wallet.list_transactions(false).unwrap()[0]; - assert_eq!(list_tx_item.txid, txid, "incorrect txid after invalidate"); - assert_eq!(list_tx_item.confirmation_time, None, "incorrect confirmation time after invalidate"); - } + pub fn test_sync_after_send(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + println!("{}", descriptors.0); + let node_addr = test_client.get_node_address(None); - #[test] - fn test_sync_after_send() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - println!("{}", descriptors.0); - let node_addr = test_client.get_node_address(None); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 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)); + blockchain.broadcast(&tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().trusted_pending, + details.received, + "incorrect balance after send" + ); - 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)); - blockchain.broadcast(&tx).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().trusted_pending, 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" + ); + } - test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + // Syncing wallet should not result in wallet address index to decrement. + // This is critical as we should always ensure to not reuse addresses. + pub fn test_sync_address_index_should_not_decrement( + init_blockchain: &dyn Fn(&TestClient) -> B, + ) where + B: Blockchain, + { + let (wallet, blockchain, _descriptors, mut test_client) = init_single_sig(init_blockchain); - assert_eq!(wallet.get_balance().unwrap().confirmed, details.received, "incorrect balance after send"); + const ADDRS_TO_FUND: u32 = 7; + const ADDRS_TO_IGNORE: u32 = 11; - 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"); - } + let mut first_addr_index: u32 = 0; - // Syncing wallet should not result in wallet address index to decrement. - // This is critical as we should always ensure to not reuse addresses. - #[test] - fn test_sync_address_index_should_not_decrement() { - let (wallet, blockchain, _descriptors, mut test_client) = init_single_sig(); + (0..ADDRS_TO_FUND + ADDRS_TO_IGNORE).for_each(|i| { + let new_addr = wallet.get_address(AddressIndex::New).unwrap(); - const ADDRS_TO_FUND: u32 = 7; - const ADDRS_TO_IGNORE: u32 = 11; + if i == 0 { + first_addr_index = new_addr.index; + } + assert_eq!( + new_addr.index, + i + first_addr_index, + "unexpected new address index (before sync)" + ); - let mut first_addr_index: u32 = 0; + if i < ADDRS_TO_FUND { + test_client.receive(testutils! { + @tx ((@addr new_addr.address) => 50_000) + }); + } + }); - (0..ADDRS_TO_FUND + ADDRS_TO_IGNORE).for_each(|i| { - let new_addr = wallet.get_address(AddressIndex::New).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - if i == 0 { - first_addr_index = new_addr.index; - } - assert_eq!(new_addr.index, i+first_addr_index, "unexpected new address index (before sync)"); + let new_addr = wallet.get_address(AddressIndex::New).unwrap(); + assert_eq!( + new_addr.index, + ADDRS_TO_FUND + ADDRS_TO_IGNORE + first_addr_index, + "unexpected new address index (after sync)" + ); + } - if i < ADDRS_TO_FUND { - test_client.receive(testutils! { - @tx ((@addr new_addr.address) => 50_000) - }); - } - }); + // Even if user does not explicitly grab new addresses, the address index should + // increment after sync (if wallet has a balance). + pub fn test_sync_address_index_should_increment(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + + const START_FUND: u32 = 4; + const END_FUND: u32 = 20; + + // "secretly" fund wallet via given range + (START_FUND..END_FUND).for_each(|addr_index| { + test_client.receive(testutils! { + @tx ((@external descriptors, addr_index) => 50_000) + }); + }); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - let new_addr = wallet.get_address(AddressIndex::New).unwrap(); - assert_eq!(new_addr.index, ADDRS_TO_FUND+ADDRS_TO_IGNORE+first_addr_index, "unexpected new address index (after sync)"); - } + let address = wallet.get_address(AddressIndex::New).unwrap(); + assert_eq!( + address.index, END_FUND, + "unexpected new address index (after sync)" + ); + } - // Even if user does not explicitly grab new addresses, the address index should - // increment after sync (if wallet has a balance). - #[test] - fn test_sync_address_index_should_increment() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); + /// Send two conflicting transactions to the same address twice in a row. + /// The coins should only be received once! + pub fn test_sync_double_receive(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let receiver_wallet = get_wallet_from_descriptors(&( + "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), + None, + )); + // need to sync so rpc can start watching + receiver_wallet + .sync(&blockchain, SyncOptions::default()) + .unwrap(); - const START_FUND: u32 = 4; - const END_FUND: u32 = 20; + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) + }); - // "secretly" fund wallet via given range - (START_FUND..END_FUND).for_each(|addr_index| { - test_client.receive(testutils! { - @tx ((@external descriptors, addr_index) => 50_000) - }); - }); + wallet + .sync(&blockchain, SyncOptions::default()) + .expect("sync"); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 75_000, + "incorrect balance" + ); + let target_addr = receiver_wallet + .get_address(wallet::AddressIndex::New) + .unwrap() + .address; + + let tx1 = { + let mut builder = wallet.build_tx(); + builder + .add_recipient(target_addr.script_pubkey(), 49_000) + .enable_rbf(); + let (mut psbt, _details) = builder.finish().expect("building first tx"); + let finalized = wallet + .sign(&mut psbt, Default::default()) + .expect("signing first tx"); + assert!(finalized, "Cannot finalize transaction"); + psbt.extract_tx() + }; - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let tx2 = { + let mut builder = wallet.build_tx(); + builder + .add_recipient(target_addr.script_pubkey(), 49_000) + .enable_rbf() + .fee_rate(FeeRate::from_sat_per_vb(5.0)); + let (mut psbt, _details) = builder.finish().expect("building replacement tx"); + let finalized = wallet + .sign(&mut psbt, Default::default()) + .expect("signing replacement tx"); + assert!(finalized, "Cannot finalize transaction"); + psbt.extract_tx() + }; - let address = wallet.get_address(AddressIndex::New).unwrap(); - assert_eq!(address.index, END_FUND, "unexpected new address index (after sync)"); - } + blockchain.broadcast(&tx1).expect("broadcasting first"); + blockchain + .broadcast(&tx2) + .expect("broadcasting replacement"); + + receiver_wallet + .sync(&blockchain, SyncOptions::default()) + .expect("syncing receiver"); + assert_eq!( + receiver_wallet.get_balance().unwrap().untrusted_pending, + 49_000, + "should have received coins once and only once" + ); + } - /// Send two conflicting transactions to the same address twice in a row. - /// The coins should only be received once! - #[test] - fn test_sync_double_receive() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None)); - // need to sync so rpc can start watching - receiver_wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + pub fn test_sync_many_sends_to_a_single_address(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + for _ in 0..4 { + // split this up into multiple blocks so rpc doesn't get angry + for _ in 0..20 { test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) + @tx ( (@external descriptors, 0) => 1_000 ) }); - - wallet.sync(&blockchain, SyncOptions::default()).expect("sync"); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance"); - let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; - - let tx1 = { - let mut builder = wallet.build_tx(); - builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf(); - let (mut psbt, _details) = builder.finish().expect("building first tx"); - let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing first tx"); - assert!(finalized, "Cannot finalize transaction"); - psbt.extract_tx() - }; - - let tx2 = { - let mut builder = wallet.build_tx(); - builder.add_recipient(target_addr.script_pubkey(), 49_000).enable_rbf().fee_rate(FeeRate::from_sat_per_vb(5.0)); - let (mut psbt, _details) = builder.finish().expect("building replacement tx"); - let finalized = wallet.sign(&mut psbt, Default::default()).expect("signing replacement tx"); - assert!(finalized, "Cannot finalize transaction"); - psbt.extract_tx() - }; - - blockchain.broadcast(&tx1).expect("broadcasting first"); - blockchain.broadcast(&tx2).expect("broadcasting replacement"); - receiver_wallet.sync(&blockchain, SyncOptions::default()).expect("syncing receiver"); - assert_eq!(receiver_wallet.get_balance().expect("balance").untrusted_pending, 49_000, "should have received coins once and only once"); } + test_client.generate(1, None); + } - #[test] - fn test_sync_many_sends_to_a_single_address() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - - for _ in 0..4 { - // split this up into multiple blocks so rpc doesn't get angry - for _ in 0..20 { - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 1_000 ) - }); - } - test_client.generate(1, None); - } + // add some to the mempool as well. + for _ in 0..20 { + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 1_000 ) + }); + } - // add some to the mempool as well. - for _ in 0..20 { - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 1_000 ) - }); - } + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let balance = wallet.get_balance().unwrap(); + assert_eq!(balance.untrusted_pending + balance.get_spendable(), 100_000); + } - let balance = wallet.get_balance().unwrap(); - assert_eq!(balance.untrusted_pending + balance.get_spendable(), 100_000); - } + pub fn test_update_confirmation_time_after_generate( + init_blockchain: &dyn Fn(&TestClient) -> B, + ) where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + println!("{}", descriptors.0); + let node_addr = test_client.get_node_address(None); + + let received_txid = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - #[test] - fn test_update_confirmation_time_after_generate() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - println!("{}", descriptors.0); - let node_addr = test_client.get_node_address(None); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); - let received_txid = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + let tx_map = wallet + .list_transactions(false) + .unwrap() + .into_iter() + .map(|tx| (tx.txid, tx)) + .collect::>(); + let details = tx_map.get(&received_txid).unwrap(); + assert!(details.confirmation_time.is_none()); + + test_client.generate(1, Some(node_addr)); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); + let tx_map = wallet + .list_transactions(false) + .unwrap() + .into_iter() + .map(|tx| (tx.txid, tx)) + .collect::>(); + let details = tx_map.get(&received_txid).unwrap(); + assert!(details.confirmation_time.is_some()); + } - let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); - let details = tx_map.get(&received_txid).unwrap(); - assert!(details.confirmation_time.is_none()); + pub fn test_sync_outgoing_from_scratch(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); + let received_txid = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); - let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); - let details = tx_map.get(&received_txid).unwrap(); - assert!(details.confirmation_time.is_some()); + 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 sent_tx = psbt.extract_tx(); + blockchain.broadcast(&sent_tx).unwrap(); - #[test] - fn test_sync_outgoing_from_scratch() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); - let received_txid = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + details.received, + "incorrect balance after receive" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); + // empty wallet + let wallet = get_wallet_from_descriptors(&descriptors); - let mut builder = wallet.build_tx(); - builder.add_recipient(node_addr.script_pubkey(), 25_000); - let (mut psbt, details) = builder.finish().unwrap(); + #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti + test_client.generate(1, Some(node_addr)); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - let sent_tx = psbt.extract_tx(); - blockchain.broadcast(&sent_tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let tx_map = wallet + .list_transactions(false) + .unwrap() + .into_iter() + .map(|tx| (tx.txid, tx)) + .collect::>(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect balance after receive"); + let received = tx_map.get(&received_txid).unwrap(); + assert_eq!( + received.received, 50_000, + "incorrect received from receiver" + ); + assert_eq!(received.sent, 0, "incorrect sent from receiver"); - // empty wallet - let wallet = get_wallet_from_descriptors(&descriptors); + let sent = tx_map.get(&sent_tx.txid()).unwrap(); + assert_eq!( + sent.received, details.received, + "incorrect received from sender" + ); + assert_eq!(sent.sent, details.sent, "incorrect sent from sender"); + assert_eq!( + sent.fee.unwrap_or(0), + details.fee.unwrap_or(0), + "incorrect fees from sender" + ); + } - #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti - test_client.generate(1, Some(node_addr)); + pub fn test_sync_long_change_chain(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - let received = tx_map.get(&received_txid).unwrap(); - assert_eq!(received.received, 50_000, "incorrect received from receiver"); - assert_eq!(received.sent, 0, "incorrect sent from receiver"); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); - let sent = tx_map.get(&sent_tx.txid()).unwrap(); - assert_eq!(sent.received, details.received, "incorrect received from sender"); - assert_eq!(sent.sent, details.sent, "incorrect sent from sender"); - assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender"); - } + let mut total_sent = 0; + for _ in 0..5 { + let mut builder = wallet.build_tx(); + builder.add_recipient(node_addr.script_pubkey(), 5_000); + let (mut psbt, details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); - #[test] - fn test_sync_long_change_chain() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + total_sent += 5_000 + details.fee.unwrap_or(0); + } - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000 - total_sent, + "incorrect balance after chain" + ); - let mut total_sent = 0; - for _ in 0..5 { - let mut builder = wallet.build_tx(); - builder.add_recipient(node_addr.script_pubkey(), 5_000); - let (mut psbt, details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&psbt.extract_tx()).unwrap(); + // empty wallet - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let wallet = get_wallet_from_descriptors(&descriptors); - total_sent += 5_000 + details.fee.unwrap_or(0); - } + #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti + test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - total_sent, "incorrect balance after chain"); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000 - total_sent, + "incorrect balance empty wallet" + ); + } - // empty wallet + pub fn test_sync_bump_fee_basic(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); - let wallet = get_wallet_from_descriptors(&descriptors); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) + }); - #[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti - test_client.generate(1, Some(node_addr)); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000, + "incorrect balance" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - total_sent, "incorrect balance empty wallet"); + let mut builder = wallet.build_tx(); + builder + .add_recipient(node_addr.script_pubkey().clone(), 5_000) + .enable_rbf(); + let (mut psbt, details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000 - details.fee.unwrap_or(0) - 5_000, + "incorrect balance from fees" + ); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + details.received, + "incorrect balance from received" + ); - } + let mut builder = wallet.build_fee_bump(details.txid).unwrap(); + builder.fee_rate(FeeRate::from_sat_per_vb(2.1)); + let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx"); + let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000 - new_details.fee.unwrap_or(0) - 5_000, + "incorrect balance from fees after bump" + ); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + new_details.received, + "incorrect balance from received after bump" + ); - #[test] - fn test_sync_bump_fee_basic() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + assert!( + new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), + "incorrect fees" + ); + } - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) - }); + pub fn test_sync_bump_fee_remove_change(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance"); - - let mut builder = wallet.build_tx(); - builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf(); - let (mut psbt, details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees"); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect balance from received"); - - let mut builder = wallet.build_fee_bump(details.txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(2.1)); - let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx"); - let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump"); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), new_details.received, "incorrect balance from received after bump"); - - assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees"); - } + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) + }); - #[test] - fn test_sync_bump_fee_remove_change() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000, + "incorrect balance" + ); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1) - }); + let mut builder = wallet.build_tx(); + builder + .add_recipient(node_addr.script_pubkey().clone(), 49_000) + .enable_rbf(); + let (mut psbt, details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 1_000 - details.fee.unwrap_or(0), + "incorrect balance after send" + ); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + details.received, + "incorrect received after send" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000, "incorrect balance"); - - let mut builder = wallet.build_tx(); - builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); - let (mut psbt, details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send"); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), details.received, "incorrect received after send"); - - let mut builder = wallet.build_fee_bump(details.txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(5.1)); - let (mut new_psbt, new_details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 0, "incorrect balance after change removal"); - assert_eq!(new_details.received, 0, "incorrect received after change removal"); - - assert!(new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), "incorrect fees"); - } + let mut builder = wallet.build_fee_bump(details.txid).unwrap(); + builder.fee_rate(FeeRate::from_sat_per_vb(5.1)); + let (mut new_psbt, new_details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 0, + "incorrect balance after change removal" + ); + assert_eq!( + new_details.received, 0, + "incorrect received after change removal" + ); - #[test] - fn test_sync_bump_fee_add_input_simple() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + assert!( + new_details.fee.unwrap_or(0) > details.fee.unwrap_or(0), + "incorrect fees" + ); + } - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) - }); + pub fn test_sync_bump_fee_add_input_simple(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance"); - - let mut builder = wallet.build_tx(); - builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); - let (mut psbt, details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send"); - assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send"); - - let mut builder = wallet.build_fee_bump(details.txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(10.0)); - let (mut new_psbt, new_details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(new_details.sent, 75_000, "incorrect sent"); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), new_details.received, "incorrect balance after add input"); - } + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) + }); - #[test] - fn test_sync_bump_fee_add_input_no_change() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 75_000, + "incorrect balance" + ); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) - }); + let mut builder = wallet.build_tx(); + builder + .add_recipient(node_addr.script_pubkey().clone(), 49_000) + .enable_rbf(); + let (mut psbt, details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 26_000 - details.fee.unwrap_or(0), + "incorrect balance after send" + ); + assert_eq!( + details.received, + 1_000 - details.fee.unwrap_or(0), + "incorrect received after send" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 75_000, "incorrect balance"); - - let mut builder = wallet.build_tx(); - builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); - let (mut psbt, details) = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send"); - assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send"); - - let mut builder = wallet.build_fee_bump(details.txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(123.0)); - let (mut new_psbt, new_details) = builder.finish().unwrap(); - println!("{:#?}", new_details); - - let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); - assert!(finalized, "Cannot finalize transaction"); - blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(new_details.sent, 75_000, "incorrect sent"); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 0, "incorrect balance after add input"); - assert_eq!(new_details.received, 0, "incorrect received after add input"); - } + let mut builder = wallet.build_fee_bump(details.txid).unwrap(); + builder.fee_rate(FeeRate::from_sat_per_vb(10.0)); + let (mut new_psbt, new_details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(new_details.sent, 75_000, "incorrect sent"); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + new_details.received, + "incorrect balance after add input" + ); + } + pub fn test_sync_bump_fee_add_input_no_change(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); - #[test] - fn test_add_data() { - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); - let _ = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1) + }); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); - - let mut builder = wallet.build_tx(); - let data = [42u8;80]; - builder.add_data(&data); - 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(); - let serialized_tx = bitcoin::consensus::encode::serialize(&tx); - assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction"); - blockchain.broadcast(&tx).unwrap(); - test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send"); - - let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); - let _ = tx_map.get(&tx.txid()).unwrap(); - } + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 75_000, + "incorrect balance" + ); - #[test] - fn test_sync_receive_coinbase() { - let (wallet, blockchain, _, mut test_client) = init_single_sig(); + let mut builder = wallet.build_tx(); + builder + .add_recipient(node_addr.script_pubkey().clone(), 49_000) + .enable_rbf(); + let (mut psbt, details) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 26_000 - details.fee.unwrap_or(0), + "incorrect balance after send" + ); + assert_eq!( + details.received, + 1_000 - details.fee.unwrap_or(0), + "incorrect received after send" + ); - let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address; - println!("wallet addr: {}", wallet_addr); + let mut builder = wallet.build_fee_bump(details.txid).unwrap(); + builder.fee_rate(FeeRate::from_sat_per_vb(123.0)); + let (mut new_psbt, new_details) = builder.finish().unwrap(); + println!("{:#?}", new_details); + + let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + blockchain.broadcast(&new_psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(new_details.sent, 75_000, "incorrect sent"); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 0, + "incorrect balance after add input" + ); + assert_eq!( + new_details.received, 0, + "incorrect received after add input" + ); + } - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().immature, 0, "incorrect balance"); + pub fn test_add_data(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - test_client.generate(1, Some(wallet_addr)); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "incorrect balance" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let mut builder = wallet.build_tx(); + let data = [42u8; 80]; + builder.add_data(&data); + let (mut psbt, details) = builder.finish().unwrap(); - assert!(wallet.get_balance().unwrap().immature > 0, "incorrect balance after receiving coinbase"); + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + let tx = psbt.extract_tx(); + let serialized_tx = bitcoin::consensus::encode::serialize(&tx); + assert!( + serialized_tx.windows(data.len()).any(|e| e == data), + "cannot find op_return data in transaction" + ); + blockchain.broadcast(&tx).unwrap(); + test_client.generate(1, Some(node_addr)); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 50_000 - details.fee.unwrap_or(0), + "incorrect balance after send" + ); - // make coinbase mature (100 blocks) - let node_addr = test_client.get_node_address(None); - test_client.generate(100, Some(node_addr)); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let tx_map = wallet + .list_transactions(false) + .unwrap() + .into_iter() + .map(|tx| (tx.txid, tx)) + .collect::>(); + let _ = tx_map.get(&tx.txid()).unwrap(); + } - assert!(wallet.get_balance().unwrap().confirmed > 0, "incorrect balance after maturing coinbase"); + pub fn test_sync_receive_coinbase(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, _, mut test_client) = init_single_sig(init_blockchain); - } + let wallet_addr = wallet + .get_address(wallet::AddressIndex::New) + .unwrap() + .address; - #[test] - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_send_to_bech32m_addr() { - use std::str::FromStr; - use serde; - use serde_json; - use serde::Serialize; - use bitcoincore_rpc::jsonrpc::serde_json::Value; - use bitcoincore_rpc::{Auth, Client, RpcApi}; - - let (wallet, blockchain, 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, - } + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().immature, + 0, + "incorrect balance" + ); + + test_client.generate(1, Some(wallet_addr)); + + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + assert!( + wallet.get_balance().unwrap().immature > 0, + "incorrect balance after receiving coinbase" + ); + + // make coinbase mature (100 blocks) + let node_addr = test_client.get_node_address(None); + test_client.generate(100, Some(node_addr)); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + assert!( + wallet.get_balance().unwrap().confirmed > 0, + "incorrect balance after maturing coinbase" + ); + } - // 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(), - } - } + #[cfg(not(feature = "test-rpc-legacy"))] + pub fn test_send_to_bech32m_addr(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + use bitcoincore_rpc::jsonrpc::serde_json::Value; + use bitcoincore_rpc::{Auth, Client}; + use serde::Serialize; + + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + + // 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 - // 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 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(); - // 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"; - let wallet_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/0/*)#y283ssmn"; - let change_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/1/*)#47zsd9tt"; + let tr_descriptors = vec![ + ImportDescriptorRequest::new(wallet_descriptor, false), + ImportDescriptorRequest::new(change_descriptor, false), + ]; - let tr_descriptors = vec![ - ImportDescriptorRequest::new(wallet_descriptor, false), - ImportDescriptorRequest::new(change_descriptor, false), - ]; + // 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(); - // 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 - // 2. Get a new bech32m address from test bitcoind node taproot wallet + // 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() + ); - // 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()); + // 3. Send 50_000 sats from test bitcoind node to test BDK wallet - // 3. Send 50_000 sats from test bitcoind node to test BDK wallet + test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().untrusted_pending, + 50_000, + "wallet has incorrect balance" + ); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 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, "wallet cannot finalize transaction"); + let tx = psbt.extract_tx(); + blockchain.broadcast(&tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!( + wallet.get_balance().unwrap().get_spendable(), + 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); - // 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet + // 5. Verify 25_000 sats are received by 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, "wallet cannot finalize transaction"); - let tx = psbt.extract_tx(); - blockchain.broadcast(&tx).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 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); + 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" + ); + } - // 5. Verify 25_000 sats are received by test bitcoind node taproot wallet + pub fn test_tx_chain(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + // use $crate::wallet::AddressIndex; + + // Here we want to test that we set correctly the send and receive + // fields in the transaction object. For doing so, we create two + // different txs, the second one spending from the first: + // 1. + // Core (#1) -> Core (#2) + // -> Us (#3) + // 2. + // Core (#2) -> Us (#4) + + let (wallet, blockchain, _, mut test_client) = init_single_sig(init_blockchain); + let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address; + let core_address = test_client.get_new_address(None, None).unwrap(); + let tx = testutils! { + @tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 ) + }; - 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"); - } + // Tx one: from Core #1 to Core #2 and Us #3. + let txid_1 = test_client.receive(tx); + let tx_1: Transaction = + deserialize(&test_client.get_transaction(&txid_1, None).unwrap().hex).unwrap(); + let vout_1 = tx_1 + .output + .into_iter() + .position(|o| o.script_pubkey == core_address.script_pubkey()) + .unwrap() as u32; + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let tx_1 = wallet + .list_transactions(false) + .unwrap() + .into_iter() + .find(|tx| tx.txid == txid_1) + .unwrap(); + assert_eq!(tx_1.received, 50_000); + assert_eq!(tx_1.sent, 0); - #[test] - fn test_tx_chain() { - use bitcoincore_rpc::RpcApi; - use bitcoin::consensus::encode::deserialize; - use $crate::wallet::AddressIndex; - - // Here we want to test that we set correctly the send and receive - // fields in the transaction object. For doing so, we create two - // different txs, the second one spending from the first: - // 1. - // Core (#1) -> Core (#2) - // -> Us (#3) - // 2. - // Core (#2) -> Us (#4) - - let (wallet, blockchain, _, mut test_client) = init_single_sig(); - let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address; - let core_address = test_client.get_new_address(None, None).unwrap(); - let tx = testutils! { - @tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 ) - }; - - // Tx one: from Core #1 to Core #2 and Us #3. - let txid_1 = test_client.receive(tx); - let tx_1: Transaction = deserialize(&test_client.get_transaction(&txid_1, None).unwrap().hex).unwrap(); - let vout_1 = tx_1.output.into_iter().position(|o| o.script_pubkey == core_address.script_pubkey()).unwrap() as u32; - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - let tx_1 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_1).unwrap(); - assert_eq!(tx_1.received, 50_000); - assert_eq!(tx_1.sent, 0); - - // Tx two: from Core #2 to Us #4. - let tx = testutils! { - @tx ( (@addr bdk_address) => 10_000 ) ( @inputs (txid_1,vout_1)) - }; - let txid_2 = test_client.receive(tx); - - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - let tx_2 = wallet.list_transactions(false).unwrap().into_iter().find(|tx| tx.txid == txid_2).unwrap(); - assert_eq!(tx_2.received, 10_000); - assert_eq!(tx_2.sent, 0); - } + // Tx two: from Core #2 to Us #4. + let tx = testutils! { + @tx ( (@addr bdk_address) => 10_000 ) ( @inputs (txid_1,vout_1)) + }; + let txid_2 = test_client.receive(tx); - #[test] - fn test_double_spend() { - // We create a tx and then we try to double spend it; BDK will always allow - // us to do so, as it never forgets about spent UTXOs - let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(); - let node_addr = test_client.get_node_address(None); - let _ = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let tx_2 = wallet + .list_transactions(false) + .unwrap() + .into_iter() + .find(|tx| tx.txid == txid_2) + .unwrap(); + assert_eq!(tx_2.received, 10_000); + assert_eq!(tx_2.sent, 0); + } - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - 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 initial_tx = psbt.extract_tx(); - let _sent_txid = blockchain.broadcast(&initial_tx).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - for utxo in wallet.list_unspent().unwrap() { - // Making sure the TXO we just spent is not returned by list_unspent - assert!(utxo.outpoint != initial_tx.input[0].previous_output, "wallet displays spent txo in unspents"); - } - // We can still create a transaction double spending `initial_tx` - let mut builder = wallet.build_tx(); - builder - .add_utxo(initial_tx.input[0].previous_output) - .expect("Can't manually add an UTXO spent"); - test_client.generate(1, Some(node_addr)); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - // Even after confirmation, we can still create a tx double spend it - let mut builder = wallet.build_tx(); - builder - .add_utxo(initial_tx.input[0].previous_output) - .expect("Can't manually add an UTXO spent"); - for utxo in wallet.list_unspent().unwrap() { - // Making sure the TXO we just spent is not returned by list_unspent - assert!(utxo.outpoint != initial_tx.input[0].previous_output, "wallet displays spent txo in unspents"); - } - } + pub fn test_double_spend(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + // We create a tx and then we try to double spend it; BDK will always allow + // us to do so, as it never forgets about spent UTXOs + let (wallet, blockchain, descriptors, mut test_client) = init_single_sig(init_blockchain); + let node_addr = test_client.get_node_address(None); + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - #[test] - fn test_send_receive_pkh() { - let descriptors = ("pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None); - let mut test_client = TestClient::default(); - let blockchain = get_blockchain(&test_client); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + 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 initial_tx = psbt.extract_tx(); + let _sent_txid = blockchain.broadcast(&initial_tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + for utxo in wallet.list_unspent().unwrap() { + // Making sure the TXO we just spent is not returned by list_unspent + assert!( + utxo.outpoint != initial_tx.input[0].previous_output, + "wallet displays spent txo in unspents" + ); + } + // We can still create a transaction double spending `initial_tx` + let mut builder = wallet.build_tx(); + builder + .add_utxo(initial_tx.input[0].previous_output) + .expect("Can't manually add an UTXO spent"); + test_client.generate(1, Some(node_addr)); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + // Even after confirmation, we can still create a tx double spend it + let mut builder = wallet.build_tx(); + builder + .add_utxo(initial_tx.input[0].previous_output) + .expect("Can't manually add an UTXO spent"); + for utxo in wallet.list_unspent().unwrap() { + // Making sure the TXO we just spent is not returned by list_unspent + assert!( + utxo.outpoint != initial_tx.input[0].previous_output, + "wallet displays spent txo in unspents" + ); + } + } - let wallet = get_wallet_from_descriptors(&descriptors); - #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))] - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + pub fn test_send_receive_pkh(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let descriptors = ( + "pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), + None, + ); + let mut test_client = TestClient::default(); + let blockchain = init_blockchain(&test_client); + let wallet = get_wallet_from_descriptors(&descriptors); + #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))] + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); - let _ = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000); + let tx = { + let mut builder = wallet.build_tx(); + builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000); + let (mut psbt, _details) = builder.finish().unwrap(); + wallet.sign(&mut psbt, Default::default()).unwrap(); + psbt.extract_tx() + }; + blockchain.broadcast(&tx).unwrap(); - let tx = { - let mut builder = wallet.build_tx(); - builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000); - let (mut psbt, _details) = builder.finish().unwrap(); - wallet.sign(&mut psbt, Default::default()).unwrap(); - psbt.extract_tx() - }; - blockchain.broadcast(&tx).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + } - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - } + #[cfg(not(feature = "test-rpc-legacy"))] + pub fn test_taproot_key_spend(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = + init_wallet(WalletType::TaprootKeySpend, init_blockchain); - #[test] - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_taproot_key_spend() { - let (wallet, blockchain, descriptors, mut test_client) = init_wallet(WalletType::TaprootKeySpend); + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000); + + let tx = { + let mut builder = wallet.build_tx(); + builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000); + let (mut psbt, _details) = builder.finish().unwrap(); + wallet.sign(&mut psbt, Default::default()).unwrap(); + psbt.extract_tx() + }; + blockchain.broadcast(&tx).unwrap(); + } - let _ = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) - }); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000); - - let tx = { - let mut builder = wallet.build_tx(); - builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000); - let (mut psbt, _details) = builder.finish().unwrap(); - wallet.sign(&mut psbt, Default::default()).unwrap(); - psbt.extract_tx() - }; - blockchain.broadcast(&tx).unwrap(); - } + #[cfg(not(feature = "test-rpc-legacy"))] + pub fn test_taproot_script_spend(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + let (wallet, blockchain, descriptors, mut test_client) = + init_wallet(WalletType::TaprootScriptSpend, init_blockchain); - #[test] - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_taproot_script_spend() { - let (wallet, blockchain, descriptors, mut test_client) = init_wallet(WalletType::TaprootScriptSpend); + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 6 ) + }); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000); + + let ext_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); + let int_policy = wallet.policies(KeychainKind::Internal).unwrap().unwrap(); + + let ext_path = vec![(ext_policy.id.clone(), vec![1])].into_iter().collect(); + let int_path = vec![(int_policy.id.clone(), vec![1])].into_iter().collect(); + + let tx = { + let mut builder = wallet.build_tx(); + builder + .add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000) + .policy_path(ext_path, KeychainKind::External) + .policy_path(int_path, KeychainKind::Internal); + let (mut psbt, _details) = builder.finish().unwrap(); + wallet.sign(&mut psbt, Default::default()).unwrap(); + psbt.extract_tx() + }; + blockchain.broadcast(&tx).unwrap(); + } - let _ = test_client.receive(testutils! { - @tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 6 ) - }); - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - assert_eq!(wallet.get_balance().unwrap().get_spendable(), 50_000); - - let ext_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); - let int_policy = wallet.policies(KeychainKind::Internal).unwrap().unwrap(); - - let ext_path = vec![(ext_policy.id.clone(), vec![1])].into_iter().collect(); - let int_path = vec![(int_policy.id.clone(), vec![1])].into_iter().collect(); - - let tx = { - let mut builder = wallet.build_tx(); - builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000) - .policy_path(ext_path, KeychainKind::External) - .policy_path(int_path, KeychainKind::Internal); - let (mut psbt, _details) = builder.finish().unwrap(); - wallet.sign(&mut psbt, Default::default()).unwrap(); - psbt.extract_tx() - }; - blockchain.broadcast(&tx).unwrap(); - } + #[cfg(not(feature = "test-rpc-legacy"))] + pub fn test_sign_taproot_core_keyspend_psbt(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + test_sign_taproot_core_psbt(WalletType::TaprootKeySpend, init_blockchain); + } - #[test] - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_sign_taproot_core_keyspend_psbt() { - test_sign_taproot_core_psbt(WalletType::TaprootKeySpend); - } + #[cfg(not(feature = "test-rpc-legacy"))] + pub fn test_sign_taproot_core_scriptspend2_psbt(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend2, init_blockchain); + } - #[test] - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_sign_taproot_core_scriptspend2_psbt() { - test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend2); - } + #[cfg(not(feature = "test-rpc-legacy"))] + pub fn test_sign_taproot_core_scriptspend3_psbt(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend3, init_blockchain); + } - #[test] - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_sign_taproot_core_scriptspend3_psbt() { - test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend3); - } + #[cfg(not(feature = "test-rpc-legacy"))] + fn test_sign_taproot_core_psbt( + wallet_type: WalletType, + init_blockchain: &dyn Fn(&TestClient) -> B, + ) where + B: Blockchain, + { + use bitcoincore_rpc::jsonrpc::serde_json::Value; + use bitcoincore_rpc::{Auth, Client}; + + let (wallet, _blockchain, _descriptors, test_client) = + init_wallet(wallet_type, init_blockchain); + + // 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(), + true.into(), + true.into(), + serde_json::to_value("").unwrap(), + false.into(), + true.into(), + true.into(), + false.into(), + ], + ) + .expect("created wallet"); - #[cfg(not(feature = "test-rpc-legacy"))] - fn test_sign_taproot_core_psbt(wallet_type: WalletType) { - use std::str::FromStr; - use serde_json; - use bitcoincore_rpc::jsonrpc::serde_json::Value; - use bitcoincore_rpc::{Auth, Client, RpcApi}; - - let (wallet, _blockchain, _descriptors, test_client) = init_wallet(wallet_type); - - // 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(), true.into(), true.into(), serde_json::to_value("").unwrap(), false.into(), true.into(), true.into(), false.into()]).expect("created wallet"); - - let external_descriptor = wallet.get_descriptor_for_keychain(KeychainKind::External); - - // 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 descriptor_info = taproot_wallet_client.get_descriptor_info(external_descriptor.to_string().as_str()).expect("descriptor info"); - - let import_descriptor_args = json!([{ - "desc": descriptor_info.descriptor, - "active": true, - "timestamp": "now", - "label":"taproot key spend", - }]); - let _importdescriptors_result: Value = taproot_wallet_client.call("importdescriptors", &[import_descriptor_args]).expect("import wallet"); - let generate_to_address: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).expect("new address"); - let _generatetoaddress_result = taproot_wallet_client.generate_to_address(101, &generate_to_address).expect("generated to address"); - let send_to_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string(); - let change_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string(); - let send_addr_amounts = json!([{ - send_to_address: "0.4321" - }]); - let send_options = json!({ - "change_address": change_address, - "psbt": true, - }); - let send_result: Value = taproot_wallet_client.call("send", &[send_addr_amounts, Value::Null, "unset".into(), Value::Null, send_options]).expect("send psbt"); - let core_psbt = send_result["psbt"].as_str().expect("core psbt str"); + let external_descriptor = wallet.get_descriptor_for_keychain(KeychainKind::External); - use bitcoin::util::psbt::PartiallySignedTransaction; + // 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 descriptor_info = taproot_wallet_client + .get_descriptor_info(external_descriptor.to_string().as_str()) + .expect("descriptor info"); + + let import_descriptor_args = json!([{ + "desc": descriptor_info.descriptor, + "active": true, + "timestamp": "now", + "label":"taproot key spend", + }]); + let _importdescriptors_result: Value = taproot_wallet_client + .call("importdescriptors", &[import_descriptor_args]) + .expect("import wallet"); + let generate_to_address: bitcoin::Address = taproot_wallet_client + .call("getnewaddress", &["test address".into(), "bech32m".into()]) + .expect("new address"); + let _generatetoaddress_result = taproot_wallet_client + .generate_to_address(101, &generate_to_address) + .expect("generated to address"); + let send_to_address = wallet + .get_address(crate::wallet::AddressIndex::New) + .unwrap() + .address + .to_string(); + let change_address = wallet + .get_address(crate::wallet::AddressIndex::New) + .unwrap() + .address + .to_string(); + let send_addr_amounts = json!([{ + send_to_address: "0.4321" + }]); + let send_options = json!({ + "change_address": change_address, + "psbt": true, + }); + let send_result: Value = taproot_wallet_client + .call( + "send", + &[ + send_addr_amounts, + Value::Null, + "unset".into(), + Value::Null, + send_options, + ], + ) + .expect("send psbt"); + let core_psbt = send_result["psbt"].as_str().expect("core psbt str"); - // Test parsing core created PSBT - let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot psbt"); + use bitcoin::util::psbt::PartiallySignedTransaction; - // Test signing core created PSBT - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert_eq!(finalized, true); + // Test parsing core created PSBT + let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot psbt"); - // Test with updated psbt - let update_result: Value = taproot_wallet_client.call("utxoupdatepsbt", &[core_psbt.into()]).expect("update psbt utxos"); - let core_updated_psbt = update_result.as_str().expect("core updated psbt"); + // Test signing core created PSBT + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert_eq!(finalized, true); - // Test parsing core created and updated PSBT - let mut psbt = PartiallySignedTransaction::from_str(&core_updated_psbt).expect("core taproot psbt"); + // Test with updated psbt + let update_result: Value = taproot_wallet_client + .call("utxoupdatepsbt", &[core_psbt.into()]) + .expect("update psbt utxos"); + let core_updated_psbt = update_result.as_str().expect("core updated psbt"); - // Test signing core created and updated PSBT - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert_eq!(finalized, true); - } + // Test parsing core created and updated PSBT + let mut psbt = + PartiallySignedTransaction::from_str(&core_updated_psbt).expect("core taproot psbt"); - #[test] - fn test_get_block_hash() { - use bitcoincore_rpc::{ RpcApi }; - use crate::blockchain::GetBlockHash; + // Test signing core created and updated PSBT + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert_eq!(finalized, true); + } - // create wallet with init_wallet - let (_, blockchain, _descriptors, mut test_client) = init_single_sig(); + pub fn test_get_block_hash(init_blockchain: &dyn Fn(&TestClient) -> B) + where + B: Blockchain, + { + // create wallet with init_wallet + let (_, blockchain, _descriptors, mut test_client) = init_single_sig(init_blockchain); - let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64; - let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap(); + let height = test_client + .bitcoind + .client + .get_blockchain_info() + .unwrap() + .blocks as u64; + let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap(); - // use get_block_hash to get best block hash and compare with best_hash above - let block_hash = blockchain.get_block_hash(height).unwrap(); - assert_eq!(best_hash, block_hash); + // use get_block_hash to get best block hash and compare with best_hash above + let block_hash = blockchain.get_block_hash(height).unwrap(); + assert_eq!(best_hash, block_hash); - // generate blocks to address - let node_addr = test_client.get_node_address(None); - test_client.generate(10, Some(node_addr)); + // generate blocks to address + let node_addr = test_client.get_node_address(None); + test_client.generate(10, Some(node_addr)); - let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64; - let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap(); + let height = test_client + .bitcoind + .client + .get_blockchain_info() + .unwrap() + .blocks as u64; + let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap(); - let block_hash = blockchain.get_block_hash(height).unwrap(); - assert_eq!(best_hash, block_hash); + let block_hash = blockchain.get_block_hash(height).unwrap(); + assert_eq!(best_hash, block_hash); - // try to get hash for block that has not yet been created. - assert!(blockchain.get_block_hash(height + 1).is_err()); - } - } - }; + // try to get hash for block that has not yet been created. + assert!(blockchain.get_block_hash(height + 1).is_err()); + } +} - ( fn $fn_name:ident ($( $tt:tt )+) -> $blockchain:ty $block:block) => { - compile_error!(concat!("Invalid arguments `", stringify!($($tt)*), "` in the blockchain tests fn.")); - compile_error!("Only the exact `&TestClient` type is supported, **without** any leading path items."); +#[macro_export] +#[doc(hidden)] +macro_rules! make_blockchain_tests { + ($fn_name:ident, tests ( $($x:tt) , + $(,)? )) => { + $( + #[test] + fn $x() + { + $crate::testutils::blockchain_tests::test::$x(&$fn_name); + } + )+ }; } From 63dd109c270db424999911e51c004018c2e248ea Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Wed, 31 Aug 2022 18:31:05 +0530 Subject: [PATCH 4/5] Apply blockchain test changes Apply the new `make_blockchain_test` macro to run the blockchain tests for rpc, electrum and esplora blockchain. Co-authored-by: SanthoshAnguluri Co-authored-by: saikishore222 --- src/blockchain/electrum.rs | 45 +++++++++++++++++-- src/blockchain/esplora/mod.rs | 61 ++++++++++++++++++++++---- src/blockchain/rpc.rs | 81 +++++++++++++++++++++++++++-------- 3 files changed, 157 insertions(+), 30 deletions(-) diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index fdb10b470..d0ef860ea 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -337,16 +337,53 @@ mod test { use super::*; use crate::database::MemoryDatabase; + use crate::make_blockchain_tests; use crate::testutils::blockchain_tests::TestClient; use crate::testutils::configurable_blockchain_tests::ConfigurableBlockchainTester; use crate::wallet::{AddressIndex, Wallet}; + use electrum_client::Client; - crate::bdk_blockchain_tests! { - fn test_instance(test_client: &TestClient) -> ElectrumBlockchain { - ElectrumBlockchain::from(Client::new(&test_client.electrsd.electrum_url).unwrap()) - } + fn init_blockchain(test_client: &TestClient) -> ElectrumBlockchain { + ElectrumBlockchain::from(Client::new(&test_client.electrsd.electrum_url).unwrap()) } + make_blockchain_tests![ + init_blockchain, + tests( + test_sync_simple, + test_sync_stop_gap_20, + test_sync_before_and_after_receive, + test_sync_multiple_outputs_same_tx, + test_sync_receive_multi, + test_sync_address_reuse, + test_sync_receive_rbf_replaced, + test_sync_reorg_block, + test_sync_after_send, + test_sync_address_index_should_not_decrement, + test_sync_address_index_should_increment, + test_sync_double_receive, + test_sync_many_sends_to_a_single_address, + test_update_confirmation_time_after_generate, + test_sync_outgoing_from_scratch, + test_sync_long_change_chain, + test_sync_bump_fee_basic, + test_sync_bump_fee_remove_change, + test_sync_bump_fee_add_input_simple, + test_sync_bump_fee_add_input_no_change, + test_add_data, + test_sync_receive_coinbase, + test_send_to_bech32m_addr, + test_tx_chain, + test_double_spend, + test_send_receive_pkh, + test_taproot_script_spend, + test_sign_taproot_core_keyspend_psbt, + test_sign_taproot_core_scriptspend2_psbt, + test_sign_taproot_core_scriptspend3_psbt, + test_get_block_hash, + ) + ]; + fn get_factory() -> (TestClient, Arc) { let test_client = TestClient::default(); diff --git a/src/blockchain/esplora/mod.rs b/src/blockchain/esplora/mod.rs index 57032e49d..40630a8f2 100644 --- a/src/blockchain/esplora/mod.rs +++ b/src/blockchain/esplora/mod.rs @@ -80,20 +80,63 @@ impl From for crate::BlockTime { } } -#[cfg(test)] -#[cfg(feature = "test-esplora")] -crate::bdk_blockchain_tests! { - fn test_instance(test_client: &TestClient) -> EsploraBlockchain { - EsploraBlockchain::new(&format!("http://{}",test_client.electrsd.esplora_url.as_ref().unwrap()), 20) - } -} - const DEFAULT_CONCURRENT_REQUESTS: u8 = 4; +#[cfg(feature = "test-esplora")] #[cfg(test)] mod test { + use super::*; + use crate::make_blockchain_tests; + use crate::testutils::blockchain_tests::TestClient; + + fn init_blockchain(test_client: &TestClient) -> EsploraBlockchain { + EsploraBlockchain::new( + &format!( + "http://{}", + test_client.electrsd.esplora_url.as_ref().unwrap() + ), + 20, + ) + } + + make_blockchain_tests![ + init_blockchain, + tests( + test_sync_simple, + test_sync_stop_gap_20, + test_sync_before_and_after_receive, + test_sync_multiple_outputs_same_tx, + test_sync_receive_multi, + test_sync_address_reuse, + test_sync_receive_rbf_replaced, + test_sync_after_send, + test_sync_address_index_should_not_decrement, + test_sync_address_index_should_increment, + test_sync_double_receive, + test_sync_many_sends_to_a_single_address, + test_update_confirmation_time_after_generate, + test_sync_outgoing_from_scratch, + test_sync_long_change_chain, + test_sync_bump_fee_basic, + test_sync_bump_fee_remove_change, + test_sync_bump_fee_add_input_simple, + test_sync_bump_fee_add_input_no_change, + test_add_data, + test_sync_receive_coinbase, + test_send_to_bech32m_addr, + test_tx_chain, + test_double_spend, + test_send_receive_pkh, + test_taproot_key_spend, + test_taproot_script_spend, + test_sign_taproot_core_keyspend_psbt, + test_sign_taproot_core_scriptspend2_psbt, + test_sign_taproot_core_scriptspend3_psbt, + test_get_block_hash, + ) + ]; + #[test] - #[cfg(feature = "test-esplora")] fn test_esplora_with_variable_configs() { use super::*; diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index b2c64ba5a..f385e4947 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -870,32 +870,79 @@ impl BlockchainFactory for RpcBlockchainFactory { #[cfg(test)] #[cfg(any(feature = "test-rpc", feature = "test-rpc-legacy"))] -mod test { +pub mod test { use super::*; - use crate::{ - descriptor::{into_wallet_descriptor_checked, AsDerived}, - testutils::blockchain_tests::TestClient, - wallet::utils::SecpCtx, - }; + use crate::descriptor::into_wallet_descriptor_checked; + use crate::descriptor::AsDerived; + use crate::make_blockchain_tests; + use crate::testutils::blockchain_tests::TestClient; + use crate::wallet::utils::SecpCtx; use bitcoin::{Address, Network}; use bitcoincore_rpc::RpcApi; use log::LevelFilter; use miniscript::DescriptorTrait; - crate::bdk_blockchain_tests! { - fn test_instance(test_client: &TestClient) -> RpcBlockchain { - let config = RpcConfig { - url: test_client.bitcoind.rpc_url(), - auth: Auth::Cookie { file: test_client.bitcoind.params.cookie_file.clone() }, - network: Network::Regtest, - wallet_name: format!("client-wallet-test-{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() ), - sync_params: None, - }; - RpcBlockchain::from_config(&config).unwrap() - } + pub fn init_blockchain(test_client: &TestClient) -> RpcBlockchain { + let config = RpcConfig { + url: test_client.bitcoind.rpc_url(), + auth: Auth::Cookie { + file: test_client.bitcoind.params.cookie_file.clone(), + }, + network: Network::Regtest, + wallet_name: format!( + "client-wallet-test-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ), + sync_params: Some(RpcSyncParams::default()), + }; + RpcBlockchain::from_config(&config).unwrap() } + make_blockchain_tests![ + init_blockchain, + tests( + test_sync_simple, + test_sync_stop_gap_20, + test_sync_before_and_after_receive, + test_sync_multiple_outputs_same_tx, + test_sync_receive_multi, + test_sync_address_reuse, + test_sync_receive_rbf_replaced, + test_sync_after_send, + test_sync_address_index_should_not_decrement, + test_sync_address_index_should_increment, + test_sync_double_receive, + test_sync_many_sends_to_a_single_address, + test_update_confirmation_time_after_generate, + test_sync_outgoing_from_scratch, + test_sync_long_change_chain, + test_sync_bump_fee_basic, + test_sync_bump_fee_add_input_simple, + test_sync_bump_fee_add_input_no_change, + test_sync_receive_coinbase, + test_double_spend, + test_tx_chain, + test_get_block_hash, + ) + ]; + + #[cfg(not(feature = "test-rpc-legacy"))] + make_blockchain_tests![ + init_blockchain, + tests( + test_send_to_bech32m_addr, + test_taproot_key_spend, + test_taproot_script_spend, + test_sign_taproot_core_keyspend_psbt, + test_sign_taproot_core_scriptspend2_psbt, + test_sign_taproot_core_scriptspend3_psbt, + ) + ]; + fn get_factory() -> (TestClient, RpcBlockchainFactory) { let test_client = TestClient::default(); From 2e7612d7cff27e372edcb577985f4f94fb48ab2c Mon Sep 17 00:00:00 2001 From: rajarshimaitra Date: Wed, 31 Aug 2022 18:32:34 +0530 Subject: [PATCH 5/5] Update CHANGELOG Co-authored-by: SanthoshAnguluri Co-authored-by: saikishore222 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42bd10a06..f6cbe70d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change the meaning of the `fee_amount` field inside `CoinSelectionResult`: from now on the `fee_amount` will represent only the fees asociated with the utxos in the `selected` field of `CoinSelectionResult`. - New `RpcBlockchain` implementation with various fixes. - Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`. +- Transform testing macros into functions. For both Database and Blockchain tests. +- Move testing macros and functions into their own module inside `testutils/helpers.rs`. ## [v0.20.0] - [v0.19.0]