use brk_rpc::Client;
use brk_types::{Sats, SatsSigned, TxidPrefix};
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::{debug, warn};
use super::super::block_builder::{BLOCK_VSIZE, Package};
use crate::stores::{Entry, TxIndex};
type PrefixSet = FxHashSet<TxidPrefix>;
type FeeByPrefix = FxHashMap<TxidPrefix, Sats>;
pub struct Verifier;
impl Verifier {
pub fn check(client: &Client, blocks: &[Vec<Package>], entries: &[Option<Entry>]) {
Self::check_structure(blocks, entries);
Self::compare_to_core(client, blocks, entries);
}
fn check_structure(blocks: &[Vec<Package>], entries: &[Option<Entry>]) {
let in_pool: PrefixSet = entries
.iter()
.filter_map(|e| e.as_ref().map(Entry::txid_prefix))
.collect();
let mut placed = PrefixSet::default();
for (b, block) in blocks.iter().enumerate() {
for (p, pkg) in block.iter().enumerate() {
let mut summed_vsize = 0u64;
for &tx_index in &pkg.txs {
let entry = Self::live_entry(entries, tx_index, b, p);
Self::assert_parents_placed_first(entry, &in_pool, &placed, b, p);
Self::place(entry, &mut placed, b, p);
summed_vsize += u64::from(entry.vsize);
}
assert_eq!(
pkg.vsize, summed_vsize,
"block {b} pkg {p}: pkg.vsize {} != sum {summed_vsize}",
pkg.vsize
);
}
if b + 1 < blocks.len() {
Self::assert_block_fits_budget(block, b);
}
}
}
fn live_entry(
entries: &[Option<Entry>],
tx_index: TxIndex,
b: usize,
p: usize,
) -> &Entry {
entries[tx_index.as_usize()]
.as_ref()
.unwrap_or_else(|| panic!("block {b} pkg {p}: dead tx_index {tx_index:?}"))
}
fn assert_parents_placed_first(
entry: &Entry,
in_pool: &PrefixSet,
placed: &PrefixSet,
b: usize,
p: usize,
) {
for parent in &entry.depends {
if in_pool.contains(parent) && !placed.contains(parent) {
panic!(
"block {b} pkg {p}: {} placed before its parent",
entry.txid
);
}
}
}
fn place(entry: &Entry, placed: &mut PrefixSet, b: usize, p: usize) {
assert!(
placed.insert(entry.txid_prefix()),
"block {b} pkg {p}: duplicate txid {}",
entry.txid
);
}
fn assert_block_fits_budget(block: &[Package], b: usize) {
let total: u64 = block.iter().map(|pkg| pkg.vsize).sum();
let is_oversized_singleton = block.len() == 1 && total > BLOCK_VSIZE;
if is_oversized_singleton {
return;
}
assert!(
total <= BLOCK_VSIZE,
"block {b}: vsize {total} exceeds {BLOCK_VSIZE}"
);
}
fn compare_to_core(client: &Client, blocks: &[Vec<Package>], entries: &[Option<Entry>]) {
let Some(next_block) = blocks.first() else {
return;
};
let core: FeeByPrefix = match client.get_block_template_txs() {
Ok(txs) => txs
.into_iter()
.map(|t| (TxidPrefix::from(&t.txid), t.fee))
.collect(),
Err(e) => {
warn!("verify: getblocktemplate failed: {e}");
return;
}
};
let ours: FeeByPrefix = next_block
.iter()
.flat_map(|pkg| &pkg.txs)
.filter_map(|&i| entries[i.as_usize()].as_ref())
.map(|e| (e.txid_prefix(), e.fee))
.collect();
let overlap = ours.keys().filter(|k| core.contains_key(k)).count();
let union = ours.len() + core.len() - overlap;
let jaccard = if union == 0 {
1.0
} else {
overlap as f64 / union as f64
};
let ours_fee: Sats = ours.values().copied().sum();
let core_fee: Sats = core.values().copied().sum();
let delta = SatsSigned::from(ours_fee) - SatsSigned::from(core_fee);
let delta_bps = if core_fee == Sats::ZERO {
0.0
} else {
f64::from(delta) / f64::from(core_fee) * 10_000.0
};
debug!(
"verify block 0: txs {}/{} (overlap {}, jaccard {:.3}) | fee {}/{} (delta {:+}, {:+.1} bps)",
ours.len(),
core.len(),
overlap,
jaccard,
ours_fee,
core_fee,
delta.inner(),
delta_bps,
);
}
}