Home / Input Output / ouroboros-consensus
May 29, 12-1 AM (0)
May 29, 1-2 AM (0)
May 29, 2-3 AM (0)
May 29, 3-4 AM (0)
May 29, 4-5 AM (0)
May 29, 5-6 AM (0)
May 29, 6-7 AM (0)
May 29, 7-8 AM (0)
May 29, 8-9 AM (0)
May 29, 9-10 AM (0)
May 29, 10-11 AM (0)
May 29, 11-12 PM (0)
May 29, 12-1 PM (2)
May 29, 1-2 PM (4)
May 29, 2-3 PM (1)
May 29, 3-4 PM (3)
May 29, 4-5 PM (0)
May 29, 5-6 PM (4)
May 29, 6-7 PM (0)
May 29, 7-8 PM (0)
May 29, 8-9 PM (1)
May 29, 9-10 PM (1)
May 29, 10-11 PM (0)
May 29, 11-12 AM (0)
May 30, 12-1 AM (0)
May 30, 1-2 AM (0)
May 30, 2-3 AM (0)
May 30, 3-4 AM (0)
May 30, 4-5 AM (0)
May 30, 5-6 AM (0)
May 30, 6-7 AM (0)
May 30, 7-8 AM (0)
May 30, 8-9 AM (0)
May 30, 9-10 AM (0)
May 30, 10-11 AM (0)
May 30, 11-12 PM (0)
May 30, 12-1 PM (0)
May 30, 1-2 PM (0)
May 30, 2-3 PM (0)
May 30, 3-4 PM (0)
May 30, 4-5 PM (0)
May 30, 5-6 PM (0)
May 30, 6-7 PM (0)
May 30, 7-8 PM (0)
May 30, 8-9 PM (0)
May 30, 9-10 PM (0)
May 30, 10-11 PM (0)
May 30, 11-12 AM (0)
May 31, 12-1 AM (0)
May 31, 1-2 AM (0)
May 31, 2-3 AM (0)
May 31, 3-4 AM (0)
May 31, 4-5 AM (0)
May 31, 5-6 AM (0)
May 31, 6-7 AM (0)
May 31, 7-8 AM (0)
May 31, 8-9 AM (2)
May 31, 9-10 AM (0)
May 31, 10-11 AM (0)
May 31, 11-12 PM (0)
May 31, 12-1 PM (0)
May 31, 1-2 PM (0)
May 31, 2-3 PM (0)
May 31, 3-4 PM (0)
May 31, 4-5 PM (0)
May 31, 5-6 PM (0)
May 31, 6-7 PM (0)
May 31, 7-8 PM (0)
May 31, 8-9 PM (0)
May 31, 9-10 PM (0)
May 31, 10-11 PM (0)
May 31, 11-12 AM (0)
Jun 01, 12-1 AM (0)
Jun 01, 1-2 AM (0)
Jun 01, 2-3 AM (0)
Jun 01, 3-4 AM (0)
Jun 01, 4-5 AM (0)
Jun 01, 5-6 AM (0)
Jun 01, 6-7 AM (0)
Jun 01, 7-8 AM (0)
Jun 01, 8-9 AM (1)
Jun 01, 9-10 AM (0)
Jun 01, 10-11 AM (0)
Jun 01, 11-12 PM (1)
Jun 01, 12-1 PM (1)
Jun 01, 1-2 PM (2)
Jun 01, 2-3 PM (0)
Jun 01, 3-4 PM (0)
Jun 01, 4-5 PM (24)
Jun 01, 5-6 PM (0)
Jun 01, 6-7 PM (0)
Jun 01, 7-8 PM (0)
Jun 01, 8-9 PM (0)
Jun 01, 9-10 PM (0)
Jun 01, 10-11 PM (0)
Jun 01, 11-12 AM (0)
Jun 02, 12-1 AM (0)
Jun 02, 1-2 AM (0)
Jun 02, 2-3 AM (0)
Jun 02, 3-4 AM (0)
Jun 02, 4-5 AM (0)
Jun 02, 5-6 AM (2)
Jun 02, 6-7 AM (1)
Jun 02, 7-8 AM (23)
Jun 02, 8-9 AM (13)
Jun 02, 9-10 AM (0)
Jun 02, 10-11 AM (1)
Jun 02, 11-12 PM (1)
Jun 02, 12-1 PM (5)
Jun 02, 1-2 PM (21)
Jun 02, 2-3 PM (1)
Jun 02, 3-4 PM (0)
Jun 02, 4-5 PM (1)
Jun 02, 5-6 PM (0)
Jun 02, 6-7 PM (0)
Jun 02, 7-8 PM (0)
Jun 02, 8-9 PM (0)
Jun 02, 9-10 PM (0)
Jun 02, 10-11 PM (0)
Jun 02, 11-12 AM (0)
Jun 03, 12-1 AM (0)
Jun 03, 1-2 AM (0)
Jun 03, 2-3 AM (0)
Jun 03, 3-4 AM (0)
Jun 03, 4-5 AM (0)
Jun 03, 5-6 AM (0)
Jun 03, 6-7 AM (0)
Jun 03, 7-8 AM (2)
Jun 03, 8-9 AM (2)
Jun 03, 9-10 AM (1)
Jun 03, 10-11 AM (1)
Jun 03, 11-12 PM (0)
Jun 03, 12-1 PM (0)
Jun 03, 1-2 PM (1)
Jun 03, 2-3 PM (1)
Jun 03, 3-4 PM (0)
Jun 03, 4-5 PM (0)
Jun 03, 5-6 PM (0)
Jun 03, 6-7 PM (0)
Jun 03, 7-8 PM (0)
Jun 03, 8-9 PM (3)
Jun 03, 9-10 PM (0)
Jun 03, 10-11 PM (0)
Jun 03, 11-12 AM (0)
Jun 04, 12-1 AM (0)
Jun 04, 1-2 AM (0)
Jun 04, 2-3 AM (0)
Jun 04, 3-4 AM (0)
Jun 04, 4-5 AM (0)
Jun 04, 5-6 AM (0)
Jun 04, 6-7 AM (2)
Jun 04, 7-8 AM (1)
Jun 04, 8-9 AM (0)
Jun 04, 9-10 AM (2)
Jun 04, 10-11 AM (0)
Jun 04, 11-12 PM (1)
Jun 04, 12-1 PM (1)
Jun 04, 1-2 PM (1)
Jun 04, 2-3 PM (0)
Jun 04, 3-4 PM (0)
Jun 04, 4-5 PM (0)
Jun 04, 5-6 PM (0)
Jun 04, 6-7 PM (1)
Jun 04, 7-8 PM (0)
Jun 04, 8-9 PM (1)
Jun 04, 9-10 PM (0)
Jun 04, 10-11 PM (0)
Jun 04, 11-12 AM (0)
Jun 05, 12-1 AM (0)
137 commits this week May 29, 2026 - Jun 05, 2026
ChainDB: add registerHeaderListener
Add a new entry on the ChainDB API:

    registerHeaderListener :: (Header blk -> STM m ()) -> m ()

