From: 志宇 Date: Fri, 18 Sep 2026 09:36:19 +0000 (+0000) Subject: fix(electrum): refresh header cache with headers fetched for the chain update X-Git-Url: http://internal-gitweb-vhost/-client-interface/src/example_cli/blockdata/script/struct.EncoderStringWriter.html?a=commitdiff_plain;h=b4343702e7da3f3d634559b5cbdb12dbe419c94f;p=bdk fix(electrum): refresh header cache with headers fetched for the chain update `block_header_cache` was only written by `batch_fetch_anchors`, so after a reorg it kept serving the pre-reorg header for that height. The anchor cache is keyed by `(txid, hash)` from that header, so a tx re-mined at the same height hit the anchor cache with the stale hash, skipped proof validation, and was never re-anchored to the replacement block. `fetch_tip_and_latest_blocks` now writes the headers it fetches (the chain suffix and the agreement-walk checkpoints) into the header cache. --- diff --git a/crates/electrum/src/bdk_electrum_client.rs b/crates/electrum/src/bdk_electrum_client.rs index a7c94315..d80525ba 100644 --- a/crates/electrum/src/bdk_electrum_client.rs +++ b/crates/electrum/src/bdk_electrum_client.rs @@ -132,7 +132,11 @@ impl BdkElectrumClient { let start_time = request.start_time(); let tip_and_latest_blocks = match request.chain_tip() { - Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?), + Some(chain_tip) => Some(fetch_tip_and_latest_blocks( + &self.inner, + chain_tip, + &mut self.block_header_cache.lock().unwrap(), + )?), None => None, }; @@ -217,7 +221,11 @@ impl BdkElectrumClient { let start_time = request.start_time(); let tip_and_latest_blocks = match request.chain_tip() { - Some(chain_tip) => Some(fetch_tip_and_latest_blocks(&self.inner, chain_tip)?), + Some(chain_tip) => Some(fetch_tip_and_latest_blocks( + &self.inner, + chain_tip, + &mut self.block_header_cache.lock().unwrap(), + )?), None => None, }; @@ -652,6 +660,7 @@ impl BdkElectrumClient { fn fetch_tip_and_latest_blocks( client: &impl ElectrumApi, prev_tip: CheckPoint, + block_header_cache: &mut HashMap, ) -> Result<(CheckPoint, BTreeMap), Error> { let HeaderNotification { height, .. } = client.block_headers_subscribe()?; let new_tip_height = height as u32; @@ -666,12 +675,14 @@ fn fetch_tip_and_latest_blocks( // to construct our checkpoint update. let mut new_blocks = { let start_height = new_tip_height.saturating_sub(CHAIN_SUFFIX_LENGTH - 1); - let hashes = client + let headers = client .block_headers(start_height as _, CHAIN_SUFFIX_LENGTH as _)? - .headers - .into_iter() - .map(|h| h.block_hash()); - (start_height..).zip(hashes).collect::>() + .headers; + block_header_cache.extend((start_height..).zip(headers.iter().copied())); + + (start_height..) + .zip(headers.into_iter().map(|h| h.block_hash())) + .collect::>() }; // Find the "point of agreement" (if any). @@ -686,7 +697,9 @@ fn fetch_tip_and_latest_blocks( new_tip_height >= cp_block.height, "already checked that electrum's tip cannot be smaller" ); - let hash = client.block_header(cp_block.height as _)?.block_hash(); + let header = client.block_header(cp_block.height as _)?; + block_header_cache.insert(cp_block.height, header); + let hash = header.block_hash(); new_blocks.insert(cp_block.height, hash); hash }