diff --git a/crates/esplora/Cargo.toml b/crates/esplora/Cargo.toml index 33c60104a..49af68a99 100644 --- a/crates/esplora/Cargo.toml +++ b/crates/esplora/Cargo.toml @@ -21,6 +21,10 @@ futures = { version = "0.3.26", optional = true } bitcoin = { version = "0.30.0", optional = true, default-features = false } miniscript = { version = "10.0.0", optional = true, default-features = false } +[dev-dependencies] +electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } + [features] default = ["std", "async-https", "blocking"] std = ["bdk_chain/std"] diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index a0c3d9d3b..98c47253f 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -21,8 +21,8 @@ use crate::{anchor_from_status, ASSUME_FINAL_DEPTH}; pub trait EsploraAsyncExt { /// Prepare an [`LocalChain`] update with blocks fetched from Esplora. /// - /// * `prev_tip` is the previous tip of [`LocalChain::tip`]. - /// * `get_heights` is the block heights that we are interested in fetching from Esplora. + /// * `local_tip` is the previous tip of [`LocalChain::tip`]. + /// * `request_heights` is the block heights that we are interested in fetching from Esplora. /// /// The result of this method can be applied to [`LocalChain::apply_update`]. /// diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs new file mode 100644 index 000000000..3b64d7bee --- /dev/null +++ b/crates/esplora/tests/async_ext.rs @@ -0,0 +1,117 @@ +use bdk_esplora::EsploraAsyncExt; +use electrsd::bitcoind::bitcoincore_rpc::RpcApi; +use electrsd::bitcoind::{self, anyhow, BitcoinD}; +use electrsd::{Conf, ElectrsD}; +use esplora_client::{self, AsyncClient, Builder}; +use std::str::FromStr; +use std::thread::sleep; +use std::time::Duration; + +use bdk_chain::bitcoin::{Address, Amount, BlockHash, Txid}; + +struct TestEnv { + bitcoind: BitcoinD, + #[allow(dead_code)] + electrsd: ElectrsD, + client: AsyncClient, +} + +impl TestEnv { + fn new() -> Result { + let bitcoind_exe = + bitcoind::downloaded_exe_path().expect("bitcoind version feature must be enabled"); + let bitcoind = BitcoinD::new(bitcoind_exe).unwrap(); + + let mut electrs_conf = Conf::default(); + electrs_conf.http_enabled = true; + let electrs_exe = + electrsd::downloaded_exe_path().expect("electrs version feature must be enabled"); + let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &electrs_conf)?; + + let base_url = format!("http://{}", &electrsd.esplora_url.clone().unwrap()); + let client = Builder::new(base_url.as_str()).build_async()?; + + Ok(Self { + bitcoind, + electrsd, + client, + }) + } + + fn mine_blocks( + &self, + count: usize, + address: Option
, + ) -> anyhow::Result> { + let coinbase_address = match address { + Some(address) => address, + None => self + .bitcoind + .client + .get_new_address(None, None)? + .assume_checked(), + }; + let block_hashes = self + .bitcoind + .client + .generate_to_address(count as _, &coinbase_address)?; + Ok(block_hashes) + } +} + +#[tokio::test] +pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { + let env = TestEnv::new()?; + let receive_address0 = + Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm")?.assume_checked(); + let receive_address1 = + Address::from_str("bcrt1qfjg5lv3dvc9az8patec8fjddrs4aqtauadnagr")?.assume_checked(); + + let misc_spks = [ + receive_address0.script_pubkey(), + receive_address1.script_pubkey(), + ]; + + let _block_hashes = env.mine_blocks(101, None)?; + let txid1 = env.bitcoind.client.send_to_address( + &receive_address1, + Amount::from_sat(10000), + None, + None, + None, + None, + Some(1), + None, + )?; + let txid2 = env.bitcoind.client.send_to_address( + &receive_address0, + Amount::from_sat(20000), + None, + None, + None, + None, + Some(1), + None, + )?; + let _block_hashes = env.mine_blocks(1, None)?; + while env.client.get_height().await.unwrap() < 102 { + sleep(Duration::from_millis(10)) + } + + let graph_update = env + .client + .scan_txs( + misc_spks.into_iter(), + vec![].into_iter(), + vec![].into_iter(), + 1, + ) + .await?; + + let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect(); + graph_update_txids.sort(); + let mut expected_txids = vec![txid1, txid2]; + expected_txids.sort(); + assert_eq!(graph_update_txids, expected_txids); + Ok(()) +} diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs new file mode 100644 index 000000000..6c319945b --- /dev/null +++ b/crates/esplora/tests/blocking_ext.rs @@ -0,0 +1,114 @@ +use bdk_esplora::EsploraExt; +use electrsd::bitcoind::bitcoincore_rpc::RpcApi; +use electrsd::bitcoind::{self, anyhow, BitcoinD}; +use electrsd::{Conf, ElectrsD}; +use esplora_client::{self, BlockingClient, Builder}; +use std::str::FromStr; +use std::thread::sleep; +use std::time::Duration; + +use bdk_chain::bitcoin::{Address, Amount, BlockHash, Txid}; + +struct TestEnv { + bitcoind: BitcoinD, + #[allow(dead_code)] + electrsd: ElectrsD, + client: BlockingClient, +} + +impl TestEnv { + fn new() -> Result { + let bitcoind_exe = + bitcoind::downloaded_exe_path().expect("bitcoind version feature must be enabled"); + let bitcoind = BitcoinD::new(bitcoind_exe).unwrap(); + + let mut electrs_conf = Conf::default(); + electrs_conf.http_enabled = true; + let electrs_exe = + electrsd::downloaded_exe_path().expect("electrs version feature must be enabled"); + let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &electrs_conf)?; + + let base_url = format!("http://{}", &electrsd.esplora_url.clone().unwrap()); + let client = Builder::new(base_url.as_str()).build_blocking()?; + + Ok(Self { + bitcoind, + electrsd, + client, + }) + } + + fn mine_blocks( + &self, + count: usize, + address: Option
, + ) -> anyhow::Result> { + let coinbase_address = match address { + Some(address) => address, + None => self + .bitcoind + .client + .get_new_address(None, None)? + .assume_checked(), + }; + let block_hashes = self + .bitcoind + .client + .generate_to_address(count as _, &coinbase_address)?; + Ok(block_hashes) + } +} + +#[test] +pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { + let env = TestEnv::new()?; + let receive_address0 = + Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm")?.assume_checked(); + let receive_address1 = + Address::from_str("bcrt1qfjg5lv3dvc9az8patec8fjddrs4aqtauadnagr")?.assume_checked(); + + let misc_spks = [ + receive_address0.script_pubkey(), + receive_address1.script_pubkey(), + ]; + + let _block_hashes = env.mine_blocks(101, None)?; + let txid1 = env.bitcoind.client.send_to_address( + &receive_address1, + Amount::from_sat(10000), + None, + None, + None, + None, + Some(1), + None, + )?; + let txid2 = env.bitcoind.client.send_to_address( + &receive_address0, + Amount::from_sat(20000), + None, + None, + None, + None, + Some(1), + None, + )?; + let _block_hashes = env.mine_blocks(1, None)?; + while env.client.get_height().unwrap() < 102 { + sleep(Duration::from_millis(10)) + } + + let graph_update = env.client.scan_txs( + misc_spks.into_iter(), + vec![].into_iter(), + vec![].into_iter(), + 1, + )?; + + let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect(); + graph_update_txids.sort(); + let mut expected_txids = vec![txid1, txid2]; + expected_txids.sort(); + assert_eq!(graph_update_txids, expected_txids); + Ok(()) +}