May 20, 6-7 PM (17)
May 20, 7-8 PM (23)
May 20, 8-9 PM (15)
May 20, 9-10 PM (5)
May 20, 10-11 PM (34)
May 20, 11-12 AM (16)
May 21, 12-1 AM (16)
May 21, 1-2 AM (9)
May 21, 2-3 AM (11)
May 21, 3-4 AM (7)
May 21, 4-5 AM (4)
May 21, 5-6 AM (27)
May 21, 6-7 AM (14)
May 21, 7-8 AM (22)
May 21, 8-9 AM (34)
May 21, 9-10 AM (45)
May 21, 10-11 AM (37)
May 21, 11-12 PM (27)
May 21, 12-1 PM (63)
May 21, 1-2 PM (68)
May 21, 2-3 PM (60)
May 21, 3-4 PM (53)
May 21, 4-5 PM (20)
May 21, 5-6 PM (27)
May 21, 6-7 PM (27)
May 21, 7-8 PM (25)
May 21, 8-9 PM (23)
May 21, 9-10 PM (3)
May 21, 10-11 PM (29)
May 21, 11-12 AM (10)
May 22, 12-1 AM (16)
May 22, 1-2 AM (6)
May 22, 2-3 AM (8)
May 22, 3-4 AM (4)
May 22, 4-5 AM (11)
May 22, 5-6 AM (10)
May 22, 6-7 AM (21)
May 22, 7-8 AM (13)
May 22, 8-9 AM (38)
May 22, 9-10 AM (12)
May 22, 10-11 AM (18)
May 22, 11-12 PM (25)
May 22, 12-1 PM (24)
May 22, 1-2 PM (34)
May 22, 2-3 PM (56)
May 22, 3-4 PM (13)
May 22, 4-5 PM (29)
May 22, 5-6 PM (13)
May 22, 6-7 PM (19)
May 22, 7-8 PM (20)
May 22, 8-9 PM (12)
May 22, 9-10 PM (12)
May 22, 10-11 PM (41)
May 22, 11-12 AM (12)
May 23, 12-1 AM (9)
May 23, 1-2 AM (0)
May 23, 2-3 AM (3)
May 23, 3-4 AM (1)
May 23, 4-5 AM (1)
May 23, 5-6 AM (4)
May 23, 6-7 AM (12)
May 23, 7-8 AM (1)
May 23, 8-9 AM (3)
May 23, 9-10 AM (1)
May 23, 10-11 AM (1)
May 23, 11-12 PM (5)
May 23, 12-1 PM (1)
May 23, 1-2 PM (6)
May 23, 2-3 PM (5)
May 23, 3-4 PM (5)
May 23, 4-5 PM (4)
May 23, 5-6 PM (0)
May 23, 6-7 PM (3)
May 23, 7-8 PM (23)
May 23, 8-9 PM (1)
May 23, 9-10 PM (9)
May 23, 10-11 PM (21)
May 23, 11-12 AM (27)
May 24, 12-1 AM (9)
May 24, 1-2 AM (0)
May 24, 2-3 AM (1)
May 24, 3-4 AM (1)
May 24, 4-5 AM (0)
May 24, 5-6 AM (3)
May 24, 6-7 AM (1)
May 24, 7-8 AM (2)
May 24, 8-9 AM (2)
May 24, 9-10 AM (4)
May 24, 10-11 AM (4)
May 24, 11-12 PM (1)
May 24, 12-1 PM (7)
May 24, 1-2 PM (46)
May 24, 2-3 PM (5)
May 24, 3-4 PM (3)
May 24, 4-5 PM (18)
May 24, 5-6 PM (2)
May 24, 6-7 PM (4)
May 24, 7-8 PM (13)
May 24, 8-9 PM (10)
May 24, 9-10 PM (15)
May 24, 10-11 PM (33)
May 24, 11-12 AM (42)
May 25, 12-1 AM (9)
May 25, 1-2 AM (4)
May 25, 2-3 AM (6)
May 25, 3-4 AM (1)
May 25, 4-5 AM (6)
May 25, 5-6 AM (14)
May 25, 6-7 AM (17)
May 25, 7-8 AM (17)
May 25, 8-9 AM (32)
May 25, 9-10 AM (43)
May 25, 10-11 AM (64)
May 25, 11-12 PM (33)
May 25, 12-1 PM (43)
May 25, 1-2 PM (40)
May 25, 2-3 PM (20)
May 25, 3-4 PM (27)
May 25, 4-5 PM (16)
May 25, 5-6 PM (6)
May 25, 6-7 PM (7)
May 25, 7-8 PM (11)
May 25, 8-9 PM (12)
May 25, 9-10 PM (16)
May 25, 10-11 PM (43)
May 25, 11-12 AM (25)
May 26, 12-1 AM (11)
May 26, 1-2 AM (11)
May 26, 2-3 AM (8)
May 26, 3-4 AM (11)
May 26, 4-5 AM (6)
May 26, 5-6 AM (9)
May 26, 6-7 AM (26)
May 26, 7-8 AM (43)
May 26, 8-9 AM (39)
May 26, 9-10 AM (42)
May 26, 10-11 AM (45)
May 26, 11-12 PM (59)
May 26, 12-1 PM (34)
May 26, 1-2 PM (50)
May 26, 2-3 PM (50)
May 26, 3-4 PM (18)
May 26, 4-5 PM (20)
May 26, 5-6 PM (13)
May 26, 6-7 PM (20)
May 26, 7-8 PM (12)
May 26, 8-9 PM (15)
May 26, 9-10 PM (15)
May 26, 10-11 PM (35)
May 26, 11-12 AM (30)
May 27, 12-1 AM (16)
May 27, 1-2 AM (8)
May 27, 2-3 AM (9)
May 27, 3-4 AM (5)
May 27, 4-5 AM (32)
May 27, 5-6 AM (9)
May 27, 6-7 AM (49)
May 27, 7-8 AM (63)
May 27, 8-9 AM (37)
May 27, 9-10 AM (72)
May 27, 10-11 AM (83)
May 27, 11-12 PM (30)
May 27, 12-1 PM (50)
May 27, 1-2 PM (35)
May 27, 2-3 PM (52)
May 27, 3-4 PM (36)
May 27, 4-5 PM (6)
May 27, 5-6 PM (6)
May 27, 6-7 PM (0)
3,342 commits this week May 20, 2026 - May 27, 2026
fix: explicit rewrite for /dashboard-api/ index
The `/dashboard-api/:path*` pattern alone doesn't match the bare
trailing-slash request (Vercel's path matcher treats `:path*` as
"one or more segments" in practice, not zero-or-more), so visiting
`/dashboard-api/` produced a Vercel NOT_FOUND. Adding an explicit
rewrite for the index path makes the root work; the wildcard rule
continues to handle nested paths (`/openapi.yaml`, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
feat: serve dashboard-api docs at /dashboard-api/
Adds a Vercel rewrite forwarding `docs.blockfrost.io/dashboard-api/*`
to the standalone dashboard-api docs project
(https://blockfrost-dashboard-api-docs.vercel.app), and a 308 redirect
from the bare `/dashboard-api` path to the trailing-slash form —
matching the existing `/midnight` pattern.

The dashboard-api spec + docs live in the blockfrost-dashboard-api repo
and deploy independently. This repo just proxies the path under the
shared `docs.blockfrost.io` host.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-core: front-prune LeiosStore notifications alongside slot eviction
`LeiosStore::notifications: Vec<LeiosNotification>` was never pruned.
Every `inject_block` / `inject_block_txs` / `inject_votes` pushed an
entry that lived forever, even though the blocks/votes those entries
point at get slot-window-evicted by the same `bump_version` call.
Over a long-running cluster the vec grows monotonically with the
total inject rate.

Switch storage to `VecDeque` and add a `notifications_pruned_count`
so logical (caller-facing) cursors stay monotonic across pruning.
At slot-window eviction, front-prune notifications whose every
referenced slot is below the cutoff — those refer to data the store
no longer holds, so re-sending them to a subscriber would be a
wasted round trip.  Stop at the first non-evictable front entry:
notifications arrive in roughly slot order, so the leak past the
cutoff is bounded by out-of-order arrivals (next bump catches up).

`notifications_after` now takes `&mut usize`.  Callers track a
monotonic logical cursor; if it lags the prune frontier the call
bumps it forward so subsequent `*after += 1` increments stay aligned
with the items actually consumed.  `notification_count` reports the
all-time logical total.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-node, shared-consensus: demote per-tx / per-event spam to debug
Three info! lines fire once per item under steady cluster load and
together accounted for ~600K of the ~660K info-level log lines in a
27-minute test run, dominating disk usage:

  - `transaction received` (net-node)         366K lines
  - `network event` (net-node, default arm)   155K lines
  - `mempool: evicting oldest tx` (shared)     71K lines

At a 1 tx/s/node generation rate with a 10K-cap mempool, the
eviction line fires on every admit once the cap is reached.  At
RUST_LOG=info on a 25-node cluster these saturate disk in roughly
half a day.

None of the three is useful at info: the per-tx and per-event lines
are item-level traces (debug territory) and eviction at cap is a
steady-state condition, not a notable event.  Periodic
`mempool state sizes` / `praos state sizes` / `leios_store: stats`
lines remain at info — those carry the diagnostic signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-core: BlockFetch demuxer cap uses INGRESS_LIMIT, not per-message cap
The demuxer enforces `set_ingress_limit` as a *buffer* cap on the
per-protocol ingress queue, not as a per-message cap.  Under server
pipelining (`MsgStartBatch` immediately followed by `MsgBlock`) both
segments can land in the buffer before the codec processes
`MsgStartBatch` and bumps the limit for `StStreaming`.  With the
prior per-state caps that race manifested two ways:

  1. `StBusy` at `SIZE_LIMIT_SMALL = 65_535` rejected the pipelined
     block body outright (a 65K+ block was already enough to trip
     it).
  2. Even with `StBusy` raised to `SIZE_LIMIT_STREAMING = 2.5 MB`,
     a real Praos block body — particularly the post-EB-overflow
     fallback path where txs the EB couldn't carry get inlined into
     the RB — can legitimately exceed 2.5 MB.  Overnight one landed
     at 2,506,268 bytes and tripped the new cap, cascading SDU
     timeouts and freezing the cluster.

The spec defines `INGRESS_LIMIT = 230 MB` as the per-protocol
ingress buffer cap exactly for this case.  Use it for both `StBusy`
and `StStreaming`.  Spec per-message rejection at
`SIZE_LIMIT_SMALL` / `SIZE_LIMIT_STREAMING` belongs in the codec at
decode time (not yet wired); the framework's `size_limit` callback
controls buffer sizing only.

`StIdle` keeps `SIZE_LIMIT_SMALL` — the client never receives in
that state, so the tighter cap stands.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-core: enforce per-message size limits at codec decode time
Two distinct size guards on the receive path are now correctly
layered:

1. **Demuxer buffer cap** (DoS protection): fixed at protocol
   registration via `ProtocolConfig::ingress_limit`, never narrowed
   at runtime.  Sized to bound runaway accumulation when a protocol
   consumer falls behind.

2. **Codec per-message cap** (protocol conformance): `Runner::recv`
   passes `P::size_limit(state)` to `CodecRecv::recv`; after a
   CBOR value decodes, the consumed-bytes count is checked against
   the per-state spec limit and `MuxError::MessageTooLarge` is
   returned (and the connection torn down) for spec-violating peers.

The previous design conflated these — `Runner` overrode the demuxer's
buffer cap with the current state's per-message cap on every
transition, so a fast peer pipelining `MsgStartBatch` + `MsgBlock`
into one TCP read could legitimately overflow before the local
runner advanced the state.  Reverts `BlockFetch::size_limit` to spec
(`SIZE_LIMIT_SMALL` for StIdle/StBusy, `SIZE_LIMIT_STREAMING` for
StStreaming); the values are now actually enforced at decode time
rather than misused as buffer caps.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
shared-consensus: O(log P) idle peek via per-peer "unannounced" set
`MempoolState::peek_unannounced_for_peer` used to scan the entire
`txs` queue on every call, doing a `BTreeSet<TxId>::contains` check
per element to find the few ids the peer hadn't yet been told about.
Under steady TxSubmission pull traffic with a caught-up peer that's
`O(N · log A · 32)` per poll for no useful work — the dominant
mempool-CPU symptom under cluster load.

Flip the polarity: keep a per-peer "still owed to this peer"
`BTreeSet<TxId>` that's the inverse partition of `peer_advertised`
over the current mempool.  Lazily seed it from `tx_index` on first
peer-facing call; admit fan-out inserts the new id into every known
peer's owed set; pruning (`drain_*`, `on_block_applied`, capacity
eviction) drops the id from both sides; `forget_peer` clears both.

The hot path becomes: look up the peer's owed set (`O(log P)`),
return immediately if empty, otherwise drain up to `max_count` and
resolve bodies.  The remaining `for tx in &self.txs` scan only runs
when there's actually unannounced work — the empty-poll case (which
dominates under steady-state cluster traffic) returns without
touching the queue.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
[Leios prototype] Implement NTC chainsync (#2050)
Dijkstra cert blocks are deserialised, resolved (txs spliced from the
announcer's EB), and reencoded. Relies on the protocol invariant that a
cert block always immediately follows an announcer.

Added a new `resolveLeiosBlockHdr` rather than overloading the existing
`resolveLeiosBlock`, to avoid conflicts

No integration was needed in network/node, except srps.
tcp-model: guard config footguns surfaced by Copilot review
Reject mss_bytes == 0 and loss_prob_per_segment outside [0, 1] at the
YAML override layer, with a matching early-return in msg_loss_prob so
the library can't be panicked from configuration. Add a debug_assert
when add_edge sees a tcp_envelope but the rng_oracle hasn't been wired.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
sim-core: thread tcp-envelope through the sequential/turbo engine
sequential.rs builds Connections directly (it doesn't route through
NetworkCoordinator), so the envelope wiring added earlier was only
reaching the actor engine. Fix: construct an EnvelopeWiring at link-
build time whenever `LinkConfiguration.tcp_envelope` is `Some`, using
the same `Rng::new(config.seed)` stateless oracle already used by other
sequential-engine machinery.

Caught by a NA,0.200 / top-stake-fraction / 750n sanity run with
loss-prob-per-segment 0.01: the turbo-engine output was byte-identical
to baseline because the envelope cfg was being discarded at the
Connection::new call site.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>