diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 62ad137c..da559f7e 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -262,7 +262,10 @@ impl StateService { /// Return the utxo pointed to by `outpoint` if it exists in any chain. pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option { - self.mem.utxo(outpoint).or_else(|| self.disk.utxo(outpoint)) + self.mem + .utxo(outpoint) + .or_else(|| self.disk.utxo(outpoint)) + .or_else(|| self.queued_blocks.utxo(outpoint)) } /// Return an iterator over the relevant chain of the block identified by diff --git a/zebra-state/src/service/non_finalized_state/queued_blocks.rs b/zebra-state/src/service/non_finalized_state/queued_blocks.rs index 783ddeb0..790c1238 100644 --- a/zebra-state/src/service/non_finalized_state/queued_blocks.rs +++ b/zebra-state/src/service/non_finalized_state/queued_blocks.rs @@ -4,7 +4,7 @@ use std::{ }; use tracing::instrument; -use zebra_chain::block; +use zebra_chain::{block, transparent}; use crate::service::QueuedBlock; @@ -17,6 +17,8 @@ pub struct QueuedBlocks { by_parent: HashMap>, /// Hashes from `queued_blocks`, indexed by block height. by_height: BTreeMap>, + /// Known UTXOs. + known_utxos: HashMap, } impl QueuedBlocks { @@ -33,6 +35,21 @@ impl QueuedBlocks { .expect("validated non-finalized blocks have a coinbase height"); let parent_hash = new.block.header.previous_block_hash; + // XXX QueuedBlock should include this data + let prev_utxo_count = self.known_utxos.len(); + for transaction in &new.block.transactions { + let hash = transaction.hash(); + for (index, output) in transaction.outputs().iter().cloned().enumerate() { + let index = index as u32; + self.known_utxos + .insert(transparent::OutPoint { hash, index }, output); + } + } + tracing::trace!( + known_utxos = self.known_utxos.len(), + new = self.known_utxos.len() - prev_utxo_count + ); + let replaced = self.blocks.insert(new_hash, new); assert!(replaced.is_none(), "hashes must be unique"); let inserted = self @@ -62,9 +79,26 @@ impl QueuedBlocks { .unwrap_or_default() .into_iter() .map(|hash| { - self.blocks + let queued = self + .blocks .remove(&hash) - .expect("block is present if its hash is in by_parent") + .expect("block is present if its hash is in by_parent"); + + let prev_utxo_count = self.known_utxos.len(); + for transaction in &queued.block.transactions { + let hash = transaction.hash(); + for (index, _output) in transaction.outputs().iter().cloned().enumerate() { + let index = index as u32; + self.known_utxos + .remove(&transparent::OutPoint { hash, index }); + } + } + tracing::trace!( + known_utxos = self.known_utxos.len(), + removed = prev_utxo_count - self.known_utxos.len() + ); + + queued }) .collect::>(); @@ -142,6 +176,11 @@ impl QueuedBlocks { } metrics::gauge!("state.memory.queued.block.count", self.blocks.len() as _); } + + /// Try to look up this UTXO in any queued block. + pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option { + self.known_utxos.get(outpoint).cloned() + } } #[cfg(test)]