diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 64aead883..0be45db79 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.75.0" +channel = "1.88.0" components = [ "cargo", "clippy", diff --git a/src/electrum/server.rs b/src/electrum/server.rs index e518f42ea..8c6f89a76 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -314,6 +314,14 @@ impl Connection { // let _historical_mode = // bool_from_value_or(params.get(2), "historical", false).unwrap_or(false); + // Parse optional dust_limit parameter + let dust_limit: Option = params.get(1) + .map(|v| v.as_u64()) + .flatten(); + + // Parse optional exclude_spent parameter + let filter_spent: bool = bool_from_value_or(params.get(2), "filter_spent", false)?; + let sp_begin_height = self.query.sp_begin_height(); // let last_header_entry = self.query.chain().best_header(); @@ -323,7 +331,7 @@ impl Connection { height }; - let tweaks = self.query.block_tweaks(scan_height); + let tweaks = self.query.block_tweaks_with_filters(scan_height, dust_limit, filter_spent)?; Ok(json!(tweaks)) } diff --git a/src/new_index/query.rs b/src/new_index/query.rs index 175f8ed9b..7721adf89 100644 --- a/src/new_index/query.rs +++ b/src/new_index/query.rs @@ -117,6 +117,14 @@ impl Query { .get_block_tweaks(&self.chain.hash_by_height(height as usize).unwrap()) } + pub fn block_tweaks_with_filters(&self, height: u32, dust_limit: Option, filter_spent: bool) -> Result> { + let block_hash = self.chain + .hash_by_height(height as usize) + .chain_err(|| format!("block not found at height {}", height))?; + // Note: Parameter name was changed to filter_spent in schema.rs + Ok(self.chain.get_block_tweaks_with_filters(&block_hash, dust_limit, filter_spent)) + } + pub fn tweaks_iter_scan_reverse(&self, height: u32) -> ReverseScanIterator { self.chain.tweaks_iter_scan_reverse(height) } diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index f8d29a6ee..5f2e8ee61 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -581,7 +581,7 @@ impl Indexer { let amount = (txo.value as Amount).to_sat(); #[allow(deprecated)] if txo.script_pubkey.is_v1_p2tr() - && amount >= self.iconfig.sp_min_dust.unwrap_or(1_000) as u64 + && amount >= self.iconfig.sp_min_dust.unwrap_or(0) as u64 { output_pubkeys.push(VoutData { vout: txo_index, @@ -634,8 +634,8 @@ impl Indexer { let pubkeys_ref: Vec<_> = pubkeys.iter().collect(); if !pubkeys_ref.is_empty() { if let Some(tweak) = calculate_tweak_data(&pubkeys_ref, &outpoints).ok() { - // persist tweak index: - // K{blockhash}{txid} → {tweak}{serialized-vout-data} + // persist detailed tweak index: + // K{blockheight}{txid} → {tweak}{serialized-vout-data} rows.push( TweakTxRow::new( blockheight, @@ -647,6 +647,7 @@ impl Indexer { ) .into_row(), ); + tweaks.push(tweak.serialize().to_vec()); } } @@ -1137,8 +1138,64 @@ impl ChainQuery { } pub fn get_block_tweaks(&self, hash: &BlockHash) -> Vec { + self.get_block_tweaks_with_filters(hash, None, false) + } + + pub fn get_block_tweaks_with_filters(&self, hash: &BlockHash, min_dust: Option, filter_spent: bool) -> Vec { let _timer = self.start_timer("get_block_tweaks"); + // If no filtering needed, use the fast path (existing behavior) + if (min_dust.is_none() || min_dust == Some(0)) && !filter_spent { + return self.get_block_tweaks_fast_path(hash); + } + + // Filtering path: scan TweakTxRow entries for this block + let block_height = self.height_by_hash(hash); + if block_height.is_none() { + return vec![]; + } + let block_height = block_height.unwrap() as u32; + + let mut filtered_tweaks = Vec::new(); + let prefix = TweakTxRow::prefix_blockheight(block_height); + let tweak_iter = self.store.tweak_db.iter_scan(&prefix) + .map(TweakTxRow::from_row) + .take_while(|row| row.key.blockheight == block_height); + + for tweak_row in tweak_iter { + let tweak_data = tweak_row.get_tweak_data(); + + // Apply dust filter if specified + if let Some(min_dust_value) = min_dust { + if !tweak_data.vout_data.iter().any(|vout| vout.amount >= min_dust_value) { + continue; // Skip this tweak - no outputs above dust limit + } + } + + // Apply spent filter if specified + if filter_spent { + let has_unspent_output = tweak_data.vout_data.iter().any(|vout| { + let outpoint = OutPoint { + txid: tweak_row.key.txid, + vout: vout.vout as u32, + }; + // On-demand spend lookup - if lookup_spend returns None, the output is unspent + self.lookup_spend(&outpoint).is_none() + }); + + if !has_unspent_output { + continue; // Skip this tweak - all outputs are spent + } + } + + filtered_tweaks.push(tweak_data.tweak); + } + + filtered_tweaks + } + + // Fast path for backward compatibility + fn get_block_tweaks_fast_path(&self, hash: &BlockHash) -> Vec { let tweaks: Vec> = self .store .tweak_db