Skip to content

Commit

Permalink
test(electrum): added scan and reorg tests
Browse files Browse the repository at this point in the history
Added scan and reorg tests to check electrum functionality using
`TestEnv`.
  • Loading branch information
LagginTimes committed Jan 23, 2024
1 parent dd4c029 commit 2196685
Showing 1 changed file with 240 additions and 0 deletions.
240 changes: 240 additions & 0 deletions crates/electrum/tests/test_electrum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use anyhow::Result;
use bdk_chain::{
bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
keychain::Balance,
local_chain::LocalChain,
ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
};
use bdk_electrum::{ElectrumExt, ElectrumUpdate};
use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
use electrum_client::ElectrumApi;
use std::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<ConfirmationTimeHeightAnchor, SpkTxOutIndex<()>>,
) -> Result<Balance> {
let chain_tip = recv_chain.tip().block_id();
let outpoints = recv_graph.index.outpoints().clone();
let balance = recv_graph
.graph()
.balance(recv_chain, chain_tip, outpoints, |_, _| true);
Ok(balance)
}

/// Ensure that [`ElectrumExt`] can sync properly.
///
/// 1. Mine 101 blocks.
/// 2. Send a tx.
/// 3. Mine extra block to confirm sent tx.
/// 4. Check [`Balance`] to ensure tx is confirmed.
#[test]
fn scan_detects_confirmed_tx() -> 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::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<ConfirmationTimeHeightAnchor, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
recv_index
});

// Mine some blocks.
env.mine_blocks(101, Some(addr_to_mine))?;

// Create transaction that is tracked by our receiver.
env.send(&addr_to_track, SEND_AMOUNT)?;

// Mine a block to confirm sent tx.
env.mine_blocks(1, None)?;

// Sync up to tip.
wait_for_block(&env, &client)?;
let ElectrumUpdate {
chain_update,
relevant_txids,
} = client.sync(recv_chain.tip(), [spk_to_track], None, None, 5)?;

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_chain.apply_update(chain_update);
let _ = recv_graph.apply_update(graph_update);

// Check to see if tx is confirmed.
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)?;
wait_for_block(&env, &client)?;
let height = env.bitcoind.client.get_block_count()?;
let blocks = (0..=height)
.map(|i| env.bitcoind.client.get_block_hash(i))
.collect::<Result<Vec<_>, _>>()?;

// Perform reorg on six blocks.
env.reorg(6)?;
wait_for_block(&env, &client)?;
let reorged_height = env.bitcoind.client.get_block_count()?;
let reorged_blocks = (0..=height)
.map(|i| env.bitcoind.client.get_block_hash(i))
.collect::<Result<Vec<_>, _>>()?;

assert_eq!(height, reorged_height);

// Block hashes should not be equal on the six reorged blocks.
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(())
}

/// Ensure that confirmed txs that are reorged become unconfirmed.
///
/// 1. Mine 101 blocks.
/// 2. Mine 11 blocks with a confirmed tx in each.
/// 3. Perform 11 separate reorgs on each block with a confirmed tx.
/// 4. Check [`Balance`] after each reorg to ensure unconfirmed amount is correct.
#[test]
fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
const REORG_COUNT: usize = 8;
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::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
let mut recv_graph = IndexedTxGraph::<ConfirmationTimeHeightAnchor, _>::new({
let mut recv_index = SpkTxOutIndex::default();
recv_index.insert_spk((), spk_to_track.clone());
recv_index
});

// Mine some blocks.
env.mine_blocks(101, Some(addr_to_mine))?;

// Create transactions that are tracked by our receiver.
for _ in 0..REORG_COUNT {
env.send(&addr_to_track, SEND_AMOUNT)?;
env.mine_blocks(1, None)?;
}

// Sync up to tip.
wait_for_block(&env, &client)?;
let ElectrumUpdate {
chain_update,
relevant_txids,
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;

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_chain.apply_update(chain_update);
let _ = recv_graph.apply_update(graph_update.clone());

// Retain a snapshot of all anchors before reorg process.
let initial_anchors = graph_update.all_anchors();

// Check if initial balance is correct.
assert_eq!(
get_balance(&recv_chain, &recv_graph)?,
Balance {
confirmed: SEND_AMOUNT.to_sat() * REORG_COUNT as u64,
..Balance::default()
},
"initial balance must be correct",
);

// Perform reorgs with different depths.
for depth in 1..=REORG_COUNT {
env.reorg_empty_blocks(depth)?;

wait_for_block(&env, &client)?;
let ElectrumUpdate {
chain_update,
relevant_txids,
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;

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_chain.apply_update(chain_update);

// Check to see if a new anchor is added during current reorg.
if !initial_anchors.is_superset(graph_update.all_anchors()) {
println!("New anchor added at reorg depth {}", depth);
}
let _ = recv_graph.apply_update(graph_update);

assert_eq!(
get_balance(&recv_chain, &recv_graph)?,
Balance {
confirmed: SEND_AMOUNT.to_sat() * (REORG_COUNT - depth) as u64,
trusted_pending: SEND_AMOUNT.to_sat() * depth as u64,
..Balance::default()
},
"reorg_count: {}",
depth,
);
}

Ok(())
}

0 comments on commit 2196685

Please sign in to comment.