agent-joule/crates/joule_coded/tests/consensus.rs
David Charlot 80bc73b33b joule_coded: BFT consensus rounds across the fleet
Wire fleet-wide Byzantine-fault-tolerant agreement on a committed op log,
reusing joule-code-collab's proven LockstepCluster commit rule, with the
signed proposals carried over the same transport as everything else (socket /
TCP / QUIC).

- `serve --members <hex keys>` enables a node as a consensus member.
- New control methods: `consensus_propose` (sign + buffer this node's op,
  return it), `propose` (buffer a peer's op after verifying the signature and
  membership), `commit_round` (commit the buffered set, return the agreed-log
  hash + op ids). A node also reports its `pubkey` in `state`.
- `consensus` subcommand orchestrates one round across the local node + paired
  remotes: every member proposes, proposals are broadcast to all, each commits,
  and the returned hashes are compared — agreement iff they match.

Verified: a deterministic test (3 members reach a byte-identical, order-
independent agreed log; a non-member op is refused) and a live 3-daemon round
where all members commit 3 ops and return the same agreed-log hash.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 01:31:58 -04:00

73 lines
2.7 KiB
Rust

//! BFT consensus, deterministically: three members each propose a signed op, all
//! proposals reach every member, and each commits to a byte-identical agreed log —
//! while a non-member's op is rejected. This exercises the same `Round` the daemon
//! drives over QUIC/TCP/socket via `consensus_propose`/`propose`/`commit_round`.
use ed25519_dalek::SigningKey;
use joule_code_schema::{ContentId, Tier};
use joule_coded::consensus::Round;
use joule_core::ops::build_coding_op;
/// Build a signed op for `intent` authored by `key`, with its prior.
fn member_op(key: &SigningKey, intent: &str) -> (joule_code_cargo::SignedOp, ContentId) {
let op = build_coding_op(
"consensus",
"",
intent,
intent.to_string(),
Tier::L1,
key.verifying_key().to_bytes(),
);
let prior = op.prior.clone();
(op.sign(key).expect("sign"), prior)
}
#[test]
fn three_members_reach_byte_identical_agreed_log() {
let keys: Vec<SigningKey> = (1u8..=3).map(|i| SigningKey::from_bytes(&[i; 32])).collect();
let members: Vec<[u8; 32]> = keys.iter().map(|k| k.verifying_key().to_bytes()).collect();
// Each member proposes its own op (distinct author ⇒ distinct op).
let proposals: Vec<_> = keys.iter().map(|key| member_op(key, "round-1")).collect();
// Three independent member views each receive all proposals and commit.
let mut hashes = Vec::new();
for _ in 0..members.len() {
let mut round = Round {
members: members.clone(),
proposals: Vec::new(),
};
for (op, prior) in &proposals {
round.accept(op.clone(), prior.clone()).expect("accept valid member op");
}
let (hash, committed) = round.commit();
assert_eq!(committed.len(), 3, "all three valid proposals commit");
hashes.push(hash);
}
assert!(
hashes.windows(2).all(|pair| pair[0] == pair[1]),
"honest members must agree on a byte-identical log: {hashes:?}"
);
// Buffering order does not change the agreed log (commit is op-id ordered).
let mut reversed = Round {
members: members.clone(),
proposals: Vec::new(),
};
for (op, prior) in proposals.iter().rev() {
reversed.accept(op.clone(), prior.clone()).expect("accept");
}
assert_eq!(reversed.commit().0, hashes[0], "order-independent agreed log");
// A non-member's op is rejected before it can enter the round.
let intruder = SigningKey::from_bytes(&[9u8; 32]);
let (op, prior) = member_op(&intruder, "round-1");
let mut round = Round {
members,
proposals: Vec::new(),
};
assert!(
round.accept(op, prior).is_err(),
"a non-member proposal must be refused"
);
}