Home / Input Output / ouroboros-leios
Apr 30, 6-7 PM (0)
Apr 30, 7-8 PM (6)
Apr 30, 8-9 PM (6)
Apr 30, 9-10 PM (0)
Apr 30, 10-11 PM (0)
Apr 30, 11-12 AM (1)
May 01, 12-1 AM (0)
May 01, 1-2 AM (0)
May 01, 2-3 AM (0)
May 01, 3-4 AM (0)
May 01, 4-5 AM (0)
May 01, 5-6 AM (0)
May 01, 6-7 AM (0)
May 01, 7-8 AM (0)
May 01, 8-9 AM (0)
May 01, 9-10 AM (0)
May 01, 10-11 AM (0)
May 01, 11-12 PM (0)
May 01, 12-1 PM (0)
May 01, 1-2 PM (0)
May 01, 2-3 PM (0)
May 01, 3-4 PM (0)
May 01, 4-5 PM (0)
May 01, 5-6 PM (0)
May 01, 6-7 PM (0)
May 01, 7-8 PM (0)
May 01, 8-9 PM (0)
May 01, 9-10 PM (0)
May 01, 10-11 PM (0)
May 01, 11-12 AM (0)
May 02, 12-1 AM (0)
May 02, 1-2 AM (0)
May 02, 2-3 AM (0)
May 02, 3-4 AM (0)
May 02, 4-5 AM (0)
May 02, 5-6 AM (0)
May 02, 6-7 AM (0)
May 02, 7-8 AM (0)
May 02, 8-9 AM (0)
May 02, 9-10 AM (0)
May 02, 10-11 AM (0)
May 02, 11-12 PM (0)
May 02, 12-1 PM (0)
May 02, 1-2 PM (0)
May 02, 2-3 PM (0)
May 02, 3-4 PM (0)
May 02, 4-5 PM (0)
May 02, 5-6 PM (0)
May 02, 6-7 PM (0)
May 02, 7-8 PM (0)
May 02, 8-9 PM (0)
May 02, 9-10 PM (0)
May 02, 10-11 PM (0)
May 02, 11-12 AM (0)
May 03, 12-1 AM (0)
May 03, 1-2 AM (0)
May 03, 2-3 AM (0)
May 03, 3-4 AM (0)
May 03, 4-5 AM (0)
May 03, 5-6 AM (0)
May 03, 6-7 AM (0)
May 03, 7-8 AM (0)
May 03, 8-9 AM (0)
May 03, 9-10 AM (0)
May 03, 10-11 AM (0)
May 03, 11-12 PM (0)
May 03, 12-1 PM (0)
May 03, 1-2 PM (0)
May 03, 2-3 PM (0)
May 03, 3-4 PM (0)
May 03, 4-5 PM (0)
May 03, 5-6 PM (0)
May 03, 6-7 PM (0)
May 03, 7-8 PM (0)
May 03, 8-9 PM (0)
May 03, 9-10 PM (0)
May 03, 10-11 PM (0)
May 03, 11-12 AM (0)
May 04, 12-1 AM (0)
May 04, 1-2 AM (0)
May 04, 2-3 AM (0)
May 04, 3-4 AM (0)
May 04, 4-5 AM (0)
May 04, 5-6 AM (0)
May 04, 6-7 AM (0)
May 04, 7-8 AM (0)
May 04, 8-9 AM (0)
May 04, 9-10 AM (0)
May 04, 10-11 AM (0)
May 04, 11-12 PM (0)
May 04, 12-1 PM (0)
May 04, 1-2 PM (0)
May 04, 2-3 PM (0)
May 04, 3-4 PM (0)
May 04, 4-5 PM (0)
May 04, 5-6 PM (0)
May 04, 6-7 PM (0)
May 04, 7-8 PM (0)
May 04, 8-9 PM (0)
May 04, 9-10 PM (0)
May 04, 10-11 PM (0)
May 04, 11-12 AM (0)
May 05, 12-1 AM (0)
May 05, 1-2 AM (0)
May 05, 2-3 AM (0)
May 05, 3-4 AM (0)
May 05, 4-5 AM (0)
May 05, 5-6 AM (0)
May 05, 6-7 AM (1)
May 05, 7-8 AM (0)
May 05, 8-9 AM (0)
May 05, 9-10 AM (0)
May 05, 10-11 AM (0)
May 05, 11-12 PM (0)
May 05, 12-1 PM (0)
May 05, 1-2 PM (0)
May 05, 2-3 PM (0)
May 05, 3-4 PM (0)
May 05, 4-5 PM (0)
May 05, 5-6 PM (0)
May 05, 6-7 PM (0)
May 05, 7-8 PM (0)
May 05, 8-9 PM (1)
May 05, 9-10 PM (0)
May 05, 10-11 PM (0)
May 05, 11-12 AM (0)
May 06, 12-1 AM (0)
May 06, 1-2 AM (0)
May 06, 2-3 AM (0)
May 06, 3-4 AM (0)
May 06, 4-5 AM (0)
May 06, 5-6 AM (0)
May 06, 6-7 AM (0)
May 06, 7-8 AM (0)
May 06, 8-9 AM (71)
May 06, 9-10 AM (0)
May 06, 10-11 AM (0)
May 06, 11-12 PM (0)
May 06, 12-1 PM (0)
May 06, 1-2 PM (0)
May 06, 2-3 PM (0)
May 06, 3-4 PM (1)
May 06, 4-5 PM (0)
May 06, 5-6 PM (0)
May 06, 6-7 PM (0)
May 06, 7-8 PM (0)
May 06, 8-9 PM (0)
May 06, 9-10 PM (0)
May 06, 10-11 PM (0)
May 06, 11-12 AM (0)
May 07, 12-1 AM (0)
May 07, 1-2 AM (0)
May 07, 2-3 AM (0)
May 07, 3-4 AM (0)
May 07, 4-5 AM (0)
May 07, 5-6 AM (0)
May 07, 6-7 AM (0)
May 07, 7-8 AM (118)
May 07, 8-9 AM (0)
May 07, 9-10 AM (0)
May 07, 10-11 AM (0)
May 07, 11-12 PM (0)
May 07, 12-1 PM (0)
May 07, 1-2 PM (0)
May 07, 2-3 PM (0)
May 07, 3-4 PM (0)
May 07, 4-5 PM (0)
May 07, 5-6 PM (0)
May 07, 6-7 PM (0)
205 commits this week Apr 30, 2026 - May 07, 2026
con-rs: lift Point and chain_tree
Adds two modules to con-rs:

