/// We include a chain suffix of a certain length for the purpose of robustness.
const CHAIN_SUFFIX_LENGTH: u32 = 8;
-/// Maximum batch size for proof validation requests
-const MAX_BATCH_SIZE: usize = 100;
-
/// Wrapper around an [`electrum_client::ElectrumApi`] which includes an internal in-memory
/// transaction cache to avoid re-fetching already downloaded transactions.
#[derive(Debug)]
batch_size: usize,
pending_anchors: &mut Vec<(Txid, usize)>,
) -> Result<Option<u32>, Error> {
- let mut unused_spk_count = 0;
- let mut last_active_index = None;
+ let mut unused_spk_count = 0_usize;
+ let mut last_active_index = Option::<u32>::None;
- 'batch_loop: loop {
+ loop {
let spks = (0..batch_size)
.map_while(|_| spks_with_expected_txids.next())
.collect::<Vec<_>>();
if spks.is_empty() {
- break;
+ return Ok(last_active_index);
}
let spk_histories = self
for ((spk_index, spk), spk_history) in spks.into_iter().zip(spk_histories) {
if spk_history.is_empty() {
- unused_spk_count += 1;
- if unused_spk_count >= stop_gap {
- break 'batch_loop;
- }
+ match unused_spk_count.checked_add(1) {
+ Some(i) if i < stop_gap => unused_spk_count = i,
+ _ => return Ok(last_active_index),
+ };
} else {
last_active_index = Some(spk_index);
unused_spk_count = 0;
}
}
- // Fetch missing proofs in batches
- for chunk in to_fetch.chunks(MAX_BATCH_SIZE) {
- for &(txid, height, hash) in chunk {
- // Fetch the raw proof.
- let proof = self.inner.transaction_get_merkle(&txid, height)?;
-
- // Validate against header, retrying once on stale header.
- let mut header = {
- let cache = self.block_header_cache.lock().unwrap();
- cache[&(height as u32)]
- };
- let mut valid = electrum_client::utils::validate_merkle_proof(
+ // Batch all get_merkle calls.
+ let mut batch = electrum_client::Batch::default();
+ for &(txid, height, _) in &to_fetch {
+ batch.raw(
+ "blockchain.transaction.get_merkle".into(),
+ vec![
+ electrum_client::Param::String(format!("{:x}", txid)),
+ electrum_client::Param::Usize(height),
+ ],
+ );
+ }
+ let resps = self.inner.batch_call(&batch)?;
+
+ // Validate each proof, retrying once for each stale header.
+ for ((txid, height, hash), resp) in to_fetch.into_iter().zip(resps.into_iter()) {
+ let proof: electrum_client::GetMerkleRes = serde_json::from_value(resp)?;
+
+ let mut header = {
+ let cache = self.block_header_cache.lock().unwrap();
+ cache
+ .get(&(height as u32))
+ .copied()
+ .expect("header already fetched above")
+ };
+ let mut valid =
+ electrum_client::utils::validate_merkle_proof(&txid, &header.merkle_root, &proof);
+ if !valid {
+ header = self.inner.block_header(height)?;
+ self.block_header_cache
+ .lock()
+ .unwrap()
+ .insert(height as u32, header);
+ valid = electrum_client::utils::validate_merkle_proof(
&txid,
&header.merkle_root,
&proof,
);
- if !valid {
- let new_header = self.inner.block_header(height)?;
- self.block_header_cache
- .lock()
- .unwrap()
- .insert(height as u32, new_header);
- header = new_header;
- valid = electrum_client::utils::validate_merkle_proof(
- &txid,
- &header.merkle_root,
- &proof,
- );
- }
+ }
- // Build and cache the anchor if merkle proof is valid.
- if valid {
- let anchor = ConfirmationBlockTime {
- confirmation_time: header.time as u64,
- block_id: BlockId {
- height: height as u32,
- hash,
- },
- };
- self.anchor_cache
- .lock()
- .unwrap()
- .insert((txid, hash), anchor);
- results.push((txid, anchor));
- }
+ // Build and cache the anchor if merkle proof is valid.
+ if valid {
+ let anchor = ConfirmationBlockTime {
+ confirmation_time: header.time as u64,
+ block_id: BlockId {
+ height: height as u32,
+ hash,
+ },
+ };
+ self.anchor_cache
+ .lock()
+ .unwrap()
+ .insert((txid, hash), anchor);
+ results.push((txid, anchor));
}
}
Ok(results)
}
- /// Validate a single transaction’s Merkle proof, cache its confirmation anchor, and update.
- #[allow(dead_code)]
- fn validate_anchor_for_update(
- &self,
- tx_update: &mut TxUpdate<ConfirmationBlockTime>,
- txid: Txid,
- confirmation_height: usize,
- ) -> Result<(), Error> {
- let anchors = self.batch_fetch_anchors(&[(txid, confirmation_height)])?;
- for (txid, anchor) in anchors {
- tx_update.anchors.insert((anchor, txid));
- }
- Ok(())
- }
-
// Helper function which fetches the `TxOut`s of our relevant transactions' previous
// transactions, which we do not have by default. This data is needed to calculate the
// transaction fee.