May 30, 4-5 AM (2)
May 30, 5-6 AM (11)
May 30, 6-7 AM (0)
May 30, 7-8 AM (2)
May 30, 8-9 AM (11)
May 30, 9-10 AM (13)
May 30, 10-11 AM (10)
May 30, 11-12 PM (5)
May 30, 12-1 PM (8)
May 30, 1-2 PM (5)
May 30, 2-3 PM (18)
May 30, 3-4 PM (5)
May 30, 4-5 PM (1)
May 30, 5-6 PM (9)
May 30, 6-7 PM (9)
May 30, 7-8 PM (1)
May 30, 8-9 PM (5)
May 30, 9-10 PM (4)
May 30, 10-11 PM (27)
May 30, 11-12 AM (12)
May 31, 12-1 AM (17)
May 31, 1-2 AM (0)
May 31, 2-3 AM (1)
May 31, 3-4 AM (1)
May 31, 4-5 AM (0)
May 31, 5-6 AM (0)
May 31, 6-7 AM (7)
May 31, 7-8 AM (4)
May 31, 8-9 AM (10)
May 31, 9-10 AM (3)
May 31, 10-11 AM (4)
May 31, 11-12 PM (4)
May 31, 12-1 PM (1)
May 31, 1-2 PM (2)
May 31, 2-3 PM (24)
May 31, 3-4 PM (16)
May 31, 4-5 PM (2)
May 31, 5-6 PM (1)
May 31, 6-7 PM (2)
May 31, 7-8 PM (2)
May 31, 8-9 PM (2)
May 31, 9-10 PM (7)
May 31, 10-11 PM (25)
May 31, 11-12 AM (11)
Jun 01, 12-1 AM (14)
Jun 01, 1-2 AM (7)
Jun 01, 2-3 AM (3)
Jun 01, 3-4 AM (10)
Jun 01, 4-5 AM (13)
Jun 01, 5-6 AM (16)
Jun 01, 6-7 AM (10)
Jun 01, 7-8 AM (14)
Jun 01, 8-9 AM (46)
Jun 01, 9-10 AM (50)
Jun 01, 10-11 AM (19)
Jun 01, 11-12 PM (27)
Jun 01, 12-1 PM (49)
Jun 01, 1-2 PM (40)
Jun 01, 2-3 PM (44)
Jun 01, 3-4 PM (34)
Jun 01, 4-5 PM (54)
Jun 01, 5-6 PM (5)
Jun 01, 6-7 PM (32)
Jun 01, 7-8 PM (37)
Jun 01, 8-9 PM (9)
Jun 01, 9-10 PM (12)
Jun 01, 10-11 PM (30)
Jun 01, 11-12 AM (22)
Jun 02, 12-1 AM (13)
Jun 02, 1-2 AM (8)
Jun 02, 2-3 AM (5)
Jun 02, 3-4 AM (14)
Jun 02, 4-5 AM (10)
Jun 02, 5-6 AM (43)
Jun 02, 6-7 AM (32)
Jun 02, 7-8 AM (58)
Jun 02, 8-9 AM (65)
Jun 02, 9-10 AM (28)
Jun 02, 10-11 AM (19)
Jun 02, 11-12 PM (15)
Jun 02, 12-1 PM (47)
Jun 02, 1-2 PM (66)
Jun 02, 2-3 PM (97)
Jun 02, 3-4 PM (23)
Jun 02, 4-5 PM (17)
Jun 02, 5-6 PM (27)
Jun 02, 6-7 PM (29)
Jun 02, 7-8 PM (18)
Jun 02, 8-9 PM (9)
Jun 02, 9-10 PM (19)
Jun 02, 10-11 PM (33)
Jun 02, 11-12 AM (22)
Jun 03, 12-1 AM (13)
Jun 03, 1-2 AM (31)
Jun 03, 2-3 AM (16)
Jun 03, 3-4 AM (0)
Jun 03, 4-5 AM (7)
Jun 03, 5-6 AM (12)
Jun 03, 6-7 AM (80)
Jun 03, 7-8 AM (16)
Jun 03, 8-9 AM (24)
Jun 03, 9-10 AM (22)
Jun 03, 10-11 AM (39)
Jun 03, 11-12 PM (76)
Jun 03, 12-1 PM (93)
Jun 03, 1-2 PM (28)
Jun 03, 2-3 PM (62)
Jun 03, 3-4 PM (26)
Jun 03, 4-5 PM (24)
Jun 03, 5-6 PM (23)
Jun 03, 6-7 PM (15)
Jun 03, 7-8 PM (17)
Jun 03, 8-9 PM (19)
Jun 03, 9-10 PM (8)
Jun 03, 10-11 PM (31)
Jun 03, 11-12 AM (14)
Jun 04, 12-1 AM (12)
Jun 04, 1-2 AM (4)
Jun 04, 2-3 AM (1)
Jun 04, 3-4 AM (5)
Jun 04, 4-5 AM (1)
Jun 04, 5-6 AM (0)
Jun 04, 6-7 AM (14)
Jun 04, 7-8 AM (10)
Jun 04, 8-9 AM (11)
Jun 04, 9-10 AM (19)
Jun 04, 10-11 AM (11)
Jun 04, 11-12 PM (14)
Jun 04, 12-1 PM (53)
Jun 04, 1-2 PM (39)
Jun 04, 2-3 PM (60)
Jun 04, 3-4 PM (12)
Jun 04, 4-5 PM (4)
Jun 04, 5-6 PM (7)
Jun 04, 6-7 PM (46)
Jun 04, 7-8 PM (27)
Jun 04, 8-9 PM (4)
Jun 04, 9-10 PM (2)
Jun 04, 10-11 PM (24)
Jun 04, 11-12 AM (7)
Jun 05, 12-1 AM (6)
Jun 05, 1-2 AM (8)
Jun 05, 2-3 AM (1)
Jun 05, 3-4 AM (1)
Jun 05, 4-5 AM (1)
Jun 05, 5-6 AM (5)
Jun 05, 6-7 AM (9)
Jun 05, 7-8 AM (9)
Jun 05, 8-9 AM (8)
Jun 05, 9-10 AM (11)
Jun 05, 10-11 AM (12)
Jun 05, 11-12 PM (8)
Jun 05, 12-1 PM (52)
Jun 05, 1-2 PM (61)
Jun 05, 2-3 PM (26)
Jun 05, 3-4 PM (24)
Jun 05, 4-5 PM (17)
Jun 05, 5-6 PM (7)
Jun 05, 6-7 PM (14)
Jun 05, 7-8 PM (10)
Jun 05, 8-9 PM (6)
Jun 05, 9-10 PM (2)
Jun 05, 10-11 PM (20)
Jun 05, 11-12 AM (9)
Jun 06, 12-1 AM (6)
Jun 06, 1-2 AM (0)
Jun 06, 2-3 AM (3)
Jun 06, 3-4 AM (4)
Jun 06, 4-5 AM (0)
3,039 commits this week May 30, 2026 - Jun 06, 2026
fix(leios): exempt verified votes from record admission cap
The vote record ledger's capacity cap applied to every peer vote, so
in partially-registered lenient mode an attacker could fill it with
fabricated unverified votes (committee members without registered keys
need no valid signature) and starve subsequent verified votes -- the
only ones that feed verified stake and certificates -- stalling
certification of new endorser blocks for the duration of a flood.

