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"]
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`].
///
--- /dev/null
+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<Self, anyhow::Error> {
+ 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<Address>,
+ ) -> anyhow::Result<Vec<BlockHash>> {
+ 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<Txid> = 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(())
+}
--- /dev/null
+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<Self, anyhow::Error> {
+ 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<Address>,
+ ) -> anyhow::Result<Vec<BlockHash>> {
+ 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<Txid> = 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(())
+}