CN-FORGE-03
CN derived enforcedProducer/validator codec symmetry: forge_block emits the era-tagged [era, block] envelope (era = Conway discriminant 7) via the single canonical ade_codec::encode_block_envelope (symmetric to decode_block_envelope), so forge_block output round-trips through the SAME decode_block authority that validates received blocks. decode_block(forge_block(tick).bytes) is Ok and yields a DecodedBlock whose header-body fields and four preserved body-bucket bytes equal what was forged. A bare-block (no-envelope) forge output, or any forge<->decode asymmetry, is CI-gated impossible. Root cause (PHASE4-N-T): forge_block emitted a bare array(5) block, so decode_block_envelope rejected EVERY forged block at offset 0 (BlockValidityError::Body(Decoding(InvalidStructure))) before any header/KES/leader/self_accept logic.
- Source
docs/planning/phase4-n-v-invariants.md; docs/clusters/PHASE4-N-V/cluster.md §1
- Cluster
- PHASE4-N-V
- Introduced in
- PHASE4-N-V
Enforcement trace
Tests 4
- encode_decode_block_envelope_round_trips
- conway_envelope_head_is_82_07
- encode_block_envelope_reencodes_corpus_block_identically
- forge_block_output_decodes_via_decode_block
Cross-references
Strengthened in
Attack rationale
Cardano-specific: a producer whose forged blocks its own validator cannot decode can never self-accept, serve, or have a peer accept a block — it silently produces nothing, or (worse, if decode were lenient) propagates a block the rest of the network rejects. Binding forge output to the single decode_block envelope grammar makes producer/validator codec divergence structurally impossible.
Evidence notes
S1 round-trip + corpus-pin tests (encode_decode_block_envelope_round_trips, conway_envelope_head_matches_corpus). S2 forge<->decode round-trip test (forge_block_output_decodes_via_decode_block) — fails on the pre-N-V tree, locking the regression — plus ci/ci_check_forge_decode_round_trip.sh. S3 forge_to_self_accept_succeeds (first in-process ForgeSucceeded). The bug was latent since N-C because no test ever round-tripped forge_block output through decode_block; self_accept tests used real enveloped corpus blocks. PHASE4-N-X: the serve-side tag-24 wire-wrap landed under CN-WIRE-08 — producer_block_fetch_serve now emits tag24(bytes([era,block])) (verified byte-identical to the real cardano-node 11.0.1 capture); the planning-doc's '[serialisationInfo, tag24(...)]' guess was wrong (bare tag-24, no serialisationInfo word).