Skip to content

Commit 969d627

Browse files
committed
Design note: three-module layout, seat definition, review addendum
Incorporate the module-count concessions from the 2026-08-25 design discussion with Carlos and Andre: the umbrella re-export module is dropped and the committee definitions fold into Leios.Validity, leaving three new modules; the map and the interface table follow. Define 'seat' at first use (a position in the committee's canonical order, which votes and certificate bitfields address by index). Append an addendum answering the two review questions: why a Vote type exists (certificate meaning, the implementation's validateVote and REQ-LedgerSerializationVote, metatheory vocabulary) with the severable fallback and its owner made explicit, and why shared Leios definitions get their own subtree rather than folding into the rule modules. AI-assisted development: Claude Fable 5 (Anthropic)
1 parent 76de59a commit 969d627

1 file changed

Lines changed: 113 additions & 6 deletions

File tree

docs/leios/design-note.md

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,11 @@ The new modules, and the edits to existing modules, are as follows:
6060

6161
```text
6262
src/Ledger/Dijkstra/Specification/
63-
├── Leios.lagda.md -- umbrella: overview prose, re-exports
6463
├── Leios/
6564
│ ├── Abstract.lagda.md -- LeiosAbstract: abstract voting crypto
6665
│ ├── Types.lagda.md -- EndorserBlock, Announcement, Vote, Certificate
67-
── Committee.lagda.md -- Seat, Committee, quorum arithmetic
68-
└── Validity.lagda.md -- ValidVote, ValidCert, ValidEB
66+
── Validity.lagda.md -- Seat, Committee, quorum arithmetic;
67+
-- ValidVote, ValidCert, ValidEB
6968
├── Gov/Base.lagda.md -- edit: leiosAbstract field in GovStructure
7069
├── Transaction.lagda.md -- edit: supply leiosAbstract via TransactionStructure
7170
├── PParams.lagda.md -- edit: the Leios parameter block
@@ -233,8 +232,10 @@ The LLF adds the following defaults, each grounded in the design document:
233232
which is why no epoch module appears among the touched modules in the [Module
234233
placement](#module-placement) section above.
235234

236-
+ **Order and indices**. The descending-stake order fixes the seat indices that
237-
votes (`voter_id`) and certificate bitfields address.
235+
+ **Order and indices**. A *seat* is a position in the committee's canonical
236+
order, carrying its pool, its weight, and (optionally) its voting key. The
237+
descending-stake order fixes the seat indices that votes (`voter_id`) and
238+
certificate bitfields address.
238239

239240
The CIP names no tie-break; the design document breaks ties by pool id, and the
240241
LLF adopts it as a stated law, not a remark: equal-stake pools are
@@ -368,7 +369,7 @@ each to its LLF counterpart, or records what stays outside the LLF and why.
368369
| `applyCertifiedEb` | The certificate branch of the block and chain rules: `ValidCert` plus the closure applied via `LEDGERS` from the announcing state, per the ordering above. |
369370
| `validateCertificate` | `ValidCert` (`Leios.Validity`): signers are seats of the pinned committee, the aggregate signature verifies over (slot, EB hash), and the signers' stake meets τ times the total active stake. The contextual half — agreement with the pending announcement, the timing window — sits as block/chain premises. |
370371
| `validateVote` | `ValidVote` (`Leios.Validity`): the voter index denotes a keyed committee seat whose key verifies the signature over (slot, EB hash). Votes never appear on-chain individually; this is the meaning consensus uses to filter votes before aggregation. |
371-
| `doesEpochCommitteeIncludeMe` | Decidable membership on `Committee` (`Leios.Committee`), a seat lookup by pool. The "me" binding is consensus-local; the ledger side is the seat lookup, which the implementation serves from its materialized committee ([REQ-LedgerStateVotingCommittee][dd-certver]). |
372+
| `doesEpochCommitteeIncludeMe` | Decidable membership on `Committee` (`Leios.Validity`), a seat lookup by pool. The "me" binding is consensus-local; the ledger side is the seat lookup, which the implementation serves from its materialized committee ([REQ-LedgerStateVotingCommittee][dd-certver]). |
372373
| `initializeVotingLedgerState` | Follow-up (the voting-state interface). Meaning fixed now: the announcing block's post-`BBODY` state paired with fresh EB accumulators, one per cumulative `ValidEB` bound: referenced-transaction bytes, `ExUnits`, and reference-script bytes. |
373374
| `applyTxForVoting` | Follow-up. Meaning fixed now: one LEDGER step plus accumulator updates and bound checks; folding it over the closure from the initialized state succeeds exactly when `ValidEB`'s extension-and-bounds conjuncts hold. |
374375
| `reapplyTxForVoting` | Follow-up. The minimal-checks variant; its agreement with `applyTxForVoting` on previously validated transactions is the certified-application soundness theorem. |
@@ -399,6 +400,112 @@ like any other). Should a Leios incentive mechanism ever become normative
399400
(rewards for voting or EB production, tiered fees), it enters through the full
400401
roadmap, not the LLF.
401402

403+
## Addendum: two design questions raised in review
404+
405+
### Why a `Vote` type at all?
406+
407+
No ledger transition rule consumes a `Vote`. Votes never reach the chain, and
408+
CIP-164 draws the line accordingly: certificate validation is block validity,
409+
while the vote-casting conditions are node behavior. If "consumed by a rule"
410+
were the only criterion for a type's existence, `Vote` would fail it.
411+
412+
But rules are not a specification's only consumers, and creation is no
413+
criterion at all: the ledger creates neither votes nor certificates, yet
414+
nobody disputes `Certificate`. Three consumers need the vote's fields.
415+
416+
1. **The certificate's meaning**. A certificate is a compressed set of
417+
votes: `cSig` is an aggregate signature over precisely the message each
418+
vote signs, `(slot, EB-hash)`, under the keys of the seats named in the
419+
bitfield (a bitfield index is a `voter_id` in another encoding). The
420+
CIP's reason for signing the slot is a replay-protection argument about
421+
votes ("binding the vote signature to `slot_no` … ensures voters validated
422+
the EB against the same ledger state it extends"). Without a `Vote` type,
423+
the aggregate check verifies an unexplained pair of bytes; with one, the
424+
certificate is a definition instead of an incantation. The price is one
425+
four-field record.
426+
427+
2. **The implementation's own requirements**. The proposed consensus↔ledger
428+
interface includes `validateVote`, with its stated reason: "To
429+
receive/relay votes, we also need to validate them. They're never in a
430+
block, but we get at least the epoch's committee member's public key from
431+
the ledger state." And the design document's requirement register
432+
includes `REQ-LedgerSerializationVote`: the ledger owns the vote wire
433+
format, alongside RB and EB. A ledger spec without a vote type can give
434+
neither requirement a formal counterpart.
435+
436+
3. **Vocabulary for the metatheory**. Quorum safety, the statement that a
437+
valid certificate implies some honest committee member attested `ValidEB`,
438+
is the justification for certified application skipping re-validation, and
439+
it is a statement about votes: honest voters vote only for valid EBs; a
440+
quorum of stake signed; hence an honest vote exists. The proof is
441+
deferred; the statement needs votes as objects.
442+
443+
The design isolates the question by construction: `ValidCert` never mentions
444+
`Vote`, so dropping `Vote` and `ValidVote` costs exactly one record and one
445+
relation, and nothing else moves. The fallback is correspondingly principled:
446+
the `validateVote` interface row becomes "consensus-side composition of
447+
ledger-provided pieces, namely committee seat lookup plus `isSignedVote`,
448+
which `LeiosAbstract` exports regardless".
449+
450+
The fallback has an owner, though, and it is not this note. Whether the
451+
ledger owns vote validation and serialization is fixed, today, in the
452+
implementation team's own artifacts: `validateVote` in the proposed interface
453+
and `REQ-LedgerSerializationVote` in the design document. If the answer is to
454+
become "no", those artifacts must change first; a ledger spec that silently
455+
drops `Vote` while they stand manufactures a spec–implementation divergence.
456+
457+
**Recommendation**. Keep `Vote`/`ValidVote` as definitions serving the
458+
interface and the metatheory, consumed by no rule; this is the framing the
459+
note already uses for the protocol spec's declared-but-uncalled `V-chkCerts`.
460+
461+
### Why a Leios subtree instead of folding into existing modules?
462+
463+
One concession first, because it clarifies the question: the rule edits land
464+
in `BlockBody`, `Chain`, `Certs`, and `PParams` regardless; premises live
465+
where rules live. The question is only where the shared type definitions go.
466+
One placement is already settled by constraint, not taste: the abstract crypto
467+
cannot live in `AbstractFunctions`, because `Certs` sits upstream of it and
468+
the proof-of-possession premise would be unstatable there.
469+
470+
For the rest, every Leios type has more than one consumer, as follows:
471+
472+
+ `Certificate`: `BlockBody`'s field and `ValidCert`;
473+
+ `EndorserBlock`: `BlockBody`'s payload and `ValidEB`;
474+
+ `Announcement`: `BlockBody`'s header and `Chain`'s `PendingEB`;
475+
+ the committee: `Chain`'s pin and both validity relations.
476+
477+
Shared definitions need one home upstream of their consumers, and the lowest
478+
existing candidate is `BlockBody`. "Fold into existing modules" therefore
479+
means "put essentially everything in `BlockBody.lagda.md`", which makes
480+
`BlockBody` the Leios module in all but name: same content, worse label, and
481+
entangled with a rule module that `master` actively churns. Hence the
482+
practical arguments:
483+
484+
+ **Merge economics**. `leios-main` lives by routine merges from `master`,
485+
and the fold targets are `master`'s most active files: `Certs` and
486+
`PParams` churn with parameter work, and the preservation-of-value stack is
487+
touching the `*/Properties` families now. New files never conflict; edits
488+
to shared files can. The subtree is the shape that keeps a long-lived
489+
parallel branch's merges cheap.
490+
+ **Ripple economics**. The derived layers (`Computational`, `Properties`,
491+
`Foreign`) mirror the records and premises of the modules they shadow, so
492+
adding fields to shared records breaks them immediately, while standalone
493+
type modules defer that cost until a rule consumes the types. Both data
494+
points exist on this branch: the `GovStructure` field needed only a
495+
`Foreign` stub, while the `StakePoolParams` field, a fold-style edit, is
496+
the one with real `Foreign` fallout.
497+
+ **Reversibility asymmetry**. Inlining later is trivial: move each
498+
definition into its consumer and delete the module. Extracting later means
499+
editing the shared modules again, a second pass through the conflict
500+
surface. Under residual doubt, the cheap-to-reverse choice is
501+
separate-first.
502+
+ **Precedent**. The consensus implementation keeps Leios in Leios-named
503+
modules (`LeiosVoting.hs`, `Shelley/Ledger/Leios.hs`); the protocol-level
504+
Agda spec has its own namespace; and this specification already organizes
505+
by concern (`Gov/`, `Certs/`, `Utxo/`). A concern-scoped subtree is the
506+
house style. The protocol has also pivoted once (Full → Linear); if it
507+
moves again, a subtree contains the blast radius.
508+
402509
---
403510

404511
[^1]: Promoting the aggregate-signature abstraction into `Ledger.Core`

0 commit comments

Comments
 (0)