use std::collections::BTreeMap;
use bbg::transition::{self, Error, NativeChange};
use bbg::{
Bbg, Cyberlink, Dim, Keyed, NeuronRecord, RootVersion, Signal, SignalRecord, bbg_query,
signal_key, verify_entity,
};
fn p(seed: u8) -> [u8; 32] {
[seed; 32]
}
fn record(neuron: [u8; 32], step: u64, network: u8) -> SignalRecord {
SignalRecord {
neuron,
network: p(network),
link_count: 1,
block_height: step,
proof_hash: p(network ^ 0x5A),
step,
}
}
fn link_signal(neuron: [u8; 32], to: u8, amount: u64, height: u64) -> Signal {
Signal {
neuron,
links: vec![Cyberlink { from: p(200), to: p(to), token: p(0), amount, valence: 1 }],
box_moves: vec![],
height,
}
}
fn header(signal: &Signal, step: u64) -> SignalRecord {
SignalRecord {
neuron: signal.neuron,
network: p(77),
link_count: signal.links.len() as u32,
block_height: signal.height,
proof_hash: p(78),
step,
}
}
fn funded() -> Bbg {
let mut b = Bbg::new();
for i in 1..=8u8 {
b.state.neurons.insert(p(i), NeuronRecord { focus: 1 << 40, karma: 0, stake: 0, nonce: 0 });
}
b.state.refresh_root();
b.checkpoint = b.checkpoint.advance(&b.state);
b
}
fn records(b: &Bbg) -> BTreeMap<Vec<u8>, Vec<u8>> {
transition::records(b).unwrap().map(|r| (r.key, r.value)).collect()
}
fn shuffled(n: usize, seed: u64) -> Vec<usize> {
let mut v: Vec<usize> = (0..n).collect();
let mut x = seed | 1;
for i in (1..v.len()).rev() {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
v.swap(i, (x % (i as u64 + 1)) as usize);
}
v
}
#[test]
fn signal_key_binds_neuron_and_step() {
let mut preimage = p(1).to_vec();
preimage.extend_from_slice(&3u64.to_le_bytes());
let expected: [u8; 32] = hemera::hash(&preimage).as_bytes()[..32].try_into().unwrap();
assert_eq!(signal_key(&p(1), 3), expected);
assert_ne!(signal_key(&p(1), 0), signal_key(&p(2), 0), "neurons separate");
assert_ne!(signal_key(&p(1), 0), signal_key(&p(1), 1), "steps separate");
let mut bare = [0u8; 32];
bare[..8].copy_from_slice(&0u64.to_le_bytes());
assert_ne!(signal_key(&p(0), 0), bare);
}
#[test]
fn two_neurons_first_signals_both_persist() {
for version in [RootVersion::V1, RootVersion::V2] {
let mut b = Bbg::new().with_root_version(version);
let k1 = b.apply_signal_record(record(p(1), 0, 10));
let k2 = b.apply_signal_record(record(p(2), 0, 20));
assert_ne!(k1, k2);
assert_eq!(b.state.signals.len(), 2, "{version:?}: the step-0 collision is gone");
let r1 = b.state.signals.get(&k1).unwrap();
let r2 = b.state.signals.get(&k2).unwrap();
assert_eq!((r1.neuron, r1.step, r1.network), (p(1), 0, p(10)));
assert_eq!((r2.neuron, r2.step, r2.network), (p(2), 0, p(20)));
assert_eq!(b.state.statistics().relation_sizes[Dim::Signals as usize], 2);
}
}
#[test]
fn arrival_order_does_not_move_the_root() {
let all: Vec<SignalRecord> = (1..=8u8)
.flat_map(|n| (0..6u64).map(move |s| record(p(n), s, n * 16 + s as u8)))
.collect();
for version in [RootVersion::V1, RootVersion::V2] {
let build = |order: &[usize]| {
let mut b = Bbg::new().with_root_version(version);
for &i in order {
b.apply_signal_record(SignalRecord { ..all[i] });
}
b
};
let in_order: Vec<usize> = (0..all.len()).collect();
let reference = build(&in_order);
assert_eq!(reference.state.signals.len(), all.len());
for seed in [1u64, 7, 0xDEAD_BEEF, 1 << 40] {
let other = build(&shuffled(all.len(), seed));
assert_eq!(other.state.root(), reference.state.root(), "{version:?} seed {seed}");
assert_eq!(other.state.root_leaves().dims[9], reference.state.root_leaves().dims[9]);
}
let reversed: Vec<usize> = (0..all.len()).rev().collect();
assert_eq!(build(&reversed).state.root(), reference.state.root(), "{version:?}");
}
}
#[test]
fn every_record_is_under_the_root_and_provable() {
let mut v2 = Bbg::new();
let mut v1 = Bbg::new().with_root_version(RootVersion::V1);
for b in [&mut v1, &mut v2] {
b.apply_signal_record(record(p(1), 0, 10));
b.apply_signal_record(record(p(2), 0, 20));
b.state.refresh_root();
}
let leaves = v2.state.root_leaves();
for (neuron, net) in [(p(1), 10), (p(2), 20)] {
let key = signal_key(&neuron, 0);
let leaf = Keyed::<[u8; 32], SignalRecord>::leaf(&key, &record(neuron, 0, net));
assert!(v2.state.signals.prove(&key).verify(&leaves.dims[9], &key, Some(&leaf)));
}
let root = v1.state.root();
let proof = v1.prove_signal(&p(2), 0).unwrap();
assert!(verify_entity(&proof, &root, Dim::Signals, &signal_key(&p(2), 0)));
assert!(!verify_entity(&proof, &root, Dim::Signals, &signal_key(&p(1), 0)));
assert!(bbg_query(&v1.state, Dim::Signals, &signal_key(&p(3), 0)).is_none());
let mut moved = Bbg::new();
moved.apply_signal_record(record(p(1), 1, 10));
moved.apply_signal_record(record(p(2), 0, 20));
assert_ne!(moved.state.root(), v2.state.root());
}
#[test]
fn native_batch_keeps_both_step_zero_signals_and_rejects_a_repeat() {
let mut b = funded();
let before = records(&b);
let (a, c) = (link_signal(p(1), 30, 5, 0), link_signal(p(2), 31, 6, 0));
let (ha, hc) = (header(&a, 0), header(&c, 0));
let batch = [
NativeChange::Signal { signal: &a, header: &ha },
NativeChange::Signal { signal: &c, header: &hc },
];
{
let prepared = b.prepare_native_batch(&batch).unwrap();
let written: Vec<_> = prepared.changes().iter().filter(|ch| ch.key[0] == 10).collect();
assert_eq!(written.len(), 2, "two signal records, not one overwritten");
for ch in &written {
let value = ch.value.as_ref().unwrap();
assert_eq!(value.len(), 116);
assert_eq!(&value[108..], &0u64.to_le_bytes(), "the step is in the record");
}
} assert_eq!(records(&b), before);
b.prepare_native_batch(&batch).unwrap().publish();
assert_eq!(b.state.signals.len(), 2);
for (n, key) in [(p(1), signal_key(&p(1), 0)), (p(2), signal_key(&p(2), 0))] {
assert_eq!(b.state.signals.get(&key).unwrap().neuron, n);
assert!(records(&b).contains_key(&[&[10u8][..], &key].concat()));
}
let after = records(&b);
let again = link_signal(p(1), 32, 1, 1);
assert!(matches!(
b.prepare_native(Some(&again), Some(header(&again, 0)), None),
Err(Error::SignalExists)
));
assert_eq!(records(&b), after);
b.prepare_native(Some(&again), Some(header(&again, 1)), None).unwrap().publish();
assert_eq!(b.state.signals.len(), 3);
}
fn script() -> Vec<Vec<(Signal, SignalRecord)>> {
let mut steps = [0u64; 9];
let mut x = 0x5157_u64;
let mut batches = Vec::new();
for h in 0..40u64 {
let mut batch = Vec::new();
for _ in 0..(1 + h % 2) {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
let n = 1 + (x % 8) as u8;
let s = link_signal(p(n), 40 + (x >> 8) as u8 % 16, (x >> 16) % 4, h);
let r = header(&s, steps[n as usize]);
steps[n as usize] += 1;
batch.push((s, r));
}
batches.push(batch);
}
batches
}
fn run(b: &mut Bbg, disk: &mut BTreeMap<Vec<u8>, Vec<u8>>) -> Vec<transition::Snapshot> {
let mut snapshots = Vec::new();
for batch in script() {
let changes: Vec<_> = batch
.iter()
.map(|(signal, header)| NativeChange::Signal { signal, header })
.collect();
let prepared = b.prepare_native_batch(&changes).unwrap();
for ch in prepared.changes() {
match &ch.value {
Some(v) => disk.insert(ch.key.clone(), v.clone()),
None => disk.remove(&ch.key),
};
}
snapshots.extend_from_slice(prepared.snapshots());
prepared.publish();
}
snapshots
}
#[test]
fn replay_is_deterministic_and_recovery_matches_every_stored_root() {
let mut live = funded();
let mut disk = records(&live);
let snapshots = run(&mut live, &mut disk);
let total: usize = script().iter().map(Vec::len).sum();
assert_eq!(live.state.signals.len(), total, "no signal lost to a shared step");
assert_eq!(records(&live), disk, "the persisted change stream is the state");
let mut recovered = funded();
let mut recovered_disk = records(&recovered);
assert_eq!(run(&mut recovered, &mut recovered_disk), snapshots);
assert_eq!(live.state.time.len(), recovered.state.time.len());
for (h, root) in live.state.time.iter() {
assert_eq!(recovered.state.time.get(h), Some(root), "stored root at {h}");
}
assert_eq!(recovered.state.root(), live.state.root());
assert_eq!(recovered_disk, disk);
}