github.com/theQRL/go-zond@v0.2.1/zond/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 "errors" 22 "fmt" 23 "sync" 24 "time" 25 26 "github.com/theQRL/go-zond/beacon/engine" 27 "github.com/theQRL/go-zond/common" 28 "github.com/theQRL/go-zond/common/hexutil" 29 "github.com/theQRL/go-zond/core/rawdb" 30 "github.com/theQRL/go-zond/core/types" 31 "github.com/theQRL/go-zond/log" 32 "github.com/theQRL/go-zond/miner" 33 "github.com/theQRL/go-zond/node" 34 "github.com/theQRL/go-zond/rpc" 35 "github.com/theQRL/go-zond/zond" 36 "github.com/theQRL/go-zond/zond/downloader" 37 ) 38 39 // Register adds the engine API to the full node. 40 func Register(stack *node.Node, backend *zond.Zond) error { 41 log.Warn("Engine API enabled", "protocol", "zond") 42 stack.RegisterAPIs([]rpc.API{ 43 { 44 Namespace: "engine", 45 Service: NewConsensusAPI(backend), 46 Authenticated: true, 47 }, 48 }) 49 return nil 50 } 51 52 const ( 53 // invalidBlockHitEviction is the number of times an invalid block can be 54 // referenced in forkchoice update or new payload before it is attempted 55 // to be reprocessed again. 56 invalidBlockHitEviction = 128 57 58 // invalidTipsetsCap is the max number of recent block hashes tracked that 59 // have lead to some bad ancestor block. It's just an OOM protection. 60 invalidTipsetsCap = 512 61 62 // beaconUpdateStartupTimeout is the time to wait for a beacon client to get 63 // attached before starting to issue warnings. 64 beaconUpdateStartupTimeout = 30 * time.Second 65 66 // beaconUpdateConsensusTimeout is the max time allowed for a beacon client 67 // to send a consensus update before it's considered offline and the user is 68 // warned. 69 beaconUpdateConsensusTimeout = 2 * time.Minute 70 71 // beaconUpdateWarnFrequency is the frequency at which to warn the user that 72 // the beacon client is offline. 73 beaconUpdateWarnFrequency = 5 * time.Minute 74 ) 75 76 // All methods provided over the engine endpoint. 77 var caps = []string{ 78 "engine_forkchoiceUpdatedV2", 79 "engine_getPayloadV2", 80 "engine_newPayloadV2", 81 "engine_getPayloadBodiesByHashV1", 82 "engine_getPayloadBodiesByRangeV1", 83 } 84 85 type ConsensusAPI struct { 86 zond *zond.Zond 87 88 remoteBlocks *headerQueue // Cache of remote payloads received 89 localBlocks *payloadQueue // Cache of local payloads generated 90 91 // The forkchoice update and new payload method require us to return the 92 // latest valid hash in an invalid chain. To support that return, we need 93 // to track historical bad blocks as well as bad tipsets in case a chain 94 // is constantly built on it. 95 // 96 // There are a few important caveats in this mechanism: 97 // - The bad block tracking is ephemeral, in-memory only. We must never 98 // persist any bad block information to disk as a bug in Gzond could end 99 // up blocking a valid chain, even if a later Gzond update would accept 100 // it. 101 // - Bad blocks will get forgotten after a certain threshold of import 102 // attempts and will be retried. The rationale is that if the network 103 // really-really-really tries to feed us a block, we should give it a 104 // new chance, perhaps us being racey instead of the block being legit 105 // bad (this happened in Gzond at a point with import vs. pending race). 106 // - Tracking all the blocks built on top of the bad one could be a bit 107 // problematic, so we will only track the head chain segment of a bad 108 // chain to allow discarding progressing bad chains and side chains, 109 // without tracking too much bad data. 110 invalidBlocksHits map[common.Hash]int // Ephemeral cache to track invalid blocks and their hit count 111 invalidTipsets map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor 112 invalidLock sync.Mutex // Protects the invalid maps from concurrent access 113 114 // Gzond can appear to be stuck or do strange things if the beacon client is 115 // offline or is sending us strange data. Stash some update stats away so 116 // that we can warn the user and not have them open issues on our tracker. 117 lastTransitionUpdate time.Time 118 lastTransitionLock sync.Mutex 119 lastForkchoiceUpdate time.Time 120 lastForkchoiceLock sync.Mutex 121 lastNewPayloadUpdate time.Time 122 lastNewPayloadLock sync.Mutex 123 124 forkchoiceLock sync.Mutex // Lock for the forkChoiceUpdated method 125 newPayloadLock sync.Mutex // Lock for the NewPayload method 126 } 127 128 // NewConsensusAPI creates a new consensus api for the given backend. 129 // The underlying blockchain needs to have a valid terminal total difficulty set. 130 func NewConsensusAPI(zond *zond.Zond) *ConsensusAPI { 131 api := newConsensusAPIWithoutHeartbeat(zond) 132 go api.heartbeat() 133 return api 134 } 135 136 // newConsensusAPIWithoutHeartbeat creates a new consensus api for the SimulatedBeacon Node. 137 func newConsensusAPIWithoutHeartbeat(zond *zond.Zond) *ConsensusAPI { 138 api := &ConsensusAPI{ 139 zond: zond, 140 remoteBlocks: newHeaderQueue(), 141 localBlocks: newPayloadQueue(), 142 invalidBlocksHits: make(map[common.Hash]int), 143 invalidTipsets: make(map[common.Hash]*types.Header), 144 } 145 zond.Downloader().SetBadBlockCallback(api.setInvalidAncestor) 146 return api 147 } 148 149 // ForkchoiceUpdatedV2 has several responsibilities: 150 // 151 // We try to set our blockchain to the headBlock. 152 // 153 // If the method is called with an empty head block: we return success, which can be used 154 // to check if the engine API is enabled. 155 // 156 // If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db, 157 // if not we start a sync. 158 // 159 // If there are payloadAttributes: we try to assemble a block with the payloadAttributes 160 // and return its payloadID. 161 // ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes. 162 func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { 163 if params != nil && params.Withdrawals == nil { 164 return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals")) 165 } 166 return api.forkchoiceUpdated(update, params, false) 167 } 168 169 func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, simulatorMode bool) (engine.ForkChoiceResponse, error) { 170 api.forkchoiceLock.Lock() 171 defer api.forkchoiceLock.Unlock() 172 173 log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) 174 if update.HeadBlockHash == (common.Hash{}) { 175 log.Warn("Forkchoice requested update to zero hash") 176 return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? 177 } 178 // Stash away the last update to warn the user if the beacon client goes offline 179 api.lastForkchoiceLock.Lock() 180 api.lastForkchoiceUpdate = time.Now() 181 api.lastForkchoiceLock.Unlock() 182 183 // Check whether we have the block yet in our database or not. If not, we'll 184 // need to either trigger a sync, or to reject this forkchoice update for a 185 // reason. 186 block := api.zond.BlockChain().GetBlockByHash(update.HeadBlockHash) 187 if block == nil { 188 // If this block was previously invalidated, keep rejecting it here too 189 if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil { 190 return engine.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil 191 } 192 // If the head hash is unknown (was not given to us in a newPayload request), 193 // we cannot resolve the header, so not much to do. This could be extended in 194 // the future to resolve from the `zond` network, but it's an unexpected case 195 // that should be fixed, not papered over. 196 header := api.remoteBlocks.get(update.HeadBlockHash) 197 if header == nil { 198 log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash) 199 return engine.STATUS_SYNCING, nil 200 } 201 // If the finalized hash is known, we can direct the downloader to move 202 // potentially more data to the freezer from the get go. 203 finalized := api.remoteBlocks.get(update.FinalizedBlockHash) 204 205 // Header advertised via a past newPayload request. Start syncing to it. 206 context := []interface{}{"number", header.Number, "hash", header.Hash()} 207 if update.FinalizedBlockHash != (common.Hash{}) { 208 if finalized == nil { 209 context = append(context, []interface{}{"finalized", "unknown"}...) 210 } else { 211 context = append(context, []interface{}{"finalized", finalized.Number}...) 212 } 213 } 214 log.Info("Forkchoice requested sync to new head", context...) 215 if err := api.zond.Downloader().BeaconSync(api.zond.SyncMode(), header, finalized); err != nil { 216 return engine.STATUS_SYNCING, err 217 } 218 return engine.STATUS_SYNCING, nil 219 } 220 221 valid := func(id *engine.PayloadID) engine.ForkChoiceResponse { 222 return engine.ForkChoiceResponse{ 223 PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &update.HeadBlockHash}, 224 PayloadID: id, 225 } 226 } 227 if rawdb.ReadCanonicalHash(api.zond.ChainDb(), block.NumberU64()) != update.HeadBlockHash { 228 // Block is not canonical, set head. 229 if latestValid, err := api.zond.BlockChain().SetCanonical(block); err != nil { 230 return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err 231 } 232 } else if api.zond.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash { 233 // If the specified head matches with our local head, do nothing and keep 234 // generating the payload. It's a special corner case that a few slots are 235 // missing and we are requested to generate the payload in slot. 236 } else { 237 // If the head block is already in our canonical chain, the beacon client is 238 // probably resyncing. Ignore the update. 239 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.zond.BlockChain().CurrentBlock().Number) 240 return valid(nil), nil 241 } 242 api.zond.SetSynced() 243 244 // If the beacon client also advertised a finalized block, mark the local 245 // chain final and completely in PoS mode. 246 if update.FinalizedBlockHash != (common.Hash{}) { 247 // If the finalized block is not in our canonical tree, something is wrong 248 finalBlock := api.zond.BlockChain().GetBlockByHash(update.FinalizedBlockHash) 249 if finalBlock == nil { 250 log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash) 251 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database")) 252 } else if rawdb.ReadCanonicalHash(api.zond.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash { 253 log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash) 254 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain")) 255 } 256 // Set the finalized block 257 api.zond.BlockChain().SetFinalized(finalBlock.Header()) 258 } 259 // Check if the safe block hash is in our canonical tree, if not something is wrong 260 if update.SafeBlockHash != (common.Hash{}) { 261 safeBlock := api.zond.BlockChain().GetBlockByHash(update.SafeBlockHash) 262 if safeBlock == nil { 263 log.Warn("Safe block not available in database") 264 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database")) 265 } 266 if rawdb.ReadCanonicalHash(api.zond.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash { 267 log.Warn("Safe block not in canonical chain") 268 return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) 269 } 270 // Set the safe block 271 api.zond.BlockChain().SetSafe(safeBlock.Header()) 272 } 273 // If payload generation was requested, create a new block to be potentially 274 // sealed by the beacon client. The payload will be requested later, and we 275 // will replace it arbitrarily many times in between. 276 if payloadAttributes != nil { 277 args := &miner.BuildPayloadArgs{ 278 Parent: update.HeadBlockHash, 279 Timestamp: payloadAttributes.Timestamp, 280 FeeRecipient: payloadAttributes.SuggestedFeeRecipient, 281 Random: payloadAttributes.Random, 282 Withdrawals: payloadAttributes.Withdrawals, 283 } 284 id := args.Id() 285 // If we already are busy generating this work, then we do not need 286 // to start a second process. 287 if api.localBlocks.has(id) { 288 return valid(&id), nil 289 } 290 // If the beacon chain is ran by a simulator, then transaction insertion, 291 // block insertion and block production will happen without any timing 292 // delay between them. This will cause flaky simulator executions due to 293 // the transaction pool running its internal reset operation on a back- 294 // ground thread. To avoid the racey behavior - in simulator mode - the 295 // pool will be explicitly blocked on its reset before continuing to the 296 // block production below. 297 if simulatorMode { 298 if err := api.zond.TxPool().Sync(); err != nil { 299 log.Error("Failed to sync transaction pool", "err", err) 300 return valid(nil), engine.InvalidPayloadAttributes.With(err) 301 } 302 } 303 payload, err := api.zond.Miner().BuildPayload(args) 304 if err != nil { 305 log.Error("Failed to build payload", "err", err) 306 return valid(nil), engine.InvalidPayloadAttributes.With(err) 307 } 308 api.localBlocks.put(id, payload) 309 return valid(&id), nil 310 } 311 return valid(nil), nil 312 } 313 314 // GetPayloadV2 returns a cached payload by id. 315 func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { 316 return api.getPayload(payloadID, false) 317 } 318 319 func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) { 320 log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) 321 data := api.localBlocks.get(payloadID, full) 322 if data == nil { 323 return nil, engine.UnknownPayload 324 } 325 return data, nil 326 } 327 328 // NewPayloadV2 creates a Zond execution block, inserts it in the chain, and returns the status of the chain. 329 func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { 330 if params.Withdrawals == nil { 331 return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) 332 } 333 334 return api.newPayload(params) 335 } 336 337 func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.PayloadStatusV1, error) { 338 // The locking here is, strictly, not required. Without these locks, this can happen: 339 // 340 // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to 341 // api.eth.BlockChain().InsertBlockWithoutSetHead, where it is blocked on 342 // e.g database compaction. 343 // 2. The call times out on the CL layer, which issues another NewPayload (execdata-N) call. 344 // Similarly, this also get stuck on the same place. Importantly, since the 345 // first call has not gone through, the early checks for "do we already have this block" 346 // will all return false. 347 // 3. When the db compaction ends, then N calls inserting the same payload are processed 348 // sequentially. 349 // Hence, we use a lock here, to be sure that the previous call has finished before we 350 // check whether we already have the block locally. 351 api.newPayloadLock.Lock() 352 defer api.newPayloadLock.Unlock() 353 354 log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) 355 block, err := engine.ExecutableDataToBlock(params) 356 if err != nil { 357 log.Warn("Invalid NewPayload params", 358 "params.Number", params.Number, 359 "params.ParentHash", params.ParentHash, 360 "params.BlockHash", params.BlockHash, 361 "params.StateRoot", params.StateRoot, 362 "params.FeeRecipient", params.FeeRecipient, 363 "params.LogsBloom", common.PrettyBytes(params.LogsBloom), 364 "params.Random", params.Random, 365 "params.GasLimit", params.GasLimit, 366 "params.GasUsed", params.GasUsed, 367 "params.Timestamp", params.Timestamp, 368 "params.ExtraData", common.PrettyBytes(params.ExtraData), 369 "params.BaseFeePerGas", params.BaseFeePerGas, 370 "len(params.Transactions)", len(params.Transactions), 371 "len(params.Withdrawals)", len(params.Withdrawals), 372 "error", err) 373 return api.invalid(err, nil), nil 374 } 375 // Stash away the last update to warn the user if the beacon client goes offline 376 api.lastNewPayloadLock.Lock() 377 api.lastNewPayloadUpdate = time.Now() 378 api.lastNewPayloadLock.Unlock() 379 380 // If we already have the block locally, ignore the entire execution and just 381 // return a fake success. 382 if block := api.zond.BlockChain().GetBlockByHash(params.BlockHash); block != nil { 383 log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) 384 hash := block.Hash() 385 return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil 386 } 387 // If this block was rejected previously, keep rejecting it 388 if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil { 389 return *res, nil 390 } 391 // If the parent is missing, we - in theory - could trigger a sync, but that 392 // would also entail a reorg. That is problematic if multiple sibling blocks 393 // are being fed to us, and even more so, if some semi-distant uncle shortens 394 // our live chain. As such, payload execution will not permit reorgs and thus 395 // will not trigger a sync cycle. That is fine though, if we get a fork choice 396 // update after legit payload executions. 397 parent := api.zond.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1) 398 if parent == nil { 399 return api.delayPayloadImport(block), nil 400 } 401 // We have an existing parent, do some sanity checks to avoid the beacon client 402 // triggering too early 403 if block.Time() <= parent.Time() { 404 log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) 405 return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil 406 } 407 // Another cornercase: if the node is in snap sync mode, but the CL client 408 // tries to make it import a block. That should be denied as pushing something 409 // into the database directly will conflict with the assumptions of snap sync 410 // that it has an empty db that it can fill itself. 411 if api.zond.SyncMode() != downloader.FullSync { 412 return api.delayPayloadImport(block), nil 413 } 414 if !api.zond.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { 415 api.remoteBlocks.put(block.Hash(), block.Header()) 416 log.Warn("State not available, ignoring new payload") 417 return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil 418 } 419 log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number) 420 if err := api.zond.BlockChain().InsertBlockWithoutSetHead(block); err != nil { 421 log.Warn("NewPayloadV1: inserting block failed", "error", err) 422 423 api.invalidLock.Lock() 424 api.invalidBlocksHits[block.Hash()] = 1 425 api.invalidTipsets[block.Hash()] = block.Header() 426 api.invalidLock.Unlock() 427 428 return api.invalid(err, parent.Header()), nil 429 } 430 hash := block.Hash() 431 return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil 432 } 433 434 // delayPayloadImport stashes the given block away for import at a later time, 435 // either via a forkchoice update or a sync extension. This method is meant to 436 // be called by the newpayload command when the block seems to be ok, but some 437 // prerequisite prevents it from being processed (e.g. no parent, or snap sync). 438 func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadStatusV1 { 439 // Sanity check that this block's parent is not on a previously invalidated 440 // chain. If it is, mark the block as invalid too. 441 if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil { 442 return *res 443 } 444 // Stash the block away for a potential forced forkchoice update to it 445 // at a later time. 446 api.remoteBlocks.put(block.Hash(), block.Header()) 447 448 // Although we don't want to trigger a sync, if there is one already in 449 // progress, try to extend it with the current payload request to relieve 450 // some strain from the forkchoice update. 451 err := api.zond.Downloader().BeaconExtend(api.zond.SyncMode(), block.Header()) 452 if err == nil { 453 log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash()) 454 return engine.PayloadStatusV1{Status: engine.SYNCING} 455 } 456 // Either no beacon sync was started yet, or it rejected the delivered 457 // payload as non-integratable on top of the existing sync. We'll just 458 // have to rely on the beacon client to forcefully update the head with 459 // a forkchoice update request. 460 if api.zond.SyncMode() == downloader.FullSync { 461 // In full sync mode, failure to import a well-formed block can only mean 462 // that the parent state is missing and the syncer rejected extending the 463 // current cycle with the new payload. 464 log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err) 465 } else { 466 // In non-full sync mode (i.e. snap sync) all payloads are rejected until 467 // snap sync terminates as snap sync relies on direct database injections 468 // and cannot afford concurrent out-if-band modifications via imports. 469 log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err) 470 } 471 return engine.PayloadStatusV1{Status: engine.SYNCING} 472 } 473 474 // setInvalidAncestor is a callback for the downloader to notify us if a bad block 475 // is encountered during the async sync. 476 func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) { 477 api.invalidLock.Lock() 478 defer api.invalidLock.Unlock() 479 480 api.invalidTipsets[origin.Hash()] = invalid 481 api.invalidBlocksHits[invalid.Hash()]++ 482 } 483 484 // checkInvalidAncestor checks whether the specified chain end links to a known 485 // bad ancestor. If yes, it constructs the payload failure response to return. 486 func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *engine.PayloadStatusV1 { 487 api.invalidLock.Lock() 488 defer api.invalidLock.Unlock() 489 490 // If the hash to check is unknown, return valid 491 invalid, ok := api.invalidTipsets[check] 492 if !ok { 493 return nil 494 } 495 // If the bad hash was hit too many times, evict it and try to reprocess in 496 // the hopes that we have a data race that we can exit out of. 497 badHash := invalid.Hash() 498 499 api.invalidBlocksHits[badHash]++ 500 if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction { 501 log.Warn("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash) 502 delete(api.invalidBlocksHits, badHash) 503 504 for descendant, badHeader := range api.invalidTipsets { 505 if badHeader.Hash() == badHash { 506 delete(api.invalidTipsets, descendant) 507 } 508 } 509 return nil 510 } 511 // Not too many failures yet, mark the head of the invalid chain as invalid 512 if check != head { 513 log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash) 514 for len(api.invalidTipsets) >= invalidTipsetsCap { 515 for key := range api.invalidTipsets { 516 delete(api.invalidTipsets, key) 517 break 518 } 519 } 520 api.invalidTipsets[head] = invalid 521 } 522 lastValid := &invalid.ParentHash 523 failure := "links to previously rejected block" 524 return &engine.PayloadStatusV1{ 525 Status: engine.INVALID, 526 LatestValidHash: lastValid, 527 ValidationError: &failure, 528 } 529 } 530 531 // invalid returns a response "INVALID" with the latest valid hash supplied by latest. 532 func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.PayloadStatusV1 { 533 var currentHash *common.Hash 534 if latestValid != nil { 535 h := latestValid.Hash() 536 currentHash = &h 537 } 538 errorMsg := err.Error() 539 return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: currentHash, ValidationError: &errorMsg} 540 } 541 542 // heartbeat loops indefinitely, and checks if there have been beacon client updates 543 // received in the last while. If not - or if they but strange ones - it warns the 544 // user that something might be off with their consensus node. 545 // 546 // TODO(karalabe): Spin this goroutine down somehow 547 func (api *ConsensusAPI) heartbeat() { 548 // Sleep a bit on startup since there's obviously no beacon client yet 549 // attached, so no need to print scary warnings to the user. 550 time.Sleep(beaconUpdateStartupTimeout) 551 552 var offlineLogged time.Time 553 554 for { 555 // Sleep a bit and retrieve the last known consensus updates 556 time.Sleep(5 * time.Second) 557 558 api.lastTransitionLock.Lock() 559 lastTransitionUpdate := api.lastTransitionUpdate 560 api.lastTransitionLock.Unlock() 561 562 api.lastForkchoiceLock.Lock() 563 lastForkchoiceUpdate := api.lastForkchoiceUpdate 564 api.lastForkchoiceLock.Unlock() 565 566 api.lastNewPayloadLock.Lock() 567 lastNewPayloadUpdate := api.lastNewPayloadUpdate 568 api.lastNewPayloadLock.Unlock() 569 570 // If there have been no updates for the past while, warn the user 571 // that the beacon client is probably offline 572 if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout { 573 offlineLogged = time.Time{} 574 continue 575 } 576 577 if time.Since(offlineLogged) > beaconUpdateWarnFrequency { 578 if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() { 579 if lastTransitionUpdate.IsZero() { 580 log.Warn("No beacon client seen. Please launch one to follow the chain!") 581 } else { 582 log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!") 583 } 584 } else { 585 log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!") 586 } 587 offlineLogged = time.Now() 588 } 589 continue 590 } 591 } 592 593 // ExchangeCapabilities returns the current methods provided by this node. 594 func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { 595 return caps 596 } 597 598 // GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list 599 // of block bodies by the engine api. 600 func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBodyV1 { 601 bodies := make([]*engine.ExecutionPayloadBodyV1, len(hashes)) 602 for i, hash := range hashes { 603 block := api.zond.BlockChain().GetBlockByHash(hash) 604 bodies[i] = getBody(block) 605 } 606 return bodies 607 } 608 609 // GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range 610 // of block bodies by the engine api. 611 func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV1, error) { 612 if start == 0 || count == 0 { 613 return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count)) 614 } 615 if count > 1024 { 616 return nil, engine.TooLargeRequest.With(fmt.Errorf("requested count too large: %v", count)) 617 } 618 // limit count up until current 619 current := api.zond.BlockChain().CurrentBlock().Number.Uint64() 620 last := uint64(start) + uint64(count) - 1 621 if last > current { 622 last = current 623 } 624 bodies := make([]*engine.ExecutionPayloadBodyV1, 0, uint64(count)) 625 for i := uint64(start); i <= last; i++ { 626 block := api.zond.BlockChain().GetBlockByNumber(i) 627 bodies = append(bodies, getBody(block)) 628 } 629 return bodies, nil 630 } 631 632 func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 { 633 if block == nil { 634 return nil 635 } 636 637 var ( 638 body = block.Body() 639 txs = make([]hexutil.Bytes, len(body.Transactions)) 640 withdrawals = body.Withdrawals 641 ) 642 643 for j, tx := range body.Transactions { 644 data, _ := tx.MarshalBinary() 645 txs[j] = hexutil.Bytes(data) 646 } 647 648 // Post-shanghai withdrawals MUST be set to empty slice instead of nil 649 if withdrawals == nil && block.Header().WithdrawalsHash != nil { 650 withdrawals = make([]*types.Withdrawal, 0) 651 } 652 653 return &engine.ExecutionPayloadBodyV1{ 654 TransactionData: txs, 655 Withdrawals: withdrawals, 656 } 657 }