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:
parent
fe8bbb2388
commit
7319f150e8
|
@ -70,6 +70,7 @@ type
|
|||
beaconClock*: BeaconClock
|
||||
taskpool*: TaskPoolPtr
|
||||
onAttestationSent*: OnAttestationCallback
|
||||
restKeysCache*: Table[ValidatorPubKey, ValidatorIndex]
|
||||
|
||||
const
|
||||
MaxEmptySlotCount* = uint64(10*60) div SECONDS_PER_SLOT
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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": ""}}]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue