Allow /api/eth/v1/validator/duties/sync/{epoch} to be called for epochs in the next sync committee period (#3133)
This commit is contained in:
parent
872b88db50
commit
3aa804035f
|
@ -322,7 +322,8 @@ proc toValidatorIndex*(value: RestValidatorIndex): Result[ValidatorIndex,
|
||||||
doAssert(false, "ValidatorIndex type size is incorrect")
|
doAssert(false, "ValidatorIndex type size is incorrect")
|
||||||
|
|
||||||
func syncCommitteeParticipants*(forkedState: ForkedHashedBeaconState,
|
func syncCommitteeParticipants*(forkedState: ForkedHashedBeaconState,
|
||||||
epoch: Epoch): Result[seq[ValidatorPubKey], cstring] =
|
epoch: Epoch
|
||||||
|
): Result[seq[ValidatorPubKey], cstring] =
|
||||||
withState(forkedState):
|
withState(forkedState):
|
||||||
when stateFork >= BeaconStateFork.Altair:
|
when stateFork >= BeaconStateFork.Altair:
|
||||||
let
|
let
|
||||||
|
|
|
@ -180,45 +180,94 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
if res > MaxEpoch:
|
if res > MaxEpoch:
|
||||||
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
|
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
|
||||||
|
|
||||||
if res > node.dag.head.slot.epoch() + 1:
|
res
|
||||||
|
|
||||||
|
# We use a local proc in order to:
|
||||||
|
# * avoid code duplication
|
||||||
|
# * reduce code bloat from `withState`
|
||||||
|
proc produceResponse(requestedValidatorIndices: openArray[ValidatorIndex],
|
||||||
|
syncCommittee: openArray[ValidatorPubKey],
|
||||||
|
stateValidators: seq[Validator]
|
||||||
|
): seq[RestSyncCommitteeDuty] {.nimcall.} =
|
||||||
|
result = newSeqOfCap[RestSyncCommitteeDuty](len(requestedValidatorIndices))
|
||||||
|
for requestedValidatorIdx in requestedValidatorIndices:
|
||||||
|
if requestedValidatorIdx.uint64 >= stateValidators.lenu64:
|
||||||
|
# If the requested validator index was not valid within this old
|
||||||
|
# state, it's not possible that it will sit on the sync committee.
|
||||||
|
# Since this API must omit results for validators that don't have
|
||||||
|
# duties, we can simply ingnore this requested index.
|
||||||
|
# (we won't bother to validate it agains a more recent state).
|
||||||
|
continue
|
||||||
|
|
||||||
|
let requestedValidatorPubkey =
|
||||||
|
stateValidators[requestedValidatorIdx].pubkey
|
||||||
|
|
||||||
|
var indicesInSyncCommittee = newSeq[IndexInSyncCommittee]()
|
||||||
|
for idx, syncCommitteeMemberPubkey in syncCommittee:
|
||||||
|
if syncCommitteeMemberPubkey == requestedValidatorPubkey:
|
||||||
|
indicesInSyncCommittee.add(IndexInSyncCommittee idx)
|
||||||
|
|
||||||
|
if indicesInSyncCommittee.len > 0:
|
||||||
|
result.add RestSyncCommitteeDuty(
|
||||||
|
pubkey: requestedValidatorPubkey,
|
||||||
|
validator_index: requestedValidatorIdx,
|
||||||
|
validator_sync_committee_indices: indicesInSyncCommittee)
|
||||||
|
|
||||||
|
template emptyResponse: auto =
|
||||||
|
newSeq[RestSyncCommitteeDuty]()
|
||||||
|
|
||||||
|
# We check the head state first in order to avoid costly replays
|
||||||
|
# if possible:
|
||||||
|
let
|
||||||
|
qSyncPeriod = sync_committee_period(qepoch)
|
||||||
|
headEpoch = node.dag.head.slot.epoch
|
||||||
|
headSyncPeriod = sync_committee_period(headEpoch)
|
||||||
|
|
||||||
|
if qSyncPeriod == headSyncPeriod:
|
||||||
|
let res = withState(node.dag.headState.data):
|
||||||
|
when stateFork >= BeaconStateFork.Altair:
|
||||||
|
produceResponse(indexList,
|
||||||
|
state.data.current_sync_committee.pubkeys.data,
|
||||||
|
state.data.validators.asSeq)
|
||||||
|
else:
|
||||||
|
emptyResponse()
|
||||||
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
elif qSyncPeriod == (headSyncPeriod + 1):
|
||||||
|
let res = withState(node.dag.headState.data):
|
||||||
|
when stateFork >= BeaconStateFork.Altair:
|
||||||
|
produceResponse(indexList,
|
||||||
|
state.data.next_sync_committee.pubkeys.data,
|
||||||
|
state.data.validators.asSeq)
|
||||||
|
else:
|
||||||
|
emptyResponse()
|
||||||
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
elif qSyncPeriod > headSyncPeriod:
|
||||||
|
# The requested epoch may still be too far in the future.
|
||||||
if not(node.isSynced(node.dag.head)):
|
if not(node.isSynced(node.dag.head)):
|
||||||
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
||||||
else:
|
else:
|
||||||
return RestApiResponse.jsonError(Http400, EpochFromFutureError)
|
return RestApiResponse.jsonError(Http400, EpochFromFutureError)
|
||||||
res
|
else:
|
||||||
|
# The slot at the start of the sync committee period is likely to have a
|
||||||
|
# state snapshot in the database, so we can restore the state relatively
|
||||||
|
# cheaply:
|
||||||
|
let earliestSlotInQSyncPeriod =
|
||||||
|
Slot(qSyncPeriod * SLOTS_PER_SYNC_COMMITTEE_PERIOD)
|
||||||
|
|
||||||
let bslot = node.dag.head.atSlot(qepoch.compute_start_slot_at_epoch())
|
# TODO
|
||||||
|
# The DAG can offer a short-cut for getting just the information we need
|
||||||
node.withStateForBlockSlot(bslot):
|
# in order to compute the sync committee for the epoch. See the following
|
||||||
let syncCommittee =
|
# discussion for more details:
|
||||||
block:
|
# https://github.com/status-im/nimbus-eth2/pull/3133#pullrequestreview-817184693
|
||||||
let res = syncCommitteeParticipants(stateData().data, qepoch)
|
node.withStateForBlockSlot(node.dag.head.atSlot(earliestSlotInQSyncPeriod)):
|
||||||
if res.isErr():
|
let res = withState(stateData().data):
|
||||||
return RestApiResponse.jsonError(Http400,
|
when stateFork >= BeaconStateFork.Altair:
|
||||||
$res.error())
|
produceResponse(indexList,
|
||||||
let kres = res.get()
|
state.data.current_sync_committee.pubkeys.data,
|
||||||
if len(kres) == 0:
|
state.data.validators.asSeq)
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError,
|
else:
|
||||||
"List of sync committee participants is empty")
|
emptyResponse()
|
||||||
kres
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
|
||||||
var duties =
|
|
||||||
withState(stateData().data):
|
|
||||||
var res = newSeq[RestSyncCommitteeDuty](len(indexList))
|
|
||||||
for resIdx, validatorIdx in indexList:
|
|
||||||
if not validatorIdx.isValidInState(state.data):
|
|
||||||
return RestApiResponse.jsonError(Http400, "Invalid index: " & $validatorIdx)
|
|
||||||
|
|
||||||
res[resIdx].pubkey = state.data.validators.asSeq()[validatorIdx].pubkey
|
|
||||||
res[resIdx].validator_index = validatorIdx
|
|
||||||
|
|
||||||
for idx, pubkey in syncCommittee:
|
|
||||||
if pubkey == res[resIdx].pubkey:
|
|
||||||
res[resIdx].validator_sync_committee_indices.add(
|
|
||||||
IndexInSyncCommittee idx)
|
|
||||||
res
|
|
||||||
|
|
||||||
return RestApiResponse.jsonResponse(duties)
|
|
||||||
|
|
||||||
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
||||||
|
|
||||||
|
|
|
@ -357,6 +357,9 @@ else:
|
||||||
|
|
||||||
# createConstantsFromPreset const_preset
|
# createConstantsFromPreset const_preset
|
||||||
|
|
||||||
|
const SLOTS_PER_SYNC_COMMITTEE_PERIOD* =
|
||||||
|
SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
|
||||||
func parse(T: type uint64, input: string): T {.raises: [ValueError, Defect].} =
|
func parse(T: type uint64, input: string): T {.raises: [ValueError, Defect].} =
|
||||||
var res: BiggestUInt
|
var res: BiggestUInt
|
||||||
if input.len > 2 and input[0] == '0' and input[1] == 'x':
|
if input.len > 2 and input[0] == '0' and input[1] == 'x':
|
||||||
|
|
Loading…
Reference in New Issue