CAIN-42 Byzantine-replica tests across three regions
Byzantine-replica tests across the three regions of the CAIN-42 deployment (Atlanta, Los Angeles ×2, Miami) on the production image. The live production cluster refuses fault injection by design, so the tests ran on a separate, disposable 4-replica cluster with the same placement, removed afterwards. This page loads every quorum certificate the three honest replicas hold, and your browser re-checks them.
What happened
B1: a replica forging votes (cain-byz-node-3). It sent COMMIT votes claiming to come from another member, signed with its own key. Every honest replica rejected them (cain-byz-node-1: 6, cain-byz-node-2: 6, cain-byz-node-4: 6 rejected), and 6/6 writes committed: PASS.
B2: an equivocating primary (cain-byz-node-1). The leader sent two conflicting, correctly signed proposals for one sequence. All three honest replicas built an equivocation proof from the leader's own two signatures and quarantined it (cain-byz-node-2, cain-byz-node-3, cain-byz-node-4). The view changed 0 to 1, and 6/6 writes committed under the new primary: PASS.
The equivocation proof is in the bundle. verify_byzantine_bundle.py checks that both conflicting proposals carry the accused replica's own valid signature, so the accusation needs no trust in the accusers.
Verify (about 5 seconds)
What is checked
- The membership configuration hash is recomputed from the 4 member ids and Ed25519 public keys. Every node and every certificate must carry it.
- For every sequence on every node, the COMMIT_QC and the PREPARE_QC. Each vote must be an Ed25519 signature by a distinct member over SHA-256 of the canonical signed message. It must have the right type (a COMMIT vote never counts as a PREPARE vote) and match this cluster, epoch, view, sequence and digest. Each certificate needs at least 3 distinct signers. The leader's proposal must be signed by the primary of that view, and its digest must bind the proposed operation.
- The certificate hash and signature-bundle hash are recomputed from the content.
- Evolution 3 fast path: a
FAST_COMMIT_QC(a decision taken without the COMMIT round) is accepted only if the published membership declares the fast path and all four members signed it. Three of four is never enough for a fast commit. - The decision chain is folded from genesis:
decision_hash(seq) = H(cluster, epoch, seq, digest, parent). It must be contiguous and identical on all four nodes, and the nodes must end with the same application-state hash. - View-change quorum certificates, and one consensus-to-enforcement AuthorizationCertificate per node.
- Negative controls: a certificate is tampered with in seven ways, and every tampered copy must be rejected.
The same checks, without a browser: curl -so verify_pbft_qc_bundle.py verify_pbft_qc_bundle.py.txt && python3 verify_pbft_qc_bundle.py PBFT_QC_BUNDLE.json (needs pip install cryptography, no CAIN code).