Skip to content

Commit f4f18b3

Browse files
committed
add tests that reproduces bug
1 parent 71bf53d commit f4f18b3

File tree

1 file changed

+201
-3
lines changed

1 file changed

+201
-3
lines changed

crates/bitcoind_rpc/tests/test_filter_iter.rs

Lines changed: 201 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use bitcoin::{constants, Address, Amount, Network, ScriptBuf};
2-
3-
use bdk_bitcoind_rpc::bip158::FilterIter;
1+
use bdk_bitcoind_rpc::bip158::{Event, EventInner, FilterIter};
42
use bdk_core::{BlockId, CheckPoint};
53
use bdk_testenv::{anyhow, bitcoind, block_id, TestEnv};
4+
use bitcoin::{constants, Address, Amount, Network, ScriptBuf};
65
use bitcoincore_rpc::RpcApi;
76

87
fn testenv() -> anyhow::Result<TestEnv> {
@@ -163,3 +162,202 @@ fn filter_iter_error_no_scripts() -> anyhow::Result<()> {
163162

164163
Ok(())
165164
}
165+
166+
#[test]
167+
fn filter_iter_handles_reorg_during_iteration() -> anyhow::Result<()> {
168+
let env = testenv()?;
169+
let client = env.rpc_client();
170+
171+
// 1. Initial setup & mining
172+
println!("STEP: Initial mining (target height 101 for maturity)");
173+
174+
let additional_blocks_to_mine = 100; // Mine 100 *additional* blocks
175+
let _ = env.mine_blocks(additional_blocks_to_mine, None)?;
176+
// *****************************
177+
// Now check against the expected final height
178+
let expected_initial_height = 101; // Assuming genesis(0)+initial(1)+100 = 101
179+
assert_eq!(
180+
client.get_block_count()?,
181+
expected_initial_height as u64,
182+
"Block count should be {} after initial mine",
183+
expected_initial_height
184+
);
185+
186+
// 2. Create watched script
187+
println!("STEP: Creating watched script");
188+
// ***** FIX: Ensure address and spk_to_watch are defined here *****
189+
let address = client
190+
.get_new_address(
191+
Some("reorg-test"),
192+
Some(bitcoincore_rpc::json::AddressType::Bech32),
193+
)?
194+
.assume_checked();
195+
let spk_to_watch = address.script_pubkey();
196+
// ******************************************************************
197+
println!("Watching SPK: {}", spk_to_watch.to_hex_string());
198+
199+
// 3. Mine block A (now height 102) WITH relevant tx
200+
println!("STEP: Sending tx for original block A (now height 102)");
201+
let txid_a = env.send(&address, Amount::from_sat(1000))?;
202+
println!("STEP: Mining original block A (now height 102)");
203+
let hash_102a = env.mine_blocks(1, None)?[0];
204+
println!("Block 102 (A) hash: {}", hash_102a);
205+
assert_eq!(
206+
client.get_block_count()?,
207+
102,
208+
"Block count should be 102 after mining block A"
209+
);
210+
211+
// 4. Mine block B (now height 103) WITH relevant tx
212+
println!("STEP: Sending tx for original block B (now height 103)");
213+
let txid_b = env.send(&address, Amount::from_sat(2000))?;
214+
println!("STEP: Mining original block B (now height 103)");
215+
let hash_103b = env.mine_blocks(1, None)?[0];
216+
println!("Block 103 (B) hash: {}", hash_103b);
217+
assert_eq!(
218+
client.get_block_count()?,
219+
103,
220+
"Block count should be 103 after mining block B"
221+
);
222+
223+
// 5. Instantiate FilterIter (Start height remains 101)
224+
println!("STEP: Instantiating FilterIter");
225+
let start_height = 101; // Start scan *after* the initial mine tip (height 101)
226+
let mut iter = FilterIter::new_with_height(client, start_height + 1); // Start processing from height 102
227+
iter.add_spk(spk_to_watch.clone());
228+
let initial_tip = iter.get_tip()?.expect("Should get initial tip");
229+
assert_eq!(initial_tip.height, 103); // Tip is now block B
230+
assert_eq!(initial_tip.hash, hash_103b);
231+
232+
// 6. Iterate once (process block A - now height 102)
233+
println!("STEP: Iterating once (original block A at height 102)");
234+
let event_a_result = iter.next().expect("Iterator should have item A");
235+
let event_a = event_a_result?;
236+
println!("First event: {:?}", event_a);
237+
match &event_a {
238+
Event::Block(EventInner { height, block }) => {
239+
assert_eq!(*height, 102);
240+
assert_eq!(block.block_hash(), hash_102a);
241+
assert!(block.txdata.iter().any(|tx| tx.compute_txid() == txid_a));
242+
}
243+
_ => panic!("Expected block A"),
244+
}
245+
246+
// 7. Simulate Reorg (Invalidate blocks 103B and 102A)
247+
println!("STEP: Invalidating blocks B (103) and A (102)");
248+
println!(
249+
"Invalidating blocks B ({}) and A ({})",
250+
hash_103b, hash_102a
251+
);
252+
client.invalidate_block(&hash_103b)?;
253+
client.invalidate_block(&hash_102a)?;
254+
// Current tip is now 101
255+
256+
// 8. Mine Replacement Blocks WITHOUT relevant txs
257+
// Block A' (height 102) - empty or unrelated txs
258+
println!("STEP: Mining replacement block A' (height 102, no send)");
259+
println!("Mining replacement Block 102 (A') without relevant tx");
260+
let hash_102a_prime = env.mine_blocks(1, None)?[0]; // Mine block 102 (A')
261+
println!("Block 102 (A') hash: {}", hash_102a_prime);
262+
assert_eq!(client.get_block_count()?, 102);
263+
assert_ne!(hash_102a, hash_102a_prime);
264+
265+
// Block B' (height 103) - empty or unrelated txs
266+
println!("STEP: Mining replacement block B' (height 103, no send)");
267+
println!("Mining replacement Block 103 (B') without relevant tx");
268+
let hash_103b_prime = env.mine_blocks(1, None)?[0]; // Mine block 103 (B')
269+
println!("Block 103 (B') hash: {}", hash_103b_prime);
270+
assert_eq!(client.get_block_count()?, 103);
271+
assert_ne!(hash_103b, hash_103b_prime);
272+
273+
// 9. Continue Iterating & Collect Events AFTER reorg
274+
// Iterator should now process heights 102 (A') and 103 (B').
275+
let mut post_reorg_events: Vec<bdk_bitcoind_rpc::bip158::Event> = Vec::new();
276+
277+
println!("STEP: Starting post-reorg iteration loop");
278+
println!("Continuing iteration after reorg...");
279+
while let Some(event_result) = iter.next() {
280+
match event_result {
281+
Ok(event) => {
282+
println!("Post-reorg event: {:?}", event);
283+
post_reorg_events.push(event);
284+
}
285+
Err(e) => {
286+
// Print the error and fail the test immediately if an error occurs during iteration
287+
eprintln!("Error during post-reorg iteration: {:?}", e);
288+
return Err(anyhow::Error::msg(format!(
289+
"Iteration failed post-reorg: {:?}",
290+
e
291+
)));
292+
}
293+
}
294+
}
295+
296+
// 10. Assertions (Adjust heights)
297+
println!("STEP: Checking post-reorg assertions");
298+
println!("Checking assertions (expecting failure)...");
299+
300+
// Check event for height 102 post-reorg (Block A')
301+
let event_102_post = post_reorg_events.iter().find(|e| e.height() == 102);
302+
assert!(
303+
event_102_post.is_some(),
304+
"Should have yielded an event for height 102 post-reorg (Block A')"
305+
);
306+
match event_102_post.unwrap() {
307+
Event::Block(inner) => {
308+
assert_eq!(
309+
inner.block.block_hash(),
310+
hash_102a_prime,
311+
"BUG: Iterator yielded wrong block for height 102! Expected A', maybe got A?"
312+
);
313+
}
314+
Event::NoMatch(h) => {
315+
assert_eq!(*h, 102, "Should be NoMatch for height 102");
316+
}
317+
}
318+
319+
// Check event for height 103 post-reorg (Block B')
320+
let event_103_post = post_reorg_events.iter().find(|e| e.height() == 103);
321+
assert!(
322+
event_103_post.is_some(),
323+
"Should have yielded an event for height 103 post-reorg (Block B')"
324+
);
325+
match event_103_post.unwrap() {
326+
Event::Block(inner) => {
327+
assert_eq!(
328+
inner.block.block_hash(),
329+
hash_103b_prime,
330+
"BUG: Iterator yielded wrong block for height 103! Expected B', maybe got B?"
331+
);
332+
assert!(
333+
!inner
334+
.block
335+
.txdata
336+
.iter()
337+
.any(|tx| tx.compute_txid() == txid_b),
338+
"BUG: Iterator yielded block for height 103 containing old txid_b!"
339+
);
340+
}
341+
Event::NoMatch(h) => {
342+
assert_eq!(*h, 103, "Should be NoMatch for height 103");
343+
}
344+
}
345+
346+
// Check chain update tip (Adjust height)
347+
println!("STEP: Checking chain_update");
348+
let final_update = iter.chain_update();
349+
assert!(final_update.is_some(), "Should get a final chain update");
350+
let final_tip_id = final_update.unwrap().block_id();
351+
assert_eq!(
352+
final_tip_id.height, 103,
353+
"BUG: Final checkpoint height mismatch!"
354+
);
355+
assert_eq!(
356+
final_tip_id.hash, hash_103b_prime,
357+
"BUG: Final checkpoint hash mismatch! Expected hash of B'."
358+
);
359+
360+
println!("If you see this, the test PASSED unexpectedly. The bug might already be fixed or the test logic is flawed.");
361+
362+
Ok(())
363+
}

0 commit comments

Comments
 (0)