use crate::types::{OutPoint, UtxoSet, UTXO};
#[cfg(feature = "production")]
use rustc_hash::{FxHashMap, FxHashSet};
#[cfg(not(feature = "production"))]
use std::collections::{HashMap as FxHashMap, HashSet as FxHashSet};
pub type UtxoDeletionKey = [u8; 36];
type OutPointKey = UtxoDeletionKey;
#[inline]
fn outpoint_to_key(op: &OutPoint) -> OutPointKey {
let mut key = [0u8; 36];
key[..32].copy_from_slice(&op.hash);
key[32..36].copy_from_slice(&op.index.to_be_bytes());
key
}
#[inline]
pub fn utxo_deletion_key_to_outpoint(key: &UtxoDeletionKey) -> OutPoint {
let mut hash = [0u8; 32];
hash.copy_from_slice(&key[..32]);
let index = u32::from_be_bytes(key[32..36].try_into().unwrap());
OutPoint { hash, index }
}
#[inline]
fn key_to_outpoint(key: &OutPointKey) -> OutPoint {
utxo_deletion_key_to_outpoint(key)
}
pub trait UtxoLookup {
fn get(&self, outpoint: &OutPoint) -> Option<&UTXO>;
fn contains_key(&self, outpoint: &OutPoint) -> bool {
self.get(outpoint).is_some()
}
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(feature = "production")]
impl UtxoLookup for UtxoSet {
#[inline]
fn get(&self, outpoint: &OutPoint) -> Option<&UTXO> {
FxHashMap::get(self, outpoint).map(|a| a.as_ref())
}
#[inline]
fn contains_key(&self, outpoint: &OutPoint) -> bool {
FxHashMap::contains_key(self, outpoint)
}
#[inline]
fn len(&self) -> usize {
FxHashMap::len(self)
}
#[inline]
fn is_empty(&self) -> bool {
FxHashMap::is_empty(self)
}
}
#[cfg(not(feature = "production"))]
impl UtxoLookup for UtxoSet {
#[inline]
fn get(&self, outpoint: &OutPoint) -> Option<&UTXO> {
std::collections::HashMap::get(self, outpoint).map(|a| a.as_ref())
}
#[inline]
fn contains_key(&self, outpoint: &OutPoint) -> bool {
std::collections::HashMap::contains_key(self, outpoint)
}
#[inline]
fn len(&self) -> usize {
std::collections::HashMap::len(self)
}
#[inline]
fn is_empty(&self) -> bool {
std::collections::HashMap::is_empty(self)
}
}
#[derive(Debug)]
pub struct UtxoOverlay<'a> {
base: &'a UtxoSet,
additions: FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
deletions: FxHashSet<OutPointKey>,
has_deletions: bool,
}
impl<'a> UtxoOverlay<'a> {
#[inline]
pub fn new(base: &'a UtxoSet) -> Self {
Self {
base,
additions: FxHashMap::with_capacity_and_hasher(6000, Default::default()),
deletions: FxHashSet::with_capacity_and_hasher(6000, Default::default()),
has_deletions: false, }
}
#[inline]
pub fn with_capacity(base: &'a UtxoSet, additions_cap: usize, deletions_cap: usize) -> Self {
Self {
base,
additions: FxHashMap::with_capacity_and_hasher(additions_cap, Default::default()),
deletions: FxHashSet::with_capacity_and_hasher(deletions_cap, Default::default()),
has_deletions: false, }
}
#[inline]
pub fn get(&self, outpoint: &OutPoint) -> Option<&UTXO> {
if self.has_deletions && self.deletions.contains(&outpoint_to_key(outpoint)) {
return None;
}
if let Some(arc) = self.additions.get(outpoint) {
return Some(arc.as_ref());
}
self.base.get(outpoint).map(|a| a.as_ref())
}
#[inline]
pub fn contains_key(&self, outpoint: &OutPoint) -> bool {
if self.has_deletions && self.deletions.contains(&outpoint_to_key(outpoint)) {
return false;
}
self.additions.contains_key(outpoint) || self.base.contains_key(outpoint)
}
#[inline]
pub fn insert(&mut self, outpoint: OutPoint, utxo: UTXO) {
self.insert_arc(outpoint, std::sync::Arc::new(utxo));
}
#[inline]
pub fn insert_arc(&mut self, outpoint: OutPoint, utxo: std::sync::Arc<UTXO>) {
if self.deletions.remove(&outpoint_to_key(&outpoint)) {
if self.deletions.is_empty() {
self.has_deletions = false;
}
}
self.additions.insert(outpoint, utxo);
}
#[inline]
pub fn mark_spent(&mut self, outpoint: &OutPoint) {
if self.additions.remove(outpoint).is_some() {
return;
}
if self.base.contains_key(outpoint) {
self.deletions.insert(outpoint_to_key(outpoint));
self.has_deletions = true;
}
}
#[inline]
pub fn remove(&mut self, outpoint: &OutPoint) -> Option<std::sync::Arc<UTXO>> {
if let Some(arc) = self.additions.remove(outpoint) {
return Some(arc);
}
if let Some(arc) = self.base.get(outpoint) {
self.deletions.insert(outpoint_to_key(outpoint));
self.has_deletions = true;
return Some(std::sync::Arc::clone(arc));
}
None
}
#[inline]
pub fn additions_len(&self) -> usize {
self.additions.len()
}
#[inline]
pub fn deletions_len(&self) -> usize {
self.deletions.len()
}
#[inline]
pub fn base_len(&self) -> usize {
self.base.len()
}
pub fn apply_to_base(self) -> UtxoSet {
let mut result = self.base.clone();
for key in self.deletions {
result.remove(&key_to_outpoint(&key));
}
for (outpoint, arc) in self.additions {
result.insert(outpoint, arc);
}
result
}
#[inline]
pub fn additions(&self) -> &FxHashMap<OutPoint, std::sync::Arc<UTXO>> {
&self.additions
}
#[inline]
pub fn deletions(&self) -> &FxHashSet<OutPointKey> {
&self.deletions
}
#[inline]
pub fn into_changes(
self,
) -> (
FxHashMap<OutPoint, std::sync::Arc<UTXO>>,
FxHashSet<UtxoDeletionKey>,
) {
(self.additions, self.deletions)
}
}
impl<'a> UtxoLookup for UtxoOverlay<'a> {
#[inline]
fn get(&self, outpoint: &OutPoint) -> Option<&UTXO> {
if self.has_deletions && self.deletions.contains(&outpoint_to_key(outpoint)) {
return None;
}
if let Some(arc) = self.additions.get(outpoint) {
return Some(arc.as_ref());
}
self.base.get(outpoint).map(|a| a.as_ref())
}
#[inline]
fn contains_key(&self, outpoint: &OutPoint) -> bool {
if self.has_deletions && self.deletions.contains(&outpoint_to_key(outpoint)) {
return false;
}
self.additions.contains_key(outpoint) || self.base.contains_key(outpoint)
}
#[inline]
fn len(&self) -> usize {
self.base.len() + self.additions.len() - self.deletions.len()
}
}
#[cfg(feature = "production")]
pub type FastUtxoSet = rustc_hash::FxHashMap<OutPoint, UTXO>;
#[cfg(not(feature = "production"))]
pub type FastUtxoSet = std::collections::HashMap<OutPoint, UTXO>;
#[cfg(feature = "production")]
#[inline]
pub fn to_fast_utxo_set(utxo_set: &UtxoSet) -> FastUtxoSet {
utxo_set.iter().map(|(k, v)| (*k, (**v).clone())).collect()
}
#[cfg(not(feature = "production"))]
#[inline]
pub fn to_fast_utxo_set(utxo_set: &UtxoSet) -> FastUtxoSet {
utxo_set.iter().map(|(k, v)| (*k, (**v).clone())).collect()
}
#[inline]
pub fn apply_transaction_to_overlay(
overlay: &mut UtxoOverlay<'_>,
tx: &crate::types::Transaction,
tx_id: crate::types::Hash,
height: crate::types::Natural,
) -> Vec<crate::reorganization::UndoEntry> {
use crate::reorganization::UndoEntry;
use crate::transaction::is_coinbase;
let mut undo_entries = Vec::with_capacity(tx.inputs.len() + tx.outputs.len());
if !is_coinbase(tx) {
for input in &tx.inputs {
if let Some(previous_utxo) = overlay.remove(&input.prevout) {
undo_entries.push(UndoEntry {
outpoint: input.prevout,
previous_utxo: Some(previous_utxo),
new_utxo: None, });
}
}
}
for (i, output) in tx.outputs.iter().enumerate() {
let outpoint = OutPoint {
hash: tx_id,
index: i as u32,
};
let utxo = UTXO {
value: output.value,
script_pubkey: output.script_pubkey.as_slice().into(),
height,
is_coinbase: is_coinbase(tx),
};
let utxo_arc = std::sync::Arc::new(utxo);
undo_entries.push(UndoEntry {
outpoint,
previous_utxo: None, new_utxo: Some(std::sync::Arc::clone(&utxo_arc)),
});
overlay.insert_arc(outpoint, utxo_arc);
}
undo_entries
}
#[inline]
pub fn apply_transaction_to_overlay_no_undo(
overlay: &mut UtxoOverlay<'_>,
tx: &crate::types::Transaction,
tx_id: crate::types::Hash,
height: crate::types::Natural,
) {
let is_cb = crate::transaction::is_coinbase(tx);
if !is_cb {
for input in &tx.inputs {
overlay.mark_spent(&input.prevout);
}
}
for (i, output) in tx.outputs.iter().enumerate() {
let outpoint = OutPoint {
hash: tx_id,
index: i as u32,
};
let utxo = UTXO {
value: output.value,
script_pubkey: output.script_pubkey.as_slice().into(),
height,
is_coinbase: is_cb,
};
overlay.insert(outpoint, utxo);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utxo_set_insert;
fn make_outpoint(idx: u8) -> OutPoint {
OutPoint {
hash: [idx; 32],
index: idx as u32,
}
}
fn make_utxo(value: i64) -> UTXO {
UTXO {
value,
script_pubkey: vec![crate::opcodes::OP_DUP, crate::opcodes::OP_HASH160].into(), height: 1,
is_coinbase: false,
}
}
#[test]
fn test_overlay_lookup_from_base() {
let mut base = UtxoSet::default();
utxo_set_insert(&mut base, make_outpoint(1), make_utxo(1000));
utxo_set_insert(&mut base, make_outpoint(2), make_utxo(2000));
let overlay = UtxoOverlay::new(&base);
assert_eq!(overlay.get(&make_outpoint(1)).unwrap().value, 1000);
assert_eq!(overlay.get(&make_outpoint(2)).unwrap().value, 2000);
assert!(overlay.get(&make_outpoint(3)).is_none());
}
#[test]
fn test_overlay_additions() {
let base = UtxoSet::default();
let mut overlay = UtxoOverlay::new(&base);
overlay.insert(make_outpoint(1), make_utxo(1000));
assert_eq!(overlay.get(&make_outpoint(1)).unwrap().value, 1000);
assert_eq!(overlay.additions_len(), 1);
}
#[test]
fn test_overlay_deletions() {
let mut base = UtxoSet::default();
utxo_set_insert(&mut base, make_outpoint(1), make_utxo(1000));
let mut overlay = UtxoOverlay::new(&base);
let removed = overlay.remove(&make_outpoint(1));
assert_eq!(removed.unwrap().value, 1000);
assert!(overlay.get(&make_outpoint(1)).is_none());
assert_eq!(overlay.deletions_len(), 1);
}
#[test]
fn test_overlay_intra_block_spend() {
let base = UtxoSet::default();
let mut overlay = UtxoOverlay::new(&base);
overlay.insert(make_outpoint(1), make_utxo(1000));
assert!(overlay.get(&make_outpoint(1)).is_some());
let removed = overlay.remove(&make_outpoint(1));
assert_eq!(removed.unwrap().value, 1000);
assert!(overlay.get(&make_outpoint(1)).is_none());
assert_eq!(overlay.deletions_len(), 0);
assert_eq!(overlay.additions_len(), 0);
}
#[test]
fn test_overlay_apply() {
let mut base = UtxoSet::default();
utxo_set_insert(&mut base, make_outpoint(1), make_utxo(1000));
utxo_set_insert(&mut base, make_outpoint(2), make_utxo(2000));
let mut overlay = UtxoOverlay::new(&base);
overlay.remove(&make_outpoint(1));
overlay.insert(make_outpoint(3), make_utxo(3000));
let result = overlay.apply_to_base();
assert!(result.get(&make_outpoint(1)).is_none()); assert_eq!(result.get(&make_outpoint(2)).unwrap().value, 2000); assert_eq!(result.get(&make_outpoint(3)).unwrap().value, 3000); }
#[test]
fn test_overlay_no_clone_on_creation() {
let mut base = UtxoSet::default();
for i in 0..10000 {
utxo_set_insert(&mut base, make_outpoint(i as u8), make_utxo(i as i64));
}
let start = std::time::Instant::now();
let _overlay = UtxoOverlay::new(&base);
let elapsed = start.elapsed();
assert!(
elapsed.as_micros() < 100,
"Overlay creation took {:?}",
elapsed
);
}
}