Bellatrix TTD detection (#3745)

* Bellatrix TTD detection

* Update beacon_chain/eth1/eth1_monitor.nim

Co-authored-by: Etan Kissling <etan@status.im>

* Update beacon_chain/nimbus_beacon_node.nim

Co-authored-by: tersec <tersec@users.noreply.github.com>

Co-authored-by: Etan Kissling <etan@status.im>
Co-authored-by: tersec <tersec@users.noreply.github.com>
This commit is contained in:
zah 2022-06-15 05:38:27 +03:00 committed by GitHub
parent 20e646a47f
commit 694b653757
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 31 deletions

View File

@ -73,6 +73,7 @@ type
restKeysCache*: Table[ValidatorPubKey, ValidatorIndex]
validatorMonitor*: ref ValidatorMonitor
stateTtlCache*: StateTtlCache
nextExchangeTransitionConfTime*: Moment
const
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT

View File

@ -118,6 +118,9 @@ type
depositsChain: Eth1Chain
eth1Progress: AsyncEvent
terminalBlockHash*: Option[BlockHash]
terminalBlockNumber*: Option[Quantity]
runFut: Future[void]
stopFut: Future[void]
getBeaconTime: GetBeaconTimeFn
@ -517,6 +520,50 @@ proc forkchoiceUpdated*(p: Eth1Monitor,
prevRandao: FixedBytes[32] randomData,
suggestedFeeRecipient: suggestedFeeRecipient)))
# TODO can't be defined within exchangeTransitionConfiguration
proc `==`(x, y: Quantity): bool {.borrow, noSideEffect.}
proc exchangeTransitionConfiguration*(p: Eth1Monitor): Future[void] {.async.} =
# Eth1 monitor can recycle connections without (external) warning; at least,
# don't crash.
if p.isNil:
debug "exchangeTransitionConfiguration: nil Eth1Monitor"
if p.isNil or p.dataProvider.isNil:
return
let ccTransitionConfiguration = TransitionConfigurationV1(
terminalTotalDifficulty: p.depositsChain.cfg.TERMINAL_TOTAL_DIFFICULTY,
terminalBlockHash:
if p.terminalBlockHash.isSome:
p.terminalBlockHash.get
else:
# TODO can't use static(default(...)) in this context
default(BlockHash),
terminalBlockNumber:
if p.terminalBlockNumber.isSome:
p.terminalBlockNumber.get
else:
# TODO can't use static(default(...)) in this context
default(Quantity))
let ecTransitionConfiguration =
await p.dataProvider.web3.provider.engine_exchangeTransitionConfigurationV1(
ccTransitionConfiguration)
if ccTransitionConfiguration != ecTransitionConfiguration:
warn "exchangeTransitionConfiguration: Configuration mismatch detected",
consensusTerminalTotalDifficulty =
ccTransitionConfiguration.terminalTotalDifficulty,
consensusTerminalBlockHash =
ccTransitionConfiguration.terminalBlockHash,
consensusTerminalBlockNumber =
ccTransitionConfiguration.terminalBlockNumber.uint64,
executionTerminalTotalDifficulty =
ecTransitionConfiguration.terminalTotalDifficulty,
executionTerminalBlockHash =
ecTransitionConfiguration.terminalBlockHash,
executionTerminalBlockNumber =
ecTransitionConfiguration.terminalBlockNumber.uint64
template readJsonField(j: JsonNode, fieldName: string, ValueType: type): untyped =
var res: ValueType
fromJson(j[fieldName], fieldName, res)
@ -949,6 +996,12 @@ proc createInitialDepositSnapshot*(
return ok DepositContractSnapshot(eth1Block: knownStartBlockHash)
proc currentEpoch(m: Eth1Monitor): Epoch =
if m.getBeaconTime != nil:
m.getBeaconTime().slotOrZero.epoch
else:
Epoch 0
proc init*(T: type Eth1Monitor,
cfg: RuntimeConfig,
db: BeaconChainDB,
@ -1323,7 +1376,10 @@ proc startEth1Syncing(m: Eth1Monitor, delayBeforeStart: Duration) {.async.} =
await m.dataProvider.onBlockHeaders(newBlockHeadersHandler,
subscriptionErrorHandler)
if m.depositsChain.blocks.len == 0:
let shouldProcessDeposits = not m.depositContractAddress.isZeroMemory
var scratchMerkleizer: ref DepositsMerkleizer
var eth1SyncedTo: Eth1BlockNumber
if shouldProcessDeposits and m.depositsChain.blocks.len == 0:
let startBlock = awaitWithRetries(
m.dataProvider.getBlockByHash(m.depositsChain.finalizedBlockHash.asBlockHash))
@ -1334,15 +1390,16 @@ proc startEth1Syncing(m: Eth1Monitor, delayBeforeStart: Duration) {.async.} =
m.depositsChain.finalizedBlockHash,
m.depositsChain.finalizedDepositsMerkleizer))
var eth1SyncedTo = Eth1BlockNumber m.depositsChain.blocks.peekLast.number
eth1_synced_head.set eth1SyncedTo.toGaugeValue
eth1_finalized_head.set eth1SyncedTo.toGaugeValue
eth1_finalized_deposits.set(
m.depositsChain.finalizedDepositsMerkleizer.getChunkCount.toGaugeValue)
eth1SyncedTo = Eth1BlockNumber startBlock.number
var scratchMerkleizer = newClone(copy m.finalizedDepositsMerkleizer)
eth1_synced_head.set eth1SyncedTo.toGaugeValue
eth1_finalized_head.set eth1SyncedTo.toGaugeValue
eth1_finalized_deposits.set(
m.depositsChain.finalizedDepositsMerkleizer.getChunkCount.toGaugeValue)
debug "Starting Eth1 syncing", `from` = shortLog(m.depositsChain.blocks[0])
scratchMerkleizer = newClone(copy m.finalizedDepositsMerkleizer)
debug "Starting Eth1 syncing", `from` = shortLog(m.depositsChain.blocks[0])
while true:
if bnStatus == BeaconNodeStatus.Stopping:
@ -1361,7 +1418,7 @@ proc startEth1Syncing(m: Eth1Monitor, delayBeforeStart: Duration) {.async.} =
m.startIdx = 0
return
if mustUsePolling:
let nextBlock = if mustUsePolling:
let blk = awaitWithRetries(
m.dataProvider.web3.provider.eth_getBlockByNumber(blockId("latest"), false))
@ -1373,26 +1430,56 @@ proc startEth1Syncing(m: Eth1Monitor, delayBeforeStart: Duration) {.async.} =
continue
m.latestEth1Block = some fullBlockId
blk
else:
awaitWithTimeout(m.eth1Progress.wait(), 5.minutes):
raise newException(CorruptDataProvider, "No eth1 chain progress for too long")
m.eth1Progress.clear()
if m.latestEth1BlockNumber <= m.cfg.ETH1_FOLLOW_DISTANCE:
continue
if m.latestEth1Block.isNone:
# It should not be possible for `latestEth1Block` to be none here.
# Firing the `eth1Progress` event is always done after assinging
# a value for it.
continue
let targetBlock = m.latestEth1BlockNumber - m.cfg.ETH1_FOLLOW_DISTANCE
if targetBlock <= eth1SyncedTo:
continue
awaitWithRetries m.dataProvider.getBlockByHash(m.latestEth1Block.get.hash)
let earliestBlockOfInterest = m.earliestBlockOfInterest()
await m.syncBlockRange(scratchMerkleizer,
eth1SyncedTo + 1,
targetBlock,
earliestBlockOfInterest)
eth1SyncedTo = targetBlock
eth1_synced_head.set eth1SyncedTo.toGaugeValue
if m.currentEpoch >= m.cfg.BELLATRIX_FORK_EPOCH and m.terminalBlockHash.isNone:
var terminalBlockCandidate = nextBlock
info "startEth1Syncing: checking for merge terminal block",
currentEpoch = m.currentEpoch,
BELLATRIX_FORK_EPOCH = m.cfg.BELLATRIX_FORK_EPOCH,
totalDifficulty = nextBlock.totalDifficulty,
ttd = m.cfg.TERMINAL_TOTAL_DIFFICULTY,
terminalBlockHash = m.terminalBlockHash
if terminalBlockCandidate.totalDifficulty >= m.cfg.TERMINAL_TOTAL_DIFFICULTY:
while not terminalBlockCandidate.parentHash.isZeroMemory:
var parentBlock = awaitWithRetries(
m.dataProvider.getBlockByHash(terminalBlockCandidate.parentHash))
if parentBlock.totalDifficulty < m.cfg.TERMINAL_TOTAL_DIFFICULTY:
break
terminalBlockCandidate = parentBlock
m.terminalBlockHash = some terminalBlockCandidate.hash
m.terminalBlockNumber = some terminalBlockCandidate.number
if shouldProcessDeposits:
if m.latestEth1BlockNumber <= m.cfg.ETH1_FOLLOW_DISTANCE:
continue
let targetBlock = m.latestEth1BlockNumber - m.cfg.ETH1_FOLLOW_DISTANCE
if targetBlock <= eth1SyncedTo:
continue
let earliestBlockOfInterest = m.earliestBlockOfInterest()
await m.syncBlockRange(scratchMerkleizer,
eth1SyncedTo + 1,
targetBlock,
earliestBlockOfInterest)
eth1SyncedTo = targetBlock
eth1_synced_head.set eth1SyncedTo.toGaugeValue
proc start(m: Eth1Monitor, delayBeforeStart: Duration) {.gcsafe.} =
if m.runFut.isNil:

View File

@ -9,7 +9,7 @@
import
std/[os, random, sequtils, terminal, times],
bearssl, chronicles, chronos,
bearssl, chronos, chronicles, chronicles/chronos_tools,
metrics, metrics/chronos_httpserver,
stew/[byteutils, io2],
eth/p2p/discoveryv5/[enr, random2],
@ -559,8 +559,8 @@ proc init*(T: type BeaconNode,
dag = loadChainDag(
config, cfg, db, eventBus,
validatorMonitor, networkGenesisValidatorsRoot)
beaconClock = BeaconClock.init(
getStateField(dag.headState, genesis_time))
genesisTime = getStateField(dag.headState, genesis_time)
beaconClock = BeaconClock.init(genesisTime)
getBeaconTime = beaconClock.getBeaconTimeFn()
if config.weakSubjectivityCheckpoint.isSome:
@ -665,6 +665,13 @@ proc init*(T: type BeaconNode,
else:
nil
bellatrixEpochTime =
genesisTime + cfg.BELLATRIX_FORK_EPOCH * SLOTS_PER_EPOCH * SECONDS_PER_SLOT
nextExchangeTransitionConfTime =
max(Moment.init(int64 bellatrixEpochTime, Second),
Moment.now)
let node = BeaconNode(
nickname: nickname,
graffitiBytes: if config.graffiti.isSome: config.graffiti.get
@ -683,7 +690,8 @@ proc init*(T: type BeaconNode,
gossipState: {},
beaconClock: beaconClock,
validatorMonitor: validatorMonitor,
stateTtlCache: stateTtlCache)
stateTtlCache: stateTtlCache,
nextExchangeTransitionConfTime: nextExchangeTransitionConfTime)
node.initLightClient(
rng, cfg, dag.forkDigests, getBeaconTime, dag.genesis_validators_root)
@ -1255,7 +1263,7 @@ proc handleMissingBlocks(node: BeaconNode) =
debug "Requesting detected missing blocks", blocks = shortLog(missingBlocks)
node.requestManager.fetchAncestorBlocks(missingBlocks)
proc onSecond(node: BeaconNode) =
proc onSecond(node: BeaconNode, time: Moment) =
## This procedure will be called once per second.
if not(node.syncManager.inProgress):
node.handleMissingBlocks()
@ -1263,6 +1271,12 @@ proc onSecond(node: BeaconNode) =
# Nim GC metrics (for the main thread)
updateThreadMetrics()
## This procedure will be called once per minute.
# https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.9/src/engine/specification.md#engine_exchangetransitionconfigurationv1
if time > node.nextExchangeTransitionConfTime and not node.eth1Monitor.isNil:
node.nextExchangeTransitionConfTime = time + chronos.minutes(1)
traceAsyncErrors node.eth1Monitor.exchangeTransitionConfiguration()
if node.config.stopAtSyncedEpoch != 0 and
node.dag.head.slot.epoch >= node.config.stopAtSyncedEpoch:
notice "Shutting down after having reached the target synced epoch"
@ -1276,7 +1290,7 @@ proc runOnSecondLoop(node: BeaconNode) {.async.} =
await chronos.sleepAsync(sleepTime)
let afterSleep = chronos.now(chronos.Moment)
let sleepTime = afterSleep - start
node.onSecond()
node.onSecond(start)
let finished = chronos.now(chronos.Moment)
let processingTime = finished - afterSleep
ticks_delay.set(sleepTime.nanoseconds.float / nanosecondsIn1s)

View File

@ -571,11 +571,16 @@ proc getExecutionPayload(
const GETPAYLOAD_TIMEOUT = 1.seconds
let
terminalBlockHash =
if node.eth1Monitor.terminalBlockHash.isSome:
node.eth1Monitor.terminalBlockHash.get.asEth2Digest
else:
default(Eth2Digest)
latestHead =
if not node.dag.head.executionBlockRoot.isZero:
node.dag.head.executionBlockRoot
else:
default(Eth2Digest)
terminalBlockHash
latestFinalized = node.dag.finalizedHead.blck.executionBlockRoot
payload_id = (await forkchoice_updated(
proposalState.bellatrixData.data, latestHead, latestFinalized,
@ -653,9 +658,10 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
else:
node.syncCommitteeMsgPool[].produceSyncAggregate(head.root),
if slot.epoch < node.dag.cfg.BELLATRIX_FORK_EPOCH or
# TODO when Eth1Monitor TTD following comes in, actually detect
# transition block directly
not is_merge_transition_complete(proposalState.bellatrixData.data):
not (
is_merge_transition_complete(proposalState.bellatrixData.data) or
((not node.eth1Monitor.isNil) and
node.eth1Monitor.terminalBlockHash.isSome)):
default(bellatrix.ExecutionPayload)
else:
let pubkey = node.dag.validatorKey(validator_index)