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
|
validator_duties, validator_api
|
||||||
|
|
||||||
const
|
const
|
||||||
|
genesisFile* = "genesis.ssz"
|
||||||
hasPrompt = not defined(withoutPrompt)
|
hasPrompt = not defined(withoutPrompt)
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -61,6 +62,58 @@ declareHistogram beacon_attestation_received_seconds_from_slot_start,
|
||||||
|
|
||||||
logScope: topics = "beacnde"
|
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 =
|
proc enrForkIdFromState(state: BeaconState): ENRForkID =
|
||||||
let
|
let
|
||||||
forkVer = state.fork.current_version
|
forkVer = state.fork.current_version
|
||||||
|
@ -557,7 +610,7 @@ proc installDebugApiHandlers(rpcServer: RpcServer, node: BeaconNode) =
|
||||||
|
|
||||||
proc installRpcHandlers(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
|
# 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.installValidatorApiHandlers(node)
|
||||||
rpcServer.installBeaconApiHandlers(node)
|
rpcServer.installBeaconApiHandlers(node)
|
||||||
rpcServer.installDebugApiHandlers(node)
|
rpcServer.installDebugApiHandlers(node)
|
||||||
|
@ -823,9 +876,7 @@ when hasPrompt:
|
||||||
# createThread(t, processPromptCommands, addr p)
|
# createThread(t, processPromptCommands, addr p)
|
||||||
|
|
||||||
programMain:
|
programMain:
|
||||||
let
|
let config = makeBannerAndConfig(clientId, BeaconNodeConf)
|
||||||
banner = clientId & "\p" & copyrights & "\p\p" & nimBanner
|
|
||||||
config = BeaconNodeConf.load(version = banner, copyrightBanner = banner)
|
|
||||||
|
|
||||||
setupMainProc(config.logLevel)
|
setupMainProc(config.logLevel)
|
||||||
|
|
||||||
|
|
|
@ -135,3 +135,11 @@ proc updateHead*(node: BeaconNode): BlockRef =
|
||||||
beacon_head_root.set newHead.root.toGaugeValue
|
beacon_head_root.set newHead.root.toGaugeValue
|
||||||
|
|
||||||
newHead
|
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"
|
abbr: "v"
|
||||||
name: "validator" }: seq[ValidatorKeyPath]
|
name: "validator" }: seq[ValidatorKeyPath]
|
||||||
|
|
||||||
externalValidators* {.
|
validatorApi* {.
|
||||||
defaultValue: false
|
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."
|
desc: "Specify whether the validator API should be enabled which would allow for external validators (validator clients) to use this beacon node."
|
||||||
name: "external-validators" }: bool
|
name: "validator-api" }: bool
|
||||||
|
|
||||||
stateSnapshot* {.
|
stateSnapshot* {.
|
||||||
desc: "Json file specifying a recent state snapshot."
|
desc: "Json file specifying a recent state snapshot."
|
||||||
|
@ -319,11 +319,6 @@ type
|
||||||
abbr: "v"
|
abbr: "v"
|
||||||
name: "validator" }: seq[ValidatorKeyPath]
|
name: "validator" }: seq[ValidatorKeyPath]
|
||||||
|
|
||||||
stateSnapshot* {.
|
|
||||||
desc: "Json file specifying a recent state snapshot."
|
|
||||||
abbr: "s"
|
|
||||||
name: "state-snapshot" }: Option[InputFile]
|
|
||||||
|
|
||||||
delayStart* {.
|
delayStart* {.
|
||||||
defaultValue: 0
|
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)."
|
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,
|
tables, json,
|
||||||
|
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
stew/[bitseqs],
|
stew/[byteutils, bitseqs],
|
||||||
json_rpc/jsonmarshal,
|
json_rpc/jsonmarshal,
|
||||||
|
|
||||||
# Local modules
|
# Local modules
|
||||||
|
@ -31,6 +31,12 @@ proc fromJson*(n: JsonNode, argName: string, result: var ValidatorSig) =
|
||||||
proc `%`*(value: ValidatorSig): JsonNode =
|
proc `%`*(value: ValidatorSig): JsonNode =
|
||||||
result = newJString($value)
|
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) =
|
template genFromJsonForIntType(t: untyped) =
|
||||||
proc fromJson*(n: JsonNode, argName: string, result: var t) =
|
proc fromJson*(n: JsonNode, argName: string, result: var t) =
|
||||||
n.kind.expect(JInt, argName)
|
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).
|
# * 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.
|
# 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
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
os, tables, random, strutils,
|
tables, random, strutils,
|
||||||
|
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
chronos,
|
chronos,
|
||||||
chronicles, chronicles/helpers as chroniclesHelpers,
|
chronicles, chronicles/helpers as chroniclesHelpers,
|
||||||
|
|
||||||
# Local modules
|
# Local modules
|
||||||
spec/[datatypes, crypto],
|
spec/[datatypes, crypto], eth2_network
|
||||||
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
|
|
||||||
|
|
||||||
proc setupMainProc*(logLevel: string) =
|
proc setupMainProc*(logLevel: string) =
|
||||||
when compiles(defaultChroniclesStream.output.writer):
|
when compiles(defaultChroniclesStream.output.writer):
|
||||||
|
@ -110,3 +53,7 @@ template ctrlCHandling*(extraCode: untyped) =
|
||||||
info "Shutting down after having received SIGINT"
|
info "Shutting down after having received SIGINT"
|
||||||
extraCode
|
extraCode
|
||||||
setControlCHook(controlCHandler)
|
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 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
|
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
|
# 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
|
proc get_v1_validator_attestation_data(slot: Slot, committee_index: CommitteeIndex): AttestationData
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,13 @@ type
|
||||||
validator_committee_index*: uint64
|
validator_committee_index*: uint64
|
||||||
slot*: Slot
|
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
|
ValidatorPubkeySlotPair* = object
|
||||||
public_key*: ValidatorPubKey
|
public_key*: ValidatorPubKey
|
||||||
slot*: Slot
|
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)
|
result.add (currSlot, idx.get)
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/validator.md#validator-assignments
|
# 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):
|
state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex):
|
||||||
Option[tuple[a: seq[ValidatorIndex], b: CommitteeIndex, c: Slot]] {.used.} =
|
Option[tuple[a: seq[ValidatorIndex], b: CommitteeIndex, c: Slot]] {.used.} =
|
||||||
# Return the committee assignment in the ``epoch`` for ``validator_index``.
|
# Return the committee assignment in the ``epoch`` for ``validator_index``.
|
||||||
|
|
|
@ -27,19 +27,21 @@ type
|
||||||
|
|
||||||
BeaconTime* = distinct int64 ## Seconds from beacon genesis time
|
BeaconTime* = distinct int64 ## Seconds from beacon genesis time
|
||||||
|
|
||||||
proc init*(T: type BeaconClock, state: BeaconState): T =
|
proc init*(T: type BeaconClock, genesis_time: uint64): 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.
|
|
||||||
|
|
||||||
let
|
let
|
||||||
unixGenesis = fromUnix(state.genesis_time.int64)
|
unixGenesis = fromUnix(genesis_time.int64)
|
||||||
# GENESIS_SLOT offsets slot time, but to simplify calculations, we apply that
|
# GENESIS_SLOT offsets slot time, but to simplify calculations, we apply that
|
||||||
# offset to genesis instead of applying it at every time conversion
|
# offset to genesis instead of applying it at every time conversion
|
||||||
unixGenesisOffset = times.seconds(int(GENESIS_SLOT * SECONDS_PER_SLOT))
|
unixGenesisOffset = times.seconds(int(GENESIS_SLOT * SECONDS_PER_SLOT))
|
||||||
|
|
||||||
T(genesis: unixGenesis - unixGenesisOffset)
|
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 =
|
template `<`*(a, b: BeaconTime): bool =
|
||||||
int64(a) < int64(b)
|
int64(a) < int64(b)
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,36 @@ type
|
||||||
|
|
||||||
proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
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()
|
var head = node.updateHead()
|
||||||
|
|
||||||
let proposer = node.blockPool.getProposer(head, slot)
|
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)
|
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...
|
# TODO how do we handle the case when we cannot return a meaningful block? 404...
|
||||||
doAssert(res.message.isSome())
|
# currently this fails often - perhaps because the block has already been
|
||||||
return res.message.get()
|
# 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)
|
onBeaconBlock(node, body)
|
||||||
|
return true
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_validator_attestation_data") do (slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
|
rpcServer.rpc("get_v1_validator_attestation_data") do (slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
|
||||||
discard
|
discard
|
||||||
|
@ -55,11 +88,22 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_validator_duties_attester") do (epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[AttesterDuties]:
|
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]:
|
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()
|
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,
|
public_key: node.blockPool.headState.data.data.validators[it.i].pubkey,
|
||||||
slot: it.s
|
slot: it.s
|
||||||
))
|
))
|
||||||
|
|
|
@ -12,6 +12,7 @@ import
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
stew/shims/[tables, macros],
|
stew/shims/[tables, macros],
|
||||||
chronos, confutils, metrics, json_rpc/[rpcclient, jsonmarshal],
|
chronos, confutils, metrics, json_rpc/[rpcclient, jsonmarshal],
|
||||||
|
chronicles,
|
||||||
blscurve, json_serialization/std/[options, sets, net],
|
blscurve, json_serialization/std/[options, sets, net],
|
||||||
|
|
||||||
# Local modules
|
# Local modules
|
||||||
|
@ -27,7 +28,7 @@ import
|
||||||
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||||
|
|
||||||
## Generate client convenience marshalling wrappers from forward declarations
|
## 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
|
type
|
||||||
ValidatorClient = ref object
|
ValidatorClient = ref object
|
||||||
|
@ -35,9 +36,41 @@ type
|
||||||
client: RpcHttpClient
|
client: RpcHttpClient
|
||||||
beaconClock: BeaconClock
|
beaconClock: BeaconClock
|
||||||
attachedValidators: ValidatorPool
|
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.} =
|
proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} =
|
||||||
|
vc.port_logged "WAKE UP! slot " & $scheduledSlot
|
||||||
|
|
||||||
let
|
let
|
||||||
# The slot we should be at, according to the clock
|
# 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
|
nextSlot = slot + 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# TODO think about handling attestations in addition to block proposals - is waitFor OK...?
|
# 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
|
||||||
# at the start of each epoch - request all validators which should propose
|
# because there are a few back-and-forth requests which could take up time for attesting...
|
||||||
# during this epoch and match that against the validators in this VC instance
|
|
||||||
if scheduledSlot.isEpoch:
|
if scheduledSlot.isEpoch:
|
||||||
let validatorDutiesForEpoch = waitFor vc.client.get_v1_validator_duties_proposer(scheduledSlot.compute_epoch_at_slot)
|
await getValidatorDutiesForEpoch(vc, 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)
|
|
||||||
# check if we have a validator which needs to propose on this slot
|
# check if we have a validator which needs to propose on this slot
|
||||||
if vc.validatorDutiesForEpoch.contains slot:
|
if vc.proposalsForEpoch.contains slot:
|
||||||
let pubkey = vc.validatorDutiesForEpoch[slot]
|
let public_key = vc.proposalsForEpoch[slot]
|
||||||
let validator = vc.attachedValidators.validators[pubkey]
|
let validator = vc.attachedValidators.validators[public_key]
|
||||||
|
|
||||||
# TODO get these from the BN and store them in the ValidatorClient
|
let randao_reveal = validator.genRandaoReveal(
|
||||||
let fork = Fork()
|
vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
|
||||||
let genesis_validators_root = Eth2Digest()
|
|
||||||
|
|
||||||
|
vc.port_logged "await 3"
|
||||||
let randao_reveal = validator.genRandaoReveal(fork, genesis_validators_root, slot)
|
|
||||||
|
|
||||||
var newBlock = SignedBeaconBlock(
|
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)
|
vc.port_logged "await 4"
|
||||||
newBlock.signature = waitFor validator.signBlockProposal(fork, genesis_validators_root, slot, blockRoot)
|
|
||||||
|
|
||||||
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:
|
except CatchableError as err:
|
||||||
echo err.msg
|
error "Caught an unexpected error", err = err.msg
|
||||||
|
|
||||||
let
|
let
|
||||||
nextSlotStart = saturate(vc.beaconClock.fromNow(nextSlot))
|
nextSlotStart = saturate(vc.beaconClock.fromNow(nextSlot))
|
||||||
|
@ -93,10 +135,7 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
|
||||||
asyncCheck vc.onSlotStart(slot, nextSlot)
|
asyncCheck vc.onSlotStart(slot, nextSlot)
|
||||||
|
|
||||||
programMain:
|
programMain:
|
||||||
let
|
let config = makeBannerAndConfig("Nimbus validator client v" & fullVersionStr, ValidatorClientConf)
|
||||||
clientIdVC = "Nimbus validator client v" & fullVersionStr
|
|
||||||
banner = clientIdVC & "\p" & copyrights & "\p\p" & nimBanner
|
|
||||||
config = ValidatorClientConf.load(version = banner, copyrightBanner = banner)
|
|
||||||
|
|
||||||
sleep(config.delayStart * 1000)
|
sleep(config.delayStart * 1000)
|
||||||
|
|
||||||
|
@ -113,29 +152,37 @@ programMain:
|
||||||
cmdParams = commandLineParams(),
|
cmdParams = commandLineParams(),
|
||||||
config
|
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(
|
var vc = ValidatorClient(
|
||||||
config: config,
|
config: config,
|
||||||
client: newRpcHttpClient(),
|
client: newRpcHttpClient(),
|
||||||
beaconClock: BeaconClock.init(genesisState[]),
|
|
||||||
attachedValidators: ValidatorPool.init()
|
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:
|
for curr in vc.config.validatorKeys:
|
||||||
vc.attachedValidators.addLocalValidator(curr.toPubKey, curr)
|
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
|
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
|
let
|
||||||
curSlot = vc.beaconClock.now().slotOrZero()
|
curSlot = vc.beaconClock.now().slotOrZero()
|
||||||
nextSlot = curSlot + 1 # No earlier than GENESIS_SLOT + 1
|
nextSlot = curSlot + 1 # No earlier than GENESIS_SLOT + 1
|
||||||
fromNow = saturate(vc.beaconClock.fromNow(nextSlot))
|
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",
|
info "Scheduling first slot action",
|
||||||
beaconTime = shortLog(vc.beaconClock.now()),
|
beaconTime = shortLog(vc.beaconClock.now()),
|
||||||
nextSlot = shortLog(nextSlot),
|
nextSlot = shortLog(nextSlot),
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
# Standard library
|
# Standard library
|
||||||
os, tables, strutils, times,
|
os, tables, strutils,
|
||||||
|
|
||||||
# Nimble packages
|
# Nimble packages
|
||||||
stew/[objects, bitseqs], stew/shims/macros,
|
stew/[objects, bitseqs], stew/shims/macros,
|
||||||
|
@ -40,14 +40,6 @@ proc saveValidatorKey*(keyName, key: string, conf: BeaconNodeConf) =
|
||||||
writeFile(outputFile, key)
|
writeFile(outputFile, key)
|
||||||
info "Imported validator key", file = outputFile
|
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,
|
proc addLocalValidator*(node: BeaconNode,
|
||||||
state: BeaconState,
|
state: BeaconState,
|
||||||
privKey: ValidatorPrivKey) =
|
privKey: ValidatorPrivKey) =
|
||||||
|
@ -137,13 +129,13 @@ proc sendAttestation(node: BeaconNode,
|
||||||
beacon_attestations_sent.inc()
|
beacon_attestations_sent.inc()
|
||||||
|
|
||||||
type
|
type
|
||||||
ValidatorInfoForMakeBeaconBlockType* = enum
|
ValidatorInfoForMakeBeaconBlockKind* = enum
|
||||||
viValidator
|
viValidator
|
||||||
viRandao_reveal
|
viRandao_reveal
|
||||||
ValidatorInfoForMakeBeaconBlock* = object
|
ValidatorInfoForMakeBeaconBlock* = object
|
||||||
case kind*: ValidatorInfoForMakeBeaconBlockType
|
case kind*: ValidatorInfoForMakeBeaconBlockKind
|
||||||
of viValidator: validator*: AttachedValidator
|
of viValidator: validator*: AttachedValidator
|
||||||
else: randao_reveal*: ValidatorSig
|
of viRandao_reveal: randao_reveal*: ValidatorSig
|
||||||
|
|
||||||
proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
|
||||||
val_info: ValidatorInfoForMakeBeaconBlock,
|
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...
|
# and it's causing problems when the function becomes a generic for 2 types...
|
||||||
proc getRandaoReveal(val_info: ValidatorInfoForMakeBeaconBlock): ValidatorSig =
|
proc getRandaoReveal(val_info: ValidatorInfoForMakeBeaconBlock): ValidatorSig =
|
||||||
if val_info.kind == viValidator:
|
if val_info.kind == viValidator:
|
||||||
val_info.validator.genRandaoReveal(state.fork, state.genesis_validators_root, slot)
|
return val_info.validator.genRandaoReveal(state.fork, state.genesis_validators_root, slot)
|
||||||
else:
|
elif val_info.kind == viRandao_reveal:
|
||||||
val_info.randao_reveal
|
return val_info.randao_reveal
|
||||||
|
|
||||||
let
|
let
|
||||||
poolPtr = unsafeAddr node.blockPool.dag # safe because restore is short-lived
|
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
|
# 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
|
# TODO remove this when done with implementing the VC - here just for convenience during dev
|
||||||
#EXTERNAL_VALIDATORS="yes"
|
#VALIDATOR_API="yes"
|
||||||
|
|
||||||
EXTERNAL_VALIDATORS_ARG=""
|
VALIDATOR_API_ARG=""
|
||||||
if [ "${EXTERNAL_VALIDATORS:-}" == "yes" ]; then
|
if [ "${VALIDATOR_API:-}" == "yes" ]; then
|
||||||
EXTERNAL_VALIDATORS_ARG="--external-validators"
|
VALIDATOR_API_ARG="--validator-api"
|
||||||
# we lass a few seconds as delay for the start ==> that way we can start the
|
# 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
|
# 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
|
# 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 \
|
$VALIDATOR_CLIENT_BIN \
|
||||||
|
--log-level=${LOG_LEVEL:-DEBUG} \
|
||||||
--data-dir=$DATA_DIR \
|
--data-dir=$DATA_DIR \
|
||||||
--rpc-port="$(( $BASE_RPC_PORT + $NODE_ID ))" \
|
--rpc-port="$(( $BASE_RPC_PORT + $NODE_ID ))" \
|
||||||
--delay-start=5 &
|
--delay-start=5 &
|
||||||
|
@ -86,7 +89,7 @@ $BEACON_NODE_BIN \
|
||||||
--tcp-port=$PORT \
|
--tcp-port=$PORT \
|
||||||
--udp-port=$PORT \
|
--udp-port=$PORT \
|
||||||
$SNAPSHOT_ARG \
|
$SNAPSHOT_ARG \
|
||||||
$EXTERNAL_VALIDATORS_ARG \
|
$VALIDATOR_API_ARG \
|
||||||
$NAT_ARG \
|
$NAT_ARG \
|
||||||
$WEB3_ARG \
|
$WEB3_ARG \
|
||||||
--deposit-contract=$DEPOSIT_CONTRACT_ADDRESS \
|
--deposit-contract=$DEPOSIT_CONTRACT_ADDRESS \
|
||||||
|
|
Loading…
Reference in New Issue