Add Eth1 deposits simulation to block_sim
This commit is contained in:
parent
52ca12c79f
commit
338428cbd7
|
@ -55,8 +55,12 @@ type
|
|||
activeValidatorsCount*: uint64
|
||||
|
||||
Eth1Chain* = object
|
||||
db: BeaconChainDB
|
||||
preset: RuntimePreset
|
||||
blocks: Deque[Eth1Block]
|
||||
blocksByHash: Table[BlockHash, Eth1Block]
|
||||
finalizedBlockHash: Eth2Digest
|
||||
finalizedDepositsMerkleizer: DepositsMerkleizer
|
||||
|
||||
Eth1MonitorState = enum
|
||||
Initialized
|
||||
|
@ -67,21 +71,16 @@ type
|
|||
|
||||
Eth1Monitor* = ref object
|
||||
state: Eth1MonitorState
|
||||
preset: RuntimePreset
|
||||
web3Url: string
|
||||
eth1Network: Option[Eth1Network]
|
||||
depositContractAddress*: Eth1Address
|
||||
|
||||
dataProvider: Web3DataProviderRef
|
||||
|
||||
eth1Chain: Eth1Chain
|
||||
latestEth1BlockNumber: Eth1BlockNumber
|
||||
eth1Progress: AsyncEvent
|
||||
|
||||
db: BeaconChainDB
|
||||
eth1Chain: Eth1Chain
|
||||
knownStart: DepositContractSnapshot
|
||||
eth2FinalizedDepositsMerkleizer: DepositsMerkleizer
|
||||
|
||||
runFut: Future[void]
|
||||
stopFut: Future[void]
|
||||
|
||||
|
@ -213,6 +212,15 @@ when hasGenesisDetection:
|
|||
template blocks*(m: Eth1Monitor): Deque[Eth1Block] =
|
||||
m.eth1Chain.blocks
|
||||
|
||||
template db(m: Eth1Monitor): auto =
|
||||
m.eth1Chain.db
|
||||
|
||||
template preset(m: Eth1Monitor): auto =
|
||||
m.eth1Chain.preset
|
||||
|
||||
template finalizedDepositsMerkleizer(m: Eth1Monitor): auto =
|
||||
m.eth1Chain.finalizedDepositsMerkleizer
|
||||
|
||||
proc fixupWeb3Urls*(web3Url: var string) =
|
||||
## Converts HTTP and HTTPS Infura URLs to their WebSocket equivalents
|
||||
## because we are missing a functional HTTPS client.
|
||||
|
@ -291,8 +299,8 @@ template asBlockHash(x: Eth2Digest): BlockHash =
|
|||
func shortLog*(b: Eth1Block): string =
|
||||
&"{b.number}:{shortLog b.voteData.block_hash}(deposits = {b.voteData.deposit_count})"
|
||||
|
||||
template findBlock*(eth1Chain: Eth1Chain, eth1Data: Eth1Data): Eth1Block =
|
||||
getOrDefault(eth1Chain.blocksByHash, asBlockHash(eth1Data.block_hash), nil)
|
||||
template findBlock*(chain: Eth1Chain, eth1Data: Eth1Data): Eth1Block =
|
||||
getOrDefault(chain.blocksByHash, asBlockHash(eth1Data.block_hash), nil)
|
||||
|
||||
func makeSuccessorWithoutDeposits(existingBlock: Eth1Block,
|
||||
successor: BlockObject): ETh1Block =
|
||||
|
@ -307,21 +315,21 @@ func makeSuccessorWithoutDeposits(existingBlock: Eth1Block,
|
|||
when hasGenesisDetection:
|
||||
result.activeValidatorsCount = existingBlock.activeValidatorsCount
|
||||
|
||||
func latestCandidateBlock(m: Eth1Monitor, periodStart: uint64): Eth1Block =
|
||||
for i in countdown(m.eth1Chain.blocks.len - 1, 0):
|
||||
let blk = m.eth1Chain.blocks[i]
|
||||
if is_candidate_block(m.preset, blk, periodStart):
|
||||
func latestCandidateBlock(chain: Eth1Chain, periodStart: uint64): Eth1Block =
|
||||
for i in countdown(chain.blocks.len - 1, 0):
|
||||
let blk = chain.blocks[i]
|
||||
if is_candidate_block(chain.preset, blk, periodStart):
|
||||
return blk
|
||||
|
||||
proc popFirst(eth1Chain: var Eth1Chain) =
|
||||
let removed = eth1Chain.blocks.popFirst
|
||||
eth1Chain.blocksByHash.del removed.voteData.block_hash.asBlockHash
|
||||
eth1_chain_len.set eth1Chain.blocks.len.int64
|
||||
proc popFirst(chain: var Eth1Chain) =
|
||||
let removed = chain.blocks.popFirst
|
||||
chain.blocksByHash.del removed.voteData.block_hash.asBlockHash
|
||||
eth1_chain_len.set chain.blocks.len.int64
|
||||
|
||||
proc addBlock(eth1Chain: var Eth1Chain, newBlock: Eth1Block) =
|
||||
eth1Chain.blocks.addLast newBlock
|
||||
eth1Chain.blocksByHash[newBlock.voteData.block_hash.asBlockHash] = newBlock
|
||||
eth1_chain_len.set eth1Chain.blocks.len.int64
|
||||
proc addBlock*(chain: var Eth1Chain, newBlock: Eth1Block) =
|
||||
chain.blocks.addLast newBlock
|
||||
chain.blocksByHash[newBlock.voteData.block_hash.asBlockHash] = newBlock
|
||||
eth1_chain_len.set chain.blocks.len.int64
|
||||
|
||||
func hash*(x: Eth1Data): Hash =
|
||||
hashData(unsafeAddr x, sizeof(x))
|
||||
|
@ -495,20 +503,21 @@ proc onBlockHeaders*(p: Web3DataProviderRef,
|
|||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
func getDepositsRoot(m: DepositsMerkleizer): Eth2Digest =
|
||||
func getDepositsRoot*(m: DepositsMerkleizer): Eth2Digest =
|
||||
mixInLength(m.getFinalHash, int m.totalChunks)
|
||||
|
||||
func toDepositContractState(merkleizer: DepositsMerkleizer): DepositContractState =
|
||||
func toDepositContractState*(merkleizer: DepositsMerkleizer): DepositContractState =
|
||||
# TODO There is an off by one discrepancy in the size of the arrays here that
|
||||
# need to be investigated. It shouldn't matter as long as the tree is
|
||||
# not populated to its maximum size.
|
||||
result.branch[0..31] = merkleizer.getCombinedChunks[0..31]
|
||||
result.deposit_count[24..31] = merkleizer.getChunkCount().toBytesBE
|
||||
|
||||
func createMerkleizer(s: DepositContractSnapshot): DepositsMerkleizer =
|
||||
DepositsMerkleizer.init(
|
||||
s.depositContractState.branch,
|
||||
s.depositContractState.depositCountU64)
|
||||
func createMerkleizer*(s: DepositContractState): DepositsMerkleizer =
|
||||
DepositsMerkleizer.init(s.branch, s.depositCountU64)
|
||||
|
||||
func createMerkleizer*(s: DepositContractSnapshot): DepositsMerkleizer =
|
||||
createMerkleizer(s.depositContractState)
|
||||
|
||||
func eth1DataFromMerkleizer(eth1Block: Eth2Digest,
|
||||
merkleizer: DepositsMerkleizer): Eth1Data =
|
||||
|
@ -517,24 +526,24 @@ func eth1DataFromMerkleizer(eth1Block: Eth2Digest,
|
|||
deposit_count: merkleizer.getChunkCount,
|
||||
deposit_root: merkleizer.getDepositsRoot)
|
||||
|
||||
proc pruneOldBlocks(m: Eth1Monitor, depositIndex: uint64) =
|
||||
let initialChunks = m.eth2FinalizedDepositsMerkleizer.getChunkCount
|
||||
proc pruneOldBlocks(chain: var Eth1Chain, depositIndex: uint64) =
|
||||
let initialChunks = chain.finalizedDepositsMerkleizer.getChunkCount
|
||||
var lastBlock: Eth1Block
|
||||
|
||||
while m.eth1Chain.blocks.len > 0:
|
||||
let blk = m.eth1Chain.blocks.peekFirst
|
||||
while chain.blocks.len > 0:
|
||||
let blk = chain.blocks.peekFirst
|
||||
if blk.voteData.deposit_count >= depositIndex:
|
||||
break
|
||||
else:
|
||||
for deposit in blk.deposits:
|
||||
m.eth2FinalizedDepositsMerkleizer.addChunk hash_tree_root(deposit).data
|
||||
m.eth1Chain.popFirst()
|
||||
chain.finalizedDepositsMerkleizer.addChunk hash_tree_root(deposit).data
|
||||
chain.popFirst()
|
||||
lastBlock = blk
|
||||
|
||||
if m.eth2FinalizedDepositsMerkleizer.getChunkCount > initialChunks:
|
||||
m.db.putEth2FinalizedTo DepositContractSnapshot(
|
||||
if chain.finalizedDepositsMerkleizer.getChunkCount > initialChunks:
|
||||
chain.db.putEth2FinalizedTo DepositContractSnapshot(
|
||||
eth1Block: lastBlock.voteData.block_hash,
|
||||
depositContractState: m.eth2FinalizedDepositsMerkleizer.toDepositContractState)
|
||||
depositContractState: chain.finalizedDepositsMerkleizer.toDepositContractState)
|
||||
|
||||
eth1_finalized_head.set lastBlock.number.toGaugeValue
|
||||
eth1_finalized_deposits.set lastBlock.voteData.deposit_count.toGaugeValue
|
||||
|
@ -543,24 +552,24 @@ proc pruneOldBlocks(m: Eth1Monitor, depositIndex: uint64) =
|
|||
newTailBlock = lastBlock.voteData.block_hash,
|
||||
depositsCount = lastBlock.voteData.deposit_count
|
||||
|
||||
proc advanceMerkleizer(eth1Chain: Eth1Chain,
|
||||
proc advanceMerkleizer(chain: Eth1Chain,
|
||||
merkleizer: var DepositsMerkleizer,
|
||||
depositIndex: uint64): bool =
|
||||
if eth1Chain.blocks.len == 0:
|
||||
if chain.blocks.len == 0:
|
||||
return depositIndex == merkleizer.getChunkCount
|
||||
|
||||
if eth1Chain.blocks.peekLast.voteData.deposit_count < depositIndex:
|
||||
if chain.blocks.peekLast.voteData.deposit_count < depositIndex:
|
||||
return false
|
||||
|
||||
let
|
||||
firstBlock = eth1Chain.blocks[0]
|
||||
firstBlock = chain.blocks[0]
|
||||
depositsInLastPrunedBlock = firstBlock.voteData.deposit_count -
|
||||
firstBlock.deposits.lenu64
|
||||
|
||||
# advanceMerkleizer should always be called shortly after prunning the chain
|
||||
doAssert depositsInLastPrunedBlock == merkleizer.getChunkCount
|
||||
|
||||
for blk in eth1Chain.blocks:
|
||||
for blk in chain.blocks:
|
||||
for deposit in blk.deposits:
|
||||
if merkleizer.getChunkCount < depositIndex:
|
||||
merkleizer.addChunk hash_tree_root(deposit).data
|
||||
|
@ -569,13 +578,13 @@ proc advanceMerkleizer(eth1Chain: Eth1Chain,
|
|||
|
||||
return merkleizer.getChunkCount == depositIndex
|
||||
|
||||
proc getDepositsRange(eth1Chain: Eth1Chain, first, last: uint64): seq[DepositData] =
|
||||
proc getDepositsRange(chain: Eth1Chain, first, last: uint64): seq[DepositData] =
|
||||
# TODO It's possible to make this faster by performing binary search that
|
||||
# will locate the blocks holding the `first` and `last` indices.
|
||||
# TODO There is an assumption here that the requested range will be present
|
||||
# in the Eth1Chain. This should hold true at the single call site right
|
||||
# now, but we need to guard the pre-conditions better.
|
||||
for blk in eth1Chain.blocks:
|
||||
for blk in chain.blocks:
|
||||
if blk.voteData.deposit_count <= first:
|
||||
continue
|
||||
|
||||
|
@ -597,64 +606,72 @@ proc lowerBound(chain: Eth1Chain, depositCount: uint64): Eth1Block =
|
|||
return
|
||||
result = eth1Block
|
||||
|
||||
proc trackFinalizedState*(m: Eth1Monitor,
|
||||
proc trackFinalizedState*(chain: var Eth1Chain,
|
||||
finalizedEth1Data: Eth1Data,
|
||||
finalizedStateDepositIndex: uint64): bool =
|
||||
# Returns true if the Eth1Monitor is synced to the finalization point
|
||||
if m.eth1Chain.blocks.len == 0:
|
||||
if chain.blocks.len == 0:
|
||||
debug "Eth1 chain not initialized"
|
||||
return false
|
||||
|
||||
let latest = m.eth1Chain.blocks.peekLast
|
||||
let latest = chain.blocks.peekLast
|
||||
if latest.voteData.deposit_count < finalizedEth1Data.deposit_count:
|
||||
debug "Eth1 chain not synced",
|
||||
ourDepositsCount = latest.voteData.deposit_count,
|
||||
targetDepositsCount = finalizedEth1Data.deposit_count
|
||||
return false
|
||||
|
||||
let matchingBlock = m.eth1Chain.lowerBound(finalizedEth1Data.deposit_count)
|
||||
let matchingBlock = chain.lowerBound(finalizedEth1Data.deposit_count)
|
||||
result = if matchingBlock != nil:
|
||||
if matchingBlock.voteData.deposit_root == finalizedEth1Data.deposit_root:
|
||||
matchingBlock.voteDataVerified = true
|
||||
true
|
||||
else:
|
||||
error "Corrupted deposits history detected",
|
||||
depositsCount = finalizedEth1Data.deposit_count,
|
||||
targetDepositsRoot = finalizedEth1Data.deposit_root,
|
||||
ourDepositsRoot = matchingBlock.voteData.deposit_root
|
||||
ourDepositsCount = matchingBlock.voteData.deposit_count,
|
||||
taretDepositsCount = finalizedEth1Data.deposit_count,
|
||||
ourDepositsRoot = matchingBlock.voteData.deposit_root,
|
||||
targetDepositsRoot = finalizedEth1Data.deposit_root
|
||||
false
|
||||
else:
|
||||
error "The Eth1 chain is in inconsistent state",
|
||||
checkpointHash = finalizedEth1Data.block_hash,
|
||||
checkpointDeposits = finalizedEth1Data.deposit_count,
|
||||
localChainStart = shortLog(m.eth1Chain.blocks.peekFirst),
|
||||
localChainEnd = shortLog(m.eth1Chain.blocks.peekLast)
|
||||
localChainStart = shortLog(chain.blocks.peekFirst),
|
||||
localChainEnd = shortLog(chain.blocks.peekLast)
|
||||
false
|
||||
|
||||
if result:
|
||||
m.pruneOldBlocks(finalizedStateDepositIndex)
|
||||
chain.pruneOldBlocks(finalizedStateDepositIndex)
|
||||
|
||||
template trackFinalizedState*(m: Eth1Monitor,
|
||||
finalizedEth1Data: Eth1Data,
|
||||
finalizedStateDepositIndex: uint64): bool =
|
||||
trackFinalizedState(m.eth1Chain, finalizedEth1Data, finalizedStateDepositIndex)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/validator.md#get_eth1_data
|
||||
proc getBlockProposalData*(m: Eth1Monitor,
|
||||
proc getBlockProposalData*(chain: var Eth1Chain,
|
||||
state: BeaconState,
|
||||
finalizedEth1Data: Eth1Data,
|
||||
finalizedStateDepositIndex: uint64): BlockProposalEth1Data =
|
||||
let
|
||||
periodStart = voting_period_start_time(state)
|
||||
hasLatestDeposits = m.trackFinalizedState(finalizedEth1Data,
|
||||
hasLatestDeposits = chain.trackFinalizedState(finalizedEth1Data,
|
||||
finalizedStateDepositIndex)
|
||||
|
||||
var otherVotesCountTable = initCountTable[Eth1Data]()
|
||||
for vote in state.eth1_data_votes:
|
||||
let eth1Block = chain.findBlock(vote)
|
||||
if eth1Block == nil:
|
||||
continue
|
||||
let
|
||||
eth1Block = m.eth1Chain.findBlock(vote)
|
||||
isSuccessor = vote.deposit_count >= state.eth1_data.deposit_count
|
||||
# TODO(zah)
|
||||
# There is a slight deviation from the spec here to deal with the following
|
||||
# problem: the in-memory database of eth1 blocks for a restarted node will
|
||||
# be empty which will lead a "no change" vote. To fix this, we'll need to
|
||||
# add rolling persistance for all potentially voted on blocks.
|
||||
isCandidate = (eth1Block == nil or is_candidate_block(m.preset, eth1Block, periodStart))
|
||||
isCandidate = (is_candidate_block(chain.preset, eth1Block, periodStart))
|
||||
|
||||
if isSuccessor and isCandidate:
|
||||
otherVotesCountTable.inc vote
|
||||
|
@ -669,7 +686,7 @@ proc getBlockProposalData*(m: Eth1Monitor,
|
|||
if uint64((votes + 1) * 2) > SLOTS_PER_ETH1_VOTING_PERIOD:
|
||||
pendingDepositsCount = winningVote.deposit_count - state.eth1_deposit_index
|
||||
else:
|
||||
let latestBlock = m.latestCandidateBlock(periodStart)
|
||||
let latestBlock = chain.latestCandidateBlock(periodStart)
|
||||
if latestBlock == nil:
|
||||
debug "No acceptable eth1 votes and no recent candidates. Voting no change"
|
||||
result.vote = state.eth1_data
|
||||
|
@ -681,13 +698,13 @@ proc getBlockProposalData*(m: Eth1Monitor,
|
|||
if hasLatestDeposits:
|
||||
let
|
||||
totalDepositsInNewBlock = min(MAX_DEPOSITS, pendingDepositsCount)
|
||||
deposits = m.eth1Chain.getDepositsRange(
|
||||
deposits = chain.getDepositsRange(
|
||||
state.eth1_deposit_index,
|
||||
state.eth1_deposit_index + pendingDepositsCount)
|
||||
depositRoots = mapIt(deposits, hash_tree_root(it))
|
||||
|
||||
var scratchMerkleizer = copy m.eth2FinalizedDepositsMerkleizer
|
||||
if m.eth1Chain.advanceMerkleizer(scratchMerkleizer, state.eth1_deposit_index):
|
||||
var scratchMerkleizer = copy chain.finalizedDepositsMerkleizer
|
||||
if chain.advanceMerkleizer(scratchMerkleizer, state.eth1_deposit_index):
|
||||
let proofs = scratchMerkleizer.addChunksAndGenMerkleProofs(depositRoots)
|
||||
for i in 0 ..< totalDepositsInNewBlock:
|
||||
var proof: array[33, Eth2Digest]
|
||||
|
@ -701,6 +718,12 @@ proc getBlockProposalData*(m: Eth1Monitor,
|
|||
else:
|
||||
result.hasMissingDeposits = true
|
||||
|
||||
template getBlockProposalData*(m: Eth1Monitor,
|
||||
state: BeaconState,
|
||||
finalizedEth1Data: Eth1Data,
|
||||
finalizedStateDepositIndex: uint64): BlockProposalEth1Data =
|
||||
getBlockProposalData(m.eth1Chain, state, finalizedEth1Data, finalizedStateDepositIndex)
|
||||
|
||||
{.pop.}
|
||||
|
||||
proc new(T: type Web3DataProvider,
|
||||
|
@ -718,9 +741,28 @@ proc new(T: type Web3DataProvider,
|
|||
|
||||
return ok Web3DataProviderRef(url: web3Url, web3: web3, ns: ns)
|
||||
|
||||
proc putInitialDepositContractSnapshot*(db: BeaconChainDB,
|
||||
s: DepositContractSnapshot) =
|
||||
let existingStart = db.getEth2FinalizedTo()
|
||||
if not existingStart.isOk:
|
||||
db.putEth2FinalizedTo(s)
|
||||
|
||||
template getOrDefault[T, E](r: Result[T, E]): T =
|
||||
type TT = T
|
||||
get(r, default(TT))
|
||||
|
||||
proc init*(T: type Eth1Chain, preset: RuntimePreset, db: BeaconChainDB): T =
|
||||
let finalizedDeposits = db.getEth2FinalizedTo().getOrDefault()
|
||||
let m = finalizedDeposits.createMerkleizer
|
||||
|
||||
T(db: db,
|
||||
preset: preset,
|
||||
finalizedBlockHash: finalizedDeposits.eth1Block,
|
||||
finalizedDepositsMerkleizer: finalizedDeposits.createMerkleizer)
|
||||
|
||||
proc init*(T: type Eth1Monitor,
|
||||
db: BeaconChainDB,
|
||||
preset: RuntimePreset,
|
||||
db: BeaconChainDB,
|
||||
web3Url: string,
|
||||
depositContractAddress: Eth1Address,
|
||||
depositContractSnapshot: DepositContractSnapshot,
|
||||
|
@ -728,10 +770,10 @@ proc init*(T: type Eth1Monitor,
|
|||
var web3Url = web3Url
|
||||
fixupWeb3Urls web3Url
|
||||
|
||||
putInitialDepositContractSnapshot(db, depositContractSnapshot)
|
||||
|
||||
T(state: Initialized,
|
||||
db: db,
|
||||
preset: preset,
|
||||
knownStart: depositContractSnapshot,
|
||||
eth1Chain: Eth1Chain.init(preset, db),
|
||||
depositContractAddress: depositContractAddress,
|
||||
web3Url: web3Url,
|
||||
eth1Network: eth1Network,
|
||||
|
@ -985,30 +1027,24 @@ proc startEth1Syncing(m: Eth1Monitor, delayBeforeStart: Duration) {.async.} =
|
|||
do (err: CatchableError):
|
||||
debug "Error while processing Eth1 block headers subscription", err = err.msg
|
||||
|
||||
let eth2PreviouslyFinalizedTo = m.db.getEth2FinalizedTo()
|
||||
if eth2PreviouslyFinalizedTo.isOk:
|
||||
m.knownStart = eth2PreviouslyFinalizedTo.get
|
||||
|
||||
m.eth2FinalizedDepositsMerkleizer = m.knownStart.createMerkleizer
|
||||
|
||||
let startBlock = awaitWithRetries(
|
||||
m.dataProvider.getBlockByHash(m.knownStart.eth1Block.asBlockHash))
|
||||
m.dataProvider.getBlockByHash(m.eth1Chain.finalizedBlockHash.asBlockHash))
|
||||
|
||||
doAssert m.eth1Chain.blocks.len == 0
|
||||
m.eth1Chain.addBlock Eth1Block(
|
||||
number: Eth1BlockNumber startBlock.number,
|
||||
timestamp: Eth1BlockTimestamp startBlock.timestamp,
|
||||
voteData: eth1DataFromMerkleizer(
|
||||
m.knownStart.eth1Block,
|
||||
m.eth2FinalizedDepositsMerkleizer))
|
||||
m.eth1Chain.finalizedBlockHash,
|
||||
m.eth1Chain.finalizedDepositsMerkleizer))
|
||||
|
||||
var eth1SyncedTo = Eth1BlockNumber startBlock.number
|
||||
eth1_synced_head.set eth1SyncedTo.toGaugeValue
|
||||
eth1_finalized_head.set eth1SyncedTo.toGaugeValue
|
||||
eth1_finalized_deposits.set(
|
||||
m.eth2FinalizedDepositsMerkleizer.getChunkCount.toGaugeValue)
|
||||
m.eth1Chain.finalizedDepositsMerkleizer.getChunkCount.toGaugeValue)
|
||||
|
||||
var scratchMerkleizer = newClone(copy m.eth2FinalizedDepositsMerkleizer)
|
||||
var scratchMerkleizer = newClone(copy m.finalizedDepositsMerkleizer)
|
||||
|
||||
debug "Starting Eth1 syncing", `from` = shortLog(m.eth1Chain.blocks[0])
|
||||
|
||||
|
|
|
@ -140,8 +140,8 @@ proc init*(T: type BeaconNode,
|
|||
# TODO Could move this to a separate "GenesisMonitor" process or task
|
||||
# that would do only this - see Paul's proposal for this.
|
||||
let eth1MonitorRes = await Eth1Monitor.init(
|
||||
db,
|
||||
conf.runtimePreset,
|
||||
db,
|
||||
conf.web3Url,
|
||||
depositContractAddress,
|
||||
depositContractDeployedAt,
|
||||
|
@ -254,8 +254,8 @@ proc init*(T: type BeaconNode,
|
|||
let genesisDepositsSnapshot = SSZ.decode(genesisDepositsSnapshotContents[],
|
||||
DepositContractSnapshot)
|
||||
eth1Monitor = Eth1Monitor.init(
|
||||
db,
|
||||
conf.runtimePreset,
|
||||
db,
|
||||
conf.web3Url,
|
||||
depositContractAddress,
|
||||
genesisDepositsSnapshot,
|
||||
|
|
|
@ -23,7 +23,6 @@ logScope: topics = "nimbusapi"
|
|||
|
||||
type
|
||||
RpcServer = RpcHttpServer
|
||||
Eth1Block = eth1_monitor.Eth1Block
|
||||
|
||||
FutureInfo* = object
|
||||
id*: int
|
||||
|
|
|
@ -404,13 +404,6 @@ type
|
|||
deposit_count*: uint64
|
||||
block_hash*: Eth2Digest
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/validator.md#eth1block
|
||||
Eth1Block* = object
|
||||
timestamp*: uint64
|
||||
deposit_root*: Eth2Digest
|
||||
deposit_count*: uint64
|
||||
# All other eth1 block fields
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#signedvoluntaryexit
|
||||
SignedVoluntaryExit* = object
|
||||
message*: VoluntaryExit
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
# a database, as if a real node was running.
|
||||
|
||||
import
|
||||
confutils, chronicles, stats, times, strformat,
|
||||
math, stats, times, strformat,
|
||||
options, random, tables, os,
|
||||
eth/db/kvstore_sqlite3,
|
||||
confutils, chronicles, eth/db/kvstore_sqlite3,
|
||||
../tests/[testblockutil],
|
||||
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, presets,
|
||||
helpers, validator, signatures, state_transition],
|
||||
../beacon_chain/[
|
||||
attestation_pool, beacon_node_types, beacon_chain_db,
|
||||
interop, validator_pool],
|
||||
validator_pool, eth1_monitor, extras],
|
||||
../beacon_chain/block_pools/[
|
||||
spec_cache, chain_dag, quarantine, clearance],
|
||||
../beacon_chain/ssz/[merkleization, ssz_serialization],
|
||||
|
@ -37,6 +37,18 @@ type Timers = enum
|
|||
tAttest = "Have committee attest to block"
|
||||
tReplay = "Replay all produced blocks"
|
||||
|
||||
proc gauss(r: var Rand; mu = 0.0; sigma = 1.0): float =
|
||||
# TODO This is present in Nim 1.4
|
||||
const K = sqrt(2 / E)
|
||||
var
|
||||
a = 0.0
|
||||
b = 0.0
|
||||
while true:
|
||||
a = rand(r, 1.0)
|
||||
b = (2.0 * rand(r, 1.0) - 1.0) * K
|
||||
if b * b <= -4.0 * a * a * ln(a): break
|
||||
result = mu + sigma * (b / a)
|
||||
|
||||
# TODO confutils is an impenetrable black box. how can a help text be added here?
|
||||
cli do(slots = SLOTS_PER_EPOCH * 6,
|
||||
validators = SLOTS_PER_EPOCH * 200, # One per shard is minimum
|
||||
|
@ -44,9 +56,10 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
|||
blockRatio {.desc: "ratio of slots with blocks"} = 1.0,
|
||||
replay = true):
|
||||
let
|
||||
state = loadGenesis(validators, true)
|
||||
(state, depositContractSnapshot) = loadGenesis(validators, false)
|
||||
genesisBlock = get_initial_beacon_block(state[].data)
|
||||
runtimePreset = defaultRuntimePreset
|
||||
genesisTime = float state[].data.genesis_time
|
||||
|
||||
echo "Starting simulation..."
|
||||
|
||||
|
@ -54,15 +67,25 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
|||
defer: db.close()
|
||||
|
||||
ChainDAGRef.preInit(db, state[].data, state[].data, genesisBlock)
|
||||
putInitialDepositContractSnapshot(db, depositContractSnapshot)
|
||||
|
||||
var
|
||||
chainDag = init(ChainDAGRef, runtimePreset, db)
|
||||
chainDag = ChainDAGRef.init(runtimePreset, db)
|
||||
eth1Chain = Eth1Chain.init(runtimePreset, db)
|
||||
merkleizer = depositContractSnapshot.createMerkleizer
|
||||
quarantine = QuarantineRef()
|
||||
attPool = AttestationPool.init(chainDag, quarantine)
|
||||
timers: array[Timers, RunningStat]
|
||||
attesters: RunningStat
|
||||
r = initRand(1)
|
||||
|
||||
eth1Chain.addBlock Eth1Block(
|
||||
number: Eth1BlockNumber 1,
|
||||
timestamp: Eth1BlockTimestamp genesisTime,
|
||||
voteData: Eth1Data(
|
||||
deposit_root: merkleizer.getDepositsRoot,
|
||||
deposit_count: merkleizer.getChunkCount))
|
||||
|
||||
let replayState = assignClone(chainDag.headState)
|
||||
|
||||
proc handleAttestations(slot: Slot) =
|
||||
|
@ -105,20 +128,23 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
|||
|
||||
chainDag.withState(chainDag.tmpState, head.atSlot(slot)):
|
||||
let
|
||||
finalizedEpochRef = chainDag.getFinalizedEpochRef()
|
||||
proposerIdx = get_beacon_proposer_index(state, cache).get()
|
||||
privKey = hackPrivKey(state.validators[proposerIdx])
|
||||
eth1data = get_eth1data_stub(
|
||||
state.eth1_deposit_index, slot.compute_epoch_at_slot())
|
||||
eth1ProposalData = eth1Chain.getBlockProposalData(
|
||||
state,
|
||||
finalizedEpochRef.eth1_data,
|
||||
finalizedEpochRef.eth1_deposit_index)
|
||||
message = makeBeaconBlock(
|
||||
runtimePreset,
|
||||
hashedState,
|
||||
proposerIdx,
|
||||
head.root,
|
||||
privKey.genRandaoReveal(state.fork, state.genesis_validators_root, slot),
|
||||
eth1data,
|
||||
eth1ProposalData.vote,
|
||||
default(GraffitiBytes),
|
||||
attPool.getAttestationsForBlock(state, cache),
|
||||
@[],
|
||||
eth1ProposalData.deposits,
|
||||
@[],
|
||||
@[],
|
||||
@[],
|
||||
|
@ -148,12 +174,42 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
|||
blck() = added[]
|
||||
chainDag.updateHead(added[], quarantine)
|
||||
|
||||
var
|
||||
lastEth1BlockAt = genesisTime
|
||||
eth1BlockNum = 1000
|
||||
|
||||
for i in 0..<slots:
|
||||
let
|
||||
slot = Slot(i + 1)
|
||||
t =
|
||||
if slot.isEpoch: tEpoch
|
||||
else: tBlock
|
||||
now = genesisTime + float(slot * SECONDS_PER_SLOT)
|
||||
|
||||
while true:
|
||||
let nextBlockTime = lastEth1BlockAt +
|
||||
max(1.0, gauss(r, float SECONDS_PER_ETH1_BLOCK, 3.0))
|
||||
if nextBlockTime > now:
|
||||
break
|
||||
|
||||
inc eth1BlockNum
|
||||
var eth1Block = Eth1Block(
|
||||
number: Eth1BlockNumber eth1BlockNum,
|
||||
timestamp: Eth1BlockTimestamp nextBlockTime,
|
||||
voteData: Eth1Data(
|
||||
block_hash: makeFakeHash(eth1BlockNum)))
|
||||
|
||||
let newDeposits = int clamp(gauss(r, 5.0, 8.0), 0.0, 1000.0)
|
||||
for i in 0 ..< newDeposits:
|
||||
let d = makeDeposit(merkleizer.getChunkCount.int, {skipBLSValidation})
|
||||
eth1Block.deposits.add d
|
||||
merkleizer.addChunk hash_tree_root(d).data
|
||||
|
||||
eth1Block.voteData.deposit_root = merkleizer.getDepositsRoot
|
||||
eth1Block.voteData.deposit_count = merkleizer.getChunkCount
|
||||
|
||||
eth1Chain.addBlock eth1Block
|
||||
lastEth1BlockAt = nextBlockTime
|
||||
|
||||
if blockRatio > 0.0:
|
||||
withTimer(timers[t]):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import
|
||||
stats, os, strformat, times,
|
||||
../tests/[testblockutil],
|
||||
../beacon_chain/[extras],
|
||||
../beacon_chain/[extras, eth1_monitor, beacon_chain_db],
|
||||
../beacon_chain/ssz/[merkleization, ssz_serialization],
|
||||
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers, presets]
|
||||
|
||||
|
@ -40,11 +40,17 @@ func verifyConsensus*(state: BeaconState, attesterRatio: auto) =
|
|||
if current_epoch >= 4:
|
||||
doAssert state.finalized_checkpoint.epoch + 2 >= current_epoch
|
||||
|
||||
proc loadGenesis*(validators: Natural, validate: bool): ref HashedBeaconState =
|
||||
let fn = &"genesim_{const_preset}_{validators}_{SPEC_VERSION}.ssz"
|
||||
let res = (ref HashedBeaconState)()
|
||||
if fileExists(fn):
|
||||
res.data = SSZ.loadFile(fn, BeaconState)
|
||||
proc loadGenesis*(validators: Natural, validate: bool):
|
||||
(ref HashedBeaconState, DepositContractSnapshot) =
|
||||
let
|
||||
genesisFn =
|
||||
&"genesis_{const_preset}_{validators}_{SPEC_VERSION}.ssz"
|
||||
contractSnapshotFn =
|
||||
&"deposit_contract_snapshot_{const_preset}_{validators}_{SPEC_VERSION}.ssz"
|
||||
res = (ref HashedBeaconState)()
|
||||
|
||||
if fileExists(genesisFn) and fileExists(contractSnapshotFn):
|
||||
res.data = SSZ.loadFile(genesisFn, BeaconState)
|
||||
res.root = hash_tree_root(res.data)
|
||||
if res.data.slot != GENESIS_SLOT:
|
||||
echo "Can only start from genesis state"
|
||||
|
@ -53,9 +59,13 @@ proc loadGenesis*(validators: Natural, validate: bool): ref HashedBeaconState =
|
|||
if res.data.validators.len != validators:
|
||||
echo &"Supplied genesis file has {res.data.validators.len} validators, while {validators} where requested, running anyway"
|
||||
|
||||
echo &"Loaded {fn}..."
|
||||
echo &"Loaded {genesisFn}..."
|
||||
|
||||
# TODO check that the private keys are interop keys
|
||||
res
|
||||
|
||||
let contractSnapshot = SSZ.loadFile(contractSnapshotFn,
|
||||
DepositContractSnapshot)
|
||||
(res, contractSnapshot)
|
||||
else:
|
||||
echo "Genesis file not found, making one up (use nimbus_beacon_node createTestnet to make one)"
|
||||
|
||||
|
@ -65,6 +75,11 @@ proc loadGenesis*(validators: Natural, validate: bool): ref HashedBeaconState =
|
|||
deposits = makeInitialDeposits(validators.uint64, flags)
|
||||
|
||||
echo "Generating Genesis..."
|
||||
var merkleizer = init DepositsMerkleizer
|
||||
for d in deposits:
|
||||
merkleizer.addChunk hash_tree_root(d).data
|
||||
let contractSnapshot = DepositContractSnapshot(
|
||||
depositContractState: merkleizer.toDepositContractState)
|
||||
|
||||
res.data = initialize_beacon_state_from_eth1(
|
||||
defaultRuntimePreset,
|
||||
|
@ -75,10 +90,12 @@ proc loadGenesis*(validators: Natural, validate: bool): ref HashedBeaconState =
|
|||
|
||||
res.root = hash_tree_root(res.data)
|
||||
|
||||
echo &"Saving to {fn}..."
|
||||
SSZ.saveFile(fn, res.data)
|
||||
echo &"Saving to {genesisFn}..."
|
||||
SSZ.saveFile(genesisFn, res.data)
|
||||
echo &"Saving to {contractSnapshotFn}..."
|
||||
SSZ.saveFile(contractSnapshotFn, contractSnapshot)
|
||||
|
||||
res
|
||||
(res, contractSnapshot)
|
||||
|
||||
proc printTimers*[Timers: enum](
|
||||
validate: bool,
|
||||
|
|
|
@ -42,7 +42,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
|
|||
validate = true):
|
||||
let
|
||||
flags = if validate: {} else: {skipBlsValidation}
|
||||
state = loadGenesis(validators, validate)
|
||||
(state, depositContractState) = loadGenesis(validators, validate)
|
||||
genesisBlock = get_initial_beacon_block(state.data)
|
||||
|
||||
echo "Starting simulation..."
|
||||
|
|
|
@ -33,6 +33,13 @@ type
|
|||
# Some have a signing_root field
|
||||
signing_root: string
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/validator.md#eth1block
|
||||
Eth1Block* = object
|
||||
timestamp*: uint64
|
||||
deposit_root*: Eth2Digest
|
||||
deposit_count*: uint64
|
||||
# All other eth1 block fields
|
||||
|
||||
# Make signing root optional
|
||||
setDefaultValue(SSZHashTreeRoot, signing_root, "")
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ func makeFakeValidatorPrivKey(i: int): ValidatorPrivKey =
|
|||
var bytes = uint64(i + 1000).toBytesLE()
|
||||
copyMem(addr result, addr bytes[0], sizeof(bytes))
|
||||
|
||||
func makeFakeHash(i: int): Eth2Digest =
|
||||
func makeFakeHash*(i: int): Eth2Digest =
|
||||
var bytes = uint64(i).toBytesLE()
|
||||
static: doAssert sizeof(bytes) <= sizeof(result.data)
|
||||
copyMem(addr result.data[0], addr bytes[0], sizeof(bytes))
|
||||
|
@ -34,7 +34,7 @@ func hackPrivKey*(v: Validator): ValidatorPrivKey =
|
|||
let i = int(uint64.fromBytesLE(bytes))
|
||||
makeFakeValidatorPrivKey(i)
|
||||
|
||||
func makeDeposit(i: int, flags: UpdateFlags): DepositData =
|
||||
func makeDeposit*(i: int, flags: UpdateFlags = {}): DepositData =
|
||||
## Ugly hack for now: we stick the private key in withdrawal_credentials
|
||||
## which means we can repro private key and randao reveal from this data,
|
||||
## for testing :)
|
||||
|
|
Loading…
Reference in New Issue