Add Eth1 deposits simulation to block_sim

This commit is contained in:
Zahary Karadjov 2020-12-03 06:30:35 +02:00 committed by zah
parent 52ca12c79f
commit 338428cbd7
9 changed files with 217 additions and 109 deletions

View File

@ -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,
finalizedStateDepositIndex)
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])

View File

@ -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,

View File

@ -23,7 +23,6 @@ logScope: topics = "nimbusapi"
type
RpcServer = RpcHttpServer
Eth1Block = eth1_monitor.Eth1Block
FutureInfo* = object
id*: int

View File

@ -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

View File

@ -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]):

View File

@ -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,

View File

@ -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..."

View File

@ -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, "")

View File

@ -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 :)