]> Untitled Git - bdk/commitdiff
test(electrum): added scan and reorg tests
authorWei Chen <wzc110@gmail.com>
Fri, 2 Feb 2024 10:35:24 +0000 (18:35 +0800)
committerWei Chen <wzc110@gmail.com>
Fri, 22 Mar 2024 09:59:35 +0000 (17:59 +0800)
Added scan and reorg tests to check electrum functionality using
`TestEnv`.

crates/electrum/tests/test_electrum.rs [new file with mode: 0644]

diff --git a/crates/electrum/tests/test_electrum.rs b/crates/electrum/tests/test_electrum.rs
new file mode 100644 (file)
index 0000000..6d1c189
--- /dev/null
@@ -0,0 +1,225 @@
+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 bdk_testenv::TestEnv;
+use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
+
+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.
+    env.wait_until_electrum_sees_block()?;
+    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)
+        .map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
+    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()?;
+
+    // Mine some blocks.
+    env.mine_blocks(101, None)?;
+    env.wait_until_electrum_sees_block()?;
+    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)?;
+    env.wait_until_electrum_sees_block()?;
+    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 8 blocks with a confirmed tx in each.
+/// 3. Perform 8 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.
+    env.wait_until_electrum_sees_block()?;
+    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)
+        .map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
+    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)?;
+
+        env.wait_until_electrum_sees_block()?;
+        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)
+            .map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
+
+        // 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(())
+}