diff --git a/examples/example_wallet_electrum/Cargo.toml b/examples/example_wallet_electrum/Cargo.toml index 9e0e4605..cd038830 100644 --- a/examples/example_wallet_electrum/Cargo.toml +++ b/examples/example_wallet_electrum/Cargo.toml @@ -5,5 +5,6 @@ edition = "2021" [dependencies] bdk_wallet = { path = "../../wallet", features = ["file_store"] } -bdk_electrum = { version = "0.21" } +#bdk_electrum = { version = "0.21" } +bdk_electrum = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization" } anyhow = "1" diff --git a/examples/example_wallet_electrum/src/main.rs b/examples/example_wallet_electrum/src/main.rs index 5942714e..c9b19f4e 100644 --- a/examples/example_wallet_electrum/src/main.rs +++ b/examples/example_wallet_electrum/src/main.rs @@ -22,7 +22,7 @@ const ELECTRUM_URL: &str = "ssl://electrum.blockstream.info:60002"; fn main() -> Result<(), anyhow::Error> { let db_path = "bdk-electrum-example.db"; - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; + let (mut db, _) = Store::::load_or_create(DB_MAGIC.as_bytes(), db_path)?; let wallet_opt = Wallet::load() .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) @@ -41,7 +41,7 @@ fn main() -> Result<(), anyhow::Error> { wallet.persist(&mut db)?; println!("Generated Address: {}", address); - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); println!("Wallet balance before syncing: {}", balance.total()); print!("Syncing..."); @@ -70,7 +70,7 @@ fn main() -> Result<(), anyhow::Error> { wallet.apply_update(update)?; wallet.persist(&mut db)?; - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); println!("Wallet balance after syncing: {}", balance.total()); if balance.total() < SEND_AMOUNT { @@ -81,7 +81,8 @@ fn main() -> Result<(), anyhow::Error> { std::process::exit(0); } - let mut tx_builder = wallet.build_tx(); + let canonicalization_params = wallet.include_unbroadcasted(); + let mut tx_builder = wallet.build_tx(canonicalization_params); tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); let mut psbt = tx_builder.finish()?; @@ -89,6 +90,8 @@ fn main() -> Result<(), anyhow::Error> { assert!(finalized); let tx = psbt.extract_tx()?; + wallet.insert_unbroadcasted(tx.clone()); + client.transaction_broadcast(&tx)?; println!("Tx broadcasted! Txid: {}", tx.compute_txid()); diff --git a/examples/example_wallet_esplora_async/Cargo.toml b/examples/example_wallet_esplora_async/Cargo.toml index 089105d7..ac134c4e 100644 --- a/examples/example_wallet_esplora_async/Cargo.toml +++ b/examples/example_wallet_esplora_async/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] bdk_wallet = { path = "../../wallet", features = ["rusqlite"] } -bdk_esplora = { version = "0.20", features = ["async-https", "tokio"] } +#bdk_esplora = { version = "0.20", features = ["async-https", "tokio"] } +bdk_esplora = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization", features = ["async-https", "tokio"] } +bdk_testenv = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization" } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } anyhow = "1" diff --git a/examples/example_wallet_esplora_async/src/main.rs b/examples/example_wallet_esplora_async/src/main.rs index b6ab5d6d..8255f3a3 100644 --- a/examples/example_wallet_esplora_async/src/main.rs +++ b/examples/example_wallet_esplora_async/src/main.rs @@ -1,7 +1,9 @@ +#![allow(unused)] use std::{collections::BTreeSet, io::Write}; use anyhow::Ok; use bdk_esplora::{esplora_client, EsploraAsyncExt}; +use bdk_testenv::bitcoincore_rpc::RpcApi; use bdk_wallet::{ bitcoin::{Amount, Network}, rusqlite::Connection, @@ -13,14 +15,16 @@ const STOP_GAP: usize = 5; const PARALLEL_REQUESTS: usize = 5; const DB_PATH: &str = "bdk-example-esplora-async.sqlite"; -const NETWORK: Network = Network::Signet; +// const NETWORK: Network = Network::Signet; +const NETWORK: Network = Network::Regtest; const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - let mut conn = Connection::open(DB_PATH)?; + // let mut conn = Connection::open(DB_PATH)?; + let mut conn = Connection::open_in_memory()?; let wallet_opt = Wallet::load() .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) @@ -35,57 +39,196 @@ async fn main() -> Result<(), anyhow::Error> { .create_wallet(&mut conn)?, }; - let address = wallet.next_unused_address(KeychainKind::External); + // let address = wallet.next_unused_address(KeychainKind::External); + let recv_addr = wallet.next_unused_address(KeychainKind::External); wallet.persist(&mut conn)?; - println!("Next unused address: ({}) {}", address.index, address); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - print!("Syncing..."); - let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{:?}]", keychain); - } - print!(" {:<3}", spk_i); - stdout.flush().expect("must flush") - } - }); - - let update = client - .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) - .await?; - - wallet.apply_update(update)?; + // println!("Next unused address: ({}) {}", address.index, address); + + // let balance = wallet.balance(); + // println!("Wallet balance before syncing: {}", balance.total()); + + // print!("Syncing..."); + // let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; + + // let request = wallet.start_full_scan().inspect({ + // let mut stdout = std::io::stdout(); + // let mut once = BTreeSet::::new(); + // move |keychain, spk_i, _| { + // if once.insert(keychain) { + // print!("\nScanning keychain [{:?}]", keychain); + // } + // print!(" {:<3}", spk_i); + // stdout.flush().expect("must flush") + // } + // }); + + // let update = client + // .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) + // .await?; + + // wallet.apply_update(update)?; + // wallet.persist(&mut conn)?; + // println!(); + + // let balance = wallet.balance(); + // println!("Wallet balance after syncing: {}", balance.total()); + + // if balance.total() < SEND_AMOUNT { + // println!( + // "Please send at least {} to the receiving address", + // SEND_AMOUNT + // ); + // std::process::exit(0); + // } + + // let mut tx_builder = wallet.build_tx(); + // tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); + + // let mut psbt = tx_builder.finish()?; + // let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + // assert!(finalized); + + // let tx = psbt.extract_tx()?; + // client.broadcast(&tx).await?; + // println!("Tx broadcasted! Txid: {}", tx.compute_txid()); + + use bdk_testenv::bitcoincore_rpc::bitcoincore_rpc_json::CreateRawTransactionInput; + use bdk_testenv::TestEnv; + let env = TestEnv::new()?; + + // premine + let rpc = env.rpc_client(); + let _ = env.mine_blocks(100, None); + assert_eq!(rpc.get_block_count()?, 101); + + let utxo = rpc.list_unspent(None, None, None, None, None)?[0].clone(); + + // Create tx1 + let utxos = vec![CreateRawTransactionInput { + txid: utxo.txid, + vout: utxo.vout, + sequence: None, + }]; + let to_send = Amount::ONE_BTC; + let fee = Amount::from_sat(1_000); + let change_addr = rpc.get_new_address(None, None)?.assume_checked(); + let out = [ + (recv_addr.to_string(), to_send), + (change_addr.to_string(), utxo.amount - to_send - fee), + ] + .into(); + let tx = rpc.create_raw_transaction(&utxos, &out, None, None)?; + let tx1 = rpc + .sign_raw_transaction_with_wallet(&tx, None, None)? + .transaction()?; + + // Create tx2 the double spend + let new_addr = rpc.get_new_address(None, None)?.assume_checked(); + let out = [ + (new_addr.to_string(), to_send), + (change_addr.to_string(), utxo.amount - to_send - (fee * 2)), + ] + .into(); + let tx = rpc.create_raw_transaction(&utxos, &out, None, None)?; + let tx2 = rpc + .sign_raw_transaction_with_wallet(&tx, None, None)? + .transaction()?; + + // Sync after send tx 1 + let txid1 = rpc.send_raw_transaction(&tx1)?; + println!("Send tx1 {}", txid1); + + let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap()); + let client = esplora_client::Builder::new(base_url.as_str()).build_async()?; + + while client.get_height().await? < 101 { + std::thread::sleep(std::time::Duration::from_millis(64)); + } + env.wait_until_electrum_sees_txid(txid1, std::time::Duration::from_secs(10))?; + + let request = wallet.start_sync_with_revealed_spks(); + + let resp = client.sync(request, PARALLEL_REQUESTS).await?; + assert_eq!(resp.tx_update.txs.first().unwrap().compute_txid(), txid1); + + wallet.apply_update(resp)?; wallet.persist(&mut conn)?; - println!(); - let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); + assert_eq!( + wallet.balance(wallet.include_unbroadcasted()).total(), + Amount::ONE_BTC + ); + println!( + "Balance after send tx1: {}", + wallet.balance(wallet.include_unbroadcasted()).total() + ); + // We should expect tx1 to occur in a future sync + let exp_spk_txids = wallet + .tx_graph() + .list_expected_spk_txids( + wallet.local_chain(), + wallet.local_chain().tip().block_id(), + wallet.spk_index(), + /*spk_index_range: */ .., + ) + .collect::>(); + assert_eq!( + exp_spk_txids.first(), + Some(&(recv_addr.script_pubkey(), txid1)) + ); + + // Now sync after send tx 2 + let txid2 = rpc.send_raw_transaction(&tx2)?; + println!("Send tx2 {}", txid2); + env.wait_until_electrum_sees_txid(txid2, std::time::Duration::from_secs(10))?; + + let request = wallet.start_sync_with_revealed_spks(); + + let resp = client.sync(request, PARALLEL_REQUESTS).await?; + assert!(resp.tx_update.txs.is_empty()); + assert!(resp + .tx_update + .evicted_ats + .iter() + .any(|&(txid, _)| txid == txid1)); + + wallet.apply_update(resp)?; + wallet.persist(&mut conn)?; - if balance.total() < SEND_AMOUNT { + println!( + "Balance after send tx2: {}", + wallet.balance(wallet.include_unbroadcasted()).total() + ); + assert_eq!( + wallet.balance(wallet.include_unbroadcasted()).total(), + Amount::ZERO + ); + + // Load the persisted wallet + { + wallet = Wallet::load() + .load_wallet(&mut conn)? + .expect("wallet was persisted"); + + // tx1 is there, but is not canonical + assert!(wallet.tx_graph().full_txs().any(|node| node.txid == txid1)); + assert!(wallet + .transactions(wallet.include_unbroadcasted()) + .next() + .is_none()); + assert!(wallet + .list_unspent(wallet.include_unbroadcasted()) + .next() + .is_none()); + assert_eq!( + wallet.balance(wallet.include_unbroadcasted()).total(), + Amount::ZERO + ); println!( - "Please send at least {} to the receiving address", - SEND_AMOUNT + "Balance after load wallet: {}", + wallet.balance(wallet.include_unbroadcasted()).total() ); - std::process::exit(0); } - let mut tx_builder = wallet.build_tx(); - tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); - - let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized); - - let tx = psbt.extract_tx()?; - client.broadcast(&tx).await?; - println!("Tx broadcasted! Txid: {}", tx.compute_txid()); - Ok(()) } diff --git a/examples/example_wallet_esplora_blocking/Cargo.toml b/examples/example_wallet_esplora_blocking/Cargo.toml index b8bf20c4..eade6115 100644 --- a/examples/example_wallet_esplora_blocking/Cargo.toml +++ b/examples/example_wallet_esplora_blocking/Cargo.toml @@ -8,5 +8,6 @@ publish = false [dependencies] bdk_wallet = { path = "../../wallet", features = ["file_store"] } -bdk_esplora = { version = "0.20", features = ["blocking"] } +#bdk_esplora = { version = "0.20", features = ["blocking"] } +bdk_esplora = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization", features = ["blocking"] } anyhow = "1" diff --git a/examples/example_wallet_esplora_blocking/src/main.rs b/examples/example_wallet_esplora_blocking/src/main.rs index 7966f19f..520b271f 100644 --- a/examples/example_wallet_esplora_blocking/src/main.rs +++ b/examples/example_wallet_esplora_blocking/src/main.rs @@ -19,7 +19,7 @@ const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7 const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; fn main() -> Result<(), anyhow::Error> { - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?; + let (mut db, _) = Store::::load_or_create(DB_MAGIC.as_bytes(), DB_PATH)?; let wallet_opt = Wallet::load() .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) @@ -41,7 +41,7 @@ fn main() -> Result<(), anyhow::Error> { address.index, address.address ); - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); println!("Wallet balance before syncing: {}", balance.total()); print!("Syncing..."); @@ -65,7 +65,7 @@ fn main() -> Result<(), anyhow::Error> { wallet.persist(&mut db)?; println!(); - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); println!("Wallet balance after syncing: {}", balance.total()); if balance.total() < SEND_AMOUNT { @@ -76,7 +76,8 @@ fn main() -> Result<(), anyhow::Error> { std::process::exit(0); } - let mut tx_builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut tx_builder = wallet.build_tx(params); tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); let mut psbt = tx_builder.finish()?; @@ -84,6 +85,8 @@ fn main() -> Result<(), anyhow::Error> { assert!(finalized); let tx = psbt.extract_tx()?; + wallet.insert_unbroadcasted(tx.clone()); + client.broadcast(&tx)?; println!("Tx broadcasted! Txid: {}", tx.compute_txid()); diff --git a/examples/example_wallet_rpc/Cargo.toml b/examples/example_wallet_rpc/Cargo.toml index 546bc64e..e3a8fcad 100644 --- a/examples/example_wallet_rpc/Cargo.toml +++ b/examples/example_wallet_rpc/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" [dependencies] bdk_wallet = { path = "../../wallet", features = ["file_store"] } -bdk_bitcoind_rpc = { version = "0.18" } +#bdk_bitcoind_rpc = { version = "0.18" } +bdk_bitcoind_rpc = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization" } anyhow = "1" clap = { version = "4.5.17", features = ["derive", "env"] } diff --git a/examples/example_wallet_rpc/src/main.rs b/examples/example_wallet_rpc/src/main.rs index 2021f835..78a40b09 100644 --- a/examples/example_wallet_rpc/src/main.rs +++ b/examples/example_wallet_rpc/src/main.rs @@ -86,8 +86,8 @@ fn main() -> anyhow::Result<()> { ); let start_load_wallet = Instant::now(); - let mut db = - Store::::open_or_create_new(DB_MAGIC.as_bytes(), args.db_path)?; + let (mut db, _) = + Store::::load_or_create(DB_MAGIC.as_bytes(), args.db_path)?; let wallet_opt = Wallet::load() .descriptor(KeychainKind::External, Some(args.descriptor.clone())) .descriptor(KeychainKind::Internal, args.change_descriptor.clone()) @@ -110,7 +110,7 @@ fn main() -> anyhow::Result<()> { start_load_wallet.elapsed().as_secs_f32() ); - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); println!("Wallet balance before syncing: {}", balance.total()); let wallet_tip = wallet.latest_checkpoint(); @@ -173,7 +173,7 @@ fn main() -> anyhow::Result<()> { } } let wallet_tip_end = wallet.latest_checkpoint(); - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); println!( "Synced {} blocks in {}s", blocks_received, @@ -187,8 +187,8 @@ fn main() -> anyhow::Result<()> { println!("Wallet balance is {}", balance.total()); println!( "Wallet has {} transactions and {} utxos", - wallet.transactions().count(), - wallet.list_unspent().count() + wallet.transactions(wallet.include_unbroadcasted()).count(), + wallet.list_unspent(wallet.include_unbroadcasted()).count() ); Ok(()) diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 4d917c82..2bad647a 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -21,12 +21,23 @@ miniscript = { version = "12.3.1", features = [ "serde" ], default-features = fa bitcoin = { version = "0.32.4", features = [ "serde", "base64" ], default-features = false } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } -bdk_chain = { version = "0.21.1", features = [ "miniscript", "serde" ], default-features = false } -bdk_file_store = { version = "0.18.1", optional = true } +#bdk_chain = { version = "0.21.1", features = [ "miniscript", "serde" ], default-features = false } +#bdk_file_store = { version = "0.18.1", optional = true } # Optional dependencies bip39 = { version = "2.0", optional = true } +[dependencies.bdk_chain] +git = "https://github.com/evanlinjin/bdk" +branch = "superimposed_canonicalization" +default-features = false +features = ["miniscript", "serde"] + +[dependencies.bdk_file_store] +git = "https://github.com/evanlinjin/bdk" +branch = "superimposed_canonicalization" +optional = true + [features] default = ["std"] std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std"] @@ -41,12 +52,17 @@ test-utils = ["std"] lazy_static = "1.4" assert_matches = "1.5.0" tempfile = "3" -bdk_chain = { version = "0.21.1", features = ["rusqlite"] } +#bdk_chain = { version = "0.21.1", features = ["rusqlite"] } bdk_wallet = { path = ".", features = ["rusqlite", "file_store", "test-utils"] } -bdk_file_store = { version = "0.18.1" } +#bdk_file_store = { version = "0.18.1" } anyhow = "1" rand = "^0.8" +[dev-dependencies.bdk_chain] +git = "https://github.com/evanlinjin/bdk" +branch = "superimposed_canonicalization" +features = ["rusqlite"] + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/wallet/README.md b/wallet/README.md index bf5b644d..f4538288 100644 --- a/wallet/README.md +++ b/wallet/README.md @@ -65,13 +65,12 @@ To persist `Wallet` state data use a data store crate that reads and writes [`Ch **Example** - ```rust,no_run use bdk_wallet::{bitcoin::Network, KeychainKind, ChangeSet, Wallet}; // Open or create a new file store for wallet data. -let mut db = - bdk_file_store::Store::::open_or_create_new(b"magic_bytes", "/tmp/my_wallet.db") +let (mut db, _changeset) = + bdk_file_store::Store::::load_or_create(b"magic_bytes", "/tmp/my_wallet.db") .expect("create store"); // Create a wallet with initial wallet data read from the file store. diff --git a/wallet/src/test_utils.rs b/wallet/src/test_utils.rs index 7e1778fa..e8d484ad 100644 --- a/wallet/src/test_utils.rs +++ b/wallet/src/test_utils.rs @@ -4,7 +4,7 @@ use alloc::string::ToString; use alloc::sync::Arc; use core::str::FromStr; -use bdk_chain::{tx_graph, BlockId, ConfirmationBlockTime}; +use bdk_chain::{BlockId, ConfirmationBlockTime}; use bitcoin::{ absolute, hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut, Txid, @@ -312,43 +312,45 @@ pub fn insert_checkpoint(wallet: &mut Wallet, block: BlockId) { .unwrap(); } -/// Insert transaction +/// Inserts a transaction into the local view, assuming it is currently present in the mempool. +/// +/// This can be used, for example, to track a transaction immediately after it is broadcast. pub fn insert_tx(wallet: &mut Wallet, tx: Transaction) { + let txid = tx.compute_txid(); + let seen_at = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); + let mut tx_update = chain::TxUpdate::default(); + tx_update.txs = vec![Arc::new(tx)]; + tx_update.seen_ats = [(txid, seen_at)].into(); wallet .apply_update(Update { - tx_update: bdk_chain::TxUpdate { - txs: vec![Arc::new(tx)], - ..Default::default() - }, + tx_update, ..Default::default() }) - .unwrap(); + .expect("failed to apply update"); } /// Simulates confirming a tx with `txid` by applying an update to the wallet containing /// the given `anchor`. Note: to be considered confirmed the anchor block must exist in /// the current active chain. pub fn insert_anchor(wallet: &mut Wallet, txid: Txid, anchor: ConfirmationBlockTime) { + let mut tx_update = chain::TxUpdate::default(); + tx_update.anchors = [(anchor, txid)].into(); wallet .apply_update(Update { - tx_update: tx_graph::TxUpdate { - anchors: [(anchor, txid)].into(), - ..Default::default() - }, + tx_update, ..Default::default() }) - .unwrap(); + .expect("failed to apply update"); } /// Marks the given `txid` seen as unconfirmed at `seen_at` pub fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) { + let mut tx_update = chain::TxUpdate::default(); + tx_update.seen_ats = [(txid, seen_at)].into(); wallet - .apply_update(crate::Update { - tx_update: tx_graph::TxUpdate { - seen_ats: [(txid, seen_at)].into_iter().collect(), - ..Default::default() - }, + .apply_update(Update { + tx_update, ..Default::default() }) - .unwrap(); + .expect("failed to apply update"); } diff --git a/wallet/src/wallet/changeset.rs b/wallet/src/wallet/changeset.rs index 94dba2b7..128fc3e7 100644 --- a/wallet/src/wallet/changeset.rs +++ b/wallet/src/wallet/changeset.rs @@ -3,6 +3,8 @@ use bdk_chain::{ }; use miniscript::{Descriptor, DescriptorPublicKey}; +use super::unbroadcasted; + type IndexedTxGraphChangeSet = indexed_tx_graph::ChangeSet; @@ -21,6 +23,8 @@ pub struct ChangeSet { pub tx_graph: tx_graph::ChangeSet, /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex). pub indexer: keychain_txout::ChangeSet, + /// Changes to [`Unbroadcasted`](crate::unbroadcasted::Unbroadcasted). + pub unbroadcasted: unbroadcasted::ChangeSet, } impl Merge for ChangeSet { diff --git a/wallet/src/wallet/coin_selection.rs b/wallet/src/wallet/coin_selection.rs index e5654fb4..8ce4fc69 100644 --- a/wallet/src/wallet/coin_selection.rs +++ b/wallet/src/wallet/coin_selection.rs @@ -91,7 +91,10 @@ //! .require_network(Network::Testnet) //! .unwrap(); //! let psbt = { -//! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything); +//! let params = wallet.include_unbroadcasted(); +//! let mut builder = wallet +//! .build_tx(params) +//! .coin_selection(AlwaysSpendEverything); //! builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); //! builder.finish()? //! }; diff --git a/wallet/src/wallet/export.rs b/wallet/src/wallet/export.rs index cbbee2e2..a23e2013 100644 --- a/wallet/src/wallet/export.rs +++ b/wallet/src/wallet/export.rs @@ -128,12 +128,15 @@ impl FullyNodedExport { Self::is_compatible_with_core(&descriptor)?; let blockheight = if include_blockheight { - wallet.transactions().next().map_or(0, |canonical_tx| { - canonical_tx - .chain_position - .confirmation_height_upper_bound() - .unwrap_or(0) - }) + wallet + .transactions(wallet.include_unbroadcasted()) + .next() + .map_or(0, |canonical_tx| { + canonical_tx + .chain_position + .confirmation_height_upper_bound() + .unwrap_or(0) + }) } else { 0 }; diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index e4dc6d05..745199fe 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -19,6 +19,7 @@ use alloc::{ sync::Arc, vec::Vec, }; +use chain::CanonicalizationParams; use core::{cmp::Ordering, fmt, mem, ops::Deref}; use bdk_chain::{ @@ -57,6 +58,7 @@ mod params; mod persisted; pub mod signer; pub mod tx_builder; +pub mod unbroadcasted; pub(crate) mod utils; use crate::collections::{BTreeMap, HashMap, HashSet}; @@ -80,6 +82,7 @@ pub use bdk_chain::Balance; pub use changeset::ChangeSet; pub use params::*; pub use persisted::*; +pub use unbroadcasted::Unbroadcasted; pub use utils::IsDust; /// A Bitcoin wallet @@ -105,6 +108,7 @@ pub struct Wallet { change_signers: Arc, chain: LocalChain, indexed_graph: IndexedTxGraph>, + unbroadcasted: Unbroadcasted, stage: ChangeSet, network: Network, secp: SecpCtx, @@ -405,6 +409,7 @@ impl Wallet { let change_descriptor = index.get_descriptor(KeychainKind::Internal).cloned(); let indexed_graph = IndexedTxGraph::new(index); let indexed_graph_changeset = indexed_graph.initial_changeset(); + let unbroadcasted = Unbroadcasted::default(); let stage = ChangeSet { descriptor, @@ -412,6 +417,7 @@ impl Wallet { local_chain: chain_changeset, tx_graph: indexed_graph_changeset.tx_graph, indexer: indexed_graph_changeset.indexer, + unbroadcasted: Default::default(), network: Some(network), }; @@ -421,6 +427,7 @@ impl Wallet { network, chain, indexed_graph, + unbroadcasted, stage, secp, }) @@ -605,6 +612,8 @@ impl Wallet { indexed_graph.apply_changeset(changeset.indexer.into()); indexed_graph.apply_changeset(changeset.tx_graph.into()); + let unbroadcasted = Unbroadcasted::from_changeset(changeset.unbroadcasted); + let stage = ChangeSet::default(); Ok(Some(Wallet { @@ -612,6 +621,7 @@ impl Wallet { change_signers, chain, indexed_graph, + unbroadcasted, stage, network, secp, @@ -809,13 +819,60 @@ impl Wallet { self.indexed_graph.index.index_of_spk(spk).cloned() } + /// Modifies canonicalization by assuming that all unbroadcasted transactions are canonical. + pub fn include_unbroadcasted(&self) -> CanonicalizationParams { + CanonicalizationParams { + assume_canonical: self.unbroadcasted.txids().collect(), + } + } + + /// Returns unbroadcasted transactions that are still eligible for broadcast. + /// + /// A transaction is no longer broadcastable if it conflicts with another transaction + /// that has been confirmed. For example, if another transaction spending the same inputs + /// gets mined, this one becomes invalid for broadcast. + /// + /// This method filters out such transactions and returns only the ones that are still + /// considered valid in the current chain state. + pub fn eligible_unbroadcasted(&self) -> impl Iterator)> + '_ { + let mut canonical_txs = self + .tx_graph() + .canonical_iter( + &self.chain, + self.chain.tip().block_id(), + self.include_unbroadcasted(), + ) + .map(|r| r.expect("infallible")) + .map(|(txid, tx, _)| (txid, tx)) + .collect::>>(); + self.unbroadcasted + .txids() + .filter_map(move |txid| canonical_txs.remove(&txid).map(|tx| (txid, tx))) + } + + /// Inserts an unbroadcasted transaction `tx`. + pub fn insert_unbroadcasted(&mut self, tx: impl Into>) { + let tx: Arc = tx.into(); + let txid = tx.compute_txid(); + self.stage + .unbroadcasted + .merge(self.unbroadcasted.insert(txid)); + let changeset = self.indexed_graph.insert_tx(tx); + self.stage.tx_graph.merge(changeset.tx_graph); + self.stage.indexer.merge(changeset.indexer); + } + /// Return the list of unspent outputs of this wallet - pub fn list_unspent(&self) -> impl Iterator + '_ { + pub fn list_unspent( + &self, + params: CanonicalizationParams, + ) -> impl Iterator + '_ { self.indexed_graph .graph() .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), + params, self.indexed_graph.index.outpoints().iter().cloned(), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) @@ -824,12 +881,16 @@ impl Wallet { /// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed). /// /// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead. - pub fn list_output(&self) -> impl Iterator + '_ { + pub fn list_output( + &self, + params: CanonicalizationParams, + ) -> impl Iterator + '_ { self.indexed_graph .graph() .filter_chain_txouts( &self.chain, self.chain.tip().block_id(), + params, self.indexed_graph.index.outpoints().iter().cloned(), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) @@ -876,13 +937,14 @@ impl Wallet { /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the /// wallet's database. - pub fn get_utxo(&self, op: OutPoint) -> Option { + pub fn get_utxo(&self, params: CanonicalizationParams, op: OutPoint) -> Option { let ((keychain, index), _) = self.indexed_graph.index.txout(op)?; self.indexed_graph .graph() .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), + params, core::iter::once(((), op)), ) .map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo)) @@ -925,7 +987,11 @@ impl Wallet { /// # use bdk_wallet::Wallet; /// # let mut wallet: Wallet = todo!(); /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; + /// let tx = wallet + /// .get_tx(wallet.include_unbroadcasted(), txid) + /// .expect("transaction") + /// .tx_node + /// .tx; /// let fee = wallet.calculate_fee(&tx).expect("fee"); /// ``` /// @@ -956,7 +1022,11 @@ impl Wallet { /// # use bdk_wallet::Wallet; /// # let mut wallet: Wallet = todo!(); /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; + /// let tx = wallet + /// .get_tx(wallet.include_unbroadcasted(), txid) + /// .expect("transaction") + /// .tx_node + /// .tx; /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate"); /// ``` /// @@ -986,7 +1056,11 @@ impl Wallet { /// # use bdk_wallet::Wallet; /// # let mut wallet: Wallet = todo!(); /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx; + /// let tx = wallet + /// .get_tx(wallet.include_unbroadcasted(), txid) + /// .expect("tx exists") + /// .tx_node + /// .tx; /// let (sent, received) = wallet.sent_and_received(&tx); /// ``` /// @@ -1018,7 +1092,9 @@ impl Wallet { /// # let wallet: Wallet = todo!(); /// # let my_txid: bitcoin::Txid = todo!(); /// - /// let wallet_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist"); + /// let wallet_tx = wallet + /// .get_tx(wallet.include_unbroadcasted(), my_txid) + /// .expect("panic if tx does not exist"); /// /// // get reference to full transaction /// println!("my tx: {:#?}", wallet_tx.tx_node.tx); @@ -1055,10 +1131,10 @@ impl Wallet { /// ``` /// /// [`Anchor`]: bdk_chain::Anchor - pub fn get_tx(&self, txid: Txid) -> Option { + pub fn get_tx(&self, params: CanonicalizationParams, txid: Txid) -> Option { let graph = self.indexed_graph.graph(); graph - .list_canonical_txs(&self.chain, self.chain.tip().block_id()) + .list_canonical_txs(&self.chain, self.chain.tip().block_id(), params) .find(|tx| tx.tx_node.txid == txid) } @@ -1073,11 +1149,14 @@ impl Wallet { /// /// To iterate over all canonical transactions, including those that are irrelevant, use /// [`TxGraph::list_canonical_txs`]. - pub fn transactions(&self) -> impl Iterator + '_ { + pub fn transactions( + &self, + params: CanonicalizationParams, + ) -> impl Iterator + '_ { let tx_graph = self.indexed_graph.graph(); let tx_index = &self.indexed_graph.index; tx_graph - .list_canonical_txs(&self.chain, self.chain.tip().block_id()) + .list_canonical_txs(&self.chain, self.chain.tip().block_id(), params) .filter(|c_tx| tx_index.is_tx_relevant(&c_tx.tx_node.tx)) } @@ -1093,25 +1172,32 @@ impl Wallet { /// # use bdk_wallet::{LoadParams, Wallet, WalletTx}; /// # let mut wallet:Wallet = todo!(); /// // Transactions by chain position: first unconfirmed then descending by confirmed height. - /// let sorted_txs: Vec = - /// wallet.transactions_sort_by(|tx1, tx2| tx2.chain_position.cmp(&tx1.chain_position)); + /// let sorted_txs: Vec = wallet + /// .transactions_sort_by(wallet.include_unbroadcasted(), |tx1, tx2| { + /// tx2.chain_position.cmp(&tx1.chain_position) + /// }); /// # Ok::<(), anyhow::Error>(()) /// ``` - pub fn transactions_sort_by(&self, compare: F) -> Vec + pub fn transactions_sort_by( + &self, + params: CanonicalizationParams, + compare: F, + ) -> Vec where F: FnMut(&WalletTx, &WalletTx) -> Ordering, { - let mut txs: Vec = self.transactions().collect(); + let mut txs: Vec = self.transactions(params).collect(); txs.sort_unstable_by(compare); txs } /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature /// values. - pub fn balance(&self) -> Balance { + pub fn balance(&self, params: CanonicalizationParams) -> Balance { self.indexed_graph.graph().balance( &self.chain, self.chain.tip().block_id(), + params, self.indexed_graph.index.outpoints().iter().cloned(), |&(k, _), _| k == KeychainKind::Internal, ) @@ -1198,7 +1284,8 @@ impl Wallet { /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let psbt = { - /// let mut builder = wallet.build_tx(); + /// let params = wallet.include_unbroadcasted(); + /// let mut builder = wallet.build_tx(params); /// builder /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); /// builder.finish()? @@ -1209,10 +1296,16 @@ impl Wallet { /// ``` /// /// [`TxBuilder`]: crate::TxBuilder - pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm> { + pub fn build_tx( + &mut self, + params: CanonicalizationParams, + ) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm> { TxBuilder { wallet: self, - params: TxParams::default(), + params: TxParams { + canonicalization_params: params, + ..Default::default() + }, coin_selection: DefaultCoinSelectionAlgorithm::default(), } } @@ -1561,7 +1654,8 @@ impl Wallet { /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let mut psbt = { - /// let mut builder = wallet.build_tx(); + /// let params = wallet.include_unbroadcasted(); + /// let mut builder = wallet.build_tx(params); /// builder /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); /// builder.finish()? @@ -1590,7 +1684,7 @@ impl Wallet { let txout_index = &self.indexed_graph.index; let chain_tip = self.chain.tip().block_id(); let chain_positions = graph - .list_canonical_txs(&self.chain, chain_tip) + .list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default()) .map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position)) .collect::>(); @@ -1752,7 +1846,8 @@ impl Wallet { /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let mut psbt = { - /// let mut builder = wallet.build_tx(); + /// let params = wallet.include_unbroadcasted(); + /// let mut builder = wallet.build_tx(params); /// builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); /// builder.finish()? /// }; @@ -1858,7 +1953,13 @@ impl Wallet { let confirmation_heights = self .indexed_graph .graph() - .list_canonical_txs(&self.chain, chain_tip) + .list_canonical_txs( + &self.chain, + chain_tip, + CanonicalizationParams { + assume_canonical: prev_txids.iter().copied().collect(), + }, + ) .filter(|canon_tx| prev_txids.contains(&canon_tx.tx_node.txid)) // This is for a small performance gain. Although `.filter` filters out excess txs, it // will still consume the internal `CanonicalIter` entirely. Having a `.take` here @@ -2010,6 +2111,7 @@ impl Wallet { .filter_chain_unspents( &self.chain, self.chain.tip().block_id(), + params.canonicalization_params.clone(), self.indexed_graph.index.outpoints().iter().cloned(), ) // only create LocalOutput if UTxO is mature @@ -2219,32 +2321,8 @@ impl Wallet { /// /// After applying updates you should persist the staged wallet changes. For an example of how /// to persist staged wallet changes see [`Wallet::reveal_next_address`]. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn apply_update(&mut self, update: impl Into) -> Result<(), CannotConnectError> { - use std::time::*; - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("time now must surpass epoch anchor"); - self.apply_update_at(update, now.as_secs()) - } - - /// Applies an `update` alongside a `seen_at` timestamp and stages the changes. - /// - /// `seen_at` represents when the update is seen (in unix seconds). It is used to determine the - /// `last_seen`s for all transactions in the update which have no corresponding anchor(s). The - /// `last_seen` value is used internally to determine precedence of conflicting unconfirmed - /// transactions (where the transaction with the lower `last_seen` value is omitted from the - /// canonical history). - /// - /// Use [`apply_update`](Wallet::apply_update) to have the `seen_at` value automatically set to - /// the current time. - pub fn apply_update_at( - &mut self, - update: impl Into, - seen_at: u64, - ) -> Result<(), CannotConnectError> { - let update = update.into(); + let update: Update = update.into(); let mut changeset = match update.chain { Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?), None => ChangeSet::default(), @@ -2255,11 +2333,10 @@ impl Wallet { .index .reveal_to_target_multi(&update.last_active_indices); changeset.merge(index_changeset.into()); - changeset.merge( - self.indexed_graph - .apply_update_at(update.tx_update, Some(seen_at)) - .into(), - ); + changeset + .unbroadcasted + .merge(self.unbroadcasted.update(&update.tx_update)); + changeset.merge(self.indexed_graph.apply_update(update.tx_update).into()); self.stage.merge(changeset); Ok(()) } @@ -2397,16 +2474,47 @@ impl Wallet { /// Methods to construct sync/full-scan requests for spk-based chain sources. impl Wallet { + /// Create a partial [`SyncRequest`] for all revealed spks at `start_time`. + /// + /// The `start_time` is used to record the time that a mempool transaction was last seen + /// (or evicted). See [`Wallet::start_sync_with_revealed_spks`] for more. + pub fn start_sync_with_revealed_spks_at( + &self, + start_time: u64, + ) -> SyncRequestBuilder<(KeychainKind, u32)> { + use bdk_chain::keychain_txout::SyncRequestBuilderExt; + SyncRequest::builder_at(start_time) + .chain_tip(self.chain.tip()) + .revealed_spks_from_indexer(&self.indexed_graph.index, ..) + .expected_spk_txids(self.indexed_graph.list_expected_spk_txids( + &self.chain, + self.chain.tip().block_id(), + .., + )) + } + /// Create a partial [`SyncRequest`] for this wallet for all revealed spks. /// /// This is the first step when performing a spk-based wallet partial sync, the returned /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to /// start a blockchain sync with a spk based blockchain client. + /// + /// The time of the sync is the current system time and is used to record the + /// tx last-seen for mempool transactions. Or if an expected transaction is missing + /// or evicted, it is the time of the eviction. Note that timestamps may only increase + /// to be counted by the tx graph. To supply your own start time see [`Wallet::start_sync_at`]. + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + #[cfg(feature = "std")] pub fn start_sync_with_revealed_spks(&self) -> SyncRequestBuilder<(KeychainKind, u32)> { use bdk_chain::keychain_txout::SyncRequestBuilderExt; SyncRequest::builder() .chain_tip(self.chain.tip()) .revealed_spks_from_indexer(&self.indexed_graph.index, ..) + .expected_spk_txids(self.indexed_graph.list_expected_spk_txids( + &self.chain, + self.chain.tip().block_id(), + .., + )) } /// Create a [`FullScanRequest] for this wallet. @@ -2417,12 +2525,22 @@ impl Wallet { /// /// This operation is generally only used when importing or restoring a previously used wallet /// in which the list of used scripts is not known. + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + #[cfg(feature = "std")] pub fn start_full_scan(&self) -> FullScanRequestBuilder { use bdk_chain::keychain_txout::FullScanRequestBuilderExt; FullScanRequest::builder() .chain_tip(self.chain.tip()) .spks_from_indexer(&self.indexed_graph.index) } + + /// Create a [`FullScanRequest`] builder at `start_time`. + pub fn start_full_scan_at(&self, start_time: u64) -> FullScanRequestBuilder { + use bdk_chain::keychain_txout::FullScanRequestBuilderExt; + FullScanRequest::builder_at(start_time) + .chain_tip(self.chain.tip()) + .spks_from_indexer(&self.indexed_graph.index) + } } impl AsRef> for Wallet { @@ -2600,7 +2718,9 @@ mod test { insert_tx(&mut wallet, two_output_tx); let mut params = TxParams::default(); - let output = wallet.get_utxo(OutPoint { txid, vout: 0 }).unwrap(); + let output = wallet + .get_utxo(wallet.include_unbroadcasted(), OutPoint { txid, vout: 0 }) + .unwrap(); params.utxos.insert( output.outpoint, WeightedUtxo { @@ -2616,7 +2736,7 @@ mod test { // notice expected doesn't include the first output from two_output_tx as it should be // filtered out let expected = vec![wallet - .get_utxo(OutPoint { txid, vout: 1 }) + .get_utxo(wallet.include_unbroadcasted(), OutPoint { txid, vout: 1 }) .map(|utxo| WeightedUtxo { satisfaction_weight: wallet .public_descriptor(utxo.keychain) diff --git a/wallet/src/wallet/persisted.rs b/wallet/src/wallet/persisted.rs index 28a6ec78..71236989 100644 --- a/wallet/src/wallet/persisted.rs +++ b/wallet/src/wallet/persisted.rs @@ -291,7 +291,7 @@ impl WalletPersister for bdk_chain::rusqlite::Connection { #[derive(Debug)] pub enum FileStoreError { /// Error when loading from the store. - Load(bdk_file_store::AggregateChangesetsError), + Load(bdk_file_store::StoreErrorWithDump), /// Error when writing to the store. Write(std::io::Error), } @@ -316,15 +316,13 @@ impl WalletPersister for bdk_file_store::Store { fn initialize(persister: &mut Self) -> Result { persister - .aggregate_changesets() + .dump() .map(Option::unwrap_or_default) .map_err(FileStoreError::Load) } fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { - persister - .append_changeset(changeset) - .map_err(FileStoreError::Write) + persister.append(changeset).map_err(FileStoreError::Write) } } diff --git a/wallet/src/wallet/tx_builder.rs b/wallet/src/wallet/tx_builder.rs index 7d693761..ea1eacfa 100644 --- a/wallet/src/wallet/tx_builder.rs +++ b/wallet/src/wallet/tx_builder.rs @@ -23,7 +23,8 @@ //! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); //! # let mut wallet = doctest_wallet!(); //! // create a TxBuilder from a wallet -//! let mut tx_builder = wallet.build_tx(); +//! let params = wallet.include_unbroadcasted(); +//! let mut tx_builder = wallet.build_tx(params); //! //! tx_builder //! // Create a transaction with one output to `to_address` of 50_000 satoshi @@ -37,6 +38,7 @@ //! ``` use alloc::{boxed::Box, string::String, vec::Vec}; +use chain::CanonicalizationParams; use core::fmt; use alloc::sync::Arc; @@ -77,7 +79,8 @@ use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo}; /// # let addr2 = addr1.clone(); /// // chaining /// let psbt1 = { -/// let mut builder = wallet.build_tx(); +/// let params = wallet.include_unbroadcasted(); +/// let mut builder = wallet.build_tx(params); /// builder /// .ordering(TxOrdering::Untouched) /// .add_recipient(addr1.script_pubkey(), Amount::from_sat(50_000)) @@ -87,7 +90,8 @@ use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo}; /// /// // non-chaining /// let psbt2 = { -/// let mut builder = wallet.build_tx(); +/// let params = wallet.include_unbroadcasted(); +/// let mut builder = wallet.build_tx(params); /// builder.ordering(TxOrdering::Untouched); /// for addr in &[addr1, addr2] { /// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(50_000)); @@ -140,6 +144,7 @@ pub(crate) struct TxParams { pub(crate) bumping_fee: Option, pub(crate) current_height: Option, pub(crate) allow_dust: bool, + pub(crate) canonicalization_params: CanonicalizationParams, } #[derive(Clone, Copy, Debug)] @@ -246,8 +251,9 @@ impl<'a, Cs> TxBuilder<'a, Cs> { /// let mut path = BTreeMap::new(); /// path.insert("aabbccdd".to_string(), vec![0, 1]); /// + /// let params = wallet.include_unbroadcasted(); /// let builder = wallet - /// .build_tx() + /// .build_tx(params) /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)) /// .policy_path(path, KeychainKind::External); /// @@ -278,7 +284,7 @@ impl<'a, Cs> TxBuilder<'a, Cs> { .iter() .map(|outpoint| { self.wallet - .get_utxo(*outpoint) + .get_utxo(self.params.canonicalization_params.clone(), *outpoint) .ok_or(AddUtxoError::UnknownUtxo(*outpoint)) .map(|output| { ( @@ -647,7 +653,8 @@ impl<'a, Cs> TxBuilder<'a, Cs> { /// .unwrap() /// .assume_checked(); /// # let mut wallet = doctest_wallet!(); - /// let mut tx_builder = wallet.build_tx(); + /// let params = wallet.include_unbroadcasted(); + /// let mut tx_builder = wallet.build_tx(params); /// /// tx_builder /// // Spend all outputs in this wallet. @@ -1103,7 +1110,7 @@ mod test { "5120e8f5c4dc2f5d6a7595e7b108cb063da9c7550312da1e22875d78b9db62b59cd5", ) .unwrap(); - let mut builder = wallet.build_tx(); + let mut builder = wallet.build_tx(wallet.include_unbroadcasted()); builder.add_recipient(recip.clone(), Amount::from_sat(15_000)); builder.fee_absolute(Amount::from_sat(1_000)); let psbt = builder.finish().unwrap(); @@ -1153,15 +1160,23 @@ mod test { // if the transactions were produced by the same wallet the following assert should fail assert_ne!(txid1, txid2); - let utxo1 = wallet1.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); + let utxo1 = wallet1 + .list_unspent(wallet1.include_unbroadcasted()) + .next() + .unwrap(); + let tx1 = wallet1 + .get_tx(wallet1.include_unbroadcasted(), txid1) + .unwrap() + .tx_node + .tx + .clone(); let satisfaction_weight = wallet1 .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() .unwrap(); - let mut builder = wallet2.build_tx(); + let mut builder = wallet2.build_tx(wallet2.include_unbroadcasted()); // add foreign utxo with satisfaction weight x assert!(builder @@ -1225,15 +1240,23 @@ mod test { .create_wallet_no_persist() .expect("descriptors must be valid"); - let utxo1 = wallet1.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); + let utxo1 = wallet1 + .list_unspent(wallet1.include_unbroadcasted()) + .next() + .unwrap(); + let tx1 = wallet1 + .get_tx(wallet1.include_unbroadcasted(), txid1) + .unwrap() + .tx_node + .tx + .clone(); let satisfaction_weight = wallet1 .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() .unwrap(); - let mut builder = wallet2.build_tx(); + let mut builder = wallet2.build_tx(wallet2.include_unbroadcasted()); // Add local UTxO manually, through tx_builder private parameters and not through // add_utxo method because we are assuming wallet2 has not knowledge of utxo1 yet @@ -1298,15 +1321,23 @@ mod test { .create_wallet_no_persist() .expect("descriptors must be valid"); - let utxo1 = wallet1.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); + let utxo1 = wallet1 + .list_unspent(wallet1.include_unbroadcasted()) + .next() + .unwrap(); + let tx1 = wallet1 + .get_tx(wallet1.include_unbroadcasted(), txid1) + .unwrap() + .tx_node + .tx + .clone(); let satisfaction_weight = wallet1 .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() .unwrap(); - let mut builder = wallet2.build_tx(); + let mut builder = wallet2.build_tx(wallet2.include_unbroadcasted()); // add foreign utxo assert!(builder diff --git a/wallet/src/wallet/unbroadcasted.rs b/wallet/src/wallet/unbroadcasted.rs new file mode 100644 index 00000000..ac5b24be --- /dev/null +++ b/wallet/src/wallet/unbroadcasted.rs @@ -0,0 +1,117 @@ +//! Unbroadcasted transaction queue. + +use crate::collections::BTreeMap; + +use crate::collections::HashSet; + +use crate::collections::hash_map; +use crate::collections::HashMap; + +use bitcoin::Txid; +use chain::Merge; +use chain::TxUpdate; + +/// An ordered unbroadcasted list. +/// +/// It is ordered in case of RBF txs. +#[derive(Debug, Clone, Default)] +pub struct Unbroadcasted { + txs: HashMap, + order: HashSet<(u64, Txid)>, + next_seq: u64, +} + +/// Represents changes made to [`Unbroadcasted`]. +#[must_use] +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ChangeSet { + /// Add or remove? + pub txs: BTreeMap<(u64, Txid), bool>, +} + +impl Merge for ChangeSet { + fn merge(&mut self, other: Self) { + self.txs.merge(other.txs); + } + + fn is_empty(&self) -> bool { + self.txs.is_empty() + } +} + +impl Unbroadcasted { + /// Construct [`Unbroadcasted`] from the given `changeset`. + pub fn from_changeset(changeset: ChangeSet) -> Self { + let mut out = Unbroadcasted::default(); + out.apply_changeset(changeset); + out + } + + /// Apply the given `changeset`. + pub fn apply_changeset(&mut self, changeset: ChangeSet) { + for ((_, txid), is_add) in changeset.txs { + if is_add { + let _ = self.insert(txid); + } else { + let _ = self.remove(txid); + } + } + } + + /// Reinserting will bump the tx's seq to `next_seq`. + pub fn insert(&mut self, txid: Txid) -> ChangeSet { + let seq = self.next_seq; + self.next_seq += 1; + + match self.txs.entry(txid) { + hash_map::Entry::Occupied(mut entry) => { + // remove stuff + let entry_seq = entry.get_mut(); + self.order.remove(&(*entry_seq, txid)); + self.order.insert((seq, txid)); + *entry_seq = seq; + } + hash_map::Entry::Vacant(entry) => { + entry.insert(seq); + self.order.insert((seq, txid)); + } + } + + let mut changeset = ChangeSet::default(); + changeset.txs.insert((seq, txid), true); + changeset + } + + /// Untrack the `txid`. + pub fn remove(&mut self, txid: Txid) -> ChangeSet { + let mut changeset = ChangeSet::default(); + if let Some(seq) = self.txs.remove(&txid) { + self.order.remove(&(seq, txid)); + + let seq = self.next_seq; + self.next_seq += 1; + + changeset.txs.insert((seq, txid), false); + } + changeset + } + + /// Untrack transactions that are given anchors and seen-at timestamps. + pub fn update(&mut self, tx_update: &TxUpdate) -> ChangeSet { + let mut changeset = ChangeSet::default(); + for (_, txid) in &tx_update.anchors { + changeset.merge(self.remove(*txid)); + } + for (txid, _) in &tx_update.seen_ats { + changeset.merge(self.remove(*txid)); + } + changeset + } + + /// Txids ordered by precedence. + /// + /// Transactions with greater precedence will appear later in this list. + pub fn txids(&self) -> impl ExactSizeIterator + '_ { + self.order.iter().map(|&(_, txid)| txid) + } +} diff --git a/wallet/tests/psbt.rs b/wallet/tests/psbt.rs index a4d17493..40a36891 100644 --- a/wallet/tests/psbt.rs +++ b/wallet/tests/psbt.rs @@ -12,7 +12,8 @@ fn test_psbt_malformed_psbt_input_legacy() { let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); let mut psbt = builder.finish().unwrap(); psbt.inputs.push(psbt_bip.inputs[0].clone()); @@ -29,7 +30,8 @@ fn test_psbt_malformed_psbt_input_segwit() { let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); let mut psbt = builder.finish().unwrap(); psbt.inputs.push(psbt_bip.inputs[1].clone()); @@ -45,7 +47,8 @@ fn test_psbt_malformed_psbt_input_segwit() { fn test_psbt_malformed_tx_input() { let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); let mut psbt = builder.finish().unwrap(); psbt.unsigned_tx.input.push(TxIn::default()); @@ -61,7 +64,8 @@ fn test_psbt_sign_with_finalized() { let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); let (mut wallet, _) = get_funded_wallet_wpkh(); let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); let mut psbt = builder.finish().unwrap(); @@ -82,7 +86,8 @@ fn test_psbt_fee_rate_with_witness_utxo() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(expected_fee_rate); let mut psbt = builder.finish().unwrap(); @@ -107,7 +112,8 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { let (mut wallet, _) = get_funded_wallet_single("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(expected_fee_rate); let mut psbt = builder.finish().unwrap(); @@ -131,7 +137,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let (mut wpkh_wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wpkh_wallet.peek_address(KeychainKind::External, 0); - let mut builder = wpkh_wallet.build_tx(); + let mut builder = wpkh_wallet.build_tx(wpkh_wallet.include_unbroadcasted()); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(expected_fee_rate); let mut wpkh_psbt = builder.finish().unwrap(); @@ -145,7 +151,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)"; let (mut pkh_wallet, _) = get_funded_wallet(desc, change_desc); let addr = pkh_wallet.peek_address(KeychainKind::External, 0); - let mut builder = pkh_wallet.build_tx(); + let mut builder = pkh_wallet.build_tx(pkh_wallet.include_unbroadcasted()); builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.fee_rate(expected_fee_rate); let mut pkh_psbt = builder.finish().unwrap(); @@ -173,9 +179,10 @@ fn test_psbt_multiple_internalkey_signers() { let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; let (mut wallet, _) = get_funded_wallet(&desc, change_desc); - let to_spend = wallet.balance().total(); + let to_spend = wallet.balance(wallet.include_unbroadcasted()).total(); let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(send_to.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); let unsigned_tx = psbt.unsigned_tx.clone(); diff --git a/wallet/tests/wallet.rs b/wallet/tests/wallet.rs index 35cbd85d..246b1a20 100644 --- a/wallet/tests/wallet.rs +++ b/wallet/tests/wallet.rs @@ -117,8 +117,8 @@ fn wallet_is_persisted() -> anyhow::Result<()> { run( "store.db", - |path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?), - |path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?), + |path| Ok(bdk_file_store::Store::create(DB_MAGIC, path)?), + |path| Ok(bdk_file_store::Store::load(DB_MAGIC, path)?.0), )?; run::( "store.sqlite", @@ -209,12 +209,8 @@ fn wallet_load_checks() -> anyhow::Result<()> { run( "store.db", - |path| { - Ok(bdk_file_store::Store::::create_new( - DB_MAGIC, path, - )?) - }, - |path| Ok(bdk_file_store::Store::::open(DB_MAGIC, path)?), + |path| Ok(bdk_file_store::Store::::create(DB_MAGIC, path)?), + |path| Ok(bdk_file_store::Store::::load(DB_MAGIC, path)?.0), )?; run( "store.sqlite", @@ -272,7 +268,7 @@ fn wallet_should_persist_anchors_and_recover() { anchor: obtained_anchor, .. } = wallet - .get_tx(txid) + .get_tx(wallet.include_unbroadcasted(), txid) .expect("should retrieve stored tx") .chain_position { @@ -383,7 +379,10 @@ fn test_get_funded_wallet_balance() { // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // sats are the transaction fee. - assert_eq!(wallet.balance().confirmed, Amount::from_sat(50_000)); + assert_eq!( + wallet.balance(wallet.include_unbroadcasted()).confirmed, + Amount::from_sat(50_000) + ); } #[test] @@ -391,12 +390,16 @@ fn test_get_funded_wallet_sent_and_received() { let (wallet, txid) = get_funded_wallet_wpkh(); let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet - .transactions() + .transactions(wallet.include_unbroadcasted()) .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node))) .collect(); tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); - let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; + let tx = wallet + .get_tx(wallet.include_unbroadcasted(), txid) + .expect("transaction") + .tx_node + .tx; let (sent, received) = wallet.sent_and_received(&tx); // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 @@ -410,7 +413,11 @@ fn test_get_funded_wallet_sent_and_received() { fn test_get_funded_wallet_tx_fees() { let (wallet, txid) = get_funded_wallet_wpkh(); - let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; + let tx = wallet + .get_tx(wallet.include_unbroadcasted(), txid) + .expect("transaction") + .tx_node + .tx; let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee"); // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 @@ -423,7 +430,11 @@ fn test_get_funded_wallet_tx_fees() { fn test_get_funded_wallet_tx_fee_rate() { let (wallet, txid) = get_funded_wallet_wpkh(); - let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; + let tx = wallet + .get_tx(wallet.include_unbroadcasted(), txid) + .expect("transaction") + .tx_node + .tx; let tx_fee_rate = wallet .calculate_fee_rate(&tx) .expect("transaction fee rate"); @@ -443,7 +454,7 @@ fn test_get_funded_wallet_tx_fee_rate() { fn test_list_output() { let (wallet, txid) = get_funded_wallet_wpkh(); let txos = wallet - .list_output() + .list_output(wallet.include_unbroadcasted()) .map(|op| (op.outpoint, op)) .collect::>(); assert_eq!(txos.len(), 2); @@ -520,7 +531,8 @@ macro_rules! from_str { #[should_panic(expected = "NoRecipients")] fn test_create_tx_empty_recipients() { let (mut wallet, _) = get_funded_wallet_wpkh(); - wallet.build_tx().finish().unwrap(); + let params = wallet.include_unbroadcasted(); + wallet.build_tx(params).finish().unwrap(); } #[test] @@ -528,7 +540,8 @@ fn test_create_tx_empty_recipients() { fn test_create_tx_manually_selected_empty_utxos() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .manually_selected_only(); @@ -539,7 +552,8 @@ fn test_create_tx_manually_selected_empty_utxos() { fn test_create_tx_version_0() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .version(0); @@ -550,7 +564,8 @@ fn test_create_tx_version_0() { fn test_create_tx_version_1_csv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .version(1); @@ -561,7 +576,8 @@ fn test_create_tx_version_1_csv() { fn test_create_tx_custom_version() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .version(42); @@ -575,7 +591,8 @@ fn test_create_tx_default_locktime_is_last_sync_height() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -588,7 +605,8 @@ fn test_create_tx_default_locktime_is_last_sync_height() { fn test_create_tx_fee_sniping_locktime_last_sync() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -604,7 +622,8 @@ fn test_create_tx_fee_sniping_locktime_last_sync() { fn test_create_tx_default_locktime_cltv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -615,7 +634,8 @@ fn test_create_tx_default_locktime_cltv() { fn test_create_tx_locktime_cltv_timestamp() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv_timestamp()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); @@ -630,7 +650,8 @@ fn test_create_tx_locktime_cltv_timestamp() { fn test_create_tx_custom_locktime() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .current_height(630_001) @@ -647,7 +668,8 @@ fn test_create_tx_custom_locktime() { fn test_create_tx_custom_locktime_compatible_with_cltv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .nlocktime(absolute::LockTime::from_height(630_000).unwrap()); @@ -660,7 +682,8 @@ fn test_create_tx_custom_locktime_compatible_with_cltv() { fn test_create_tx_custom_locktime_incompatible_with_cltv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .nlocktime(absolute::LockTime::from_height(50000).unwrap()); @@ -674,7 +697,8 @@ fn test_create_tx_custom_csv() { // desc: wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6))) let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .set_exact_sequence(Sequence(42)) .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -687,7 +711,8 @@ fn test_create_tx_custom_csv() { fn test_create_tx_no_rbf_csv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -698,7 +723,8 @@ fn test_create_tx_no_rbf_csv() { fn test_create_tx_incompatible_csv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .set_exact_sequence(Sequence(3)); @@ -711,7 +737,8 @@ fn test_create_tx_incompatible_csv() { fn test_create_tx_with_default_rbf_csv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); // When CSV is enabled it takes precedence over the rbf value (unless forced by the user). @@ -723,7 +750,8 @@ fn test_create_tx_with_default_rbf_csv() { fn test_create_tx_no_rbf_cltv() { let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.set_exact_sequence(Sequence(0xFFFFFFFE)); let psbt = builder.finish().unwrap(); @@ -735,7 +763,8 @@ fn test_create_tx_no_rbf_cltv() { fn test_create_tx_custom_rbf_sequence() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .set_exact_sequence(Sequence(0xDEADBEEF)); @@ -748,7 +777,8 @@ fn test_create_tx_custom_rbf_sequence() { fn test_create_tx_change_policy() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .do_not_spend_change(); @@ -756,7 +786,8 @@ fn test_create_tx_change_policy() { // wallet has no change, so setting `only_spend_change` // should cause tx building to fail - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .only_spend_change(); @@ -772,7 +803,8 @@ fn test_create_tx_change_policy() { fn test_create_tx_default_sequence() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -792,7 +824,8 @@ macro_rules! check_fee { fn test_create_tx_drain_wallet_and_drain_to() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); @@ -811,7 +844,8 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { .unwrap() .assume_checked(); let drain_addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(20_000)) .drain_to(drain_addr.script_pubkey()) @@ -840,8 +874,12 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { fn test_create_tx_drain_to_and_utxos() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect(); - let mut builder = wallet.build_tx(); + let utxos: Vec<_> = wallet + .list_unspent(wallet.include_unbroadcasted()) + .map(|u| u.outpoint) + .collect(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .add_utxos(&utxos) @@ -861,7 +899,8 @@ fn test_create_tx_drain_to_and_utxos() { fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { let (mut wallet, _) = get_funded_wallet_wpkh(); let drain_addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(drain_addr.script_pubkey()); builder.finish().unwrap(); } @@ -870,7 +909,8 @@ fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { fn test_create_tx_default_fee_rate() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); @@ -882,7 +922,8 @@ fn test_create_tx_default_fee_rate() { fn test_create_tx_custom_fee_rate() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); @@ -896,7 +937,8 @@ fn test_create_tx_custom_fee_rate() { fn test_create_tx_absolute_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .drain_wallet() @@ -916,7 +958,8 @@ fn test_create_tx_absolute_fee() { fn test_create_tx_absolute_zero_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .drain_wallet() @@ -937,7 +980,8 @@ fn test_create_tx_absolute_zero_fee() { fn test_create_tx_absolute_high_fee() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .drain_wallet() @@ -952,7 +996,8 @@ fn test_create_tx_add_change() { let mut rng: StdRng = SeedableRng::from_seed(seed); let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .ordering(TxOrdering::Shuffle); @@ -971,7 +1016,8 @@ fn test_create_tx_add_change() { fn test_create_tx_skip_change_dust() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800)); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); @@ -987,7 +1033,8 @@ fn test_create_tx_drain_to_dust_amount() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); // very high fee rate, so that the only output would be below dust - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .drain_wallet() @@ -1015,7 +1062,8 @@ fn test_create_tx_ordering_respected() { output_sort: Arc::new(bip69_txout_cmp), }; - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)) @@ -1037,7 +1085,8 @@ fn test_create_tx_ordering_respected() { fn test_create_tx_default_sighash() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); let psbt = builder.finish().unwrap(); @@ -1048,7 +1097,8 @@ fn test_create_tx_default_sighash() { fn test_create_tx_custom_sighash() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .sighash(EcdsaSighashType::Single.into()); @@ -1067,7 +1117,8 @@ fn test_create_tx_input_hd_keypaths() { let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1089,7 +1140,8 @@ fn test_create_tx_output_hd_keypaths() { let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1111,7 +1163,8 @@ fn test_create_tx_set_redeem_script_p2sh() { let (mut wallet, _) = get_funded_wallet_single("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1134,7 +1187,8 @@ fn test_create_tx_set_witness_script_p2wsh() { let (mut wallet, _) = get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1156,7 +1210,8 @@ fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() { "sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))", ); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1174,7 +1229,8 @@ fn test_create_tx_non_witness_utxo() { let (mut wallet, _) = get_funded_wallet_single("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1187,7 +1243,8 @@ fn test_create_tx_only_witness_utxo() { let (mut wallet, _) = get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .only_witness_utxo() @@ -1203,7 +1260,8 @@ fn test_create_tx_shwpkh_has_witness_utxo() { let (mut wallet, _) = get_funded_wallet_single("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1215,7 +1273,8 @@ fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() { let (mut wallet, _) = get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -1248,7 +1307,8 @@ fn test_create_tx_add_utxo() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .add_utxo(OutPoint { txid, vout: 0 }) @@ -1295,7 +1355,8 @@ fn test_create_tx_manually_selected_insufficient() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .add_utxo(OutPoint { txid, vout: 0 }) @@ -1312,7 +1373,8 @@ fn test_create_tx_policy_path_required() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)); builder.finish().unwrap(); } @@ -1346,7 +1408,8 @@ fn test_create_tx_policy_path_no_csv() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .policy_path(path, KeychainKind::External); @@ -1367,7 +1430,8 @@ fn test_create_tx_policy_path_use_csv() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .policy_path(path, KeychainKind::External); @@ -1388,7 +1452,8 @@ fn test_create_tx_policy_path_ignored_subtree_with_csv() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) .policy_path(path, KeychainKind::External); @@ -1402,7 +1467,8 @@ fn test_create_tx_global_xpubs_with_origin() { use bitcoin::bip32; let (mut wallet, _) = get_funded_wallet_single("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .add_global_xpubs(); @@ -1515,7 +1581,8 @@ fn test_create_tx_increment_change_index() { // fund wallet receive_output(&mut wallet, amount, ReceiveTo::Mempool(0)); // create tx - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(recipient.clone(), Amount::from_sat(test.to_send)); let res = builder.finish(); if !test.name.contains("error") { @@ -1546,7 +1613,10 @@ fn test_add_foreign_utxo() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let utxo = wallet2.list_unspent().next().expect("must take!"); + let utxo = wallet2 + .list_unspent(wallet2.include_unbroadcasted()) + .next() + .expect("must take!"); let foreign_utxo_satisfaction = wallet2 .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() @@ -1557,7 +1627,8 @@ fn test_add_foreign_utxo() { ..Default::default() }; - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) .only_witness_utxo() @@ -1620,7 +1691,10 @@ fn test_calculate_fee_with_missing_foreign_utxo() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let utxo = wallet2.list_unspent().next().expect("must take!"); + let utxo = wallet2 + .list_unspent(wallet2.include_unbroadcasted()) + .next() + .expect("must take!"); let foreign_utxo_satisfaction = wallet2 .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() @@ -1631,7 +1705,8 @@ fn test_calculate_fee_with_missing_foreign_utxo() { ..Default::default() }; - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) .only_witness_utxo() @@ -1648,13 +1723,18 @@ fn test_calculate_fee_with_missing_foreign_utxo() { #[test] fn test_add_foreign_utxo_invalid_psbt_input() { let (mut wallet, _) = get_funded_wallet_wpkh(); - let outpoint = wallet.list_unspent().next().expect("must exist").outpoint; + let outpoint = wallet + .list_unspent(wallet.include_unbroadcasted()) + .next() + .expect("must exist") + .outpoint; let foreign_utxo_satisfaction = wallet .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() .unwrap(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); let result = builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction); assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo))); @@ -1666,16 +1746,30 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { let (wallet2, txid2) = get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); - let utxo2 = wallet2.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); - let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone(); + let utxo2 = wallet2 + .list_unspent(wallet2.include_unbroadcasted()) + .next() + .unwrap(); + let tx1 = wallet1 + .get_tx(wallet1.include_unbroadcasted(), txid1) + .unwrap() + .tx_node + .tx + .clone(); + let tx2 = wallet2 + .get_tx(wallet2.include_unbroadcasted(), txid2) + .unwrap() + .tx_node + .tx + .clone(); let satisfaction_weight = wallet2 .public_descriptor(KeychainKind::External) .max_weight_to_satisfy() .unwrap(); - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); assert!( builder .add_foreign_utxo( @@ -1712,7 +1806,8 @@ fn test_add_foreign_utxo_only_witness_utxo() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let utxo2 = wallet2.list_unspent().next().unwrap(); + let params = wallet2.include_unbroadcasted(); + let utxo2 = wallet2.list_unspent(params).next().unwrap(); let satisfaction_weight = wallet2 .public_descriptor(KeychainKind::External) @@ -1720,7 +1815,8 @@ fn test_add_foreign_utxo_only_witness_utxo() { .unwrap(); { - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); let psbt_input = psbt::Input { @@ -1737,7 +1833,8 @@ fn test_add_foreign_utxo_only_witness_utxo() { } { - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); let psbt_input = psbt::Input { @@ -1755,10 +1852,15 @@ fn test_add_foreign_utxo_only_witness_utxo() { } { - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); - let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx; + let tx2 = wallet2 + .get_tx(wallet2.include_unbroadcasted(), txid2) + .unwrap() + .tx_node + .tx; let psbt_input = psbt::Input { non_witness_utxo: Some(tx2.as_ref().clone()), ..Default::default() @@ -1777,7 +1879,7 @@ fn test_add_foreign_utxo_only_witness_utxo() { fn test_get_psbt_input() { // this should grab a known good utxo and set the input let (wallet, _) = get_funded_wallet_wpkh(); - for utxo in wallet.list_unspent() { + for utxo in wallet.list_unspent(wallet.include_unbroadcasted()) { let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap(); assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some()); } @@ -1790,7 +1892,8 @@ fn test_get_psbt_input() { fn test_create_tx_global_xpubs_origin_missing() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .add_global_xpubs(); @@ -1802,7 +1905,8 @@ fn test_create_tx_global_xpubs_master_without_origin() { use bitcoin::bip32; let (mut wallet, _) = get_funded_wallet_single("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .add_global_xpubs(); @@ -1823,7 +1927,8 @@ fn test_create_tx_global_xpubs_master_without_origin() { fn test_bump_fee_irreplaceable_tx() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.set_exact_sequence(Sequence(0xFFFFFFFE)); let psbt = builder.finish().unwrap(); @@ -1839,7 +1944,8 @@ fn test_bump_fee_irreplaceable_tx() { fn test_bump_fee_confirmed_tx() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -1861,7 +1967,8 @@ fn test_bump_fee_confirmed_tx() { fn test_bump_fee_low_fee_rate() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -1891,7 +1998,8 @@ fn test_bump_fee_low_fee_rate() { fn test_bump_fee_low_abs() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -1909,7 +2017,8 @@ fn test_bump_fee_low_abs() { fn test_bump_fee_zero_abs() { let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); @@ -1928,7 +2037,8 @@ fn test_bump_fee_reduce_change() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let psbt = builder.finish().unwrap(); let original_sent_received = @@ -2022,7 +2132,8 @@ fn test_bump_fee_reduce_single_recipient() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); let tx = psbt.clone().extract_tx().expect("failed to extract tx"); @@ -2065,7 +2176,8 @@ fn test_bump_fee_absolute_reduce_single_recipient() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); let original_fee = check_fee!(wallet, psbt); @@ -2127,7 +2239,8 @@ fn test_bump_fee_drain_wallet() { .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .add_utxo(OutPoint { @@ -2177,8 +2290,11 @@ fn test_bump_fee_remove_output_manually_selected_only() { }], }; - let position: ChainPosition = - wallet.transactions().last().unwrap().chain_position; + let position: ChainPosition = wallet + .transactions(wallet.include_unbroadcasted()) + .last() + .unwrap() + .chain_position; insert_tx(&mut wallet, init_tx.clone()); match position { ChainPosition::Confirmed { anchor, .. } => { @@ -2194,7 +2310,8 @@ fn test_bump_fee_remove_output_manually_selected_only() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .add_utxo(outpoint) @@ -2229,8 +2346,11 @@ fn test_bump_fee_add_input() { }], }; let txid = init_tx.compute_txid(); - let pos: ChainPosition = - wallet.transactions().last().unwrap().chain_position; + let pos: ChainPosition = wallet + .transactions(wallet.include_unbroadcasted()) + .last() + .unwrap() + .chain_position; insert_tx(&mut wallet, init_tx); match pos { ChainPosition::Confirmed { anchor, .. } => insert_anchor(&mut wallet, txid, anchor), @@ -2240,7 +2360,10 @@ fn test_bump_fee_add_input() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet + .build_tx(params) + .coin_selection(LargestFirstCoinSelection); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx().expect("failed to extract tx"); @@ -2293,7 +2416,10 @@ fn test_bump_fee_absolute_add_input() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet + .build_tx(params) + .coin_selection(LargestFirstCoinSelection); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); let psbt = builder.finish().unwrap(); let tx = psbt.extract_tx().expect("failed to extract tx"); @@ -2349,7 +2475,8 @@ fn test_bump_fee_no_change_add_input_and_change() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .add_utxo(op) @@ -2413,7 +2540,10 @@ fn test_bump_fee_add_input_change_dust() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet + .build_tx(params) + .coin_selection(LargestFirstCoinSelection); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); let psbt = builder.finish().unwrap(); let original_sent_received = @@ -2486,7 +2616,10 @@ fn test_bump_fee_force_add_input() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet + .build_tx(params) + .coin_selection(LargestFirstCoinSelection); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); let psbt = builder.finish().unwrap(); let mut tx = psbt.extract_tx().expect("failed to extract tx"); @@ -2548,7 +2681,10 @@ fn test_bump_fee_absolute_force_add_input() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet + .build_tx(params) + .coin_selection(LargestFirstCoinSelection); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); let psbt = builder.finish().unwrap(); let mut tx = psbt.extract_tx().expect("failed to extract tx"); @@ -2617,7 +2753,8 @@ fn test_bump_fee_unconfirmed_inputs_only() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_wallet().drain_to(addr.script_pubkey()); let psbt = builder.finish().unwrap(); // Now we receive one transaction with 0 confirmations. We won't be able to use that for @@ -2648,7 +2785,8 @@ fn test_bump_fee_unconfirmed_input() { // We receive a tx with 0 confirmations, which will be used as an input // in the drain tx. receive_output(&mut wallet, 25_000, ReceiveTo::Mempool(0)); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_wallet().drain_to(addr.script_pubkey()); let psbt = builder.finish().unwrap(); let mut tx = psbt.extract_tx().expect("failed to extract tx"); @@ -2687,7 +2825,8 @@ fn test_fee_amount_negative_drain_val() { let fee_rate = FeeRate::from_sat_per_kwu(500); let incoming_op = receive_output_in_latest_block(&mut wallet, 8859); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(send_to.script_pubkey(), Amount::from_sat(8630)) .add_utxo(incoming_op) @@ -2704,7 +2843,8 @@ fn test_fee_amount_negative_drain_val() { fn test_sign_single_xprv() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2719,7 +2859,8 @@ fn test_sign_single_xprv() { fn test_sign_single_xprv_with_master_fingerprint_and_path() { let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2734,7 +2875,8 @@ fn test_sign_single_xprv_with_master_fingerprint_and_path() { fn test_sign_single_xprv_bip44_path() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2749,7 +2891,8 @@ fn test_sign_single_xprv_bip44_path() { fn test_sign_single_xprv_sh_wpkh() { let (mut wallet, _) = get_funded_wallet_single("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2765,7 +2908,8 @@ fn test_sign_single_wif() { let (mut wallet, _) = get_funded_wallet_single("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2780,7 +2924,8 @@ fn test_sign_single_wif() { fn test_sign_single_xprv_no_hd_keypaths() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2802,7 +2947,8 @@ fn test_include_output_redeem_witness_script() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)) .include_output_redeem_witness_script(); @@ -2821,7 +2967,8 @@ fn test_signing_only_one_of_multiple_inputs() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)) .include_output_redeem_witness_script(); @@ -2867,7 +3014,8 @@ fn test_try_finalize_sign_option() { for try_finalize in &[true, false] { let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2901,7 +3049,8 @@ fn test_taproot_try_finalize_sign_option() { for try_finalize in &[true, false] { let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); @@ -2950,7 +3099,8 @@ fn test_sign_nonstandard_sighash() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .sighash(sighash.into()) @@ -3199,7 +3349,8 @@ fn test_sending_to_bip350_bech32m_address() { let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); builder.finish().unwrap(); } @@ -3291,7 +3442,8 @@ fn test_taproot_psbt_populate_tap_key_origins() { let (mut wallet, _) = get_funded_wallet(desc, change_desc); let addr = wallet.reveal_next_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -3330,7 +3482,8 @@ fn test_taproot_psbt_populate_tap_key_origins_repeated_key() { .into_iter() .collect(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .drain_wallet() @@ -3393,7 +3546,8 @@ fn test_taproot_psbt_input_tap_tree() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let psbt = builder.finish().unwrap(); @@ -3435,7 +3589,8 @@ fn test_taproot_psbt_input_tap_tree() { fn test_taproot_sign_missing_witness_utxo() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); let witness_utxo = psbt.inputs[0].witness_utxo.take(); @@ -3475,13 +3630,20 @@ fn test_taproot_sign_missing_witness_utxo() { fn test_taproot_sign_using_non_witness_utxo() { let (mut wallet, prev_txid) = get_funded_wallet_single(get_test_tr_single_sig()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); psbt.inputs[0].witness_utxo = None; - psbt.inputs[0].non_witness_utxo = - Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone()); + psbt.inputs[0].non_witness_utxo = Some( + wallet + .get_tx(wallet.include_unbroadcasted(), prev_txid) + .unwrap() + .tx_node + .as_ref() + .clone(), + ); assert!( psbt.inputs[0].non_witness_utxo.is_some(), "Previous tx should be present in the database" @@ -3503,7 +3665,10 @@ fn test_taproot_foreign_utxo() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let utxo = wallet2.list_unspent().next().unwrap(); + let utxo = wallet2 + .list_unspent(wallet2.include_unbroadcasted()) + .next() + .unwrap(); let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap(); let foreign_utxo_satisfaction = wallet2 .public_descriptor(KeychainKind::External) @@ -3515,7 +3680,8 @@ fn test_taproot_foreign_utxo() { "`non_witness_utxo` should never be populated for taproot" ); - let mut builder = wallet1.build_tx(); + let params = wallet1.include_unbroadcasted(); + let mut builder = wallet1.build_tx(params); builder .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) @@ -3544,7 +3710,8 @@ fn test_taproot_foreign_utxo() { fn test_spend_from_wallet(mut wallet: Wallet) { let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); @@ -3569,7 +3736,8 @@ fn test_taproot_no_key_spend() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); @@ -3604,7 +3772,8 @@ fn test_taproot_script_spend_sign_all_leaves() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); @@ -3635,7 +3804,8 @@ fn test_taproot_script_spend_sign_include_some_leaves() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); let mut script_leaves: Vec<_> = psbt.inputs[0] @@ -3675,7 +3845,8 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); let mut script_leaves: Vec<_> = psbt.inputs[0] @@ -3713,7 +3884,8 @@ fn test_taproot_script_spend_sign_no_leaves() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); @@ -3736,7 +3908,8 @@ fn test_taproot_sign_derive_index_from_psbt() { let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); let mut psbt = builder.finish().unwrap(); @@ -3758,7 +3931,8 @@ fn test_taproot_sign_derive_index_from_psbt() { fn test_taproot_sign_explicit_sighash_all() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .sighash(TapSighashType::All.into()) @@ -3778,7 +3952,8 @@ fn test_taproot_sign_non_default_sighash() { let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .sighash(sighash.into()) @@ -3888,7 +4063,7 @@ fn test_spend_coinbase() { let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 2; let maturity_time = confirmation_height + COINBASE_MATURITY - 1; - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); assert_eq!( balance, Balance { @@ -3904,7 +4079,8 @@ fn test_spend_coinbase() { let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), balance.immature / 2) .current_height(confirmation_height); @@ -3919,7 +4095,8 @@ fn test_spend_coinbase() { )); // Still unspendable... - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), balance.immature / 2) .current_height(not_yet_mature_time); @@ -3940,7 +4117,7 @@ fn test_spend_coinbase() { hash: BlockHash::all_zeros(), }, ); - let balance = wallet.balance(); + let balance = wallet.balance(wallet.include_unbroadcasted()); assert_eq!( balance, Balance { @@ -3950,7 +4127,8 @@ fn test_spend_coinbase() { confirmed: Amount::from_sat(25_000) } ); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .add_recipient(addr.script_pubkey(), balance.confirmed / 2) .current_height(maturity_time); @@ -3963,7 +4141,8 @@ fn test_allow_dust_limit() { let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::ZERO); @@ -3972,7 +4151,8 @@ fn test_allow_dust_limit() { Err(CreateTxError::OutputBelowDustLimit(0)) ); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .allow_dust(true) @@ -3989,7 +4169,8 @@ fn test_fee_rate_sign_no_grinding_high_r() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.next_unused_address(KeychainKind::External); let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); let mut data = PushBytesBuf::try_from(vec![0]).unwrap(); builder .drain_to(addr.script_pubkey()) @@ -4056,7 +4237,8 @@ fn test_fee_rate_sign_grinding_low_r() { let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.next_unused_address(KeychainKind::External); let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder .drain_to(addr.script_pubkey()) .drain_wallet() @@ -4119,7 +4301,10 @@ fn test_keychains_with_overlapping_spks() { let non_wildcard_keychain = "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/1)"; let (mut wallet, _) = get_funded_wallet(wildcard_keychain, non_wildcard_keychain); - assert_eq!(wallet.balance().confirmed, Amount::from_sat(50000)); + assert_eq!( + wallet.balance(wallet.include_unbroadcasted()).confirmed, + Amount::from_sat(50000) + ); let addr = wallet .reveal_addresses_to(KeychainKind::External, 1) @@ -4134,7 +4319,10 @@ fn test_keychains_with_overlapping_spks() { confirmation_time: 0, }; let _outpoint = receive_output_to_address(&mut wallet, addr, 8000, anchor); - assert_eq!(wallet.balance().confirmed, Amount::from_sat(58000)); + assert_eq!( + wallet.balance(wallet.include_unbroadcasted()).confirmed, + Amount::from_sat(58000) + ); } #[test] @@ -4145,7 +4333,8 @@ fn test_tx_cancellation() { let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") .unwrap() .assume_checked(); - let mut builder = $wallet.build_tx(); + let params = $wallet.include_unbroadcasted(); + let mut builder = $wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)); let psbt = builder.finish().unwrap(); @@ -4228,14 +4417,17 @@ fn single_descriptor_wallet_can_create_tx_and_receive_change() { let addr = Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm") .unwrap() .assume_checked(); - let mut builder = wallet.build_tx(); + let params = wallet.include_unbroadcasted(); + let mut builder = wallet.build_tx(params); builder.add_recipient(addr.script_pubkey(), amt); let mut psbt = builder.finish().unwrap(); assert!(wallet.sign(&mut psbt, SignOptions::default()).unwrap()); let tx = psbt.extract_tx().unwrap(); let _txid = tx.compute_txid(); insert_tx(&mut wallet, tx); - let unspent: Vec<_> = wallet.list_unspent().collect(); + let unspent: Vec<_> = wallet + .list_unspent(wallet.include_unbroadcasted()) + .collect(); assert_eq!(unspent.len(), 1); let utxo = unspent.first().unwrap(); assert!(utxo.txout.value < amt); @@ -4252,8 +4444,10 @@ fn test_transactions_sort_by() { receive_output(&mut wallet, 25_000, ReceiveTo::Mempool(0)); // sort by chain position, unconfirmed then confirmed by descending block height - let sorted_txs: Vec = - wallet.transactions_sort_by(|t1, t2| t2.chain_position.cmp(&t1.chain_position)); + let sorted_txs: Vec = wallet + .transactions_sort_by(wallet.include_unbroadcasted(), |t1, t2| { + t2.chain_position.cmp(&t1.chain_position) + }); let conf_heights: Vec> = sorted_txs .iter() .map(|tx| tx.chain_position.confirmation_height_upper_bound()) @@ -4264,18 +4458,25 @@ fn test_transactions_sort_by() { #[test] fn test_tx_builder_is_send_safe() { let (mut wallet, _txid) = get_funded_wallet_wpkh(); - let _box: Box = Box::new(wallet.build_tx()); + let params = wallet.include_unbroadcasted(); + let _box: Box = Box::new(wallet.build_tx(params)); } #[test] fn test_wallet_transactions_relevant() { let (mut test_wallet, _txid) = get_funded_wallet_wpkh(); - let relevant_tx_count_before = test_wallet.transactions().count(); + let relevant_tx_count_before = test_wallet + .transactions(test_wallet.include_unbroadcasted()) + .count(); let full_tx_count_before = test_wallet.tx_graph().full_txs().count(); let chain_tip = test_wallet.local_chain().tip().block_id(); let canonical_tx_count_before = test_wallet .tx_graph() - .list_canonical_txs(test_wallet.local_chain(), chain_tip) + .list_canonical_txs( + test_wallet.local_chain(), + chain_tip, + test_wallet.include_unbroadcasted(), + ) .count(); // add not relevant transaction to test wallet @@ -4288,20 +4489,30 @@ fn test_wallet_transactions_relevant() { test_wallet.apply_update(test_wallet_update).unwrap(); // verify transaction from other wallet was added but is not in relevant transactions list. - let relevant_tx_count_after = test_wallet.transactions().count(); + let relevant_tx_count_after = test_wallet + .transactions(test_wallet.include_unbroadcasted()) + .count(); let full_tx_count_after = test_wallet.tx_graph().full_txs().count(); let canonical_tx_count_after = test_wallet .tx_graph() - .list_canonical_txs(test_wallet.local_chain(), chain_tip) + .list_canonical_txs( + test_wallet.local_chain(), + chain_tip, + test_wallet.include_unbroadcasted(), + ) .count(); assert_eq!(relevant_tx_count_before, relevant_tx_count_after); assert!(!test_wallet - .transactions() + .transactions(test_wallet.include_unbroadcasted()) .any(|wallet_tx| wallet_tx.tx_node.txid == other_txid)); assert!(test_wallet .tx_graph() - .list_canonical_txs(test_wallet.local_chain(), chain_tip) + .list_canonical_txs( + test_wallet.local_chain(), + chain_tip, + test_wallet.include_unbroadcasted() + ) .any(|wallet_tx| wallet_tx.tx_node.txid == other_txid)); assert!(full_tx_count_before < full_tx_count_after); assert!(canonical_tx_count_before < canonical_tx_count_after);