Files
brk/crates/mmpl/src/event.rs
T
2026-05-14 13:59:15 +02:00

161 lines
4.6 KiB
Rust

//! NDJSON event schema. One [`Event`] per line; consumers pipe to
//! `jq` / `grep` to filter. Per-event fields are flat (no nested
//! objects) so `jq -c 'select(...)'` works without `..` walks.
use brk_mempool::{Cycle, TxAdded, TxRemoval, TxRemoved};
use brk_types::{
Addr, BlockHash, FeeRate, Height, NextBlockHash, RecommendedFees, Sats, Timestamp, Txid, VSize,
};
use serde::Serialize;
#[derive(Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Event {
/// A tx entered the pool this cycle (either brand new or revived
/// from the graveyard - the stream collapses both to one event).
Enter {
t: f64,
txid: Txid,
vsize: VSize,
fee: Sats,
rate: FeeRate,
first_seen: Timestamp,
},
/// A tx left the pool this cycle. `rate` is the package-effective
/// rate at burial, not raw fee/vsize.
Leave {
t: f64,
txid: Txid,
#[serde(flatten)]
reason: LeaveReason,
rate: FeeRate,
},
/// An address went 0 → 1+ live mempool txs this cycle. Same-cycle
/// flip-flops are collapsed by the upstream tracker (no event).
AddrEnter { t: f64, addr: Addr },
/// An address went 1+ → 0 live mempool txs this cycle.
AddrLeave { t: f64, addr: Addr },
/// New confirmed block: bitcoind's chain tip moved since the last
/// cycle. `height` is the tip's own height (one less than the next
/// block being templated).
Tip {
t: f64,
hash: BlockHash,
height: Height,
},
/// The projected next block changed (different tx set or order).
/// `hash` is the same opaque content hash used as the mempool ETag.
/// `added`/`removed` is the txid-level diff against the previous
/// template; on the very first cycle `added` is the full template
/// and `removed` is empty.
Block {
t: f64,
hash: NextBlockHash,
added: Vec<Txid>,
removed: Vec<Txid>,
},
/// Recommended fee rates changed since the last cycle.
Fees {
t: f64,
fastest: FeeRate,
half_hour: FeeRate,
hour: FeeRate,
economy: FeeRate,
minimum: FeeRate,
},
/// Per-cycle heartbeat. Always emitted, even on idle cycles, so
/// downstream consumers see a steady pulse and can spot stalls.
/// `addr_enters`/`addr_leaves` count the post-cancellation 0↔1+
/// address transitions this cycle.
Cycle {
t: f64,
added: usize,
removed: usize,
addr_enters: usize,
addr_leaves: usize,
count: usize,
vsize: VSize,
fee: Sats,
took_ms: u64,
},
}
#[derive(Serialize)]
#[serde(tag = "reason", rename_all = "snake_case")]
pub enum LeaveReason {
Replaced { by: Txid },
Vanished,
}
impl Event {
pub fn enter(t: f64, tx: &TxAdded) -> Self {
Self::Enter {
t,
txid: tx.txid,
vsize: tx.vsize,
fee: tx.fee,
rate: tx.fee_rate,
first_seen: tx.first_seen,
}
}
pub fn leave(t: f64, tx: &TxRemoved) -> Self {
Self::Leave {
t,
txid: tx.txid,
reason: LeaveReason::from(tx.reason),
rate: tx.chunk_rate,
}
}
pub fn addr_enter(t: f64, addr: Addr) -> Self {
Self::AddrEnter { t, addr }
}
pub fn addr_leave(t: f64, addr: Addr) -> Self {
Self::AddrLeave { t, addr }
}
pub fn tip(t: f64, hash: BlockHash, height: Height) -> Self {
Self::Tip { t, hash, height }
}
pub fn block(t: f64, hash: NextBlockHash, added: Vec<Txid>, removed: Vec<Txid>) -> Self {
Self::Block { t, hash, added, removed }
}
pub fn fees(t: f64, fees: &RecommendedFees) -> Self {
Self::Fees {
t,
fastest: fees.fastest_fee,
half_hour: fees.half_hour_fee,
hour: fees.hour_fee,
economy: fees.economy_fee,
minimum: fees.minimum_fee,
}
}
pub fn summary(t: f64, cycle: &Cycle) -> Self {
Self::Cycle {
t,
added: cycle.added.len(),
removed: cycle.removed.len(),
addr_enters: cycle.addr_enters.len(),
addr_leaves: cycle.addr_leaves.len(),
count: cycle.info.count,
vsize: cycle.info.vsize,
fee: cycle.info.total_fee,
took_ms: cycle.took.as_millis() as u64,
}
}
}
impl From<TxRemoval> for LeaveReason {
fn from(reason: TxRemoval) -> Self {
match reason {
TxRemoval::Replaced { by } => Self::Replaced { by },
TxRemoval::Vanished => Self::Vanished,
}
}
}