github.com/ethereum/go-ethereum@v1.16.1/eth/catalyst/witness.go (about) 1 // Copyright 2025 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package catalyst 18 19 import ( 20 "errors" 21 "strconv" 22 "time" 23 24 "github.com/ethereum/go-ethereum/beacon/engine" 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/common/hexutil" 27 "github.com/ethereum/go-ethereum/core" 28 "github.com/ethereum/go-ethereum/core/stateless" 29 "github.com/ethereum/go-ethereum/core/vm" 30 "github.com/ethereum/go-ethereum/log" 31 "github.com/ethereum/go-ethereum/params/forks" 32 "github.com/ethereum/go-ethereum/rlp" 33 ) 34 35 // ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it 36 // generates an execution witness too if block building was requested. 37 func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 38 if payloadAttributes != nil { 39 switch { 40 case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: 41 return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1") 42 case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai): 43 return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai") 44 } 45 } 46 return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true) 47 } 48 49 // ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it 50 // generates an execution witness too if block building was requested. 51 func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 52 if params != nil { 53 switch { 54 case params.BeaconRoot != nil: 55 return engine.STATUS_INVALID, attributesErr("unexpected beacon root") 56 case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil: 57 return engine.STATUS_INVALID, attributesErr("withdrawals before shanghai") 58 case api.checkFork(params.Timestamp, forks.Shanghai) && params.Withdrawals == nil: 59 return engine.STATUS_INVALID, attributesErr("missing withdrawals") 60 case !api.checkFork(params.Timestamp, forks.Paris, forks.Shanghai): 61 return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads") 62 } 63 } 64 return api.forkchoiceUpdated(update, params, engine.PayloadV2, true) 65 } 66 67 // ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it 68 // generates an execution witness too if block building was requested. 69 func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 70 if params != nil { 71 switch { 72 case params.Withdrawals == nil: 73 return engine.STATUS_INVALID, attributesErr("missing withdrawals") 74 case params.BeaconRoot == nil: 75 return engine.STATUS_INVALID, attributesErr("missing beacon root") 76 case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague): 77 return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads") 78 } 79 } 80 // TODO(matt): the spec requires that fcu is applied when called on a valid 81 // hash, even if params are wrong. To do this we need to split up 82 // forkchoiceUpdate into a function that only updates the head and then a 83 // function that kicks off block construction. 84 return api.forkchoiceUpdated(update, params, engine.PayloadV3, true) 85 } 86 87 // NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates 88 // and returns a stateless witness after running the payload. 89 func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { 90 if params.Withdrawals != nil { 91 return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) 92 } 93 return api.newPayload(params, nil, nil, nil, true) 94 } 95 96 // NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates 97 // and returns a stateless witness after running the payload. 98 func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { 99 var ( 100 cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp) 101 shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp) 102 ) 103 switch { 104 case cancun: 105 return invalidStatus, paramsErr("can't use newPayloadV2 post-cancun") 106 case shanghai && params.Withdrawals == nil: 107 return invalidStatus, paramsErr("nil withdrawals post-shanghai") 108 case !shanghai && params.Withdrawals != nil: 109 return invalidStatus, paramsErr("non-nil withdrawals pre-shanghai") 110 case params.ExcessBlobGas != nil: 111 return invalidStatus, paramsErr("non-nil excessBlobGas pre-cancun") 112 case params.BlobGasUsed != nil: 113 return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun") 114 } 115 return api.newPayload(params, nil, nil, nil, true) 116 } 117 118 // NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates 119 // and returns a stateless witness after running the payload. 120 func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { 121 switch { 122 case params.Withdrawals == nil: 123 return invalidStatus, paramsErr("nil withdrawals post-shanghai") 124 case params.ExcessBlobGas == nil: 125 return invalidStatus, paramsErr("nil excessBlobGas post-cancun") 126 case params.BlobGasUsed == nil: 127 return invalidStatus, paramsErr("nil blobGasUsed post-cancun") 128 case versionedHashes == nil: 129 return invalidStatus, paramsErr("nil versionedHashes post-cancun") 130 case beaconRoot == nil: 131 return invalidStatus, paramsErr("nil beaconRoot post-cancun") 132 case !api.checkFork(params.Timestamp, forks.Cancun): 133 return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") 134 } 135 return api.newPayload(params, versionedHashes, beaconRoot, nil, true) 136 } 137 138 // NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates 139 // and returns a stateless witness after running the payload. 140 func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) { 141 switch { 142 case params.Withdrawals == nil: 143 return invalidStatus, paramsErr("nil withdrawals post-shanghai") 144 case params.ExcessBlobGas == nil: 145 return invalidStatus, paramsErr("nil excessBlobGas post-cancun") 146 case params.BlobGasUsed == nil: 147 return invalidStatus, paramsErr("nil blobGasUsed post-cancun") 148 case versionedHashes == nil: 149 return invalidStatus, paramsErr("nil versionedHashes post-cancun") 150 case beaconRoot == nil: 151 return invalidStatus, paramsErr("nil beaconRoot post-cancun") 152 case executionRequests == nil: 153 return invalidStatus, paramsErr("nil executionRequests post-prague") 154 case !api.checkFork(params.Timestamp, forks.Prague): 155 return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") 156 } 157 requests := convertRequests(executionRequests) 158 if err := validateRequests(requests); err != nil { 159 return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) 160 } 161 return api.newPayload(params, versionedHashes, beaconRoot, requests, true) 162 } 163 164 // ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in 165 // a stateless mode on top of a provided witness instead of the local database. 166 func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { 167 if params.Withdrawals != nil { 168 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) 169 } 170 return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness) 171 } 172 173 // ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in 174 // a stateless mode on top of a provided witness instead of the local database. 175 func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { 176 var ( 177 cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp) 178 shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp) 179 ) 180 switch { 181 case cancun: 182 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("can't use newPayloadV2 post-cancun") 183 case shanghai && params.Withdrawals == nil: 184 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai") 185 case !shanghai && params.Withdrawals != nil: 186 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil withdrawals pre-shanghai") 187 case params.ExcessBlobGas != nil: 188 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil excessBlobGas pre-cancun") 189 case params.BlobGasUsed != nil: 190 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun") 191 } 192 return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness) 193 } 194 195 // ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in 196 // a stateless mode on top of a provided witness instead of the local database. 197 func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { 198 switch { 199 case params.Withdrawals == nil: 200 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai") 201 case params.ExcessBlobGas == nil: 202 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun") 203 case params.BlobGasUsed == nil: 204 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun") 205 case versionedHashes == nil: 206 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun") 207 case beaconRoot == nil: 208 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun") 209 case !api.checkFork(params.Timestamp, forks.Cancun): 210 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") 211 } 212 return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness) 213 } 214 215 // ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in 216 // a stateless mode on top of a provided witness instead of the local database. 217 func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { 218 switch { 219 case params.Withdrawals == nil: 220 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai") 221 case params.ExcessBlobGas == nil: 222 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun") 223 case params.BlobGasUsed == nil: 224 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun") 225 case versionedHashes == nil: 226 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun") 227 case beaconRoot == nil: 228 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun") 229 case executionRequests == nil: 230 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague") 231 case !api.checkFork(params.Timestamp, forks.Prague): 232 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") 233 } 234 requests := convertRequests(executionRequests) 235 if err := validateRequests(requests); err != nil { 236 return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) 237 } 238 return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness) 239 } 240 241 func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { 242 log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash) 243 block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, requests) 244 if err != nil { 245 bgu := "nil" 246 if params.BlobGasUsed != nil { 247 bgu = strconv.Itoa(int(*params.BlobGasUsed)) 248 } 249 ebg := "nil" 250 if params.ExcessBlobGas != nil { 251 ebg = strconv.Itoa(int(*params.ExcessBlobGas)) 252 } 253 log.Warn("Invalid ExecuteStatelessPayload params", 254 "params.Number", params.Number, 255 "params.ParentHash", params.ParentHash, 256 "params.BlockHash", params.BlockHash, 257 "params.StateRoot", params.StateRoot, 258 "params.FeeRecipient", params.FeeRecipient, 259 "params.LogsBloom", common.PrettyBytes(params.LogsBloom), 260 "params.Random", params.Random, 261 "params.GasLimit", params.GasLimit, 262 "params.GasUsed", params.GasUsed, 263 "params.Timestamp", params.Timestamp, 264 "params.ExtraData", common.PrettyBytes(params.ExtraData), 265 "params.BaseFeePerGas", params.BaseFeePerGas, 266 "params.BlobGasUsed", bgu, 267 "params.ExcessBlobGas", ebg, 268 "len(params.Transactions)", len(params.Transactions), 269 "len(params.Withdrawals)", len(params.Withdrawals), 270 "beaconRoot", beaconRoot, 271 "len(requests)", len(requests), 272 "error", err) 273 errorMsg := err.Error() 274 return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil 275 } 276 witness := new(stateless.Witness) 277 if err := rlp.DecodeBytes(opaqueWitness, witness); err != nil { 278 log.Warn("Invalid ExecuteStatelessPayload witness", "err", err) 279 errorMsg := err.Error() 280 return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil 281 } 282 // Stash away the last update to warn the user if the beacon client goes offline 283 api.lastNewPayloadUpdate.Store(time.Now().Unix()) 284 285 log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash) 286 stateRoot, receiptRoot, err := core.ExecuteStateless(api.config(), vm.Config{}, block, witness) 287 if err != nil { 288 log.Warn("ExecuteStatelessPayload: execution failed", "err", err) 289 errorMsg := err.Error() 290 return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil 291 } 292 return engine.StatelessPayloadStatusV1{Status: engine.VALID, StateRoot: stateRoot, ReceiptsRoot: receiptRoot}, nil 293 }