From b2ab18ab6888d6e2b59494a8009eb583a7adfcaa Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Thu, 26 Dec 2024 18:28:21 +0200 Subject: [PATCH] Don't deserialize transactions if not needed After filtering, we need the serialized bytes for caching only. --- src/cache.rs | 12 ++++++------ src/electrum.rs | 8 ++++++-- src/status.rs | 36 ++++++++++++++++-------------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index c54df1fc1..b09a9165a 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,4 +1,4 @@ -use bitcoin::{Transaction, Txid}; +use bitcoin::Txid; use parking_lot::RwLock; use std::collections::HashMap; @@ -7,7 +7,7 @@ use std::sync::Arc; use crate::metrics::{self, Histogram, Metrics}; pub(crate) struct Cache { - txs: Arc>>, + txs: Arc>>>, // stats txs_size: Histogram, @@ -26,18 +26,18 @@ impl Cache { } } - pub fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Transaction) { + pub fn add_tx(&self, txid: Txid, f: impl FnOnce() -> Box<[u8]>) { self.txs.write().entry(txid).or_insert_with(|| { let tx = f(); - self.txs_size.observe("serialized", tx.total_size() as f64); + self.txs_size.observe("serialized", tx.len() as f64); tx }); } pub fn get_tx(&self, txid: &Txid, f: F) -> Option where - F: FnOnce(&Transaction) -> T, + F: FnOnce(&[u8]) -> T, { - self.txs.read().get(txid).map(f) + self.txs.read().get(txid).map(|tx_bytes| f(tx_bytes)) } } diff --git a/src/electrum.rs b/src/electrum.rs index 46e0fa968..a7924324f 100644 --- a/src/electrum.rs +++ b/src/electrum.rs @@ -2,6 +2,7 @@ use anyhow::{bail, Context, Result}; use bitcoin::{ consensus::{deserialize, encode::serialize_hex}, hashes::hex::FromHex, + hex::DisplayHex, BlockHash, Txid, }; use crossbeam_channel::Receiver; @@ -373,8 +374,11 @@ impl Rpc { .map(|(blockhash, _tx)| blockhash); return self.daemon.get_transaction_info(&txid, blockhash); } - if let Some(tx) = self.cache.get_tx(&txid, serialize_hex) { - return Ok(json!(tx)); + if let Some(tx_hex) = self + .cache + .get_tx(&txid, |tx_bytes| tx_bytes.to_lower_hex_string()) + { + return Ok(json!(tx_hex)); } debug!("tx cache miss: txid={}", txid); // use internal index to load confirmed transaction without an RPC diff --git a/src/status.rs b/src/status.rs index 0282f09db..df56e340a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,6 +1,6 @@ use anyhow::Result; use bitcoin::{ - consensus::Decodable, + consensus::serialize, hashes::{sha256, Hash, HashEngine}, Amount, BlockHash, OutPoint, SignedAmount, Transaction, Txid, }; @@ -337,7 +337,7 @@ impl ScriptHashStatus { self.for_new_blocks(funding_blockhashes, daemon, |blockhash, block| { let block_entries = result.entry(blockhash).or_default(); for filtered_outputs in filter_block_txs_outputs(block, scripthash) { - cache.add_tx(filtered_outputs.txid, move || filtered_outputs.tx); + cache.add_tx(filtered_outputs.txid, move || filtered_outputs.tx_bytes); outpoints.extend(make_outpoints( filtered_outputs.txid, &filtered_outputs.result, @@ -355,7 +355,7 @@ impl ScriptHashStatus { self.for_new_blocks(spending_blockhashes, daemon, |blockhash, block| { let block_entries = result.entry(blockhash).or_default(); for filtered_inputs in filter_block_txs_inputs(&block, outpoints) { - cache.add_tx(filtered_inputs.txid, move || filtered_inputs.tx); + cache.add_tx(filtered_inputs.txid, move || filtered_inputs.tx_bytes); block_entries .entry(filtered_inputs.pos) .or_insert_with(|| TxEntry::new(filtered_inputs.txid)) @@ -394,7 +394,7 @@ impl ScriptHashStatus { .entry(entry.txid) .or_insert_with(|| TxEntry::new(entry.txid)) .outputs = funding_outputs; - cache.add_tx(entry.txid, || entry.tx.clone()); + cache.add_tx(entry.txid, || serialize(&entry.tx).into_boxed_slice()); } for entry in outpoints .iter() @@ -406,7 +406,7 @@ impl ScriptHashStatus { .entry(entry.txid) .or_insert_with(|| TxEntry::new(entry.txid)) .spent = spent_outpoints; - cache.add_tx(entry.txid, || entry.tx.clone()); + cache.add_tx(entry.txid, || serialize(&entry.tx).into_boxed_slice()); } result.into_values().collect() } @@ -501,7 +501,7 @@ fn compute_status_hash(history: &[HistoryEntry]) -> Option { } struct FilteredTx { - tx: Transaction, + tx_bytes: Box<[u8]>, txid: Txid, pos: usize, result: Vec, @@ -515,22 +515,20 @@ fn filter_block_txs_outputs(block: SerBlock, scripthash: ScriptHash) -> Vec ControlFlow<()> { if !self.buffer.is_empty() { - let result = std::mem::take(&mut self.buffer); - let txid = bsl_txid(tx); - let tx = bitcoin::Transaction::consensus_decode(&mut tx.as_ref()) - .expect("transaction was already validated"); self.result.push(FilteredTx:: { - tx, - txid, + tx_bytes: tx.as_ref().into(), + txid: bsl_txid(tx), pos: self.pos, - result, + result: std::mem::take(&mut self.buffer), // clear buffer for next tx }); } self.pos += 1; ControlFlow::Continue(()) } + // Keep only relevant outputs fn visit_tx_out(&mut self, vout: usize, tx_out: &bsl::TxOut) -> ControlFlow<()> { let current = ScriptHash::hash(tx_out.script_pubkey()); if current == self.scripthash { @@ -566,22 +564,20 @@ fn filter_block_txs_inputs( } impl Visitor for FindInputs<'_> { + // Called after all TxIns are visited fn visit_transaction(&mut self, tx: &bsl::Transaction) -> ControlFlow<()> { if !self.buffer.is_empty() { - let result = std::mem::take(&mut self.buffer); - let txid = bsl_txid(tx); - let tx = bitcoin::Transaction::consensus_decode(&mut tx.as_ref()) - .expect("transaction was already validated"); self.result.push(FilteredTx:: { - tx, - txid, + tx_bytes: tx.as_ref().into(), + txid: bsl_txid(tx), pos: self.pos, - result, + result: std::mem::take(&mut self.buffer), // clear buffer for next tx }); } self.pos += 1; ControlFlow::Continue(()) } + // Keep only relevant outpoints fn visit_tx_in(&mut self, _vin: usize, tx_in: &bsl::TxIn) -> ControlFlow<()> { let current: OutPoint = tx_in.prevout().into(); if self.outpoints.contains(¤t) {