nimbus-eth2/beacon_chain/rpc/rest_validator_api.nim

771 lines
32 KiB
Nim
Raw Normal View History

# Copyright (c) 2018-2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import std/[typetraits, strutils, sets]
import stew/[results, base10], chronicles
import ".."/[beacon_chain_db, beacon_node],
".."/networking/eth2_network,
".."/consensus_object_pools/[blockchain_dag, spec_cache,
attestation_pool, sync_committee_msg_pool],
".."/validators/validator_duties,
".."/spec/[beaconstate, forks, network],
".."/spec/datatypes/[phase0, altair],
"."/[rest_utils, state_ttl_cache]
export rest_utils
logScope: topics = "rest_validatorapi"
proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties
router.api(MethodPost, "/eth/v1/validator/duties/attester/{epoch}") do (
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
let indexList =
block:
if contentBody.isNone():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[RestValidatorIndex], contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidValidatorIndexValueError,
$dres.error())
var res: HashSet[ValidatorIndex]
let items = dres.get()
for item in items:
let vres = item.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
2021-04-08 10:49:28 +00:00
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
2021-04-08 10:49:28 +00:00
UnsupportedValidatorIndexValueError)
res.incl(vres.get())
if len(res) == 0:
return RestApiResponse.jsonError(Http400,
EmptyValidatorIndexArrayError)
res
let qepoch =
block:
if epoch.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
$epoch.error())
let
res = epoch.get()
wallTime = node.beaconClock.now() + MAXIMUM_GOSSIP_CLOCK_DISPARITY
wallEpoch = wallTime.slotOrZero().epoch
if res > wallEpoch + 1:
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
"Cannot request duties past next epoch")
res
let qhead =
block:
let res = node.getCurrentHead(qepoch)
if res.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
res.get()
let droot = qhead.prevDependentBlock(node.dag.tail, qepoch).root
let duties =
block:
var res: seq[RestAttesterDuty]
let epochRef = block:
let tmp = node.dag.getEpochRef(qhead, qepoch, true)
if isErr(tmp):
return RestApiResponse.jsonError(Http400, PrunedStateError)
tmp.get()
let
committees_per_slot = get_committee_count_per_slot(epochRef)
for committee_index in get_committee_indices(committees_per_slot):
for slot in qepoch.slots():
let committee = get_beacon_committee(epochRef, slot, committee_index)
for index_in_committee, validator_index in committee:
if validator_index in indexList:
let validator_key = epochRef.validatorKey(validator_index)
if validator_key.isSome():
res.add(
RestAttesterDuty(
pubkey: validator_key.get().toPubKey(),
validator_index: validator_index,
committee_index: committee_index,
committee_length: lenu64(committee),
committees_at_slot: committees_per_slot,
validator_committee_index: uint64(index_in_committee),
slot: slot
)
)
res
return RestApiResponse.jsonResponseWRoot(duties, droot)
# https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties
router.api(MethodGet, "/eth/v1/validator/duties/proposer/{epoch}") do (
epoch: Epoch) -> RestApiResponse:
let qepoch =
block:
if epoch.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
$epoch.error())
let
res = epoch.get()
wallTime = node.beaconClock.now() + MAXIMUM_GOSSIP_CLOCK_DISPARITY
wallEpoch = wallTime.slotOrZero().epoch
if res > wallEpoch + 1:
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
"Cannot request duties past next epoch")
res
let qhead =
block:
let res = node.getCurrentHead(qepoch)
if res.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
res.get()
let droot = qhead.dependentBlock(node.dag.tail, qepoch).root
let duties =
block:
var res: seq[RestProposerDuty]
let epochRef = block:
let tmp = node.dag.getEpochRef(qhead, qepoch, true)
if isErr(tmp):
return RestApiResponse.jsonError(Http400, PrunedStateError)
tmp.get()
for i, bp in epochRef.beacon_proposers:
if i == 0 and qepoch == 0:
# Fix for https://github.com/status-im/nimbus-eth2/issues/2488
# Slot(0) at Epoch(0) do not have a proposer.
continue
if bp.isSome():
res.add(
RestProposerDuty(
pubkey: epochRef.validatorKey(bp.get()).get().toPubKey(),
validator_index: bp.get(),
slot: qepoch.start_slot() + i
)
)
res
return RestApiResponse.jsonResponseWRoot(duties, droot)
router.api(MethodPost, "/eth/v1/validator/duties/sync/{epoch}") do (
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
let indexList =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[RestValidatorIndex], contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidValidatorIndexValueError,
$dres.error())
var res: seq[ValidatorIndex]
let items = dres.get()
for item in items:
let vres = item.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
res.add(vres.get())
if len(res) == 0:
return RestApiResponse.jsonError(Http400,
EmptyValidatorIndexArrayError)
res
let qepoch =
block:
if epoch.isErr():
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
$epoch.error())
let res = epoch.get()
if res > MaxEpoch:
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
res
# We use a local proc in order to:
# * avoid code duplication
# * reduce code bloat from `withState`
proc produceResponse(requestedValidatorIndices: openArray[ValidatorIndex],
syncCommittee: openArray[ValidatorPubKey],
stateValidators: seq[Validator]
): seq[RestSyncCommitteeDuty] {.nimcall.} =
result = newSeqOfCap[RestSyncCommitteeDuty](len(requestedValidatorIndices))
for requestedValidatorIdx in requestedValidatorIndices:
if requestedValidatorIdx.uint64 >= stateValidators.lenu64:
# If the requested validator index was not valid within this old
# state, it's not possible that it will sit on the sync committee.
# Since this API must omit results for validators that don't have
# duties, we can simply ingnore this requested index.
# (we won't bother to validate it agains a more recent state).
continue
let requestedValidatorPubkey =
stateValidators[requestedValidatorIdx].pubkey
var indicesInSyncCommittee = newSeq[IndexInSyncCommittee]()
for idx, syncCommitteeMemberPubkey in syncCommittee:
if syncCommitteeMemberPubkey == requestedValidatorPubkey:
indicesInSyncCommittee.add(IndexInSyncCommittee idx)
if indicesInSyncCommittee.len > 0:
result.add RestSyncCommitteeDuty(
pubkey: requestedValidatorPubkey,
validator_index: requestedValidatorIdx,
validator_sync_committee_indices: indicesInSyncCommittee)
template emptyResponse: auto =
newSeq[RestSyncCommitteeDuty]()
# We check the head state first in order to avoid costly replays
# if possible:
let
qSyncPeriod = sync_committee_period(qepoch)
headEpoch = node.dag.head.slot.epoch
headSyncPeriod = sync_committee_period(headEpoch)
if qSyncPeriod == headSyncPeriod:
let res = withState(node.dag.headState.data):
when stateFork >= BeaconStateFork.Altair:
produceResponse(indexList,
state.data.current_sync_committee.pubkeys.data,
state.data.validators.asSeq)
else:
emptyResponse()
return RestApiResponse.jsonResponse(res)
elif qSyncPeriod == (headSyncPeriod + 1):
let res = withState(node.dag.headState.data):
when stateFork >= BeaconStateFork.Altair:
produceResponse(indexList,
state.data.next_sync_committee.pubkeys.data,
state.data.validators.asSeq)
else:
emptyResponse()
return RestApiResponse.jsonResponse(res)
elif qSyncPeriod > headSyncPeriod:
# The requested epoch may still be too far in the future.
if not(node.isSynced(node.dag.head)):
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
else:
return RestApiResponse.jsonError(Http400, EpochFromFutureError)
else:
# The slot at the start of the sync committee period is likely to have a
# state snapshot in the database, so we can restore the state relatively
# cheaply:
let earliestSlotInQSyncPeriod =
Slot(qSyncPeriod * SLOTS_PER_SYNC_COMMITTEE_PERIOD)
# TODO
# The DAG can offer a short-cut for getting just the information we need
# in order to compute the sync committee for the epoch. See the following
# discussion for more details:
# https://github.com/status-im/nimbus-eth2/pull/3133#pullrequestreview-817184693
limit by-root requests to non-finalized blocks (#3293) * limit by-root requests to non-finalized blocks Presently, we keep a mapping from block root to `BlockRef` in memory - this has simplified reasoning about the dag, but is not sustainable with the chain growing. We can distinguish between two cases where by-root access is useful: * unfinalized blocks - this is where the beacon chain is operating generally, by validating incoming data as interesting for future fork choice decisions - bounded by the length of the unfinalized period * finalized blocks - historical access in the REST API etc - no bounds, really In this PR, we limit the by-root block index to the first use case: finalized chain data can more efficiently be addressed by slot number. Future work includes: * limiting the `BlockRef` horizon in general - each instance is 40 bytes+overhead which adds up - this needs further refactoring to deal with the tail vs state problem * persisting the finalized slot-to-hash index - this one also keeps growing unbounded (albeit slowly) Anyway, this PR easily shaves ~128mb of memory usage at the time of writing. * No longer honor `BeaconBlocksByRoot` requests outside of the non-finalized period - previously, Nimbus would generously return any block through this libp2p request - per the spec, finalized blocks should be fetched via `BeaconBlocksByRange` instead. * return `Opt[BlockRef]` instead of `nil` when blocks can't be found - this becomes a lot more common now and thus deserves more attention * `dag.blocks` -> `dag.forkBlocks` - this index only carries unfinalized blocks from now - `finalizedBlocks` covers the other `BlockRef` instances * in backfill, verify that the last backfilled block leads back to genesis, or panic * add backfill timings to log * fix missing check that `BlockRef` block can be fetched with `getForkedBlock` reliably * shortcut doppelganger check when feature is not enabled * in REST/JSON-RPC, fetch blocks without involving `BlockRef` * fix dag.blocks ref
2022-01-21 11:33:16 +00:00
let bs = node.dag.getBlockAtSlot(earliestSlotInQSyncPeriod)
if bs.blck.isNil:
return RestApiResponse.jsonError(Http404, StateNotFoundError)
node.withStateForBlockSlot(bs):
let res = withState(stateData().data):
when stateFork >= BeaconStateFork.Altair:
produceResponse(indexList,
state.data.current_sync_committee.pubkeys.data,
state.data.validators.asSeq)
else:
emptyResponse()
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlock
router.api(MethodGet, "/eth/v1/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig],
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
let message =
block:
let qslot =
block:
if slot.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$slot.error())
slot.get()
let qrandao =
if randao_reveal.isNone():
return RestApiResponse.jsonError(Http400, MissingRandaoRevealValue)
else:
let res = randao_reveal.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidRandaoRevealValue,
$res.error())
res.get()
let qgraffiti =
if graffiti.isNone():
defaultGraffitiBytes()
else:
let res = graffiti.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidGraffitiBytesValue,
$res.error())
res.get()
let qhead =
block:
let res = node.getCurrentHead(qslot)
if res.isErr():
if not(node.isSynced(node.dag.head)):
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
else:
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, NoHeadForSlotError,
$res.error())
res.get()
let proposer = node.dag.getProposer(qhead, qslot)
if proposer.isNone():
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
let res = await makeBeaconBlockForHeadAndSlot(
node, qrandao, proposer.get(), qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
res.get()
return
case message.kind
of BeaconBlockFork.Phase0:
RestApiResponse.jsonResponse(message.phase0Data)
else:
RestApiResponse.jsonError(Http400,
"Unable to produce block for altair fork")
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2
router.api(MethodGet, "/eth/v2/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig],
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
let message =
block:
let qslot =
block:
if slot.isErr():
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$slot.error())
slot.get()
let qrandao =
if randao_reveal.isNone():
return RestApiResponse.jsonError(Http400, MissingRandaoRevealValue)
else:
let res = randao_reveal.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidRandaoRevealValue,
$res.error())
res.get()
let qgraffiti =
if graffiti.isNone():
defaultGraffitiBytes()
else:
let res = graffiti.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidGraffitiBytesValue,
$res.error())
res.get()
let qhead =
block:
let res = node.getCurrentHead(qslot)
if res.isErr():
if not(node.isSynced(node.dag.head)):
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
else:
return RestApiResponse.jsonError(Http400, NoHeadForSlotError,
$res.error())
res.get()
let proposer = node.dag.getProposer(qhead, qslot)
if proposer.isNone():
return RestApiResponse.jsonError(Http400, ProposerNotFoundError)
let res = await makeBeaconBlockForHeadAndSlot(
node, qrandao, proposer.get(), qgraffiti, qhead, qslot)
if res.isErr():
return RestApiResponse.jsonError(Http400, res.error())
res.get()
return RestApiResponse.jsonResponsePlain(message)
# https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData
router.api(MethodGet, "/eth/v1/validator/attestation_data") do (
slot: Option[Slot],
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
let adata =
block:
let qslot =
block:
if slot.isNone():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, MissingSlotValueError)
let res = slot.get()
if res.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$res.error())
res.get()
let qindex =
block:
if committee_index.isNone():
return RestApiResponse.jsonError(Http400,
2021-04-08 10:49:28 +00:00
MissingCommitteeIndexValueError)
let res = committee_index.get()
if res.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400,
InvalidCommitteeIndexValueError,
$res.error())
res.get()
let qhead =
block:
let res = node.getCurrentHead(qslot)
if res.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
res.get()
let epochRef = block:
let tmp = node.dag.getEpochRef(qhead, qslot.epoch, true)
if isErr(tmp):
return RestApiResponse.jsonError(Http400, PrunedStateError)
tmp.get()
makeAttestationData(epochRef, qhead.atSlot(qslot), qindex)
return RestApiResponse.jsonResponse(adata)
# https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation
router.api(MethodGet, "/eth/v1/validator/aggregate_attestation") do (
attestation_data_root: Option[Eth2Digest],
slot: Option[Slot]) -> RestApiResponse:
let attestation =
block:
let qslot =
block:
if slot.isNone():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, MissingSlotValueError)
let res = slot.get()
if res.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$res.error())
res.get()
let qroot =
block:
if attestation_data_root.isNone():
return RestApiResponse.jsonError(Http400,
MissingAttestationDataRootValueError)
let res = attestation_data_root.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidAttestationDataRootValueError, $res.error())
res.get()
let res = node.attestationPool[].getAggregatedAttestation(qslot, qroot)
if res.isNone():
return RestApiResponse.jsonError(Http400,
UnableToGetAggregatedAttestationError)
res.get()
return RestApiResponse.jsonResponse(attestation)
# https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs
router.api(MethodPost, "/eth/v1/validator/aggregate_and_proofs") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let proofs =
block:
if contentBody.isNone():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[SignedAggregateAndProof], contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidAggregateAndProofObjectError,
$dres.error())
dres.get()
# Since our validation logic supports batch processing, we will submit all
# aggregated attestations for validation.
var pending =
block:
var res: seq[Future[SendResult]]
for proof in proofs:
res.add(node.sendAggregateAndProof(proof))
res
await allFutures(pending)
for future in pending:
if future.done():
let res = future.read()
if res.isErr():
return RestApiResponse.jsonError(Http400,
AggregateAndProofValidationError,
$res.error())
else:
return RestApiResponse.jsonError(Http500,
"Unexpected server failure, while sending aggregate and proof")
New validator client using REST API. (#2651) * Initial commit. * Exporting getConfig(). * Add beacon node checking procedures. * Post rebase fixes. * Use runSlotLoop() from nimbus_beacon_node. Fallback implementation. Fixes for ETH2 REST serialization. * Add beacon_clock.durationToNextSlot(). Move type declarations from beacon_rest_api to json_rest_serialization. Fix seq[ValidatorIndex] serialization. Refactor ValidatorPool and add some utility procedures. Create separate version of validator_client. * Post-rebase fixes. Remove CookedPubKey from validator_pool.nim. * Now we should be able to produce attestations and aggregate and proofs. But its not working yet. * Debugging attestation sending. * Add durationToNextAttestation. Optimize some debug logs. Fix aggregation_bits encoding. Bump chronos/presto. * Its alive. * Fixes for launch_local_testnet script. Bump chronos. * Switch client API to not use `/api` prefix. * Post-rebase adjustments. * Fix endpoint for publishBlock(). * Add CONFIG_NAME. Add more checks to ensure that beacon_node is compatible. * Add beacon committee subscription support to validator_client. * Fix stacktrace should be an array of strings. Fix committee subscriptions should not be `data` keyed. * Log duration to next block proposal. * Fix beacon_node_status import. * Use jsonMsgResponse() instead of jsonError(). * Fix graffityBytes usage. Remove unnecessary `await`. Adjust creation of SignedBlock instance. Remove legacy files. * Rework durationToNextSlot() and durationToNextEpoch() to use `fromNow`. * Fix race condition for block proposal and attestations for same slot. Fix local_testnet script to properly kill tasks on Windows. Bump chronos and nim-http-tools, to allow connections to infura.io (basic auth). * Catch services errors. Improve performance of local_testnet.sh script on Windows. Fix race condition when attestation producing. * Post-rebase fixes. * Bump chronos and presto. * Calculate block publishing delay. Fix pkill in one more place. * Add error handling and timeouts to firstSuccess() template. Add onceToAll() template. Add checkNodes() procedure. Refactor firstSuccess() template. Add error checking to api.nim calls. * Deprecated usage onceToAll() for better stability. Address comment and send attestations asap. * Avoid unnecessary loop when calculating minimal duration.
2021-07-13 11:15:07 +00:00
return RestApiResponse.jsonMsgResponse(AggregateAndProofValidationSuccess)
# https://ethereum.github.io/beacon-APIs/#/Validator/prepareBeaconCommitteeSubnet
router.api(MethodPost,
"/eth/v1/validator/beacon_committee_subscriptions") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let requests =
block:
if contentBody.isNone():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[RestCommitteeSubscription],
contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidSubscriptionRequestValueError,
$dres.error())
dres.get()
if not(node.isSynced(node.dag.head)):
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
let
wallSlot = node.beaconClock.now.slotOrZero
wallEpoch = wallSlot.epoch
head = node.dag.head
var currentEpoch, nextEpoch: Option[EpochRef]
template getAndCacheEpochRef(epochRefVar: var Option[EpochRef],
epoch: Epoch): EpochRef =
if epochRefVar.isNone:
epochRefVar = block:
let epochRef = node.dag.getEpochRef(head, epoch, true)
if isErr(epochRef):
return RestApiResponse.jsonError(Http400, PrunedStateError)
some epochRef.get()
epochRefVar.get
for request in requests:
2021-04-18 08:24:59 +00:00
if uint64(request.committee_index) >= uint64(MAX_COMMITTEES_PER_SLOT):
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400,
InvalidCommitteeIndexValueError)
if uint64(request.validator_index) >=
lenu64(getStateField(node.dag.headState.data, validators)):
return RestApiResponse.jsonError(Http400,
InvalidValidatorIndexValueError)
if wallSlot > request.slot + 1:
return RestApiResponse.jsonError(Http400, SlotFromThePastError)
let epoch = request.slot.epoch
let epochRef = if epoch == wallEpoch:
currentEpoch.getAndCacheEpochRef(wallEpoch)
elif epoch == wallEpoch + 1:
nextEpoch.getAndCacheEpochRef(wallEpoch + 1)
else:
return RestApiResponse.jsonError(Http400,
SlotNotInNextWallSlotEpochError)
let subnet_id = compute_subnet_for_attestation(
get_committee_count_per_slot(epochRef), request.slot,
request.committee_index)
node.registerDuty(
request.slot, subnet_id, request.validator_index,
request.is_aggregator)
let validator_pubkey = getStateField(
node.dag.headState.data, validators).asSeq()[request.validator_index].pubkey
node.validatorMonitor[].addAutoMonitor(
validator_pubkey, ValidatorIndex(request.validator_index))
New validator client using REST API. (#2651) * Initial commit. * Exporting getConfig(). * Add beacon node checking procedures. * Post rebase fixes. * Use runSlotLoop() from nimbus_beacon_node. Fallback implementation. Fixes for ETH2 REST serialization. * Add beacon_clock.durationToNextSlot(). Move type declarations from beacon_rest_api to json_rest_serialization. Fix seq[ValidatorIndex] serialization. Refactor ValidatorPool and add some utility procedures. Create separate version of validator_client. * Post-rebase fixes. Remove CookedPubKey from validator_pool.nim. * Now we should be able to produce attestations and aggregate and proofs. But its not working yet. * Debugging attestation sending. * Add durationToNextAttestation. Optimize some debug logs. Fix aggregation_bits encoding. Bump chronos/presto. * Its alive. * Fixes for launch_local_testnet script. Bump chronos. * Switch client API to not use `/api` prefix. * Post-rebase adjustments. * Fix endpoint for publishBlock(). * Add CONFIG_NAME. Add more checks to ensure that beacon_node is compatible. * Add beacon committee subscription support to validator_client. * Fix stacktrace should be an array of strings. Fix committee subscriptions should not be `data` keyed. * Log duration to next block proposal. * Fix beacon_node_status import. * Use jsonMsgResponse() instead of jsonError(). * Fix graffityBytes usage. Remove unnecessary `await`. Adjust creation of SignedBlock instance. Remove legacy files. * Rework durationToNextSlot() and durationToNextEpoch() to use `fromNow`. * Fix race condition for block proposal and attestations for same slot. Fix local_testnet script to properly kill tasks on Windows. Bump chronos and nim-http-tools, to allow connections to infura.io (basic auth). * Catch services errors. Improve performance of local_testnet.sh script on Windows. Fix race condition when attestation producing. * Post-rebase fixes. * Bump chronos and presto. * Calculate block publishing delay. Fix pkill in one more place. * Add error handling and timeouts to firstSuccess() template. Add onceToAll() template. Add checkNodes() procedure. Refactor firstSuccess() template. Add error checking to api.nim calls. * Deprecated usage onceToAll() for better stability. Address comment and send attestations asap. * Avoid unnecessary loop when calculating minimal duration.
2021-07-13 11:15:07 +00:00
return RestApiResponse.jsonMsgResponse(BeaconCommitteeSubscriptionSuccess)
2021-04-13 10:19:31 +00:00
# https://ethereum.github.io/beacon-APIs/#/Validator/prepareSyncCommitteeSubnets
router.api(MethodPost,
"/eth/v1/validator/sync_committee_subscriptions") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let subscriptions =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[RestSyncCommitteeSubscription],
contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidSyncCommitteeSubscriptionRequestError)
let subs = dres.get()
for item in subs:
if item.until_epoch > MaxEpoch:
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
if item.until_epoch < node.dag.cfg.ALTAIR_FORK_EPOCH:
return RestApiResponse.jsonError(Http400,
EpochFromTheIncorrectForkError)
if uint64(item.validator_index) >=
lenu64(getStateField(node.dag.headState.data, validators)):
return RestApiResponse.jsonError(Http400,
InvalidValidatorIndexValueError)
let validator_pubkey = getStateField(
node.dag.headState.data, validators).asSeq()[item.validator_index].pubkey
node.validatorMonitor[].addAutoMonitor(
validator_pubkey, ValidatorIndex(item.validator_index))
subs
# TODO because we subscribe to all sync committee subnets, we don not need
# to remember which ones are requested by validator clients
return RestApiResponse.jsonMsgResponse(SyncCommitteeSubscriptionSuccess)
# https://ethereum.github.io/beacon-APIs/#/Validator/produceSyncCommitteeContribution
router.api(MethodGet,
"/eth/v1/validator/sync_committee_contribution") do (
slot: Option[Slot], subcommittee_index: Option[SyncSubCommitteeIndex],
beacon_block_root: Option[Eth2Digest]) -> RestApiResponse:
let qslot =
if slot.isNone():
return RestApiResponse.jsonError(Http400, MissingSlotValueError)
else:
let res = slot.get()
if res.isErr():
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
$res.error())
let rslot = res.get()
if epoch(rslot) < node.dag.cfg.ALTAIR_FORK_EPOCH:
return RestApiResponse.jsonError(Http400,
SlotFromTheIncorrectForkError)
rslot
let qindex =
if subcommittee_index.isNone():
return RestApiResponse.jsonError(Http400,
MissingSubCommitteeIndexValueError)
else:
let res = subcommittee_index.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidSubCommitteeIndexValueError,
$res.error())
res.get()
let qroot =
if beacon_block_root.isNone():
return RestApiResponse.jsonError(Http400,
MissingBeaconBlockRootValueError)
else:
let res = beacon_block_root.get()
if res.isErr():
return RestApiResponse.jsonError(Http400,
InvalidBeaconBlockRootValueError,
$res.error())
res.get()
# Check if node is fully synced.
let sres = node.getCurrentHead(qslot)
if sres.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
var contribution = SyncCommitteeContribution()
let res = node.syncCommitteeMsgPool[].produceContribution(
qslot, qroot, qindex, contribution)
if not(res):
return RestApiResponse.jsonError(Http400, ProduceContributionError)
return RestApiResponse.jsonResponse(contribution)
# https://ethereum.github.io/beacon-APIs/#/Validator/publishContributionAndProofs
router.api(MethodPost,
"/eth/v1/validator/contribution_and_proofs") do (
contentBody: Option[ContentBody]) -> RestApiResponse:
let proofs =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
let dres = decodeBody(seq[SignedContributionAndProof],
contentBody.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidContributionAndProofMessageError)
dres.get()
let pending =
block:
var res: seq[Future[SendResult]]
for proof in proofs:
res.add(node.sendSyncCommitteeContribution(proof, true))
res
let failures =
block:
var res: seq[RestAttestationsFailure]
await allFutures(pending)
for index, future in pending.pairs():
if future.done():
let fres = future.read()
if fres.isErr():
let failure = RestAttestationsFailure(index: uint64(index),
message: $fres.error())
res.add(failure)
elif future.failed() or future.cancelled():
# This is unexpected failure, so we log the error message.
let exc = future.readError()
let failure = RestAttestationsFailure(index: uint64(index),
message: $exc.msg)
res.add(failure)
res
if len(failures) > 0:
return RestApiResponse.jsonErrorList(Http400,
ContributionAndProofValidationError,
failures)
else:
return RestApiResponse.jsonMsgResponse(
ContributionAndProofValidationSuccess
)
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
# `/api` path component
2021-04-13 10:19:31 +00:00
router.redirect(
MethodPost,
"/api/eth/v1/validator/duties/attester/{epoch}",
"/eth/v1/validator/duties/attester/{epoch}"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodGet,
"/api/eth/v1/validator/duties/proposer/{epoch}",
"/eth/v1/validator/duties/proposer/{epoch}"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodPost,
"/api/eth/v1/validator/duties/sync/{epoch}",
"/eth/v1/validator/duties/sync/{epoch}"
)
2021-04-13 10:19:31 +00:00
router.redirect(
MethodGet,
"/api/eth/v1/validator/blocks/{slot}",
"/eth/v1/validator/blocks/{slot}"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodGet,
"/api/eth/v2/validator/blocks/{slot}",
"/eth/v2/validator/blocks/{slot}"
)
2021-04-13 10:19:31 +00:00
router.redirect(
MethodGet,
"/api/eth/v1/validator/attestation_data",
"/eth/v1/validator/attestation_data"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodGet,
"/api/eth/v1/validator/aggregate_attestation",
"/eth/v1/validator/aggregate_attestation"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodPost,
"/api/eth/v1/validator/aggregate_and_proofs",
"/eth/v1/validator/aggregate_and_proofs"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodPost,
"/api/eth/v1/validator/beacon_committee_subscriptions",
"/eth/v1/validator/beacon_committee_subscriptions"
2021-04-13 10:19:31 +00:00
)
router.redirect(
MethodPost,
"/api/eth/v1/validator/sync_committee_subscriptions",
"/eth/v1/validator/sync_committee_subscriptions"
)
router.redirect(
MethodGet,
"/api/eth/v1/validator/sync_committee_contribution",
"/eth/v1/validator/sync_committee_contribution"
)
router.redirect(
MethodPost,
"/api/eth/v1/validator/contribution_and_proofs",
"/eth/v1/validator/contribution_and_proofs"
)