- work towards more REST API endpoints being implemented

- testnets can now be launched with a separate validator client - make altona SCRIPT_PARAMS="--separateVC"
- reverted the ctrl+C signal handler code reuse - not necessary for the VC anyway (default is good enough)
- added a bit more logging in the VC
- removed unnecessary code in the VC - connect() just parses the address & port...
- fixed a couple more VC issues - when fetching the duties for an epoch fails on the BN side ==> the VC shouldn't be left in a broken state
- documented the currently supported json-rpc endpoints
- added more checks on the BN side for the API - bounds-checking the requests & also checking if the BN itself is synced
- other cleanup

currently a local sim doesn't finalize, but participation in the altona network with a separate VC is painless and works just as well as with in-process validators in a BN
This commit is contained in:
Viktor Kirilov 2020-07-08 13:11:22 +03:00
parent fc8502c54e
commit 1482b0430d
10 changed files with 528 additions and 145 deletions

View File

@ -1223,7 +1223,14 @@ programMain:
var node = waitFor BeaconNode.init(rng, config) var node = waitFor BeaconNode.init(rng, config)
ctrlCHandling: status = BeaconNodeStatus.Stopping ## Ctrl+C handling
proc controlCHandler() {.noconv.} =
when defined(windows):
# workaround for https://github.com/nim-lang/Nim/issues/4057
setupForeignThreadGc()
info "Shutting down after having received SIGINT"
status = BeaconNodeStatus.Stopping
setControlCHook(controlCHandler)
when hasPrompt: when hasPrompt:
initPrompt(node) initPrompt(node)

View File

@ -44,16 +44,6 @@ proc setupMainProc*(logLevel: string) =
stderr.write "Invalid value for --log-level. " & err.msg stderr.write "Invalid value for --log-level. " & err.msg
quit 1 quit 1
template ctrlCHandling*(extraCode: untyped) =
## Ctrl+C handling
proc controlCHandler() {.noconv.} =
when defined(windows):
# workaround for https://github.com/nim-lang/Nim/issues/4057
setupForeignThreadGc()
info "Shutting down after having received SIGINT"
extraCode
setControlCHook(controlCHandler)
template makeBannerAndConfig*(clientId: string, ConfType: type): untyped = template makeBannerAndConfig*(clientId: string, ConfType: type): untyped =
let let
version = clientId & "\p" & copyrights & "\p\p" & version = clientId & "\p" & copyrights & "\p\p" &

View File

@ -12,6 +12,55 @@ proc get_v1_beacon_states_root(stateId: string): Eth2Digest
# TODO stateId is part of the REST path # TODO stateId is part of the REST path
proc get_v1_beacon_states_fork(stateId: string): Fork proc get_v1_beacon_states_fork(stateId: string): Fork
# TODO stateId is part of the REST path
proc get_v1_beacon_states_finality_checkpoints(
stateId: string): BeaconStatesFinalityCheckpointsTuple
# TODO stateId is part of the REST path
proc get_v1_beacon_states_stateId_validators(
stateId: string, validatorIds: seq[string],
status: string): seq[BeaconStatesValidatorsTuple]
# TODO stateId and validatorId are part of the REST path
proc get_v1_beacon_states_stateId_validators_validatorId(
stateId: string, validatorId: string): BeaconStatesValidatorsTuple
# TODO stateId and epoch are part of the REST path
proc get_v1_beacon_states_stateId_committees_epoch(stateId: string,
epoch: uint64, index: uint64, slot: uint64): seq[BeaconStatesCommitteesTuple]
proc get_v1_beacon_headers(slot: uint64, parent_root: Eth2Digest): seq[BeaconHeadersTuple]
# TODO blockId is part of the REST path
proc get_v1_beacon_headers_blockId(blockId: string):
tuple[canonical: bool, header: SignedBeaconBlockHeader]
# TODO blockId is part of the REST path
proc get_v1_beacon_blocks_blockId(blockId: string): SignedBeaconBlock
# TODO blockId is part of the REST path
proc get_v1_beacon_blocks_blockId_root(blockId: string): Eth2Digest
# TODO blockId is part of the REST path
proc get_v1_beacon_blocks_blockId_attestations(blockId: string): seq[Attestation]
# TODO POST /v1/beacon/pool/attester_slashings
# TODO GET /v1/beacon/pool/attester_slashings
# TODO POST /v1/beacon/pool/proposer_slashings
# TODO GET /v1/beacon/pool/proposer_slashings
# TODO POST /v1/beacon/pool/voluntary_exits
# TODO GET /v1/beacon/pool/voluntary_exits
# TODO POST /v1/beacon/pool/attestations
# TODO GET /v1/beacon/pool/attestations
proc post_v1_beacon_pool_attestations(attestation: Attestation): bool
proc get_v1_config_fork_schedule(): seq[tuple[epoch: uint64, version: Version]]
# TODO stateId is part of the REST path
proc get_v1_debug_beacon_states_stateId(stateId: string): BeaconState
# TODO: delete old stuff # TODO: delete old stuff

View File

@ -21,3 +21,23 @@ type
genesis_time: uint64 genesis_time: uint64
genesis_validators_root: Eth2Digest genesis_validators_root: Eth2Digest
genesis_fork_version: Version genesis_fork_version: Version
BeaconStatesFinalityCheckpointsTuple* = tuple
previous_justified: Checkpoint
current_justified: Checkpoint
finalized: Checkpoint
BeaconStatesValidatorsTuple* = tuple
validator: Validator
status: string
balance: uint64
BeaconStatesCommitteesTuple* = tuple
index: uint64
slot: uint64
validators: seq[uint64] # each object in the sequence should have an index field...
BeaconHeadersTuple* = tuple
root: Eth2Digest
canonical: bool
header: SignedBeaconBlockHeader

View File