- `types::Point` (with Display + minicbor Encode/Decode codec).  Both
  consumers must agree on the on-wire encoding of a chain point, so the
  codec lives with the type.  con-rs picks up `minicbor` as a dep.
- `chain_tree` (`ChainTree`, `ChainTreeEntry`, `is_better_tip`,
  `ChainNode`): the fork-aware tree of block headers used by Praos
  longest-chain selection.  Lifted verbatim; HashMap iterations are
  sort-stabilized at the public API boundary, so internal collection
  choice doesn't leak determinism.  Adds `Default` impl and `is_empty`
  to satisfy clippy.

Net-core now depends on con-rs and re-exports `Point` via `pub use
con_rs::Point`, so all callers continue to import from
`net_core::types`.  Net-core's `Tip`, `MAX_POINTS`, `decode_points` /
`encode_points`, and the existing tests stay where they are — they're
wire-protocol concerns.

Net-node's `chain_tree.rs` is deleted; imports in telemetry, consensus
mod, and the praos sub-layer are rewritten to `con_rs::chain_tree::*`.

Verified: 57 con-rs tests pass (was 42 — +4 Point tests, +15 chain_tree
tests now run in con-rs), 529 net-rs workspace tests pass (was 544 —
the 15 chain_tree tests moved out), cargo clippy --all-targets shows 11
warnings in net-rs (baseline) and 0 in con-rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-rs: anchor-based PeerChain for chain selection with narrow windows
Aligns PeerChain with Haskell's AnchoredFragment pattern: the
ChainSync intersection point is stored as an explicit anchor on
each peer's candidate chain. When select_chain_once's walk through
PeerChain entries fails to find a common ancestor (narrow window
after reconnection), the anchor provides a guaranteed fallback.

