mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-20 06:44:47 -07:00
global: fixes
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>] {
|
||||
|
||||
@@ -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};
|
||||
|
||||
45
crates/brk_mempool/src/stores/outpoint_spends.rs
Normal file
45
crates/brk_mempool/src/stores/outpoint_spends.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user