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 beaconClock*: BeaconClock
taskpool*: TaskPoolPtr taskpool*: TaskPoolPtr
onAttestationSent*: OnAttestationCallback onAttestationSent*: OnAttestationCallback
restKeysCache*: Table[ValidatorPubKey, ValidatorIndex]
const const
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT

View File

@ -1037,6 +1037,18 @@ iterator syncSubcommittee*(
yield syncCommittee[i] yield syncCommittee[i]
inc 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, func syncCommitteeParticipants*(dagParam: ChainDAGRef,
slotParam: Slot): seq[ValidatorPubKey] = slotParam: Slot): seq[ValidatorPubKey] =
# TODO: # TODO:

View File

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

View File

@ -99,24 +99,6 @@ proc toString*(kind: ValidatorFilterKind): string =
of ValidatorFilterKind.WithdrawalDone: of ValidatorFilterKind.WithdrawalDone:
"withdrawal_done" "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) = proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis # https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis
router.api(MethodGet, "/api/eth/v1/beacon/genesis") do () -> RestApiResponse: router.api(MethodGet, "/api/eth/v1/beacon/genesis") do () -> RestApiResponse:
@ -150,7 +132,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
bres.get() bres.get()
node.withStateForBlockSlot(bslot): node.withStateForBlockSlot(bslot):
return RestApiResponse.jsonResponse((root: stateRoot)) return RestApiResponse.jsonResponse((root: stateRoot))
return RestApiResponse.jsonError(Http500, InternalServerError) return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/fork") do ( 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 getStateField(stateData.data, fork).epoch
) )
) )
return RestApiResponse.jsonError(Http500, InternalServerError) return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints
router.api(MethodGet, router.api(MethodGet,
@ -214,7 +196,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
finalized: getStateField(stateData.data, finalized_checkpoint) finalized: getStateField(stateData.data, finalized_checkpoint)
) )
) )
return RestApiResponse.jsonError(Http500, InternalServerError) return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/validators") do ( 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.error())
res.get() 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): node.withStateForBlockSlot(bslot):
let current_epoch = get_current_epoch(node.dag.headState.data) let
var res: seq[RestValidator] current_epoch = getStateField(stateData.data, slot).epoch()
for index, validator in getStateField(stateData.data, validators).pairs(): validatorsCount = lenu64(getStateField(stateData.data, validators))
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)
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 # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator
router.api(MethodGet, router.api(MethodGet,
@ -337,33 +357,21 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400, InvalidValidatorIdValueError, return RestApiResponse.jsonError(Http400, InvalidValidatorIdValueError,
$validator_id.error()) $validator_id.error())
node.withStateForBlockSlot(bslot): node.withStateForBlockSlot(bslot):
let current_epoch = get_current_epoch(node.dag.headState.data) let
let vid = validator_id.get() current_epoch = getStateField(stateData.data, slot).epoch()
case vid.kind validatorsCount = lenu64(getStateField(stateData.data, validators))
of ValidatorQueryKind.Key:
for index, validator in getStateField(stateData.data, let vindex =
validators).pairs(): block:
if validator.pubkey == vid.key: let vid = validator_id.get()
let sres = validator.getStatus(current_epoch) case vid.kind
if sres.isOk(): of ValidatorQueryKind.Key:
return RestApiResponse.jsonResponse( let optIndices = keysToIndices(node.restKeysCache, stateData.data,
( [vid.key])
index: ValidatorIndex(index), if optIndices[0].isNone():
balance: return RestApiResponse.jsonError(Http400, ValidatorNotFoundError)
Base10.toString( optIndices[0].get()
getStateField(stateData.data, balances)[index] of ValidatorQueryKind.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 vres = vid.index.toValidatorIndex() let vres = vid.index.toValidatorIndex()
if vres.isErr(): if vres.isErr():
case vres.error() case vres.error()
@ -373,28 +381,26 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
of ValidatorIndexError.UnsupportedValue: of ValidatorIndexError.UnsupportedValue:
return RestApiResponse.jsonError(Http500, return RestApiResponse.jsonError(Http500,
UnsupportedValidatorIndexValueError) UnsupportedValidatorIndexValueError)
vres.get() let index = vres.get()
if uint64(index) >= validatorsCount:
return RestApiResponse.jsonError(Http400, ValidatorNotFoundError)
index
if uint64(vindex) >= let
uint64(len(getStateField(stateData.data, validators))): validator = getStateField(stateData.data, validators)[vindex]
return RestApiResponse.jsonError(Http404, ValidatorNotFoundError) balance = getStateField(stateData.data, balances)[vindex]
let validator = getStateField(stateData.data, validators)[vindex] status =
let sres = validator.getStatus(current_epoch) block:
if sres.isOk(): let sres = validator.getStatus(current_epoch)
return RestApiResponse.jsonResponse( if sres.isErr():
( return RestApiResponse.jsonError(Http400,
index: vindex, ValidatorStatusNotFoundError,
balance: Base10.toString( $sres.get())
getStateField(stateData.data, balances)[vindex] toString(sres.get())
), return RestApiResponse.jsonResponse(
status: toString(sres.get()), RestValidator.init(vindex, balance, status, validator)
validator: validator )
) return RestApiResponse.jsonError(Http404, StateNotFoundError)
)
else:
return RestApiResponse.jsonError(Http400,
ValidatorStatusNotFoundError)
return RestApiResponse.jsonError(Http500, InternalServerError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidatorBalances # https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidatorBalances
router.api(MethodGet, router.api(MethodGet,
@ -426,55 +432,77 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400, return RestApiResponse.jsonError(Http400,
MaximumNumberOfValidatorIdsError) MaximumNumberOfValidatorIdsError)
ires 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 # https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees
router.api(MethodGet, router.api(MethodGet,
@ -564,7 +592,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonResponse(res) return RestApiResponse.jsonResponse(res)
return RestApiResponse.jsonError(Http500, InternalServerError) return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochSyncCommittees # https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochSyncCommittees
router.api(MethodGet, router.api(MethodGet,
@ -618,16 +646,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
let indices = let indices =
block: block:
var res: seq[ValidatorIndex] var res: seq[ValidatorIndex]
let keyset = keys.toHashSet() let optIndices = keysToIndices(node.restKeysCache, stateData().data,
for index, validator in getStateField(stateData.data, keys)
validators).pairs(): for item in optIndices:
if validator.pubkey in keyset: if item.isNone():
res.add(ValidatorIndex(uint64(index))) return RestApiResponse.jsonError(Http500, InternalServerError,
"Could not get validator indices")
res.add(item.get())
res res
if len(indices) != len(keys):
return RestApiResponse.jsonError(Http500, InternalServerError,
"Could not get validator indices")
let aggregates = let aggregates =
block: block:
var var
@ -645,7 +672,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
validators: indices, validator_aggregates: aggregates) 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 # https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders
router.api(MethodGet, "/api/eth/v1/beacon/headers") do ( 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, nimcrypto/utils as ncrutils,
../spec/[forks], ../spec/[forks],
../spec/eth2_apis/[rest_types, eth2_rest_serialization], ../spec/eth2_apis/[rest_types, eth2_rest_serialization],
@ -6,7 +7,7 @@ import presto,
../consensus_object_pools/[block_pools_types, blockchain_dag] ../consensus_object_pools/[block_pools_types, blockchain_dag]
export export
eth2_rest_serialization, blockchain_dag, presto, rest_types options, eth2_rest_serialization, blockchain_dag, presto, rest_types
const const
MaxEpoch* = compute_epoch_at_slot(not(0'u64)) MaxEpoch* = compute_epoch_at_slot(not(0'u64))
@ -85,6 +86,8 @@ const
"Invalid graffiti bytes value" "Invalid graffiti bytes value"
InvalidEpochValueError* = InvalidEpochValueError* =
"Invalid epoch value" "Invalid epoch value"
EpochFromFutureError* =
"Epoch value is far from the future"
InvalidStateIdValueError* = InvalidStateIdValueError* =
"Invalid state identifier value" "Invalid state identifier value"
InvalidBlockIdValueError* = InvalidBlockIdValueError* =
@ -114,7 +117,7 @@ const
UniqueValidatorIndexError* = UniqueValidatorIndexError* =
"Only unique validator's index are allowed" "Only unique validator's index are allowed"
StateNotFoundError* = StateNotFoundError* =
"State not found" "Could not get requested state"
SlotNotFoundError* = SlotNotFoundError* =
"Slot number is too far away" "Slot number is too far away"
SlotNotInNextWallSlotEpochError* = SlotNotInNextWallSlotEpochError* =
@ -322,5 +325,48 @@ proc toValidatorIndex*(value: RestValidatorIndex): Result[ValidatorIndex,
else: else:
doAssert(false, "ValidatorIndex type size is incorrect") 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 = proc getRouter*(): RestRouter =
RestRouter.init(validate) RestRouter.init(validate)

View File

@ -3,7 +3,7 @@
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * 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). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import std/[typetraits, strutils] import std/[typetraits, strutils, sequtils]
import stew/[results, base10], chronicles, json_serialization, import stew/[results, base10], chronicles, json_serialization,
json_serialization/std/[options, net], json_serialization/std/[options, net],
nimcrypto/utils as ncrutils nimcrypto/utils as ncrutils
@ -142,6 +142,117 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
res res
return RestApiResponse.jsonResponseWRoot(duties, droot) 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 # https://ethereum.github.io/beacon-APIs/#/Validator/produceBlock
router.api(MethodGet, "/api/eth/v1/validator/blocks/{slot}") do ( router.api(MethodGet, "/api/eth/v1/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig], slot: Slot, randao_reveal: Option[ValidatorSig],
@ -558,6 +669,11 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/eth/v1/validator/duties/proposer/{epoch}", "/eth/v1/validator/duties/proposer/{epoch}",
"/api/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( router.redirect(
MethodGet, MethodGet,
"/eth/v1/validator/blocks/{slot}", "/eth/v1/validator/blocks/{slot}",

View File

@ -15,6 +15,7 @@
import import
std/[json, typetraits], std/[json, typetraits],
stew/base10,
".."/forks, ".."/forks,
".."/datatypes/[phase0, altair] ".."/datatypes/[phase0, altair]
@ -101,6 +102,11 @@ type
validator_index*: ValidatorIndex validator_index*: ValidatorIndex
slot*: Slot slot*: Slot
RestSyncCommitteeDuty* = object
pubkey*: ValidatorPubKey
validator_index*: ValidatorIndex
validator_sync_committee_indices*: seq[SyncCommitteeIndex]
RestCommitteeSubscription* = object RestCommitteeSubscription* = object
validator_index*: ValidatorIndex validator_index*: ValidatorIndex
committee_index*: CommitteeIndex committee_index*: CommitteeIndex
@ -355,6 +361,9 @@ type
ProduceBlockResponse* = DataEnclosedObject[phase0.BeaconBlock] ProduceBlockResponse* = DataEnclosedObject[phase0.BeaconBlock]
ProduceBlockResponseV2* = ForkedBeaconBlock ProduceBlockResponseV2* = ForkedBeaconBlock
func `==`*(a, b: RestValidatorIndex): bool =
uint64(a) == uint64(b)
func init*(t: typedesc[StateIdent], v: StateIdentType): StateIdent = func init*(t: typedesc[StateIdent], v: StateIdentType): StateIdent =
StateIdent(kind: StateQueryKind.Named, value: v) StateIdent(kind: StateQueryKind.Named, value: v)
@ -382,3 +391,13 @@ func init*(t: typedesc[ValidatorIdent], v: ValidatorPubKey): ValidatorIdent =
func init*(t: typedesc[RestBlockInfo], func init*(t: typedesc[RestBlockInfo],
v: ForkedTrustedSignedBeaconBlock): RestBlockInfo = v: ForkedTrustedSignedBeaconBlock): RestBlockInfo =
RestBlockInfo(slot: v.slot(), blck: v.root()) 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"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "oneof", "value": ["404", "200"]}, "status": {"operator": "oneof", "value": ["400", "200"]},
} }
}, },
{ {
@ -672,9 +672,9 @@
"headers": {"Accept": "application/json"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "200"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "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"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "200"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "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"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "200"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "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"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "oneof", "value": ["404", "200"]}, "status": {"operator": "oneof", "value": ["400", "200"]},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
} }
}, },
@ -1155,7 +1155,7 @@
"headers": {"Accept": "application/json"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "404"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}] "body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
} }
@ -1168,7 +1168,7 @@
"headers": {"Accept": "application/json"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "404"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}],
"body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}] "body": [{"operator": "jstructcmpns", "value": {"code": "", "message": ""}}]
} }
@ -1231,7 +1231,7 @@
"headers": {"Accept": "application/json"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "oneof", "value": ["404", "200"]}, "status": {"operator": "oneof", "value": ["400", "200"]},
} }
}, },
{ {
@ -1359,9 +1359,9 @@
"headers": {"Accept": "application/json"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "200"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "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"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "200"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "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"} "headers": {"Accept": "application/json"}
}, },
"response": { "response": {
"status": {"operator": "equals", "value": "200"}, "status": {"operator": "equals", "value": "400"},
"headers": [{"key": "Content-Type", "value": "application/json", "operator": "equals"}], "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": ""}}]
} }
}, },
{ {