mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 23:43:32 -07:00
206 lines
6.8 KiB
Rust
206 lines
6.8 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 filter exactly (per-tx OP_RETURN drop + per-output
|
|
//! `eligible_bin`), so it exercises the same code path
|
|
//! `brk_computer::prices::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::{
|
|
Config, HistogramRaw, Oracle, PRICES, START_HEIGHT_FAST, bin_to_cents, cents_to_bin,
|
|
for_each_round_dollar_bin,
|
|
};
|
|
use brk_types::{OutputType, Sats, TxIndex, TxOutIndex};
|
|
use vecdb::{AnyVec, ReadableVec, VecIndex};
|
|
|
|
fn seed_bin_for_start_height() -> f64 {
|
|
let price: f64 = PRICES
|
|
.lines()
|
|
.nth(START_HEIGHT_FAST - 1)
|
|
.expect("prices.txt too short for START_HEIGHT_FAST")
|
|
.parse()
|
|
.expect("Failed to parse seed price");
|
|
cents_to_bin(price * 100.0)
|
|
}
|
|
|
|
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 mut hist = HistogramRaw::zeros();
|
|
for tx in 0..block.tx_starts.len() {
|
|
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);
|
|
let outputs = block.values[lo..hi]
|
|
.iter()
|
|
.copied()
|
|
.zip(block.output_types[lo..hi].iter().copied());
|
|
for_each_round_dollar_bin(block.height, outputs, |bin| hist.increment(bin as usize));
|
|
}
|
|
hist
|
|
}
|
|
|
|
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 config = Config::default();
|
|
let window_size = 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;
|
|
|
|
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 ({START_HEIGHT_FAST}..{end_height})...",
|
|
end_height - START_HEIGHT_FAST
|
|
);
|
|
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 - START_HEIGHT_FAST);
|
|
for h in START_HEIGHT_FAST..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::new(seed_bin_for_start_height(), config.clone());
|
|
let continuous_bins: Vec<f64> = blocks
|
|
.iter()
|
|
.map(|b| continuous.process_histogram(&build_histogram(b)))
|
|
.collect();
|
|
println!(
|
|
"Continuous oracle: {} blocks processed",
|
|
continuous_bins.len()
|
|
);
|
|
|
|
let prev_bin = continuous_bins[restart_at - START_HEIGHT_FAST - 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 - START_HEIGHT_FAST..restart_at - START_HEIGHT_FAST];
|
|
let mut restored = Oracle::from_checkpoint(seed_bin, config.clone(), |o| {
|
|
for b in warmup_slice {
|
|
o.process_histogram(&build_histogram(b));
|
|
}
|
|
});
|
|
|
|
let restored_bins: Vec<f64> = blocks[restart_at - START_HEIGHT_FAST..]
|
|
.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 - START_HEIGHT_FAST + 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.");
|
|
}
|