Skip to content

Consensus changes to block producing chains with missing EBs; new sen… - #110

Closed
shd wants to merge 13 commits into
mainfrom
shd/t22-modeling
Closed

shd wants to merge 13 commits into
mainfrom
shd/t22-modeling

Conversation

@shd

@shd shd commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

…sors added

@shd
shd requested a balanced review from Copilot October 20, 2026 12:15
from: from.clone(),
to: to.clone(),
});
self.remove_peer(peer_id, format!("Block fetch {from}..{to} failed")).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Important: this disconnects a peer for a legitimate protocol response.

PeerEvent::BlockFetchFailed is emitted from exactly one place — peer_task.rs:437-441, the Ok(false) / NoBlocks arm. The connection is healthy; the peer simply doesn't have that range yet (it rolled back, or we asked ahead of its tip). The comment directly above (lines 914-921, unchanged by this PR) still says the opposite: "Don't remove from fragments — the peer may still have the blocks (transient failure)", and NetworkEvent::BlockFetchFailed's own doc (multi_peer/types.rs:64-69) says the app should "put it on cooldown and re-route", not that the peer is gone.

Two concrete consequences:

  1. Every NoBlocks now aborts the peer task and schedules a reconnect (remove_peer → task_handle.abort() + backoff), so a node that asks for a block slightly early churns its whole upstream set.
  2. remove_peer treats any reason not in its locally-initiated allow-list ("disconnect requested", "peer command channel full", "peer command channel closed", coordinator.rs:1326-1331) as "the peer reset us", so a peer with a PeerSharing request in flight gets its address added to peershare_hostile permanently on a single NoBlocks.

If the intent is to stop re-routing to this peer, use a cooldown on the app side (which the event already supports) rather than tearing the connection down. The same concern applies to the new LeiosBlockFetchFailed arm at line 872, though that one at least follows a real protocol error.

Suggested change
self.remove_peer(peer_id, format!("Block fetch {from}..{to} failed")).await;
}