The caller registers an STM callback that runs on every successful
addBlockAsync add, after the block is written to the VolatileDB and
before chain selection runs on it. A subsystem that wants its own
state to track the chain database can write the update from the
listener; downstream observers then see the block on disk and the
listener's writes together.

Internals:

- New field cdbHeaderListeners :: StrictTVar m [HeaderListener m blk]
  on ChainDbEnv. HeaderListener is a newtype around the callback so
  the Generic-derived NoThunks instance on ChainDbEnv can pass over
  the function via WHNF; same pattern as FollowerHandle and
  ChainSelQueue in the same module.
- The call site in the ChainSelAddBlock arm of chainSelSync sits
  between VolatileDB.putBlock and deliverWrittenToDisk. Order:
  putBlock, atomically (runHeaderListeners hdr), deliverWrittenToDisk
  True, chainSelectionForBlock. Any external thread woken by
  deliverWrittenToDisk therefore sees the block on disk and the
  listener's writes committed in the same step.
- All registered listeners fire inside a single atomically block.
  Listener B sees listener A's writes; a throw from any listener
  aborts the joint transaction (the in-flight add then completes with
  FailedToAddBlock; the ChainSel thread survives).
- Listener bodies must be small and total. throwSTM and indefinite
  retry inside the joint transaction stall addBlockAsync processing.