@ -10,19 +10,16 @@ import
# calls that return a bool are actually without a return type in the main REST API # calls that return a bool are actually without a return type in the main REST API
# spec but nim-json-rpc requires that all RPC calls have a return type. # spec but nim-json-rpc requires that all RPC calls have a return type.
proc post_v1_beacon_pool_attestations(attestation: Attestation): bool proc get_v1_validator_block(slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig): BeaconBlock
# TODO slot is part of the REST path proc post_v1_validator_block(body: SignedBeaconBlock): bool
proc get_v1_validator_blocks(slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig): BeaconBlock
proc post_v1_beacon_blocks(body: SignedBeaconBlock): bool proc get_v1_validator_attestation(slot: Slot, committee_index: CommitteeIndex): AttestationData
proc get_v1_validator_attestation_data(slot: Slot, committee_index: CommitteeIndex): AttestationData
# TODO at the time of writing (10.06.2020) the API specifies this call to have a hash of # TODO at the time of writing (10.06.2020) the API specifies this call to have a hash of
# the attestation data instead of the object itself but we also need the slot.. see here: # the attestation data instead of the object itself but we also need the slot.. see here:
# https://docs.google.com/spreadsheets/d/1kVIx6GvzVLwNYbcd-Fj8YUlPf4qGrWUlS35uaTnIAVg/edit?disco=AAAAGh7r_fQ # https://docs.google.com/spreadsheets/d/1kVIx6GvzVLwNYbcd-Fj8YUlPf4qGrWUlS35uaTnIAVg/edit?disco=AAAAGh7r_fQ
proc get_v1_validator_aggregate_attestation(attestation_data: AttestationData): Attestation proc get_v1_validator_aggregate_and_proof(attestation_data: AttestationData): Attestation
proc post_v1_validator_aggregate_and_proof(payload: SignedAggregateAndProof): bool proc post_v1_validator_aggregate_and_proof(payload: SignedAggregateAndProof): bool

View File

