Skip to content

Commit 04d0ab5

Browse files
committed
test(electrum): added scan and reorg tests
Added scan and reorg tests to check electrum functionality using `TestEnv`.
1 parent 4edf533 commit 04d0ab5

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use anyhow::Result;
2+
use bdk_chain::{
3+
bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, WScriptHash},
4+
keychain::Balance,
5+
local_chain::LocalChain,
6+
ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
7+
};
8+
use bdk_electrum::{ElectrumExt, ElectrumUpdate};
9+
use bdk_testenv::TestEnv;
10+
use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
11+
12+
fn get_balance(
13+
recv_chain: &LocalChain,
14+
recv_graph: &IndexedTxGraph<ConfirmationTimeHeightAnchor, SpkTxOutIndex<()>>,
15+
) -> Result<Balance> {
16+
let chain_tip = recv_chain.tip().block_id();
17+
let outpoints = recv_graph.index.outpoints().clone();
18+
let balance = recv_graph
19+
.graph()
20+
.balance(recv_chain, chain_tip, outpoints, |_, _| true);
21+
Ok(balance)
22+
}
23+
24+
/// Ensure that [`ElectrumExt`] can sync properly.
25+
///
26+
/// 1. Mine 101 blocks.
27+
/// 2. Send a tx.
28+
/// 3. Mine extra block to confirm sent tx.
29+
/// 4. Check [`Balance`] to ensure tx is confirmed.
30+
#[test]
31+
fn scan_detects_confirmed_tx() -> Result<()> {
32+
const SEND_AMOUNT: Amount = Amount::from_sat(10_000);
33+
34+
let env = TestEnv::new()?;
35+
let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?;
36+
37+
// Setup addresses.
38+
let addr_to_mine = env
39+
.bitcoind
40+
.client
41+
.get_new_address(None, None)?
42+
.assume_checked();
43+
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
44+
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
45+
46+
// Setup receiver.
47+
let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
48+
let mut recv_graph = IndexedTxGraph::<ConfirmationTimeHeightAnchor, _>::new({
49+
let mut recv_index = SpkTxOutIndex::default();
50+
recv_index.insert_spk((), spk_to_track.clone());
51+
recv_index
52+
});
53+
54+
// Mine some blocks.
55+
env.mine_blocks(101, Some(addr_to_mine))?;
56+
57+
// Create transaction that is tracked by our receiver.
58+
env.send(&addr_to_track, SEND_AMOUNT)?;
59+
60+
// Mine a block to confirm sent tx.
61+
env.mine_blocks(1, None)?;
62+
63+
// Sync up to tip.
64+
env.wait_until_electrum_sees_block()?;
65+
let ElectrumUpdate {
66+
chain_update,
67+
relevant_txids,
68+
} = client.sync(recv_chain.tip(), [spk_to_track], None, None, 5)?;
69+
70+
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
71+
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
72+
let _ = recv_chain
73+
.apply_update(chain_update)
74+
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
75+
let _ = recv_graph.apply_update(graph_update);
76+
77+
// Check to see if tx is confirmed.
78+
assert_eq!(
79+
get_balance(&recv_chain, &recv_graph)?,
80+
Balance {
81+
confirmed: SEND_AMOUNT.to_sat(),
82+
..Balance::default()
83+
},
84+
);
85+
86+
Ok(())
87+
}
88+
89+
#[test]
90+
fn test_reorg_is_detected_in_electrsd() -> Result<()> {
91+
let env = TestEnv::new()?;
92+
93+
// Mine some blocks.
94+
env.mine_blocks(101, None)?;
95+
env.wait_until_electrum_sees_block()?;
96+
let height = env.bitcoind.client.get_block_count()?;
97+
let blocks = (0..=height)
98+
.map(|i| env.bitcoind.client.get_block_hash(i))
99+
.collect::<Result<Vec<_>, _>>()?;
100+
101+
// Perform reorg on six blocks.
102+
env.reorg(6)?;
103+
env.wait_until_electrum_sees_block()?;
104+
let reorged_height = env.bitcoind.client.get_block_count()?;
105+
let reorged_blocks = (0..=height)
106+
.map(|i| env.bitcoind.client.get_block_hash(i))
107+
.collect::<Result<Vec<_>, _>>()?;
108+
109+
assert_eq!(height, reorged_height);
110+
111+
// Block hashes should not be equal on the six reorged blocks.
112+
for (i, (block, reorged_block)) in blocks.iter().zip(reorged_blocks.iter()).enumerate() {
113+
match i <= height as usize - 6 {
114+
true => assert_eq!(block, reorged_block),
115+
false => assert_ne!(block, reorged_block),
116+
}
117+
}
118+
119+
Ok(())
120+
}
121+
122+
/// Ensure that confirmed txs that are reorged become unconfirmed.
123+
///
124+
/// 1. Mine 101 blocks.
125+
/// 2. Mine 8 blocks with a confirmed tx in each.
126+
/// 3. Perform 8 separate reorgs on each block with a confirmed tx.
127+
/// 4. Check [`Balance`] after each reorg to ensure unconfirmed amount is correct.
128+
#[test]
129+
fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
130+
const REORG_COUNT: usize = 8;
131+
const SEND_AMOUNT: Amount = Amount::from_sat(10_000);
132+
133+
let env = TestEnv::new()?;
134+
let client = electrum_client::Client::new(env.electrsd.electrum_url.as_str())?;
135+
136+
// Setup addresses.
137+
let addr_to_mine = env
138+
.bitcoind
139+
.client
140+
.get_new_address(None, None)?
141+
.assume_checked();
142+
let spk_to_track = ScriptBuf::new_v0_p2wsh(&WScriptHash::all_zeros());
143+
let addr_to_track = Address::from_script(&spk_to_track, bdk_chain::bitcoin::Network::Regtest)?;
144+
145+
// Setup receiver.
146+
let (mut recv_chain, _) = LocalChain::from_genesis_hash(env.bitcoind.client.get_block_hash(0)?);
147+
let mut recv_graph = IndexedTxGraph::<ConfirmationTimeHeightAnchor, _>::new({
148+
let mut recv_index = SpkTxOutIndex::default();
149+
recv_index.insert_spk((), spk_to_track.clone());
150+
recv_index
151+
});
152+
153+
// Mine some blocks.
154+
env.mine_blocks(101, Some(addr_to_mine))?;
155+
156+
// Create transactions that are tracked by our receiver.
157+
for _ in 0..REORG_COUNT {
158+
env.send(&addr_to_track, SEND_AMOUNT)?;
159+
env.mine_blocks(1, None)?;
160+
}
161+
162+
// Sync up to tip.
163+
env.wait_until_electrum_sees_block()?;
164+
let ElectrumUpdate {
165+
chain_update,
166+
relevant_txids,
167+
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;
168+
169+
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
170+
let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
171+
let _ = recv_chain
172+
.apply_update(chain_update)
173+
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
174+
let _ = recv_graph.apply_update(graph_update.clone());
175+
176+
// Retain a snapshot of all anchors before reorg process.
177+
let initial_anchors = graph_update.all_anchors();
178+
179+
// Check if initial balance is correct.
180+
assert_eq!(
181+
get_balance(&recv_chain, &recv_graph)?,
182+
Balance {
183+
confirmed: SEND_AMOUNT.to_sat() * REORG_COUNT as u64,
184+
..Balance::default()
185+
},
186+
"initial balance must be correct",
187+
);
188+
189+
// Perform reorgs with different depths.
190+
for depth in 1..=REORG_COUNT {
191+
env.reorg_empty_blocks(depth)?;
192+
193+
env.wait_until_electrum_sees_block()?;
194+
let ElectrumUpdate {
195+
chain_update,
196+
relevant_txids,
197+
} = client.sync(recv_chain.tip(), [spk_to_track.clone()], None, None, 5)?;
198+
199+
let missing = relevant_txids.missing_full_txs(recv_graph.graph());
200+
let graph_update =
201+
relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?;
202+
let _ = recv_chain
203+
.apply_update(chain_update)
204+
.map_err(|err| anyhow::anyhow!("LocalChain update error: {:?}", err))?;
205+
206+
// Check to see if a new anchor is added during current reorg.
207+
if !initial_anchors.is_superset(graph_update.all_anchors()) {
208+
println!("New anchor added at reorg depth {}", depth);
209+
}
210+
let _ = recv_graph.apply_update(graph_update);
211+
212+
assert_eq!(
213+
get_balance(&recv_chain, &recv_graph)?,
214+
Balance {
215+
confirmed: SEND_AMOUNT.to_sat() * (REORG_COUNT - depth) as u64,
216+
trusted_pending: SEND_AMOUNT.to_sat() * depth as u64,
217+
..Balance::default()
218+
},
219+
"reorg_count: {}",
220+
depth,
221+
);
222+
}
223+
224+
Ok(())
225+
}

0 commit comments

Comments
 (0)