- Registrants that wire up after chainSelSync has already started may
  miss blocks added in the registration window. Boot-time fencing is
  the caller's responsibility.

No caller yet; the new entry is dormant.
ChainDB: add reconsiderBlockAsync
Add a new entry on the ChainDB API:

    reconsiderBlockAsync :: RealPoint blk -> m ()

The caller asks ChainSel to re-run for a block already stored in the
VolatileDB. The block was added earlier but rejected at selection time
by an external filter; the caller invokes this entry when the condition
that rejected the block may have cleared.

Internals:

- New ChainSelMessage constructor ChainSelReconsiderBlock, enqueued on
  cdbChainSelQueue alongside the other entries.
- The new arm of chainSelSync looks the block up via getBlockComponent
  (Maybe-shaped, not the throwing getKnownBlockComponent). Nothing means
  VolatileDB garbage collection dropped the block between enqueue and
  dequeue; the arm logs IgnoreBlockOlderThanImmTip and returns. VolDB GC
  is slot-based, so a block on a stale fork is droppable as soon as its
  slot falls below the ImmutableDB tip slot.
- The point is intentionally not inserted into varChainSelPoints. The
  block is already on disk, not pending-add, so memberChainSelQueue
  (which answers "is this block pending its first selection?") does not
  include it.
- The starvation-tracing falling edge fires for ChainSelAddBlock only.
  Reconsider is not block-arrival; the constructor haddock notes the
  asymmetry.
- New trace event PoppedReconsiderBlockFromQueue on the dequeue side.

No caller yet; the new entry is dormant.
LeiosDbHandle: add a cache of completed EB closures
Add 'readCompletedClosures :: m (Set EbHash)' to 'LeiosDbHandle'.  The
handle owns a TVar; ChainSel will read it on the block-add hot path
(O(1) 'readTVarIO').

Seed at construction:
- SQLite: 'SELECT ebHashBytes FROM ebs WHERE missingTxCount IS NOT
  NULL AND missingTxCount <= 0'.  Covers both "just completed" (0)
  and "completed and notified" (-1); both states mean the closure is
  in the DB.  Run on a short-lived connection that also guarantees
  schema initialisation before any 'open'-ed connection later.
- In-memory: derive from 'imTxs' / 'imEbBodies' via the same
  predicate the insert paths use.

Update inside the existing insert paths:
- Both SQLite insert paths share a 'findAndMarkCompletedEbs' helper
  inside the BEGIN and a 'notifyAndCacheCompleted' helper after
  COMMIT.  The notify+cache step pushes the just-transitioned
  closures into the cache.
- In-memory insert paths do the same update inside their STM
  transaction, so the state mutation and the cache update are
  atomic.

'LeiosDemoLogic.msgLeiosBlock' now also emits
'TraceLeiosBlockTxsAcquired' for closures completed by a body
insert (not just tx inserts), matching the symmetry the cache update
exposes.

Cache is unbounded for now; future work caps it to a k-window with
DB query on miss.  See 'readCompletedClosures' TODO and the
late-join plan.
ResolveLeiosBlock: add headerIsCertRB
ChainSel needs a header-level query to identify candidate CertRBs whose
certified EB closure is not locally available.  Add 'headerIsCertRB ::
Header blk -> IsCertRB' to the 'ResolveLeiosBlock' class, with a
'NotCertRB' default for headers in eras that don't carry the bit (same
shape as the existing 'resolveLeiosBlock', 'resolveLeiosBlockHdr',
'headerLeiosAnnouncement' methods).

