Skip to content

op-node: P2P Block Signature Grace Period#20063

Open
axelKingsley wants to merge 7 commits intodevelopfrom
feat/signer-grace-period
Open

op-node: P2P Block Signature Grace Period#20063
axelKingsley wants to merge 7 commits intodevelopfrom
feat/signer-grace-period

Conversation

@axelKingsley
Copy link
Copy Markdown
Contributor

@axelKingsley axelKingsley commented Apr 14, 2026

Tool Use Notice

Entirely Generated using personal skill guidance and monorepo guidance.

Self reviewed and updated.

What

Adds a signer grace period to op-node so that when the unsafe block signer address is rotated on L1, blocks from the previous signer continue to be accepted for up to 20 minutes (or until a block from the new signer is verified).

Why

When the unsafe block signer is rotated in the L1 SystemConfig contract, verifier nodes experience a stale-signer window during which they reject valid blocks. The signer address is loaded by a periodic background reload (default: every 10 minutes) with confirmation depth applied, creating a worst-case activation delay of ~11 minutes where the node rejects blocks from whichever key it hasn't loaded yet.

This is no less secure than the current behavior — today the node already accepts blocks from whichever single key it happens to have cached, with a random mismatch window depending on reload timing. The grace period makes the transition gapless. The unsafe signer key rotation logic is not consensus code.

Closes #19981

How

GossipRuntimeConfig interface (op-node/p2p/gossip.go) gains two methods:

  • PreviousP2PSequencerAddress() — returns the old signer address during the grace period, zero otherwise
  • ConfirmCurrentSigner() — signals that a block from the new signer was verified, ending the grace period early

RuntimeConfig (op-node/node/runcfg/runtime_config.go) tracks grace period state:

  • Load() detects when p2pBlockSignerAddr changes and saves the old address + timestamp
  • PreviousP2PSequencerAddress() returns the previous address if within DefaultSignerGracePeriod (20 min), zero if expired
  • ConfirmCurrentSigner() clears the previous address immediately

verifyBlockSignature (op-node/p2p/gossip.go) tries the current signer first. On match it calls ConfirmCurrentSigner() and accepts. On mismatch it falls back to the previous signer (if the grace period is active). If neither matches, the block is rejected.

MockRuntimeConfig (op-service/testutils/runtime_config.go) updated to satisfy the expanded interface.

Tests

Test File Asserts
PreviousSignerAccepted gossip_test.go Block from old signer → ValidationAccept during grace period; Confirmed == false
NewSignerConfirms gossip_test.go Block from new signer → ValidationAccept; Confirmed == true
GracePeriodExpired gossip_test.go Block from old signer → ValidationReject when previous address is zero (expired)
ThirdPartySignerRejected gossip_test.go Block from unrelated key → ValidationReject even with grace period active
ValidNoGracePeriod gossip_test.go Normal steady-state block → ValidationAccept; ConfirmCurrentSigner still called
ChangeStartsGracePeriod runtime_config_test.go A→B rotation sets PreviousP2PSequencerAddress() to A
FirstLoadNoGracePeriod runtime_config_test.go Zero→A (first load) does not start a grace period
ConfirmClearsPrevious runtime_config_test.go ConfirmCurrentSigner() clears previous address to zero
SameSignerNoChange runtime_config_test.go Reloading same signer preserves existing grace period state
Expiry runtime_config_test.go PreviousP2PSequencerAddress() returns zero after 20 min timeout
DoubleRotation runtime_config_test.go A→B→C: previous is B (most recent old signer), not A

When the unsafe block signer address is rotated on L1, verifier nodes
can reject valid blocks during the window between the L1 update and
the runtime config reload. Add a grace period so both the old and new
signers are accepted for up to 20 minutes after a rotation is detected.

The grace period ends early when a block from the new signer is
verified, confirming the rotation is complete.

Closes #19981

Made-with: Cursor
Tests for verifyBlockSignature grace period behavior:
- Previous signer accepted during grace period
- New signer confirmation ends grace period
- Expired grace period rejects old signer
- Third-party signer rejected even with grace active
- Normal flow still calls ConfirmCurrentSigner

Tests for RuntimeConfig grace period lifecycle:
- Signer change starts grace period
- First load (zero -> addr) does not start grace period
- ConfirmCurrentSigner clears previous
- Same signer reload preserves grace state
- Timeout expiry returns zero previous address
- Double rotation (A->B->C) tracks only most recent previous

Made-with: Cursor
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.6%. Comparing base (229f574) to head (df4558e).
⚠️ Report is 249 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #20063      +/-   ##
===========================================
- Coverage     75.9%    75.6%    -0.3%     
===========================================
  Files          684      195     -489     
  Lines        73064    11343   -61721     
===========================================
- Hits         55474     8581   -46893     
+ Misses       17446     2618   -14828     
  Partials       144      144              
Flag Coverage Δ
cannon-go-tests-64 66.4% <ø> (ø)
contracts-bedrock-tests 80.7% <ø> (ø)
unit ?

Flags with carried forward coverage won't be shown. Click here to find out more.
see 489 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Collapse 5 individual grace period subtests into a single table-driven
loop, reducing ~60 lines to ~20 with no behavior change.

Made-with: Cursor
Copy link
Copy Markdown
Contributor Author

@axelKingsley axelKingsley left a comment

Choose a reason for hiding this comment

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

self review

Comment thread op-node/node/runcfg/runtime_config.go Outdated
Comment thread op-node/node/runcfg/runtime_config.go Outdated
Comment thread op-node/node/runcfg/runtime_config.go
- Remove redundant zero-check early return in PreviousP2PSequencerAddress
- Invert ConfirmCurrentSigner guard: warn on nil current address, then
  unconditionally log + clear previous signer
- Extract signer rotation logic from Load into rotateSigner helper

Made-with: Cursor
@axelKingsley axelKingsley marked this pull request as ready for review April 14, 2026 19:36
@axelKingsley axelKingsley requested a review from a team as a code owner April 14, 2026 19:36
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.

op-node: reactive runtime config reload on unsafe block signer mismatch

2 participants