mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-10 14:26:26 +00:00
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
This commit is contained in:
parent
18eca263b1
commit
cefd525ab3
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)."
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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``.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -26,8 +26,36 @@ type
|
||||
|
||||
proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
||||
|
||||
rpcServer.rpc("get_v1_validator_blocks") do (slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig) -> BeaconBlock:
|
||||
# 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
|
||||
))
|
||||
|
@ -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)
|
||||
)
|
||||
|
||||
let blockRoot = hash_tree_root(newBlock.message)
|
||||
newBlock.signature = waitFor validator.signBlockProposal(fork, genesis_validators_root, slot, blockRoot)
|
||||
vc.port_logged "await 4"
|
||||
|
||||
discard waitFor vc.client.post_v1_beacon_blocks(newBlock)
|
||||
let blockRoot = hash_tree_root(newBlock.message)
|
||||
newBlock.signature = await validator.signBlockProposal(
|
||||
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, blockRoot)
|
||||
|
||||
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),
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
Loading…
x
Reference in New Issue
Block a user