//! The frame transition and persistence, end to end. use smart_byte_core::keys::IndexedSignature; use smart_byte_core::ownership::Transition; use smart_byte_core::{Aid, Cargo, Envelope, JouleCost, Said, SigningKey}; use smart_byte_keri_witness::{Controller, KeyEvent, KeyPair, Threshold}; use smart_byte_state::demo::Workload; use smart_byte_state::{Input, InputClass, State, Store}; fn key(label: &str) -> KeyPair { KeyPair::from_signing(SigningKey::from_bytes(&Said::hash(label.as_bytes()).0)) } /// A one-key controller, its next key, and the input that incepts it. fn party(label: &str) -> (Controller, Vec) { let (c, icp) = Controller::incept( vec![key(&format!("{label}-0"))], vec![key(&format!("{label}-1"))], Threshold(1), Threshold(1), vec![], Threshold(0), ) .unwrap(); let input = signed(&c, &KeyEvent::Inception(icp)); (c, input) } fn signed(c: &Controller, event: &KeyEvent) -> Vec { let sigs: Vec = c .sign_event(event) .unwrap() .into_iter() .map(|(index, signature)| IndexedSignature { index: u64::from(index), signature, }) .collect(); Input::key_event(event, &sigs) } fn classes(report: &smart_byte_state::FrameReport) -> Vec> { report .results .iter() .map(|r| r.outcome.as_ref().map(|_| ()).map_err(|e| e.class())) .collect() } struct World { state: State, issuer: Controller, alice: Controller, bob: Controller, byte: Envelope, mint: Vec, } /// Frame 0 applied: the issuer, Alice and Bob incepted, and a USD byte /// minted to Alice. fn world() -> World { let (issuer, issuer_icp) = party("issuer"); let (alice, alice_icp) = party("alice"); let (bob, bob_icp) = party("bob"); let byte = Envelope::issue( &issuer.signer(), alice.aid, [7; 16], Cargo::usd_micros(5_000_000), JouleCost::default(), ); let mint = Input::mint(&byte.provenance); let mut state = State::genesis(Said::hash(b"cluster"), [(issuer.aid, 1)], [], 0); let report = state.apply_frame(&[issuer_icp, alice_icp, bob_icp, mint.clone()]); assert_eq!(classes(&report), vec![Ok(()); 4]); World { state, issuer, alice, bob, byte, mint, } } fn transfer( owner: &Controller, byte: &Said, history: [u8; 32], to: Aid, frame: u64, ) -> (Vec, Transition) { let t = Transition::sign(&owner.signer(), byte, history, to, frame); (Input::transfer(byte, &t), t) } #[test] fn a_byte_is_minted_and_moves() { let mut w = world(); assert_eq!(w.state.frame, 1); let id = w.byte.identity(); let record = &w.state.bytes[&id]; assert_eq!( (record.owner, record.minted, record.history_hash), (w.alice.aid, 0, id.0) ); let (input, t) = transfer(&w.alice, &id, id.0, w.bob.aid, 1); let report = w.state.apply_frame(&[input]); assert_eq!(classes(&report), vec![Ok(())]); assert_eq!(w.state.bytes[&id].owner, w.bob.aid); assert_eq!(w.state.bytes[&id].history_hash, t.next_history_hash()); // The cluster's view agrees with the envelope a client builds. let envelope = w.byte.transfer(&w.alice.signer(), w.bob.aid, 1); assert_eq!(envelope.history_hash(), w.state.bytes[&id].history_hash); } #[test] fn a_replayed_mint_is_refused_in_any_later_frame() { let mut w = world(); let report = w.state.apply_frame(&[w.mint.clone()]); assert_eq!(classes(&report), vec![Err("duplicate_byte")]); assert_eq!(w.state.bytes.len(), 1); } #[test] fn only_a_registered_issuer_mints_and_only_its_cargo_types() { let mut w = world(); let by_alice = Envelope::issue( &w.alice.signer(), w.bob.aid, [1; 16], Cargo::usd_micros(1), JouleCost::default(), ); let joules = Envelope::issue( &w.issuer.signer(), w.bob.aid, [2; 16], Cargo::joules(9), JouleCost::default(), ); let report = w.state.apply_frame(&[ Input::mint(&by_alice.provenance), Input::mint(&joules.provenance), ]); assert_eq!(classes(&report), vec![Err("issuer_not_registered"); 2]); } #[test] fn a_rotation_retires_the_old_keys_in_the_frame_it_lands() { let mut w = world(); let id = w.byte.identity(); // Alice rotates; in the same frame, a transfer signed with her old key. let rot = KeyEvent::Rotation( w.alice .rotate( vec![key("alice-2")], Threshold(1), vec![], vec![], Threshold(0), ) .unwrap(), ); let rotation = signed(&w.alice, &rot); let stale = Transition::sign( &smart_byte_core::Signer::single(w.alice.aid, 0, key("alice-0").signing), &id, id.0, w.bob.aid, 1, ); let report = w .state .apply_frame(&[Input::transfer(&id, &stale), rotation]); // Key events apply first, whatever the inputs' order. assert_eq!(report.results[0].class, Some(InputClass::KeyEvent)); assert_eq!( classes(&report), vec![Ok(()), Err("establishment_not_in_force")] ); // Under the new establishment, it goes through. let (fresh, _) = transfer(&w.alice, &id, id.0, w.bob.aid, 2); assert_eq!(classes(&w.state.apply_frame(&[fresh])), vec![Ok(())]); } #[test] fn a_byte_moves_once_per_frame() { let mut w = world(); let id = w.byte.identity(); let (to_bob, _) = transfer(&w.alice, &id, id.0, w.bob.aid, 1); let (to_issuer, _) = transfer(&w.alice, &id, id.0, w.issuer.aid, 1); let report = w.state.apply_frame(&[to_bob.clone(), to_issuer.clone()]); let mut outcomes = classes(&report); outcomes.sort(); assert_eq!(outcomes, vec![Ok(()), Err("broken_history_chain")]); // The input with the lower digest won. let lower = std::cmp::min( *blake3::hash(&to_bob).as_bytes(), *blake3::hash(&to_issuer).as_bytes(), ); assert_eq!( (report.results[0].input, report.results[0].outcome.is_ok()), (lower, true) ); } #[test] fn transfers_are_checked_against_the_byte_and_the_frame() { let mut w = world(); let id = w.byte.identity(); let (late, _) = transfer(&w.alice, &id, id.0, w.bob.aid, 9); let (not_owner, _) = transfer(&w.bob, &id, id.0, w.alice.aid, 1); let (stale_history, _) = transfer(&w.alice, &id, [0; 32], w.bob.aid, 1); let (unknown, _) = transfer( &w.alice, &Said::hash(b"no such byte"), [0; 32], w.bob.aid, 1, ); let report = w .state .apply_frame(&[late, not_owner, stale_history, unknown]); let mut outcomes = classes(&report); outcomes.sort(); assert_eq!( outcomes, vec![ Err("broken_history_chain"), Err("not_current_owner"), Err("unknown_byte"), Err("wrong_frame") ] ); assert_eq!(w.state.bytes[&id].owner, w.alice.aid); } #[test] fn a_mint_and_its_first_transfer_share_a_frame() { let (issuer, issuer_icp) = party("issuer"); let (alice, alice_icp) = party("alice"); let byte = Envelope::issue( &issuer.signer(), alice.aid, [3; 16], Cargo::usd_micros(1), JouleCost::default(), ); let id = byte.identity(); let (move_it, _) = transfer(&alice, &id, id.0, issuer.aid, 0); let mut state = State::genesis(Said::hash(b"cluster"), [(issuer.aid, 1)], [], 0); let report = state.apply_frame(&[ move_it, Input::mint(&byte.provenance), alice_icp, issuer_icp, ]); assert_eq!(classes(&report), vec![Ok(()); 4]); assert_eq!(state.bytes[&id].owner, issuer.aid); } #[test] fn identical_inputs_are_one_input() { let mut w = world(); let id = w.byte.identity(); let (input, _) = transfer(&w.alice, &id, id.0, w.bob.aid, 1); let report = w.state.apply_frame(&[input.clone(), input.clone(), input]); assert_eq!(classes(&report), vec![Ok(())]); assert_eq!(report.inputs.len(), 1); } #[test] fn a_controller_signs_only_once_its_log_is_in_the_cluster() { let (issuer, issuer_icp) = party("issuer"); let (carol, carol_icp) = party("carol"); let byte = Envelope::issue( &issuer.signer(), carol.aid, [4; 16], Cargo::usd_micros(1), JouleCost::default(), ); let id = byte.identity(); let mut state = State::genesis(Said::hash(b"cluster"), [(issuer.aid, 1)], [], 0); state.apply_frame(&[issuer_icp, Input::mint(&byte.provenance)]); let (early, _) = transfer(&carol, &id, id.0, issuer.aid, 1); assert_eq!( classes(&state.apply_frame(&[early])), vec![Err("unknown_controller")] ); // Carol's inception and her transfer in one frame: key events first. let (now, _) = transfer(&carol, &id, id.0, issuer.aid, 2); assert_eq!( classes(&state.apply_frame(&[now, carol_icp])), vec![Ok(()), Ok(())] ); } #[test] fn key_events_extend_the_cluster_s_logs() { let mut w = world(); let (alice_again, _) = party("alice"); let (dora, _) = party("dora"); let dora_rot = KeyEvent::Rotation({ let mut d = dora; d.rotate( vec![key("dora-2")], Threshold(1), vec![], vec![], Threshold(0), ) .unwrap() }); let alice_icp_again = { let icp = alice_again.key_event_log[0].clone(); signed(&alice_again, &icp) }; let report = w .state .apply_frame(&[alice_icp_again, signed(&w.bob, &dora_rot)]); let mut outcomes = classes(&report); outcomes.sort(); assert_eq!( outcomes, vec![Err("controller_exists"), Err("unknown_controller")] ); } #[test] fn the_demo_workload_runs_and_is_deterministic() { let run = || { let mut workload = Workload::new(3, 2); let mut state = workload.genesis(); let mut hashes = Vec::new(); for f in 0..5 { let report = state.apply_frame(&workload.frame(f)); assert_eq!( report.applied(), report.results.len(), "frame {f}: every input applies" ); hashes.push(state.hash()); } (state, hashes) }; let (a, ha) = run(); let (b, hb) = run(); assert_eq!((a.frame, a.bytes.len(), a.controllers.len()), (5, 6, 4)); assert_eq!((ha, a), (hb, b)); } #[test] fn a_store_recovers_the_state_and_refuses_a_tampered_log() { let dir = tempfile::tempdir().unwrap(); let mut workload = Workload::new(2, 1); let genesis = workload.genesis(); let (mut store, mut state) = Store::open_with_genesis(dir.path(), 3, &genesis).unwrap(); assert_eq!(state, genesis); for f in 0..5 { let report = state.apply_frame(&workload.frame(f)); store.commit(report.frame, &report.inputs, &state).unwrap(); } drop(store); // Five frames at a cadence of three: a checkpoint at frame 3, two logged. let (_, recovered) = Store::open_with_genesis(dir.path(), 3, &genesis).unwrap(); assert_eq!(recovered, state); // A flipped byte in the log's last record. let log = dir.path().join(smart_byte_state::store::LOG); let mut bytes = std::fs::read(&log).unwrap(); let last = bytes.len() - 1; bytes[last] ^= 1; std::fs::write(&log, bytes).unwrap(); assert!(matches!( Store::open_with_genesis(dir.path(), 3, &genesis), Err(smart_byte_state::StoreError::Corrupt(_)) )); }