use std::sync::Mutex;
use nebu::Goldilocks;
use nox::{CallProvider, LookProvider, Reduction, Order};
use zheng::LookOpening;
use crate::dim::goldilocks_from_bytes32;
use crate::proof::{
prove_axons_in, prove_axons_out, prove_card, prove_coin, prove_file, prove_location,
prove_neuron, prove_particle, prove_signal, prove_time, QueryProof,
};
use crate::state::BbgState;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u64)]
pub enum Dim {
Particles = 0,
AxonsOut = 1,
AxonsIn = 2,
Neurons = 3,
Locations = 4,
Coins = 5,
Cards = 6,
Files = 7,
Time = 8,
Signals = 9,
Balances = 10,
}
impl Dim {
pub fn from_u64(v: u64) -> Option<Self> {
match v {
0 => Some(Self::Particles),
1 => Some(Self::AxonsOut),
2 => Some(Self::AxonsIn),
3 => Some(Self::Neurons),
4 => Some(Self::Locations),
5 => Some(Self::Coins),
6 => Some(Self::Cards),
7 => Some(Self::Files),
8 => Some(Self::Time),
9 => Some(Self::Signals),
10 => Some(Self::Balances),
_ => None,
}
}
}
pub fn bbg_query(state: &BbgState, dim: Dim, key: &[u8; 32]) -> Option<QueryProof> {
match dim {
Dim::Particles => prove_particle(state, key),
Dim::AxonsOut => prove_axons_out(state, key),
Dim::AxonsIn => prove_axons_in(state, key),
Dim::Neurons => prove_neuron(state, key),
Dim::Locations => prove_location(state, key),
Dim::Coins => prove_coin(state, key),
Dim::Cards => prove_card(state, key),
Dim::Files => prove_file(state, key),
Dim::Time => {
let mut buf = [0u8; 8];
buf.copy_from_slice(&key[..8]);
prove_time(state, u64::from_le_bytes(buf))
}
Dim::Signals => {
let mut buf = [0u8; 8];
buf.copy_from_slice(&key[..8]);
prove_signal(state, u64::from_le_bytes(buf))
}
Dim::Balances => None,
}
}
pub fn verify_query(proof: &QueryProof) -> bool {
use lens::{brakedown::Brakedown, Lens, Transcript as LensTx};
let value = query_value(proof);
let mut tx = LensTx::new(b"bbg-dim-open");
Brakedown::verify(&proof.commitment, &proof.point, value, &proof.opening, &mut tx)
}
pub struct BbgLookProvider<'a> {
pub state: &'a BbgState,
}
impl<'a> LookProvider for BbgLookProvider<'a> {
fn look(&self, _commitment: Goldilocks, namespace: Goldilocks, key: Goldilocks) -> Option<Goldilocks> {
if namespace.as_u64() > 9 {
return None;
}
let dim = Dim::from_u64(namespace.as_u64())?;
crate::proof::cell_value(self.state, dim, key.as_u64() as usize)
}
}
pub struct ProofLookProvider<'a> {
pub state: &'a BbgState,
openings: Mutex<Vec<LookOpening>>,
}
impl<'a> ProofLookProvider<'a> {
pub fn new(state: &'a BbgState) -> Self {
Self { state, openings: Mutex::new(Vec::new()) }
}
pub fn take_look_openings(&self) -> Vec<LookOpening> {
self.openings.lock().unwrap().drain(..).collect()
}
}
impl<'a> LookProvider for ProofLookProvider<'a> {
fn look(&self, _commitment: Goldilocks, namespace: Goldilocks, key: Goldilocks) -> Option<Goldilocks> {
if namespace.as_u64() > 9 {
return None;
}
let dim = Dim::from_u64(namespace.as_u64())?;
let proof = crate::proof::open_cell(self.state, dim, key.as_u64() as usize)?;
let value = query_value(&proof);
let opening = LookOpening {
commitment: proof.commitment,
point: proof.point,
value,
opening: proof.opening,
transcript_seed: b"bbg-dim-open".to_vec(),
bbg_root: goldilocks_from_bytes32(&self.state.root),
namespace,
a_commit: None,
};
self.openings.lock().unwrap().push(opening);
Some(value)
}
}
impl<'a, const N: usize> CallProvider<N> for ProofLookProvider<'a> {
fn provide(&self, _reduction: &mut Reduction<N>, _tag: Goldilocks, _object: Order) -> Option<Order> {
None
}
}
pub fn collect_look_openings(state: &BbgState, trace: &[nox::TraceRow]) -> Vec<LookOpening> {
let mut result = Vec::new();
for row in trace {
if row.r()[0] != 17 {
continue;
}
let ns = Goldilocks::new(row.r()[5]);
let key = Goldilocks::new(row.r()[6]);
if ns.as_u64() > 9 {
continue; }
let Some(dim) = Dim::from_u64(ns.as_u64()) else { continue };
let Some(proof) = crate::proof::open_cell(state, dim, key.as_u64() as usize) else { continue };
let value = query_value(&proof);
result.push(LookOpening {
commitment: proof.commitment,
point: proof.point,
value,
opening: proof.opening,
transcript_seed: b"bbg-dim-open".to_vec(),
bbg_root: goldilocks_from_bytes32(&state.root),
namespace: ns,
a_commit: None,
});
}
result
}
fn query_value(proof: &QueryProof) -> Goldilocks {
if proof.value_bytes.len() < 8 {
return Goldilocks::ZERO;
}
let mut buf = [0u8; 8];
buf.copy_from_slice(&proof.value_bytes[..8]);
Goldilocks::new(u64::from_le_bytes(buf))
}
pub fn verify_opening(lo: &LookOpening) -> bool {
use lens::{brakedown::Brakedown, Lens, Transcript as LensTx};
let mut tx = LensTx::new(b"bbg-dim-open");
Brakedown::verify(&lo.commitment, &lo.point, lo.value, &lo.opening, &mut tx)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::signal::{Cyberlink, Signal};
use crate::state::EPOCH_BLOCKS;
use crate::types::NeuronRecord;
fn particle(seed: u8) -> [u8; 32] { [seed; 32] }
fn seeded_state() -> BbgState {
let mut s = BbgState::new();
s.neurons.insert(particle(1), NeuronRecord { focus: 50_000, karma: 0, stake: 0 });
let sig = Signal {
neuron: particle(1),
links: vec![Cyberlink {
from: particle(2),
to: particle(3),
token: particle(0),
amount: 100,
valence: 1,
}],
box_moves: vec![],
height: 0,
};
s.insert(&sig).unwrap();
s.time.insert(0, particle(99));
s
}
#[test]
fn dim_roundtrip() {
for i in 0u64..=10 {
assert_eq!(Dim::from_u64(i).unwrap() as u64, i);
}
assert!(Dim::from_u64(11).is_none());
}
#[test]
fn bbg_query_particles_returns_proof() {
let state = seeded_state();
let axon = crate::state::axon_id(&particle(2), &particle(3));
let proof = bbg_query(&state, Dim::Particles, &axon);
assert!(proof.is_some());
}
#[test]
fn bbg_query_neurons_returns_proof() {
let state = seeded_state();
let proof = bbg_query(&state, Dim::Neurons, &particle(1));
assert!(proof.is_some());
}
#[test]
fn bbg_query_time_uses_first_8_bytes_as_u64() {
let state = seeded_state();
let mut key = [0u8; 32];
key[..8].copy_from_slice(&0u64.to_le_bytes());
let proof = bbg_query(&state, Dim::Time, &key);
assert!(proof.is_some());
}
#[test]
fn bbg_query_balances_returns_none() {
let state = seeded_state();
let proof = bbg_query(&state, Dim::Balances, &particle(42));
assert!(proof.is_none());
}
#[test]
fn verify_query_valid_proof() {
let state = seeded_state();
let axon = crate::state::axon_id(&particle(2), &particle(3));
let proof = bbg_query(&state, Dim::Particles, &axon).unwrap();
assert!(verify_query(&proof));
}
fn ct() -> Goldilocks { Goldilocks::ZERO }
#[test]
fn look_provider_reads_cell_by_index() {
let state = seeded_state(); let prov = BbgLookProvider { state: &state };
let ns = Goldilocks::new(Dim::Time as u64);
let expect = crate::dim::goldilocks_from_bytes32(&[99u8; 32])[0];
assert_eq!(prov.look(ct(), ns, Goldilocks::new(4)), Some(expect));
}
#[test]
fn look_provider_missing_returns_none() {
let state = seeded_state();
let prov = BbgLookProvider { state: &state };
let ns = Goldilocks::new(Dim::Time as u64);
let key = Goldilocks::new(9999); assert!(prov.look(ct(), ns, key).is_none());
}
#[test]
fn look_provider_invalid_namespace_returns_none() {
let state = seeded_state();
let prov = BbgLookProvider { state: &state };
assert!(prov.look(ct(), Goldilocks::new(99), Goldilocks::ZERO).is_none());
}
#[test]
fn look_provider_balances_returns_none() {
let state = seeded_state();
let prov = BbgLookProvider { state: &state };
assert!(prov.look(ct(), Goldilocks::new(Dim::Balances as u64), Goldilocks::ZERO).is_none());
}
#[test]
fn look_provider_neuron_focus_cell_by_index() {
let mut state = BbgState::new();
let mut key = [0u8; 32];
key[..8].copy_from_slice(&42u64.to_le_bytes());
state.neurons.insert(key, crate::types::NeuronRecord { focus: 777, karma: 0, stake: 0 });
let prov = BbgLookProvider { state: &state };
let ns = Goldilocks::new(Dim::Neurons as u64);
assert_eq!(prov.look(ct(), ns, Goldilocks::new(4)), Some(Goldilocks::new(777)));
}
#[test]
fn proof_look_provider_returns_value_and_accumulates_opening() {
let state = seeded_state();
let prov = ProofLookProvider::new(&state);
let ns = Goldilocks::new(Dim::Time as u64);
let key = Goldilocks::new(0);
let val = prov.look(ct(), ns, key);
assert!(val.is_some(), "should return the time root value");
let openings = prov.take_look_openings();
assert_eq!(openings.len(), 1);
assert!(crate::query::verify_opening(&openings[0]));
}
#[test]
fn proof_look_provider_missing_key_returns_none_no_opening() {
let state = seeded_state();
let prov = ProofLookProvider::new(&state);
let ns = Goldilocks::new(Dim::Time as u64);
let key = Goldilocks::new(9999);
let val = prov.look(ct(), ns, key);
assert!(val.is_none());
assert!(prov.take_look_openings().is_empty());
}
#[test]
fn proof_look_provider_accumulates_multiple_openings() {
let state = seeded_state();
let prov = ProofLookProvider::new(&state);
let ns = Goldilocks::new(Dim::Time as u64);
prov.look(ct(), ns, Goldilocks::new(0)); prov.look(ct(), ns, Goldilocks::new(9999)); prov.look(ct(), ns, Goldilocks::new(0));
let openings = prov.take_look_openings();
assert_eq!(openings.len(), 2, "only successful lookups accumulate openings");
}
#[test]
fn take_look_openings_drains_the_buffer() {
let state = seeded_state();
let prov = ProofLookProvider::new(&state);
let ns = Goldilocks::new(Dim::Time as u64);
prov.look(ct(), ns, Goldilocks::new(0));
let first = prov.take_look_openings();
let second = prov.take_look_openings();
assert_eq!(first.len(), 1);
assert!(second.is_empty(), "take drains the buffer");
}
#[test]
fn proof_provider_value_matches_fast_provider_neuron() {
let mut state = BbgState::new();
let mut key = [0u8; 32];
key[..8].copy_from_slice(&42u64.to_le_bytes());
state.neurons.insert(key, NeuronRecord { focus: 777, karma: 0, stake: 0 });
let fast = BbgLookProvider { state: &state };
let proof = ProofLookProvider::new(&state);
let ns = Goldilocks::new(Dim::Neurons as u64);
let k = Goldilocks::new(4);
assert_eq!(fast.look(ct(), ns, k), Some(Goldilocks::new(777)));
assert_eq!(
proof.look(ct(), ns, k),
Some(Goldilocks::new(777)),
"proof provider must return the real cell, matching the fast provider"
);
let openings = proof.take_look_openings();
assert_eq!(openings.len(), 1);
assert_eq!(openings[0].value, Goldilocks::new(777));
assert!(verify_opening(&openings[0]), "corner opening must verify");
}
#[test]
fn proof_provider_value_matches_fast_provider_time() {
let state = seeded_state(); let fast = BbgLookProvider { state: &state };
let proof = ProofLookProvider::new(&state);
let ns = Goldilocks::new(Dim::Time as u64);
let key = Goldilocks::new(4);
let fast_v = fast.look(ct(), ns, key);
assert!(fast_v.is_some());
assert_eq!(proof.look(ct(), ns, key), fast_v, "proof and fast providers must agree");
}
#[test]
fn collect_look_openings_empty_trace() {
let state = seeded_state();
assert!(collect_look_openings(&state, &[]).is_empty());
}
#[test]
fn collect_look_openings_non_look_row_ignored() {
use nox::TraceRow;
let state = seeded_state();
let row = TraceRow::default(); let ops = collect_look_openings(&state, &[row]);
assert!(ops.is_empty());
}
#[test]
fn collect_look_openings_from_real_trace() {
use nox::{reduce, Reduction, VecTrace, Outcome};
let state = seeded_state();
let prov = ProofLookProvider::new(&state);
let mut reduction = Reduction::<1024>::new();
let g = |v: u64| Goldilocks::new(v);
let al0 = reduction.atom(g(0)).unwrap();
let al1 = reduction.atom(g(0)).unwrap();
let al2 = reduction.atom(g(0)).unwrap();
let al3 = reduction.atom(g(0)).unwrap();
let inner2 = reduction.pair(al2, al3).unwrap();
let mid = reduction.pair(al1, inner2).unwrap();
let root_cell = reduction.pair(al0, mid).unwrap();
let rest = reduction.atom(g(0)).unwrap();
let obj = reduction.pair(root_cell, rest).unwrap();
let t17 = reduction.atom(g(17)).unwrap();
let t1 = reduction.atom(g(1)).unwrap();
let vns = reduction.atom(g(8)).unwrap(); let vkey = reduction.atom(g(0)).unwrap(); let ns_f = reduction.pair(t1, vns).unwrap();
let key_f = reduction.pair(t1, vkey).unwrap();
let body = reduction.pair(ns_f, key_f).unwrap();
let f = reduction.pair(t17, body).unwrap();
let mut trace = VecTrace::default();
let outcome = reduce(&mut reduction, obj, f, 1000, &prov, &mut trace);
assert!(matches!(outcome, Outcome::Ok(_, _)), "look must succeed with ProofLookProvider");
let prov_openings = prov.take_look_openings();
assert_eq!(prov_openings.len(), 1);
let col_openings = collect_look_openings(&state, &trace.0);
assert_eq!(col_openings.len(), 1, "collect_look_openings should find 1 look row");
assert!(verify_opening(&prov_openings[0]));
assert!(verify_opening(&col_openings[0]));
}
const _: u64 = EPOCH_BLOCKS;
}