From 3545b48dbce53a5297b36f0c8e76bb18c16b6b38 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Mon, 9 Dec 2024 23:13:03 +0800 Subject: [PATCH] test(wallet): test redefined relevant txs WIP --- crates/wallet/Cargo.toml | 4 ++ crates/wallet/tests/wallet.rs | 117 ++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/crates/wallet/Cargo.toml b/crates/wallet/Cargo.toml index 859c712a0..04b6cf31f 100644 --- a/crates/wallet/Cargo.toml +++ b/crates/wallet/Cargo.toml @@ -47,6 +47,10 @@ bdk_file_store = { path = "../file_store" } anyhow = "1" rand = "^0.8" +# temporary dev-dependencies for testing purposes +bdk_bitcoind_rpc = { path = "../bitcoind_rpc" } +bdk_testenv = { path = "../testenv" } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 05baa5c33..417df107a 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -4196,8 +4196,125 @@ fn test_transactions_sort_by() { assert_eq!([None, Some(2000), Some(1000)], conf_heights.as_slice()); } +use std::collections::HashMap; + +use bdk_bitcoind_rpc::Emitter; +use bdk_testenv::bitcoincore_rpc::json::CreateRawTransactionInput; +use bdk_testenv::bitcoincore_rpc::{RawTx, RpcApi}; +use bdk_testenv::TestEnv; +use bdk_wallet::CreateParams; + #[test] fn test_tx_builder_is_send_safe() { let (mut wallet, _txid) = get_funded_wallet_wpkh(); let _box: Box = Box::new(wallet.build_tx()); } + +// ErikDeSmedt's double spend test scenario +#[test] +fn detect_double_spend() -> anyhow::Result<()> { + // Create a test environment and mine initial funds + let env = TestEnv::new().expect("Failed to launch bitcoind"); + let client = env + .bitcoind + .create_wallet("") + .expect("Failed to create wallet"); + let address = client + .get_new_address(None, None) + .expect("Failed to create address"); + client + .generate_to_address(106, address.assume_checked_ref()) + .expect("Failed to mine block"); + + // Create the wallet under test + let mut alice = { + const DESC: &str ="tr(tprv8ZgxMBicQKsPdnxnfzsvrUZ58eNTq85PA6nhJALQiGy9GVhcvXmHX2r9znpyApMVNLdkPBp3WArLgU3UnA6npK9TtGoZDKdAjjkoYm3rY7F/84'/0'/0'/0/*)"; + Wallet::create_with_params( + CreateParams::new_single(DESC).network(Network::Regtest) + ) + } + .expect("Wallet can be created"); + + // Sync it with the chain + let mut emitter = Emitter::new(&env.bitcoind.client, alice.latest_checkpoint(), 0); + while let Some(ev) = emitter.next_block().unwrap() { + alice + .apply_block_connected_to(&ev.block, ev.block_height(), ev.connected_to()) + .unwrap(); + } + + // Creates some transactions + let unspent = client + .list_unspent(None, None, None, Some(false), None) + .unwrap(); + + let input_amount = unspent[0].amount; + let destination_amount = Amount::from_sat(100_000); + let fee_amount = Amount::from_sat(2_000); + let change_amount = input_amount - destination_amount - fee_amount; + + // Create a transaction that pays Alice + let address = alice.reveal_next_address(KeychainKind::External).address; + let change_address = client.get_new_address(None, None).unwrap().assume_checked(); + let inputs = [CreateRawTransactionInput { + txid: unspent[0].txid, + vout: unspent[0].vout, + sequence: Some(0xFFFFFFFE), + }]; + let mut outputs = HashMap::new(); + outputs.insert(address.to_string(), destination_amount); + outputs.insert(change_address.to_string(), change_amount); + let tx1a = client + .create_raw_transaction(&inputs, &outputs, None, None) + .unwrap(); + let tx1a = client + .sign_raw_transaction_with_wallet(tx1a.raw_hex(), None, None) + .unwrap() + .transaction() + .unwrap(); + + // Create a double-spent of tx1a + let address = client.get_new_address(None, None).unwrap().assume_checked(); + let mut outputs = HashMap::new(); + outputs.insert(address.to_string(), destination_amount); + outputs.insert(change_address.to_string(), change_amount); + let tx1b = client + .create_raw_transaction(&inputs, &outputs, None, None) + .unwrap(); + let tx1b: Transaction = client + .sign_raw_transaction_with_wallet(tx1b.raw_hex(), None, None) + .unwrap() + .transaction() + .unwrap(); + + // Alice observes tx1a in the mempool + alice.apply_unconfirmed_txs(vec![(tx1a.clone(), 100)]); + + // A block is create + // In this block tx1a is doublespent by tx1b + client.send_raw_transaction(tx1b.raw_hex()).unwrap(); + let address = client + .get_new_address(None, None) + .expect("Failed to create address"); + client + .generate_to_address(6, address.assume_checked_ref()) + .expect("Failed to mine block"); + + // Apply the block to the w1 + let mut emitter = Emitter::new(&env.bitcoind.client, alice.latest_checkpoint(), 0); + while let Some(ev) = emitter.next_block().unwrap() { + alice + .apply_block_connected_to(&ev.block, ev.block_height(), ev.connected_to()) + .unwrap(); + } + + // We also add txb do the wallet + alice.apply_unconfirmed_txs([(tx1b.clone(), 101)]); + + // I expect list_unspent to be empty. + // (tx1a) was double-spent + assert_eq!(alice.list_unspent().collect::>(), vec![]); + //assert_eq!(alice.transactions().collect::>(), vec![]); + + Ok(()) +}