diff --git a/Cargo.toml b/Cargo.toml index 1d34408..32ac6f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ esplora-client = { version = "0.4", default-features = false, optional = true } log = "^0.4" [features] -use-esplora-blocking = ["esplora-client/blocking"] +use-esplora-blocking = ["esplora-client/blocking", "bdk/use-esplora-blocking"] [dev-dependencies] rstest = "^0.11" bdk-testutils = "^0.4" bdk = { version = "0.28", default-features = true } -electrsd = { version = "0.23", features = ["bitcoind_22_0", "electrs_0_9_1"] } +electrsd = { version = "0.23.1", features = ["bitcoind_23_0", "esplora_a33e97e1", "legacy"] } diff --git a/tests/point_in_time.rs b/tests/point_in_time.rs new file mode 100644 index 0000000..f137500 --- /dev/null +++ b/tests/point_in_time.rs @@ -0,0 +1,83 @@ +#[cfg(feature = "use-esplora-blocking" )] +mod regtestenv; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::bitcoin::Network; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::blockchain::{Blockchain, GetHeight}; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::database::memory::MemoryDatabase; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::wallet::{SyncOptions, Wallet}; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::Error; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::SignOptions; +#[cfg(feature = "use-esplora-blocking" )] +use bdk_reserves::reserves::*; +#[cfg(feature = "use-esplora-blocking" )] +use bdk::blockchain::esplora::EsploraBlockchain; +#[cfg(feature = "use-esplora-blocking" )] +use esplora_client::Builder; +#[cfg(feature = "use-esplora-blocking" )] +use electrsd::bitcoind::bitcoincore_rpc::bitcoin::Address; +#[cfg(feature = "use-esplora-blocking" )] +use regtestenv::RegTestEnv; +#[cfg(feature = "use-esplora-blocking" )] +use std::str::FromStr; + +fn construct_wallet(desc: &str, network: Network) -> Result, Error> { + let wallet = Wallet::new(desc, None, network, MemoryDatabase::default())?; + + Ok(wallet) +} + +#[test] +#[cfg(feature = "use-esplora-blocking" )] +fn point_in_time() { + let wallet = construct_wallet( + "wpkh(cTTgG6x13nQjAeECaCaDrjrUdcjReZBGspcmNavsnSRyXq7zXT7r)", + Network::Regtest, + ) + .unwrap(); + + let regtestenv = RegTestEnv::new(); + regtestenv.generate(&[&wallet]); + let esplora_url = format!("http://{}", regtestenv.esplora_url().as_ref().unwrap()); + let client = Builder::new(&esplora_url).build_blocking().unwrap(); + let blockchain = EsploraBlockchain::from_client(client, 20); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + let old_height = blockchain.get_height().unwrap(); + let old_balance = wallet.get_balance().unwrap(); + + let message = "This belonged to me."; + let mut psbt = wallet.create_proof(message).unwrap(); + let signopts = SignOptions { + trust_witness_utxo: true, + ..Default::default() + }; + let finalized = wallet.sign(&mut psbt, signopts.clone()).unwrap(); + let proof = psbt; + assert!(finalized); + + const MY_FOREIGN_ADDR: &str = "mpSFfNURcFTz2yJxBzRY9NhnozxeJ2AUC8"; + let foreign_addr = Address::from_str(MY_FOREIGN_ADDR).unwrap(); + let mut builder = wallet.build_tx(); + builder + .add_recipient(foreign_addr.script_pubkey(), 1_000) + .fee_rate(bdk::FeeRate::from_sat_per_vb(2.0)); + let (mut psbt, _) = builder.finish().unwrap(); + let finalized = wallet.sign(&mut psbt, signopts).unwrap(); + assert!(finalized); + blockchain.broadcast(&psbt.extract_tx()).unwrap(); + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); + + regtestenv.generate_to_address(6, &foreign_addr); + + let new_balance = wallet.get_balance().unwrap(); + assert_ne!(old_balance, new_balance); + + let spendable = proof + .verify_reserve_proof(message, WalletAtHeight::new(&wallet, old_height)) + .unwrap(); + assert_eq!(spendable, old_balance.confirmed); +} diff --git a/tests/regtestenv.rs b/tests/regtestenv.rs index 5bcb387..6c14e31 100644 --- a/tests/regtestenv.rs +++ b/tests/regtestenv.rs @@ -30,6 +30,7 @@ impl RegTestEnv { let mut elect_conf = electrsd::Conf::default(); elect_conf.view_stderr = false; // setting this to true will lead to very verbose logging + elect_conf.http_enabled = true; let elect_exe = electrsd::downloaded_exe_path().expect("We should always have downloaded path"); let electrsd = ElectrsD::with_conf(elect_exe, &bitcoind, &elect_conf).unwrap(); @@ -37,12 +38,18 @@ impl RegTestEnv { RegTestEnv { bitcoind, electrsd } } - /// returns the URL where a client can connect to the embedded electrum server + /// returns the URL where an electrum client can connect to the embedded electrum server pub fn electrum_url(&self) -> &str { &self.electrsd.electrum_url } + /// returns the URL where an esplora client can connect to the embedded esplora server + pub fn esplora_url(&self) -> &Option { + &self.electrsd.esplora_url + } + /// generates some blocks to have some coins to test with + /// @wallets: either a single wallet, or all the signer wallets that belong to the same multisig. pub fn generate(&self, wallets: &[&Wallet]) { let addr2 = wallets[0].get_address(AddressIndex::Peek(1)).unwrap(); let addr1 = wallets[0].get_address(AddressIndex::Peek(0)).unwrap(); @@ -89,7 +96,7 @@ impl RegTestEnv { .for_each(|wallet| wallet.sync(&blockchain, SyncOptions::default()).unwrap()); } - fn generate_to_address(&self, blocks: usize, address: &Address) { + pub fn generate_to_address(&self, blocks: usize, address: &Address) { let old_height = self .electrsd .client