diff --git a/crates/electrum/src/bdk_electrum_client.rs b/crates/electrum/src/bdk_electrum_client.rs index eb1d43e35..be77af15f 100644 --- a/crates/electrum/src/bdk_electrum_client.rs +++ b/crates/electrum/src/bdk_electrum_client.rs @@ -531,3 +531,52 @@ fn chain_update( } Ok(tip) } + +#[cfg(test)] +mod test { + use crate::{bdk_electrum_client::TxUpdate, BdkElectrumClient}; + use bdk_chain::bitcoin::{hashes::Hash, OutPoint, Transaction, TxIn, Txid}; + use bdk_core::collections::BTreeMap; + use bdk_testenv::{utils::new_tx, TestEnv}; + use std::sync::Arc; + + #[cfg(feature = "default")] + #[test] + fn test_fetch_prev_txout_with_coinbase() { + let env = TestEnv::new().unwrap(); + let electrum_client = + electrum_client::Client::new(env.electrsd.electrum_url.as_str()).unwrap(); + let client = BdkElectrumClient::new(electrum_client); + + // Calling `fetch_prev_txout` on a coinbase transaction triggers a `fetch_tx` on a + // transaction with a txid of all zeros. However, since the error from Electrum's + // `transaction_get` is propagated upwards and may not interrupt the `sync`/`full_scan` + // process, we have to insert a transaction with a txid of all zeros into the `tx_cache` to + // guarantee a crash. + let mut tx_cache = client.tx_cache.lock().unwrap(); + tx_cache.insert(Txid::all_zeros(), new_tx(0).into()); + drop(tx_cache); + + // Create a coinbase transaction. + let coinbase_tx = Transaction { + input: vec![TxIn { + previous_output: OutPoint::null(), + ..Default::default() + }], + ..new_tx(0) + }; + + assert!(coinbase_tx.is_coinbase()); + + // Test that `fetch_prev_txout` does not process our coinbase transaction. If it attempts to + // fetch our previously inserted transaction with a txid of all zeros, this test will crash. + let mut tx_update = TxUpdate { + txs: vec![Arc::new(coinbase_tx)], + ..Default::default() + }; + let _ = client.fetch_prev_txout(&mut tx_update); + + // Ensure that the txouts are empty. + assert_eq!(tx_update.txouts, BTreeMap::default()); + } +}