fix(zebrad): score inbound gossip block failures via RouterError downcast - #11228
fix(zebrad): score inbound gossip block failures via RouterError downcast#11228natalieesk wants to merge 1 commit into
Conversation
…cast The inbound gossip cleanup path downcast completed block-download errors to `VerifyBlockError` to extract a peer misbehavior score. But the inbound block verifier is the consensus router, which returns `RouterError`, so the downcast failed for normal router failures and scoring was silently skipped: a peer serving a score-100 invalid block (e.g. `InvalidDifficulty`) is banned when it arrives via sync, but went unpenalised through inbound gossip. Extract the extraction into `block_download_misbehavior_score`, which tries a `RouterError` downcast first and falls back to `VerifyBlockError`, mirroring the sync download path. Unit-tests cover all three cases (RouterError, VerifyBlockError, unrelated error).
Analyzed one file, diff |
Merge Protections🟠 1 of 1 protections blocking · waiting on 🕒 schedule
🟠 ❄️ 6.3.0 release [Scheduled Freeze]Waiting for
This freeze has no end date and must be removed manually.A freeze on the repository is scheduled for the following reason: 6.3.0 release
|
Summary
Closes #10616.
zebrad/src/components/inbound.rs: the inbound gossip cleanup path inpoll_readynow scores a completed block-download failure by trying aRouterErrordowncast first, falling back toVerifyBlockError. The extraction moves into ablock_download_misbehavior_scorehelper.CHANGELOG.md: Security entry.zebrad/src/components/inbound/tests.rs.Approach & Key Decisions
The inbound block verifier is the consensus router (
inbound.rs:BoxService<…, RouterError>), so a verification failure arriving through gossip boxes aRouterError. The old code downcast that box only toVerifyBlockError, which never matches aRouterError, so scoring was silently skipped — a peer serving a score-100 invalid block was banned when the block came via sync but not via gossip. The sync path (sync/downloads.rs:569,sync.rs:1194) already works withRouterErrorandRouterError::misbehavior_score(); this change gives inbound the same behavior.The scoring logic is pulled into
block_download_misbehavior_score(err: BoxError) -> u32so it can be unit-tested directly rather than through the fullInboundservice. TheVerifyBlockErrorbranch is kept as a defensive fallback (not reached on the current router path).RouterError::misbehavior_score()delegates to the wrapped block error, so the scores match the sync path exactly. Only errors carryingSome(advertiser_addr)reach this code; timeouts and other non-attributable failures downcast to neither type and score 0, as before.Testing & Verification
Three unit tests in
inbound/tests.rs, usingRouterError::from(VerifyBlockError::Subsidy(SubsidyError::NoCoinbase))(score 100 — the sameRouterError::Block { .. }shape the router emits):router_error_yields_misbehavior_score— fails before the fix (returns 0), passes after (100). This is the regression.verify_block_error_yields_misbehavior_score— fallback path returns 100.unrelated_error_scores_zero— an unrelated boxed error returns 0.cargo fmtandcargo clippy -p zebrad --lib --all-featuresare clean.Risk & Impact
Behavior change confined to peer misbehavior scoring: invalid blocks gossiped by a peer now raise its score (and can lead to a ban), matching what already happens on the sync path. No consensus, state-format, RPC, or config change; no DB format bump. A misbehavior score is advisory input to ban logic, so the blast radius is limited to peer connection management.
Changelog
Added under
### SecurityinCHANGELOG.md: inbound gossiped invalid blocks now increase the sending peer's misbehavior score.AI Disclosure
Claude (Claude Code) wrote the fix, the helper refactor, and the tests.
PR Checklist
CHANGELOG.mdupdated (Security)