eth/catalyst: allow reorging the head block to a parent (#34767)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

Implements https://github.com/ethereum/execution-apis/pull/786/changes
as discussed on standup today

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
Marius van der Wijden 2026-05-06 09:59:51 +02:00 committed by GitHub
parent 84949107ce
commit 4d2af275e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 29 additions and 6 deletions

View file

@ -81,6 +81,7 @@ var (
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"} TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"} InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"} UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil} STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

View file

@ -2596,8 +2596,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
blockReorgAddMeter.Mark(int64(len(newChain))) blockReorgAddMeter.Mark(int64(len(newChain)))
} else { } else {
// len(newChain) == 0 && len(oldChain) > 0 // len(newChain) == 0 && len(oldChain) > 0
// rewind the canonical chain to a lower point. // Rewind the canonical chain to a lower point. In EPBs we can reorg to
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain)) // a parent of the head within 32 blocks.
if len(oldChain) > 32 {
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain))
} else {
log.Info("Shorten chain", "del", len(oldChain), "number", oldHead.Number, "hash", oldHead.Hash())
}
} }
// Acquire the tx-lookup lock before mutation. This step is essential // Acquire the tx-lookup lock before mutation. This step is essential
// as the txlookups should be changed atomically, and all subsequent // as the txlookups should be changed atomically, and all subsequent

View file

@ -82,6 +82,9 @@ const (
// beaconUpdateWarnFrequency is the frequency at which to warn the user that // beaconUpdateWarnFrequency is the frequency at which to warn the user that
// the beacon client is offline. // the beacon client is offline.
beaconUpdateWarnFrequency = 5 * time.Minute beaconUpdateWarnFrequency = 5 * time.Minute
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated.
maxReorgDepth = 32
) )
type ConsensusAPI struct { type ConsensusAPI struct {
@ -237,6 +240,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.
func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) { func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated") ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
defer spanEnd(&err) defer spanEnd(&err)
api.forkchoiceLock.Lock() api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock() defer api.forkchoiceLock.Unlock()
@ -321,10 +325,23 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
// generating the payload. It's a special corner case that a few slots are // generating the payload. It's a special corner case that a few slots are
// missing and we are requested to generate the payload in slot. // missing and we are requested to generate the payload in slot.
} else { } else {
// If the head block is already in our canonical chain, the beacon client is if finalized := api.eth.BlockChain().CurrentFinalBlock(); finalized != nil && block.NumberU64() <= finalized.Number.Uint64() {
// probably resyncing. Ignore the update. log.Info("Skipping beacon update to finalized ancestor", "number", block.NumberU64(), "hash", update.HeadBlockHash)
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number) return valid(nil), nil
return valid(nil), nil }
depth := api.eth.BlockChain().CurrentBlock().Number.Uint64() - block.NumberU64()
if depth >= maxReorgDepth {
log.Warn("Refusing too deep reorg", "depth", depth, "head", update.HeadBlockHash)
return engine.STATUS_INVALID, engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds limit %d", depth, maxReorgDepth))
}
if !api.eth.Synced() {
log.Info("Ignoring beacon update to old head while syncing", "number", block.NumberU64(), "hash", update.HeadBlockHash)
return valid(nil), nil
}
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
log.Error("Error setting canonical", "number", block.NumberU64(), "hash", update.HeadBlockHash, "error", err)
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
}
} }
api.eth.SetSynced() api.eth.SetSynced()