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 (9)
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)
Jun 06, 5-6 AM (24)
Jun 06, 6-7 AM (1)
Jun 06, 7-8 AM (2)
Jun 06, 8-9 AM (3)
Jun 06, 9-10 AM (0)
Jun 06, 10-11 AM (3)
Jun 06, 11-12 PM (6)
Jun 06, 12-1 PM (2)
Jun 06, 1-2 PM (2)
Jun 06, 2-3 PM (2)
Jun 06, 3-4 PM (18)
Jun 06, 4-5 PM (1)
Jun 06, 5-6 PM (6)
Jun 06, 6-7 PM (0)
Jun 06, 7-8 PM (6)
Jun 06, 8-9 PM (0)
Jun 06, 9-10 PM (1)
Jun 06, 10-11 PM (27)
Jun 06, 11-12 AM (9)
Jun 07, 12-1 AM (14)
Jun 07, 1-2 AM (2)
Jun 07, 2-3 AM (0)
Jun 07, 3-4 AM (0)
Jun 07, 4-5 AM (1)
Jun 07, 5-6 AM (1)
Jun 07, 6-7 AM (3)
Jun 07, 7-8 AM (0)
Jun 07, 8-9 AM (0)
Jun 07, 9-10 AM (1)
Jun 07, 10-11 AM (2)
Jun 07, 11-12 PM (2)
Jun 07, 12-1 PM (5)
Jun 07, 1-2 PM (35)
Jun 07, 2-3 PM (2)
Jun 07, 3-4 PM (4)
Jun 07, 4-5 PM (2)
Jun 07, 5-6 PM (4)
Jun 07, 6-7 PM (0)
Jun 07, 7-8 PM (0)
Jun 07, 8-9 PM (17)
Jun 07, 9-10 PM (1)
Jun 07, 10-11 PM (21)
Jun 07, 11-12 AM (9)
Jun 08, 12-1 AM (9)
Jun 08, 1-2 AM (5)
Jun 08, 2-3 AM (3)
Jun 08, 3-4 AM (4)
Jun 08, 4-5 AM (2)
Jun 08, 5-6 AM (9)
Jun 08, 6-7 AM (5)
Jun 08, 7-8 AM (25)
Jun 08, 8-9 AM (35)
Jun 08, 9-10 AM (33)
Jun 08, 10-11 AM (0)
2,920 commits this week Jun 01, 2026 - Jun 08, 2026
fix(ledger): forecast testnet stake distribution lookups
Port of lambdasistemi 36ea009c onto upstream/main's rewritten state.rs.

Generated private testnets (db-synthesizer / amaru-bootstrap) can serve
block headers from the next leader schedule before the stable ledger
has materialized that epoch's snapshot. Add an opt-in forecast on
StakeDistributionObserver::get_pool that falls back to the latest
cached distribution when the requested epoch is strictly newer; only
enabled for NetworkName::Testnet(_).

Public networks (mainnet/preprod/preview) keep the existing strict
lookup behaviour.

Signed-off-by: paolino <[email protected]>
fix(bootstrap): honor era-history sidecar when importing testnet tvar snapshots
The node-snapshot (tvar) import path derived the current era's epoch size
from the network default (86400), ignoring custom-testnet genesis. Load the
history.<slot>.<hash>.json sidecar (as the .cbor path already does via
make_era_history) and use it to interpret the snapshot, but only for
Testnet(_); public networks keep the snapshot-derived history unchanged.

Signed-off-by: paolino <[email protected]>
fix(ledger): saturate epoch subtraction in stake distribution lookups
Two sites in amaru-ledger/src/state.rs were doing unchecked Epoch
subtraction on a u64-backed Epoch, producing u64::MAX-1 (or panicking,
depending on overflow checks) when the latest snapshot or the
slot-derived current epoch was below the rewards/leader-schedule
horizon (epoch < 2):

- StakeDistributionObserver::get_pool: `current_epoch - 2` becomes
  saturating_sub(2). Headers validated very early in the chain (or
  with a custom era history that maps slots to low epoch numbers)
  no longer surface a misleading
  "no stake distribution available for pool access 18446744073709551614"
  — the error now correctly says "pool access 0" and points at a
  real missing snapshot.

- initial_stake_distributions: `latest_epoch - Epoch::from(2)` and
  `- Epoch::from(1)` become saturating_sub(2) / saturating_sub(1).
  Bundles produced by amaru-bootstrap from cold-start short-epoch
  testnets (e.g. lambdasistemi/amaru-bootstrap#34's antithesis
  reproducer) where the most_recent_snapshot is epoch 0 or 1 no
  longer hit a u64 underflow on Ledger::new; for_epoch(0) is
  exercised normally and surfaces a sensible StoreError if the
  snapshot is genuinely absent.

This is a defensive correctness improvement only — it does not change
behaviour for any chain past epoch 2. Surfaced by the new live
amaru-run consumer test in lambdasistemi/amaru-bootstrap#35.

Signed-off-by: Paolo Veronelli <[email protected]>
Signed-off-by: paolino <[email protected]>
fix(consensus): guard against ln(1-f) panic when active_slot_coeff >= 1
assert_leader_stake currently computes c = ln(1 - active_slot_coeff)
unconditionally. For genesis params with activeSlotsCoeff = 1.0
(used by the antithesis short-epoch fixture in
lambdasistemi/amaru-bootstrap and by the live consumer test in
lambdasistemi/amaru-bootstrap#35) this is ln(0), and pallas-math's
FixedDecimal::ln panics with "ln of a value in (-inf,0] is undefined".

When f = 1, every slot is leader-elected with probability 1 — the
leader-stake assertion is trivially satisfied for any pool with
non-zero relative stake. Mirroring the pre-existing zero-active-stake
guard added in b69fa13e, short-circuit to Ok before the ln call.

The math is continuous at f -> 1 (exp(x*c) with c -> -inf collapses
to 0, which is < recip_q, so the ordering is LT = Ok), so the guard
does not change behaviour for any f < 1.

Surfaced by the live amaru-run consumer test in
lambdasistemi/amaru-bootstrap#35 once
lambdasistemi/amaru#2 (saturating epoch subtraction) let header
validation reach this code path on a cold-start short-epoch chain.

Signed-off-by: Paolo Veronelli <[email protected]>
Signed-off-by: paolino <[email protected]>
fix(gov-configurator): tune node TraceOptions to stop the trace firehose
cardonnay's config left TraceOptions empty, so UseTraceDispatcher emitted
everything at Info (~43M events). Antithesis could not materialise that
volume (the 'very high output' Never property) and the sidecar's
log-presence assertions (Any p1/p2 log, cluster fork observed,
SwitchedToAFork, PeerStatusChanged, find log files) were starved.

Apply the master testnet's per-namespace severity discipline plus silence
ChainDB.ImmDbEvent chunk-validation replay spam. Governance invariants
already pass; this clears the trace-firehose cascade so the run reaches
baseline parity.
modernize builder tools page with app-store UX
Port the cardano.org/apps store UX onto /tools: gradient hero with notch, "I want to" intent chips, search + filter panel + sort, recently-added / browse-by-category / maintainer-picks carousels, all-tools list, and submit CTA. Tools render as colored-initial tiles (no icons yet) over the frozen data via the showcase adapter. tx/leaderboard features omitted (not on-chain apps). Cards link to the tool website.
praos: update gap_between_ancestor_and_replay test assertion
The test's docstring already described the desired behavior — "should
return WaitingForBlocks so the gap blocks get fetched" — but the
assertion still matched the older `OrphanCandidate` path that the test
was written to deprecate.  The behavior flipped to WaitingForBlocks
once PR #931's `praos: fetch only the frontier gap during catch-up`
landed (and stayed there under this branch's
`praos: heal non-contiguous candidate chains` commit), so the
assertion is now actually exercised — update it to match.

Assert the four fields that matter for the contiguous-range fetch:
`ancestor == block 3 hash`, `anchor_point == block 3 point` (so the
BlockFetch range pins to a real block, not Origin), `missing` carries
the gap tip, and `tip_block_no` reflects the peer's announced height.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
praos: rate-limit the chain_tree gap-bridge to stop re-fetch amplification
retry_select_chain bridges [adopted_tip -> best_tip] gated only on the
moving `gap_point`. As a live peer keeps producing, best_tip advances
every tick, so the per-block in-flight dedup never trips and the whole
range re-fetches continuously (observed ~44 blocks/s of re-fetch with the
tip frozen). Rate-limit the bridge per adopted tip (BRIDGE_COOLDOWN,
10s), reset as soon as the adopted tip advances (real progress).

(An earlier version of this commit also tried to "un-starve" the oldest
in-flight gap block on a short timer; that was reverted — it can't tell a
superseded fetch from a slow-but-in-progress one, so during normal
catch-up it re-issued the whole range and amplified block traffic ~47x.
The 15s IN_FLIGHT_TTL already re-tries genuinely stuck blocks.)

308 shared-consensus tests pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
coordinator: throttle re-intersection per address to stop fork-loop spin
A peer stuck on an unreconcilable fork re-intersects in a tight loop
(the original relay wedge spun at ~1.4 orphan/re-intersect per second).
The per-peer-id orphan cooldown in shared-consensus doesn't survive the
reconnect handovers that assign a fresh PeerId each time.

Rate-limit `NetworkCommand::ReIntersect` in the coordinator keyed by peer
*address* (stable across reconnects) with exponential backoff (1s → 30s).
All re-intersections — within a connection and across reconnect handovers
— flow through this command, so one throttle covers both. The first
attempt for an address always passes (legitimate single re-intersects are
never blocked); rapid repeats back off; a peer that goes quiet resets.

Measured on the round-robin divergent-backend repro: orphan/re-intersect
events dropped from ~26/90s (spin) to 3, and the follower makes progress
instead of freezing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
mux: RunningMux::abort must cancel the muxer/demuxer, not just the supervisor
`RunningMux::abort()` only aborted the supervisor task. The supervisor
merely *watches* the muxer (egress/writer) and demuxer (ingress/reader)
and aborts the survivor when one fails — it doesn't own the bearer. So
aborting only the supervisor left the muxer and demuxer running, holding
the TCP read/write halves open: the peer never saw EOF and only
disconnected via the 60s keepalive timeout.

Store the muxer/demuxer AbortHandles in RunningMux and cancel all three
in abort(), so tearing a connection down closes the socket promptly and
the remote observes the disconnect immediately. Surfaced while building
a server-side peer-reset behaviour, but affects every abort-driven
teardown (peer Disconnect, shutdown, supervisor cleanup).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
behaviour: add DeepReorg — deliberate deep self-reorg for chaos testing
Adds a config-driven producer-side behaviour that periodically abandons
a chain suffix and forks, so downstream followers must recover from a
deep RollBackward. Reusable as deep-reorg / reorg-resilience tooling
(and the harness for the deep-rollback recovery work).

- shared-consensus:
  - ChainTree::remove_above(block_number) — drop an abandoned suffix and
    recompute best_tip.
  - PraosState::force_rollback(depth) — re-anchor the adopted chain
    `depth` blocks back, prune the suffix, emit InjectRollback so the
    served chain mirrors it. No-op below depth/with no tip. Unit-tested.
  - Behaviour::praos_reorg(slot) -> Option<depth> hook (default None;
    composite returns first Some); LeiosState::ask_praos_reorg consults it.
  - BehaviourSpec::DeepReorg { every_slots, depth } + registry wiring +
    behaviours::DeepReorg (fires once per `every_slots`-aligned slot).

- net-node: PraosConsensus::force_rollback diffuses the rollback;
  Consensus::maybe_force_reorg consults the behaviour each slot; the main
  slot loop calls it before production so the producer forks.

Config: `[behaviour] kind="deep-reorg", every_slots=N, depth=D`.

303 shared-consensus tests pass. Verified live (producer+follower): the
behaviour fires and forks; an honest follower recovers cleanly from
single-producer deep reorgs (the relay wedge needs additional
conditions — multi-peer/reconnect — still under investigation).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
praos: heal non-contiguous candidate chains instead of looping on orphan
A passive follower could wedge permanently once at the live tip: after a
deep rollback the peer re-announces its chain, but our cached copy has a
gap (the peer fragment skipped a header, and that gap block was never
fetched because it never appeared in `missing` — it isn't in the
fragment).  `select_chain` then classified the strictly-better candidate
as a non-contiguous / fork-mismatch orphan, cleared the fragment, and
requested re-intersection — which the peer answers by rolling back to our
tip and re-announcing, looping ~1.4x/s with the tip frozen.

Primary fix (gap-fill): when a strictly-better candidate's cached chain
is non-contiguous, fetch the contiguous range [common ancestor ->
candidate tip] from the announcing peer instead of giving up.  A range
request needs only the two endpoints; the peer streams every intermediate
block, healing the gap so a later pass switches.  Genesis-rooted
ancestors still fall back to the orphan verdict (can't anchor a range at
Origin).

Secondary safety net (fork-tip prune): the periodic gap-bridge could also
fixate on an abandoned fork tip left far ahead in chain_tree after the
rollback — no connected peer offers it, so the bridge re-issued a peerless
(peer_count=0) no-op fetch forever.  `ChainTree::remove_fork_tip` drops
such an unreachable best_tip and recomputes best_tip; retry_select_chain
prunes rather than emit a peerless fetch.

Tests: select_chain_heals_noncontiguous_cached_candidate_via_range_fetch
and retry_prunes_unreachable_best_tip_instead_of_peerless_fetch (both
verified to fail without the respective change).  300 pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
praos: trust the ChainSync intersection anchor as the common ancestor
After reconnecting to a peer on a divergent fork (the round-robin
relay-backend handover), select_chain could perpetually fail to find a
common ancestor: the per-peer fragment is too short to reach the fork
point and `adopted_ancestors` may be truncated by pruning, so the
strictly-better peer is classified OrphanCandidate forever and the tip
freezes (the original relay wedge: "orphan — no common ancestor found",
looping on re-intersection).

`find_intersection` already probes back to genesis, so its anchor is the
authoritative common ancestor. Add a final fallback in select_chain: when
no ancestor is found via the fragment, trust the peer's intersection
anchor if it is a real block we hold and the resulting reorg is within k.
The bounded (<= k) reorg then proceeds via the existing
WaitingForBlocks/range-fetch path. Reorgs deeper than k are refused
(Praos finality — settled blocks). Origin/genesis anchors are excluded:
a switch sharing only genesis needs a full re-sync from block 1 that the
range fetch can't anchor at Origin, and isn't the round-robin case
(those backends share real history at the fork point).

Tests: select_chain_trusts_intersection_anchor_within_k (verified to fail
without the fallback) and select_chain_refuses_reorg_deeper_than_k. 308
shared-consensus tests pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
behaviour: add DropInboundPeers — server-side connection-reset chaos
Adds a config-driven behaviour that randomly resets accepted (inbound)
peer connections, so the remote reconnects and re-runs ChainSync
intersection from scratch. Mimics a relay that RSTs inbound peers — the
reconnect-handover trigger for deep-rollback recovery testing. Compose
with DeepReorg so each reconnect re-intersects against a reorged chain.

- shared-consensus:
  - Behaviour::drop_inbound_peers(slot) -> bool hook (default false;
    composite ORs children); LeiosState::ask_drop_inbound_peers.
  - BehaviourSpec::DropInboundPeers { probability } + registry wiring +
    behaviours::DropInboundPeers (deterministic per-(seed, slot) draw).
- net-core: NetworkCommand::DropInboundPeers; coordinator resets every
  accepted peer (ip_guard.is_some()) via PeerCommand::Disconnect (relies
  on the mux-teardown fix to close the socket promptly).
- net-node: Consensus::should_drop_inbound_peers; the slot loop issues
  the command when the behaviour fires.

Config: `[behaviour] kind="drop-inbound-peers", probability=P`.

Verified live (producer with DeepReorg + DropInboundPeers): the follower
disconnects promptly (mux error, not 60s keepalive) and reconnects each
drop, re-intersecting against the reorged chain.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
sim-core: update Praos lottery call site after main's API rename
PR #924 moved the lottery threshold from a free function
`lottery::rb_win_threshold(rate, stake)` returning an absolute count
in `[0, total_stake)` to a method
`LotteryParams::new(f).rb_win_threshold(stake, total_stake)` returning
a `[0, 2^64)` threshold (spec-faithful `φ(σ) = 1 − (1−f)^σ` scaled by
2^64).  net-rs/production.rs was updated on main, but sim-core was
missed because the merge auto-resolved here on an unrelated hunk
(PR #928's LeiosElectionInfo telemetry arm).

Switch the call to the new method form and pair it with a direct
`Rng::draw_u64` (uniform `[0, 2^64)`) instead of `draw_range(...,
total_stake)`, mirroring how net-rs/production.rs draws and compares.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
fix(tests): pin published snapshot and drop invalid github packages repo
The previous change replaced the GitHub Packages URL with
`https://maven.pkg.github.com/hyperledger-identus/cloud-agent-client/`,
but `cloud-agent-client` is a Maven artifact name, not a GitHub repo —
the actual repository is `hyperledger-identus/cloud-agent`. The Kotlin
client has also been migrated to Sonatype Central snapshots (see
branch 1604-migrate-publishing-of-the-cloud-agent-http-client-in-kotlin-
to-the-new-maven-central-endpoint), so GitHub Packages is no longer the
source for snapshots.

Additionally, the pinned version `2.1.1-0dfbcd7-SNAPSHOT` does not
exist: the short hash `0dfbcd7` does not correspond to any commit in
the repository, and no such snapshot was ever published to Sonatype
Central. The latest published snapshot is `2.1.1-e1e8be1-SNAPSHOT`
(matching commit `e1e8be1e` on main), so we pin to that.

Fixes the `Could not find org.hyperledger.identus:cloud-agent-client:
2.1.1-0dfbcd7-SNAPSHOT` failure in the integration test workflow.

Signed-off-by: Pat Losoponkul <[email protected]>
net-core: trace Leios client EB/vote activity with full hashes
Add client-side tracing to the per-peer LeiosNotify and LeiosFetch
sub-tasks so a follower's logs can be cross-referenced against a peer's
(e.g. a relay's) server-side logs:

  - leios_notify: EB offered / EB txs offered (slot + full eb_hash),
    votes received (count; per-vote slot/eb_hash/voter_id/sig at debug),
    EB announcement (header size).
  - leios_fetch: requesting EB / EB received (slot, full eb_hash,
    manifest bytes); requesting EB txs / received (requested index
    count + first indices); request-failed warnings carry the same
    fields so a disconnect is attributable to a specific EB.

EB hashes are logged in full (32 bytes) — the natural correlation key
against a relay's logs.  info level for the per-EB events, debug for
per-vote detail.

Co-Authored-By: Claude Opus 4.8 <[email protected]>