use brk_types::{Sats, Timestamp, Transaction, Txid, TxidPrefix, VSize};
use rustc_hash::FxHashSet;
use crate::{Mempool, TxEntry, TxRemoval, TxStore, stores::TxGraveyard};
#[derive(Debug, Clone)]
pub struct RbfNode {
pub txid: Txid,
pub fee: Sats,
pub vsize: VSize,
pub value: Sats,
pub first_seen: Timestamp,
pub rbf: bool,
pub full_rbf: bool,
pub replaces: Vec<RbfNode>,
}
#[derive(Debug, Clone, Default)]
pub struct RbfForTx {
pub root: Option<RbfNode>,
pub replaces: Vec<Txid>,
}
impl Mempool {
pub fn rbf_for_tx(&self, txid: &Txid) -> RbfForTx {
let inner = self.read();
let root_txid = walk_to_replacement_root(&inner.graveyard, *txid);
let replaces: Vec<Txid> = inner
.graveyard
.predecessors_of(txid)
.map(|(p, _)| *p)
.collect();
let root = build_node(&root_txid, &inner.txs, &inner.graveyard);
RbfForTx { root, replaces }
}
pub fn recent_rbf_trees(&self, full_rbf_only: bool, limit: usize) -> Vec<RbfNode> {
let inner = self.read();
let mut seen: FxHashSet<Txid> = FxHashSet::default();
inner
.graveyard
.replaced_iter_recent_first()
.filter_map(|(_, by)| {
let root = walk_to_replacement_root(&inner.graveyard, *by);
seen.insert(root).then_some(root)
})
.filter_map(|root| build_node(&root, &inner.txs, &inner.graveyard))
.filter(|n| !full_rbf_only || n.full_rbf)
.take(limit)
.collect()
}
}
fn walk_to_replacement_root(graveyard: &TxGraveyard, mut root: Txid) -> Txid {
while let Some(TxRemoval::Replaced { by }) = graveyard.get(&root).map(|t| t.reason()) {
root = *by;
}
root
}
fn build_node(txid: &Txid, txs: &TxStore, graveyard: &TxGraveyard) -> Option<RbfNode> {
let (tx, entry) = resolve_node(txid, txs, graveyard)?;
let replaces: Vec<RbfNode> = graveyard
.predecessors_of(txid)
.filter_map(|(pred, _)| build_node(pred, txs, graveyard))
.collect();
let full_rbf = replaces.iter().any(|c| !c.rbf || c.full_rbf);
let value: Sats = tx.output.iter().map(|o| o.value).sum();
Some(RbfNode {
txid: *txid,
fee: entry.fee,
vsize: entry.vsize,
value,
first_seen: entry.first_seen,
rbf: entry.rbf,
full_rbf,
replaces,
})
}
fn resolve_node<'a>(
txid: &Txid,
txs: &'a TxStore,
graveyard: &'a TxGraveyard,
) -> Option<(&'a Transaction, &'a TxEntry)> {
txs.record_by_prefix(&TxidPrefix::from(txid))
.map(|r| (&r.tx, &r.entry))
.or_else(|| graveyard.get(txid).map(|t| (&t.tx, &t.entry)))
}