From b6fe01c4668b1b6737e5e24ae2282f0f55da39cd Mon Sep 17 00:00:00 2001 From: Gianluca Acerbis Date: Tue, 14 Sep 2021 12:56:00 +0000 Subject: [PATCH 1/4] Implement XKeyUtils on InnerXKey Closes #395 --- src/descriptor/mod.rs | 49 ++++++++----------------------------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index bb6b619c..14045f42 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -17,13 +17,13 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::ops::Deref; -use bitcoin::util::bip32::{ - ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource, -}; +use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource}; use bitcoin::util::psbt; use bitcoin::{Network, PublicKey, Script, TxOut}; -use miniscript::descriptor::{DescriptorPublicKey, DescriptorType, DescriptorXKey, Wildcard}; +use miniscript::descriptor::{ + DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard, +}; pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0}; use miniscript::{DescriptorTrait, ForEachKey, TranslatePk}; @@ -267,41 +267,10 @@ pub(crate) trait XKeyUtils { fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; } -// FIXME: `InnerXKey` was made private in rust-miniscript, so we have to implement this manually on -// both `ExtendedPubKey` and `ExtendedPrivKey`. -// -// Revert back to using the trait once https://github.com/rust-bitcoin/rust-miniscript/pull/230 is -// released -impl XKeyUtils for DescriptorXKey { - fn full_path(&self, append: &[ChildNumber]) -> DerivationPath { - let full_path = match self.origin { - Some((_, ref path)) => path - .into_iter() - .chain(self.derivation_path.into_iter()) - .cloned() - .collect(), - None => self.derivation_path.clone(), - }; - - if self.wildcard != Wildcard::None { - full_path - .into_iter() - .chain(append.iter()) - .cloned() - .collect() - } else { - full_path - } - } - - fn root_fingerprint(&self, _: &SecpCtx) -> Fingerprint { - match self.origin { - Some((fingerprint, _)) => fingerprint, - None => self.xkey.fingerprint(), - } - } -} -impl XKeyUtils for DescriptorXKey { +impl XKeyUtils for DescriptorXKey +where + T: InnerXKey, +{ fn full_path(&self, append: &[ChildNumber]) -> DerivationPath { let full_path = match self.origin { Some((_, ref path)) => path @@ -326,7 +295,7 @@ impl XKeyUtils for DescriptorXKey { fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint { match self.origin { Some((fingerprint, _)) => fingerprint, - None => self.xkey.fingerprint(secp), + None => self.xkey.xkey_fingerprint(secp), } } } From b04bb590f385db349f8dc70cc169674b530c4205 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 17 Feb 2022 11:37:39 -0800 Subject: [PATCH 2/4] Pin tokio version to ~1.14 --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f7dece9..319c83cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0 + ## [v0.16.0] - [v0.15.0] - Disable `reqwest` default features. diff --git a/Cargo.toml b/Cargo.toml index b79beceb..d8a5ab46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ bitcoincore-rpc = { version = "0.14", optional = true } # Platform-specific dependencies [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { version = "1", features = ["rt"] } +tokio = { version = "~1.14", features = ["rt"] } [target.'cfg(target_arch = "wasm32")'.dependencies] async-trait = "0.1" From 128c37595c5cfeacdb8e999da5795a9aa28ad67b Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Thu, 17 Feb 2022 23:36:40 +0100 Subject: [PATCH 3/4] [tests] Pass tx inputs to the testutils macro --- src/testutils/blockchain_tests.rs | 8 +++++- src/testutils/mod.rs | 44 ++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 4229f5df..c7bcfb01 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -90,13 +90,19 @@ impl TestClient { map.insert(out.to_address.clone(), Amount::from_sat(out.value)); } + let input: Vec<_> = meta_tx + .input + .into_iter() + .map(|x| x.into_raw_tx_input()) + .collect(); + if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) { panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap()); } // FIXME: core can't create a tx with two outputs to the same address let tx = self - .create_raw_transaction_hex(&[], &map, meta_tx.locktime, meta_tx.replaceable) + .create_raw_transaction_hex(&input, &map, meta_tx.locktime, meta_tx.replaceable) .unwrap(); let tx = self.fund_raw_transaction(tx, None, None).unwrap(); let mut tx: Transaction = deserialize(&tx.hex).unwrap(); diff --git a/src/testutils/mod.rs b/src/testutils/mod.rs index 80f0d253..f05c9df4 100644 --- a/src/testutils/mod.rs +++ b/src/testutils/mod.rs @@ -15,11 +15,37 @@ pub mod blockchain_tests; use bitcoin::secp256k1::{Secp256k1, Verification}; -use bitcoin::{Address, PublicKey}; +use bitcoin::{Address, PublicKey, Txid}; use miniscript::descriptor::DescriptorPublicKey; use miniscript::{Descriptor, MiniscriptKey, TranslatePk}; +#[derive(Clone, Debug)] +pub struct TestIncomingInput { + pub txid: Txid, + pub vout: u32, + pub sequence: Option, +} + +impl TestIncomingInput { + pub fn new(txid: Txid, vout: u32, sequence: Option) -> Self { + Self { + txid, + vout, + sequence, + } + } + + #[cfg(feature = "test-blockchains")] + pub fn into_raw_tx_input(self) -> bitcoincore_rpc::json::CreateRawTransactionInput { + bitcoincore_rpc::json::CreateRawTransactionInput { + txid: self.txid, + vout: self.vout, + sequence: self.sequence, + } + } +} + #[derive(Clone, Debug)] pub struct TestIncomingOutput { pub value: u64, @@ -37,6 +63,7 @@ impl TestIncomingOutput { #[derive(Clone, Debug)] pub struct TestIncomingTx { + pub input: Vec, pub output: Vec, pub min_confirmations: Option, pub locktime: Option, @@ -45,12 +72,14 @@ pub struct TestIncomingTx { impl TestIncomingTx { pub fn new( + input: Vec, output: Vec, min_confirmations: Option, locktime: Option, replaceable: Option, ) -> Self { Self { + input, output, min_confirmations, locktime, @@ -58,6 +87,10 @@ impl TestIncomingTx { } } + pub fn add_input(&mut self, input: TestIncomingInput) { + self.input.push(input); + } + pub fn add_output(&mut self, output: TestIncomingOutput) { self.output.push(output); } @@ -123,16 +156,21 @@ macro_rules! testutils { }); ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) }); ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) }); + ( @addr $addr:expr ) => ({ $addr }); - ( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @locktime $locktime:expr ) )? $( ( @confirmations $confirmations:expr ) )? $( ( @replaceable $replaceable:expr ) )? ) => ({ + ( @tx ( $( ( $( $addr:tt )* ) => $amount:expr ),+ ) $( ( @inputs $( ($txid:expr, $vout:expr) ),+ ) )? $( ( @locktime $locktime:expr ) )? $( ( @confirmations $confirmations:expr ) )? $( ( @replaceable $replaceable:expr ) )? ) => ({ let outs = vec![$( $crate::testutils::TestIncomingOutput::new($amount, testutils!( $($addr)* ))),+]; + let _ins: Vec<$crate::testutils::TestIncomingInput> = vec![]; + $( + let _ins = vec![$( $crate::testutils::TestIncomingInput { txid: $txid, vout: $vout, sequence: None }),+]; + )? let locktime = None::$(.or(Some($locktime)))?; let min_confirmations = None::$(.or(Some($confirmations)))?; let replaceable = None::$(.or(Some($replaceable)))?; - $crate::testutils::TestIncomingTx::new(outs, min_confirmations, locktime, replaceable) + $crate::testutils::TestIncomingTx::new(_ins, outs, min_confirmations, locktime, replaceable) }); ( @literal $key:expr ) => ({ From bfd0d13779003f11fa099a0b665052c18ce6e500 Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Thu, 17 Feb 2022 23:39:11 +0100 Subject: [PATCH 4/4] [blockchain] Fix `sent` calculation in the RPC backend We used to consider a tx input as ours if we had the tx that creates it in the database. This commit actually checks if an input is ours before adding its value to the `sent` field. --- src/blockchain/rpc.rs | 4 ++- src/testutils/blockchain_tests.rs | 45 ++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/blockchain/rpc.rs b/src/blockchain/rpc.rs index 5c9ee2dd..251913ef 100644 --- a/src/blockchain/rpc.rs +++ b/src/blockchain/rpc.rs @@ -257,7 +257,9 @@ impl Blockchain for RpcBlockchain { for input in tx.input.iter() { if let Some(previous_output) = db.get_previous_output(&input.previous_output)? { - sent += previous_output.value; + if db.is_mine(&previous_output.script_pubkey)? { + sent += previous_output.value; + } } } diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index c7bcfb01..02047d67 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -359,7 +359,7 @@ 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::Network; + use $crate::bitcoin::{Transaction, Network}; use $crate::testutils::blockchain_tests::TestClient; use $crate::blockchain::noop_progress; use $crate::database::MemoryDatabase; @@ -1079,6 +1079,49 @@ macro_rules! bdk_blockchain_tests { 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"); } + + #[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, _, 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(noop_progress(), None).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(noop_progress(), None).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); + } } };