Verified votes now bypass the cap: each requires a valid BLS signature
from a registered committee key and dedup bounds them to one record
per (slot, registered voter) inside the slot window, so their record
growth is protocol-bounded without the cap. The cap keeps gating
unverified peer votes, whose only effect is observed-stake visibility.
Identified by cubic on the previous commit.

The capacity regression test now fills the ledger with unverified
votes (the only kind still subject to the cap), and a new test pins
the bypass: a verified vote is admitted past a full ledger while
unverified votes remain rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Chris Guiney <[email protected]>
fix(leios): decouple vote dedup ledger from serving-store eviction
The vote manager deduplicated votes against the size-capped serving
store while tallies lived on their own TTL. A vote re-received after
its serving entry was evicted (reconnecting peers re-serve their full
log) re-counted its stake into a still-live tally and appended a
duplicate voter id to the verified set, after which BuildEbCertificate
failed on the duplicate forever and the endorser block became
permanently un-certifiable. Post-eviction equivocation was likewise
accepted as fresh, and the tally map was unbounded under fabricated
vote floods in lenient mode.

- Add a voteRecord ledger as the authoritative dedup and
  tally-accounting state, decoupled from serving-store eviction.
  Records are pruned only in lockstep with their endorser block's
  tally (TTL), by epoch at transitions, and by slot on rollbacks, so
  re-delivered votes never re-count and first-wins equivocation
  detection stays durable. The ledger is capacity-capped with
  reject-new semantics (evicting a record would reintroduce the
  re-count); locally emitted votes bypass the cap. The cap
  transitively bounds the tally map.
- Window incoming vote slots against the current (or tip) slot before
  any epoch or committee work. EpochForSlot projects future slots
  indefinitely, so fabricated far-past/future votes previously reached
  stake-snapshot database queries and bloated the committee memo. The
  bounds are provisional pending CIP-0164's L_vote. LedgerState
  satisfies the new SlotProvider directly; a nil provider disables the
  check. The local emission path shares the window so the node does
  not sign votes peers would reject.
- Document the proof-of-possession invariant on BLS aggregate
  verification and the voter registry as its trust root: rogue-key
  attacks make summed-pubkey verification forgeable without it.
- Add a vote-record gauge, slot_window/capacity reject reasons, and
  regression tests covering re-count after size eviction and TTL
  expiry, durable equivocation, capacity semantics, the slot window,
  and rollback re-votes. The re-count tests fail against the previous
  dedup logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Chris Guiney <[email protected]>
fix(leios): address review-bot findings
- ComputeCommittee now errors when the coverage target is unreachable
  (pool stake sum below sigma_c of total active stake) instead of
  returning a partial committee
