From 38f1de919338c8813b09e8c5b45b35578c419b1f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 5 May 2026 11:18:48 +0200 Subject: [PATCH] eth/catalyst: update to new spec --- beacon/engine/errors.go | 1 + eth/catalyst/api.go | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go index 62773a0ea9..80e13b11b9 100644 --- a/beacon/engine/errors.go +++ b/beacon/engine/errors.go @@ -81,6 +81,7 @@ var ( TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"} InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"} 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_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 371e88b5de..b81ed57a2c 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -82,6 +82,9 @@ const ( // beaconUpdateWarnFrequency is the frequency at which to warn the user that // the beacon client is offline. beaconUpdateWarnFrequency = 5 * time.Minute + + // maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated. + maxReorgDepth = 32 ) type ConsensusAPI struct { @@ -322,15 +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 // missing and we are requested to generate the payload in slot. } else { - // In EPBs we can reorg to a parent of the head within 32 blocks. - // Allow this once the client is synced. - if api.eth.Synced() && api.eth.BlockChain().CurrentBlock().Number.Uint64()-block.NumberU64() < 32 { - if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil { - return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err - } + if finalized := api.eth.BlockChain().CurrentFinalBlock(); finalized != nil && block.NumberU64() <= finalized.Number.Uint64() { + log.Info("Skipping beacon update to finalized ancestor", "number", block.NumberU64(), "hash", update.HeadBlockHash) + 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 } - 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 } api.eth.SetSynced()