From 794e57ae9eb92ffc89d036b13503b99600314a11 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 25 Feb 2026 15:19:19 +0100 Subject: [PATCH] eth/catalyst: add engine_forkchoiceUpdatedV4 for Amsterdam Add ForkchoiceUpdatedV4 and ForkchoiceUpdatedWithWitnessV4 scoped to the Amsterdam fork using PayloadV4. Remove Amsterdam from V3's fork check so each version is properly scoped. Co-Authored-By: Claude Opus 4.6 --- eth/catalyst/api.go | 19 +++++++++++++++++-- eth/catalyst/witness.go | 20 ++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index a94f69cfff..3cfee2a705 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -198,8 +198,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa return engine.STATUS_INVALID, attributesErr("missing withdrawals") case params.BeaconRoot == nil: return engine.STATUS_INVALID, attributesErr("missing beacon root") - case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.Amsterdam, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): - return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka/amsterdam payloads") + case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): + return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads") } } // TODO(matt): the spec requires that fcu is applied when called on a valid @@ -209,6 +209,21 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa return api.forkchoiceUpdated(update, params, engine.PayloadV3, false) } +// ForkchoiceUpdatedV4 is equivalent to V3 with PayloadV4 for the Amsterdam fork. +func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { + if params != nil { + switch { + case params.Withdrawals == nil: + return engine.STATUS_INVALID, attributesErr("missing withdrawals") + case params.BeaconRoot == nil: + return engine.STATUS_INVALID, attributesErr("missing beacon root") + case !api.checkFork(params.Timestamp, forks.Amsterdam): + return engine.STATUS_INVALID, unsupportedForkErr("fcuV4 must only be called for amsterdam payloads") + } + } + return api.forkchoiceUpdated(update, params, engine.PayloadV4, false) +} + func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) { api.forkchoiceLock.Lock() defer api.forkchoiceLock.Unlock() diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 5ae40de5b6..f2bf5737ce 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -73,8 +73,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice return engine.STATUS_INVALID, attributesErr("missing withdrawals") case params.BeaconRoot == nil: return engine.STATUS_INVALID, attributesErr("missing beacon root") - case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.Amsterdam, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): - return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka/amsterdam payloads") + case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): + return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads") } } // TODO(matt): the spec requires that fcu is applied when called on a valid @@ -84,6 +84,22 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice return api.forkchoiceUpdated(update, params, engine.PayloadV3, true) } +// ForkchoiceUpdatedWithWitnessV4 is analogous to ForkchoiceUpdatedV4, only it +// generates an execution witness too if block building was requested. +func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV4(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { + if params != nil { + switch { + case params.Withdrawals == nil: + return engine.STATUS_INVALID, attributesErr("missing withdrawals") + case params.BeaconRoot == nil: + return engine.STATUS_INVALID, attributesErr("missing beacon root") + case !api.checkFork(params.Timestamp, forks.Amsterdam): + return engine.STATUS_INVALID, unsupportedForkErr("fcuV4 must only be called for amsterdam payloads") + } + } + return api.forkchoiceUpdated(update, params, engine.PayloadV4, true) +} + // NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates // and returns a stateless witness after running the payload. func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {