//! Governance, burns, and bytes moving between clusters. use smart_byte_core::keys::IndexedSignature; use smart_byte_core::{Aid, Cargo, Envelope, JouleCost, Said, SigningKey}; use smart_byte_keri_witness::{Controller, KeyEvent, KeyPair, Threshold}; use smart_byte_state::certificate::node_key; use smart_byte_state::{Approval, Burn, Certificate, Change, Input, Proposal, State}; fn key(label: &str) -> KeyPair { KeyPair::from_signing(SigningKey::from_bytes(&Said::hash(label.as_bytes()).0)) } /// A one-key controller and the signed events of its log so far. struct Party { c: Controller, log: Vec<(KeyEvent, Vec)>, } impl Party { fn new(label: &str) -> Self { let (c, icp) = Controller::incept( vec![key(&format!("{label}-0"))], vec![key(&format!("{label}-1"))], Threshold(1), Threshold(1), vec![], Threshold(0), ) .unwrap(); let event = KeyEvent::Inception(icp); let sigs = sigs(&c, &event); Party { c, log: vec![(event, sigs)], } } fn aid(&self) -> Aid { self.c.aid } fn incept(&self) -> Vec { Input::key_event(&self.log[0].0, &self.log[0].1) } fn approve(&self, proposal: &Proposal) -> Approval { Approval { approver: self.aid(), signature: self .c .signer() .sign(&smart_byte_core::wire::to_vec(proposal).unwrap()), } } } fn sigs(c: &Controller, event: &KeyEvent) -> Vec { c.sign_event(event) .unwrap() .into_iter() .map(|(index, signature)| IndexedSignature { index: u64::from(index), signature, }) .collect() } fn classes(report: &smart_byte_state::FrameReport) -> Vec> { report .results .iter() .map(|r| r.outcome.as_ref().map(|_| ()).map_err(|e| e.class())) .collect() } fn sorted(mut v: Vec>) -> Vec> { v.sort(); v } const USD: u64 = 1; fn cluster(label: &str) -> Said { Said::hash(label.as_bytes()) } #[test] fn an_issuer_is_added_by_the_others_and_only_after_the_delay() { let (a, b, c, new) = ( Party::new("a"), Party::new("b"), Party::new("c"), Party::new("new"), ); let id = cluster("one"); let mut state = State::genesis(id, [(a.aid(), USD), (b.aid(), USD), (c.aid(), USD)], [], 2); state.apply_frame(&[a.incept(), b.incept(), c.incept(), new.incept()]); let proposal = Proposal { cluster: id, sequence: 0, change: Change::AddIssuer { cargo_type: USD, issuer: new.aid(), }, }; // Three issuers: the quorum is floor(2ยท3/3)+1 = 3. let two = Input::governance(&proposal, &[a.approve(&proposal), b.approve(&proposal)]); let three = Input::governance( &proposal, &[ a.approve(&proposal), b.approve(&proposal), c.approve(&proposal), ], ); assert_eq!( classes(&state.apply_frame(&[two])), vec![Err("below_quorum")] ); assert_eq!(classes(&state.apply_frame(&[three.clone()])), vec![Ok(())]); assert_eq!(state.governance.pending.len(), 1); assert_eq!(state.governance.pending[0].effective, 2 + 1 + 2); // The same proposal again: its sequence number is spent. assert_eq!( classes(&state.apply_frame(&[three])), vec![Err("stale_sequence")] ); let mint = |label: &str, frame_note: &str| { let e = Envelope::issue( &new.c.signer(), a.aid(), [frame_note.len() as u8; 16], Cargo::usd_micros(1), JouleCost::default(), ); let _ = label; Input::mint(&e.provenance) }; // Frame 4: not yet in force. assert_eq!(state.frame, 4); assert_eq!( classes(&state.apply_frame(&[mint("early", "x")])), vec![Err("issuer_not_registered")] ); // Frame 5: in force from its start. assert_eq!( classes(&state.apply_frame(&[mint("in force", "xy")])), vec![Ok(())] ); assert!(state.authorizes(&new.aid(), USD) && state.governance.pending.is_empty()); } #[test] fn governance_is_checked_before_it_is_counted() { let (a, b, outsider) = (Party::new("a"), Party::new("b"), Party::new("outsider")); let id = cluster("one"); let mut state = State::genesis(id, [(a.aid(), USD), (b.aid(), USD)], [], 0); state.apply_frame(&[a.incept(), b.incept(), outsider.incept()]); let p = |change: Change, sequence: u64, cluster: Said| Proposal { cluster, sequence, change, }; let add = |issuer: Aid| Change::AddIssuer { cargo_type: USD, issuer, }; let remove = |issuer: Aid| Change::RemoveIssuer { cargo_type: USD, issuer, }; let elsewhere = p(add(outsider.aid()), 0, cluster("two")); let stale = p(add(outsider.aid()), 7, id); let registered = p(add(a.aid()), 0, id); let unregistered = p(remove(outsider.aid()), 0, id); let by_outsider = p(add(outsider.aid()), 0, id); let twice = p(add(outsider.aid()), 0, id); let report = state.apply_frame(&[ Input::governance(&elsewhere, &[a.approve(&elsewhere), b.approve(&elsewhere)]), Input::governance(&stale, &[a.approve(&stale), b.approve(&stale)]), Input::governance( ®istered, &[a.approve(®istered), b.approve(®istered)], ), Input::governance( &unregistered, &[a.approve(&unregistered), b.approve(&unregistered)], ), Input::governance( &by_outsider, &[a.approve(&by_outsider), outsider.approve(&by_outsider)], ), Input::governance(&twice, &[a.approve(&twice), a.approve(&twice)]), ]); assert_eq!( sorted(classes(&report)), sorted(vec![ Err("wrong_cluster"), Err("stale_sequence"), Err("already_registered"), Err("not_registered"), Err("not_an_elector"), Err("duplicate_approver"), ]) ); assert_eq!(state.governance.sequence, 0); } #[test] fn an_issuer_cannot_block_its_own_removal() { let (a, b, c) = (Party::new("a"), Party::new("b"), Party::new("c")); let id = cluster("one"); let mut state = State::genesis(id, [(a.aid(), USD), (b.aid(), USD), (c.aid(), USD)], [], 0); state.apply_frame(&[a.incept(), b.incept(), c.incept()]); let proposal = Proposal { cluster: id, sequence: 0, change: Change::RemoveIssuer { cargo_type: USD, issuer: c.aid(), }, }; // The electorate is the other two; both approve. The target's vote is // not counted, and not needed. let with_target = Input::governance(&proposal, &[a.approve(&proposal), c.approve(&proposal)]); assert_eq!( classes(&state.apply_frame(&[with_target])), vec![Err("not_an_elector")] ); let others = Input::governance(&proposal, &[a.approve(&proposal), b.approve(&proposal)]); assert_eq!(classes(&state.apply_frame(&[others])), vec![Ok(())]); state.apply_frame(&[]); assert!(!state.authorizes(&c.aid(), USD)); } #[test] fn redemption_is_a_return_to_the_issuer_and_a_burn() { let (issuer, alice) = (Party::new("issuer"), Party::new("alice")); let mut state = State::genesis(cluster("one"), [(issuer.aid(), USD)], [], 0); let byte = Envelope::issue( &issuer.c.signer(), alice.aid(), [1; 16], Cargo::usd_micros(5), JouleCost::default(), ); let mint = Input::mint(&byte.provenance); state.apply_frame(&[issuer.incept(), alice.incept(), mint.clone()]); let id = byte.identity(); // Alice returns it; the issuer burns it in the same frame: burns apply // after transfers. let back = byte.transfer(&alice.c.signer(), issuer.aid(), 1); let t = back.ownership_chain.last().unwrap(); let burn = Burn::sign(&issuer.c.signer(), id, back.history_hash(), 1); let by_alice = Burn::sign(&alice.c.signer(), id, back.history_hash(), 1); let report = state.apply_frame(&[ Input::burn(&burn), Input::burn(&by_alice), Input::transfer(&id, t), ]); assert_eq!( sorted(classes(&report)), sorted(vec![Ok(()), Ok(()), Err("not_current_owner")]) ); assert!(state.bytes.is_empty()); assert_eq!(state.retired[&id].owner, issuer.aid()); // Burned for good: no transfer, no re-mint. let after = Burn::sign(&issuer.c.signer(), id, back.history_hash(), 2); assert_eq!( sorted(classes(&state.apply_frame(&[Input::burn(&after), mint]))), vec![Err("byte_retired"), Err("duplicate_byte")] ); } /// Two clusters, each with four nodes, peering with each other. struct Federation { a: State, b: State, a_nodes: Vec, b_nodes: Vec, } fn federation(registry: &[(Aid, u64)]) -> Federation { let nodes = |label: &str| { (0..4) .map(|i| SigningKey::from_bytes(&Said::hash(format!("{label}-node-{i}").as_bytes()).0)) .collect::>() }; let (a_nodes, b_nodes) = (nodes("a"), nodes("b")); let keys = |n: &[SigningKey]| n.iter().map(node_key).collect::>(); Federation { a: State::genesis( cluster("A"), registry.iter().copied(), [(cluster("B"), keys(&b_nodes))], 0, ), b: State::genesis( cluster("B"), registry.iter().copied(), [(cluster("A"), keys(&a_nodes))], 0, ), a_nodes, b_nodes, } } fn certify( statement: smart_byte_state::ExportStatement, nodes: &[SigningKey], signers: &[u64], ) -> Certificate { let picked: Vec<(u64, &SigningKey)> = signers.iter().map(|i| (*i, &nodes[*i as usize])).collect(); Certificate::sign(statement, &picked) } #[test] fn a_byte_moves_between_clusters_and_back() { let (issuer, alice, bob) = (Party::new("issuer"), Party::new("alice"), Party::new("bob")); let mut f = federation(&[(issuer.aid(), USD)]); let byte = Envelope::issue( &issuer.c.signer(), alice.aid(), [1; 16], Cargo::usd_micros(5), JouleCost::default(), ); let id = byte.identity(); f.a.apply_frame(&[ issuer.incept(), alice.incept(), Input::mint(&byte.provenance), ]); f.b.apply_frame(&[bob.incept()]); // Alice sends it to Bob, in cluster B. let out = byte.export(&alice.c.signer(), bob.aid(), 1, cluster("B")); let report = f.a.apply_frame(&[Input::transfer(&id, out.ownership_chain.last().unwrap())]); assert_eq!(classes(&report), vec![Ok(())]); assert!(f.a.bytes.is_empty() && f.a.exported.contains_key(&id)); let statement = report.exports[0].clone(); // A transfer of a byte that has left is refused. let stale = byte.transfer(&alice.c.signer(), issuer.aid(), 2); assert_eq!( classes(&f.a.apply_frame(&[Input::transfer(&id, stale.ownership_chain.last().unwrap())])), vec![Err("byte_exported")] ); let logs = vec![issuer.log.clone(), alice.log.clone()]; let import = |cert: &Certificate, logs: &[Vec<(KeyEvent, Vec)>]| { Input::import(cert, &out, logs) }; let three = certify(statement.clone(), &f.a_nodes, &[0, 1, 2]); let two = certify(statement.clone(), &f.a_nodes, &[0, 2]); let missing_log = import(&three, &logs[..1]); let report = f.b.apply_frame(&[import(&two, &logs), missing_log]); assert_eq!( sorted(classes(&report)), vec![Err("below_threshold"), Err("unknown_key_state")] ); // With three of A's four nodes and every log the history needs: applied. assert_eq!( classes(&f.b.apply_frame(&[import(&three, &logs)])), vec![Ok(())] ); assert_eq!(f.b.bytes[&id].owner, bob.aid()); assert_eq!(f.b.bytes[&id].history_hash, out.history_hash()); // Once. assert_eq!( classes(&f.b.apply_frame(&[import(&three, &logs)])), vec![Err("duplicate_import")] ); // Bob sends it back to Alice in A; A takes it back over its own export. let home = out.export(&bob.c.signer(), alice.aid(), f.b.frame, cluster("A")); let report = f.b.apply_frame(&[Input::transfer(&id, home.ownership_chain.last().unwrap())]); assert_eq!(classes(&report), vec![Ok(())]); let cert = certify(report.exports[0].clone(), &f.b_nodes, &[1, 2, 3]); let logs = vec![issuer.log.clone(), alice.log.clone(), bob.log.clone()]; assert_eq!( classes(&f.a.apply_frame(&[Input::import(&cert, &home, &logs)])), vec![Ok(())] ); assert!(f.a.bytes.contains_key(&id) && !f.a.exported.contains_key(&id)); // A's first certificate, replayed in A, is not for A. assert_eq!( classes(&f.a.apply_frame(&[import(&three, &[issuer.log.clone(), alice.log.clone()])])), vec![Err("wrong_cluster")] ); } #[test] fn an_import_must_be_the_byte_certified() { let (issuer, alice, bob) = (Party::new("issuer"), Party::new("alice"), Party::new("bob")); let mut f = federation(&[(issuer.aid(), USD)]); let byte = Envelope::issue( &issuer.c.signer(), alice.aid(), [1; 16], Cargo::usd_micros(5), JouleCost::default(), ); let id = byte.identity(); f.a.apply_frame(&[ issuer.incept(), alice.incept(), Input::mint(&byte.provenance), ]); let out = byte.export(&alice.c.signer(), bob.aid(), 1, cluster("B")); let report = f.a.apply_frame(&[Input::transfer(&id, out.ownership_chain.last().unwrap())]); let cert = certify(report.exports[0].clone(), &f.a_nodes, &[0, 1, 2]); let logs = vec![issuer.log.clone(), alice.log.clone()]; // The envelope before the export: not the history A certified. let report = f.b.apply_frame(&[Input::import(&cert, &byte, &logs)]); assert_eq!(classes(&report), vec![Err("certificate_mismatch")]); // A certificate from a cluster B does not peer with. let mut stranger = f.b.clone(); stranger.peers.clear(); assert_eq!( classes(&stranger.apply_frame(&[Input::import(&cert, &out, &logs)])), vec![Err("unknown_cluster")] ); // An export to a cluster A does not peer with is refused in A. let lost = byte.export(&alice.c.signer(), bob.aid(), 2, cluster("nowhere")); let mut a = f.a.clone(); a.exported.clear(); a.bytes.insert( id, smart_byte_state::ByteRecord { issuer: issuer.aid(), cargo: Cargo::usd_micros(5), minted: 0, owner: alice.aid(), history_hash: id.0, }, ); assert_eq!( classes(&a.apply_frame(&[Input::transfer(&id, lost.ownership_chain.last().unwrap())])), vec![Err("unknown_cluster")] ); } #[test] fn peers_change_by_governance() { let (a, b) = (Party::new("a"), Party::new("b")); let id = cluster("one"); let mut state = State::genesis(id, [(a.aid(), USD), (b.aid(), 2)], [], 0); state.apply_frame(&[a.incept(), b.incept()]); let node = SigningKey::from_bytes(&[9; 32]); let proposal = Proposal { cluster: id, sequence: 0, change: Change::SetPeer { peer: cluster("two"), keys: vec![node_key(&node)], }, }; // Peer changes are every issuer's business: both must approve. let one = Input::governance(&proposal, &[a.approve(&proposal)]); let both = Input::governance(&proposal, &[a.approve(&proposal), b.approve(&proposal)]); assert_eq!( classes(&state.apply_frame(&[one])), vec![Err("below_quorum")] ); assert_eq!(classes(&state.apply_frame(&[both])), vec![Ok(())]); state.apply_frame(&[]); assert!(state.peers.contains_key(&cluster("two"))); // A peer's keys must be keys. let bad = Proposal { cluster: id, sequence: 1, change: Change::SetPeer { peer: cluster("three"), keys: vec![smart_byte_core::PrefixedKey(vec![1, 2, 3])], }, }; assert_eq!( classes( &state.apply_frame(&[Input::governance(&bad, &[a.approve(&bad), b.approve(&bad)])]) ), vec![Err("malformed_verifying_key")] ); }