From 4363b81e6fc71441c121a2b784f9c059ef1f6087 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 20 Jul 2026 09:08:23 +0200 Subject: [PATCH] eth/catalyst: pass TargetGasLimit via engine api (#35372) Pass targetGasLimit via engine api --------- Co-authored-by: Gary Rong --- beacon/engine/ed_codec.go | 12 ++++---- beacon/engine/pa_codec.go | 6 ++++ beacon/engine/types.go | 12 ++++---- eth/catalyst/api.go | 29 +++++++++++++------ eth/catalyst/api_test.go | 2 +- miner/payload_building.go | 61 ++++++++++++++++++++++----------------- miner/worker.go | 29 ++++++++++++------- 7 files changed, 92 insertions(+), 59 deletions(-) diff --git a/beacon/engine/ed_codec.go b/beacon/engine/ed_codec.go index 4a6ae70b69..9694084ef5 100644 --- a/beacon/engine/ed_codec.go +++ b/beacon/engine/ed_codec.go @@ -34,8 +34,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"` BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"` + SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -60,8 +60,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.Withdrawals = e.Withdrawals enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) - enc.SlotNumber = (*hexutil.Uint64)(e.SlotNumber) enc.BlockAccessList = e.BlockAccessList + enc.SlotNumber = (*hexutil.Uint64)(e.SlotNumber) return json.Marshal(&enc) } @@ -85,8 +85,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"` BlockAccessList *hexutil.Bytes `json:"blockAccessList,omitempty"` + SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -160,11 +160,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.ExcessBlobGas != nil { e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) } - if dec.SlotNumber != nil { - e.SlotNumber = (*uint64)(dec.SlotNumber) - } if dec.BlockAccessList != nil { e.BlockAccessList = *dec.BlockAccessList } + if dec.SlotNumber != nil { + e.SlotNumber = (*uint64)(dec.SlotNumber) + } return nil } diff --git a/beacon/engine/pa_codec.go b/beacon/engine/pa_codec.go index 3a5d7ae593..3d28897b0e 100644 --- a/beacon/engine/pa_codec.go +++ b/beacon/engine/pa_codec.go @@ -22,6 +22,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { Withdrawals []*types.Withdrawal `json:"withdrawals"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` SlotNumber *hexutil.Uint64 `json:"slotNumber"` + TargetGasLimit *hexutil.Uint64 `json:"targetGasLimit"` } var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) @@ -30,6 +31,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { enc.Withdrawals = p.Withdrawals enc.BeaconRoot = p.BeaconRoot enc.SlotNumber = (*hexutil.Uint64)(p.SlotNumber) + enc.TargetGasLimit = (*hexutil.Uint64)(p.TargetGasLimit) return json.Marshal(&enc) } @@ -42,6 +44,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { Withdrawals []*types.Withdrawal `json:"withdrawals"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` SlotNumber *hexutil.Uint64 `json:"slotNumber"` + TargetGasLimit *hexutil.Uint64 `json:"targetGasLimit"` } var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { @@ -68,5 +71,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { if dec.SlotNumber != nil { p.SlotNumber = (*uint64)(dec.SlotNumber) } + if dec.TargetGasLimit != nil { + p.TargetGasLimit = (*uint64)(dec.TargetGasLimit) + } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 9d820ac772..a213648373 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -59,7 +59,7 @@ var ( // // https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#executionpayloadv4 // ExecutionPayloadV4 has the syntax of ExecutionPayloadV3 and appends the new - // field slotNumber. + // fields slotNumber and blockAccessList. PayloadV4 PayloadVersion = 0x4 ) @@ -74,12 +74,14 @@ type PayloadAttributes struct { Withdrawals []*types.Withdrawal `json:"withdrawals"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` SlotNumber *uint64 `json:"slotNumber"` + TargetGasLimit *uint64 `json:"targetGasLimit"` } // JSON type overrides for PayloadAttributes. type payloadAttributesMarshaling struct { - Timestamp hexutil.Uint64 - SlotNumber *hexutil.Uint64 + Timestamp hexutil.Uint64 + SlotNumber *hexutil.Uint64 + TargetGasLimit *hexutil.Uint64 } //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out ed_codec.go @@ -103,8 +105,8 @@ type ExecutableData struct { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *uint64 `json:"blobGasUsed"` ExcessBlobGas *uint64 `json:"excessBlobGas"` - SlotNumber *uint64 `json:"slotNumber,omitempty"` BlockAccessList []byte `json:"blockAccessList,omitempty"` + SlotNumber *uint64 `json:"slotNumber,omitempty"` } // JSON type overrides for executableData. @@ -440,7 +442,7 @@ type ExecutionPayloadBody struct { // ExecutionPayloadBodyV2 extends ExecutionPayloadBody with the block access list. type ExecutionPayloadBodyV2 struct { ExecutionPayloadBody - BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` + BlockAccessList *hexutil.Bytes `json:"blockAccessList"` } // Client identifiers to support ClientVersionV1. diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 46f8cc98fc..e94dd2e21b 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -229,6 +229,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine. return engine.STATUS_INVALID, attributesErr("missing beacon root") case params.SlotNumber == nil: return engine.STATUS_INVALID, attributesErr("missing slot number") + case params.TargetGasLimit == nil: + return engine.STATUS_INVALID, attributesErr("missing target gas limit") case !api.checkFork(params.Timestamp, forks.Amsterdam): return engine.STATUS_INVALID, unsupportedForkErr("fcuV4 must only be called for amsterdam payloads") } @@ -385,14 +387,15 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo // will replace it arbitrarily many times in between. if payloadAttributes != nil { args := &miner.BuildPayloadArgs{ - Parent: update.HeadBlockHash, - Timestamp: payloadAttributes.Timestamp, - FeeRecipient: payloadAttributes.SuggestedFeeRecipient, - Random: payloadAttributes.Random, - Withdrawals: payloadAttributes.Withdrawals, - BeaconRoot: payloadAttributes.BeaconRoot, - SlotNum: payloadAttributes.SlotNumber, - Version: payloadVersion, + Parent: update.HeadBlockHash, + Timestamp: payloadAttributes.Timestamp, + FeeRecipient: payloadAttributes.SuggestedFeeRecipient, + Random: payloadAttributes.Random, + Withdrawals: payloadAttributes.Withdrawals, + BeaconRoot: payloadAttributes.BeaconRoot, + SlotNum: payloadAttributes.SlotNumber, + TargetGasLimit: payloadAttributes.TargetGasLimit, + Version: payloadVersion, } id := args.Id() // If we already are busy generating this work, then we do not need @@ -1320,9 +1323,17 @@ func getBodyV2(block *types.Block) *engine.ExecutionPayloadBodyV2 { if body == nil { return nil } + var balData *hexutil.Bytes + if bal := block.AccessList(); bal != nil { + // Ignore the RLP encoding error just in case, interpreting + // the BAL as unavailable. + if enc, err := rlp.EncodeToBytes(bal); err == nil { + balData = (*hexutil.Bytes)(&enc) + } + } return &engine.ExecutionPayloadBodyV2{ ExecutionPayloadBody: *body, - BlockAccessList: block.AccessList(), + BlockAccessList: balData, } } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index c4298b25ab..9f200cc422 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1594,7 +1594,7 @@ func TestGetPayloadBodyV2BlockAccessList(t *testing.T) { name: "retained empty BAL", header: &types.Header{BlockAccessListHash: &emptyHash}, accessList: &empty, - want: "[]", + want: `"0xc0"`, // JSON-encoded hex string, quotes included }, { name: "pruned BAL", diff --git a/miner/payload_building.go b/miner/payload_building.go index c4322e3317..be4a78082a 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -40,14 +40,15 @@ import ( // Check engine-api specification for more details. // https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3 type BuildPayloadArgs struct { - Parent common.Hash // The parent block to build payload on top - Timestamp uint64 // The provided timestamp of generated payload - FeeRecipient common.Address // The provided recipient address for collecting transaction fee - Random common.Hash // The provided randomness value - Withdrawals types.Withdrawals // The provided withdrawals - BeaconRoot *common.Hash // The provided beaconRoot (Cancun) - SlotNum *uint64 // The provided slotNumber - Version engine.PayloadVersion // Versioning byte for payload id calculation. + Parent common.Hash // The parent block to build payload on top + Timestamp uint64 // The provided timestamp of generated payload + FeeRecipient common.Address // The provided recipient address for collecting transaction fee + Random common.Hash // The provided randomness value + Withdrawals types.Withdrawals // The provided withdrawals + BeaconRoot *common.Hash // The provided beaconRoot (Cancun) + SlotNum *uint64 // The provided slotNumber (Amsterdam) + TargetGasLimit *uint64 // The provided target gas limit (Amsterdam) + Version engine.PayloadVersion // Versioning byte for payload id calculation. } // Id computes an 8-byte identifier by hashing the components of the payload arguments. @@ -62,7 +63,10 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID { hasher.Write(args.BeaconRoot[:]) } if args.SlotNum != nil { - binary.Write(hasher, binary.BigEndian, args.SlotNum) + binary.Write(hasher, binary.BigEndian, *args.SlotNum) + } + if args.TargetGasLimit != nil { + binary.Write(hasher, binary.BigEndian, *args.TargetGasLimit) } var out engine.PayloadID copy(out[:], hasher.Sum(nil)[:8]) @@ -246,15 +250,16 @@ func (miner *Miner) buildPayload(ctx context.Context, args *BuildPayloadArgs, wi // enough to run. The empty payload can at least make sure there is something // to deliver for not missing slot. emptyParams := &generateParams{ - timestamp: args.Timestamp, - forceTime: true, - parentHash: args.Parent, - coinbase: args.FeeRecipient, - random: args.Random, - withdrawals: args.Withdrawals, - beaconRoot: args.BeaconRoot, - slotNum: args.SlotNum, - noTxs: true, + timestamp: args.Timestamp, + forceTime: true, + parentHash: args.Parent, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: args.BeaconRoot, + slotNum: args.SlotNum, + targetGasLimit: args.TargetGasLimit, + noTxs: true, } empty := miner.generateWork(ctx, emptyParams, witness) if empty.err != nil { @@ -286,15 +291,16 @@ func (miner *Miner) buildPayload(ctx context.Context, args *BuildPayloadArgs, wi endTimer := time.NewTimer(time.Second * 12) fullParams := &generateParams{ - timestamp: args.Timestamp, - forceTime: true, - parentHash: args.Parent, - coinbase: args.FeeRecipient, - random: args.Random, - withdrawals: args.Withdrawals, - beaconRoot: args.BeaconRoot, - slotNum: args.SlotNum, - noTxs: false, + timestamp: args.Timestamp, + forceTime: true, + parentHash: args.Parent, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: args.BeaconRoot, + slotNum: args.SlotNum, + targetGasLimit: args.TargetGasLimit, + noTxs: false, } for { select { @@ -351,6 +357,7 @@ func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []* withdrawals: args.Withdrawals, beaconRoot: args.BeaconRoot, slotNum: args.SlotNum, + targetGasLimit: args.TargetGasLimit, noTxs: empty, forceOverrides: true, overrideExtraData: extraData, diff --git a/miner/worker.go b/miner/worker.go index 3ba2fca2b0..1a527007e7 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -117,15 +117,16 @@ type newPayloadResult struct { // generateParams wraps various settings for generating sealing task. type generateParams struct { - timestamp uint64 // The timestamp for sealing task - forceTime bool // Flag whether the given timestamp is immutable or not - parentHash common.Hash // Parent block hash, empty means the latest chain head - coinbase common.Address // The fee recipient address for including transaction - random common.Hash // The randomness generated by beacon chain, empty before the merge - withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field) - beaconRoot *common.Hash // The beacon root (cancun field). - slotNum *uint64 // The slot number (amsterdam field). - noTxs bool // Flag whether an empty block without any transaction is expected + timestamp uint64 // The timestamp for sealing task + forceTime bool // Flag whether the given timestamp is immutable or not + parentHash common.Hash // Parent block hash, empty means the latest chain head + coinbase common.Address // The fee recipient address for including transaction + random common.Hash // The randomness generated by beacon chain, empty before the merge + withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field) + beaconRoot *common.Hash // The beacon root (cancun field). + slotNum *uint64 // The slot number (amsterdam field). + targetGasLimit *uint64 // The target gas limit requested by the CL (amsterdam field). + noTxs bool // Flag whether an empty block without any transaction is expected forceOverrides bool // Flag whether we should overwrite extraData and transactions overrideExtraData []byte @@ -267,11 +268,17 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams, } timestamp = parent.Time + 1 } + // Post-Amsterdam use TargetGasLimit provided by CL + number := new(big.Int).Add(parent.Number, common.Big1) + gasCeil := miner.config.GasCeil + if miner.chainConfig.IsAmsterdam(number, timestamp) && genParams.targetGasLimit != nil { + gasCeil = *genParams.targetGasLimit + } // Construct the sealing block header. header := &types.Header{ ParentHash: parent.Hash(), - Number: new(big.Int).Add(parent.Number, common.Big1), - GasLimit: core.CalcGasLimit(parent.GasLimit, miner.config.GasCeil), + Number: number, + GasLimit: core.CalcGasLimit(parent.GasLimit, gasCeil), Time: timestamp, Coinbase: genParams.coinbase, }