From 42f2861fec21503be0feb5fa3b838d414d25ba1c Mon Sep 17 00:00:00 2001 From: Sosthene Date: Mon, 23 Feb 2026 17:24:56 +0800 Subject: [PATCH 1/3] refactor: extract shared logic for SpScanner in logic mod --- spdk-wallet/src/scanner/logic.rs | 186 +++++++++++++++++++++++++++++ spdk-wallet/src/scanner/mod.rs | 1 + spdk-wallet/src/scanner/scanner.rs | 148 ++--------------------- 3 files changed, 197 insertions(+), 138 deletions(-) create mode 100644 spdk-wallet/src/scanner/logic.rs diff --git a/spdk-wallet/src/scanner/logic.rs b/spdk-wallet/src/scanner/logic.rs new file mode 100644 index 0000000..58f62ae --- /dev/null +++ b/spdk-wallet/src/scanner/logic.rs @@ -0,0 +1,186 @@ +use std::collections::{HashMap, HashSet}; + +use anyhow::{Error, Result}; +use bitcoin::{ + BlockHash, OutPoint, Txid, XOnlyPublicKey, absolute::Height, bip158::BlockFilter, hashes::{Hash, sha256}, hex::DisplayHex, secp256k1::{PublicKey, Scalar} +}; +use silentpayments::receiving::{Label, Receiver}; + +use crate::{ + client::{OutputSpendStatus, OwnedOutput}, +}; + +use spdk_core::chain::UtxoData; + +/// Check if a block's created-UTXO filter matches any of our candidate scriptpubkeys. +pub(crate) fn check_block_outputs( + created_utxo_filter: BlockFilter, + blkhash: BlockHash, + candidate_spks: Vec<&[u8; 34]>, +) -> Result { + let output_keys: Vec<_> = candidate_spks + .into_iter() + .map(|spk| spk[2..].as_ref()) + .collect(); + + // note: match will always return true for an empty query! + if !output_keys.is_empty() { + Ok(created_utxo_filter.match_any(&blkhash, &mut output_keys.into_iter())?) + } else { + Ok(false) + } +} + +/// Compute 8-byte input hashes for our owned outpoints against a given block hash. +pub(crate) fn get_input_hashes( + owned_outpoints: &HashSet, + blkhash: BlockHash, +) -> Result> { + let mut map: HashMap<[u8; 8], OutPoint> = HashMap::new(); + + for outpoint in owned_outpoints { + let mut arr = [0u8; 68]; + arr[..32].copy_from_slice(&outpoint.txid.to_raw_hash().to_byte_array()); + arr[32..36].copy_from_slice(&outpoint.vout.to_le_bytes()); + arr[36..].copy_from_slice(&blkhash.to_byte_array()); + let hash = sha256::Hash::hash(&arr); + + let mut res = [0u8; 8]; + res.copy_from_slice(&hash[..8]); + + map.insert(res, *outpoint); + } + + Ok(map) +} + +/// Check if a block's spent filter matches any of our input hashes. +pub(crate) fn check_block_inputs( + spent_filter: BlockFilter, + blkhash: BlockHash, + input_hashes: Vec<[u8; 8]>, +) -> Result { + // note: match will always return true for an empty query! + if !input_hashes.is_empty() { + Ok(spent_filter.match_any(&blkhash, &mut input_hashes.into_iter())?) + } else { + Ok(false) + } +} + +/// Given fetched UTXOs and a secret map, find which ones belong to us. +pub(crate) fn find_owned_in_utxos( + sp_receiver: &Receiver, + utxos: Vec, + secrets_map: &HashMap<[u8; 34], PublicKey>, +) -> Result, UtxoData, Scalar)>> { + let mut res: Vec<(Option