@ -7,10 +7,10 @@
import import
# Standard library # Standard library
tables, strutils, parseutils, tables, strutils, parseutils, sequtils,
# Nimble packages # Nimble packages
stew/[objects], stew/[byteutils, objects],
chronos, metrics, json_rpc/[rpcserver, jsonmarshal], chronos, metrics, json_rpc/[rpcserver, jsonmarshal],
chronicles, chronicles,
@ -27,86 +27,281 @@ type
logScope: topics = "valapi" logScope: topics = "valapi"
proc toBlockSlot(blckRef: BlockRef): BlockSlot =
blckRef.atSlot(blckRef.slot)
proc parseRoot(str: string): Eth2Digest =
return Eth2Digest(data: hexToByteArray[32](str))
proc parsePubkey(str: string): ValidatorPubKey =
let pubkeyRes = fromHex(ValidatorPubKey, str)
if pubkeyRes.isErr:
raise newException(CatchableError, "Not a valid public key")
return pubkeyRes[]
proc doChecksAndGetCurrentHead(node: BeaconNode, slot: Slot): BlockRef =
result = node.blockPool.head.blck
if not node.isSynced(result):
raise newException(CatchableError, "Cannot fulfill request until ndoe is synced")
# TODO for now we limit the requests arbitrarily by up to 2 epochs into the future
if result.slot + uint64(2 * SLOTS_PER_EPOCH) < slot:
raise newException(CatchableError, "Requesting way ahead of the current head")
proc doChecksAndGetCurrentHead(node: BeaconNode, epoch: Epoch): BlockRef =
node.doChecksAndGetCurrentHead(epoch.compute_start_slot_at_epoch)
# TODO currently this function throws if the validator isn't found - is this OK?
proc getValidatorInfoFromValidatorId(
state: BeaconState,
current_epoch: Epoch,
validatorId: string,
status = ""):
Option[BeaconStatesValidatorsTuple] =
const allowedStatuses = ["", "pending", "pending_initialized", "pending_queued",
"active", "active_ongoing", "active_exiting", "active_slashed", "exited",
"exited_unslashed", "exited_slashed", "withdrawal", "withdrawal_possible",
"withdrawal_done"]
if status notin allowedStatuses:
raise newException(CatchableError, "Invalid status requested")
let validator = if validatorId.startsWith("0x"):
let pubkey = parsePubkey(validatorId)
let idx = state.validators.asSeq.findIt(it.pubKey == pubkey)
if idx == -1:
raise newException(CatchableError, "Could not find validator")
state.validators[idx]
else:
var valIdx: BiggestUInt
if parseBiggestUInt(validatorId, valIdx) != validatorId.len:
raise newException(CatchableError, "Not a valid index")
if state.validators.len >= valIdx.int:
raise newException(CatchableError, "Index out of bounds")
state.validators[valIdx]
# time to determine the status of the validator - the code mimics
# whatever is detailed here: https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ
let actual_status = if validator.activation_epoch > current_epoch:
# pending
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH:
"pending_initialized"
else:
# validator.activation_eligibility_epoch < FAR_FUTURE_EPOCH:
"pending_queued"
elif validator.activation_epoch <= current_epoch and
current_epoch < validator.exit_epoch:
# active
if validator.exit_epoch == FAR_FUTURE_EPOCH:
"active_ongoing"
elif not validator.slashed:
# validator.exit_epoch < FAR_FUTURE_EPOCH
"active_exiting"
else:
# validator.exit_epoch < FAR_FUTURE_EPOCH and validator.slashed:
"active_slashed"
elif validator.exit_epoch <= current_epoch and
current_epoch < validator.withdrawable_epoch:
# exited
if not validator.slashed:
"exited_unslashed"
else:
# validator.slashed
"exited_slashed"
elif validator.withdrawable_epoch <= current_epoch:
# withdrawal
if validator.effective_balance != 0:
"withdrawal_possible"
else:
# validator.effective_balance == 0
"withdrawal_done"
else:
raise newException(CatchableError, "Invalid validator status")
# if the requested status doesn't match the actual status
if status != "" and status notin actual_status:
return none(BeaconStatesValidatorsTuple)
return some((validator: validator, status: actual_status,
balance: validator.effective_balance))
proc getBlockSlotFromString(node: BeaconNode, slot: string): BlockSlot =
var parsed: BiggestUInt
if parseBiggestUInt(slot, parsed) != slot.len:
raise newException(CatchableError, "Not a valid slot number")
let head = node.doChecksAndGetCurrentHead(parsed.Slot)
return head.atSlot(parsed.Slot)
proc getBlockDataFromBlockId(node: BeaconNode, blockId: string): BlockData =
result = case blockId:
of "head":
node.blockPool.get(node.blockPool.head.blck)
of "genesis":
node.blockPool.get(node.blockPool.tail)
of "finalized":
node.blockPool.get(node.blockPool.finalizedHead.blck)
else:
if blockId.startsWith("0x"):
let blckRoot = parseRoot(blockId)
let blockData = node.blockPool.get(blckRoot)
if blockData.isNone:
raise newException(CatchableError, "Block not found")
blockData.get()
else:
let blockSlot = node.getBlockSlotFromString(blockId)
if blockSlot.blck.isNil:
raise newException(CatchableError, "Block not found")
node.blockPool.get(blockSlot.blck)
proc stateIdToBlockSlot(node: BeaconNode, stateId: string): BlockSlot =
result = case stateId:
of "head":
node.blockPool.head.blck.toBlockSlot()
of "genesis":
node.blockPool.tail.toBlockSlot()
of "finalized":
node.blockPool.finalizedHead
of "justified":
node.blockPool.justifiedState.blck.toBlockSlot()
else:
if stateId.startsWith("0x"):
let blckRoot = parseRoot(stateId)
let blckRef = node.blockPool.getRef(blckRoot)
if blckRef.isNil:
raise newException(CatchableError, "Block not found")
blckRef.toBlockSlot()
else:
node.getBlockSlotFromString(stateId)
# TODO Probably the `beacon` ones should be defined elsewhere...? # TODO Probably the `beacon` ones should be defined elsewhere...?
proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) = proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
template withStateForSlot(stateId: string, body: untyped): untyped = template withStateForStateId(stateId: string, body: untyped): untyped =
var res: BiggestInt node.blockPool.withState(node.blockPool.tmpState,
if parseBiggestInt(stateId, res) == stateId.len: node.stateIdToBlockSlot(stateId)):
raise newException(CatchableError, "Not a valid slot number")
let head = node.updateHead()
let blockSlot = head.atSlot(res.Slot)
node.blockPool.withState(node.blockPool.tmpState, blockSlot):
body body
rpcServer.rpc("get_v1_beacon_genesis") do () -> BeaconGenesisTuple: rpcServer.rpc("get_v1_beacon_genesis") do () -> BeaconGenesisTuple:
debug "get_v1_beacon_genesis"
return (genesis_time: node.blockPool.headState.data.data.genesis_time, return (genesis_time: node.blockPool.headState.data.data.genesis_time,
genesis_validators_root: genesis_validators_root:
node.blockPool.headState.data.data.genesis_validators_root, node.blockPool.headState.data.data.genesis_validators_root,
genesis_fork_version: Version(GENESIS_FORK_VERSION)) genesis_fork_version: Version(GENESIS_FORK_VERSION))
rpcServer.rpc("get_v1_beacon_states_root") do (stateId: string) -> Eth2Digest: rpcServer.rpc("get_v1_beacon_states_root") do (stateId: string) -> Eth2Digest:
debug "get_v1_beacon_states_root", stateId = stateId withStateForStateId(stateId):
# TODO do we need to call node.updateHead() before using headState? return hashedState.root
result = case stateId:
of "head":
node.blockPool.headState.blck.root
of "genesis":
node.blockPool.headState.data.data.genesis_validators_root
of "finalized":
node.blockPool.headState.data.data.finalized_checkpoint.root
of "justified":
node.blockPool.headState.data.data.current_justified_checkpoint.root
else:
if stateId.startsWith("0x"):
# TODO not sure if `fromHex` is the right thing here...
# https://github.com/ethereum/eth2.0-APIs/issues/37#issuecomment-638566144
# we return whatever was passed to us (this is a nonsense request)
fromHex(Eth2Digest, stateId[2..<stateId.len]) # skip first 2 chars
else:
withStateForSlot(stateId):
hashedState.root
rpcServer.rpc("get_v1_beacon_states_fork") do (stateId: string) -> Fork: rpcServer.rpc("get_v1_beacon_states_fork") do (stateId: string) -> Fork:
debug "get_v1_beacon_states_fork", stateId = stateId withStateForStateId(stateId):
result = case stateId: return state.fork
of "head":
node.blockPool.headState.data.data.fork rpcServer.rpc("get_v1_beacon_states_finality_checkpoints") do (
of "genesis": stateId: string) -> BeaconStatesFinalityCheckpointsTuple:
Fork(previous_version: Version(GENESIS_FORK_VERSION), withStateForStateId(stateId):
current_version: Version(GENESIS_FORK_VERSION), return (previous_justified: state.previous_justified_checkpoint,
epoch: GENESIS_EPOCH) current_justified: state.current_justified_checkpoint,
of "finalized": finalized: state.finalized_checkpoint)
node.blockPool.withState(node.blockPool.tmpState, node.blockPool.finalizedHead):
state.fork rpcServer.rpc("get_v1_beacon_states_stateId_validators") do (
of "justified": stateId: string, validatorIds: seq[string],
node.blockPool.justifiedState.data.data.fork status: string) -> seq[BeaconStatesValidatorsTuple]:
let current_epoch = get_current_epoch(node.blockPool.headState.data.data)
withStateForStateId(stateId):
for validatorId in validatorIds:
let res = state.getValidatorInfoFromValidatorId(
current_epoch, validatorId, status)
if res.isSome():
result.add(res.get())
rpcServer.rpc("get_v1_beacon_states_stateId_validators_validatorId") do (
stateId: string, validatorId: string) -> BeaconStatesValidatorsTuple:
let current_epoch = get_current_epoch(node.blockPool.headState.data.data)
withStateForStateId(stateId):
let res = state.getValidatorInfoFromValidatorId(current_epoch, validatorId)
if res.isNone:
# TODO should we raise here? Maybe this is different from the array case...
raise newException(CatchableError, "Validator status differs")
return res.get()
rpcServer.rpc("get_v1_beacon_states_stateId_committees_epoch") do (
stateId: string, epoch: uint64, index: uint64, slot: uint64) ->
seq[BeaconStatesCommitteesTuple]:
withStateForStateId(stateId):
var cache = get_empty_per_epoch_cache() # TODO is this OK?
proc getCommittee(slot: Slot, index: CommitteeIndex): BeaconStatesCommitteesTuple =
let vals = get_beacon_committee(state, slot, index, cache).mapIt(it.uint64)
return (index: index.uint64, slot: slot.uint64, validators: vals)
proc forSlot(slot: Slot, res: var seq[BeaconStatesCommitteesTuple]) =
if index == 0: # TODO this means if the parameter is missing (its optional)
let committees_per_slot = get_committee_count_at_slot(state, slot)
for committee_index in 0'u64..<committees_per_slot:
res.add(getCommittee(slot, committee_index.CommitteeIndex))
else: else:
if stateId.startsWith("0x"): res.add(getCommittee(slot, index.CommitteeIndex))
# TODO not sure if `fromHex` is the right thing here...
# https://github.com/ethereum/eth2.0-APIs/issues/37#issuecomment-638566144 if slot == 0: # TODO this means if the parameter is missing (its optional)
let blckRoot = fromHex(Eth2Digest, stateId[2..<stateId.len]) # skip first 2 chars for i in 0 ..< SLOTS_PER_EPOCH:
let blckRef = node.blockPool.getRef(blckRoot) forSlot((compute_start_slot_at_epoch(epoch.Epoch).int + i).Slot, result)
if blckRef.isNil:
raise newException(CatchableError, "Block not found")
let blckSlot = blckRef.atSlot(blckRef.slot)
node.blockPool.withState(node.blockPool.tmpState, blckSlot):
state.fork
else: else:
withStateForSlot(stateId): forSlot(slot.Slot, result)
state.fork
rpcServer.rpc("get_v1_beacon_headers") do (
slot: uint64, parent_root: Eth2Digest) -> seq[BeaconHeadersTuple]:
# @mratsim: I'm adding a toposorted iterator that returns all blocks from last finalization to all heads in the dual fork choice PR @viktor
# filterIt(dag.blocks.values(), it.blck.slot == slot_of_interest)
# maybe usesBlockPool.heads ??? or getBlockRange ???
# https://discordapp.com/channels/613988663034118151/614014714590134292/726095138484518912
discard # raise newException(CatchableError, "Not implemented") # cannot compile...
rpcServer.rpc("get_v1_beacon_headers_blockId") do (
blockId: string) -> tuple[canonical: bool, header: SignedBeaconBlockHeader]:
let bd = node.getBlockDataFromBlockId(blockId)
let tsbb = bd.data
result.header.signature.blob = tsbb.signature.data
result.header.message.slot = tsbb.message.slot
result.header.message.proposer_index = tsbb.message.proposer_index
result.header.message.parent_root = tsbb.message.parent_root
result.header.message.state_root = tsbb.message.state_root
result.header.message.body_root = tsbb.message.body.hash_tree_root()
result.canonical = bd.refs.isAncestorOf(node.blockPool.head.blck)
rpcServer.rpc("get_v1_beacon_blocks_blockId") do (
blockId: string) -> TrustedSignedBeaconBlock:
return node.getBlockDataFromBlockId(blockId).data
rpcServer.rpc("get_v1_beacon_blocks_blockId_root") do (
blockId: string) -> Eth2Digest:
return node.getBlockDataFromBlockId(blockId).data.message.state_root
rpcServer.rpc("get_v1_beacon_blocks_blockId_attestations") do (
blockId: string) -> seq[TrustedAttestation]:
return node.getBlockDataFromBlockId(blockId).data.message.body.attestations.asSeq
rpcServer.rpc("post_v1_beacon_pool_attestations") do ( rpcServer.rpc("post_v1_beacon_pool_attestations") do (
attestation: Attestation) -> bool: attestation: Attestation) -> bool:
node.sendAttestation(attestation) node.sendAttestation(attestation)
return true return true
rpcServer.rpc("get_v1_validator_blocks") do ( rpcServer.rpc("get_v1_config_fork_schedule") do (
) -> seq[tuple[epoch: uint64, version: Version]]:
discard # raise newException(CatchableError, "Not implemented") # cannot compile...
rpcServer.rpc("get_v1_debug_beacon_states_stateId") do (
stateId: string) -> BeaconState:
withStateForStateId(stateId):
return state
rpcServer.rpc("get_v1_validator_block") do (
slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig) -> BeaconBlock: slot: Slot, graffiti: Eth2Digest, randao_reveal: ValidatorSig) -> BeaconBlock:
debug "get_v1_validator_blocks", slot = slot debug "get_v1_validator_block", slot = slot
let head = node.updateHead() let head = node.doChecksAndGetCurrentHead(slot)
let proposer = node.blockPool.getProposer(head, slot) let proposer = node.blockPool.getProposer(head, slot)
if proposer.isNone(): if proposer.isNone():
raise newException(CatchableError, "could not retrieve block for slot: " & $slot) raise newException(CatchableError, "could not retrieve block for slot: " & $slot)
@ -118,18 +313,13 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
raise newException(CatchableError, "could not retrieve block for slot: " & $slot) raise newException(CatchableError, "could not retrieve block for slot: " & $slot)
return res.message.get() return res.message.get()
rpcServer.rpc("post_v1_beacon_blocks") do (body: SignedBeaconBlock) -> bool: rpcServer.rpc("post_v1_validator_block") do (body: SignedBeaconBlock) -> bool:
debug "post_v1_beacon_blocks", debug "post_v1_validator_block",
slot = body.message.slot, slot = body.message.slot,
prop_idx = body.message.proposer_index prop_idx = body.message.proposer_index
let head = node.doChecksAndGetCurrentHead(body.message.slot)
let head = node.updateHead()
if head.slot >= body.message.slot: if head.slot >= body.message.slot:
warn "Skipping proposal, have newer head already",
headSlot = shortLog(head.slot),
headBlockRoot = shortLog(head.root),
slot = shortLog(body.message.slot),
cat = "fastforward"
raise newException(CatchableError, raise newException(CatchableError,
"Proposal is for a past slot: " & $body.message.slot) "Proposal is for a past slot: " & $body.message.slot)
if head == await proposeSignedBlock(node, head, AttachedValidator(), if head == await proposeSignedBlock(node, head, AttachedValidator(),
@ -137,26 +327,29 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
raise newException(CatchableError, "Could not propose block") raise newException(CatchableError, "Could not propose block")
return true return true
rpcServer.rpc("get_v1_validator_attestation_data") do ( rpcServer.rpc("get_v1_validator_attestation") do (
slot: Slot, committee_index: CommitteeIndex) -> AttestationData: slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
let head = node.updateHead() debug "get_v1_validator_attestation", slot = slot
let attestationHead = head.atSlot(slot) let head = node.doChecksAndGetCurrentHead(slot)
node.blockPool.withState(node.blockPool.tmpState, attestationHead):
node.blockPool.withState(node.blockPool.tmpState, head.atSlot(slot)):
return makeAttestationData(state, slot, committee_index.uint64, blck.root) return makeAttestationData(state, slot, committee_index.uint64, blck.root)
rpcServer.rpc("get_v1_validator_aggregate_attestation") do ( rpcServer.rpc("get_v1_validator_aggregate_and_proof") do (
attestation_data: AttestationData)-> Attestation: attestation_data: AttestationData)-> Attestation:
debug "get_v1_validator_aggregate_attestation" debug "get_v1_validator_aggregate_and_proof"
raise newException(CatchableError, "Not implemented")
rpcServer.rpc("post_v1_validator_aggregate_and_proof") do ( rpcServer.rpc("post_v1_validator_aggregate_and_proof") do (
payload: SignedAggregateAndProof) -> bool: payload: SignedAggregateAndProof) -> bool:
node.network.broadcast(node.topicAggregateAndProofs, payload) debug "post_v1_validator_aggregate_and_proof"
return true raise newException(CatchableError, "Not implemented")
rpcServer.rpc("post_v1_validator_duties_attester") do ( rpcServer.rpc("post_v1_validator_duties_attester") do (
epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[AttesterDuties]: epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[AttesterDuties]:
debug "post_v1_validator_duties_attester", epoch = epoch debug "post_v1_validator_duties_attester", epoch = epoch
let head = node.updateHead() let head = node.doChecksAndGetCurrentHead(epoch)
let attestationHead = head.atSlot(compute_start_slot_at_epoch(epoch)) let attestationHead = head.atSlot(compute_start_slot_at_epoch(epoch))
node.blockPool.withState(node.blockPool.tmpState, attestationHead): node.blockPool.withState(node.blockPool.tmpState, attestationHead):
for pubkey in public_keys: for pubkey in public_keys:
@ -174,7 +367,8 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
rpcServer.rpc("get_v1_validator_duties_proposer") do ( rpcServer.rpc("get_v1_validator_duties_proposer") do (
epoch: Epoch) -> seq[ValidatorPubkeySlotPair]: epoch: Epoch) -> seq[ValidatorPubkeySlotPair]:
debug "get_v1_validator_duties_proposer", epoch = epoch debug "get_v1_validator_duties_proposer", epoch = epoch
let head = node.updateHead() let head = node.doChecksAndGetCurrentHead(epoch)
for i in 0 ..< SLOTS_PER_EPOCH: for i in 0 ..< SLOTS_PER_EPOCH:
let currSlot = (compute_start_slot_at_epoch(epoch).int + i).Slot let currSlot = (compute_start_slot_at_epoch(epoch).int + i).Slot
let proposer = node.blockPool.getProposer(head, currSlot) let proposer = node.blockPool.getProposer(head, currSlot)

View File

@ -10,7 +10,7 @@ import
os, strutils, json, times, os, strutils, json, times,
# Nimble packages # Nimble packages
stew/shims/[tables, macros], stew/byteutils, stew/shims/[tables, macros],
chronos, confutils, metrics, json_rpc/[rpcclient, jsonmarshal], chronos, confutils, metrics, json_rpc/[rpcclient, jsonmarshal],
chronicles, chronicles,
blscurve, json_serialization/std/[options, sets, net], blscurve, json_serialization/std/[options, sets, net],
@ -44,18 +44,6 @@ type
attestationsForEpoch: Table[Epoch, Table[Slot, seq[AttesterDuties]]] attestationsForEpoch: Table[Epoch, Table[Slot, seq[AttesterDuties]]]
beaconGenesis: BeaconGenesisTuple beaconGenesis: BeaconGenesisTuple
proc connectToBN(vc: ValidatorClient) {.gcsafe, async.} =
while true:
try:
await vc.client.connect($vc.config.rpcAddress, Port(vc.config.rpcPort))
info "Connected to BN",
port = vc.config.rpcPort,
address = vc.config.rpcAddress
return
except CatchableError as err:
warn "Could not connect to the BN - retrying!", err = err.msg
await sleepAsync(chronos.seconds(1)) # 1 second before retrying
template attemptUntilSuccess(vc: ValidatorClient, body: untyped) = template attemptUntilSuccess(vc: ValidatorClient, body: untyped) =
while true: while true:
try: try:
@ -63,9 +51,11 @@ template attemptUntilSuccess(vc: ValidatorClient, body: untyped) =
break break
except CatchableError as err: except CatchableError as err:
warn "Caught an unexpected error", err = err.msg warn "Caught an unexpected error", err = err.msg
waitFor vc.connectToBN() waitFor sleepAsync(chronos.seconds(1)) # 1 second before retrying
proc getValidatorDutiesForEpoch(vc: ValidatorClient, epoch: Epoch) {.gcsafe, async.} = proc getValidatorDutiesForEpoch(vc: ValidatorClient, epoch: Epoch) {.gcsafe, async.} =
info "Getting validator duties for epoch", epoch = epoch
let proposals = await vc.client.get_v1_validator_duties_proposer(epoch) let proposals = await vc.client.get_v1_validator_duties_proposer(epoch)
# update the block proposal duties this VC should do during this epoch # update the block proposal duties this VC should do during this epoch
vc.proposalsForCurrentEpoch.clear() vc.proposalsForCurrentEpoch.clear()
@ -79,29 +69,35 @@ proc getValidatorDutiesForEpoch(vc: ValidatorClient, epoch: Epoch) {.gcsafe, asy
validatorPubkeys.add key validatorPubkeys.add key
proc getAttesterDutiesForEpoch(epoch: Epoch) {.gcsafe, async.} = proc getAttesterDutiesForEpoch(epoch: Epoch) {.gcsafe, async.} =
let attestations = await vc.client.post_v1_validator_duties_attester(
epoch, validatorPubkeys)
# make sure there's an entry # make sure there's an entry
if not vc.attestationsForEpoch.contains epoch: if not vc.attestationsForEpoch.contains epoch:
vc.attestationsForEpoch.add(epoch, Table[Slot, seq[AttesterDuties]]()) vc.attestationsForEpoch.add(epoch, Table[Slot, seq[AttesterDuties]]())
let attestations = await vc.client.post_v1_validator_duties_attester(
epoch, validatorPubkeys)
for a in attestations: for a in attestations:
if vc.attestationsForEpoch[epoch].hasKeyOrPut(a.slot, @[a]): if vc.attestationsForEpoch[epoch].hasKeyOrPut(a.slot, @[a]):
vc.attestationsForEpoch[epoch][a.slot].add(a) vc.attestationsForEpoch[epoch][a.slot].add(a)
# obtain the attestation duties this VC should do during the next epoch # clear both for the current epoch and the next because a change of
await getAttesterDutiesForEpoch(epoch + 1) # fork could invalidate the attester duties even the current epoch
# also get the attestation duties for the current epoch if missing vc.attestationsForEpoch.clear()
if not vc.attestationsForEpoch.contains epoch:
await getAttesterDutiesForEpoch(epoch) await getAttesterDutiesForEpoch(epoch)
# cleanup old epoch attestation duties # obtain the attestation duties this VC should do during the next epoch
vc.attestationsForEpoch.del(epoch - 1) # TODO currently we aren't making use of this but perhaps we should
# TODO handle subscriptions to beacon committees for both the next epoch and await getAttesterDutiesForEpoch(epoch + 1)
# for the current if missing (beacon_committee_subscriptions from the REST api)
# for now we will get the fork each time we update the validator duties for each epoch # for now we will get the fork each time we update the validator duties for each epoch
# TODO should poll occasionally `/v1/config/fork_schedule` # TODO should poll occasionally `/v1/config/fork_schedule`
vc.fork = await vc.client.get_v1_beacon_states_fork("head") vc.fork = await vc.client.get_v1_beacon_states_fork("head")
var numAttestationsForEpoch = 0
for _, dutiesForSlot in vc.attestationsForEpoch[epoch]:
numAttestationsForEpoch += dutiesForSlot.len
info "Got validator duties for epoch",
num_proposals = vc.proposalsForCurrentEpoch.len,
num_attestations = numAttestationsForEpoch
proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} = proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, async.} =
let let
@ -135,18 +131,22 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
let public_key = vc.proposalsForCurrentEpoch[slot] let public_key = vc.proposalsForCurrentEpoch[slot]
let validator = vc.attachedValidators.validators[public_key] let validator = vc.attachedValidators.validators[public_key]
info "Proposing block", slot = slot, public_key = public_key
let randao_reveal = validator.genRandaoReveal( let randao_reveal = validator.genRandaoReveal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot) vc.fork, vc.beaconGenesis.genesis_validators_root, slot)
var graffiti: Eth2Digest
graffiti.data[0..<5] = toBytes("quack")
var newBlock = SignedBeaconBlock( var newBlock = SignedBeaconBlock(
message: await vc.client.get_v1_validator_blocks(slot, Eth2Digest(), randao_reveal) message: await vc.client.get_v1_validator_block(slot, graffiti, randao_reveal)
) )
let blockRoot = hash_tree_root(newBlock.message) let blockRoot = hash_tree_root(newBlock.message)
newBlock.signature = await validator.signBlockProposal( newBlock.signature = await validator.signBlockProposal(
vc.fork, vc.beaconGenesis.genesis_validators_root, slot, blockRoot) vc.fork, vc.beaconGenesis.genesis_validators_root, slot, blockRoot)
discard await vc.client.post_v1_beacon_blocks(newBlock) discard await vc.client.post_v1_validator_block(newBlock)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#attesting # https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#attesting
# A validator should create and broadcast the attestation to the associated # A validator should create and broadcast the attestation to the associated
@ -158,11 +158,13 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
seconds(int64(SECONDS_PER_SLOT)) div 3, slot, "Waiting to send attestations") seconds(int64(SECONDS_PER_SLOT)) div 3, slot, "Waiting to send attestations")
# check if we have validators which need to attest on this slot # check if we have validators which need to attest on this slot
if vc.attestationsForEpoch[epoch].contains slot: if vc.attestationsForEpoch.contains(epoch) and
vc.attestationsForEpoch[epoch].contains slot:
for a in vc.attestationsForEpoch[epoch][slot]: for a in vc.attestationsForEpoch[epoch][slot]:
let validator = vc.attachedValidators.validators[a.public_key] info "Attesting", slot = slot, public_key = a.public_key
let ad = await vc.client.get_v1_validator_attestation_data(slot, a.committee_index) let validator = vc.attachedValidators.validators[a.public_key]
let ad = await vc.client.get_v1_validator_attestation(slot, a.committee_index)
# TODO I don't like these (u)int64-to-int conversions... # TODO I don't like these (u)int64-to-int conversions...
let attestation = await validator.produceAndSignAttestation( let attestation = await validator.produceAndSignAttestation(
@ -173,7 +175,6 @@ proc onSlotStart(vc: ValidatorClient, lastSlot, scheduledSlot: Slot) {.gcsafe, a
except CatchableError as err: except CatchableError as err:
warn "Caught an unexpected error", err = err.msg, slot = shortLog(slot) warn "Caught an unexpected error", err = err.msg, slot = shortLog(slot)
await vc.connectToBN()
let let
nextSlotStart = saturate(vc.beaconClock.fromNow(nextSlot)) nextSlotStart = saturate(vc.beaconClock.fromNow(nextSlot))
@ -202,10 +203,6 @@ programMain:
setupMainProc(config.logLevel) setupMainProc(config.logLevel)
# TODO figure out how to re-enable this without the VCs continuing
# to run when `make eth2_network_simulation` is killed with CTRL+C
#ctrlCHandling: discard
case config.cmd case config.cmd
of VCNoCommand: of VCNoCommand:
debug "Launching validator client", debug "Launching validator client",
@ -222,7 +219,10 @@ programMain:
for curr in vc.config.validatorKeys: for curr in vc.config.validatorKeys:
vc.attachedValidators.addLocalValidator(curr.toPubKey, curr) vc.attachedValidators.addLocalValidator(curr.toPubKey, curr)
waitFor vc.connectToBN() waitFor vc.client.connect($vc.config.rpcAddress, Port(vc.config.rpcPort))
info "Connected to BN",
port = vc.config.rpcPort,
address = vc.config.rpcAddress
vc.attemptUntilSuccess: vc.attemptUntilSuccess:
# init the beacon clock # init the beacon clock

View File

@ -73,7 +73,7 @@ func getAttachedValidator*(node: BeaconNode,
let validatorKey = state.validators[idx].pubkey let validatorKey = state.validators[idx].pubkey
node.attachedValidators.getValidator(validatorKey) node.attachedValidators.getValidator(validatorKey)
proc isSynced(node: BeaconNode, head: BlockRef): bool = proc isSynced*(node: BeaconNode, head: BlockRef): bool =
## TODO This function is here as a placeholder for some better heurestics to ## TODO This function is here as a placeholder for some better heurestics to
## determine if we're in sync and should be producing blocks and ## determine if we're in sync and should be producing blocks and
## attestations. Generally, the problem is that slot time keeps advancing ## attestations. Generally, the problem is that slot time keeps advancing

View File

@ -19,6 +19,8 @@ Before you can access the API, make sure it's enabled using the RPC flag (`beaco
--rpc-address Listening address of the RPC server. --rpc-address Listening address of the RPC server.
``` ```
One difference is that currently endpoints that correspond to specific ones from the [spec](https://ethereum.github.io/eth2.0-APIs/) are named weirdly - for example an endpoint such as [`getGenesis`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getGenesis) is currently named `get_v1_beacon_genesis` which would map 1:1 to the actual REST path in the future - verbose but unambiguous.
## Beacon Node API ## Beacon Node API
### getBeaconHead ### getBeaconHead
@ -53,10 +55,104 @@ curl -d '{"jsonrpc":"2.0","id":"id","method":"getChainHead","params":[] }' -H 'C
curl -d '{"jsonrpc":"2.0","id":"id","method":"getNetworkEnr","params":[] }' -H 'Content-Type: application/json' localhost:9190 -s | jq curl -d '{"jsonrpc":"2.0","id":"id","method":"getNetworkEnr","params":[] }' -H 'Content-Type: application/json' localhost:9190 -s | jq
``` ```
### [`get_v1_beacon_genesis`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getGenesis)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_genesis","params":[],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_root`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateRoot)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_states_root","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_fork`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFork)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_states_fork","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_finality_checkpoints`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFinalityCheckpoints)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_states_finality_checkpoints","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_states_stateId_validators`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidators)
### [`get_v1_beacon_states_stateId_validators_validatorId`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidator)
### [`get_v1_beacon_states_stateId_committees_epoch`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getEpochCommittees)
### [`get_v1_beacon_headers`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeaders)
### [`get_v1_beacon_headers_blockId`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeader)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_headers_blockId","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_blocks_blockId`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlock)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_blocks_blockId","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_blocks_blockId_root`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockRoot)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_blocks_blockId_root","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_beacon_blocks_blockId_attestations`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockAttestations)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_beacon_blocks_blockId_attestations","params":["finalized"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`post_v1_beacon_pool_attestations`](https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttestations)
## Valdiator API ## Valdiator API
### [`get_v1_validator_block`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/produceBlock)
### [`post_v1_validator_block`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/publishBlock)
### [`get_v1_validator_attestation`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/produceAttestation)
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_validator_attestation_data","params":[0,3],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_validator_aggregate_and_proof`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/getAggregatedAttestation)
### [`post_v1_validator_aggregate_and_proof`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/publishAggregateAndProof)
### [`post_v1_validator_duties_attester`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/getAttesterDuties)
```
curl -d '{"jsonrpc":"2.0","method":"post_v1_validator_duties_attester","params":[1,["a7a0502eae26043d1ac39a39457a6cdf68fae2055d89c7dc59092c25911e4ee55c4e7a31ade61c39480110a393be28e8","a1826dd94cd96c48a81102d316a2af4960d19ca0b574ae5695f2d39a88685a43997cef9a5c26ad911847674d20c46b75"]],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### [`get_v1_validator_duties_proposer`](https://ethereum.github.io/eth2.0-APIs/#/ValidatorRequiredApi/getProposerDuties)
```
curl -d '{"jsonrpc":"2.0","id":"id","method":"get_v1_validator_duties_proposer","params":[1] }' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
## Config
### [`get_v1_config_fork_schedule`](https://ethereum.github.io/eth2.0-APIs/#/Config/getForkSchedule)
## Administrative / Debug API ## Administrative / Debug API
### `get_v1_debug_beacon_states_stateId` - returns an entire `BeaconState` object for the specified `stateId`
```
curl -d '{"jsonrpc":"2.0","method":"get_v1_debug_beacon_states_stateId","params":["head"],"id":1}' -H 'Content-Type: application/json' localhost:9190 -s | jq
```
### getNodeVersion ### getNodeVersion
Show version of the software Show version of the software

View File

@ -42,8 +42,9 @@ proc makePrometheusConfig(nodeID, baseMetricsPort: int, dataDir: string) =
# macOS may not have gnu-getopts installed and in the PATH # macOS may not have gnu-getopts installed and in the PATH
execIgnoringExitCode &"""./scripts/make_prometheus_config.sh --nodes """ & $(1 + nodeID) & &""" --base-metrics-port {baseMetricsPort} --config-file "{dataDir}/prometheus.yml" """ execIgnoringExitCode &"""./scripts/make_prometheus_config.sh --nodes """ & $(1 + nodeID) & &""" --base-metrics-port {baseMetricsPort} --config-file "{dataDir}/prometheus.yml" """
proc buildNode(nimFlags, preset, beaconNodeBinary: string) = proc buildBinary(nimFlags, preset, binary, nimFile: string) =
exec &"""nim c {nimFlags} -d:"const_preset={preset}" -o:"{beaconNodeBinary}" beacon_chain/beacon_node.nim""" if binary != "":
exec &"""nim c {nimFlags} -d:"const_preset={preset}" -o:"{binary}" beacon_chain/{nimFile}"""
proc becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContractOpt, privateGoerliKey: string, proc becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContractOpt, privateGoerliKey: string,
becomeValidatorOnly: bool) = becomeValidatorOnly: bool) =
@ -75,8 +76,8 @@ proc becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContrac
echo "\nDeposit sent, wait for confirmation then press enter to continue" echo "\nDeposit sent, wait for confirmation then press enter to continue"
discard readLineFromStdin() discard readLineFromStdin()
proc runNode(dataDir, beaconNodeBinary, bootstrapFileOpt, depositContractOpt, proc runNode(dataDir, beaconNodeBinary, validatorClientBinary, bootstrapFileOpt,
genesisFileOpt, extraBeaconNodeOptions: string, depositContractOpt, genesisFileOpt, extraBeaconNodeOptions: string,
basePort, nodeID, baseMetricsPort, baseRpcPort: int, basePort, nodeID, baseMetricsPort, baseRpcPort: int,
printCmdOnly: bool) = printCmdOnly: bool) =
let logLevel = getEnv("LOG_LEVEL") let logLevel = getEnv("LOG_LEVEL")
@ -99,21 +100,36 @@ proc runNode(dataDir, beaconNodeBinary, bootstrapFileOpt, depositContractOpt,
echo &"cd {dataDir}; exec {cmd}" echo &"cd {dataDir}; exec {cmd}"
else: else:
cd dataDir cd dataDir
mkDir dataDir & "/empty_dummy_folder"
# if launching a VC as well - send the BN looking nowhere for validators/secrets
# TODO use `start` (or something else) on windows (instead of `&`) for the 2 processes
let vcCommand = if validatorClientBinary == "": "" else:
&"""
--secrets-dir={dataDir}/empty_dummy_folder
--validators-dir={dataDir}/empty_dummy_folder
& {validatorClientBinary}
--rpc-port={baseRpcPort + nodeID}
--data-dir="{dataDir}"
{logLevelOpt}
"""
cmd = replace(&"""{beaconNodeBinary} cmd = replace(&"""{beaconNodeBinary}
--data-dir="{dataDir}" --data-dir="{dataDir}"
--dump --dump
--web3-url={web3Url} --web3-url={web3Url}
--tcp-port=""" & $(basePort + nodeID) & &""" --tcp-port={basePort + nodeID}
--udp-port=""" & $(basePort + nodeID) & &""" --udp-port={basePort + nodeID}
--metrics --metrics
--metrics-port=""" & $(baseMetricsPort + nodeID) & &""" --metrics-port={baseMetricsPort + nodeID}
--rpc --rpc
--rpc-port=""" & $(baseRpcPort + nodeID) & &""" --rpc-port={baseRpcPort + nodeID}
{bootstrapFileOpt} {bootstrapFileOpt}
{logLevelOpt} {logLevelOpt}
{depositContractOpt} {depositContractOpt}
{genesisFileOpt} {genesisFileOpt}
{extraBeaconNodeOptions}""", "\n", " ") {extraBeaconNodeOptions}
{vcCommand} """, "\n", " ")
execIgnoringExitCode cmd execIgnoringExitCode cmd
cli do (skipGoerliKey {. cli do (skipGoerliKey {.
@ -157,6 +173,9 @@ cli do (skipGoerliKey {.
runOnly {. runOnly {.
desc: "Just run it." .} = false, desc: "Just run it." .} = false,
separateVC {.
desc: "use a separate validator client process." .} = false,
printCmdOnly {. printCmdOnly {.
desc: "Just print the commands (suitable for passing to 'eval'; might replace current shell)." .} = false, desc: "Just print the commands (suitable for passing to 'eval'; might replace current shell)." .} = false,
@ -220,6 +239,11 @@ cli do (skipGoerliKey {.
doAssert specVersion in ["v0.11.3", "v0.12.1"] doAssert specVersion in ["v0.11.3", "v0.12.1"]
if defined(windows) and separateVC:
# TODO use `start` (or something else) on windows (instead of `&`) for the 2 processes
echo "Cannot use a separate validator client process on Windows! (remove --separateVC)"
quit(1)
let let
dataDirName = testnetName.replace("/", "_") dataDirName = testnetName.replace("/", "_")
.replace("(", "_") .replace("(", "_")
@ -228,12 +252,17 @@ cli do (skipGoerliKey {.
validatorsDir = dataDir / "validators" validatorsDir = dataDir / "validators"
secretsDir = dataDir / "secrets" secretsDir = dataDir / "secrets"
beaconNodeBinary = buildDir / "beacon_node_" & dataDirName beaconNodeBinary = buildDir / "beacon_node_" & dataDirName
# using a separate VC is disabled on windows until we find a substitute for `&`
validatorClientBinary = if separateVC: buildDir / "validator_client_" & dataDirName else: ""
var var
nimFlags = &"-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS") nimFlagsBN = &"-d:chronicles_log_level=TRACE " & getEnv("NIM_PARAMS")
nimFlagsVC = nimFlagsBN
if writeLogFile: if writeLogFile:
# write the logs to a file # write the logs to a file
nimFlags.add """ -d:"chronicles_sinks=textlines,json[file(nbc""" & staticExec("date +\"%Y%m%d%H%M%S\"") & """.log)]" """ let logFileNimParams = """ -d:"chronicles_sinks=textlines,json[file(placeholder_""" & staticExec("date +\"%Y%m%d%H%M%S\"") & """.log)]" """
nimFlagsBN.add logFileNimParams.replace("placeholder_", "nbc_bn_")
nimFlagsVC.add logFileNimParams.replace("placeholder_", "nbc_vc_")
let depositContractFile = testnetDir / depositContractFileName let depositContractFile = testnetDir / depositContractFileName
if system.fileExists(depositContractFile): if system.fileExists(depositContractFile):
@ -248,7 +277,8 @@ cli do (skipGoerliKey {.
if doBuild: if doBuild:
makePrometheusConfig(nodeID, baseMetricsPort, dataDir) makePrometheusConfig(nodeID, baseMetricsPort, dataDir)
buildNode(nimFlags, preset, beaconNodeBinary) buildBinary(nimFlagsBN, preset, beaconNodeBinary, "beacon_node.nim")
buildBinary(nimFlagsVC, preset, validatorClientBinary, "validator_client.nim")
if doBecomeValidator and depositContractOpt.len > 0 and not system.dirExists(validatorsDir): if doBecomeValidator and depositContractOpt.len > 0 and not system.dirExists(validatorsDir):
becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContractOpt, privateGoerliKey, becomeValidatorOnly) becomeValidator(validatorsDir, beaconNodeBinary, secretsDir, depositContractOpt, privateGoerliKey, becomeValidatorOnly)
@ -256,7 +286,7 @@ cli do (skipGoerliKey {.
echo &"extraBeaconNodeOptions = '{extraBeaconNodeOptions}'" echo &"extraBeaconNodeOptions = '{extraBeaconNodeOptions}'"
if doRun: if doRun:
runNode(dataDir, beaconNodeBinary, bootstrapFileOpt, depositContractOpt, runNode(dataDir, beaconNodeBinary, validatorClientBinary, bootstrapFileOpt,
genesisFileOpt, extraBeaconNodeOptions, depositContractOpt, genesisFileOpt, extraBeaconNodeOptions,
basePort, nodeID, baseMetricsPort, baseRpcPort, basePort, nodeID, baseMetricsPort, baseRpcPort,
printCmdOnly) printCmdOnly)