- NewVoterRegistry rejects non-28-byte pool key hashes and duplicate
  entries that normalize to the same pool hash
- LoadVoteSigningKeyFile rejects oversized key files instead of
  silently truncating, and reuses keystore.CheckOpenFilePermissions
  (newly exported) so Windows gets the real DACL check instead of a
  no-op; the leios-local permission shims are removed
- VerifyVoteSignature subgroup-checks the public key for callers
  outside the package
- Vote store prunes expired entries before the dedup check so a stale
  vote id cannot block a fresh vote after TTL
- NextVotes persists the per-connection cursor only on successful
  delivery so aborted waits do not skip undelivered votes
- WithLeiosVoterPublicKeys copies the caller's map

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Chris Guiney <[email protected]>
feat(leios): implement stake-truncated committee voting and stake quorum
Implement the CIP-0164 (post cardano-foundation/CIPs#1196) stake-based
voting committee model in a new ledger/leios package:

- Committee selection: order pools by active stake descending (pool key
  hash ascending on ties), select until cumulative coverage reaches the
  CommitteeStakeCoverage (sigma_c) parameter, and assign stable voter_id
  indexes. The committee derives from the same epoch-2 stake snapshot
  cadence as Praos leader election and is memoized in memory.
- Stake quorum: votes must represent at least QuorumStakeThreshold (tau)
  of total active stake, evaluated with exact rational arithmetic. The
  tau < sigma_c invariant is revalidated wherever parameters are read.
- Votes: one uniform vote per endorser block per voter (slot_no, EB
  hash, voter_id, BLS signature). Real BLS12-381 MinSig sign/verify/
  aggregate via gnark-crypto, with lenient verification pending CIP-0164
  key registration: a static config registry (leiosVoterPublicKeys)
  supplies voter public keys, and votes from unknown voters count toward
  observed stake but never toward certificates.
- Certificates: signers bitfield plus one aggregated BLS signature
  (gouroboros LeiosEbCertificate), built only from verified votes once
  verified stake meets tau, announced via a new leios.eb_quorum event.
  Validation is exposed but not yet wired into block validation; the
  Dijkstra CDDL leios_cert slot is still an upstream placeholder.
- VoteManager: TTL-bounded in-memory vote store with per-(slot, voter)
  dedup (first vote wins on equivocation), per-connection serving
  cursors for the exactly-count LeiosVotes protocol semantics, local
  vote emission for block producers with a leiosVoteSigningKeyFile, and
  pruning on chain rollbacks and epoch transitions.

The LeiosVotes/LeiosFetch protocol handlers now delegate to the vote
manager through a LeiosVoteHandler interface, the LeiosVotes client is
started on outbound connections, and storing an endorser block triggers
local vote emission.

Fixes #2424

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Chris Guiney <[email protected]>
Leios: late-join out-of-order CertRB sweep
Add the child-before-parent handling to leiosAddBlockListener. Two ChainSync
clients can interleave their AddBlock writes, so a CertRB can enter chain
selection before the RB that announced its EB. When that happens the CertRB's
own listener pass finds no cached announcement and cannot read the EB hash to
hold it back, so the block would be selected with a closure it does not have.

A new reverse index, certRbsByAnnouncer, maps an announcer's header hash to the
CertRB children that certify the EB it announced, kept only for CertRBs whose
announcer had not been added when they arrived. recordIfOrphan files such a
CertRB there. When its announcer is later added, sweepOrphanedCertRbs reads the
entry, drops it, and holds each child back if the announced EB's closure is
still missing. The listener runs the sweep for every block, since any block may
be an announcer.

The map holds only CertRBs whose announcer has not arrived yet: the entry is
dropped as soon as the announcer is added, and once the announcer is cached its
later children take the direct path. gcPruner does not touch it.

This changes no behaviour: nothing registers the listener yet. The
ouroboros-consensus library builds under -Werror.
Leios: late-join AddBlock listener body
Add leiosAddBlockListener, the per-block bookkeeping update for the late-join
feature. ChainDB will run it synchronously, in STM scope, between writing a
block to the VolatileDB and running chain selection on it. Sharing the
transaction that adds the block closes the window an asynchronous listener would
leave: there is no moment when the block is in the VolatileDB but absent from
this state, so a closure arriving "in between" cannot strand the block.

For every block the listener records the slot and announced EB point in
headerAnnouncementsCache. For a CertRB whose certified EB closure is not yet
local, it adds the block to announcementsMap so getBlockedCertRBs holds it back.
The certified EB hash comes from the parent's cached announcement, not from the
block itself: a CertRB certifies the EB its parent announced (CIP-0164).

Two helpers carry the work: parentAnnouncement reads the parent's announced EB
hash from a cache snapshot, and blockCertRbIfClosureMissing inserts into
announcementsMap only when the closure is still missing.

This changes no behaviour: nothing registers the listener yet, so it does not
run. The ouroboros-consensus library builds under -Werror.