mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-25 17:58:11 -07:00
mempool: snap
This commit is contained in:
@@ -15,7 +15,8 @@ use addr_entry::AddrEntry;
|
||||
pub struct AddrTracker(FxHashMap<AddrBytes, AddrEntry>);
|
||||
|
||||
impl AddrTracker {
|
||||
pub fn add_tx(&mut self, tx: &Transaction, txid: &Txid) {
|
||||
pub fn add_tx(&mut self, tx: &Transaction) {
|
||||
let txid = &tx.txid;
|
||||
for txin in &tx.input {
|
||||
if let Some(prevout) = txin.prevout.as_ref() {
|
||||
self.add_input(txid, prevout);
|
||||
@@ -28,7 +29,8 @@ impl AddrTracker {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_tx(&mut self, tx: &Transaction, txid: &Txid) {
|
||||
pub fn remove_tx(&mut self, tx: &Transaction) {
|
||||
let txid = &tx.txid;
|
||||
for txin in &tx.input {
|
||||
if let Some(prevout) = txin.prevout.as_ref() {
|
||||
self.remove_input(txid, prevout);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! Stateful in-memory holders. After Phase 3 they're plain owned
|
||||
//! types (no internal locks) — `State` aggregates them under a
|
||||
//! single `RwLock` in `crate::state`.
|
||||
//! In-memory holders for live mempool state. Plain owned types with
|
||||
//! no internal locks: `crate::state::State` aggregates them under a
|
||||
//! single `RwLock` so the cycle steps and read-side accessors share
|
||||
//! one lock-order discipline.
|
||||
|
||||
pub mod addr_tracker;
|
||||
pub(crate) mod addr_tracker;
|
||||
pub(crate) mod outpoint_spends;
|
||||
pub mod tx_graveyard;
|
||||
pub mod tx_store;
|
||||
pub(crate) mod tx_graveyard;
|
||||
pub(crate) mod tx_store;
|
||||
|
||||
pub use addr_tracker::AddrTracker;
|
||||
pub(crate) use addr_tracker::AddrTracker;
|
||||
pub(crate) use outpoint_spends::OutpointSpends;
|
||||
pub use tx_graveyard::{TxGraveyard, TxTombstone};
|
||||
pub use tx_store::TxStore;
|
||||
pub(crate) use tx_graveyard::{TxGraveyard, TxTombstone};
|
||||
pub(crate) use tx_store::TxStore;
|
||||
|
||||
@@ -13,11 +13,7 @@ pub struct OutpointSpends(FxHashMap<OutpointPrefix, TxidPrefix>);
|
||||
|
||||
impl OutpointSpends {
|
||||
pub fn insert_spends(&mut self, tx: &Transaction, spender: TxidPrefix) {
|
||||
for input in &tx.input {
|
||||
if input.is_coinbase {
|
||||
continue;
|
||||
}
|
||||
let key = OutpointPrefix::new(TxidPrefix::from(&input.txid), input.vout);
|
||||
for key in spent_outpoints(tx) {
|
||||
self.0.insert(key, spender);
|
||||
}
|
||||
}
|
||||
@@ -25,11 +21,7 @@ impl OutpointSpends {
|
||||
/// Only removes entries whose stored prefix still matches `spender`,
|
||||
/// so an outpoint already re-claimed by a later spender is left alone.
|
||||
pub fn remove_spends(&mut self, tx: &Transaction, spender: TxidPrefix) {
|
||||
for input in &tx.input {
|
||||
if input.is_coinbase {
|
||||
continue;
|
||||
}
|
||||
let key = OutpointPrefix::new(TxidPrefix::from(&input.txid), input.vout);
|
||||
for key in spent_outpoints(tx) {
|
||||
if self.0.get(&key) == Some(&spender) {
|
||||
self.0.remove(&key);
|
||||
}
|
||||
@@ -41,3 +33,10 @@ impl OutpointSpends {
|
||||
self.0.get(key).copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn spent_outpoints(tx: &Transaction) -> impl Iterator<Item = OutpointPrefix> + '_ {
|
||||
tx.input
|
||||
.iter()
|
||||
.filter(|i| !i.is_coinbase)
|
||||
.map(|i| OutpointPrefix::new(TxidPrefix::from(&i.txid), i.vout))
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use brk_types::{Transaction, Txid};
|
||||
use brk_types::{FeeRate, Transaction, Txid};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
mod tombstone;
|
||||
|
||||
pub use tombstone::TxTombstone;
|
||||
pub(crate) use tombstone::TxTombstone;
|
||||
|
||||
use crate::{TxEntry, TxRemoval};
|
||||
|
||||
@@ -23,10 +23,6 @@ pub struct TxGraveyard {
|
||||
}
|
||||
|
||||
impl TxGraveyard {
|
||||
pub fn contains(&self, txid: &Txid) -> bool {
|
||||
self.tombstones.contains_key(txid)
|
||||
}
|
||||
|
||||
pub fn tombstones_len(&self) -> usize {
|
||||
self.tombstones.len()
|
||||
}
|
||||
@@ -39,6 +35,25 @@ impl TxGraveyard {
|
||||
self.tombstones.get(txid)
|
||||
}
|
||||
|
||||
/// Tombstone iff the tx vanished from the pool (mined, expired, or
|
||||
/// dropped). `Replaced` tombstones return `None` because the tx
|
||||
/// will not confirm.
|
||||
pub fn get_vanished(&self, txid: &Txid) -> Option<&TxTombstone> {
|
||||
let tomb = self.tombstones.get(txid)?;
|
||||
matches!(tomb.removal, TxRemoval::Vanished).then_some(tomb)
|
||||
}
|
||||
|
||||
/// Walk forward through `Replaced { by }` to the terminal replacer.
|
||||
/// Returns `txid` itself if it isn't replaced (live or `Vanished`).
|
||||
pub fn replacement_root_of(&self, mut txid: Txid) -> Txid {
|
||||
while let Some(TxRemoval::Replaced { by }) =
|
||||
self.tombstones.get(&txid).map(|t| &t.removal)
|
||||
{
|
||||
txid = *by;
|
||||
}
|
||||
txid
|
||||
}
|
||||
|
||||
/// Tombstones marked as `Replaced { by: replacer }`. Used to walk
|
||||
/// backward through RBF history: given a tx that's still live (or
|
||||
/// in the graveyard), find every tx it displaced.
|
||||
@@ -61,18 +76,33 @@ impl TxGraveyard {
|
||||
pub fn replaced_iter_recent_first(&self) -> impl Iterator<Item = (&Txid, &Txid)> {
|
||||
self.order.iter().rev().filter_map(|(t, txid)| {
|
||||
let ts = self.tombstones.get(txid)?;
|
||||
if ts.removed_at() != *t {
|
||||
if ts.removed_at != *t {
|
||||
return None;
|
||||
}
|
||||
Some((txid, ts.replaced_by()?))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bury(&mut self, txid: Txid, tx: Transaction, entry: TxEntry, removal: TxRemoval) {
|
||||
let now = Instant::now();
|
||||
self.tombstones
|
||||
.insert(txid, TxTombstone::new(tx, entry, removal, now));
|
||||
self.order.push_back((now, txid));
|
||||
pub fn bury(
|
||||
&mut self,
|
||||
tx: Transaction,
|
||||
entry: TxEntry,
|
||||
chunk_rate: FeeRate,
|
||||
removal: TxRemoval,
|
||||
) {
|
||||
let txid = entry.txid;
|
||||
let removed_at = Instant::now();
|
||||
self.tombstones.insert(
|
||||
txid,
|
||||
TxTombstone {
|
||||
tx,
|
||||
entry,
|
||||
chunk_rate,
|
||||
removal,
|
||||
removed_at,
|
||||
},
|
||||
);
|
||||
self.order.push_back((removed_at, txid));
|
||||
}
|
||||
|
||||
/// Remove and return the tombstone, e.g. when the tx comes back to life.
|
||||
@@ -92,7 +122,7 @@ impl TxGraveyard {
|
||||
}
|
||||
let (_, txid) = self.order.pop_front().unwrap();
|
||||
if let Some(ts) = self.tombstones.get(&txid)
|
||||
&& ts.removed_at() == t
|
||||
&& ts.removed_at == t
|
||||
{
|
||||
self.tombstones.remove(&txid);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,23 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use brk_types::{Transaction, Txid};
|
||||
use brk_types::{FeeRate, Transaction, Txid};
|
||||
|
||||
use crate::{TxEntry, TxRemoval};
|
||||
|
||||
/// A buried mempool tx, retained for reappearance detection and
|
||||
/// post-mine analytics.
|
||||
/// post-mine analytics. `chunk_rate` is the linearized chunk feerate at
|
||||
/// burial time - same value `live_effective_fee_rate` reported while
|
||||
/// the tx was alive, so an evicted RBF predecessor reports the
|
||||
/// package-effective rate, not a misleading isolated `fee/vsize`.
|
||||
pub struct TxTombstone {
|
||||
pub tx: Transaction,
|
||||
pub entry: TxEntry,
|
||||
removal: TxRemoval,
|
||||
removed_at: Instant,
|
||||
pub chunk_rate: FeeRate,
|
||||
pub removal: TxRemoval,
|
||||
pub removed_at: Instant,
|
||||
}
|
||||
|
||||
impl TxTombstone {
|
||||
pub(crate) fn new(
|
||||
tx: Transaction,
|
||||
entry: TxEntry,
|
||||
removal: TxRemoval,
|
||||
removed_at: Instant,
|
||||
) -> Self {
|
||||
Self {
|
||||
tx,
|
||||
entry,
|
||||
removal,
|
||||
removed_at,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reason(&self) -> &TxRemoval {
|
||||
&self.removal
|
||||
}
|
||||
|
||||
pub(crate) fn removed_at(&self) -> Instant {
|
||||
self.removed_at
|
||||
}
|
||||
|
||||
pub(crate) fn replaced_by(&self) -> Option<&Txid> {
|
||||
match &self.removal {
|
||||
TxRemoval::Replaced { by } => Some(by),
|
||||
|
||||
@@ -34,10 +34,6 @@ impl TxStore {
|
||||
self.records.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.records.is_empty()
|
||||
}
|
||||
|
||||
pub fn get(&self, txid: &Txid) -> Option<&Transaction> {
|
||||
self.records.get(&TxidPrefix::from(txid)).map(|r| &r.tx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user