- 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:
parent
fc8502c54e
commit
1482b0430d
|
@ -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)
|
||||||
|
|
|
@ -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" &
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue