use std::collections::BTreeMap;
use crate::genesis::file::{Balance, GenesisFile};
use super::api::ChainApi;
use super::bbg_state::BbgBacked;
use super::engine::Chain;
use super::genesis::{Genesis, Params};
use super::header::hash32;
use super::params::GENESIS_BITS;
use super::sigs::{self, neuron_of, token};
use super::state::StateBackend;
use super::tx::{Body, Tx};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Entry {
pub key: String,
pub scenario: String,
pub value: [u8; 32],
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn unhex32(s: &str) -> Option<[u8; 32]> {
if s.len() != 64 {
return None;
}
let mut out = [0u8; 32];
for (i, b) in out.iter_mut().enumerate() {
*b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).ok()?;
}
Some(out)
}
pub const KNOWN_ANSWER_SECRET: [u8; 32] = [
0xc4, 0xa4, 0x8e, 0x2f, 0xce, 0x14, 0x81, 0xcd, 0x32, 0x94, 0xb4, 0x49, 0x0f, 0x66, 0x78, 0x09, 0x0e, 0xa9, 0x8d, 0x3d, 0x0e, 0x5c, 0xd9,
0x84, 0x55, 0x8a, 0xb0, 0x96, 0x87, 0x41, 0xb1, 0x04,
];
pub fn known_answer_key() -> mudra::SigningKey {
mudra::SigningKey::from_bytes((&KNOWN_ANSWER_SECRET).into()).expect("pinned secret is a valid scalar")
}
pub fn conformance_genesis() -> Genesis {
Genesis::from_file(&GenesisFile {
params: Params {
version: 1,
name: "conformance".into(),
hrp: "pussy".into(),
denom: "pussy".into(),
time: 0,
bits: GENESIS_BITS,
subsidy: 0,
checkpoint_key: [0; 33],
checkpoint_sunset: 0,
pool: super::pool::Pool::genesis(),
},
balances: vec![Balance { owner: [0x66; 32], token: token("pussy"), amount: 1_000_000 }],
links: vec![],
})
.expect("the conformance genesis is canonical")
}
pub fn signed_vectors() -> Vec<(String, Vec<u8>)> {
let chain = [0x44u8; 32];
let key = known_answer_key();
let from = neuron_of(&mudra::cosmos::compressed(key.verifying_key()));
let mut transfer = Tx { body: Body::Transfer { from, nonce: 0, token: token("pussy"), amount: 5, to: [0x77; 32], fee: 224 }, sig: vec![] };
sigs::sign(&chain, &mut transfer, &key).expect("own subject");
let mut link = Tx {
body: Body::Cyberlink { from, nonce: 0, token: token("pussy"), amount: 0, p: token("boot"), q: token("pussy"), valence: 1, fee: 257 },
sig: vec![],
};
sigs::sign(&chain, &mut link, &key).expect("own subject");
vec![("signed transfer".into(), transfer.encode()), ("signed cyberlink".into(), link.encode())]
}
fn state_entries() -> Vec<Entry> {
let mut out = Vec::new();
let push = |out: &mut Vec<Entry>, scenario: &str, value: [u8; 32]| {
out.push(Entry { key: "chain::state@v1".into(), scenario: scenario.into(), value });
};
let mut empty = BbgBacked::new();
push(&mut out, "empty state root", empty.root());
let g = conformance_genesis();
push(&mut out, "conformance genesis file", hash32(&g.bytes));
let mut s = g.state();
let root0 = s.root();
push(&mut out, "conformance genesis state_root", root0);
push(&mut out, "conformance block0 id", g.block0(root0).id());
out
}
pub fn genesis_entries(name: &str, bytes: &[u8]) -> Result<Vec<Entry>, String> {
use super::economy::focus::{FocusEngine, FocusGraph, Tru};
use super::economy::weights::GenesisWeights;
let g = Genesis::decode(bytes).map_err(|e| e.to_string())?;
let mut s = g.state();
let root = s.root();
let w = GenesisWeights::from_file(&g.file);
let adjacency: Vec<([u8; 32], [u8; 32], u128)> = w.axons.iter().map(|(p, q, g)| (*p, *q, *g as u128)).collect();
let graph = FocusGraph::build(&adjacency);
let f0 = Tru::new(1).focus(&graph);
let f8 = Tru::new(8).focus(&graph);
if f8 != f0 {
return Err(format!("{name}: φ*_0 differs between 1 and 8 threads"));
}
let scalars = |v: &[u64]| {
let mut out = [0u8; 32];
for (i, x) in v.iter().enumerate() {
out[i * 8..i * 8 + 8].copy_from_slice(&x.to_le_bytes());
}
out
};
let e = |scenario: String, value: [u8; 32]| Entry { key: "chain::genesis@v1".into(), scenario, value };
Ok(vec![
e(format!("{name} particle"), g.chain_id),
e(format!("{name} state_root"), root),
e(format!("{name} block0_id"), g.block0(root).id()),
e(format!("{name} genesis weights digest"), w.digest()),
e(format!("{name} genesis weights total, frames at floor"), scalars(&[w.total as u64, (w.total >> 64) as u64, w.at_floor])),
e(format!("{name} phi*_0 digest (1 and 8 threads)"), super::economy::phi_digest(&f0.phi)),
e(format!("{name} phi*_0 syntropy"), scalars(&[f0.syntropy as u64, (f0.syntropy >> 64) as u64])),
])
}
pub fn entries() -> Vec<Entry> {
let mut out = Vec::new();
for (scenario, bytes) in signed_vectors() {
out.push(Entry { key: "chain::tx@v1".into(), scenario, value: hash32(&bytes) });
}
out.extend(state_entries());
for (scenario, value) in super::pool::vectors() {
out.push(Entry { key: "chain::pool@v1".into(), scenario: scenario.into(), value });
}
for (scenario, value) in super::economy::vectors::entries() {
out.push(Entry { key: "chain::economy@v1".into(), scenario, value });
}
out
}
pub fn parse(src: &str) -> Vec<Entry> {
let mut out = Vec::new();
for line in src.lines() {
let line = line.trim_end();
if line.is_empty() || line.starts_with('#') {
continue;
}
let Some((key, rest)) = line.split_once(" :: \"") else { continue };
let Some((scenario, rest)) = rest.split_once('"') else { continue };
let Some(h) = rest.split_whitespace().find_map(|w| w.strip_prefix('h')) else { continue };
if let Some(value) = unhex32(h) {
out.push(Entry { key: key.trim().into(), scenario: scenario.into(), value });
}
}
out
}
pub fn render(entries: &[Entry], date: &str) -> String {
let mut sorted: Vec<&Entry> = entries.iter().collect();
sorted.sort_by(|a, b| (&a.key, &a.scenario).cmp(&(&b.key, &b.scenario)));
let label = |e: &Entry| format!("{} :: \"{}\"", e.key, e.scenario);
let width = sorted.iter().map(|e| label(e).len()).max().unwrap_or(0);
let mut out = String::new();
out.push_str(&format!("# generated by `soft3 conformance bless chain` ({date}); do not edit by hand\n"));
out.push_str("# chain.snap — chain-conformance.md blessed vectors: §6 signed txs (fingerprints), §8 state roots and ids, §10 genesis\n");
out.push_str("# (particle = chain id, block-0 root and id, genesis weights, phi*_0), §12 pool formats (frozen at genesis), §14 the focus economy (tru epoch engine e660d1e).\n");
out.push_str("# Roots move with bbg's DIMENSION_VERSION, economy entries with tru's engine; a moved entry is a consensus change.\n");
for e in sorted {
out.push_str(&format!("{:<width$} tier=gamma h{}\n", label(e), hex(&e.value), width = width));
}
out
}
pub fn merged(committed: &[Entry], current: &[Entry]) -> Vec<Entry> {
let mut map: BTreeMap<(String, String), [u8; 32]> = committed.iter().map(|e| ((e.key.clone(), e.scenario.clone()), e.value)).collect();
for e in current {
map.insert((e.key.clone(), e.scenario.clone()), e.value);
}
map.into_iter().map(|((key, scenario), value)| Entry { key, scenario, value }).collect()
}
pub fn drift(committed: &[Entry], current: &[Entry]) -> Vec<(String, Option<[u8; 32]>, [u8; 32])> {
let map: BTreeMap<(String, String), [u8; 32]> = committed.iter().map(|e| ((e.key.clone(), e.scenario.clone()), e.value)).collect();
let mut out = Vec::new();
for e in current {
let was = map.get(&(e.key.clone(), e.scenario.clone())).copied();
if was != Some(e.value) {
out.push((format!("{} :: \"{}\"", e.key, e.scenario), was, e.value));
}
}
out
}
pub fn utc_date() -> String {
let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
let z = (secs / 86_400) as i64 + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
format!("{y:04}-{m:02}-{d:02}")
}
pub fn block1_root() -> [u8; 32] {
let g = conformance_genesis();
let mut dir = std::env::temp_dir();
dir.push(format!("soft3-chain-conformance-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let mut chain = Chain::open(&dir, &g.bytes, true).expect("conformance net opens");
let tpl = chain.header_template([0x55; 32], 1, &[]).expect("template");
let _ = std::fs::remove_dir_all(&dir);
tpl.header.state_root
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_answer_secret_is_the_abandon_key() {
let key = known_answer_key();
let pk = mudra::cosmos::compressed(key.verifying_key());
assert_eq!(hex(&pk), "024f4e2ad99c34d60b9ba6283c9431a8418af8673212961f97a77b6377fcd05b62");
let derived = mudra::spell::cosmos_key(super::super::sigs::tests::ABANDON, "").unwrap();
assert_eq!(derived.to_bytes().as_slice(), &KNOWN_ANSWER_SECRET, "the pinned secret is the phrase's key");
assert!(utc_date().len() == 10);
}
#[test]
fn signed_vectors_verify_and_are_deterministic() {
let a = signed_vectors();
let b = signed_vectors();
assert_eq!(a, b, "RFC 6979 signatures are deterministic");
for (_, bytes) in &a {
let tx = Tx::decode(bytes).unwrap();
assert!(sigs::verify(&[0x44; 32], &tx));
assert!(!sigs::verify(&[0x45; 32], &tx));
}
assert_eq!(a[0].1.len(), 224);
assert_eq!(a[1].1.len(), 257);
}
#[test]
fn render_parse_round_trip_and_drift() {
let e = entries();
let text = render(&e, "2026-10-15");
let back = parse(&text);
let mut sorted = e.clone();
sorted.sort_by(|a, b| (&a.key, &a.scenario).cmp(&(&b.key, &b.scenario)));
assert_eq!(back, sorted);
assert!(drift(&back, &e).is_empty());
let mut moved = e.clone();
moved[0].value[0] ^= 1;
assert_eq!(drift(&back, &moved).len(), 1);
let committed = parse("chain::genesis@v1 :: \"pussy particle\" tier=gamma h2a83e54fe395ffbd924fc74a54a5c805eeec5396f97bf4c73fced8631a9e61b5\n");
assert_eq!(committed.len(), 1);
assert_eq!(merged(&committed, &e).len(), e.len() + 1);
}
#[test]
fn committed_chain_snap_matches_this_build() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../conformance/chain.snap");
let committed = parse(&std::fs::read_to_string(path).expect("conformance/chain.snap"));
let mut current = entries();
let home = std::env::var("HOME").unwrap_or_default();
let pussy = std::path::Path::new(&home).join(".cache/cyber/genesis/pussy.tade");
if let Ok(bytes) = std::fs::read(&pussy) {
current.extend(genesis_entries("pussy", &bytes).unwrap());
}
let d = drift(&committed, ¤t);
assert!(d.is_empty(), "chain.snap drift (re-bless with `soft3 conformance bless chain` and review): {d:?}");
assert!(committed.iter().any(|e| e.scenario == "pussy particle"));
}
}