Changes:
- Forward NetworkEvent::IntersectionFound from coordinator to
  consensus (previously swallowed at the coordinator level)
- Add PeerChainAnchor struct and anchor field to PeerChain, set
  from IntersectionFound, persists through rollbacks
- Add anchor fallback in select_chain_once after the walk and
  prev_hash fallback both fail
- Detect gap between anchor and oldest PeerChain entry; thread
  anchor_point through WaitingForBlocks to issue_fetch so the
  fetch range starts from the anchor, filling the gap via
  BlockFetch

Three new tests: anchor_set_on_intersection_found,
anchor_used_as_fallback_ancestor, anchor_ignored_when_walk_succeeds.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: mempool-aware bitmap for LeiosBlockTxsRequest
Receiver decodes the EB manifest on LeiosBlockReceived, caches the
ordered tx_hash list per EB, and on LeiosBlockTxsOffered builds a
sparse bitmap of indices NOT present in its mempool. Consensus
cuts wire bytes proportional to mempool overlap — local production
plus prior tx dissemination already cover most txs in steady state.

Fallback when the offer arrives before our EB fetch: select_all up
to MAX_BITMAP_ENTRIES * 64 (the protocol's maximum). The server
returns whatever subset it actually has.

New helpers: production::decode_overflow_eb (pure decoder, paired
with make_overflow_eb) and Mempool::current_tx_ids (HashSet for
O(1) membership). LeiosConsensus now owns SharedMempool and an
eb_tx_hashes cache, plumbed through Consensus::new from main.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-rs: chain selection on stored blocks via chain_tree walk
Add try_stored_switch(): walk back from chain_tree's best tip to
the adopted tip and switch to the longest contiguous prefix of
cached blocks. Finds switchable chains from ANY source regardless
of which peer announced them — blocks from multiple peers that
form a contiguous path all get applied.

Called at the top of select_chain before peer-chain evaluation.
Replaces the per-peer partial switch that could only see one
peer's replay at a time.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: produced RBs advertise certified EBs in headers
When an EB election reaches quorum and enters CertEligible phase,
the next RB produced by this node includes certified_eb=true in its
header (CIP-0164 11-field extended header). Receiving nodes parse the
flag via the existing HeaderInfo Leios extension fields.

End of Leios consensus MVP: EB → votes → quorum → cert in RB header.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: bound LeiosStore by slot-window retention
LeiosStore's votes / eb_tx_hashes / blocks / block_txs maps had no
slot-based eviction — receivers accumulated every vote and every
EB manifest forever. With actual EB and voting traffic, that's
~600 vote entries per EB committee × every EB seen = ~70 MB/s of
unbounded growth on a 25-node cluster. Pre-codec-fix, broken tx
flow meant few EBs / votes flowed and the leak was hidden.

Add max_slot + retention_slots to LeiosStoreInner. Each inject_*
updates max_slot; bump_version evicts entries with slot < max_slot
- retention_slots. Default retention is 100 slots, sized for the
13-slot Linear Leios pipeline plus headroom — far smaller than
the LeiosTracker dedup window (1000), since the tracker stores
tiny offer IDs while this store holds full bodies.

new_with_retention exposes the knob for explicit configuration.
Notifications (small fixed-size enum entries used by LeiosNotify
with absolute read indices) are intentionally left growing for
now; pruning them requires reworking the read_index protocol.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-rs: allow Origin rollback for fork convergence under high churn
When nodes diverge early under p=0.2, they end up on separate forks
sharing no common blocks — the only common point is Origin (genesis).
Previously, select_chain_once refused to roll back an adopted node to
Origin, causing permanent fork sticking (observed: 3/25 nodes stuck,
628K orphan log lines per node).

Three changes in praos.rs:

1. select_chain_once: remove the adopted_tip_hash.is_none() guard on
   the genesis fallback. Genesis is a universal common ancestor; the
   peer was already filtered to be strictly better by is_better_tip.
   The entire peer chain becomes the replay set.

2. execute_switch: when ancestor is [0u8;32] (Origin), submit
   Rollback { target: Point::Origin } and reset both
   queued_validator_tip and adopted_tip_hash to None. This puts the
   node back in fresh-node state so the first replay block
   (prev_hash=None) aligns correctly in submit_for_validation.

3. handle_rolled_back: handle Point::Origin by setting
   last_validated_tip to None and sending InjectRollback. Previously
   Origin fell through the _ => return arm, silently skipping the
   chain store rollback.

Cluster testing confirms: zero orphan log lines for genesis-diverged
forks. Nodes that previously got permanently stuck now find Origin as
the common ancestor and switch to the longer chain. Remaining lag
under p=0.2 is fetch throughput under churn, not structural.

Two new tests: select_chain_accepts_genesis_ancestor_for_adopted_node,
handle_rolled_back_origin_sends_inject_rollback.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: hash-verify EB tx responses against the manifest
The wire format gives no per-body index in MsgLeiosBlockTxs, so a
partial or out-of-order server response can't be interpreted by
position alone. Each response body is now hashed with blake2b-256
and looked up in the cached manifest, restricted to the indices we
actually requested.

LeiosConsensus tracks the pending request bitmap per EB and on
match_eb_tx_response returns the verified bodies in manifest order
along with a requested count. main.rs forwards only verified bodies
to the validator and warns on partial responses, setting up the next
stage where the missing indices can be re-fetched from another peer.

Three new tests: keep-manifest-hashes-only (with bogus mixed in),
unknown-manifest passes through, pending bitmap consumed after match.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-rs: fix production building on unvalidated peers, add chain contiguity guard
Three fixes for stuck forks under p=0.2:

- tip_hash()/next_block_number() now return adopted_tip_hash instead
  of chain_tree.best_tip_hash(), preventing block production on top of
  unvalidated peer headers with incomplete chain_tree ancestry

- PeerChain::rollback_to clears entries when rolling back to the
  anchor (ChainSync intersection), preventing non-contiguous entries
  from multiple forks accumulating after a failed rollback

- select_chain_once verifies chain_tree contiguity before returning
  Switched: walks ancestors from the last replay block and checks all
  replay hashes lie on that chain and it reaches the ancestor/genesis.
  Mixed-fork replays become OrphanCandidate; genuine gaps become
  WaitingForBlocks with a fetch

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: prune Leios eb_tx_hashes and pending_eb_tx_fetches by slot age
LeiosConsensus.eb_tx_hashes (EB manifest cache) and
pending_eb_tx_fetches (per-EB requested-bitmap cache) were inserted on
every received EB but never pruned. With ~5 EBs/sec across the cluster
this map grew indefinitely. Tag each entry with the EB's announced slot
and drop entries whose age has passed pipeline expiry in on_slot. Doing
the cleanup independently of `elections.retain` covers the case where
an EB is received but the validator never produces a corresponding
election entry — without that the manifest would still leak.
net-rs: route fork switches through validator queue with rollback support
Before this commit, a fork switch synchronously sent InjectRollback +
InjectBlock commands to the coordinator, and a real ledger would never
see the rollback. Peer-fetched blocks were also handed to the validator
without checking that the ledger's current tip matched their parent, so
two competing chains would silently corrupt any real ledger state.

This commit makes consensus drive the ledger (via the Ledger actor) as
the source of truth for chain_store updates:

- `submit_for_validation(point, body, prev_hash)`: new helper that
  issues a `LedgerCommand::Rollback` before `Apply` if the validator
  queue isn't currently aimed at the block's parent, then updates
  `queued_validator_tip` and `adopted_tip_hash` eagerly so subsequent
  decisions see the post-submission view.

- `on_validation_outcome` (replaces `on_validation_complete`) switches
  on all four `LedgerOutcome` variants:
  - `Applied { point }`: mark validated, emit `InjectBlock` to the
    coordinator (the only path for non-self-produced publishes), run
    select_chain, and prune.
  - `RolledBack { target }`: emit `InjectRollback` (the only path for
    chain_store rollbacks).
  - `ApplyFailed`: log, rewind `queued_validator_tip`/`adopted_tip_hash`
    to `last_validated_tip` so the next submission realigns.
  - `RollbackFailed`: log loudly.

- `execute_switch` rewritten: submits `LedgerCommand::Rollback` (if
  needed) then a chain of `Apply` commands. The validator processes them
  in order; outcomes drive the chain_store. Deletes the old `inject_block`
  and `drain_validated_blocks` helpers (redundant now that applies are
  strictly serialized).

- `register_self_produced(point, header, body)` now takes the body and
  routes it through the validator, matching Haskell's
  `ChainDB.addBlockAsync` behaviour — no fast-path for self-produced.
  The `InjectBlock` for self-produced blocks moves from `main.rs:144`
  into the `Applied` outcome handler.

- `main.rs` consumes `LedgerOutcome` directly from the validator's
  receiver and routes each outcome through `on_validation_outcome`.
  The old synchronous `InjectBlock` send after `register_self_produced`
  is gone.

- `validation.rs` drops the `ValidationComplete` shim (and the
  `run_outcome_shim` task). `Validator::new` now returns
  `mpsc::Receiver<LedgerOutcome>` directly and exposes a `submit`
  method for `LedgerCommand`. Tests updated accordingly.

- Tests: existing tests that manually simulated `on_validation_complete`
  are rewritten to use a `drain_validator` pump helper. A new
  `stale_in_flight_eviction_allows_refetch` replaces the old
  `stale_in_flight_evicted_so_fetch_retries` (which exercised a
  degenerate out-of-order scenario that doesn't apply under strict
  ordered validation). New tests added:
  - `applied_outcome_emits_inject_block`
  - `rolled_back_outcome_emits_inject_rollback`
  - `self_produced_runs_through_validator`
  - `queued_validator_tip_tracks_submission_order`

Cluster verification: 25-node cluster runs cleanly to block 27, all
nodes within lag 0. After pausing production (jam test protocol), all
25 nodes converge at block 31 with zero lag — fork switches route
through the validator and emit InjectRollback/InjectBlock in the right
order. 67 net-node tests pass, 298 net-core tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: mempool-driven tx inclusion in RBs and overflow EBs
Add a shared Mempool accumulator that collects transactions from both
local generation and peer receipt. When producing an RB, the mempool
determines the path: if total pending bytes fit within rb_body_max_bytes,
txs go in the RB body directly; otherwise ALL txs drain into an EB
manifest (list of tx hashes) and the RB body is empty with the EB
announced in the header's announced_eb field.

- mempool.rs: Mempool struct with push/drain_all/drain_up_to, shared via
  Arc<Mutex>; tx_from_received_bytes for peer tx accumulation
- config.rs: rb_body_max_bytes (default 64KB), mempool_capacity (10K)
- production.rs: ProducedRb with optional announced_eb, make_fake_block
  encodes txs in CBOR tx_bodies map, make_overflow_eb builds content-
  addressed EB manifests [slot, [tx_hash, ...]]
- main.rs: wire mempool to generator and main loop, replace stage-boundary
  EB production with overflow-triggered path

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: split praos.rs into module directory
Mechanical refactor — no behavioral changes. Split the 2900-line
praos.rs into separate files by concern:

  praos/mod.rs        — PraosConsensus struct, public API, integration tests
  praos/peer_chain.rs — PeerChain tracking (entries, anchors, rollbacks)
  praos/selection.rs  — chain selection (select_chain_once, try_stored_switch)
  praos/fetching.rs   — fetch decisions (issue_fetch, in_flight, retry)
  praos/validation.rs — validator pipeline (apply, rollback, block received)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: route block fetches to announcing peer, serve partial ranges
Two fetch pipeline fixes for lagging nodes:

1. FetchBlockRange carries optional peer_id hint so the coordinator
   routes directly to the peer that announced the chain, bypassing
   fragment-based lookup (fragments lose points after rollbacks).

2. ChainStore::get_range returns available blocks when the requested
   `to` point isn't in the store (peer rolled back past that tip).
   Previously returned empty/NoBlocks, starving lagging nodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: Leios telemetry events + EventLog color fix
Add three telemetry events surfacing the Leios certification path:
- LeiosQuorumReached { node, eb_slot, voted_stake, voters } — per-node,
  fired when local vote tally first crosses quorum_stake_fraction × total_stake.
  Useful for quorum-propagation latency analysis (multiple per EB).
- RbCertifiedEb { node, rb_slot, eb_slot } — per-RB, fired only on the
  producing node when the emitted RB header carries certified_eb=true.
- LeiosElectionExpired { node, eb_slot, had_quorum, voted_stake, voters } —
  fired when an election is pruned past leios_dedup_window with final tally.

record_vote now returns Option<QuorumFormed> so the caller knows when
quorum first fires. LeiosConsensus buffers these in pending_telemetry,
drained by main.rs after on_slot/on_validation_outcome.

EventLog UI:
- Default unmapped-type color was #999 (3-char hex), which became invalid
  5-char #99922 after alpha concatenation, causing inconsistent rendering
  for TipAdvanced and other unmapped events. Use #999999 + complete the
  color map for all common types.
- RbCertifiedEb gets a distinctive gold-glow highlight (bigger, bold,
  thicker border, stronger bg, box shadow) since it's the moneyshot event.

Cluster-verified: 275 LeiosQuorumReached and 11 RbCertifiedEb (= 12 RBs
minus 1 first block before any cert was eligible) over 12 EBs in a 5-min
run with EveryoneVotes + tx_rate=2.0. LeiosElectionExpired fires past
the 1013-slot pipeline+dedup window (covered by unit test).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-rs: EB-triggered voting with committee selection
When an EB election enters the Voting pipeline phase (3×Δhdr slots
after announcement), committee selection determines whether this node
votes. Structured 130-180B vote bodies are injected into the network
via InjectLeiosVotes. Stage-boundary vote production removed from main.

Verified in 25-node cluster: EBs propagate, elections track pipeline
phases, votes flood the network (~1200 VotesReceived in ~10s run).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: switch net-node to jemalloc allocator
glibc's allocator holds freed pages on its freelist and returns them
to the OS lazily, inflating RSS under heavy small-allocation churn.
At 50 TPS the cluster grew to ~10 GB with glibc but ~7 GB with
jemalloc — a ~30% reduction with no other change.

tikv-jemallocator is the standard Rust binding and is already used
by other Cardano-adjacent tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
net-rs: document data structure invariants and fix convergence bugs
Document the invariants (maintained and violated) of ChainTree,
PeerChain, ChainStore, ChainFragment, and select_chain_once as doc
comments. This makes the correctness requirements explicit for
fork convergence under high block production (p=0.2).

Three bug fixes:
- ChainTree::prune_below() now recomputes best_tip if the current
  best was pruned, preventing a dangling reference.
- on_block_received() now inserts into chain_tree even when the
  header is opaque, using fallback block_no/prev_hash to avoid
  gaps that break ancestors() walks.
- FetchBlockRange routing now checks fragment.contains() for both
  endpoints (from and to), not just to, preventing misrouted
  fetches to peers that lack the range start.

Four new invariant-encoding tests for ChainTree: ancestors_stops_at_gap,
ancestors_reaches_genesis, prune_preserves/recomputes/clears_best_tip.

Known remaining issue: Origin-as-ancestor is still broken for adopted
nodes (select_chain_once rejects genesis rollback when adopted_tip_hash
is Some). Observed in cluster testing: 3/25 nodes stuck on solo forks
with adopted_ancestors_len=2 despite adopted_block_no=67.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
net-rs: producer publishes EB tx bodies to LeiosStore
The overflow EB previously injected only the manifest
([slot, [tx_hash, ...]]); peers had no way to obtain the bodies.

ProducedEb now carries the tx body blobs alongside the manifest, and
main.rs follows InjectLeiosBlock with InjectLeiosBlockTxs so the
coordinator can populate LeiosStore::block_txs on the producer side.
A new NetworkCommand::InjectLeiosBlockTxs handler routes the bodies
through the coordinator. Tests cover the producer carrying bodies in
manifest order and the coordinator delivering them into the store.

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