global: fixes

This commit is contained in:
nym21
2026-05-02 00:42:16 +02:00
parent 6f879a5551
commit 2b8a0a8cf7
99 changed files with 4308 additions and 1525 deletions

View File

@@ -17,7 +17,7 @@ use std::{sync::Arc, thread, time::Duration};
use brk_error::Result;
use brk_rpc::Client;
use brk_types::{AddrBytes, MempoolInfo, TxOut, Txid, Vout};
use brk_types::{AddrBytes, MempoolInfo, OutpointPrefix, TxOut, Txid, TxidPrefix, Vin, Vout};
use parking_lot::RwLockReadGuard;
use tracing::error;
@@ -75,6 +75,25 @@ impl Mempool {
self.0.state.addrs.read().stats_hash(addr)
}
/// Look up the mempool tx that spends `(txid, vout)`. Returns
/// `(spender_txid, vin)` if the outpoint is spent in the mempool,
/// `None` otherwise. The spender's input list is walked to rule
/// out a `TxidPrefix` collision before returning a match.
pub fn lookup_spender(&self, txid: &Txid, vout: Vout) -> Option<(Txid, Vin)> {
let key = OutpointPrefix::new(TxidPrefix::from(txid), vout);
let txs = self.0.state.txs.read();
let entries = self.0.state.entries.read();
let outpoint_spends = self.0.state.outpoint_spends.read();
let idx = outpoint_spends.get(&key)?;
let spender_txid = entries.slot(idx)?.txid.clone();
let spender_tx = txs.get(&spender_txid)?;
let vin_pos = spender_tx
.input
.iter()
.position(|inp| inp.txid == *txid && inp.vout == vout)?;
Some((spender_txid, Vin::from(vin_pos)))
}
pub fn txs(&self) -> RwLockReadGuard<'_, TxStore> {
self.0.state.txs.read()
}

View File

@@ -32,7 +32,7 @@ impl Applier {
}
fn bury_one(s: &mut LockedState, prefix: &TxidPrefix, reason: TxRemoval) {
let Some(entry) = s.entries.remove(prefix) else {
let Some((idx, entry)) = s.entries.remove(prefix) else {
return;
};
let txid = entry.txid.clone();
@@ -41,6 +41,7 @@ impl Applier {
};
s.info.remove(&tx, entry.fee);
s.addrs.remove_tx(&tx, &txid);
s.outpoint_spends.remove_spends(&tx, idx);
s.graveyard.bury(txid, tx, entry, reason);
}
@@ -71,7 +72,8 @@ impl Applier {
s.info.add(&tx, entry.fee);
s.addrs.add_tx(&tx, &entry.txid);
let txid = entry.txid.clone();
s.entries.insert(entry);
let idx = s.entries.insert(entry);
s.outpoint_spends.insert_spends(&tx, idx);
(txid, tx)
}
}

View File

