diff --git a/crates/bitcoind_rpc/Cargo.toml b/crates/bitcoind_rpc/Cargo.toml index 80e4a219c5..7ede3f7192 100644 --- a/crates/bitcoind_rpc/Cargo.toml +++ b/crates/bitcoind_rpc/Cargo.toml @@ -20,7 +20,6 @@ bdk_chain = { path = "../chain", version = "0.6", default-features = false } [dev-dependencies] testenv = { path = "../testenv", version = "0.1.0", default_features = false } -bitcoind = { version = "0.33", features = ["25_0"] } anyhow = { version = "1" } [features] diff --git a/crates/bitcoind_rpc/tests/test_emitter.rs b/crates/bitcoind_rpc/tests/test_emitter.rs index 3264b96d79..c27fbcae43 100644 --- a/crates/bitcoind_rpc/tests/test_emitter.rs +++ b/crates/bitcoind_rpc/tests/test_emitter.rs @@ -43,15 +43,25 @@ fn block_to_chain_update(block: &bitcoin::Block, height: u32) -> local_chain::Up pub fn test_sync_local_chain() -> anyhow::Result<()> { let env = TestEnv::new()?; let mut local_chain = LocalChain::default(); - let mut emitter = Emitter::from_height(&env.client, 0); + let tip = env.rpc_client().get_block_count()?; + let mut emitter = Emitter::from_height(env.rpc_client(), tip as u32); // mine some blocks and returned the actual block hashes let exp_hashes = { - let mut hashes = vec![env.client.get_block_hash(0)?]; // include genesis block - hashes.extend(env.mine_blocks(101, None)?); + // add the hashes of the genesis block and block generated from initializing electrsd + let mut hashes = (0..=tip) + .map(|height| env.rpc_client().get_block_hash(height)) + .collect::, _>>()?; + hashes.extend(env.mine_blocks(101 - tip as usize, None)?); hashes }; + // update `local_chain` with block generated from initializing electrsd + for (height, hash) in exp_hashes.iter().enumerate().take(tip as usize) { + let changeset = BTreeMap::from([(height as u32, Some(*hash))]); + local_chain.apply_changeset(&changeset); + } + // see if the emitter outputs the right blocks println!("first sync:"); while let Some((height, block)) = emitter.next_block()? { @@ -141,9 +151,18 @@ fn test_into_tx_graph() -> anyhow::Result<()> { let env = TestEnv::new()?; println!("getting new addresses!"); - let addr_0 = env.client.get_new_address(None, None)?.assume_checked(); - let addr_1 = env.client.get_new_address(None, None)?.assume_checked(); - let addr_2 = env.client.get_new_address(None, None)?.assume_checked(); + let addr_0 = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); + let addr_1 = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); + let addr_2 = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); println!("got new addresses!"); println!("mining block!"); @@ -159,7 +178,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> { index }); - let emitter = &mut Emitter::from_height(&env.client, 0); + let emitter = &mut Emitter::from_height(env.rpc_client(), 0); while let Some((height, block)) = emitter.next_block()? { let _ = chain.apply_update(block_to_chain_update(&block, height))?; @@ -171,7 +190,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> { let exp_txids = { let mut txids = BTreeSet::new(); for _ in 0..3 { - txids.insert(env.client.send_to_address( + txids.insert(env.rpc_client().send_to_address( &addr_0, Amount::from_sat(10_000), None, @@ -207,7 +226,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> { // mine a block that confirms the 3 txs let exp_block_hash = env.mine_blocks(1, None)?[0]; - let exp_block_height = env.client.get_block_info(&exp_block_hash)?.height as u32; + let exp_block_height = env.rpc_client().get_block_info(&exp_block_hash)?.height as u32; let exp_anchors = exp_txids .iter() .map({ @@ -247,13 +266,13 @@ fn ensure_block_emitted_after_reorg_is_at_reorg_height() -> anyhow::Result<()> { const CHAIN_TIP_HEIGHT: usize = 110; let env = TestEnv::new()?; - let mut emitter = Emitter::from_height(&env.client, EMITTER_START_HEIGHT as _); + let mut emitter = Emitter::from_height(env.rpc_client(), EMITTER_START_HEIGHT as _); env.mine_blocks(CHAIN_TIP_HEIGHT, None)?; while emitter.next_header()?.is_some() {} for reorg_count in 1..=10 { - let replaced_blocks = env.reorg_empty_blocks(reorg_count)?; + let replaced_blocks = env.reorg_empty_blocks(reorg_count, &env.bitcoind)?; let (height, next_header) = emitter.next_header()?.expect("must emit block after reorg"); assert_eq!( (height as usize, next_header.block_hash()), @@ -315,10 +334,13 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { const SEND_AMOUNT: Amount = Amount::from_sat(10_000); let env = TestEnv::new()?; - let mut emitter = Emitter::from_height(&env.client, 0); + let mut emitter = Emitter::from_height(env.rpc_client(), 0); // setup addresses - let addr_to_mine = env.client.get_new_address(None, None)?.assume_checked(); + let addr_to_mine = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros()); let addr_to_track = Address::from_script(&spk_to_track, bitcoin::Network::Regtest)?; @@ -339,7 +361,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { // lock outputs that send to `addr_to_track` let outpoints_to_lock = env - .client + .rpc_client() .get_transaction(&txid, None)? .transaction()? .output @@ -348,7 +370,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { .filter(|(_, txo)| txo.script_pubkey == spk_to_track) .map(|(vout, _)| OutPoint::new(txid, vout as _)) .collect::>(); - env.client.lock_unspent(&outpoints_to_lock)?; + env.rpc_client().lock_unspent(&outpoints_to_lock)?; let _ = env.mine_blocks(1, None)?; } @@ -367,7 +389,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> { // perform reorgs with different depths for reorg_count in 1..=ADDITIONAL_COUNT { - env.reorg_empty_blocks(reorg_count)?; + env.reorg_empty_blocks(reorg_count, &env.bitcoind)?; sync_from_emitter(&mut recv_chain, &mut recv_graph, &mut emitter)?; assert_eq!( @@ -396,10 +418,13 @@ fn mempool_avoids_re_emission() -> anyhow::Result<()> { const MEMPOOL_TX_COUNT: usize = 2; let env = TestEnv::new()?; - let mut emitter = Emitter::from_height(&env.client, 0); + let mut emitter = Emitter::from_height(env.rpc_client(), 0); // mine blocks and sync up emitter - let addr = env.client.get_new_address(None, None)?.assume_checked(); + let addr = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); env.mine_blocks(BLOCKS_TO_MINE, Some(addr.clone()))?; while emitter.next_header()?.is_some() {} @@ -451,10 +476,13 @@ fn mempool_re_emits_if_tx_introduction_height_not_reached() -> anyhow::Result<() const MEMPOOL_TX_COUNT: usize = 21; let env = TestEnv::new()?; - let mut emitter = Emitter::from_height(&env.client, 0); + let mut emitter = Emitter::from_height(env.rpc_client(), 0); // mine blocks to get initial balance, sync emitter up to tip - let addr = env.client.get_new_address(None, None)?.assume_checked(); + let addr = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); env.mine_blocks(PREMINE_COUNT, Some(addr.clone()))?; while emitter.next_header()?.is_some() {} @@ -528,10 +556,13 @@ fn mempool_during_reorg() -> anyhow::Result<()> { const PREMINE_COUNT: usize = 101; let env = TestEnv::new()?; - let mut emitter = Emitter::from_height(&env.client, 0); + let mut emitter = Emitter::from_height(env.rpc_client(), 0); // mine blocks to get initial balance - let addr = env.client.get_new_address(None, None)?.assume_checked(); + let addr = env + .rpc_client() + .get_new_address(None, None)? + .assume_checked(); env.mine_blocks(PREMINE_COUNT, Some(addr.clone()))?; // introduce mempool tx at each block extension @@ -549,7 +580,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> { .into_iter() .map(|(tx, _)| tx.txid()) .collect::>(), - env.client + env.rpc_client() .get_raw_mempool()? .into_iter() .collect::>(), @@ -560,7 +591,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> { // mempool for reorg_count in 1..TIP_DIFF { println!("REORG COUNT: {}", reorg_count); - env.reorg_empty_blocks(reorg_count)?; + env.reorg_empty_blocks(reorg_count, &env.bitcoind)?; // This is a map of mempool txids to tip height where the tx was introduced to the mempool // we recalculate this at every loop as reorgs may evict transactions from mempool. We use @@ -568,7 +599,7 @@ fn mempool_during_reorg() -> anyhow::Result<()> { // emission. // TODO: How can have have reorg logic in `TestEnv` NOT blacklast old blocks first? let tx_introductions = dbg!(env - .client + .rpc_client() .get_raw_mempool_verbose()? .into_iter() .map(|(txid, entry)| (txid, entry.height as usize)) @@ -643,7 +674,7 @@ fn no_agreement_point() -> anyhow::Result<()> { let env = TestEnv::new()?; // start height is 99 - let mut emitter = Emitter::from_height(&env.client, (PREMINE_COUNT - 2) as u32); + let mut emitter = Emitter::from_height(env.rpc_client(), (PREMINE_COUNT - 2) as u32); // mine 101 blocks env.mine_blocks(PREMINE_COUNT, None)?; @@ -658,12 +689,12 @@ fn no_agreement_point() -> anyhow::Result<()> { let block_hash_100a = block_header_100a.block_hash(); // get hash for block 101a - let block_hash_101a = env.client.get_block_hash(101)?; + let block_hash_101a = env.rpc_client().get_block_hash(101)?; // invalidate blocks 99a, 100a, 101a - env.client.invalidate_block(&block_hash_99a)?; - env.client.invalidate_block(&block_hash_100a)?; - env.client.invalidate_block(&block_hash_101a)?; + env.rpc_client().invalidate_block(&block_hash_99a)?; + env.rpc_client().invalidate_block(&block_hash_100a)?; + env.rpc_client().invalidate_block(&block_hash_101a)?; // mine new blocks 99b, 100b, 101b env.mine_blocks(3, None)?; diff --git a/crates/electrum/Cargo.toml b/crates/electrum/Cargo.toml index 107a24f48e..10e94a3b79 100644 --- a/crates/electrum/Cargo.toml +++ b/crates/electrum/Cargo.toml @@ -12,6 +12,11 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bdk_chain = { path = "../chain", version = "0.6.0", default-features = false } +bdk_chain = { path = "../chain", version = "0.6.0", features = [ "miniscript" ] } electrum-client = { version = "0.18" } #rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] } + +[dev-dependencies] +testenv = { path = "../testenv", version = "0.1.0", default-features = false } +electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] } +anyhow = "1" \ No newline at end of file diff --git a/crates/electrum/tests/test_electrum.rs b/crates/electrum/tests/test_electrum.rs new file mode 100644 index 0000000000..ee15cf3045 --- /dev/null +++ b/crates/electrum/tests/test_electrum.rs @@ -0,0 +1,266 @@ +use anyhow::Result; +use bdk_chain::{ + bitcoin::{hashes::Hash, Address, Amount, OutPoint, ScriptBuf, WScriptHash}, + keychain::Balance, + local_chain::LocalChain, + BlockId, ConfirmationTimeAnchor, IndexedTxGraph, SpkTxOutIndex, +}; +use bdk_electrum::{ElectrumExt, ElectrumUpdate}; +use electrsd::bitcoind::bitcoincore_rpc::RpcApi; +use electrum_client::ElectrumApi; +use std::{collections::BTreeMap, time::Duration}; +use testenv::TestEnv; + +fn wait_for_block(env: &TestEnv, client: &electrum_client::Client) -> Result<()> { + client.block_headers_subscribe()?; + let mut delay = Duration::from_millis(64); + + loop { + env.electrsd.trigger()?; + client.ping()?; + if client.block_headers_pop()?.is_some() { + return Ok(()); + } + + if delay.as_millis() < 512 { + delay = delay.mul_f32(2.0); + } + std::thread::sleep(delay); + } +} + +fn get_balance( + recv_chain: &LocalChain, + recv_graph: &IndexedTxGraph>, +) -> Result { + let chain_tip = recv_chain + .tip() + .map_or(BlockId::default(), |cp| cp.block_id()); + let outpoints = recv_graph.index.outpoints().clone(); + let balance = recv_graph + .graph() + .balance(recv_chain, chain_tip, outpoints, |_, _| true); + Ok(balance) +} + +#[test] +fn test_chain_sync() -> Result<()> { + const SEND_AMOUNT: Amount = Amount::from_sat(10_000); + + let env = TestEnv::new()?; + let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + + // Setup addresses. + let addr_to_mine = env + .bitcoind + .client + .get_new_address(None, None)? + .assume_checked(); + let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros()); + let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?; + + // Setup receiver. + let mut recv_chain = LocalChain::default(); + let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_index = SpkTxOutIndex::default(); + recv_index.insert_spk((), spk_to_track.clone()); + recv_index + }); + + // Update `recv_chain` with blocks generated from initializing electrsd. + recv_chain.apply_changeset(&BTreeMap::from([ + (0, Some(env.bitcoind.client.get_block_hash(0)?)), + (1, Some(env.bitcoind.client.get_block_hash(1)?)), + ])); + + // Mine and sync receiver up to tip. + env.mine_blocks(101, Some(addr_to_mine))?; + + // Create transactions that are tracked by our receiver. + let txid = env.send(&addr_to_track, SEND_AMOUNT)?; + + // Lock outputs that send to `addr_to_track`. + let outpoints_to_lock = env + .bitcoind + .client + .get_transaction(&txid, None)? + .transaction()? + .output + .into_iter() + .enumerate() + .filter(|(_, txo)| txo.script_pubkey == spk_to_track) + .map(|(vout, _)| OutPoint::new(txid, vout as _)) + .collect::>(); + env.bitcoind.client.lock_unspent(&outpoints_to_lock)?; + + let _ = env.mine_blocks(1, None)?; + + // Sync up to tip. + wait_for_block(&env, &client)?; + let ElectrumUpdate { + chain_update, + relevant_txids, + } = client.scan_without_keychain(recv_chain.tip(), [spk_to_track], None, None, 5)?; + + recv_chain.apply_update(chain_update)?; + let missing = relevant_txids.missing_full_txs(recv_graph.graph()); + let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?; + let _ = recv_graph.apply_update(graph_update); + + assert_eq!( + get_balance(&recv_chain, &recv_graph)?, + Balance { + confirmed: SEND_AMOUNT.to_sat(), + ..Balance::default() + }, + ); + + Ok(()) +} + +#[test] +fn test_reorg_is_detected_in_electrsd() -> Result<()> { + let env = TestEnv::new()?; + let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + + // Mine some blocks. + env.mine_blocks(101, None)?; + let height = env.bitcoind.client.get_block_count()?; + wait_for_block(&env, &client)?; + let blocks = (0..=height) + .map(|i| env.bitcoind.client.get_block_hash(i)) + .collect::, _>>()?; + + // Perform reorg on six blocks. + env.reorg(6)?; + let reorged_height = env.bitcoind.client.get_block_count()?; + wait_for_block(&env, &client)?; + let reorged_blocks = (0..=height) + .map(|i| env.bitcoind.client.get_block_hash(i)) + .collect::, _>>()?; + + assert_eq!(height, reorged_height); + + // Block hashes should not be equal on reorged blocks only. + for (i, (block, reorged_block)) in blocks.iter().zip(reorged_blocks.iter()).enumerate() { + match i <= height as usize - 6 { + true => assert_eq!(block, reorged_block), + false => assert_ne!(block, reorged_block), + } + } + + Ok(()) +} + +#[test] +fn tx_can_become_unconfirmed_after_reorg() -> Result<()> { + const ADDITIONAL_COUNT: usize = 11; + const SEND_AMOUNT: Amount = Amount::from_sat(10_000); + + let env = TestEnv::new()?; + let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?; + + // Setup addresses. + let addr_to_mine = env + .bitcoind + .client + .get_new_address(None, None)? + .assume_checked(); + let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros()); + let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?; + + // Setup receiver. + let mut recv_chain = LocalChain::default(); + let mut recv_graph = IndexedTxGraph::::new({ + let mut recv_index = SpkTxOutIndex::default(); + recv_index.insert_spk((), spk_to_track.clone()); + recv_index + }); + + // Update `recv_chain` with blocks generated from initializing electrsd. + recv_chain.apply_changeset(&BTreeMap::from([ + (0, Some(env.bitcoind.client.get_block_hash(0)?)), + (1, Some(env.bitcoind.client.get_block_hash(1)?)), + ])); + + // Mine and sync receiver up to tip. + env.mine_blocks(101, Some(addr_to_mine))?; + + // Create transactions that are tracked by our receiver. + for _ in 0..ADDITIONAL_COUNT { + let txid = env.send(&addr_to_track, SEND_AMOUNT)?; + + // Lock outputs that send to `addr_to_track`. + let outpoints_to_lock = env + .bitcoind + .client + .get_transaction(&txid, None)? + .transaction()? + .output + .into_iter() + .enumerate() + .filter(|(_, txo)| txo.script_pubkey == spk_to_track) + .map(|(vout, _)| OutPoint::new(txid, vout as _)) + .collect::>(); + env.bitcoind.client.lock_unspent(&outpoints_to_lock)?; + + let _ = env.mine_blocks(1, None)?; + } + + // Sync up to tip. + wait_for_block(&env, &client)?; + let ElectrumUpdate { + chain_update, + relevant_txids, + } = client.scan_without_keychain(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?; + + recv_chain.apply_update(chain_update)?; + let missing = relevant_txids.missing_full_txs(recv_graph.graph()); + let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?; + let _ = recv_graph.apply_update(graph_update); + + assert_eq!( + get_balance(&recv_chain, &recv_graph)?, + Balance { + confirmed: SEND_AMOUNT.to_sat() * ADDITIONAL_COUNT as u64, + ..Balance::default() + }, + "initial balance must be correct", + ); + + // Perform reorgs with different depths. + for reorg_count in 1..=ADDITIONAL_COUNT { + env.reorg_empty_blocks(reorg_count, &env.bitcoind)?; + + wait_for_block(&env, &client)?; + let ElectrumUpdate { + chain_update, + relevant_txids, + } = client.scan_without_keychain( + recv_chain.tip(), + [spk_to_track.clone()], + None, + None, + 5, + )?; + + recv_chain.apply_update(chain_update)?; + let missing = relevant_txids.missing_full_txs(recv_graph.graph()); + let graph_update = + relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?; + let _ = recv_graph.apply_update(graph_update); + + assert_eq!( + get_balance(&recv_chain, &recv_graph)?, + Balance { + confirmed: SEND_AMOUNT.to_sat() * (ADDITIONAL_COUNT - reorg_count) as u64, + trusted_pending: SEND_AMOUNT.to_sat() * reorg_count as u64, + ..Balance::default() + }, + "reorg_count: {}", + reorg_count, + ); + } + + Ok(()) +} diff --git a/crates/esplora/Cargo.toml b/crates/esplora/Cargo.toml index bc127c087a..3b22dd18e6 100644 --- a/crates/esplora/Cargo.toml +++ b/crates/esplora/Cargo.toml @@ -21,6 +21,9 @@ 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] +testenv = { path = "../testenv", version = "0.1.0", default_features = false } + [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index 3b64d7beea..f3f9d3ed27 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -1,67 +1,20 @@ use bdk_esplora::EsploraAsyncExt; +use electrsd::bitcoind::anyhow; use electrsd::bitcoind::bitcoincore_rpc::RpcApi; -use electrsd::bitcoind::{self, anyhow, BitcoinD}; -use electrsd::{Conf, ElectrsD}; -use esplora_client::{self, AsyncClient, Builder}; +use esplora_client::{self, 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) - } -} +use bdk_chain::bitcoin::{Address, Amount, Txid}; +use testenv::TestEnv; #[tokio::test] pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { let env = TestEnv::new()?; + let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap()); + let client = Builder::new(base_url.as_str()).build_async()?; + let receive_address0 = Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm")?.assume_checked(); let receive_address1 = @@ -94,12 +47,11 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { None, )?; let _block_hashes = env.mine_blocks(1, None)?; - while env.client.get_height().await.unwrap() < 102 { + while client.get_height().await.unwrap() < 102 { sleep(Duration::from_millis(10)) } - let graph_update = env - .client + let graph_update = client .scan_txs( misc_spks.into_iter(), vec![].into_iter(), diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index 6c319945ba..e2a7a62c9d 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -1,67 +1,20 @@ use bdk_esplora::EsploraExt; +use electrsd::bitcoind::anyhow; use electrsd::bitcoind::bitcoincore_rpc::RpcApi; -use electrsd::bitcoind::{self, anyhow, BitcoinD}; -use electrsd::{Conf, ElectrsD}; -use esplora_client::{self, BlockingClient, Builder}; +use esplora_client::{self, 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) - } -} +use bdk_chain::bitcoin::{Address, Amount, Txid}; +use testenv::TestEnv; #[test] pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { let env = TestEnv::new()?; + let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap()); + let client = Builder::new(base_url.as_str()).build_blocking()?; + let receive_address0 = Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm")?.assume_checked(); let receive_address1 = @@ -94,11 +47,11 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { None, )?; let _block_hashes = env.mine_blocks(1, None)?; - while env.client.get_height().unwrap() < 102 { + while client.get_height().unwrap() < 102 { sleep(Duration::from_millis(10)) } - let graph_update = env.client.scan_txs( + let graph_update = client.scan_txs( misc_spks.into_iter(), vec![].into_iter(), vec![].into_iter(), diff --git a/crates/testenv/Cargo.toml b/crates/testenv/Cargo.toml index 0d591b2b72..87df119f80 100644 --- a/crates/testenv/Cargo.toml +++ b/crates/testenv/Cargo.toml @@ -10,8 +10,7 @@ edition = "2021" bitcoin = { version = "0.30", default-features = false } bitcoincore-rpc = { version = "0.17" } bdk_chain = { path = "../chain", version = "0.6", default-features = false } -bdk_bitcoind_rpc = { path = "../bitcoind_rpc", version = "0.1.0", default-features = false } -bitcoind = { version = "0.33", features = ["25_0"] } +electrsd = { version= "0.25.0", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] } anyhow = { version = "1" } [features] diff --git a/crates/testenv/src/lib.rs b/crates/testenv/src/lib.rs index 7859ad9679..09e883fbf2 100644 --- a/crates/testenv/src/lib.rs +++ b/crates/testenv/src/lib.rs @@ -7,24 +7,52 @@ use bitcoincore_rpc::{ bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules}, RpcApi, }; +use electrsd::electrum_client::ElectrumApi; pub struct TestEnv { #[allow(dead_code)] - pub daemon: bitcoind::BitcoinD, - pub client: bitcoincore_rpc::Client, + pub bitcoind: electrsd::bitcoind::BitcoinD, + pub electrsd: electrsd::ElectrsD, } impl TestEnv { pub fn new() -> anyhow::Result { - let daemon = match std::env::var_os("TEST_BITCOIND") { - Some(bitcoind_path) => bitcoind::BitcoinD::new(bitcoind_path), - None => bitcoind::BitcoinD::from_downloaded(), + let bitcoind = match std::env::var_os("TEST_BITCOIND") { + Some(bitcoind_path) => electrsd::bitcoind::BitcoinD::new(bitcoind_path), + None => { + let bitcoind_exe = std::env::var("BITCOIND_EXE") + .ok() + .or_else(|| electrsd::bitcoind::downloaded_exe_path().ok()) + .expect( + "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature", + ); + electrsd::bitcoind::BitcoinD::with_conf( + bitcoind_exe, + &electrsd::bitcoind::Conf::default(), + ) + } }?; - let client = bitcoincore_rpc::Client::new( - &daemon.rpc_url(), - bitcoincore_rpc::Auth::CookieFile(daemon.params.cookie_file.clone()), - )?; - Ok(Self { daemon, client }) + + let electrsd = match std::env::var_os("ELECTRS_EXE") { + Some(env_electrs_exe) => electrsd::ElectrsD::new(env_electrs_exe, &bitcoind), + None => { + let mut electrsd_conf = electrsd::Conf::default(); + electrsd_conf.http_enabled = true; + let electrs_exe = electrsd::downloaded_exe_path() + .expect("electrs version feature must be enabled"); + electrsd::ElectrsD::with_conf(electrs_exe, &bitcoind, &electrsd_conf) + } + }?; + + Ok(Self { bitcoind, electrsd }) + } + + pub fn electrum_client(&self) -> &impl ElectrumApi { + &self.electrsd.client + } + + pub fn rpc_client(&self) -> &impl RpcApi { + &self.bitcoind.client } pub fn mine_blocks( @@ -34,16 +62,21 @@ impl TestEnv { ) -> anyhow::Result> { let coinbase_address = match address { Some(address) => address, - None => self.client.get_new_address(None, None)?.assume_checked(), + 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) } pub fn mine_empty_block(&self) -> anyhow::Result<(usize, BlockHash)> { - let bt = self.client.get_block_template( + let bt = self.bitcoind.client.get_block_template( GetBlockTemplateModes::Template, &[GetBlockTemplateRules::SegWit], &[], @@ -95,15 +128,19 @@ impl TestEnv { } } - self.client.submit_block(&block)?; + self.bitcoind.client.submit_block(&block)?; Ok((bt.height as usize, block.block_hash())) } pub fn invalidate_blocks(&self, count: usize) -> anyhow::Result<()> { - let mut hash = self.client.get_best_block_hash()?; + let mut hash = self.bitcoind.client.get_best_block_hash()?; for _ in 0..count { - let prev_hash = self.client.get_block_info(&hash)?.previousblockhash; - self.client.invalidate_block(&hash)?; + let prev_hash = self + .bitcoind + .client + .get_block_info(&hash)? + .previousblockhash; + self.bitcoind.client.invalidate_block(&hash)?; match prev_hash { Some(prev_hash) => hash = prev_hash, None => break, @@ -113,27 +150,31 @@ impl TestEnv { } pub fn reorg(&self, count: usize) -> anyhow::Result> { - let start_height = self.client.get_block_count()?; + let start_height = self.bitcoind.client.get_block_count()?; self.invalidate_blocks(count)?; let res = self.mine_blocks(count, None); assert_eq!( - self.client.get_block_count()?, + self.bitcoind.client.get_block_count()?, start_height, "reorg should not result in height change" ); res } - pub fn reorg_empty_blocks(&self, count: usize) -> anyhow::Result> { - let start_height = self.client.get_block_count()?; + pub fn reorg_empty_blocks( + &self, + count: usize, + bitcoind: &electrsd::bitcoind::BitcoinD, + ) -> anyhow::Result> { + let start_height = bitcoind.client.get_block_count()?; self.invalidate_blocks(count)?; let res = (0..count) .map(|_| self.mine_empty_block()) .collect::, _>>()?; assert_eq!( - self.client.get_block_count()?, + bitcoind.client.get_block_count()?, start_height, "reorg should not result in height change" ); @@ -142,6 +183,7 @@ impl TestEnv { pub fn send(&self, address: &Address, amount: Amount) -> anyhow::Result { let txid = self + .bitcoind .client .send_to_address(address, amount, None, None, None, None, None, None)?; Ok(txid)