github.com/ethereum/go-ethereum@v1.16.1/eth/catalyst/api.go (about) 1 // Copyright 2021 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 implements the temporary eth1/eth2 RPC integration. 18 package catalyst 19 20 import ( 21 "crypto/sha256" 22 "errors" 23 "fmt" 24 "strconv" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "github.com/ethereum/go-ethereum/beacon/engine" 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/common/hexutil" 32 "github.com/ethereum/go-ethereum/core/rawdb" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/crypto/kzg4844" 35 "github.com/ethereum/go-ethereum/eth" 36 "github.com/ethereum/go-ethereum/eth/ethconfig" 37 "github.com/ethereum/go-ethereum/internal/version" 38 "github.com/ethereum/go-ethereum/log" 39 "github.com/ethereum/go-ethereum/metrics" 40 "github.com/ethereum/go-ethereum/miner" 41 "github.com/ethereum/go-ethereum/node" 42 "github.com/ethereum/go-ethereum/params" 43 "github.com/ethereum/go-ethereum/params/forks" 44 "github.com/ethereum/go-ethereum/rlp" 45 "github.com/ethereum/go-ethereum/rpc" 46 ) 47 48 // Register adds the engine API to the full node. 49 func Register(stack *node.Node, backend *eth.Ethereum) error { 50 log.Warn("Engine API enabled", "protocol", "eth") 51 stack.RegisterAPIs([]rpc.API{ 52 { 53 Namespace: "engine", 54 Service: NewConsensusAPI(backend), 55 Authenticated: true, 56 }, 57 }) 58 return nil 59 } 60 61 const ( 62 // invalidBlockHitEviction is the number of times an invalid block can be 63 // referenced in forkchoice update or new payload before it is attempted 64 // to be reprocessed again. 65 invalidBlockHitEviction = 128 66 67 // invalidTipsetsCap is the max number of recent block hashes tracked that 68 // have lead to some bad ancestor block. It's just an OOM protection. 69 invalidTipsetsCap = 512 70 71 // beaconUpdateStartupTimeout is the time to wait for a beacon client to get 72 // attached before starting to issue warnings. 73 beaconUpdateStartupTimeout = 30 * time.Second 74 75 // beaconUpdateConsensusTimeout is the max time allowed for a beacon client 76 // to send a consensus update before it's considered offline and the user is 77 // warned. 78 beaconUpdateConsensusTimeout = 2 * time.Minute 79 80 // beaconUpdateWarnFrequency is the frequency at which to warn the user that 81 // the beacon client is offline. 82 beaconUpdateWarnFrequency = 5 * time.Minute 83 ) 84 85 // All methods provided over the engine endpoint. 86 var caps = []string{ 87 "engine_forkchoiceUpdatedV1", 88 "engine_forkchoiceUpdatedV2", 89 "engine_forkchoiceUpdatedV3", 90 "engine_forkchoiceUpdatedWithWitnessV1", 91 "engine_forkchoiceUpdatedWithWitnessV2", 92 "engine_forkchoiceUpdatedWithWitnessV3", 93 "engine_exchangeTransitionConfigurationV1", 94 "engine_getPayloadV1", 95 "engine_getPayloadV2", 96 "engine_getPayloadV3", 97 "engine_getPayloadV4", 98 "engine_getPayloadV5", 99 "engine_getBlobsV1", 100 "engine_getBlobsV2", 101 "engine_newPayloadV1", 102 "engine_newPayloadV2", 103 "engine_newPayloadV3", 104 "engine_newPayloadV4", 105 "engine_newPayloadWithWitnessV1", 106 "engine_newPayloadWithWitnessV2", 107 "engine_newPayloadWithWitnessV3", 108 "engine_newPayloadWithWitnessV4", 109 "engine_executeStatelessPayloadV1", 110 "engine_executeStatelessPayloadV2", 111 "engine_executeStatelessPayloadV3", 112 "engine_executeStatelessPayloadV4", 113 "engine_getPayloadBodiesByHashV1", 114 "engine_getPayloadBodiesByHashV2", 115 "engine_getPayloadBodiesByRangeV1", 116 "engine_getPayloadBodiesByRangeV2", 117 "engine_getClientVersionV1", 118 } 119 120 var ( 121 // Number of blobs requested via getBlobsV2 122 getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil) 123 // Number of blobs requested via getBlobsV2 that are present in the blobpool 124 getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil) 125 // Number of times getBlobsV2 responded with “hit” 126 getBlobsV2RequestHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil) 127 // Number of times getBlobsV2 responded with “miss” 128 getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil) 129 ) 130 131 type ConsensusAPI struct { 132 eth *eth.Ethereum 133 134 remoteBlocks *headerQueue // Cache of remote payloads received 135 localBlocks *payloadQueue // Cache of local payloads generated 136 137 // The forkchoice update and new payload method require us to return the 138 // latest valid hash in an invalid chain. To support that return, we need 139 // to track historical bad blocks as well as bad tipsets in case a chain 140 // is constantly built on it. 141 // 142 // There are a few important caveats in this mechanism: 143 // - The bad block tracking is ephemeral, in-memory only. We must never 144 // persist any bad block information to disk as a bug in Geth could end 145 // up blocking a valid chain, even if a later Geth update would accept 146 // it. 147 // - Bad blocks will get forgotten after a certain threshold of import 148 // attempts and will be retried. The rationale is that if the network 149 // really-really-really tries to feed us a block, we should give it a 150 // new chance, perhaps us being racey instead of the block being legit 151 // bad (this happened in Geth at a point with import vs. pending race). 152 // - Tracking all the blocks built on top of the bad one could be a bit 153 // problematic, so we will only track the head chain segment of a bad 154 // chain to allow discarding progressing bad chains and side chains, 155 // without tracking too much bad data. 156 invalidBlocksHits map[common.Hash]int // Ephemeral cache to track invalid blocks and their hit count 157 invalidTipsets map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor 158 invalidLock sync.Mutex // Protects the invalid maps from concurrent access 159 160 // Geth can appear to be stuck or do strange things if the beacon client is 161 // offline or is sending us strange data. Stash some update stats away so 162 // that we can warn the user and not have them open issues on our tracker. 163 lastTransitionUpdate atomic.Int64 164 lastForkchoiceUpdate atomic.Int64 165 lastNewPayloadUpdate atomic.Int64 166 167 forkchoiceLock sync.Mutex // Lock for the forkChoiceUpdated method 168 newPayloadLock sync.Mutex // Lock for the NewPayload method 169 } 170 171 // NewConsensusAPI creates a new consensus api for the given backend. 172 // The underlying blockchain needs to have a valid terminal total difficulty set. 173 func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { 174 api := newConsensusAPIWithoutHeartbeat(eth) 175 go api.heartbeat() 176 return api 177 } 178 179 // newConsensusAPIWithoutHeartbeat creates a new consensus api for the SimulatedBeacon Node. 180 func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI { 181 if eth.BlockChain().Config().TerminalTotalDifficulty == nil { 182 log.Warn("Engine API started but chain not configured for merge yet") 183 } 184 api := &ConsensusAPI{ 185 eth: eth, 186 remoteBlocks: newHeaderQueue(), 187 localBlocks: newPayloadQueue(), 188 invalidBlocksHits: make(map[common.Hash]int), 189 invalidTipsets: make(map[common.Hash]*types.Header), 190 } 191 eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor) 192 return api 193 } 194 195 // ForkchoiceUpdatedV1 has several responsibilities: 196 // 197 // We try to set our blockchain to the headBlock. 198 // 199 // If the method is called with an empty head block: we return success, which can be used 200 // to check if the engine API is enabled. 201 // 202 // If the total difficulty was not reached: we return INVALID. 203 // 204 // If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db, 205 // if not we start a sync. 206 // 207 // If there are payloadAttributes: we try to assemble a block with the payloadAttributes 208 // and return its payloadID. 209 func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 210 if payloadAttributes != nil { 211 switch { 212 case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: 213 return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1") 214 case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai): 215 return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai") 216 } 217 } 218 return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false) 219 } 220 221 // ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload 222 // attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2. 223 func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 224 if params != nil { 225 switch { 226 case params.BeaconRoot != nil: 227 return engine.STATUS_INVALID, attributesErr("unexpected beacon root") 228 case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil: 229 return engine.STATUS_INVALID, attributesErr("withdrawals before shanghai") 230 case api.checkFork(params.Timestamp, forks.Shanghai) && params.Withdrawals == nil: 231 return engine.STATUS_INVALID, attributesErr("missing withdrawals") 232 case !api.checkFork(params.Timestamp, forks.Paris, forks.Shanghai): 233 return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads") 234 } 235 } 236 return api.forkchoiceUpdated(update, params, engine.PayloadV2, false) 237 } 238 239 // ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root 240 // in the payload attributes. It supports only PayloadAttributesV3. 241 func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 242 if params != nil { 243 switch { 244 case params.Withdrawals == nil: 245 return engine.STATUS_INVALID, attributesErr("missing withdrawals") 246 case params.BeaconRoot == nil: 247 return engine.STATUS_INVALID, attributesErr("missing beacon root") 248 case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka): 249 return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads") 250 } 251 } 252 // TODO(matt): the spec requires that fcu is applied when called on a valid 253 // hash, even if params are wrong. To do this we need to split up 254 // forkchoiceUpdate into a function that only updates the head and then a 255 // function that kicks off block construction. 256 return api.forkchoiceUpdated(update, params, engine.PayloadV3, false) 257 } 258 259 func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) { 260 api.forkchoiceLock.Lock() 261 defer api.forkchoiceLock.Unlock() 262 263 log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) 264 if update.HeadBlockHash == (common.Hash{}) { 265 log.Warn("Forkchoice requested update to zero hash") 266 return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? 267 } 268 // Stash away the last update to warn the user if the beacon client goes offline 269 api.lastForkchoiceUpdate.Store(time.Now().Unix()) 270 271 // Check whether we have the block yet in our database or not. If not, we'll 272 // need to either trigger a sync, or to reject this forkchoice update for a 273 // reason. 274 block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash) 275 if block == nil { 276 // If this block was previously invalidated, keep rejecting it here too 277 if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil { 278 return engine.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil 279 } 280 // If the head hash is unknown (was not given to us in a newPayload request), 281 // we cannot resolve the header, so not much to do. This could be extended in 282 // the future to resolve from the `eth` network, but it's an unexpected case 283 // that should be fixed, not papered over. 284 header := api.remoteBlocks.get(update.HeadBlockHash) 285 if header == nil { 286 log.Warn("Fetching the unknown forkchoice head from network", "hash", update.HeadBlockHash) 287 retrievedHead, err := api.eth.Downloader().GetHeader(update.HeadBlockHash) 288 if err != nil { 289 log.Warn("Could not retrieve unknown head from peers") 290 return engine.STATUS_SYNCING, nil 291 } 292 api.remoteBlocks.put(retrievedHead.Hash(), retrievedHead) 293 header = retrievedHead 294 } 295 // If the finalized hash is known, we can direct the downloader to move 296 // potentially more data to the freezer from the get go. 297 finalized := api.remoteBlocks.get(update.FinalizedBlockHash) 298 299 // Header advertised via a past newPayload request. Start syncing to it. 300 context := []interface{}{"number", header.Number, "hash", header.Hash()} 301 if update.FinalizedBlockHash != (common.Hash{}) { 302 if finalized == nil { 303 context = append(context, []interface{}{"finalized", "unknown"}...) 304 } else { 305 context = append(context, []interface{}{"finalized", finalized.Number}...) 306 } 307 } 308 log.Info("Forkchoice requested sync to new head", context...) 309 if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header, finalized); err != nil { 310 return engine.STATUS_SYNCING, err 311 } 312 return engine.STATUS_SYNCING, nil 313 } 314 // Block is known locally, just sanity check that the beacon client does not 315 // attempt to push us back to before the merge. 316 if block.Difficulty().BitLen() > 0 && block.NumberU64() > 0 { 317 ph := api.eth.BlockChain().GetHeader(block.ParentHash(), block.NumberU64()-1) 318 if ph == nil { 319 return engine.STATUS_INVALID, errors.New("parent unavailable for difficulty check") 320 } 321 if ph.Difficulty.Sign() == 0 && block.Difficulty().Sign() > 0 { 322 log.Error("Parent block is already post-ttd", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) 323 return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil 324 } 325 } 326 valid := func(id *engine.PayloadID) engine.ForkChoiceResponse { 327 return engine.ForkChoiceResponse{ 328 PayloadStatus: engine.PayloadStatusV1{ 329 Status: engine.VALID, 330 LatestValidHash: &update.HeadBlockHash, 331 }, 332 PayloadID: id, 333 } 334 } 335 if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { 336 // Block is not canonical, set head. 337 if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil { 338 return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err 339 } 340 } else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash { 341 // If the specified head matches with our local head, do nothing and keep 342 // generating the payload. It's a special corner case that a few slots are 343 // missing and we are requested to generate the payload in slot. 344 } else { 345 // If the head block is already in our canonical chain, the beacon client is 346 // probably resyncing. Ignore the update. 347 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) 348 return valid(nil), nil 349 } 350 api.eth.SetSynced() 351 352 // If the beacon client also advertised a finalized block, mark the local 353 // chain final and completely in PoS mode. 354 if update.FinalizedBlockHash != (common.Hash{}) { 355 // If the finalized block is not in our canonical tree, something is wrong 356 finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash) 357 if finalBlock == nil { 358 log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash) 359 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database")) 360 } else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash { 361 log.Warn("Final block not in canonical chain", "number", finalBlock.NumberU64(), "hash", update.FinalizedBlockHash) 362 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain")) 363 } 364 // Set the finalized block 365 api.eth.BlockChain().SetFinalized(finalBlock.Header()) 366 } 367 // Check if the safe block hash is in our canonical tree, if not something is wrong 368 if update.SafeBlockHash != (common.Hash{}) { 369 safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash) 370 if safeBlock == nil { 371 log.Warn("Safe block not available in database") 372 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database")) 373 } 374 if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash { 375 log.Warn("Safe block not in canonical chain") 376 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) 377 } 378 // Set the safe block 379 api.eth.BlockChain().SetSafe(safeBlock.Header()) 380 } 381 // If payload generation was requested, create a new block to be potentially 382 // sealed by the beacon client. The payload will be requested later, and we 383 // will replace it arbitrarily many times in between. 384 if payloadAttributes != nil { 385 args := &miner.BuildPayloadArgs{ 386 Parent: update.HeadBlockHash, 387 Timestamp: payloadAttributes.Timestamp, 388 FeeRecipient: payloadAttributes.SuggestedFeeRecipient, 389 Random: payloadAttributes.Random, 390 Withdrawals: payloadAttributes.Withdrawals, 391 BeaconRoot: payloadAttributes.BeaconRoot, 392 Version: payloadVersion, 393 } 394 id := args.Id() 395 // If we already are busy generating this work, then we do not need 396 // to start a second process. 397 if api.localBlocks.has(id) { 398 return valid(&id), nil 399 } 400 payload, err := api.eth.Miner().BuildPayload(args, payloadWitness) 401 if err != nil { 402 log.Error("Failed to build payload", "err", err) 403 return valid(nil), engine.InvalidPayloadAttributes.With(err) 404 } 405 api.localBlocks.put(id, payload) 406 return valid(&id), nil 407 } 408 return valid(nil), nil 409 } 410 411 // ExchangeTransitionConfigurationV1 checks the given configuration against 412 // the configuration of the node. 413 func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) { 414 log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty) 415 if config.TerminalTotalDifficulty == nil { 416 return nil, errors.New("invalid terminal total difficulty") 417 } 418 // Stash away the last update to warn the user if the beacon client goes offline 419 api.lastTransitionUpdate.Store(time.Now().Unix()) 420 421 ttd := api.config().TerminalTotalDifficulty 422 if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 { 423 log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty) 424 return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty) 425 } 426 if config.TerminalBlockHash != (common.Hash{}) { 427 if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash { 428 return &engine.TransitionConfigurationV1{ 429 TerminalTotalDifficulty: (*hexutil.Big)(ttd), 430 TerminalBlockHash: config.TerminalBlockHash, 431 TerminalBlockNumber: config.TerminalBlockNumber, 432 }, nil 433 } 434 return nil, errors.New("invalid terminal block hash") 435 } 436 return &engine.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil 437 } 438 439 // GetPayloadV1 returns a cached payload by id. 440 func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) { 441 if !payloadID.Is(engine.PayloadV1) { 442 return nil, engine.UnsupportedFork 443 } 444 data, err := api.getPayload(payloadID, false) 445 if err != nil { 446 return nil, err 447 } 448 return data.ExecutionPayload, nil 449 } 450 451 // GetPayloadV2 returns a cached payload by id. 452 func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { 453 if !payloadID.Is(engine.PayloadV1, engine.PayloadV2) { 454 return nil, engine.UnsupportedFork 455 } 456 return api.getPayload(payloadID, false) 457 } 458 459 // GetPayloadV3 returns a cached payload by id. 460 func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { 461 if !payloadID.Is(engine.PayloadV3) { 462 return nil, engine.UnsupportedFork 463 } 464 return api.getPayload(payloadID, false) 465 } 466 467 // GetPayloadV4 returns a cached payload by id. 468 func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { 469 if !payloadID.Is(engine.PayloadV3) { 470 return nil, engine.UnsupportedFork 471 } 472 return api.getPayload(payloadID, false) 473 } 474 475 // GetPayloadV5 returns a cached payload by id. 476 func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { 477 if !payloadID.Is(engine.PayloadV3) { 478 return nil, engine.UnsupportedFork 479 } 480 return api.getPayload(payloadID, false) 481 } 482 483 func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) { 484 log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) 485 data := api.localBlocks.get(payloadID, full) 486 if data == nil { 487 return nil, engine.UnknownPayload 488 } 489 return data, nil 490 } 491 492 // GetBlobsV1 returns a blob from the transaction pool. 493 func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) { 494 if len(hashes) > 128 { 495 return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) 496 } 497 var ( 498 res = make([]*engine.BlobAndProofV1, len(hashes)) 499 hasher = sha256.New() 500 index = make(map[common.Hash]int) 501 sidecars = api.eth.BlobTxPool().GetBlobs(hashes) 502 ) 503 504 for i, hash := range hashes { 505 index[hash] = i 506 } 507 for i, sidecar := range sidecars { 508 if res[i] != nil || sidecar == nil { 509 // already filled 510 continue 511 } 512 for cIdx, commitment := range sidecar.Commitments { 513 computed := kzg4844.CalcBlobHashV1(hasher, &commitment) 514 if idx, ok := index[computed]; ok { 515 res[idx] = &engine.BlobAndProofV1{ 516 Blob: sidecar.Blobs[cIdx][:], 517 Proof: sidecar.Proofs[cIdx][:], 518 } 519 } 520 } 521 } 522 return res, nil 523 } 524 525 // GetBlobsV2 returns a blob from the transaction pool. 526 func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) { 527 if len(hashes) > 128 { 528 return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) 529 } 530 531 available := api.eth.BlobTxPool().AvailableBlobs(hashes) 532 getBlobsRequestedCounter.Inc(int64(len(hashes))) 533 getBlobsAvailableCounter.Inc(int64(available)) 534 // Optimization: check first if all blobs are available, if not, return empty response 535 if available != len(hashes) { 536 getBlobsV2RequestMiss.Inc(1) 537 return nil, nil 538 } 539 getBlobsV2RequestHit.Inc(1) 540 541 // pull up the blob hashes 542 var ( 543 res = make([]*engine.BlobAndProofV2, len(hashes)) 544 index = make(map[common.Hash][]int) 545 sidecars = api.eth.BlobTxPool().GetBlobs(hashes) 546 ) 547 548 for i, hash := range hashes { 549 index[hash] = append(index[hash], i) 550 } 551 for i, sidecar := range sidecars { 552 if res[i] != nil { 553 // already filled 554 continue 555 } 556 if sidecar == nil { 557 // not found, return empty response 558 return nil, nil 559 } 560 if sidecar.Version != 1 { 561 log.Info("GetBlobs queried V0 transaction: index %v, blobhashes %v", index, sidecar.BlobHashes()) 562 return nil, nil 563 } 564 blobHashes := sidecar.BlobHashes() 565 for bIdx, hash := range blobHashes { 566 if idxes, ok := index[hash]; ok { 567 proofs := sidecar.CellProofsAt(bIdx) 568 var cellProofs []hexutil.Bytes 569 for _, proof := range proofs { 570 cellProofs = append(cellProofs, proof[:]) 571 } 572 for _, idx := range idxes { 573 res[idx] = &engine.BlobAndProofV2{ 574 Blob: sidecar.Blobs[bIdx][:], 575 CellProofs: cellProofs, 576 } 577 } 578 } 579 } 580 } 581 return res, nil 582 } 583 584 // Helper for NewPayload* methods. 585 var invalidStatus = engine.PayloadStatusV1{Status: engine.INVALID} 586 587 // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. 588 func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { 589 if params.Withdrawals != nil { 590 return invalidStatus, paramsErr("withdrawals not supported in V1") 591 } 592 return api.newPayload(params, nil, nil, nil, false) 593 } 594 595 // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. 596 func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { 597 var ( 598 cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp) 599 shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp) 600 ) 601 switch { 602 case cancun: 603 return invalidStatus, paramsErr("can't use newPayloadV2 post-cancun") 604 case shanghai && params.Withdrawals == nil: 605 return invalidStatus, paramsErr("nil withdrawals post-shanghai") 606 case !shanghai && params.Withdrawals != nil: 607 return invalidStatus, paramsErr("non-nil withdrawals pre-shanghai") 608 case params.ExcessBlobGas != nil: 609 return invalidStatus, paramsErr("non-nil excessBlobGas pre-cancun") 610 case params.BlobGasUsed != nil: 611 return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun") 612 } 613 return api.newPayload(params, nil, nil, nil, false) 614 } 615 616 // NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. 617 func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { 618 switch { 619 case params.Withdrawals == nil: 620 return invalidStatus, paramsErr("nil withdrawals post-shanghai") 621 case params.ExcessBlobGas == nil: 622 return invalidStatus, paramsErr("nil excessBlobGas post-cancun") 623 case params.BlobGasUsed == nil: 624 return invalidStatus, paramsErr("nil blobGasUsed post-cancun") 625 case versionedHashes == nil: 626 return invalidStatus, paramsErr("nil versionedHashes post-cancun") 627 case beaconRoot == nil: 628 return invalidStatus, paramsErr("nil beaconRoot post-cancun") 629 case !api.checkFork(params.Timestamp, forks.Cancun): 630 return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") 631 } 632 return api.newPayload(params, versionedHashes, beaconRoot, nil, false) 633 } 634 635 // NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. 636 func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) { 637 switch { 638 case params.Withdrawals == nil: 639 return invalidStatus, paramsErr("nil withdrawals post-shanghai") 640 case params.ExcessBlobGas == nil: 641 return invalidStatus, paramsErr("nil excessBlobGas post-cancun") 642 case params.BlobGasUsed == nil: 643 return invalidStatus, paramsErr("nil blobGasUsed post-cancun") 644 case versionedHashes == nil: 645 return invalidStatus, paramsErr("nil versionedHashes post-cancun") 646 case beaconRoot == nil: 647 return invalidStatus, paramsErr("nil beaconRoot post-cancun") 648 case executionRequests == nil: 649 return invalidStatus, paramsErr("nil executionRequests post-prague") 650 case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka): 651 return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") 652 } 653 requests := convertRequests(executionRequests) 654 if err := validateRequests(requests); err != nil { 655 return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) 656 } 657 return api.newPayload(params, versionedHashes, beaconRoot, requests, false) 658 } 659 660 func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) { 661 // The locking here is, strictly, not required. Without these locks, this can happen: 662 // 663 // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to 664 // api.eth.BlockChain().InsertBlockWithoutSetHead, where it is blocked on 665 // e.g database compaction. 666 // 2. The call times out on the CL layer, which issues another NewPayload (execdata-N) call. 667 // Similarly, this also get stuck on the same place. Importantly, since the 668 // first call has not gone through, the early checks for "do we already have this block" 669 // will all return false. 670 // 3. When the db compaction ends, then N calls inserting the same payload are processed 671 // sequentially. 672 // Hence, we use a lock here, to be sure that the previous call has finished before we 673 // check whether we already have the block locally. 674 api.newPayloadLock.Lock() 675 defer api.newPayloadLock.Unlock() 676 677 log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) 678 block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, requests) 679 if err != nil { 680 bgu := "nil" 681 if params.BlobGasUsed != nil { 682 bgu = strconv.Itoa(int(*params.BlobGasUsed)) 683 } 684 ebg := "nil" 685 if params.ExcessBlobGas != nil { 686 ebg = strconv.Itoa(int(*params.ExcessBlobGas)) 687 } 688 log.Warn("Invalid NewPayload params", 689 "params.Number", params.Number, 690 "params.ParentHash", params.ParentHash, 691 "params.BlockHash", params.BlockHash, 692 "params.StateRoot", params.StateRoot, 693 "params.FeeRecipient", params.FeeRecipient, 694 "params.LogsBloom", common.PrettyBytes(params.LogsBloom), 695 "params.Random", params.Random, 696 "params.GasLimit", params.GasLimit, 697 "params.GasUsed", params.GasUsed, 698 "params.Timestamp", params.Timestamp, 699 "params.ExtraData", common.PrettyBytes(params.ExtraData), 700 "params.BaseFeePerGas", params.BaseFeePerGas, 701 "params.BlobGasUsed", bgu, 702 "params.ExcessBlobGas", ebg, 703 "len(params.Transactions)", len(params.Transactions), 704 "len(params.Withdrawals)", len(params.Withdrawals), 705 "beaconRoot", beaconRoot, 706 "len(requests)", len(requests), 707 "error", err) 708 return api.invalid(err, nil), nil 709 } 710 // Stash away the last update to warn the user if the beacon client goes offline 711 api.lastNewPayloadUpdate.Store(time.Now().Unix()) 712 713 // If we already have the block locally, ignore the entire execution and just 714 // return a fake success. 715 if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil { 716 log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) 717 hash := block.Hash() 718 return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil 719 } 720 // If this block was rejected previously, keep rejecting it 721 if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil { 722 return *res, nil 723 } 724 // If the parent is missing, we - in theory - could trigger a sync, but that 725 // would also entail a reorg. That is problematic if multiple sibling blocks 726 // are being fed to us, and even more so, if some semi-distant uncle shortens 727 // our live chain. As such, payload execution will not permit reorgs and thus 728 // will not trigger a sync cycle. That is fine though, if we get a fork choice 729 // update after legit payload executions. 730 parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1) 731 if parent == nil { 732 return api.delayPayloadImport(block), nil 733 } 734 if block.Time() <= parent.Time() { 735 log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) 736 return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil 737 } 738 // Another corner case: if the node is in snap sync mode, but the CL client 739 // tries to make it import a block. That should be denied as pushing something 740 // into the database directly will conflict with the assumptions of snap sync 741 // that it has an empty db that it can fill itself. 742 if api.eth.SyncMode() != ethconfig.FullSync { 743 return api.delayPayloadImport(block), nil 744 } 745 if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { 746 api.remoteBlocks.put(block.Hash(), block.Header()) 747 log.Warn("State not available, ignoring new payload") 748 return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil 749 } 750 log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number()) 751 proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness) 752 if err != nil { 753 log.Warn("NewPayload: inserting block failed", "error", err) 754 755 api.invalidLock.Lock() 756 api.invalidBlocksHits[block.Hash()] = 1 757 api.invalidTipsets[block.Hash()] = block.Header() 758 api.invalidLock.Unlock() 759 760 return api.invalid(err, parent.Header()), nil 761 } 762 hash := block.Hash() 763 764 // If witness collection was requested, inject that into the result too 765 var ow *hexutil.Bytes 766 if proofs != nil { 767 ow = new(hexutil.Bytes) 768 *ow, _ = rlp.EncodeToBytes(proofs) 769 } 770 return engine.PayloadStatusV1{Status: engine.VALID, Witness: ow, LatestValidHash: &hash}, nil 771 } 772 773 // delayPayloadImport stashes the given block away for import at a later time, 774 // either via a forkchoice update or a sync extension. This method is meant to 775 // be called by the newpayload command when the block seems to be ok, but some 776 // prerequisite prevents it from being processed (e.g. no parent, or snap sync). 777 func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadStatusV1 { 778 // Sanity check that this block's parent is not on a previously invalidated 779 // chain. If it is, mark the block as invalid too. 780 if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil { 781 return *res 782 } 783 // Stash the block away for a potential forced forkchoice update to it 784 // at a later time. 785 api.remoteBlocks.put(block.Hash(), block.Header()) 786 787 // Although we don't want to trigger a sync, if there is one already in 788 // progress, try to extend it with the current payload request to relieve 789 // some strain from the forkchoice update. 790 err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()) 791 if err == nil { 792 log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash()) 793 return engine.PayloadStatusV1{Status: engine.SYNCING} 794 } 795 // Either no beacon sync was started yet, or it rejected the delivered 796 // payload as non-integratable on top of the existing sync. We'll just 797 // have to rely on the beacon client to forcefully update the head with 798 // a forkchoice update request. 799 if api.eth.SyncMode() == ethconfig.FullSync { 800 // In full sync mode, failure to import a well-formed block can only mean 801 // that the parent state is missing and the syncer rejected extending the 802 // current cycle with the new payload. 803 log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err) 804 } else { 805 // In non-full sync mode (i.e. snap sync) all payloads are rejected until 806 // snap sync terminates as snap sync relies on direct database injections 807 // and cannot afford concurrent out-if-band modifications via imports. 808 log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err) 809 } 810 return engine.PayloadStatusV1{Status: engine.SYNCING} 811 } 812 813 // setInvalidAncestor is a callback for the downloader to notify us if a bad block 814 // is encountered during the async sync. 815 func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) { 816 api.invalidLock.Lock() 817 defer api.invalidLock.Unlock() 818 819 api.invalidTipsets[origin.Hash()] = invalid 820 api.invalidBlocksHits[invalid.Hash()]++ 821 } 822 823 // checkInvalidAncestor checks whether the specified chain end links to a known 824 // bad ancestor. If yes, it constructs the payload failure response to return. 825 func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *engine.PayloadStatusV1 { 826 api.invalidLock.Lock() 827 defer api.invalidLock.Unlock() 828 829 // If the hash to check is unknown, return valid 830 invalid, ok := api.invalidTipsets[check] 831 if !ok { 832 return nil 833 } 834 // If the bad hash was hit too many times, evict it and try to reprocess in 835 // the hopes that we have a data race that we can exit out of. 836 badHash := invalid.Hash() 837 838 api.invalidBlocksHits[badHash]++ 839 if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction { 840 log.Error("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash) 841 delete(api.invalidBlocksHits, badHash) 842 843 for descendant, badHeader := range api.invalidTipsets { 844 if badHeader.Hash() == badHash { 845 delete(api.invalidTipsets, descendant) 846 } 847 } 848 return nil 849 } 850 // Not too many failures yet, mark the head of the invalid chain as invalid 851 if check != head { 852 log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash) 853 for len(api.invalidTipsets) >= invalidTipsetsCap { 854 for key := range api.invalidTipsets { 855 delete(api.invalidTipsets, key) 856 break 857 } 858 } 859 api.invalidTipsets[head] = invalid 860 } 861 // If the last valid hash is the terminal pow block, return 0x0 for latest valid hash 862 lastValid := &invalid.ParentHash 863 if header := api.eth.BlockChain().GetHeader(invalid.ParentHash, invalid.Number.Uint64()-1); header != nil && header.Difficulty.Sign() != 0 { 864 lastValid = &common.Hash{} 865 } 866 failure := "links to previously rejected block" 867 return &engine.PayloadStatusV1{ 868 Status: engine.INVALID, 869 LatestValidHash: lastValid, 870 ValidationError: &failure, 871 } 872 } 873 874 // invalid returns a response "INVALID" with the latest valid hash supplied by latest. 875 func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.PayloadStatusV1 { 876 var currentHash *common.Hash 877 if latestValid != nil { 878 if latestValid.Difficulty.BitLen() != 0 { 879 // Set latest valid hash to 0x0 if parent is PoW block 880 currentHash = &common.Hash{} 881 } else { 882 // Otherwise set latest valid hash to parent hash 883 h := latestValid.Hash() 884 currentHash = &h 885 } 886 } 887 errorMsg := err.Error() 888 return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: currentHash, ValidationError: &errorMsg} 889 } 890 891 // heartbeat loops indefinitely, and checks if there have been beacon client updates 892 // received in the last while. If not - or if they but strange ones - it warns the 893 // user that something might be off with their consensus node. 894 // 895 // TODO(karalabe): Spin this goroutine down somehow 896 func (api *ConsensusAPI) heartbeat() { 897 // Sleep a bit on startup since there's obviously no beacon client yet 898 // attached, so no need to print scary warnings to the user. 899 time.Sleep(beaconUpdateStartupTimeout) 900 901 // If the network is not yet merged/merging, don't bother continuing. 902 if api.config().TerminalTotalDifficulty == nil { 903 return 904 } 905 906 var offlineLogged time.Time 907 908 for { 909 // Sleep a bit and retrieve the last known consensus updates 910 time.Sleep(5 * time.Second) 911 912 lastTransitionUpdate := time.Unix(api.lastTransitionUpdate.Load(), 0) 913 lastForkchoiceUpdate := time.Unix(api.lastForkchoiceUpdate.Load(), 0) 914 lastNewPayloadUpdate := time.Unix(api.lastNewPayloadUpdate.Load(), 0) 915 916 // If there have been no updates for the past while, warn the user 917 // that the beacon client is probably offline 918 if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout { 919 offlineLogged = time.Time{} 920 continue 921 } 922 if time.Since(offlineLogged) > beaconUpdateWarnFrequency { 923 if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() { 924 if lastTransitionUpdate.IsZero() { 925 log.Warn("Post-merge network, but no beacon client seen. Please launch one to follow the chain!") 926 } else { 927 log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!") 928 } 929 } else { 930 log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!") 931 } 932 offlineLogged = time.Now() 933 } 934 continue 935 } 936 } 937 938 // config retrieves the chain's fork configuration. 939 func (api *ConsensusAPI) config() *params.ChainConfig { 940 return api.eth.BlockChain().Config() 941 } 942 943 // checkFork returns true if the latest fork at the given timestamp 944 // is one of the forks provided. 945 func (api *ConsensusAPI) checkFork(timestamp uint64, forks ...forks.Fork) bool { 946 latest := api.config().LatestFork(timestamp) 947 for _, fork := range forks { 948 if latest == fork { 949 return true 950 } 951 } 952 return false 953 } 954 955 // ExchangeCapabilities returns the current methods provided by this node. 956 func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { 957 return caps 958 } 959 960 // GetClientVersionV1 exchanges client version data of this node. 961 func (api *ConsensusAPI) GetClientVersionV1(info engine.ClientVersionV1) []engine.ClientVersionV1 { 962 log.Trace("Engine API request received", "method", "GetClientVersionV1", "info", info.String()) 963 commit := make([]byte, 4) 964 if vcs, ok := version.VCS(); ok { 965 commit = common.FromHex(vcs.Commit)[0:4] 966 } 967 return []engine.ClientVersionV1{ 968 { 969 Code: engine.ClientCode, 970 Name: engine.ClientName, 971 Version: version.WithMeta, 972 Commit: hexutil.Encode(commit), 973 }, 974 } 975 } 976 977 // GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list 978 // of block bodies by the engine api. 979 func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBody { 980 bodies := make([]*engine.ExecutionPayloadBody, len(hashes)) 981 for i, hash := range hashes { 982 block := api.eth.BlockChain().GetBlockByHash(hash) 983 bodies[i] = getBody(block) 984 } 985 return bodies 986 } 987 988 // GetPayloadBodiesByHashV2 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list 989 // of block bodies by the engine api. 990 func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engine.ExecutionPayloadBody { 991 bodies := make([]*engine.ExecutionPayloadBody, len(hashes)) 992 for i, hash := range hashes { 993 block := api.eth.BlockChain().GetBlockByHash(hash) 994 bodies[i] = getBody(block) 995 } 996 return bodies 997 } 998 999 // GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range 1000 // of block bodies by the engine api. 1001 func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) { 1002 return api.getBodiesByRange(start, count) 1003 } 1004 1005 // GetPayloadBodiesByRangeV2 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range 1006 // of block bodies by the engine api. 1007 func (api *ConsensusAPI) GetPayloadBodiesByRangeV2(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) { 1008 return api.getBodiesByRange(start, count) 1009 } 1010 1011 func (api *ConsensusAPI) getBodiesByRange(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) { 1012 if start == 0 || count == 0 { 1013 return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count)) 1014 } 1015 if count > 1024 { 1016 return nil, engine.TooLargeRequest.With(fmt.Errorf("requested count too large: %v", count)) 1017 } 1018 // limit count up until current 1019 current := api.eth.BlockChain().CurrentBlock().Number.Uint64() 1020 last := uint64(start) + uint64(count) - 1 1021 if last > current { 1022 last = current 1023 } 1024 bodies := make([]*engine.ExecutionPayloadBody, 0, uint64(count)) 1025 for i := uint64(start); i <= last; i++ { 1026 block := api.eth.BlockChain().GetBlockByNumber(i) 1027 bodies = append(bodies, getBody(block)) 1028 } 1029 return bodies, nil 1030 } 1031 1032 func getBody(block *types.Block) *engine.ExecutionPayloadBody { 1033 if block == nil { 1034 return nil 1035 } 1036 1037 var result engine.ExecutionPayloadBody 1038 1039 result.TransactionData = make([]hexutil.Bytes, len(block.Transactions())) 1040 for j, tx := range block.Transactions() { 1041 result.TransactionData[j], _ = tx.MarshalBinary() 1042 } 1043 1044 // Post-shanghai withdrawals MUST be set to empty slice instead of nil 1045 result.Withdrawals = block.Withdrawals() 1046 if block.Withdrawals() == nil && block.Header().WithdrawalsHash != nil { 1047 result.Withdrawals = []*types.Withdrawal{} 1048 } 1049 1050 return &result 1051 } 1052 1053 // convertRequests converts a hex requests slice to plain [][]byte. 1054 func convertRequests(hex []hexutil.Bytes) [][]byte { 1055 if hex == nil { 1056 return nil 1057 } 1058 req := make([][]byte, len(hex)) 1059 for i := range hex { 1060 req[i] = hex[i] 1061 } 1062 return req 1063 } 1064 1065 // validateRequests checks that requests are ordered by their type and are not empty. 1066 func validateRequests(requests [][]byte) error { 1067 for i, req := range requests { 1068 // No empty requests. 1069 if len(req) < 2 { 1070 return fmt.Errorf("empty request: %v", req) 1071 } 1072 // Check that requests are ordered by their type. 1073 // Each type must appear only once. 1074 if i > 0 && req[0] <= requests[i-1][0] { 1075 return fmt.Errorf("invalid request order: %v", req) 1076 } 1077 } 1078 return nil 1079 } 1080 1081 // paramsErr is a helper function for creating an InvalidPayloadAttributes 1082 // Engine API error. 1083 func paramsErr(msg string) error { 1084 return engine.InvalidParams.With(errors.New(msg)) 1085 } 1086 1087 // attributesErr is a helper function for creating an InvalidPayloadAttributes 1088 // Engine API error. 1089 func attributesErr(msg string) error { 1090 return engine.InvalidPayloadAttributes.With(errors.New(msg)) 1091 } 1092 1093 // unsupportedForkErr is a helper function for creating an UnsupportedFork 1094 // Engine API error. 1095 func unsupportedForkErr(msg string) error { 1096 return engine.UnsupportedFork.With(errors.New(msg)) 1097 }