Files
2026-06-04 18:35:48 +02:00

199 lines
6.5 KiB
Rust

//! Verify the production restart property: an oracle restored via
//! `from_checkpoint` (seeded from the previous block's stored cents price,
//! replayed over the last `window_size` blocks) produces bit-exact `ref_bin`
//! values matching a continuously-running oracle from the restart height
//! onward.
//!
//! Mirrors the production transaction filter exactly, so it exercises the same code path
//! `brk_computer::price::compute::feed_blocks` uses at runtime.
//!
//! Run with: cargo run -p brk_oracle --example determinism --release
use std::path::PathBuf;
use brk_indexer::Indexer;
use brk_oracle::{
bin_to_cents, cents_to_bin, Config, HistogramRaw, Oracle, PaymentFilter, START_HEIGHT_FAST,
START_HEIGHT_SLOW,
};
use brk_types::{OutputType, Sats, TxIndex, TxOutIndex};
use vecdb::{AnyVec, ReadableVec, VecIndex};
struct Block {
height: usize,
values: Vec<Sats>,
output_types: Vec<OutputType>,
tx_starts: Vec<usize>,
out_start: usize,
out_end: usize,
}
fn build_histogram(block: &Block) -> HistogramRaw {
let tx_outputs = (0..block.tx_starts.len()).map(|tx| {
let lo = block.tx_starts[tx] - block.out_start;
let hi = block
.tx_starts
.get(tx + 1)
.map(|s| s - block.out_start)
.unwrap_or(block.out_end - block.out_start);
block.values[lo..hi]
.iter()
.copied()
.zip(block.output_types[lo..hi].iter().copied())
});
PaymentFilter::for_height(block.height).histogram(tx_outputs)
}
fn main() {
let data_dir = std::env::var("BRK_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = std::env::var("HOME").unwrap();
PathBuf::from(home).join(".brk")
});
let indexer = Indexer::forced_import(&data_dir).expect("Failed to load indexer");
let total_heights = indexer.vecs.blocks.timestamp.len();
let fast_config = Config::default();
let window_size = fast_config.window_size;
let restart_offset = 1000;
let end_offset = restart_offset + window_size * 4;
let end_height = (START_HEIGHT_FAST + end_offset).min(total_heights);
let restart_at = START_HEIGHT_FAST + restart_offset;
let warmup_start = restart_at - window_size;
let load_start = START_HEIGHT_SLOW;
assert!(
end_height > restart_at,
"indexer has {total_heights} blocks; need at least {} to test restart at {restart_at}",
restart_at + 1
);
println!(
"Loading {} blocks ({load_start}..{end_height})...",
end_height - load_start
);
let total_txs = indexer.vecs.transactions.txid.len();
let total_outputs = indexer.vecs.outputs.value.len();
let first_tx_index: Vec<TxIndex> = indexer.vecs.transactions.first_tx_index.collect();
let out_first: Vec<TxOutIndex> = indexer.vecs.outputs.first_txout_index.collect();
let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor();
let mut blocks: Vec<Block> = Vec::with_capacity(end_height - load_start);
for h in load_start..end_height {
let ft = first_tx_index[h];
let next_ft = first_tx_index
.get(h + 1)
.copied()
.unwrap_or(TxIndex::from(total_txs));
let block_first_tx = ft.to_usize() + 1;
let tx_count = next_ft.to_usize() - block_first_tx;
let out_end = out_first
.get(h + 1)
.copied()
.unwrap_or(TxOutIndex::from(total_outputs))
.to_usize();
txout_cursor.advance(block_first_tx - txout_cursor.position());
let mut tx_starts: Vec<usize> = Vec::with_capacity(tx_count);
for _ in 0..tx_count {
tx_starts.push(txout_cursor.next().unwrap().to_usize());
}
let out_start = tx_starts.first().copied().unwrap_or(out_end);
let values: Vec<Sats> = indexer
.vecs
.outputs
.value
.collect_range_at(out_start, out_end);
let output_types: Vec<OutputType> = indexer
.vecs
.outputs
.output_type
.collect_range_at(out_start, out_end);
blocks.push(Block {
height: h,
values,
output_types,
tx_starts,
out_start,
out_end,
});
}
let mut continuous = Oracle::from_seed();
let continuous_bins: Vec<f64> = blocks
.iter()
.map(|b| {
if b.height == START_HEIGHT_FAST {
continuous.reconfigure(fast_config);
}
continuous.process_histogram(&build_histogram(b))
})
.collect();
println!(
"Continuous oracle: {} blocks processed",
continuous_bins.len()
);
let prev_bin = continuous_bins[restart_at - load_start - 1];
let seed_bin = cents_to_bin(bin_to_cents(prev_bin) as f64);
println!(
"Restart at {restart_at}: prev_bin={prev_bin:.4} -> cents -> seed_bin={seed_bin:.4} (delta {:.6})",
seed_bin - prev_bin
);
let warmup_slice = &blocks[warmup_start - load_start..restart_at - load_start];
let mut restored = Oracle::from_checkpoint(seed_bin, fast_config, |o| {
for b in warmup_slice {
o.process_histogram(&build_histogram(b));
}
});
let restored_bins: Vec<f64> = blocks[restart_at - load_start..]
.iter()
.map(|b| restored.process_histogram(&build_histogram(b)))
.collect();
println!("Restored oracle: {} blocks processed", restored_bins.len());
let mut mismatches: Vec<(usize, f64, f64)> = Vec::new();
for (i, &r) in restored_bins.iter().enumerate() {
let c = continuous_bins[restart_at - load_start + i];
if r != c {
mismatches.push((restart_at + i, c, r));
}
}
println!();
if mismatches.is_empty() {
println!(
"All {} blocks from {restart_at} onward match exactly.",
restored_bins.len()
);
} else {
println!(
"{} of {} blocks differ (showing up to 5):",
mismatches.len(),
restored_bins.len()
);
for (h, c, r) in mismatches.iter().take(5) {
println!(
" h={h}: continuous={c:.6}, restored={r:.6}, delta={:.6}",
r - c
);
}
}
assert_eq!(
mismatches.len(),
0,
"restored oracle diverged from continuous oracle"
);
println!();
println!("Assertion passed: from_checkpoint restart is bit-exact.");
}