From cefd525ab329f78f1590cb1117120bf7e5e4d927 Mon Sep 17 00:00:00 2001 From: Viktor Kirilov Date: Wed, 27 May 2020 20:06:28 +0300 Subject: [PATCH] more work on the BN/VC split - fixed comments from the last review - getting more data VIA RPC, moved some code back into the BN only - attestation duties being requested as well --- beacon_chain/beacon_node.nim | 59 ++++++++- beacon_chain/beacon_node_common.nim | 8 ++ beacon_chain/conf.nim | 11 +- beacon_chain/eth2_json_rpc_serialization.nim | 8 +- beacon_chain/nimbus_binary_common.nim | 67 ++-------- .../spec/eth2_apis/validator_callsigs.nim | 11 +- .../eth2_apis/validator_callsigs_types.nim | 8 +- beacon_chain/spec/validator.nim | 2 +- beacon_chain/time.nim | 14 ++- beacon_chain/validator_api.nim | 56 ++++++++- beacon_chain/validator_client.nim | 117 ++++++++++++------ beacon_chain/validator_duties.nim | 22 ++-- tests/simulation/run_node.sh | 13 +- 13 files changed, 253 insertions(+), 143 deletions(-) diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index a26895cd5..d8a5b7ff1 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -30,6 +30,7 @@ import validator_duties, validator_api const + genesisFile* = "genesis.ssz" hasPrompt = not defined(withoutPrompt) type @@ -61,6 +62,58 @@ declareHistogram beacon_attestation_received_seconds_from_slot_start, logScope: topics = "beacnde" +proc getStateFromSnapshot(conf: BeaconNodeConf): NilableBeaconStateRef = + var + genesisPath = conf.dataDir/genesisFile + snapshotContents: TaintedString + writeGenesisFile = false + + if conf.stateSnapshot.isSome: + let + snapshotPath = conf.stateSnapshot.get.string + snapshotExt = splitFile(snapshotPath).ext + + if cmpIgnoreCase(snapshotExt, ".ssz") != 0: + error "The supplied state snapshot must be a SSZ file", + suppliedPath = snapshotPath + quit 1 + + snapshotContents = readFile(snapshotPath) + if fileExists(genesisPath): + let genesisContents = readFile(genesisPath) + if snapshotContents != genesisContents: + error "Data directory not empty. Existing genesis state differs from supplied snapshot", + dataDir = conf.dataDir.string, snapshot = snapshotPath + quit 1 + else: + debug "No previous genesis state. Importing snapshot", + genesisPath, dataDir = conf.dataDir.string + writeGenesisFile = true + genesisPath = snapshotPath + else: + try: + snapshotContents = readFile(genesisPath) + except CatchableError as err: + error "Failed to read genesis file", err = err.msg + quit 1 + + result = try: + newClone(SSZ.decode(snapshotContents, BeaconState)) + except SerializationError: + error "Failed to import genesis file", path = genesisPath + quit 1 + + info "Loaded genesis state", path = genesisPath + + if writeGenesisFile: + try: + notice "Writing genesis to data directory", path = conf.dataDir/genesisFile + writeFile(conf.dataDir/genesisFile, snapshotContents.string) + except CatchableError as err: + error "Failed to persist genesis file to data dir", + err = err.msg, genesisFile = conf.dataDir/genesisFile + quit 1 + proc enrForkIdFromState(state: BeaconState): ENRForkID = let forkVer = state.fork.current_version @@ -557,7 +610,7 @@ proc installDebugApiHandlers(rpcServer: RpcServer, node: BeaconNode) = proc installRpcHandlers(rpcServer: RpcServer, node: BeaconNode) = # TODO: remove this if statement later - here just to test the config option for now - if node.config.externalValidators: + if node.config.validatorApi: rpcServer.installValidatorApiHandlers(node) rpcServer.installBeaconApiHandlers(node) rpcServer.installDebugApiHandlers(node) @@ -823,9 +876,7 @@ when hasPrompt: # createThread(t, processPromptCommands, addr p) programMain: - let - banner = clientId & "\p" & copyrights & "\p\p" & nimBanner - config = BeaconNodeConf.load(version = banner, copyrightBanner = banner) + let config = makeBannerAndConfig(clientId, BeaconNodeConf) setupMainProc(config.logLevel) diff --git a/beacon_chain/beacon_node_common.nim b/beacon_chain/beacon_node_common.nim index db9b38144..de231690c 100644 --- a/beacon_chain/beacon_node_common.nim +++ b/beacon_chain/beacon_node_common.nim @@ -135,3 +135,11 @@ proc updateHead*(node: BeaconNode): BlockRef = beacon_head_root.set newHead.root.toGaugeValue newHead + +template findIt*(s: openarray, predicate: untyped): int64 = + var res = -1 + for i, it {.inject.} in s: + if predicate: + res = i + break + res diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index 2cff88785..bd833fe5c 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -111,10 +111,10 @@ type abbr: "v" name: "validator" }: seq[ValidatorKeyPath] - externalValidators* {. + validatorApi* {. defaultValue: false - desc: "Specify whether validators should be in an external process (a validator client) which communicates with the beacon node or they should be embedded." - name: "external-validators" }: bool + desc: "Specify whether the validator API should be enabled which would allow for external validators (validator clients) to use this beacon node." + name: "validator-api" }: bool stateSnapshot* {. desc: "Json file specifying a recent state snapshot." @@ -319,11 +319,6 @@ type abbr: "v" name: "validator" }: seq[ValidatorKeyPath] - stateSnapshot* {. - desc: "Json file specifying a recent state snapshot." - abbr: "s" - name: "state-snapshot" }: Option[InputFile] - delayStart* {. defaultValue: 0 desc: "Seconds from now to delay the starting of the validator client (useful for debug purposes when starting before the beacon node in a script)." diff --git a/beacon_chain/eth2_json_rpc_serialization.nim b/beacon_chain/eth2_json_rpc_serialization.nim index ac6d877fc..3a32ea1b5 100644 --- a/beacon_chain/eth2_json_rpc_serialization.nim +++ b/beacon_chain/eth2_json_rpc_serialization.nim @@ -3,7 +3,7 @@ import tables, json, # Nimble packages - stew/[bitseqs], + stew/[byteutils, bitseqs], json_rpc/jsonmarshal, # Local modules @@ -31,6 +31,12 @@ proc fromJson*(n: JsonNode, argName: string, result: var ValidatorSig) = proc `%`*(value: ValidatorSig): JsonNode = result = newJString($value) +proc fromJson*(n: JsonNode, argName: string, result: var Version) = + hexToByteArray(n.getStr(), array[4, byte](result)) + +proc `%`*(value: Version): JsonNode = + result = newJString($value) + template genFromJsonForIntType(t: untyped) = proc fromJson*(n: JsonNode, argName: string, result: var t) = n.kind.expect(JInt, argName) diff --git a/beacon_chain/nimbus_binary_common.nim b/beacon_chain/nimbus_binary_common.nim index 14e499fa3..6add39c14 100644 --- a/beacon_chain/nimbus_binary_common.nim +++ b/beacon_chain/nimbus_binary_common.nim @@ -5,75 +5,18 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. -# Common routines for a BeaconNode and a BeaconValidator node +# Common routines for a BeaconNode and a ValidatorClient import # Standard library - os, tables, random, strutils, + tables, random, strutils, # Nimble packages chronos, chronicles, chronicles/helpers as chroniclesHelpers, # Local modules - spec/[datatypes, crypto], - conf, - block_pool, eth2_network - -const - genesisFile* = "genesis.ssz" - -proc getStateFromSnapshot*(conf: BeaconNodeConf|ValidatorClientConf): NilableBeaconStateRef = - var - genesisPath = conf.dataDir/genesisFile - snapshotContents: TaintedString - writeGenesisFile = false - - if conf.stateSnapshot.isSome: - let - snapshotPath = conf.stateSnapshot.get.string - snapshotExt = splitFile(snapshotPath).ext - - if cmpIgnoreCase(snapshotExt, ".ssz") != 0: - error "The supplied state snapshot must be a SSZ file", - suppliedPath = snapshotPath - quit 1 - - snapshotContents = readFile(snapshotPath) - if fileExists(genesisPath): - let genesisContents = readFile(genesisPath) - if snapshotContents != genesisContents: - error "Data directory not empty. Existing genesis state differs from supplied snapshot", - dataDir = conf.dataDir.string, snapshot = snapshotPath - quit 1 - else: - debug "No previous genesis state. Importing snapshot", - genesisPath, dataDir = conf.dataDir.string - writeGenesisFile = true - genesisPath = snapshotPath - else: - try: - snapshotContents = readFile(genesisPath) - except CatchableError as err: - error "Failed to read genesis file", err = err.msg - quit 1 - - result = try: - newClone(SSZ.decode(snapshotContents, BeaconState)) - except SerializationError: - error "Failed to import genesis file", path = genesisPath - quit 1 - - info "Loaded genesis state", path = genesisPath - - if writeGenesisFile: - try: - notice "Writing genesis to data directory", path = conf.dataDir/genesisFile - writeFile(conf.dataDir/genesisFile, snapshotContents.string) - except CatchableError as err: - error "Failed to persist genesis file to data dir", - err = err.msg, genesisFile = conf.dataDir/genesisFile - quit 1 + spec/[datatypes, crypto], eth2_network proc setupMainProc*(logLevel: string) = when compiles(defaultChroniclesStream.output.writer): @@ -110,3 +53,7 @@ template ctrlCHandling*(extraCode: untyped) = info "Shutting down after having received SIGINT" extraCode setControlCHook(controlCHandler) + +template makeBannerAndConfig*(clientId: string, ConfType: type): untyped = + let banner = clientId & "\p" & copyrights & "\p\p" & nimBanner + ConfType.load(version = banner, copyrightBanner = banner) diff --git a/beacon_chain/spec/eth2_apis/validator_callsigs.nim b/beacon_chain/spec/eth2_apis/validator_callsigs.nim index 1c2e6e798..a910e7689 100644 --- a/beacon_chain/spec/eth2_apis/validator_callsigs.nim +++ b/beacon_chain/spec/eth2_apis/validator_callsigs.nim @@ -8,10 +8,19 @@ import # TODO check which arguments are part of the path in the REST API + + +# TODO this doesn't have "validator" in it's path but is used by the validators nonetheless +proc get_v1_beacon_states_fork(stateId: string): Fork + +# TODO this doesn't have "validator" in it's path but is used by the validators nonetheless +proc get_v1_beacon_genesis(): BeaconGenesisTuple + proc get_v1_validator_blocks(slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig): BeaconBlock # TODO this doesn't have "validator" in it's path but is used by the validators nonetheless -proc post_v1_beacon_blocks(body: SignedBeaconBlock) +# TODO returns a bool even though in the API there is no return type - because of nim-json-rpc +proc post_v1_beacon_blocks(body: SignedBeaconBlock): bool proc get_v1_validator_attestation_data(slot: Slot, committee_index: CommitteeIndex): AttestationData diff --git a/beacon_chain/spec/eth2_apis/validator_callsigs_types.nim b/beacon_chain/spec/eth2_apis/validator_callsigs_types.nim index 30cce4f39..c4cd4d696 100644 --- a/beacon_chain/spec/eth2_apis/validator_callsigs_types.nim +++ b/beacon_chain/spec/eth2_apis/validator_callsigs_types.nim @@ -15,7 +15,13 @@ type validator_committee_index*: uint64 slot*: Slot - # TODO do we even need this? how about a simple tuple? + # TODO do we even need this? how about a simple tuple (alias)? ValidatorPubkeySlotPair* = object public_key*: ValidatorPubKey slot*: Slot + + # TODO do we even need this? how about a simple tuple (alias)? + BeaconGenesisTuple* = object + genesis_time*: uint64 + genesis_validators_root*: Eth2Digest + genesis_fork_version*: Version diff --git a/beacon_chain/spec/validator.nim b/beacon_chain/spec/validator.nim index a752790ff..c6e4d9887 100644 --- a/beacon_chain/spec/validator.nim +++ b/beacon_chain/spec/validator.nim @@ -220,7 +220,7 @@ func get_beacon_proposer_indexes_for_epoch*(state: BeaconState, epoch: Epoch, st result.add (currSlot, idx.get) # https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/validator.md#validator-assignments -func get_committee_assignment( +func get_committee_assignment*( state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex): Option[tuple[a: seq[ValidatorIndex], b: CommitteeIndex, c: Slot]] {.used.} = # Return the committee assignment in the ``epoch`` for ``validator_index``. diff --git a/beacon_chain/time.nim b/beacon_chain/time.nim index 4fc944f40..8e4932e10 100644 --- a/beacon_chain/time.nim +++ b/beacon_chain/time.nim @@ -27,19 +27,21 @@ type BeaconTime* = distinct int64 ## Seconds from beacon genesis time -proc init*(T: type BeaconClock, state: BeaconState): T = - ## Initialize time from a beacon state. The genesis time of a beacon state is - ## constant throughout its lifetime, so the state from any slot will do, - ## including the genesis state. - +proc init*(T: type BeaconClock, genesis_time: uint64): T = let - unixGenesis = fromUnix(state.genesis_time.int64) + unixGenesis = fromUnix(genesis_time.int64) # GENESIS_SLOT offsets slot time, but to simplify calculations, we apply that # offset to genesis instead of applying it at every time conversion unixGenesisOffset = times.seconds(int(GENESIS_SLOT * SECONDS_PER_SLOT)) T(genesis: unixGenesis - unixGenesisOffset) +proc init*(T: type BeaconClock, state: BeaconState): T = + ## Initialize time from a beacon state. The genesis time of a beacon state is + ## constant throughout its lifetime, so the state from any slot will do, + ## including the genesis state. + BeaconClock.init(state.genesis_time) + template `<`*(a, b: BeaconTime): bool = int64(a) < int64(b) diff --git a/beacon_chain/validator_api.nim b/beacon_chain/validator_api.nim index 98ee70b35..6ba2cec15 100644 --- a/beacon_chain/validator_api.nim +++ b/beacon_chain/validator_api.nim @@ -26,8 +26,36 @@ type proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = + # TODO Probably the `beacon` ones (and not `validator`) should be defined elsewhere... + rpcServer.rpc("get_v1_beacon_states_fork") do (stateId: string) -> Fork: + notice "== get_v1_beacon_states_fork", stateId = stateId + result = case stateId: + of "head": + discard node.updateHead() # TODO do we need this? + node.blockPool.headState.data.data.fork + of "genesis": + Fork(previous_version: Version(GENESIS_FORK_VERSION), + current_version: Version(GENESIS_FORK_VERSION), + epoch: 0.Epoch) + of "finalized": + # TODO + Fork() + of "justified": + # TODO + Fork() + else: + # TODO parse `stateId` as either a number (slot) or a hash (stateRoot) + Fork() + + # TODO Probably the `beacon` ones (and not `validator`) should be defined elsewhere... + rpcServer.rpc("get_v1_beacon_genesis") do () -> BeaconGenesisTuple: + notice "== get_v1_beacon_genesis" + return BeaconGenesisTuple(genesis_time: node.blockPool.headState.data.data.genesis_time, + genesis_validators_root: node.blockPool.headState.data.data.genesis_validators_root, + genesis_fork_version: Version(GENESIS_FORK_VERSION)) + rpcServer.rpc("get_v1_validator_blocks") do (slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig) -> BeaconBlock: - + notice "== get_v1_validator_blocks", slot = slot var head = node.updateHead() let proposer = node.blockPool.getProposer(head, slot) @@ -38,11 +66,16 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = let res = makeBeaconBlockForHeadAndSlot(node, valInfo, proposer.get()[0], graffiti, head, slot) # TODO how do we handle the case when we cannot return a meaningful block? 404... - doAssert(res.message.isSome()) - return res.message.get() + # currently this fails often - perhaps because the block has already been + # processed and signed with the inProcess validator... + # doAssert(res.message.isSome()) + return res.message.get(BeaconBlock()) # returning a default if empty - rpcServer.rpc("post_v1_beacon_blocks") do (body: SignedBeaconBlock): + rpcServer.rpc("post_v1_beacon_blocks") do (body: SignedBeaconBlock) -> bool : + notice "== post_v1_beacon_blocks" + # TODO make onBeaconBlock return a result and discard it wherever its unnecessary onBeaconBlock(node, body) + return true rpcServer.rpc("get_v1_validator_attestation_data") do (slot: Slot, committee_index: CommitteeIndex) -> AttestationData: discard @@ -55,11 +88,22 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = discard rpcServer.rpc("post_v1_validator_duties_attester") do (epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[AttesterDuties]: - discard + notice "== post_v1_validator_duties_attester", epoch = epoch + for pubkey in public_keys: + let idx = node.blockPool.headState.data.data.validators.asSeq.findIt(it.pubKey == pubkey) + if idx != -1: + let res = node.blockPool.headState.data.data.get_committee_assignment(epoch, idx.ValidatorIndex) + if res.isSome: + result.add(AttesterDuties(public_key: pubkey, + committee_index: res.get.b, + committee_length: res.get.a.len.uint64, + validator_committee_index: res.get.a.find(idx.ValidatorIndex).uint64, + slot: res.get.c)) rpcServer.rpc("get_v1_validator_duties_proposer") do (epoch: Epoch) -> seq[ValidatorPubkeySlotPair]: + notice "== get_v1_validator_duties_proposer", epoch = epoch var cache = get_empty_per_epoch_cache() - return get_beacon_proposer_indexes_for_epoch(node.blockPool.headState.data.data, epoch, cache).mapIt(ValidatorPubkeySlotPair( + result = get_beacon_proposer_indexes_for_epoch(node.blockPool.headState.data.data, epoch, cache).mapIt(ValidatorPubkeySlotPair( public_key: node.blockPool.headState.data.data.validators[it.i].pubkey, slot: it.s )) diff --git a/beacon_chain/validator_client.nim b/beacon_chain/validator_client.nim index d60a11755..20537eb42 100644 --- a/beacon_chain/validator_client.nim +++ b/beacon_chain/validator_client.nim @@ -12,6 +12,7 @@ import # Nimble packages stew/shims/[tables, macros], chronos, confutils, metrics, json_rpc/[rpcclient, jsonmarshal], + chronicles, blscurve, json_serialization/std/[options, sets, net], # Local modules @@ -27,7 +28,7 @@ import template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0] ## Generate client convenience marshalling wrappers from forward declarations -createRpcSigs(RpcClient, sourceDir & DirSep & "spec" & DirSep & "eth2_apis" & DirSep & "validator_callsigs.nim") +createRpcSigs(RpcClient, sourceDir / "spec" / "eth2_apis" / "validator_callsigs.nim") type ValidatorClient = ref object @@ -35,9 +36,41 @@ type client: RpcHttpClient beaconClock: BeaconClock attachedValidators: ValidatorPool - validatorDutiesForEpoch: Table[Slot, ValidatorPubKey] + fork: Fork + proposalsForEpoch: Table[Slot, ValidatorPubKey] + attestationsForEpoch: Table[Slot, AttesterDuties] + beaconGenesis: BeaconGenesisTuple + +# TODO remove this and move to real logging once done experimenting +# it's much easier to distinguish such output from the one with timestamps +proc port_logged(vc: ValidatorClient, msg: string) = + echo "== ", vc.config.rpcPort, " ", msg + +proc getValidatorDutiesForEpoch(vc: ValidatorClient, epoch: Epoch) {.gcsafe, async.} = + vc.port_logged "await 1" + let proposals = await vc.client.get_v1_validator_duties_proposer(epoch) + vc.port_logged "await 2" + # update the block proposal duties this VC should do during this epoch + vc.proposalsForEpoch.clear() + for curr in proposals: + if vc.attachedValidators.validators.contains curr.public_key: + vc.proposalsForEpoch.add(curr.slot, curr.public_key) + + # couldn't use mapIt in ANY shape or form so reverting to raw loops - sorry Sean Parent :| + var validatorPubkeys: seq[ValidatorPubKey] + for key in vc.attachedValidators.validators.keys: + validatorPubkeys.add key + # update the attestation duties this VC should do during this epoch + let attestations = await vc.client.post_v1_validator_duties_attester(epoch, validatorPubkeys) + vc.attestationsForEpoch.clear() + for a in attestations: + vc.attestationsForEpoch.add(a.slot, a) + + # for now we will get the fork each time we update the validator duties for each epoch + vc.fork = await vc.client.get_v1_beacon_states_fork("head") proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} = + vc.port_logged "WAKE UP! slot " & $scheduledSlot let # The slot we should be at, according to the clock @@ -49,40 +82,49 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a nextSlot = slot + 1 try: - # TODO think about handling attestations in addition to block proposals - is waitFor OK...? - - # at the start of each epoch - request all validators which should propose - # during this epoch and match that against the validators in this VC instance + # at the start of each epoch - request all validator duties for the current epoch + # TODO perhaps call this not on the first slot of each Epoch but perhaps 1 slot earlier + # because there are a few back-and-forth requests which could take up time for attesting... if scheduledSlot.isEpoch: - let validatorDutiesForEpoch = waitFor vc.client.get_v1_validator_duties_proposer(scheduledSlot.compute_epoch_at_slot) - # update the duties (block proposals) this VC client should do during this epoch - vc.validatorDutiesForEpoch.clear() - for curr in validatorDutiesForEpoch: - if vc.attachedValidators.validators.contains curr.public_key: - vc.validatorDutiesForEpoch.add(curr.slot, curr.public_key) + await getValidatorDutiesForEpoch(vc, scheduledSlot.compute_epoch_at_slot) + # check if we have a validator which needs to propose on this slot - if vc.validatorDutiesForEpoch.contains slot: - let pubkey = vc.validatorDutiesForEpoch[slot] - let validator = vc.attachedValidators.validators[pubkey] + if vc.proposalsForEpoch.contains slot: + let public_key = vc.proposalsForEpoch[slot] + let validator = vc.attachedValidators.validators[public_key] - # TODO get these from the BN and store them in the ValidatorClient - let fork = Fork() - let genesis_validators_root = Eth2Digest() + let randao_reveal = validator.genRandaoReveal( + vc.fork, vc.beaconGenesis.genesis_validators_root, slot) - - let randao_reveal = validator.genRandaoReveal(fork, genesis_validators_root, slot) + vc.port_logged "await 3" var newBlock = SignedBeaconBlock( - message: waitFor vc.client.get_v1_validator_blocks(slot, Eth2Digest(), randao_reveal) + message: await vc.client.get_v1_validator_blocks(slot, Eth2Digest(), randao_reveal) ) + vc.port_logged "await 4" + let blockRoot = hash_tree_root(newBlock.message) - newBlock.signature = waitFor validator.signBlockProposal(fork, genesis_validators_root, slot, blockRoot) + newBlock.signature = await validator.signBlockProposal( + vc.fork, vc.beaconGenesis.genesis_validators_root, slot, blockRoot) - discard waitFor vc.client.post_v1_beacon_blocks(newBlock) + vc.port_logged "about to await for the last time!" + + discard await vc.client.post_v1_beacon_blocks(newBlock) + + vc.port_logged "did we do it?" + + # check if we have a validator which needs to propose on this slot + if vc.attestationsForEpoch.contains slot: + let a = vc.attestationsForEpoch[slot] + let validator = vc.attachedValidators.validators[a.public_key] + + discard validator + + vc.port_logged("attestation: " & $a.committee_index.int64 & " " & $a.validator_committee_index) except CatchableError as err: - echo err.msg + error "Caught an unexpected error", err = err.msg let nextSlotStart = saturate(vc.beaconClock.fromNow(nextSlot)) @@ -93,10 +135,7 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a asyncCheck vc.onSlotStart(slot, nextSlot) programMain: - let - clientIdVC = "Nimbus validator client v" & fullVersionStr - banner = clientIdVC & "\p" & copyrights & "\p\p" & nimBanner - config = ValidatorClientConf.load(version = banner, copyrightBanner = banner) + let config = makeBannerAndConfig("Nimbus validator client v" & fullVersionStr, ValidatorClientConf) sleep(config.delayStart * 1000) @@ -113,29 +152,37 @@ programMain: cmdParams = commandLineParams(), config - # TODO: the genesis time should be obtained through calls to the beacon node - # this applies also for genesis_validators_root... and the fork! - var genesisState = config.getStateFromSnapshot() - var vc = ValidatorClient( config: config, client: newRpcHttpClient(), - beaconClock: BeaconClock.init(genesisState[]), attachedValidators: ValidatorPool.init() ) - vc.validatorDutiesForEpoch.init() + vc.proposalsForEpoch.init() + vc.attestationsForEpoch.init() + # load all the validators from the data dir into memory for curr in vc.config.validatorKeys: vc.attachedValidators.addLocalValidator(curr.toPubKey, curr) + # TODO perhaps we should handle the case if the BN is down and try to connect to it + # untill success, and also later on disconnets we should continue trying to reconnect waitFor vc.client.connect("localhost", Port(config.rpcPort)) # TODO: use config.rpcAddress - echo "connected to beacon node running on port ", config.rpcPort + info "Connected to beacon node", port = config.rpcPort + + # init the beacon clock + vc.beaconGenesis = waitFor vc.client.get_v1_beacon_genesis() + vc.beaconClock = BeaconClock.init(vc.beaconGenesis.genesis_time) let curSlot = vc.beaconClock.now().slotOrZero() nextSlot = curSlot + 1 # No earlier than GENESIS_SLOT + 1 fromNow = saturate(vc.beaconClock.fromNow(nextSlot)) + # onSlotStart() requests the validator duties only on the start of each epoch + # so we should request the duties here when the VC binary boots up in order + # to handle the case when in the middle of an epoch. Also for the genesis slot. + waitFor vc.getValidatorDutiesForEpoch(curSlot.compute_epoch_at_slot) + info "Scheduling first slot action", beaconTime = shortLog(vc.beaconClock.now()), nextSlot = shortLog(nextSlot), diff --git a/beacon_chain/validator_duties.nim b/beacon_chain/validator_duties.nim index 9b16d98c0..cdf13b22e 100644 --- a/beacon_chain/validator_duties.nim +++ b/beacon_chain/validator_duties.nim @@ -7,7 +7,7 @@ import # Standard library - os, tables, strutils, times, + os, tables, strutils, # Nimble packages stew/[objects, bitseqs], stew/shims/macros, @@ -40,14 +40,6 @@ proc saveValidatorKey*(keyName, key: string, conf: BeaconNodeConf) = writeFile(outputFile, key) info "Imported validator key", file = outputFile -template findIt(s: openarray, predicate: untyped): int = - var res = -1 - for i, it {.inject.} in s: - if predicate: - res = i - break - res - proc addLocalValidator*(node: BeaconNode, state: BeaconState, privKey: ValidatorPrivKey) = @@ -137,13 +129,13 @@ proc sendAttestation(node: BeaconNode, beacon_attestations_sent.inc() type - ValidatorInfoForMakeBeaconBlockType* = enum + ValidatorInfoForMakeBeaconBlockKind* = enum viValidator viRandao_reveal ValidatorInfoForMakeBeaconBlock* = object - case kind*: ValidatorInfoForMakeBeaconBlockType + case kind*: ValidatorInfoForMakeBeaconBlockKind of viValidator: validator*: AttachedValidator - else: randao_reveal*: ValidatorSig + of viRandao_reveal: randao_reveal*: ValidatorSig proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode, val_info: ValidatorInfoForMakeBeaconBlock, @@ -170,9 +162,9 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode, # and it's causing problems when the function becomes a generic for 2 types... proc getRandaoReveal(val_info: ValidatorInfoForMakeBeaconBlock): ValidatorSig = if val_info.kind == viValidator: - val_info.validator.genRandaoReveal(state.fork, state.genesis_validators_root, slot) - else: - val_info.randao_reveal + return val_info.validator.genRandaoReveal(state.fork, state.genesis_validators_root, slot) + elif val_info.kind == viRandao_reveal: + return val_info.randao_reveal let poolPtr = unsafeAddr node.blockPool.dag # safe because restore is short-lived diff --git a/tests/simulation/run_node.sh b/tests/simulation/run_node.sh index 103b02c05..5e31092ee 100755 --- a/tests/simulation/run_node.sh +++ b/tests/simulation/run_node.sh @@ -63,15 +63,18 @@ cd "$DATA_DIR" # uncomment to force always using an external VC binary for VC duties # TODO remove this when done with implementing the VC - here just for convenience during dev -#EXTERNAL_VALIDATORS="yes" +#VALIDATOR_API="yes" -EXTERNAL_VALIDATORS_ARG="" -if [ "${EXTERNAL_VALIDATORS:-}" == "yes" ]; then - EXTERNAL_VALIDATORS_ARG="--external-validators" +VALIDATOR_API_ARG="" +if [ "${VALIDATOR_API:-}" == "yes" ]; then + VALIDATOR_API_ARG="--validator-api" # we lass a few seconds as delay for the start ==> that way we can start the # beacon node before the VC - otherwise we would have to add "&" conditionally to # the command which starts the BN - makes the shell script much more complicated + # TODO launch the VC through the start.sh script in order to address this comment: + # https://github.com/status-im/nim-beacon-chain/pull/1055#discussion_r429540155 $VALIDATOR_CLIENT_BIN \ + --log-level=${LOG_LEVEL:-DEBUG} \ --data-dir=$DATA_DIR \ --rpc-port="$(( $BASE_RPC_PORT + $NODE_ID ))" \ --delay-start=5 & @@ -86,7 +89,7 @@ $BEACON_NODE_BIN \ --tcp-port=$PORT \ --udp-port=$PORT \ $SNAPSHOT_ARG \ - $EXTERNAL_VALIDATORS_ARG \ + $VALIDATOR_API_ARG \ $NAT_ARG \ $WEB3_ARG \ --deposit-contract=$DEPOSIT_CONTRACT_ADDRESS \