Two non-default instances:

- The Praos Dijkstra Shelley instance reads 'hbIsCertRB' off the
  header body.
- The Cardano HFC instance dispatches 'HeaderDijkstra' through to the
  Shelley instance; everything else uses the default.

Re-author of v2-clean's 'ad2cf8785' rebased onto current
'origin/leios-prototype'.  Two deviations from v2:

- Only 'headerIsCertRB' is added; v2 also added
  'headerEbAnnouncement :: Header blk -> Maybe EbAnnouncement', but
  'headerLeiosAnnouncement' (added by 31b6c0b03 on
  'origin/leios-prototype') already projects the announcement's EB hash
  and announcing slot, and the late-join cache only needs the hash
  (the announcement's size field is unused there).
- The method has a default; v2 wrote it without one to force every
  block type to spell out its stance.  Consistency with the three
  existing methods in the class wins here over the defensiveness.
Trim Leios fetch loop and block BlockFetch on staged CertRBs
Two related fixes to the issue-#890 staging plumbing:

* Drop the per-tick LeiosDb.leiosDbQueryFetchWork call from the fetch
  loop. Its own API doc declared it startup-only (O(|leios.db|)). The
  synth pipeline now only derives missing EB *body* entries +
  per-peer offerings from the staging snapshot — no DB scan, no
  tx-closure synth, no strip dance. Gated on Map.null stagedNow so
  the synth block is skipped entirely in steady state, and the
  remaining synth is short-circuited against outstanding's
  acquiredEbBodies to avoid the per-tick re-add+filter round-trip.

  Restart recovery of partial tx closures (the case
  leiosDbQueryFetchWork was working around) is deferred to a
  follow-up that primes LeiosOutstanding from the DB once at
  MVar creation. Sketch in leios-fetch-loop-sketch.md (local).

* Block the BlockFetch client thread inside the staging gate until
  the drain releases the entry, instead of returning a synthetic
  AddBlockPromise that lied about blockWrittenToDisk. The previous
  approach + the getIsFetched widening together did not suppress
  BlockFetch's redecision, causing each staged CertRB to be
  re-fetched many times during the staging window. In iosim runs
  the resulting decision-loop spin was the dominant retainer of
  SimTrace state and made prop_leios_late_join OOM at numSlots=200.

  After this change, prop_leios_late_join @ numSlots=200 runs in
  ~52s at 134 MB peak residency (was 119s, OOM at 4 GB cap).
  Restoring numSlots=200 in the test.

  Cost: head-of-line blocking on the affected peer's BlockFetch
  pipeline for the closure-fetch duration. The closure fetch runs
  on a separate LeiosFetch channel so progress is not deadlocked.
  Caveat: a peer set that empties before the closure arrives would
  leak the parked client thread — see PR open point #4 (no GC of
  staged entries); the GC pass needs to wake parked threads with a
  FailedToAddBlock verdict before evicting an entry.
