-
Notifications
You must be signed in to change notification settings - Fork 321
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(electrum): added scan and reorg tests
Added scan and reorg tests to check electrum functionality using `TestEnv`.
- Loading branch information
1 parent
dd4c029
commit 2196685
Showing
1 changed file
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |