Correct implementation of the /validator/duties/sync/{epoch} API

According to the spec, this call should return the positions of
the specified validators within the sync committee. The existing
code was instead returning the indices of the sync sub-committees
where the validator is a member.
This commit is contained in:
Zahary Karadjov 2021-11-07 23:03:23 +02:00
parent 48eba59971
commit ba42b2b316
5 changed files with 52 additions and 54 deletions

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, sets, sequtils]
import std/[typetraits, strutils, sets]
import stew/[results, base10], chronicles,
nimcrypto/utils as ncrutils
import ".."/[beacon_chain_db, beacon_node],
@ -11,7 +11,7 @@ import ".."/[beacon_chain_db, beacon_node],
".."/consensus_object_pools/[blockchain_dag, spec_cache,
attestation_pool, sync_committee_msg_pool],
".."/validators/validator_duties,
".."/spec/[forks, network],
".."/spec/[beaconstate, forks, network],
".."/spec/datatypes/[phase0, altair],
"."/rest_utils
@ -190,8 +190,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
let bslot = node.dag.head.atSlot(qepoch.compute_start_slot_at_epoch())
node.withStateForBlockSlot(bslot):
let validatorsCount = lenu64(getStateField(stateData.data, validators))
let participants =
let syncCommittee =
block:
let res = syncCommitteeParticipants(stateData().data, qepoch)
if res.isErr():
@ -203,52 +202,20 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
"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, and if any of
# public keys are missing, it means some unexpected error.
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():
# We ignore indices which could not fit in current list of
# validators.
if uint64(validatorIndex) < validatorsCount:
res[validatorIndex] = listIndex
res
template isEmpty(duty: RestSyncCommitteeDuty): bool =
len(duty.validator_sync_committee_indices) == 0
var duties =
block:
withState(stateData().data):
var res = newSeq[RestSyncCommitteeDuty](len(indexList))
for committeeIdx in allSyncSubcommittees():
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)))
for resIdx, validatorIdx in indexList:
if not validatorIdx.isValidInState(state.data):
return RestApiResponse.jsonError(Http400, "Invalid index: " & $validatorIdx)
res[resIdx].pubkey = state.data.validators[validatorIdx].pubkey
res[resIdx].validator_index = validatorIdx
for idx, pubkey in syncCommittee:
if pubkey == res[resIdx].pubkey:
res[resIdx].validator_sync_committee_indices.add(
ValidatorIndexInSyncCommittee idx)
res
return RestApiResponse.jsonResponse(duties)

View File

@ -19,6 +19,9 @@ import
export extras, forks, validator
type
SomeBeaconState* = phase0.BeaconState | altair.BeaconState | merge.BeaconState
# https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/phase0/beacon-chain.md#increase_balance
func increase_balance*(balance: var Gwei, delta: Gwei) =
balance += delta
@ -860,3 +863,7 @@ func upgrade_to_merge*(cfg: RuntimeConfig, pre: altair.BeaconState):
# Execution-layer
latest_execution_payload_header: ExecutionPayloadHeader()
)
template isValidInState*(idx: ValidatorIndex, state: SomeBeaconState): bool =
idx.int < state.validators.len

View File

@ -432,6 +432,7 @@ type
SomeBeaconBlockBody* = BeaconBlockBody | SigVerifiedBeaconBlockBody | TrustedBeaconBlockBody
SyncSubcommitteeIndex* = distinct uint8
ValidatorIndexInSyncCommittee* = distinct uint16
chronicles.formatIt BeaconBlock: it.shortLog
chronicles.formatIt SyncSubcommitteeIndex: uint8(it)
@ -440,7 +441,12 @@ template asInt*(x: SyncSubcommitteeIndex): int = int(x)
template asUInt8*(x: SyncSubcommitteeIndex): uint8 = uint8(x)
template asUInt64*(x: SyncSubcommitteeIndex): uint64 = uint64(x)
template `[]`*(a: auto; i: SyncSubcommitteeIndex): auto = a[i.asInt]
template `[]`*(a: auto; i: SyncSubcommitteeIndex): auto =
a[i.asInt]
template `[]`*(arr: array[SYNC_COMMITTEE_SIZE, any] | seq;
idx: ValidatorIndexInSyncCommittee): auto =
arr[int idx]
template `==`*(x, y: SyncSubcommitteeIndex): bool =
distinctBase(x) == distinctBase(y)

View File

@ -412,12 +412,12 @@ proc readValue*(reader: var JsonReader[RestJson], value: var Epoch) {.
reader.raiseUnexpectedValue($res.error())
## ValidatorIndex
proc writeValue*(writer: var JsonWriter[RestJson], value: ValidatorIndex) {.
raises: [IOError, Defect].} =
proc writeValue*(writer: var JsonWriter[RestJson], value: ValidatorIndex)
{.raises: [IOError, Defect].} =
writeValue(writer, Base10.toString(uint64(value)))
proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorIndex) {.
raises: [IOError, SerializationError, Defect].} =
proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorIndex)
{.raises: [IOError, SerializationError, Defect].} =
let svalue = reader.readValue(string)
let res = Base10.decode(uint64, svalue)
if res.isOk():
@ -430,6 +430,24 @@ proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorIndex) {.
else:
reader.raiseUnexpectedValue($res.error())
proc writeValue*(writer: var JsonWriter[RestJson], value: ValidatorIndexInSyncCommittee)
{.raises: [IOError, Defect].} =
writeValue(writer, Base10.toString(distinctBase(value)))
proc readValue*(reader: var JsonReader[RestJson], value: var ValidatorIndexInSyncCommittee)
{.raises: [IOError, SerializationError, Defect].} =
let svalue = reader.readValue(string)
let res = Base10.decode(uint64, svalue)
if res.isOk():
let v = res.get()
if v < SYNC_COMMITTEE_SIZE:
value = ValidatorIndexInSyncCommittee(v)
else:
reader.raiseUnexpectedValue(
"Validator index is bigger then SYNC_COMMITTEE_SIZE")
else:
reader.raiseUnexpectedValue($res.error())
## RestValidatorIndex
proc writeValue*(writer: var JsonWriter[RestJson],
value: RestValidatorIndex) {.

View File

@ -105,7 +105,7 @@ type
RestSyncCommitteeDuty* = object
pubkey*: ValidatorPubKey
validator_index*: ValidatorIndex
validator_sync_committee_indices*: seq[SyncSubcommitteeIndex]
validator_sync_committee_indices*: seq[ValidatorIndexInSyncCommittee]
RestSyncCommitteeMessage* = object
slot*: Slot