Skip to content

feat(p2p/sensor): validate block signer before rebroadcast#946

Open
minhd-vu wants to merge 5 commits into
mainfrom
feat/sensor-validate-block-signer
Open

feat(p2p/sensor): validate block signer before rebroadcast#946
minhd-vu wants to merge 5 commits into
mainfrom
feat/sensor-validate-block-signer

Conversation

@minhd-vu

@minhd-vu minhd-vu commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Description

When block rebroadcasting is enabled, the sensor now only rebroadcasts blocks whose recovered signer is in the current Heimdall validator set. Blocks from unknown signers are still persisted and their bodies are still requested; they are simply not propagated to peers.

  • Add ValidatorSet (p2p/validators.go): fetches /stake/validators-set from Heimdall, blocking initial load + periodic refresh, IsAuthorized.
  • Add Conns.IsKnownSigner to gate rebroadcast by recovered signer.
  • Gate handleNewBlock; defer block-hash rebroadcast from handleNewBlockHashes to handleBlockHeaders so the fetched header can be validated first (body requests stay ungated).
  • Consolidate util.Ecrecover to take *types.Header.
  • Add --heimdall-url and --validator-set-refresh sensor flags.

Jira / Linear Tickets

Testing

  • 👀
  • Deployed Amoy
  • Deployed Mainnet

When block rebroadcasting is enabled, the sensor now only rebroadcasts
blocks whose recovered signer is in the current Heimdall validator set.
Blocks from unknown signers are still persisted and their bodies are
still requested; they are simply not propagated to peers.

- Add ValidatorSet (p2p/validators.go): fetches /stake/validators-set
  from Heimdall, blocking initial load + periodic refresh, IsAuthorized.
- Add Conns.IsKnownSigner to gate rebroadcast by recovered signer.
- Gate handleNewBlock; defer block-hash rebroadcast from
  handleNewBlockHashes to handleBlockHeaders so the fetched header can
  be validated first (body requests stay ungated).
- Consolidate util.Ecrecover to take *types.Header.
- Add --heimdall-url and --validator-set-refresh sensor flags.
@minhd-vu minhd-vu force-pushed the feat/sensor-validate-block-signer branch from b028f73 to 60d7cc3 Compare June 29, 2026 20:38
minhd-vu added 3 commits June 30, 2026 10:02
Refine the block signer validation feature:

- Rename SignerSet -> ValidatorSet and IsAuthorized -> HasSigner; add
  RecoverSigner which returns the recovered address so skip paths can log it.
- Add --validate-block-signer (default true) to toggle signer validation.
- Add --cache-only-validated-blocks (default true): unknown-signer blocks are
  still recorded to the database but are no longer retained in or served from
  the in-memory cache (GetBlockHeaders/GetBlockBodies/RPC), and cannot evict
  legitimate blocks.
- Make block-hash rebroadcast conditional: immediate when validation is off,
  deferred to handleBlockHeaders (validate first) when on.
- Emit rich debug logs (signer, peer, block info) when a block is not
  rebroadcast.
Extract helpers to bring handleBlockHeaders, handleBlockBodies, and
handleNewBlock under the SonarQube cognitive-complexity threshold, without
changing behavior:

- cacheAndAnnounceHeader: per-header caching + deferred hash rebroadcast.
- buildBlockBody: RLP decode/re-encode of a block body.
- cacheFullBlock: validated full-block cache store + first-seen write.
@minhd-vu minhd-vu marked this pull request as ready for review July 2, 2026 00:53
@jhkimqd

jhkimqd commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Please correct me if I'm missing something, but from my understanding, the silent drops for unknown-signer blocks is the intended behavior, which seems to make sense. I'm not sure if this would be an issue though but, it seems like there could be very rare cases where if the body response arrives first before the header, it might get dropped even from a known validator:

Body-before-header race drops the body cache for valid blocks under cache-only-validated-blocks (p2p/protocol.go:1098)
handleNewBlockHashes sends GetBlockHeaders then GetBlockBodies, but eth doesn't guarantee response ordering. If the body response arrives first, retainBody = cache.Header != nil is false (header not cached yet), so the body is dropped from the serving cache with no re-fetch. When the header later arrives it's cached, leaving a header-only entry — even for a known-validator block. The DB is unaffected (body is written), but the sensor can no longer serve that body to peers on GetBlockBodies.

jhkimqd
jhkimqd previously approved these changes Jul 2, 2026

@jhkimqd jhkimqd 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.

+1 lgtm overall, left comment above about possible edge case

Under --cache-only-validated-blocks, the body was cached only if a header was
already present, so a body response arriving before its header (eth does not
guarantee ordering) was dropped and never re-fetched — leaving a header-only
entry even for valid-validator blocks, which then couldn't be served on
GetBlockBodies.

- handleBlockBodies: retain the body whenever a cache entry exists (the
  announcement marker), holding a body-first response provisionally instead of
  dropping it.
- cacheAndAnnounceHeader: evict the entry when the header's signer is unknown,
  dropping any provisionally-held body.
- handleGetBlockBodies: serve a body only when its (validated) header is also
  cached, so provisional bodies are never served before validation.

DB persistence is unchanged; bodies are still written for every block.
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

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