Skip to content

fix: marking peer as dumb on failed responses#21316

Merged
mralj merged 5 commits intomerge-train/spartanfrom
mr/mark-smart-peers-dumb
Mar 16, 2026
Merged

fix: marking peer as dumb on failed responses#21316
mralj merged 5 commits intomerge-train/spartanfrom
mr/mark-smart-peers-dumb

Conversation

@mrzeszutko
Copy link
Contributor

@mrzeszutko mrzeszutko commented Mar 10, 2026

Summary

When a peer prunes a block proposal, it can no longer serve tx requests by index. Previously, if such a peer was already marked "smart", it stayed smart indefinitely — causing repeated failed queries and eventual penalty escalation to "bad" status. This PR demotes smart peers back to dumb when they can no longer serve index-based requests, and distinguishes legitimate pruning (NOT_FOUND) from actual failures.

  • Add markPeerDumb() to IPeerCollection/PeerCollection — removes peer from the smart set
  • Add clearPeerData() to ITxMetadataCollection/MissingTxMetadataCollection — removes stale "peer has tx X" associations on demotion
  • In handleFailResponseFromPeer(): demote on both FAILURE/UNKNOWN (with penalty) and NOT_FOUND (without penalty, since pruning is a legitimate state)
  • In decideIfPeerIsSmart(): replace isBlockResponseValid() with an explicit archive root mismatch check and handleArchiveRootMismatch() which distinguishes three cases:
    • Archive roots match → continue to smart/dumb decision
    • archiveRoot is Fr.ZERO (peer pruned proposal, legitimate) → demote to dumb, no penalty
    • archiveRoot is non-zero but mismatches ours (malicious response) → penalise (LowToleranceError) + demote to dumb
  • Three new tests covering the demotion paths

Fixes A-512

@mrzeszutko mrzeszutko requested a review from deffrian March 10, 2026 15:26
// Or send us needed txs
// If block response is invalid, demote the peer back to dumb if it was smart.
// This happens when the peer pruned its proposal and can no longer serve index-based requests.
if (!this.isBlockResponseValid(response)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have NOT_FOUND check above, maybe we should do the same here? If response is invalid it doesn't mean that peer is dumb now. Maybe it's a malicious response.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor

@mralj mralj Mar 11, 2026

Choose a reason for hiding this comment

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

@deffrian @mrzeszutko let's penalise peer in case of archiveRootsMatch is false

Copy link
Contributor

Choose a reason for hiding this comment

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

@deffrian @mrzeszutko actually let me think bit more on this, not sure it's as simple as this

Copy link
Contributor

@mralj mralj Mar 12, 2026

Choose a reason for hiding this comment

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

I agree here with amp
TL;DR; if archive is not Fr.zero and there is a mismatch - we should penalise, otherwise ok.

P.S.
I agree with the code analysis, but I think the implementation might be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@mrzeszutko mrzeszutko requested review from deffrian and mralj March 12, 2026 10:23
* - Response archive is Fr.ZERO (peer pruned proposal, legitimate): marks peer dumb, returns true.
* - Non-zero archive mismatch (malicious response): penalises + marks dumb, returns true.
*/
private handleArchiveRootFromResponse(peerId: PeerId, response: BlockTxsResponse): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

Being nitpicky, this now reads weird, you have

if(this.handleArchiveRootFromResponse)

And if it returns false then it singals everytihg being ok, otherwise it's not ok.

Why not rename it to responseArchiveRootIsBad or something like that?

So it reads better and returns true if bad

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored - did not really like just changing the name as the method had some side effects apart of the mismatch check

@mrzeszutko mrzeszutko requested a review from mralj March 13, 2026 11:04
Copy link
Collaborator

@deffrian deffrian left a comment

Choose a reason for hiding this comment

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

I'm still not convinced that we should demote peers to dumb if they send us invalid response. But it's a nitpick

@mralj mralj merged commit 58de584 into merge-train/spartan Mar 16, 2026
13 checks passed
@mralj mralj deleted the mr/mark-smart-peers-dumb branch March 16, 2026 07:26
github-merge-queue bot pushed a commit that referenced this pull request Mar 16, 2026
BEGIN_COMMIT_OVERRIDE
feat: add ETHEREUM_HTTP_TIMEOUT_MS env var for viem HTTP transport
(#20919)
fix(archiver): filter tagged log queries by block number (#21388)
fix(node): handle slot zero in getL2ToL1Messages (#21386)
feat(sequencer): redistribute checkpoint budget evenly across remaining
blocks (#21378)
fix: fall back to package.json for CLI version detection (#21382)
chore: Removed multiplier config (#21412)
chore: Removed default snapshot url config (#21413)
chore: Read tx filestores from network config (#21416)
fix(node): check world state against requested block hash (#21385)
feat(p2p): use l2 priority fee only for tx priority (#21420)
feat(p2p): reject and evict txs with insufficient max fee per gas
(#21281)
revert "feat(p2p): reject and evict txs with insufficient max fee per
gas (#21281)" (#21432)
chore: Reduce log spam (#21436)
fix(tx): reject txs with invalid setup when unprotecting (#21224)
fix: orchestrator enqueue yield (#21286)
chore(builder): check archive tree next leaf index during block building
(#21457)
fix: scenario deployment (#21428)
chore: add claude skill to read network-logs (#21495)
chore: update claude network-logs skill (#21523)
feat(rpc): add package version to RPC response headers (#21526)
chore(prover): silence "epoch to prove" debug logs (#21527)
chore(sequencer): do not log blob data (#21530)
fix: dependabot alerts (#21531)
docs(p2p): nicer READMEs (#21456)
fix(archiver): guard getL1ToL2Messages against incomplete message sync
(#21494)
fix(sequencer): await syncing proposed block to archiver (#21554)
feat(ethereum): check VK tree root and protocol contracts hash in rollup
compatibility (#21537)
fix: marking peer as dumb on failed responses (#21316)
fix(kv-store): make LMDB clear and drop operations atomic across
sub-databases (#21539)
feat(world-state): add blockHash verification to syncImmediate (#21556)
chore(monitor): print out l2 fees components (#21559)
chore: rm faucet (#21538)
chore: remove old merkle trees (#21577)
feat: Implement commit all and revert all for world state checkpoints
(#21532)
chore: skip flaky browser acir tests in CI (#21596)
fix: Better detection for epoch prune (#21478)
chore: logging (#21604)
fix: Don't update state if we failed to execute sufficient transactions
(#21443)
END_COMMIT_OVERRIDE
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.

3 participants