api.go 31 KB

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