@@ -21,10 +21,11 @@ pub struct EntryPool {
}
impl EntryPool {
pub fn insert(&mut self, entry: TxEntry) {
pub fn insert(&mut self, entry: TxEntry) -> TxIndex {
let prefix = entry.txid_prefix();
let idx = self.claim_slot(entry);
self.prefix_to_idx.insert(prefix, idx);
idx
}
fn claim_slot(&mut self, entry: TxEntry) -> TxIndex {
@@ -53,11 +54,11 @@ impl EntryPool {
self.entries.get(idx.as_usize())?.as_ref()
}
pub fn remove(&mut self, prefix: &TxidPrefix) -> Option<TxEntry> {
pub fn remove(&mut self, prefix: &TxidPrefix) -> Option<(TxIndex, TxEntry)> {
let idx = self.prefix_to_idx.remove(prefix)?;
let entry = self.entries.get_mut(idx.as_usize())?.take()?;
self.free_slots.push(idx);
Some(entry)
Some((idx, entry))
}
pub fn entries(&self) -> &[Option<TxEntry>] {

View File

@@ -1,27 +1,31 @@
//! Stateful in-memory holders. Each owns its `RwLock` and exposes a
//! behaviour-shaped API (insert, remove, evict, query).
//!
//! [`state::MempoolState`] aggregates four locked buckets:
//! [`state::MempoolState`] aggregates five locked buckets:
//!
//! - [`tx_store::TxStore`] - full `Transaction` data for live txs.
//! - [`addr_tracker::AddrTracker`] - per-address mempool stats.
//! - [`entry_pool::EntryPool`] - slot-recycled [`TxEntry`](crate::TxEntry)
//! storage indexed by [`entry_pool::TxIndex`].
//! - [`outpoint_spends::OutpointSpends`] - outpoint → spending mempool
//! tx index, used to answer mempool-to-mempool outspend queries.
//! - [`tx_graveyard::TxGraveyard`] - recently-dropped txs as
//! [`tx_graveyard::TxTombstone`]s, retained for reappearance
//! detection and post-mine analytics.
//!
//! A fifth bucket, `info`, holds a `MempoolInfo` from `brk_types`,
//! A sixth bucket, `info`, holds a `MempoolInfo` from `brk_types`,
//! so it has no file here.
pub mod addr_tracker;
pub mod entry_pool;
pub(crate) mod outpoint_spends;
pub mod state;
pub mod tx_graveyard;
pub mod tx_store;
pub use addr_tracker::AddrTracker;
pub use entry_pool::{EntryPool, TxIndex};
pub(crate) use outpoint_spends::OutpointSpends;
pub(crate) use state::LockedState;
pub use state::MempoolState;
pub use tx_graveyard::{TxGraveyard, TxTombstone};

View File

@@ -0,0 +1,45 @@
use brk_types::{OutpointPrefix, Transaction, TxidPrefix};
use derive_more::Deref;
use rustc_hash::FxHashMap;
use super::TxIndex;
/// Mempool index from spent outpoint to spending mempool tx.
///
/// Keys are `OutpointPrefix` (8 bytes txid + 2 bytes vout); prefix
/// collisions are possible, so callers must verify the candidate
/// spender's input list. Values are slot indices into `EntryPool`,
/// stable for the lifetime of an entry.
#[derive(Default, Deref)]
pub struct OutpointSpends(FxHashMap<OutpointPrefix, TxIndex>);
impl OutpointSpends {
pub fn insert_spends(&mut self, tx: &Transaction, idx: TxIndex) {
for input in &tx.input {
if input.is_coinbase {
continue;
}
let key = OutpointPrefix::new(TxidPrefix::from(&input.txid), input.vout);
self.0.insert(key, idx);
}
}
/// Only removes entries whose stored `TxIndex` still matches `idx`,
/// so a slot already recycled by a later insert is left alone.
pub fn remove_spends(&mut self, tx: &Transaction, idx: TxIndex) {
for input in &tx.input {
if input.is_coinbase {
continue;
}
let key = OutpointPrefix::new(TxidPrefix::from(&input.txid), input.vout);
if self.0.get(&key) == Some(&idx) {
self.0.remove(&key);
}
}
}
#[inline]
pub fn get(&self, key: &OutpointPrefix) -> Option<TxIndex> {
self.0.get(key).copied()
}
}

View File

@@ -1,12 +1,12 @@
use brk_types::MempoolInfo;
use parking_lot::{RwLock, RwLockWriteGuard};
use super::{AddrTracker, EntryPool, TxGraveyard, TxStore};
use super::{AddrTracker, EntryPool, OutpointSpends, TxGraveyard, TxStore};
/// The five buckets making up live mempool state.
/// The six buckets making up live mempool state.
///
/// Each bucket has its own `RwLock` so readers of different buckets
/// don't contend with each other. The Applier takes all five write
/// don't contend with each other. The Applier takes all six write
/// locks in a fixed order for a brief window once per cycle.
#[derive(Default)]
pub struct MempoolState {
@@ -14,11 +14,12 @@ pub struct MempoolState {
pub(crate) txs: RwLock<TxStore>,
pub(crate) addrs: RwLock<AddrTracker>,
pub(crate) entries: RwLock<EntryPool>,
pub(crate) outpoint_spends: RwLock<OutpointSpends>,
pub(crate) graveyard: RwLock<TxGraveyard>,
}
impl MempoolState {
/// All five write guards in the canonical lock order. Used by the
/// All six write guards in the canonical lock order. Used by the
/// Applier to apply a sync diff atomically.
pub(crate) fn write_all(&self) -> LockedState<'_> {
LockedState {
@@ -26,6 +27,7 @@ impl MempoolState {
txs: self.txs.write(),
addrs: self.addrs.write(),
entries: self.entries.write(),
outpoint_spends: self.outpoint_spends.write(),
graveyard: self.graveyard.write(),
}
}
@@ -36,5 +38,6 @@ pub(crate) struct LockedState<'a> {
pub txs: RwLockWriteGuard<'a, TxStore>,
pub addrs: RwLockWriteGuard<'a, AddrTracker>,
pub entries: RwLockWriteGuard<'a, EntryPool>,
pub outpoint_spends: RwLockWriteGuard<'a, OutpointSpends>,
pub graveyard: RwLockWriteGuard<'a, TxGraveyard>,
}