Bugfix: the client can miss the genesis event in the absence of new deposits

This commit is contained in:
Zahary Karadjov 2020-11-14 22:51:50 +02:00 committed by zah
parent 9f28a464fb
commit 8012102704
1 changed files with 73 additions and 42 deletions

View File

@ -463,6 +463,13 @@ proc signalGenesis(m: Eth1Monitor, genesisState: BeaconStateRef) =
template hasEnoughValidators(m: Eth1Monitor, blk: Eth1Block): bool = template hasEnoughValidators(m: Eth1Monitor, blk: Eth1Block): bool =
blk.activeValidatorsCount >= m.preset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT blk.activeValidatorsCount >= m.preset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
func chainHasEnoughValidators(m: Eth1Monitor): bool =
if m.eth1Chain.blocks.len > 0:
m.hasEnoughValidators(m.eth1Chain.blocks[^1])
else:
m.eth1Chain.knownStart.deposit_count >=
m.preset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
func isAfterMinGenesisTime(m: Eth1Monitor, blk: Eth1Block): bool = func isAfterMinGenesisTime(m: Eth1Monitor, blk: Eth1Block): bool =
doAssert blk.timestamp != 0 doAssert blk.timestamp != 0
let t = genesis_time_from_eth1_timestamp(m.preset, uint64 blk.timestamp) let t = genesis_time_from_eth1_timestamp(m.preset, uint64 blk.timestamp)
@ -527,21 +534,23 @@ proc stop*(m: Eth1Monitor) =
proc syncBlockRange(m: Eth1Monitor, fromBlock, toBlock: Eth1BlockNumber) {.async.} = proc syncBlockRange(m: Eth1Monitor, fromBlock, toBlock: Eth1BlockNumber) {.async.} =
var currentBlock = fromBlock var currentBlock = fromBlock
while currentBlock <= toBlock: while currentBlock <= toBlock:
var depositLogs: JsonNode = nil var
depositLogs: JsonNode = nil
blocksPerRequest = 5000'u64 # This is roughly a day of Eth1 blocks
maxBlockNumberRequested: Eth1BlockNumber
var blocksPerRequest = 5000'u64 # This is roughly a day of Eth1 blocks
while true: while true:
let requestToBlock = min(toBlock, currentBlock + blocksPerRequest - 1) maxBlockNumberRequested = min(toBlock, currentBlock + blocksPerRequest - 1)
debug "Obtaining deposit log events", debug "Obtaining deposit log events",
fromBlock = currentBlock, fromBlock = currentBlock,
toBlock = requestToBlock toBlock = maxBlockNumberRequested
try: try:
depositLogs = await m.dataProvider.ns.getJsonLogs( depositLogs = await m.dataProvider.ns.getJsonLogs(
DepositEvent, DepositEvent,
fromBlock = some blockId(currentBlock), fromBlock = some blockId(currentBlock),
toBlock = some blockId(requestToBlock)) toBlock = some blockId(maxBlockNumberRequested))
currentBlock = requestToBlock + 1 currentBlock = maxBlockNumberRequested + 1
break break
except CatchableError as err: except CatchableError as err:
blocksPerRequest = blocksPerRequest div 2 blocksPerRequest = blocksPerRequest div 2
@ -568,7 +577,8 @@ proc syncBlockRange(m: Eth1Monitor, fromBlock, toBlock: Eth1BlockNumber) {.async
m.eth1Chain.addBlock blk m.eth1Chain.addBlock blk
if eth1Blocks.len > 0: if eth1Blocks.len > 0:
let lastBlock = eth1Blocks[^1] let lastIdx = eth1Blocks.len - 1
template lastBlock: auto = eth1Blocks[lastIdx]
when hasDepositRootChecks: when hasDepositRootChecks:
let status = await m.dataProvider.fetchDepositContractData(lastBlock) let status = await m.dataProvider.fetchDepositContractData(lastBlock)
@ -582,41 +592,62 @@ proc syncBlockRange(m: Eth1Monitor, fromBlock, toBlock: Eth1BlockNumber) {.async
blockNumber = lastBlock.number, blockNumber = lastBlock.number,
depositsProcessed = lastBlock.voteData.deposit_count depositsProcessed = lastBlock.voteData.deposit_count
if m.genesisStateFut != nil and m.hasEnoughValidators(lastBlock): if m.genesisStateFut != nil and m.chainHasEnoughValidators:
let lastIdx = m.eth1Chain.blocks.len - 1
template lastBlock: auto = m.eth1Chain.blocks[lastIdx]
if maxBlockNumberRequested == toBlock and
(m.eth1Chain.blocks.len == 0 or lastBlock.number != toBlock):
let web3Block = await m.dataProvider.getBlockByNumber(toBlock)
debug "Latest block doesn't hold deposits. Obtaining it",
ts = web3Block.timestamp.uint64,
number = web3Block.number.uint64
m.eth1Chain.addBlock Eth1Block(
number: Eth1BlockNumber web3Block.number,
timestamp: Eth1BlockTimestamp web3Block.timestamp,
voteData: Eth1Data(
block_hash: web3Block.hash.asEth2Digest,
deposit_count: lastBlock.voteData.deposit_count,
deposit_root: lastBlock.voteData.deposit_root),
activeValidatorsCount: lastBlock.activeValidatorsCount)
else:
await m.dataProvider.fetchTimestamp(lastBlock) await m.dataProvider.fetchTimestamp(lastBlock)
if m.isAfterMinGenesisTime(lastBlock):
var genesisBlockIdx = m.eth1Chain.blocks.len - 1 var genesisBlockIdx = m.eth1Chain.blocks.len - 1
for i in 1 ..< eth1Blocks.len: if m.isAfterMinGenesisTime(m.eth1Chain.blocks[genesisBlockIdx]):
let idx = (m.eth1Chain.blocks.len - 1) - i for i in 1 ..< eth1Blocks.len:
let blk = m.eth1Chain.blocks[idx] let idx = (m.eth1Chain.blocks.len - 1) - i
await m.dataProvider.fetchTimestamp(blk) let blk = m.eth1Chain.blocks[idx]
if m.isGenesisCandidate(blk): await m.dataProvider.fetchTimestamp(blk)
genesisBlockIdx = idx if m.isGenesisCandidate(blk):
else: genesisBlockIdx = idx
break else:
# We have a candidate state on our hands, but our current Eth1Chain break
# may consist only of blocks that have deposits attached to them # We have a candidate state on our hands, but our current Eth1Chain
# while the real genesis may have happened in a block without any # may consist only of blocks that have deposits attached to them
# deposits (triggered by MIN_GENESIS_TIME). # while the real genesis may have happened in a block without any
# # deposits (triggered by MIN_GENESIS_TIME).
# This can happen when the beacon node is launched after the genesis #
# event. We take a short cut when constructing the initial Eth1Chain # This can happen when the beacon node is launched after the genesis
# by downloading only deposit log entries. Thus, we'll see all the # event. We take a short cut when constructing the initial Eth1Chain
# blocks with deposits, but not the regular blocks in between. # by downloading only deposit log entries. Thus, we'll see all the
# # blocks with deposits, but not the regular blocks in between.
# We'll handle this special case below by examing whether we are in #
# this potential scenario and we'll use a fast guessing algorith to # We'll handle this special case below by examing whether we are in
# discover the ETh1 block with minimal valid genesis time. # this potential scenario and we'll use a fast guessing algorith to
var genesisBlock = m.eth1Chain.blocks[genesisBlockIdx] # discover the ETh1 block with minimal valid genesis time.
if genesisBlockIdx > 0: var genesisBlock = m.eth1Chain.blocks[genesisBlockIdx]
let genesisParent = m.eth1Chain.blocks[genesisBlockIdx - 1] if genesisBlockIdx > 0:
if genesisParent.timestamp == 0: let genesisParent = m.eth1Chain.blocks[genesisBlockIdx - 1]
await m.dataProvider.fetchTimestamp(genesisParent) if genesisParent.timestamp == 0:
if m.hasEnoughValidators(genesisParent) and await m.dataProvider.fetchTimestamp(genesisParent)
genesisBlock.number - genesisParent.number > 1: if m.hasEnoughValidators(genesisParent) and
genesisBlock = await m.findGenesisBlockInRange(genesisParent, genesisBlock.number - genesisParent.number > 1:
genesisBlock) genesisBlock = await m.findGenesisBlockInRange(genesisParent,
m.signalGenesis m.createGenesisState(genesisBlock) genesisBlock)
m.signalGenesis m.createGenesisState(genesisBlock)
proc handleEth1Progress(m: Eth1Monitor) {.async.} = proc handleEth1Progress(m: Eth1Monitor) {.async.} =
# ATTENTION! # ATTENTION!
@ -650,7 +681,7 @@ proc handleEth1Progress(m: Eth1Monitor) {.async.} =
await m.syncBlockRange(eth1SyncedTo + 1, targetBlock) await m.syncBlockRange(eth1SyncedTo + 1, targetBlock)
eth1SyncedTo = targetBlock eth1SyncedTo = targetBlock
while m.eth1Chain.blocks.len > 0: while m.eth1Chain.blocks.len > 1:
# We'll clean old blocks that can no longer be voting candidates. # We'll clean old blocks that can no longer be voting candidates.
# Technically, we should check that the block is outside of the current # Technically, we should check that the block is outside of the current
# voting period as determined by its timestamp, but we'll approximate # voting period as determined by its timestamp, but we'll approximate