Number of REST fixes. (#2987)

* Initial commit.

* Fix path.

* Add validator keys to indices cache mechanism.
Move syncComitteeParticipants to common place.

* Fix sync participants order issue.

* Fix error code when state could not be found.
Refactor `state/validators` to use keysToIndices mechanism.

* Fix RestValidatorIndex to ValidatorIndex conversion TODOs.

* Address review comments.

* Fix REST test rules.
This commit is contained in:
Eugene Kabanov 2021-10-14 13:38:38 +03:00 committed by GitHub
parent fe8bbb2388
commit 7319f150e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 425 additions and 200 deletions

View File

@ -70,6 +70,7 @@ type
beaconClock*: BeaconClock
taskpool*: TaskPoolPtr
onAttestationSent*: OnAttestationCallback
restKeysCache*: Table[ValidatorPubKey, ValidatorIndex]
const
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT

View File

@ -1037,6 +1037,18 @@ iterator syncSubcommittee*(
yield syncCommittee[i]
inc i
iterator syncSubcommitteePairs*(
syncCommittee: openarray[ValidatorIndex],
committeeIdx: SyncCommitteeIndex): tuple[validatorIdx: ValidatorIndex,
committeeIdx: int] =
var
i = committeeIdx.asInt * SYNC_SUBCOMMITTEE_SIZE
onePastEndIdx = min(syncCommittee.len, i + SYNC_SUBCOMMITTEE_SIZE)
while i < onePastEndIdx:
yield (syncCommittee[i], i)
inc i
func syncCommitteeParticipants*(dagParam: ChainDAGRef,
slotParam: Slot): seq[ValidatorPubKey] =
# TODO:

View File

@ -411,9 +411,13 @@ proc init*(T: type BeaconNode,
requestManager: RequestManager.init(network, blockProcessor),
beaconClock: beaconClock,
taskpool: taskpool,
onAttestationSent: onAttestationSent
onAttestationSent: onAttestationSent,
)
# initialize REST server cache tables.
if config.restEnabled:
node.restKeysCache = initTable[ValidatorPubKey, ValidatorIndex]()
# set topic validation routine
network.setValidTopics(
block:

View File

@ -99,24 +99,6 @@ proc toString*(kind: ValidatorFilterKind): string =
of ValidatorFilterKind.WithdrawalDone:
"withdrawal_done"
func syncCommitteeParticipants*(forkedState: ForkedHashedBeaconState,
epoch: Epoch): Result[seq[ValidatorPubKey], cstring] =
withState(forkedState):
when stateFork >= forkAltair:
let
headSlot = state.data.slot
epochPeriod = syncCommitteePeriod(epoch.compute_start_slot_at_epoch())
currentPeriod = syncCommitteePeriod(headSlot)
nextPeriod = currentPeriod + 1'u64
if epochPeriod == currentPeriod:
ok(@(state.data.current_sync_committee.pubkeys.data))
elif epochPeriod == nextPeriod:
ok(@(state.data.next_sync_committee.pubkeys.data))
else:
err("Epoch is outside the sync committee period of the state")
else:
err("State's fork do not support sync committees")
proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis
router.api(MethodGet, "/api/eth/v1/beacon/genesis") do () -> RestApiResponse:
@ -150,7 +132,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
bres.get()
node.withStateForBlockSlot(bslot):
return RestApiResponse.jsonResponse((root: stateRoot))
return RestApiResponse.jsonError(Http500, InternalServerError)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/fork") do (
@ -182,7 +164,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
getStateField(stateData.data, fork).epoch
)
)
return RestApiResponse.jsonError(Http500, InternalServerError)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints
router.api(MethodGet,
@ -214,7 +196,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
finalized: getStateField(stateData.data, finalized_checkpoint)
)
)
return RestApiResponse.jsonError(Http500, InternalServerError)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/validators") do (
@ -259,59 +241,97 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
$res.error())
res.get()
let (keySet, indexSet) =
block:
var res1: HashSet[ValidatorPubKey]
var res2: HashSet[ValidatorIndex]
for item in validatorIds:
case item.kind
of ValidatorQueryKind.Key:
if item.key in res1:
return RestApiResponse.jsonError(Http400, UniqueValidatorKeyError)
res1.incl(item.key)
of ValidatorQueryKind.Index:
let vitem =
block:
let vres = item.index.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
vres.get()
if vitem in res2:
return RestApiResponse.jsonError(Http400,
UniqueValidatorIndexError)
res2.incl(vitem)
(res1, res2)
node.withStateForBlockSlot(bslot):
let current_epoch = get_current_epoch(node.dag.headState.data)
var res: seq[RestValidator]
for index, validator in getStateField(stateData.data, validators).pairs():
let includeFlag =
(len(keySet) == 0) and (len(indexSet) == 0) or
(len(indexSet) > 0 and (ValidatorIndex(index) in indexSet)) or
(len(keySet) > 0 and (validator.pubkey in keySet))
let sres = validator.getStatus(current_epoch)
if sres.isOk():
let vstatus = sres.get()
let statusFlag = vstatus in validatorsMask
if includeFlag and statusFlag:
res.add(RestValidator(
index: ValidatorIndex(index),
balance:
Base10.toString(getStateField(stateData.data, balances)[index]),
status: toString(vstatus),
validator: validator
))
return RestApiResponse.jsonResponse(res)
let
current_epoch = getStateField(stateData.data, slot).epoch()
validatorsCount = lenu64(getStateField(stateData.data, validators))
return RestApiResponse.jsonError(Http500, InternalServerError)
let indices =
block:
var keyset: HashSet[ValidatorPubKey]
var indexset: HashSet[ValidatorIndex]
for item in validatorIds:
case item.kind
of ValidatorQueryKind.Key:
if item.key in keyset:
return RestApiResponse.jsonError(Http400,
UniqueValidatorKeyError)
keyset.incl(item.key)
of ValidatorQueryKind.Index:
let vindex =
block:
let vres = item.index.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
let index = vres.get()
if uint64(index) >= validatorsCount:
return RestApiResponse.jsonError(Http400,
ValidatorNotFoundError)
if index in indexset:
return RestApiResponse.jsonError(Http400,
UniqueValidatorIndexError)
index
indexset.incl(vindex)
if len(keyset) > 0:
let optIndices = keysToIndices(node.restKeysCache, stateData.data,
keyset.toSeq())
for item in optIndices:
if item.isNone():
return RestApiResponse.jsonError(Http400,
ValidatorNotFoundError)
let vindex = item.get()
if vindex in indexset:
return RestApiResponse.jsonError(Http400,
UniqueValidatorIndexError)
indexset.incl(vindex)
indexset.toSeq()
let response =
block:
var res: seq[RestValidator]
if len(indices) == 0:
# There is no indices, so we going to filter all the validators.
for index, validator in getStateField(stateData.data,
validators).pairs():
let
balance = getStateField(stateData.data, balances)[index]
status =
block:
let sres = validator.getStatus(current_epoch)
if sres.isErr():
return RestApiResponse.jsonError(Http400,
ValidatorStatusNotFoundError,
$sres.get())
sres.get()
if status in validatorsMask:
res.add(RestValidator.init(ValidatorIndex(index), balance,
toString(status), validator))
else:
for index in indices:
let
validator = getStateField(stateData.data, validators)[index]
balance = getStateField(stateData.data, balances)[index]
status =
block:
let sres = validator.getStatus(current_epoch)
if sres.isErr():
return RestApiResponse.jsonError(Http400,
ValidatorStatusNotFoundError,
$sres.get())
sres.get()
if status in validatorsMask:
res.add(RestValidator.init(index, balance, toString(status),
validator))
res
return RestApiResponse.jsonResponse(response)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator
router.api(MethodGet,
@ -337,33 +357,21 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400, InvalidValidatorIdValueError,
$validator_id.error())
node.withStateForBlockSlot(bslot):
let current_epoch = get_current_epoch(node.dag.headState.data)
let vid = validator_id.get()
case vid.kind
of ValidatorQueryKind.Key:
for index, validator in getStateField(stateData.data,
validators).pairs():
if validator.pubkey == vid.key:
let sres = validator.getStatus(current_epoch)
if sres.isOk():
return RestApiResponse.jsonResponse(
(
index: ValidatorIndex(index),
balance:
Base10.toString(
getStateField(stateData.data, balances)[index]
),
status: toString(sres.get()),
validator: validator
)
)
else:
return RestApiResponse.jsonError(Http400,
ValidatorStatusNotFoundError)
return RestApiResponse.jsonError(Http404, ValidatorNotFoundError)
of ValidatorQueryKind.Index:
let vindex =
block:
let
current_epoch = getStateField(stateData.data, slot).epoch()
validatorsCount = lenu64(getStateField(stateData.data, validators))
let vindex =
block:
let vid = validator_id.get()
case vid.kind
of ValidatorQueryKind.Key:
let optIndices = keysToIndices(node.restKeysCache, stateData.data,
[vid.key])
if optIndices[0].isNone():
return RestApiResponse.jsonError(Http400, ValidatorNotFoundError)
optIndices[0].get()
of ValidatorQueryKind.Index:
let vres = vid.index.toValidatorIndex()
if vres.isErr():
case vres.error()
@ -373,28 +381,26 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
vres.get()
let index = vres.get()
if uint64(index) >= validatorsCount:
return RestApiResponse.jsonError(Http400, ValidatorNotFoundError)
index
if uint64(vindex) >=
uint64(len(getStateField(stateData.data, validators))):
return RestApiResponse.jsonError(Http404, ValidatorNotFoundError)
let validator = getStateField(stateData.data, validators)[vindex]
let sres = validator.getStatus(current_epoch)
if sres.isOk():
return RestApiResponse.jsonResponse(
(
index: vindex,
balance: Base10.toString(
getStateField(stateData.data, balances)[vindex]
),
status: toString(sres.get()),
validator: validator
)
)
else:
return RestApiResponse.jsonError(Http400,
ValidatorStatusNotFoundError)
return RestApiResponse.jsonError(Http500, InternalServerError)
let
validator = getStateField(stateData.data, validators)[vindex]
balance = getStateField(stateData.data, balances)[vindex]
status =
block:
let sres = validator.getStatus(current_epoch)
if sres.isErr():
return RestApiResponse.jsonError(Http400,
ValidatorStatusNotFoundError,
$sres.get())
toString(sres.get())
return RestApiResponse.jsonResponse(
RestValidator.init(vindex, balance, status, validator)
)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidatorBalances
router.api(MethodGet,
@ -426,55 +432,77 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400,
MaximumNumberOfValidatorIdsError)
ires
let (keySet, indexSet) =
block:
var res1: HashSet[ValidatorPubKey]
var res2: HashSet[ValidatorIndex]
for item in validatorIds:
case item.kind
of ValidatorQueryKind.Key:
if item.key in res1:
return RestApiResponse.jsonError(Http400,
UniqueValidatorKeyError)
res1.incl(item.key)
of ValidatorQueryKind.Index:
let vitem =
block:
let vres = item.index.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
vres.get()
if vitem in res2:
return RestApiResponse.jsonError(Http400,
UniqueValidatorIndexError)
res2.incl(vitem)
(res1, res2)
node.withStateForBlockSlot(bslot):
let current_epoch = get_current_epoch(node.dag.headState.data)
var res: seq[RestValidatorBalance]
for index, validator in getStateField(stateData.data, validators).pairs():
let includeFlag =
(len(keySet) == 0) and (len(indexSet) == 0) or
(len(indexSet) > 0 and (ValidatorIndex(index) in indexSet)) or
(len(keySet) > 0 and (validator.pubkey in keySet))
let sres = validator.getStatus(current_epoch)
if sres.isOk():
let vstatus = sres.get()
if includeFlag:
res.add(RestValidatorBalance(
index: ValidatorIndex(index),
balance:
Base10.toString(getStateField(stateData.data, balances)[index]),
))
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonError(Http500, InternalServerError)
node.withStateForBlockSlot(bslot):
let
current_epoch = getStateField(stateData.data, slot).epoch()
validatorsCount = lenu64(getStateField(stateData.data, validators))
let indices =
block:
var keyset: HashSet[ValidatorPubKey]
var indexset: HashSet[ValidatorIndex]
for item in validatorIds:
case item.kind
of ValidatorQueryKind.Key:
if item.key in keyset:
return RestApiResponse.jsonError(Http400,
UniqueValidatorKeyError)
keyset.incl(item.key)
of ValidatorQueryKind.Index:
let vindex =
block:
let vres = item.index.toValidatorIndex()
if vres.isErr():
case vres.error()
of ValidatorIndexError.TooHighValue:
return RestApiResponse.jsonError(Http400,
TooHighValidatorIndexValueError)
of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError)
let index = vres.get()
if uint64(index) >= validatorsCount:
return RestApiResponse.jsonError(Http400,
ValidatorNotFoundError)
if index in indexset:
return RestApiResponse.jsonError(Http400,
UniqueValidatorIndexError)
index
indexset.incl(vindex)
if len(keyset) > 0:
let optIndices = keysToIndices(node.restKeysCache, stateData.data,
keyset.toSeq())
for item in optIndices:
if item.isNone():
return RestApiResponse.jsonError(Http400,
ValidatorNotFoundError)
let vindex = item.get()
if vindex in indexset:
return RestApiResponse.jsonError(Http400,
UniqueValidatorIndexError)
indexset.incl(vindex)
indexset.toSeq()
let response =
block:
var res: seq[RestValidatorBalance]
if len(indices) == 0:
# There is no indices, so we going to filter all the validators.
for index, validator in getStateField(stateData.data,
validators).pairs():
let balance = getStateField(stateData.data, balances)[index]
res.add(RestValidatorBalance.init(ValidatorIndex(index),
balance))
else:
for index in indices:
let balance = getStateField(stateData.data, balances)[index]
res.add(RestValidatorBalance.init(index, balance))
res
return RestApiResponse.jsonResponse(response)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees
router.api(MethodGet,
@ -564,7 +592,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonError(Http500, InternalServerError)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochSyncCommittees
router.api(MethodGet,
@ -618,16 +646,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
let indices =
block:
var res: seq[ValidatorIndex]
let keyset = keys.toHashSet()
for index, validator in getStateField(stateData.data,
validators).pairs():
if validator.pubkey in keyset:
res.add(ValidatorIndex(uint64(index)))
let optIndices = keysToIndices(node.restKeysCache, stateData().data,
keys)
for item in optIndices:
if item.isNone():
return RestApiResponse.jsonError(Http500, InternalServerError,
"Could not get validator indices")
res.add(item.get())
res
if len(indices) != len(keys):
return RestApiResponse.jsonError(Http500, InternalServerError,
"Could not get validator indices")
let aggregates =
block:
var
@ -645,7 +672,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
validators: indices, validator_aggregates: aggregates)
)
return RestApiResponse.jsonError(Http400, "Could not get requested state")
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders
router.api(MethodGet, "/api/eth/v1/beacon/headers") do (

View File

@ -1,4 +1,5 @@
import presto,
import std/options,
presto,
nimcrypto/utils as ncrutils,
../spec/[forks],
../spec/eth2_apis/[rest_types, eth2_rest_serialization],
@ -6,7 +7,7 @@ import presto,
../consensus_object_pools/[block_pools_types, blockchain_dag]
export
eth2_rest_serialization, blockchain_dag, presto, rest_types
options, eth2_rest_serialization, blockchain_dag, presto, rest_types
const
MaxEpoch* = compute_epoch_at_slot(not(0'u64))
@ -85,6 +86,8 @@ const
"Invalid graffiti bytes value"
InvalidEpochValueError* =
"Invalid epoch value"
EpochFromFutureError* =
"Epoch value is far from the future"
InvalidStateIdValueError* =
"Invalid state identifier value"
InvalidBlockIdValueError* =
@ -114,7 +117,7 @@ const
UniqueValidatorIndexError* =
"Only unique validator's index are allowed"
StateNotFoundError* =
"State not found"
"Could not get requested state"
SlotNotFoundError* =
"Slot number is too far away"
SlotNotInNextWallSlotEpochError* =
@ -322,5 +325,48 @@ proc toValidatorIndex*(value: RestValidatorIndex): Result[ValidatorIndex,
else:
doAssert(false, "ValidatorIndex type size is incorrect")
func syncCommitteeParticipants*(forkedState: ForkedHashedBeaconState,
epoch: Epoch): Result[seq[ValidatorPubKey], cstring] =
withState(forkedState):
when stateFork >= forkAltair:
let
headSlot = state.data.slot
epochPeriod = syncCommitteePeriod(epoch.compute_start_slot_at_epoch())
currentPeriod = syncCommitteePeriod(headSlot)
nextPeriod = currentPeriod + 1'u64
if epochPeriod == currentPeriod:
ok(@(state.data.current_sync_committee.pubkeys.data))
elif epochPeriod == nextPeriod:
ok(@(state.data.next_sync_committee.pubkeys.data))
else:
err("Epoch is outside the sync committee period of the state")
else:
err("State's fork do not support sync committees")
func keysToIndices*(cacheTable: var Table[ValidatorPubKey, ValidatorIndex],
forkedState: ForkedHashedBeaconState,
keys: openArray[ValidatorPubKey]
): seq[Option[ValidatorIndex]] =
var indices = newSeq[Option[ValidatorIndex]](len(keys))
var keyset =
block:
var res: Table[ValidatorPubKey, int]
for inputIndex, pubkey in keys.pairs():
# Try to search in cache first.
cacheTable.withValue(pubkey, vindex):
indices[inputIndex] = some(vindex[])
do:
res[pubkey] = inputIndex
res
if len(keyset) > 0:
for validatorIndex, validator in getStateField(forkedState,
validators).pairs():
keyset.withValue(validator.pubkey, listIndex):
# Store pair (pubkey, index) into cache table.
cacheTable[validator.pubkey] = ValidatorIndex(validatorIndex)
# Fill result sequence.
indices[listIndex[]] = some(ValidatorIndex(validatorIndex))
indices
proc getRouter*(): RestRouter =
RestRouter.init(validate)

View File

@ -3,7 +3,7 @@
# * 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]
import std/[typetraits, strutils, sequtils]
import stew/[results, base10], chronicles, json_serialization,
json_serialization/std/[options, net],
nimcrypto/utils as ncrutils
@ -142,6 +142,117 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
res
return RestApiResponse.jsonResponseWRoot(duties, droot)
router.api(MethodPost, "/api/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)
if res > node.dag.head.slot.epoch() + 1:
if not(node.isSynced(node.dag.head)):
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
else:
return RestApiResponse.jsonError(Http400, EpochFromFutureError)
res
let bslot = node.dag.head.atSlot(qepoch.compute_start_slot_at_epoch())
node.withStateForBlockSlot(bslot):
let validatorsCount = lenu64(getStateField(stateData.data, validators))
let participants =
block:
let res = syncCommitteeParticipants(stateData().data, qepoch)
if res.isErr():
return RestApiResponse.jsonError(Http400,
$res.error())
let kres = res.get()
if len(kres) == 0:
return RestApiResponse.jsonError(Http500, InternalServerError,
"List of sync committee participants is empty")
kres
# TODO: We doing this because `participants` are stored as array of
# validator keys, so we need to convert it to indices.
let participantIndices =
block:
var res: seq[ValidatorIndex]
let optIndices = keysToIndices(node.restKeysCache, stateData().data,
participants)
for item in optIndices:
if item.isNone():
return RestApiResponse.jsonError(Http500, InternalServerError,
"Could not get validator indices")
res.add(item.get())
res
let validatorsSet =
block:
var res: Table[ValidatorIndex, int]
for listIndex, validatorIndex in indexList.pairs():
if uint64(validatorIndex) >= validatorsCount:
return RestApiResponse.jsonError(Http400,
ValidatorNotFoundError)
res[validatorIndex] = listIndex
res
template isEmpty(duty: RestSyncCommitteeDuty): bool =
len(duty.validator_sync_committee_indices) == 0
var duties =
block:
var res = newSeq[RestSyncCommitteeDuty](len(indexList))
for committeeIdx in allSyncCommittees():
for valIndex, arrIndex in syncSubcommitteePairs(participantIndices,
committeeIdx):
let listIndex = validatorsSet.getOrDefault(valIndex, -1)
if listIndex >= 0:
if res[listIndex].isEmpty():
let key =
getStateField(stateData().data, validators)[valIndex].pubkey
res[listIndex] = RestSyncCommitteeDuty(
validator_index: valIndex,
pubkey: key
)
res[listIndex].validator_sync_committee_indices.add(
committeeIdx)
res.keepItIf(not(isEmpty(it)))
res
return RestApiResponse.jsonResponse(duties)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlock
router.api(MethodGet, "/api/eth/v1/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig],
@ -558,6 +669,11 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/eth/v1/validator/duties/proposer/{epoch}",
"/api/eth/v1/validator/duties/proposer/{epoch}"
)
router.redirect(
MethodPost,
"/eth/v1/validator/duties/sync/{epoch}",
"/api/eth/v1/validator/duties/sync/{epoch}"
)
router.redirect(
MethodGet,
"/eth/v1/validator/blocks/{slot}",

View File

@ -15,6 +15,7 @@
import
std/[json, typetraits],
stew/base10,
".."/forks,
".."/datatypes/[phase0, altair]
@ -101,6 +102,11 @@ type
validator_index*: ValidatorIndex
slot*: Slot
RestSyncCommitteeDuty* = object
pubkey*: ValidatorPubKey
validator_index*: ValidatorIndex
validator_sync_committee_indices*: seq[SyncCommitteeIndex]
RestCommitteeSubscription* = object
validator_index*: ValidatorIndex
committee_index*: CommitteeIndex
@ -355,6 +361,9 @@ type
ProduceBlockResponse* = DataEnclosedObject[phase0.BeaconBlock]
ProduceBlockResponseV2* = ForkedBeaconBlock
func `==`*(a, b: RestValidatorIndex): bool =
uint64(a) == uint64(b)
func init*(t: typedesc[StateIdent], v: StateIdentType): StateIdent =
StateIdent(kind: StateQueryKind.Named, value: v)
@ -382,3 +391,13 @@ func init*(t: typedesc[ValidatorIdent], v: ValidatorPubKey): ValidatorIdent =
func init*(t: typedesc[RestBlockInfo],
v: ForkedTrustedSignedBeaconBlock): RestBlockInfo =
RestBlockInfo(slot: v.slot(), blck: v.root())
func init*(t: typedesc[RestValidator], index: ValidatorIndex,
balance: uint64, status: string,
validator: Validator): RestValidator =
RestValidator(index: index, balance: Base10.toString(balance),
status: status, validator: validator)
func init*(t: typedesc[RestValidatorBalance], index: ValidatorIndex,
balance: uint64): RestValidatorBalance =
RestValidatorBalance(index: index, balance: Base10.toString(balance))

View File

@ -544,7 +544,7 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "oneof", "value": ["404", "200"]},
"status": {"operator": "oneof", "value": ["400", "200"]},
}
},
{
@ -672,9 +672,9 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "200"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}]
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
},
{
@ -685,9 +685,9 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "200"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}]
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
},
{
@ -698,9 +698,9 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "200"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}]
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
},
{
@ -997,7 +997,7 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "oneof", "value": ["404", "200"]},
"status": {"operator": "oneof", "value": ["400", "200"]},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
}
},
@ -1155,7 +1155,7 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "404"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
@ -1168,7 +1168,7 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "404"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
@ -1231,7 +1231,7 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "oneof", "value": ["404", "200"]},
"status": {"operator": "oneof", "value": ["400", "200"]},
}
},
{
@ -1359,9 +1359,9 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "200"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}]
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
},
{
@ -1372,9 +1372,9 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "200"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}]
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
},
{
@ -1385,9 +1385,9 @@
"headers": {"Accept": "application/json"}
},
"response": {
"status": {"operator": "equals", "value": "200"},
"status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmps", "start": ["data"], "value": [{"index": "", "balance": "", "status": "", "validator": {"pubkey": "", "withdrawal_credentials": "", "effective_balance": "", "slashed": false, "activation_eligibility_epoch": "", "activation_epoch": "", "exit_epoch": "", "withdrawable_epoch": ""}}]}]
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
}
},
{