github.com/Cleverse/go-ethereum@v0.0.0-20220927095127-45113064e7f2/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 "encoding/binary" 23 "errors" 24 "fmt" 25 "sync" 26 "time" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 "github.com/ethereum/go-ethereum/core/beacon" 31 "github.com/ethereum/go-ethereum/core/rawdb" 32 "github.com/ethereum/go-ethereum/core/types" 33 "github.com/ethereum/go-ethereum/eth" 34 "github.com/ethereum/go-ethereum/eth/downloader" 35 "github.com/ethereum/go-ethereum/log" 36 "github.com/ethereum/go-ethereum/node" 37 "github.com/ethereum/go-ethereum/rpc" 38 ) 39 40 // Register adds the engine API to the full node. 41 func Register(stack *node.Node, backend *eth.Ethereum) error { 42 log.Warn("Engine API enabled", "protocol", "eth") 43 stack.RegisterAPIs([]rpc.API{ 44 { 45 Namespace: "engine", 46 Service: NewConsensusAPI(backend), 47 Authenticated: true, 48 }, 49 }) 50 return nil 51 } 52 53 const ( 54 // invalidBlockHitEviction is the number of times an invalid block can be 55 // referenced in forkchoice update or new payload before it is attempted 56 // to be reprocessed again. 57 invalidBlockHitEviction = 128 58 59 // invalidTipsetsCap is the max number of recent block hashes tracked that 60 // have lead to some bad ancestor block. It's just an OOM protection. 61 invalidTipsetsCap = 512 62 ) 63 64 type ConsensusAPI struct { 65 eth *eth.Ethereum 66 67 remoteBlocks *headerQueue // Cache of remote payloads received 68 localBlocks *payloadQueue // Cache of local payloads generated 69 70 // The forkchoice update and new payload method require us to return the 71 // latest valid hash in an invalid chain. To support that return, we need 72 // to track historical bad blocks as well as bad tipsets in case a chain 73 // is constantly built on it. 74 // 75 // There are a few important caveats in this mechanism: 76 // - The bad block tracking is ephemeral, in-memory only. We must never 77 // persist any bad block information to disk as a bug in Geth could end 78 // up blocking a valid chain, even if a later Geth update would accept 79 // it. 80 // - Bad blocks will get forgotten after a certain threshold of import 81 // attempts and will be retried. The rationale is that if the network 82 // really-really-really tries to feed us a block, we should give it a 83 // new chance, perhaps us being racey instead of the block being legit 84 // bad (this happened in Geth at a point with import vs. pending race). 85 // - Tracking all the blocks built on top of the bad one could be a bit 86 // problematic, so we will only track the head chain segment of a bad 87 // chain to allow discarding progressing bad chains and side chains, 88 // without tracking too much bad data. 89 invalidBlocksHits map[common.Hash]int // Emhemeral cache to track invalid blocks and their hit count 90 invalidTipsets map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor 91 invalidLock sync.Mutex // Protects the invalid maps from concurrent access 92 93 forkChoiceLock sync.Mutex // Lock for the forkChoiceUpdated method 94 } 95 96 // NewConsensusAPI creates a new consensus api for the given backend. 97 // The underlying blockchain needs to have a valid terminal total difficulty set. 98 func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { 99 if eth.BlockChain().Config().TerminalTotalDifficulty == nil { 100 log.Warn("Engine API started but chain not configured for merge yet") 101 } 102 api := &ConsensusAPI{ 103 eth: eth, 104 remoteBlocks: newHeaderQueue(), 105 localBlocks: newPayloadQueue(), 106 invalidBlocksHits: make(map[common.Hash]int), 107 invalidTipsets: make(map[common.Hash]*types.Header), 108 } 109 eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor) 110 111 return api 112 } 113 114 // ForkchoiceUpdatedV1 has several responsibilities: 115 // If the method is called with an empty head block: 116 // we return success, which can be used to check if the engine API is enabled 117 // If the total difficulty was not reached: 118 // we return INVALID 119 // If the finalizedBlockHash is set: 120 // we check if we have the finalizedBlockHash in our db, if not we start a sync 121 // We try to set our blockchain to the headBlock 122 // If there are payloadAttributes: 123 // we try to assemble a block with the payloadAttributes and return its payloadID 124 func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { 125 api.forkChoiceLock.Lock() 126 defer api.forkChoiceLock.Unlock() 127 128 log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) 129 if update.HeadBlockHash == (common.Hash{}) { 130 log.Warn("Forkchoice requested update to zero hash") 131 return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? 132 } 133 134 // Check whether we have the block yet in our database or not. If not, we'll 135 // need to either trigger a sync, or to reject this forkchoice update for a 136 // reason. 137 block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash) 138 if block == nil { 139 // If this block was previously invalidated, keep rejecting it here too 140 if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil { 141 return beacon.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil 142 } 143 // If the head hash is unknown (was not given to us in a newPayload request), 144 // we cannot resolve the header, so not much to do. This could be extended in 145 // the future to resolve from the `eth` network, but it's an unexpected case 146 // that should be fixed, not papered over. 147 header := api.remoteBlocks.get(update.HeadBlockHash) 148 if header == nil { 149 log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash) 150 return beacon.STATUS_SYNCING, nil 151 } 152 // Header advertised via a past newPayload request. Start syncing to it. 153 // Before we do however, make sure any legacy sync in switched off so we 154 // don't accidentally have 2 cycles running. 155 if merger := api.eth.Merger(); !merger.TDDReached() { 156 merger.ReachTTD() 157 api.eth.Downloader().Cancel() 158 } 159 log.Info("Forkchoice requested sync to new head", "number", header.Number, "hash", header.Hash()) 160 if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header); err != nil { 161 return beacon.STATUS_SYNCING, err 162 } 163 return beacon.STATUS_SYNCING, nil 164 } 165 // Block is known locally, just sanity check that the beacon client does not 166 // attempt to push us back to before the merge. 167 if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 { 168 var ( 169 td = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64()) 170 ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1) 171 ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty 172 ) 173 if td == nil || (block.NumberU64() > 0 && ptd == nil) { 174 log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd) 175 return beacon.STATUS_INVALID, errors.New("TDs unavailable for TDD check") 176 } 177 if td.Cmp(ttd) < 0 { 178 log.Error("Refusing beacon update to pre-merge", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) 179 return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil 180 } 181 if block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0 { 182 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))) 183 return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil 184 } 185 } 186 valid := func(id *beacon.PayloadID) beacon.ForkChoiceResponse { 187 return beacon.ForkChoiceResponse{ 188 PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &update.HeadBlockHash}, 189 PayloadID: id, 190 } 191 } 192 if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { 193 // Block is not canonical, set head. 194 if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil { 195 return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &latestValid}}, err 196 } 197 } else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash { 198 // If the specified head matches with our local head, do nothing and keep 199 // generating the payload. It's a special corner case that a few slots are 200 // missing and we are requested to generate the payload in slot. 201 } else { 202 // If the head block is already in our canonical chain, the beacon client is 203 // probably resyncing. Ignore the update. 204 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().NumberU64()) 205 return valid(nil), nil 206 } 207 api.eth.SetSynced() 208 209 // If the beacon client also advertised a finalized block, mark the local 210 // chain final and completely in PoS mode. 211 if update.FinalizedBlockHash != (common.Hash{}) { 212 if merger := api.eth.Merger(); !merger.PoSFinalized() { 213 merger.FinalizePoS() 214 } 215 // If the finalized block is not in our canonical tree, somethings wrong 216 finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash) 217 if finalBlock == nil { 218 log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash) 219 return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not available in database")) 220 } else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash { 221 log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash) 222 return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not in canonical chain")) 223 } 224 // Set the finalized block 225 api.eth.BlockChain().SetFinalized(finalBlock) 226 } 227 // Check if the safe block hash is in our canonical tree, if not somethings wrong 228 if update.SafeBlockHash != (common.Hash{}) { 229 safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash) 230 if safeBlock == nil { 231 log.Warn("Safe block not available in database") 232 return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not available in database")) 233 } 234 if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash { 235 log.Warn("Safe block not in canonical chain") 236 return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) 237 } 238 // Set the safe block 239 api.eth.BlockChain().SetSafe(safeBlock) 240 } 241 // If payload generation was requested, create a new block to be potentially 242 // sealed by the beacon client. The payload will be requested later, and we 243 // might replace it arbitrarily many times in between. 244 if payloadAttributes != nil { 245 // Create an empty block first which can be used as a fallback 246 empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true) 247 if err != nil { 248 log.Error("Failed to create empty sealing payload", "err", err) 249 return valid(nil), beacon.InvalidPayloadAttributes.With(err) 250 } 251 // Send a request to generate a full block in the background. 252 // The result can be obtained via the returned channel. 253 resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false) 254 if err != nil { 255 log.Error("Failed to create async sealing payload", "err", err) 256 return valid(nil), beacon.InvalidPayloadAttributes.With(err) 257 } 258 id := computePayloadId(update.HeadBlockHash, payloadAttributes) 259 api.localBlocks.put(id, &payload{empty: empty, result: resCh}) 260 return valid(&id), nil 261 } 262 return valid(nil), nil 263 } 264 265 // ExchangeTransitionConfigurationV1 checks the given configuration against 266 // the configuration of the node. 267 func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.TransitionConfigurationV1) (*beacon.TransitionConfigurationV1, error) { 268 if config.TerminalTotalDifficulty == nil { 269 return nil, errors.New("invalid terminal total difficulty") 270 } 271 ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty 272 if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 { 273 log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty) 274 return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty) 275 } 276 277 if config.TerminalBlockHash != (common.Hash{}) { 278 if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash { 279 return &beacon.TransitionConfigurationV1{ 280 TerminalTotalDifficulty: (*hexutil.Big)(ttd), 281 TerminalBlockHash: config.TerminalBlockHash, 282 TerminalBlockNumber: config.TerminalBlockNumber, 283 }, nil 284 } 285 return nil, fmt.Errorf("invalid terminal block hash") 286 } 287 return &beacon.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil 288 } 289 290 // GetPayloadV1 returns a cached payload by id. 291 func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { 292 log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) 293 data := api.localBlocks.get(payloadID) 294 if data == nil { 295 return nil, beacon.UnknownPayload 296 } 297 return data, nil 298 } 299 300 // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. 301 func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) { 302 log.Trace("Engine API request received", "method", "ExecutePayload", "number", params.Number, "hash", params.BlockHash) 303 block, err := beacon.ExecutableDataToBlock(params) 304 if err != nil { 305 log.Debug("Invalid NewPayload params", "params", params, "error", err) 306 return beacon.PayloadStatusV1{Status: beacon.INVALIDBLOCKHASH}, nil 307 } 308 // If we already have the block locally, ignore the entire execution and just 309 // return a fake success. 310 if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil { 311 log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) 312 hash := block.Hash() 313 return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil 314 } 315 // If this block was rejected previously, keep rejecting it 316 if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil { 317 return *res, nil 318 } 319 // If the parent is missing, we - in theory - could trigger a sync, but that 320 // would also entail a reorg. That is problematic if multiple sibling blocks 321 // are being fed to us, and even more so, if some semi-distant uncle shortens 322 // our live chain. As such, payload execution will not permit reorgs and thus 323 // will not trigger a sync cycle. That is fine though, if we get a fork choice 324 // update after legit payload executions. 325 parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1) 326 if parent == nil { 327 return api.delayPayloadImport(block) 328 } 329 // We have an existing parent, do some sanity checks to avoid the beacon client 330 // triggering too early 331 var ( 332 ptd = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64()) 333 ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty 334 gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1) 335 ) 336 if ptd.Cmp(ttd) < 0 { 337 log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd) 338 return beacon.INVALID_TERMINAL_BLOCK, nil 339 } 340 if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 { 341 log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd) 342 return beacon.INVALID_TERMINAL_BLOCK, nil 343 } 344 if block.Time() <= parent.Time() { 345 log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) 346 return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil 347 } 348 // Another cornercase: if the node is in snap sync mode, but the CL client 349 // tries to make it import a block. That should be denied as pushing something 350 // into the database directly will conflict with the assumptions of snap sync 351 // that it has an empty db that it can fill itself. 352 if api.eth.SyncMode() != downloader.FullSync { 353 return api.delayPayloadImport(block) 354 } 355 if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { 356 api.remoteBlocks.put(block.Hash(), block.Header()) 357 log.Warn("State not available, ignoring new payload") 358 return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil 359 } 360 log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number) 361 if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil { 362 log.Warn("NewPayloadV1: inserting block failed", "error", err) 363 364 api.invalidLock.Lock() 365 api.invalidBlocksHits[block.Hash()] = 1 366 api.invalidTipsets[block.Hash()] = block.Header() 367 api.invalidLock.Unlock() 368 369 return api.invalid(err, parent.Header()), nil 370 } 371 // We've accepted a valid payload from the beacon client. Mark the local 372 // chain transitions to notify other subsystems (e.g. downloader) of the 373 // behavioral change. 374 if merger := api.eth.Merger(); !merger.TDDReached() { 375 merger.ReachTTD() 376 api.eth.Downloader().Cancel() 377 } 378 hash := block.Hash() 379 return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil 380 } 381 382 // computePayloadId computes a pseudo-random payloadid, based on the parameters. 383 func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID { 384 // Hash 385 hasher := sha256.New() 386 hasher.Write(headBlockHash[:]) 387 binary.Write(hasher, binary.BigEndian, params.Timestamp) 388 hasher.Write(params.Random[:]) 389 hasher.Write(params.SuggestedFeeRecipient[:]) 390 var out beacon.PayloadID 391 copy(out[:], hasher.Sum(nil)[:8]) 392 return out 393 } 394 395 // delayPayloadImport stashes the given block away for import at a later time, 396 // either via a forkchoice update or a sync extension. This method is meant to 397 // be called by the newpayload command when the block seems to be ok, but some 398 // prerequisite prevents it from being processed (e.g. no parent, or snap sync). 399 func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (beacon.PayloadStatusV1, error) { 400 // Sanity check that this block's parent is not on a previously invalidated 401 // chain. If it is, mark the block as invalid too. 402 if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil { 403 return *res, nil 404 } 405 // Stash the block away for a potential forced forkchoice update to it 406 // at a later time. 407 api.remoteBlocks.put(block.Hash(), block.Header()) 408 409 // Although we don't want to trigger a sync, if there is one already in 410 // progress, try to extend if with the current payload request to relieve 411 // some strain from the forkchoice update. 412 if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil { 413 log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash()) 414 return beacon.PayloadStatusV1{Status: beacon.SYNCING}, nil 415 } 416 // Either no beacon sync was started yet, or it rejected the delivered 417 // payload as non-integratable on top of the existing sync. We'll just 418 // have to rely on the beacon client to forcefully update the head with 419 // a forkchoice update request. 420 log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash()) 421 return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil 422 } 423 424 // setInvalidAncestor is a callback for the downloader to notify us if a bad block 425 // is encountered during the async sync. 426 func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) { 427 api.invalidLock.Lock() 428 defer api.invalidLock.Unlock() 429 430 api.invalidTipsets[origin.Hash()] = invalid 431 api.invalidBlocksHits[invalid.Hash()]++ 432 } 433 434 // checkInvalidAncestor checks whether the specified chain end links to a known 435 // bad ancestor. If yes, it constructs the payload failure response to return. 436 func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *beacon.PayloadStatusV1 { 437 api.invalidLock.Lock() 438 defer api.invalidLock.Unlock() 439 440 // If the hash to check is unknown, return valid 441 invalid, ok := api.invalidTipsets[check] 442 if !ok { 443 return nil 444 } 445 // If the bad hash was hit too many times, evict it and try to reprocess in 446 // the hopes that we have a data race that we can exit out of. 447 badHash := invalid.Hash() 448 449 api.invalidBlocksHits[badHash]++ 450 if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction { 451 log.Warn("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash) 452 delete(api.invalidBlocksHits, badHash) 453 454 for descendant, badHeader := range api.invalidTipsets { 455 if badHeader.Hash() == badHash { 456 delete(api.invalidTipsets, descendant) 457 } 458 } 459 return nil 460 } 461 // Not too many failures yet, mark the head of the invalid chain as invalid 462 if check != head { 463 log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash) 464 for len(api.invalidTipsets) >= invalidTipsetsCap { 465 for key := range api.invalidTipsets { 466 delete(api.invalidTipsets, key) 467 break 468 } 469 } 470 api.invalidTipsets[head] = invalid 471 } 472 failure := "links to previously rejected block" 473 return &beacon.PayloadStatusV1{ 474 Status: beacon.INVALID, 475 LatestValidHash: &invalid.ParentHash, 476 ValidationError: &failure, 477 } 478 } 479 480 // invalid returns a response "INVALID" with the latest valid hash supplied by latest or to the current head 481 // if no latestValid block was provided. 482 func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) beacon.PayloadStatusV1 { 483 currentHash := api.eth.BlockChain().CurrentBlock().Hash() 484 if latestValid != nil { 485 // Set latest valid hash to 0x0 if parent is PoW block 486 currentHash = common.Hash{} 487 if latestValid.Difficulty.BitLen() == 0 { 488 // Otherwise set latest valid hash to parent hash 489 currentHash = latestValid.Hash() 490 } 491 } 492 errorMsg := err.Error() 493 return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: ¤tHash, ValidationError: &errorMsg} 494 }