Enable searching the documentation website (#2059)
Adds local, build-time search via @easyops-cn/docusaurus-search-local.
The search index is generated as static assets at build time, so it
needs no external service.

Search is only available in production builds (yarn build && yarn
serve), not in yarn start.

<img width="1404" height="776" alt="Peek 2026-06-03 11-26"
src="https://github.com/user-attachments/assets/a264ba2f-8970-4282-8cae-35988a060249"
/>
Praos header: add hbIsCertRB, canonical len=12 CBOR
Add 'hbIsCertRB' to 'HeaderBody' and mirror it on 'HeaderView' for the
CIP-0164 header bit signalling that this RB certifies a
previously-announced EB.  Thread the bit through 'mkHeader' (Praos and
TPraos; the latter ignores it) and the Shelley forge path.

Encode canonically: every header is len=12 carrying
@(IsCertRB, StrictMaybe EbAnnouncement)@.  Decode still accepts len=10
(pre-Leios) and len=11 (announcement-only) for back-compat with
existing on-disk data; new encodings never produce those shapes.
Two valid encodings for the same logical header would have made
hashing / signature over the encoded form non-canonical.

Per-header cost: one byte for the IsCertRB tag plus one for the
SNothing tag when no announcement is present.

Also add 'EncCBOR' / 'DecCBOR' instances for 'LeiosPoint', delegating
to the existing 'encodeLeiosPoint' / 'decodeLeiosPoint' helpers; new
code that consumes 'LeiosPoint' inside an 'EncCBOR'/'DecCBOR' chain
relies on them.

Rebased onto current 'origin/leios-prototype' from v2-clean's
5309e0f0f9.  Conflict resolution was non-trivial: 9050110d3 + e4c2124a5
renamed 'hbMayEbAnnouncement :: Maybe' to 'hbLeiosEbAnnouncement ::
StrictMaybe' and rewrote the encode/decode region; 22 binary goldens
regenerated from the new encoder output.
Cap late-join slot at numSlots/4 in prop_leios_late_join
The previous range allowed the late node to join as late as
numSlots-1, leaving insufficient catch-up time for the
chain-agreement assertion to hold.  numSlots/4 keeps the join inside
the first quarter of the run, so the late joiner has at least 3/4 of
the slots to catch up to the catch-up-bounded prefix.

Mirrors `dd477343a` from the leios-late-join branch.
leios late-join: wire implicit EB-closure offer in onHeaderArrival
The ChainSync client's onHeaderArrival hook now carries the
just-committed candidate fragment alongside the arrived header, and
the production assembly site fills it with an implicit-offer body.

When the arrived header is a CertRB, the body reads the parent's
EbAnnouncement from the candidate fragment and emits a closure offer
for the upstream peer that streamed the header.  The offer covers
both the body and the txs side: 'recordEbOffer' enters 'offers1' and
'missingEbBodies' (the same effect 'MsgLeiosBlockOffer' has on the
explicit-offer path), and a subsequent 'offerings' mutation enters
'offers2' (the equivalent of 'MsgLeiosBlockTxsOffer').  A peer that
has adopted a CertRB necessarily holds the full closure, so treating
the implicit offer as a closure offer is sound; without the txs side
'choosePeerTx' has no candidate peer and the closure stays
incomplete, which keeps the CertRB pending in ChainSel.

The parent lookup is fragment-only.  If the CertRB is the first
header after the candidate-fragment anchor, the parent is
unreachable from here and the offer is skipped; missing one offer
is best-effort, since every other peer streaming the same CertRB
fires its own implicit offer.  A VolatileDB fallback would require
exposing 'getLeiosFields' through the ChainDB API and is left as a
follow-up.

Synchronous exceptions in the offer call are caught and traced so a
Leios-side failure does not kill ChainSync.  The explicit-offer arm
in 'mkHandlers' does not install this catch yet; unifying the two
arms is a separate cleanup.

prop_leios_late_join now passes for the seeds that previously
failed at the chain-agreement assertion and for fresh QuickCheck
runs.
leios late-join: add onHeaderArrival hook to ChainSync DynamicEnv
A new per-peer field on DynamicEnv that fires once per header the
ChainSync client has adopted into the candidate fragment.  The STM
transaction runs alongside setCandidate and after the jumping
callback, so consumers see only adopted headers and can read the
just-committed candidate fragment atomically with the arrival event.
The transaction returns an `m ()` to run after commit; that is where
non-STM follow-up belongs (MVar writes, traces, busy-waits).

The field defaults to a no-op-returning-a-no-op at every constructor:
the production assembly in NodeToNode, both ChainSync test modules,
the peer-simulator setup, and the ChainSync-client benchmark.  No
consumer is wired up yet, so behaviour is unchanged.