Comment on lines +385 to 405
pub fn start_eb_fetch(&mut self, point: Point, peers: &[PeerId]) -> bool {
if self.eb_attempts.get(&point).is_some_and(|x| peers.iter().any(|p| x.contains(p))) {
// already started
return false;
}

let attempts = self.eb_attempts.entry(point).or_insert(BTreeSet::new());
attempts.append(&mut BTreeSet::from_iter(peers.iter().copied()));

true
}
pub fn available_eb_candidates(&self, point: &Point) -> Vec<PeerId> {
let eb_offers = self.eb_candidates(point);
let Some(peers) = self.eb_attempts.get(point) else {
tracing::info!("EB fetch: {point}; all available: {:?}", eb_offers);
return eb_offers;
};
let available = eb_offers.iter().filter(|x| !peers.contains(x)).copied().collect();
tracing::info!("EB fetch: {point}; peers attempted: {peers:?}, offers: {eb_offers:?}, available: {:?}", available);
available
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Important: the new attempt bookkeeping both (a) silently drops the EB-fetch dedup and (b) makes the retry path added in this PR a no-op in the common case.

Dedup is gone. start_eb_fetch no longer inserts into pending_eb_fetches, and nothing else ever does (pending_eb_fetches is now written only by finish_eb_fetch/prune_below_slot, both removals). So finish_eb_fetch is dead, state_sizes()'s pending-EB slot is permanently 0, and start_eb_fetch effectively always returns true — the guard on line 386 can only fire if eb_policy.pick returns a peer that wasn't in the candidate list, but do_eb_fetch already filtered the list through available_eb_candidates, so pick only ever returns un-attempted peers. The only remaining dedup is LeiosState::in_flight, whose TTL this PR also cut from 15 s to 5 s (leios.rs:43) — so an offer from a second peer 5 s after the first now fires a duplicate concurrent fetch of the same EB, where before the pending set blocked it.

Retry can't retry. eb_attempts is only ever cleared by forget_peer (disconnect) or prune_below_slot. Neither on_eb_fetch_failed nor on_eb_fetched clears it, and evict_stale_in_flight doesn't either. So for an EB that only one peer offered — or once every offerer has been tried once — available_eb_candidates returns empty forever and the new LeiosState::retry_eb_fetch returns Vec::new() every time. That's precisely the "block producing chains with missing EBs" case this PR is meant to fix.

Suggest keeping the pending-set insert for dedup, and clearing (or at least ageing out) the attempt entry for the peer whose fetch timed out via evict_stale_in_flight so a retry has somewhere to go. There's also no unit test for start_eb_fetch/available_eb_candidates/retry_eb_fetch, so neither behaviour is pinned.

pub fn next_block_number(&self) -> u64 {
self.adopted_tip_hash
.and_then(|h| self.chain_tree.block_number(&h))
pub fn next_block_number_after(&self, tip: &Option<[u8; 32]>) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Important: breaking public-API changes in shared-consensus with nothing in the PR description.

Per the root CLAUDE.md, net-node (Piranha) consumes this crate through a path dep, so these are source-breaking for that build:

  • PraosState::next_block_number() → next_block_number_after(&Option<[u8; 32]>) (this line). Renamed and re-parameterised; every downstream call site breaks, and the new one silently accepts a tip that isn't the adopted tip.
  • TickCtx::new gained a third positional parameter (env.rs:167) — an existing 3-arg call still type-checks shape-wise at the call site only if the caller happens to pass a &DynamicEnv in slot 2, so this can mis-bind rather than fail loudly.
  • CandidateTracker::start_eb_fetch gained a peers: &[PeerId] parameter and changed meaning (see the separate comment on fetch.rs).

The PR body and the single commit message are empty of any migration note. sim-rs was updated in-tree (sim/shared_consensus.rs), but there's no statement that net-node was built against this. Please either keep a deprecated shim for next_block_number() or spell the downstream migration out in the PR description.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A missing test-context field, exhausted EB retries, and disconnects on valid NoBlocks responses remain unresolved.

3 open issues
What changed in this PR

Adds shared-consensus helpers for handling missing endorser blocks (EBs), expands fetch recovery, and introduces behavior-tree sensor inputs.

Changes:

  • Adds certified-EB availability checks and explicit-parent block numbering.
  • Tracks EB fetch attempts and propagates fetch failures through networking.
  • Adds sensor resolution and simplifies test context initialization.

Tests and builds were not run.

File Description
sim-rs/​sim-core/​src/​sim/​shared_consensus.rs Supplies an empty sensor environment.
shared-rs/​consensus/​src/​praos.rs Adds explicit-tip numbering and exposes EB fetching.
shared-rs/​consensus/​src/​pipeline.rs Enables election debug formatting.
shared-rs/​consensus/​src/​leios.rs Adds retry helpers and extends candidate retention.
shared-rs/​consensus/​src/​fetch.rs Tracks attempted EB peers.
shared-rs/​consensus/​src/​committee.rs Adds a simulated-signature helper.
shared-rs/​consensus/​src/​chain_tree.rs Queries missing certified EBs.
shared-rs/​consensus/​src/​behaviour/​tree/​env.rs Adds sensors and default tick contexts.
shared-rs/​consensus/​src/​behaviour/​tree/​control.rs Adds a production-control query.
shared-rs/​consensus/​src/​behaviour/​tree/​config.rs Updates test contexts.
shared-rs/​consensus/​src/​behaviour/​tree/​condition.rs Supports numeric sensor references.
shared-rs/​consensus/​src/​behaviour/​tree/​behaviour.rs Updates test contexts and constructor calls.
shared-rs/​consensus/​src/​behaviour/​tree/​actions.rs Updates the action test context.
shared-rs/​consensus/​src/​behaviour/​actions/​wrong_len_cert.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​wrong_context_cert.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​withhold_txs.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​withhold_eb_announce.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​vote_flood.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​tx_flood.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​t22.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​single_bls_cert.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​rb_equivocator.rs Uses context defaults with slot overrides.
shared-rs/​consensus/​src/​behaviour/​actions/​non_member_cert.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​lie_about_eb_size.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​lazy_voter.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​fetch_flood.rs Uses default context fields.
shared-rs/​consensus/​src/​behaviour/​actions/​fake_eb.rs Uses default context fields.
shared-rs/​consensus/​src/​behaviour/​actions/​fake_announce.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​echo_to_source.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​eb_burst.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​drop_inbound.rs Uses context defaults with slot overrides.
shared-rs/​consensus/​src/​behaviour/​actions/​deep_reorg.rs Uses context defaults with slot overrides.
shared-rs/​consensus/​src/​behaviour/​actions/​cert_suppressor.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​announce_slot_skew.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​announce_size_lie.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​announce_flood.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​announce_equivocate.rs Simplifies test context setup.
shared-rs/​consensus/​src/​behaviour/​actions/​announce_dangling.rs Simplifies test context setup.
net-rs/​net-core/​src/​protocols/​leios_fetch/​mod.rs Clones the request point before sending.
net-rs/​net-core/​src/​peer/​types.rs Adds an EB fetch-failure event.
net-rs/​net-core/​src/​peer/​peer_task.rs Emits point-specific EB fetch failures.
net-rs/​net-core/​src/​multi_peer/​types.rs Exposes EB fetch failures to applications.
net-rs/​net-core/​src/​multi_peer/​coordinator.rs Forwards failures and changes peer teardown.
net-rs/​net-codec/​src/​block.rs Allows an unused counting helper.
net-rs/​net-cli/​src/​multi_follow.rs Displays EB fetch failures.

Review effort: Balanced

Comment thread net-rs/net-core/src/multi_peer/coordinator.rs Outdated
Comment on lines +131 to +132
/// Separate namespace for sensor parameters
pub sensors: &'a DynamicEnv,
tracing::info!("EB fetch: {point}; all available: {:?}", eb_offers);
return eb_offers;
};
let available = eb_offers.iter().filter(|x| !peers.contains(x)).copied().collect();
ValueRef::Sensor(name) => Err(format!(
"sensor reference sensor.{name} is not valid in this condition context"
)),
ValueRef::Sensor(_name) => Ok(TypeClass::Numeric),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: accepting every sensor.* name as Numeric turns a load-time error into a silent always-false condition, and the resolver's doc above is now stale.

type_class is the only reference check consensus-mode conditions get. With it returning Ok(Numeric) unconditionally, a typo'd or unknown sensor name no longer fails validate(); at tick time resolve_number returns None, eval_with falls through to false (lines 237-243), and the branch never fires — exactly the "fallback that turns an error into a plausible value" shape. Notably the only in-tree caller, sim-rs/sim-core/src/sim/shared_consensus.rs:612, builds let sensors = DynamicEnv::new() fresh every tick, so every sensor.* condition in a sim config evaluates false forever with no diagnostic.

Also, the doc comment on this resolver impl (lines 123-125) still reads "every other reference is None (the consensus context has no sensors or bare progress idents)", which this change contradicts — worth updating along with the ValueRef::Sensor doc.

Consider validating against the set of sensor names actually declared for the run (the way env.* is checked against env), or at minimum tracing::warn! once on an unresolved sensor reference.

Comment on lines +29 to +33
impl ControlSignal {
pub fn requires_tx_production(&self) -> bool {
self != &Self::default()
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: the name promises far more than the body delivers, and there's no caller or test.

self != &Self::default() is "any field of any of the three sub-signals is non-default". It returns true for a cert-suppressor, a lazy-voter, a drop-inbound — none of which have anything to do with tx production. A downstream caller reading the name will gate tx production on "is any behaviour active at all". If the intent is "the mempool control asks us to produce txs", scope it to the field that actually says so (e.g. self.mempool != MempoolControl::default(), or the specific flood/rate field); otherwise name it is_active(). Either way it needs a doc comment and a unit test — nothing in this repo calls it.

Comment thread shared-rs/consensus/src/committee.rs Outdated
Comment on lines +167 to +169
pub fn imitate_bls_signature(voter_id: &[u8], eb_hash: &[u8; 32], eb_slot: u64) -> Vec<u8> {
npv_eligibility_signature(voter_id, eb_hash, eb_slot)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: this is a pure alias for npv_eligibility_signature (identical signature, body is a straight delegation) with no caller anywhere in the repo and no doc comment.

Two concerns: it duplicates an existing helper rather than reusing it, and the name invites misuse — npv_eligibility_signature is explicitly documented as not BLS ("in real Cardano this would be a BLS signature requiring the pool's secret key; here it's reproducible by anyone given the same inputs — we don't model BLS", lines 153-157). A function called imitate_bls_signature with no doc of its own reads like it produces something BLS-shaped and verifiable. If a downstream caller needs this spelling, please add a doc comment carrying that caveat forward; otherwise drop it and call npv_eligibility_signature directly.

@github-actions

Copy link
Copy Markdown
Contributor

3 important, 1 nit — plus 2 more nits posted inline (6 inline comments total: 3 important, 3 nits).

  • 🔴 net-rs/net-core/src/multi_peer/coordinator.rs:928 — remove_peer on BlockFetchFailed disconnects a peer for a legitimate NoBlocks response; contradicts the comment at lines 914-921 and permanently marks the peer peershare_hostile via remove_peer's reason allow-list.
  • 🔴 shared-rs/consensus/src/fetch.rs:385-405 — start_eb_fetch no longer populates pending_eb_fetches (dedup gone, finish_eb_fetch dead, state_sizes() always 0), and eb_attempts is never cleared on failure/timeout, so the new retry_eb_fetch is a no-op once every offerer has been tried once.
  • 🔴 shared-rs/consensus/src/praos.rs:600 — breaking shared-consensus API changes (next_block_number → next_block_number_after, TickCtx::new arity, start_eb_fetch signature) with no migration note for downstream net-node.
  • 🟡 shared-rs/consensus/src/behaviour/tree/condition.rs:283 — sensor.* now validates as Numeric unconditionally; unknown names silently evaluate false, sim-rs always passes an empty sensor env, and the resolver doc at lines 123-125 is stale.
  • 🟡 shared-rs/consensus/src/behaviour/tree/control.rs:29-33 — requires_tx_production is just != default; true for any active behaviour, no caller, no test.
  • 🟡 shared-rs/consensus/src/committee.rs:167-169 — imitate_bls_signature is an uncalled alias for npv_eligibility_signature that drops its "not actually BLS" caveat.

Not posted as findings: the PR description is empty, so the only claim is the commit subject; the "missing EBs" half is implemented but undermined by the fetch.rs finding, and the "new sensors" half adds only TickCtx.sensors plumbing with no sensor defined or populated in-tree. I can't run the suite — the only substantively changed test (on_slot_prunes_candidate_tracker_below_chain_tip) checks out by hand against min_keep = 108 → prune_below_slot(8), and none of the new entry points has a test. Also: IN_FLIGHT_TTL 15 s → 5 s also governs the EB-txs fetch gate (leios.rs:1191/1201), and count_leios_tx_list gained #[allow(dead_code)] instead of being removed or wired up.

Claude review of the whole PR at 062da94

@github-actions

Copy link
Copy Markdown
Contributor

No issues found in the new commits.

Delta reviewed: deb9d78 "Fixed test compilation" — one hunk in shared-rs/consensus/src/behaviour/actions/net_flood.rs:100-106, replacing an explicit seed: 0, action_params: None with ..TickCtx::default(). The Default impl (shared-rs/consensus/src/behaviour/tree/env.rs:142) yields exactly those values plus the sensors field the literal was missing, so the fix is semantics-preserving and no assertion is weakened. Note I can't run the build, so "fixes test compilation" is checked by inspection only.

Still open from earlier reviews (unchanged by this commit):

  • net-rs/net-core/src/multi_peer/coordinator.rs:928 — peer disconnected on a legitimate NoBlocks response.
  • shared-rs/consensus/src/fetch.rs:405 — EB-fetch dedup dropped and retry path a no-op (pending_eb_fetches/eb_attempts still as reported).
  • shared-rs/consensus/src/praos.rs:600 — breaking shared-consensus public-API changes with no downstream migration note.
  • shared-rs/consensus/src/behaviour/tree/condition.rs:283 (nit), shared-rs/consensus/src/behaviour/tree/control.rs:33 (nit), shared-rs/consensus/src/committee.rs:169 (nit).

Claude review of new commits 062da94..deb9d78

@shd shd self-assigned this Oct 20, 2026
Comment thread shared-rs/consensus/src/fetch.rs Outdated
Comment thread shared-rs/consensus/src/praos.rs Outdated
Comment thread shared-rs/consensus/src/committee.rs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

0 important, 3 nits (delta review of e30cc20 only).

  • 🟡 shared-rs/consensus/src/fetch.rs:657 — the new test's comment and its ..._preserves_attempts name claim a failed peer stays blacklisted, but the assertion on the next line proves the opposite: finish_eb_fetch now clears the attempt history.
  • 🟡 shared-rs/consensus/src/praos.rs:578 — new doc example x.next_block_number_after(x.tip_hash()) doesn't compile; the parameter is &Option<[u8; 32]>.
  • 🟡 shared-rs/consensus/src/committee.rs:167 — new doc names "Piranha" (= net-node), which shared-rs/consensus/CLAUDE.md discipline rule 5 forbids; plus a "determenistic" typo.

Resolved by this commit: the BlockFetchFailed → remove_peer disconnect-on-NoBlocks, and the EB fetch dedup/retry breakage (eb_attempts folded into pending_eb_fetches; finish_eb_fetch and state_sizes().4 are live again, retry-after-timeout covered by new tests).

Still open from earlier reviews: praos.rs:606 (breaking shared-consensus public-API changes with no migration note — this commit adds the imitate_bls_signature → imitate_rb_vote_signature rename/re-parameterisation to that set); behaviour/tree/condition.rs:283; behaviour/tree/control.rs:33.

Caveat: I can't run cargo test/clippy, so the new tests were reviewed by reading only, and imitate_rb_vote_signature / on_eb_fetch_failed / next_block_number_after have no in-repo callers — their downstream net-node use is unverifiable from this checkout.

Claude review of new commits deb9d78..e30cc20

shd and others added 3 commits October 21, 2026 12:20
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

No issues found in the new commits (delta review of 9519673 only).

9519673 "Better doc comment" rewrites the doc on imitate_rb_vote_signature (shared-rs/consensus/src/committee.rs:167). Checked against the body: 48-byte Blake2b over (domain tag, voter_id, announcing_rb_hash), deterministic and unauthenticated — the doc is accurate, drops the consumer name that shared-rs/consensus/CLAUDE.md rule 5 forbids, and fixes the "determenistic" typo. The commit message is supported by the diff.

Resolved since the last review (no longer open): the BlockFetchFailed → remove_peer disconnect in net-rs/net-core/src/multi_peer/coordinator.rs:916, the lost EB-fetch dedup in shared-rs/consensus/src/fetch.rs:384-389, the imitate_bls_signature alias, and BehaviourControl::is_producing_txs.

Still open from earlier reviews (not re-posted):

  • shared-rs/consensus/src/praos.rs:606 — breaking shared-consensus public-API changes (next_block_number → next_block_number_after, TickCtx::new, CandidateTracker::start_eb_fetch) with no migration note in the PR description and no statement that net-node was built against them.
  • shared-rs/consensus/src/behaviour/tree/condition.rs:283 — sensor.* always type-checks as Numeric, so an unknown sensor name silently evaluates false instead of failing validate().
  • shared-rs/consensus/src/praos.rs:578 — doc example x.next_block_number_after(x.tip_hash()) doesn't compile; needs &.
  • shared-rs/consensus/src/fetch.rs:642 — test named ..._preserves_attempts asserts the opposite (attempts are cleared).

I can't run the test suite, so claims resting on test results are unverified.

Claude review of new commits e30cc20..9519673

@github-actions

Copy link
Copy Markdown
Contributor

No issues found in the two new commits (6bd2a39, f0688c7).

Both are doc-comment fixes that correctly apply suggestions from the previous review, verified against the code:

  • shared-rs/consensus/src/praos.rs:578 — &x.tip_hash() now matches next_block_number_after(&Option<[u8; 32]>) (praos.rs:606) vs. tip_hash() -> Option<[u8; 32]> (praos.rs:571).
  • shared-rs/consensus/src/fetch.rs:657 — comment now matches behaviour: finish_eb_fetch (fetch.rs:405) removes the pending_eb_fetches entry that holds the attempt set, so available_eb_candidates (fetch.rs:394) re-offers previously tried peers, as LeiosState::on_eb_fetch_failed (leios.rs:1265) intends.

Still open from earlier reviews (untouched by these commits):

  • 🔴 net-rs/net-core/src/multi_peer/coordinator.rs — BlockFetchFailed (NoBlocks) tears down a healthy peer connection.
  • 🔴 shared-rs/consensus/src/fetch.rs:403 — EB-fetch dedup/retry bookkeeping.
  • 🔴 shared-rs/consensus/src/praos.rs:606 — breaking shared-consensus public-API changes with no downstream migration note.
  • 🟡 shared-rs/consensus/src/behaviour/tree/condition.rs:283, shared-rs/consensus/src/behaviour/tree/control.rs:33, shared-rs/consensus/src/committee.rs (two).
  • 🟡 shared-rs/consensus/src/fetch.rs:642 — the test name ..._preserves_attempts still contradicts the comment just fixed above it (part of the earlier fetch.rs comment finding; the rename was not applied).

I can't run the test suite, so claims resting on test results are unverified here.

Claude review of new commits 9519673..f0688c7

Comment thread net-rs/net-cli/src/multi_follow.rs Outdated
Comment on lines +172 to +174
NetworkEvent::LeiosBlockFetchFailed { point } => {
println!(" leios block fetch failed: {point}");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Important: this breaks the net-rs build — the pattern no longer matches the variant.

This commit added source: PeerId to NetworkEvent::LeiosBlockFetchFailed (net-rs/net-core/src/multi_peer/types.rs:115-118), but this arm, added earlier in the same PR, still destructures { point } only. A struct-variant pattern must mention every field (or use ..), so cargo build/cargo test in net-rs/ fails here with E0027 "pattern does not mention field source". net-cli is a workspace member, so the whole workspace build fails, not just this binary.

Surfacing the peer is also the useful thing to print, since the coordinator disconnects that peer right before emitting the event (coordinator.rs:874-875):

Suggested change
NetworkEvent::LeiosBlockFetchFailed { point } => {
println!(" leios block fetch failed: {point}");
}
NetworkEvent::LeiosBlockFetchFailed { source, point } => {
println!(" leios block fetch failed: {point} (from {source})");
}

Comment on lines +421 to 427
pub fn finish_eb_fetch(&mut self, source: PeerId, point: &Point) -> bool {
if let Some(peers) = self.pending_eb_fetches.get_mut(point) {
peers.remove(&source);
return peers.is_empty();
}
true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: the per-point entry is never removed, so the pending_eb diagnostic no longer means "EB fetches in flight".

finish_eb_fetch now only removes source from the set and leaves an empty BTreeSet behind, so pending_eb_fetches.len() — slot 4 of state_sizes() (line 332), which feeds LeiosState::log_state_sizes / the per-slot telemetry adapter — counts every point ever fetched rather than the points with an outstanding request. The new tests pin that drift rather than flagging it: fetch.rs:695 asserts pending_eb_fetches[&point].is_empty() and leios.rs:2116-2121 asserts state_sizes().4 == 2 after all fetches have finished. Entries are only reclaimed by prune_below_slot, so between prunes the gauge is monotonically rising.

This also contradicts point 4 of the new doc on the field (lines 288-289): "When peer action is resolved (either EB received or EB timed out), the record is removed from pending_eb_fetches" — the record is not removed, only the peer.

Dropping the point when its set becomes empty (self.pending_eb_fetches.remove(point) before returning true) restores the gauge and doesn't change available_eb_candidates, which treats a missing entry and an empty set identically (lines 410-419); the two assertions above would need updating.

@github-actions

Copy link
Copy Markdown
Contributor

1 important, 1 nit (delta review of 6a36672 "Updated pending fetch logic").

  • 🔴 net-rs/net-cli/src/multi_follow.rs:172 — adding source: PeerId to NetworkEvent::LeiosBlockFetchFailed (net-core/src/multi_peer/types.rs:115) wasn't propagated to this match arm; the pattern omits a field, so the whole net-rs workspace fails to compile (E0027).
  • 🟡 shared-rs/consensus/src/fetch.rs:421 — finish_eb_fetch leaves empty per-point sets behind, so state_sizes().4 (the pending_eb telemetry gauge) now counts resolved points too; contradicts the new field doc at lines 288-289, and the new tests pin the drift.

Still open from earlier reviews, and extended by this commit:

  • Breaking shared-consensus public API with no migration note in the PR body (previous comment on shared-rs/consensus/src/praos.rs:606). This commit adds two more source-breaking changes for the downstream net-node path dep: LeiosState::on_eb_fetch_failed gained a source: PeerId parameter (leios.rs:1267) and NetworkEvent::LeiosBlockFetchFailed gained a field.
  • net-rs/net-core/src/multi_peer/coordinator.rs:874 still disconnects the peer on a Leios fetch failure; with the 120 s TIMEOUT_SERVER in leios_fetch, a merely slow upstream is torn down.

Behaviour I checked and did not flag: on_eb_received(None, …) no longer releases the in-flight gate, which is fine because the coordinator always sets source: Some(peer) for peer-fetched EBs (coordinator.rs:863-867) and sim-rs never populates in_flight (it doesn't call on_eb_offered). The new fetch.rs and leios.rs tests trace correctly against the implementation, but I can't run them — the compile break above means CI for net-rs cannot be green as-is.

Claude review of new commits f0688c7..6a36672

Comment thread shared-rs/consensus/src/fetch.rs Outdated
Comment thread shared-rs/consensus/src/leios.rs Outdated
Comment thread net-rs/net-core/src/multi_peer/coordinator.rs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

2 important, 1 nit (delta review of 37bb1f3).

The commit does what it says — finish_eb_fetch now drops the per-point entry once its peer set empties — but two assertions added earlier in this PR were written against the old "empty sets remain" behaviour and were not updated, so cargo test in shared-rs/consensus fails in two places:

  • 🔴 shared-rs/consensus/src/fetch.rs:702 — t.pending_eb_fetches[&point] indexes a key that line 699 just removed; BTreeMap::index panics ("key not found"), failing tracker_eb_fetch_finish_removes_only_the_source_peer. Stale comment on line 701 too.
  • 🔴 shared-rs/consensus/src/leios.rs:2121 — state_sizes().4 is now 0, not 2, after both points finalize, failing eb_fetch_last_peer_finalization_clears_in_flight. Stale comment on line 2120 too.
  • 🟡 net-rs/net-core/src/multi_peer/coordinator.rs:3925 — switching peer_id to .. leaves the peer_id field this PR added to LeiosBlockTxsReceived unasserted anywhere; the emitting call uses PeerId(7), so pinning it is free.

Verified fine: finish_eb_fetch's new control flow is otherwise sound (a missing entry and an empty set are equivalent to start_eb_fetch/available_eb_candidates), and tracker_eb_fetch_finish_empty_checks, tracker_eb_fetch_dedups_pending_peers and eb_fetch_finalization_preserves_other_pending_peers all still hold under the new semantics. The multi_follow.rs hunk fixes the E0027 build break flagged in the previous review.

I can't run tests here, so the two failures above are read from the code; both are deterministic, no-input tests. Earlier review findings outside these commits were not re-checked in delta mode.

Claude review of new commits 6a36672..37bb1f3

shd and others added 3 commits October 21, 2026 13:34
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

No new issues found in the delta (9c34f8c, one hunk in shared-rs/consensus/src/fetch.rs).

The changed assertion is correct: finish_eb_fetch drops the per-point entry once its peer set empties (shared-rs/consensus/src/fetch.rs:432), so the old pending_eb_fetches[&point].is_empty() indexing would have panicked. This resolves the earlier finding on fetch.rs:702.

Still open from previous reviews (not re-posted):

  • shared-rs/consensus/src/leios.rs:2121 — the commit message "Fixed tests" is contradicted here. eb_fetch_last_peer_finalization_clears_in_flight still asserts state_sizes().4 == 2 with the stale comment "including empty peer sets". With the same finish_eb_fetch change, p's entry is dropped on the first loop iteration (leios.rs:2097-2105, PeerId(1) was its only pending peer) and other's on leios.rs:2114, so the value is 0. The sibling test was fixed; this one was not.
  • shared-rs/consensus/src/praos.rs:606 — public API breaks in shared-consensus (next_block_number → next_block_number_after, TickCtx::new arity, start_eb_fetch signature) with still no migration note in the PR description; per root CLAUDE.md these are source-breaking for net-node via its path dep.
  • Open nits: behaviour/tree/condition.rs:283 (unknown sensor.* names silently resolve false), behaviour/tree/control.rs:33, committee.rs (duplicate helper + consumer-named doc, against shared-rs/consensus/CLAUDE.md rule 5), praos.rs doc example missing &, multi_peer/coordinator.rs:3925.

I can't run tests, so the leios.rs:2121 failure above is from reading the code path, not from a test run.

Claude review of new commits 37bb1f3..9c34f8c

Comment on lines 3925 to 3928
peer_id,
} => {
assert_eq!(peer_id, PeerId(7));
} => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Important: this match arm has two => { bodies for one pattern — net-rs no longer compiles.

The earlier suggestion was pasted in without removing the } => { that already followed it, so the arm now reads:

NetworkEvent::LeiosBlockTxsReceived { point: p, transactions, peer_id } => {
    assert_eq!(peer_id, PeerId(7));
} => {                                   // <- second body, syntax error
    assert_eq!(p, point);
    assert_eq!(transactions, vec![body2, body0]);
}

rustc rejects this at parse time (expected one of ,, }, or an expression, found =>), so cargo test in net-rs/ fails for the whole workspace, not just this test. net-cli and net-codec build too, so nothing in net-rs/ can be tested until this is fixed. The two assertions that were there before (p == point, transactions == vec![body2, body0]) are also orphaned into the dead second body and would stop running.

Merge the two bodies into one arm:

Suggested change
peer_id,
} => {
assert_eq!(peer_id, PeerId(7));
} => {
peer_id,
} => {
assert_eq!(peer_id, PeerId(7));

@github-actions

Copy link
Copy Markdown
Contributor

1 important, 0 nits (delta review of dddf09c..4ce2b88)

  • 🔴 net-rs/net-core/src/multi_peer/coordinator.rs:3928 — the LeiosBlockTxsReceived match arm now has two => { bodies for one pattern. The earlier suggestion was pasted in without deleting the } => { that followed it, so the file no longer parses and cargo test fails for the whole net-rs workspace; the two original assertions (p == point, transactions == vec![body2, body0]) are stranded in the dead second body.

The other commit in this range (dddf09c, shared-rs/consensus/src/leios.rs:2121) is correct: finish_eb_fetch drops the per-point entry once its peer set empties (fetch.rs:422-433), so both p and other are gone from pending_eb_fetches by that line and state_sizes().4 is indeed 0.

Resolved since the last review: the NoBlocks disconnect (coordinator.rs:916-931 no longer removes the peer), fetch.rs:702 (contains_key instead of the panicking index), and leios.rs:2121.

Still open from earlier reviews: the breaking shared-consensus API changes with no migration note — next_block_number() → next_block_number_after(&Option<[u8;32]>) (praos.rs:606) has no deprecated shim and net-node consumes this crate by path dep.

I can't run the test suite, so the compile failure above is read off the source; everything else about test results in this PR is unverified on my side.

Claude review of new commits 9c34f8c..4ce2b88

/// fires identically).
LeiosBlockReceived {
source: Option<PeerId>,
source: PeerId,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit: the doc three lines above now contradicts the field, and the self-produced case it describes can no longer be expressed.

Lines 105-108 still read "source is the peer it was fetched from (None for self-produced EBs, which the node feeds through the same receive path so manifest-recording fires identically)". With source: PeerId there is no None, so that sentence documents a state the type forbids. The same stale claim sits in coordinator.rs:830-832 ("Self-produced EBs never take this path (they enter via InjectLeiosBlock, source=None)").

This is more than wording. In-tree the only emitter is coordinator.rs:863, which always had a real peer_id, so nothing here breaks — but the doc is the only record of the self-produced path, and that path lives in net-node (path dep per the root CLAUDE.md). The sibling variant that consumes this tagging, NetworkCommand::RecordEbManifest, still carries source: Option<PeerId> with the same contract (types.rs:240-243: "the peer that supplied the EB body (None if self-produced); the resulting BlockTxsOffer is tagged with it so LeiosNotify ..."). If net-node's self-produced EBs were flowing through LeiosBlockReceived { source: None }, it now has to invent a PeerId for them, and feeding that invented id on to RecordEbManifest would tag a self-produced EB as supplied by a real peer — suppressing an offer that should go out. Worth confirming against the net-node build, and either way the two comments should be rewritten to match the non-optional field.

@github-actions

Copy link
Copy Markdown
Contributor

1 nit (delta review of 11bde63 — "Now peer is required in leios block received msgs").

  • 🟡 net-rs/net-core/src/multi_peer/types.rs:110 — making LeiosBlockReceived.source non-optional leaves its own doc (lines 105-108, "None for self-produced EBs … the node feeds through the same receive path") and coordinator.rs:830-832 ("source=None") describing a state the type now forbids; the self-produced EB case they document can no longer be represented, while the sibling RecordEbManifest.source keeps Option<PeerId> for exactly that distinction (types.rs:240-243).

The commit's other hunk fixes the duplicated => { match arm flagged in the previous review (coordinator.rs:3921-3932); the arm now parses and keeps all three assertions, so that finding is resolved. The in-tree emitter (coordinator.rs:863) always had a concrete peer_id, and the only other in-repo match sites use .., so nothing here fails to compile from the type change.

Previously reported, still open:

  • coordinator.rs:870-876 — PeerEvent::LeiosBlockFetchFailed still tears the peer down via remove_peer (raised as the secondary half of the earlier BlockFetchFailed comment; the Praos arm was fixed, this one wasn't).
  • shared-rs/consensus/src/behaviour/tree/condition.rs:283 — ValueRef::Sensor(_) => Ok(TypeClass::Numeric) still accepts any sensor name at load time, so an unknown name silently evaluates false.

I can't run tests in CI, so compilation and the net-node downstream build against this API change are unverified.

Claude review of new commits 4ce2b88..11bde63

@github-actions

Copy link
Copy Markdown
Contributor

No issues found in the new commit.

e31c79f only rewrites the doc comment on NetworkEvent::LeiosBlockReceived (net-rs/net-core/src/multi_peer/types.rs:105-106). The new text matches the field, which this PR narrowed to source: PeerId (types.rs:108); the deleted None-for-self-produced sentence documented a state the type now forbids. It fixes the types.rs half of previous comment on net-rs/net-core/src/multi_peer/types.rs:108.

Still open from earlier reviews:

  • net-rs/net-core/src/multi_peer/coordinator.rs:830-832 — the twin stale claim ("Self-produced EBs ... enter via InjectLeiosBlock, source=None") was not updated by this commit, and the downstream question it raises (net-node has no way to express a self-produced EB on this path, and NetworkCommand::RecordEbManifest still takes source: Option<PeerId>) is unresolved.
  • net-rs/net-core/src/multi_peer/coordinator.rs:874 — a Leios EB fetch failure still calls remove_peer, tearing down the connection (the Praos BlockFetchFailed case appears to have been dropped).
  • shared-rs/consensus/src/praos.rs:606 — next_block_number() → next_block_number_after(&Option<[u8;32]>) and the other shared-consensus signature changes are still source-breaking for net-node with no migration note in the PR description (which remains empty apart from the title).

I can't run the build or tests, so the net-rs and shared-rs/consensus suites referenced above are unverified here.

Claude review of new commits 11bde63..e31c79f

@shd

shd commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Part of PR109

@shd shd closed this Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants