diff --git a/beacon_chain/eth1_monitor.nim b/beacon_chain/eth1_monitor.nim index a49068971..e4c54b56b 100644 --- a/beacon_chain/eth1_monitor.nim +++ b/beacon_chain/eth1_monitor.nim @@ -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]) diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index ddd62111c..c7f389a40 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -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, diff --git a/beacon_chain/rpc/nimbus_api.nim b/beacon_chain/rpc/nimbus_api.nim index f206097df..1e6270b5f 100644 --- a/beacon_chain/rpc/nimbus_api.nim +++ b/beacon_chain/rpc/nimbus_api.nim @@ -23,7 +23,6 @@ logScope: topics = "nimbusapi" type RpcServer = RpcHttpServer - Eth1Block = eth1_monitor.Eth1Block FutureInfo* = object id*: int diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index c76ff7dae..af30eee7d 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -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 diff --git a/research/block_sim.nim b/research/block_sim.nim index 1ce1fe58d..48179ddf3 100644 --- a/research/block_sim.nim +++ b/research/block_sim.nim @@ -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.. 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]): diff --git a/research/simutils.nim b/research/simutils.nim index b60c42b1c..50fb26d78 100644 --- a/research/simutils.nim +++ b/research/simutils.nim @@ -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, diff --git a/research/state_sim.nim b/research/state_sim.nim index e6f6bcea6..5890d5744 100644 --- a/research/state_sim.nim +++ b/research/state_sim.nim @@ -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..." diff --git a/tests/official/test_fixture_ssz_consensus_objects.nim b/tests/official/test_fixture_ssz_consensus_objects.nim index c7e0725a8..0381e5a90 100644 --- a/tests/official/test_fixture_ssz_consensus_objects.nim +++ b/tests/official/test_fixture_ssz_consensus_objects.nim @@ -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, "") diff --git a/tests/testblockutil.nim b/tests/testblockutil.nim index e00389d9d..4ab15082f 100644 --- a/tests/testblockutil.nim +++ b/tests/testblockutil.nim @@ -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 :)