diff --git a/crates/bitcoind_rpc/tests/test_emitter.rs b/crates/bitcoind_rpc/tests/test_emitter.rs index 4620a6ae65..245ce47b51 100644 --- a/crates/bitcoind_rpc/tests/test_emitter.rs +++ b/crates/bitcoind_rpc/tests/test_emitter.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, BTreeSet}; -use bdk_bitcoind_rpc::{EmittedBlock, Emitter, MempoolTx}; +use bdk_bitcoind_rpc::{EmittedBlock, EmittedHeader, Emitter, MempoolTx}; use bdk_chain::{ bitcoin::{Address, Amount, BlockHash, Txid}, indexed_tx_graph::InsertTxItem, @@ -449,3 +449,76 @@ fn mempool_avoids_re_emission() -> anyhow::Result<()> { Ok(()) } + +/// Ensure mempool txs at new height are always emitted. +/// +/// The network is at height h. The receiver is synced to height h-1. When mempool txs are +/// introduced at height h, we should always emit them. +/// +/// The receiver (bdk_chain structures) is synced to one block before the chain tip, and there are txs +/// in the mempool. When the Emitter is synced to the chain tip and we call Emitter::mempool, +/// the mempool txs should be emitted once. +#[test] +fn mempool_tx_emission_at_new_height() -> anyhow::Result<()> { + const BLOCKS_TO_MINE: usize = 101; + const MEMPOOL_TX_COUNT: usize = 2; + + let env = TestEnv::new()?; + let mut emitter = Emitter::new(&env.client, 0); + + // mine blocks and sync up emitter to chain tip height - 1 + let addr = env.client.get_new_address(None, None)?.assume_checked(); + env.mine_blocks(BLOCKS_TO_MINE, Some(addr.clone()))?; + while emitter.next_header()?.is_some() {} + + // mine one more block the emitter hasn't seen + env.mine_blocks(1, Some(addr.clone()))?; + + // add some random txs in mempool at chain tip height + let exp_txids = (0..MEMPOOL_TX_COUNT) + .map(|_| env.send(&addr, Amount::from_sat(2100))) + .collect::<Result<BTreeSet<Txid>, _>>()?; + + // the emission should include all mempool transactions + let emitted_txids = emitter + .mempool()? + .into_iter() + .map(|m_tx| m_tx.tx.txid()) + .collect::<BTreeSet<Txid>>(); + assert_eq!( + emitted_txids, exp_txids, + "all mempool txs should be emitted" + ); + + let EmittedHeader { + height, + header: _header, + } = emitter.next_header()?.unwrap(); + + assert_eq!( + height as usize, + BLOCKS_TO_MINE + 1, + "next header should be at height of last mined block" + ); + + // the next emission should include all mempool transactions again for new height + let emitted_txids = emitter + .mempool()? + .into_iter() + .map(|m_tx| m_tx.tx.txid()) + .collect::<BTreeSet<Txid>>(); + assert_eq!( + emitted_txids, exp_txids, + "all mempool txs should be emitted" + ); + + // there should be no more mempool emissions for the same height + assert!(emitter.next_header()?.is_none()); + + assert!( + emitter.mempool()?.is_empty(), + "next mempool emission should be empty" + ); + + Ok(()) +}