Sync committee related REST API implementation. (#2856)
This commit is contained in:
parent
5670d58155
commit
0c635334a2
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[options, sequtils, tables, sets],
|
std/[options, sequtils, tables, sets],
|
||||||
stew/[assign2, byteutils],
|
stew/[assign2, byteutils, results],
|
||||||
metrics, snappy, chronicles,
|
metrics, snappy, chronicles,
|
||||||
../spec/[
|
../spec/[
|
||||||
beaconstate, eth2_merkleization, eth2_ssz_serialization, forks, helpers,
|
beaconstate, eth2_merkleization, eth2_ssz_serialization, forks, helpers,
|
||||||
|
@ -18,7 +18,7 @@ import
|
||||||
".."/beacon_chain_db,
|
".."/beacon_chain_db,
|
||||||
"."/[block_pools_types, block_quarantine, forkedbeaconstate_dbhelpers]
|
"."/[block_pools_types, block_quarantine, forkedbeaconstate_dbhelpers]
|
||||||
|
|
||||||
export block_pools_types
|
export block_pools_types, results
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-metrics/blob/master/metrics.md#interop-metrics
|
# https://github.com/ethereum/eth2.0-metrics/blob/master/metrics.md#interop-metrics
|
||||||
declareGauge beacon_head_root, "Root of the head block of the beacon chain"
|
declareGauge beacon_head_root, "Root of the head block of the beacon chain"
|
||||||
|
|
|
@ -97,6 +97,24 @@ 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] =
|
||||||
|
case forkedState.beaconStateFork
|
||||||
|
of BeaconStateFork.forkPhase0:
|
||||||
|
err("State's fork do not support sync committees")
|
||||||
|
of BeaconStateFork.forkAltair:
|
||||||
|
let
|
||||||
|
headSlot = forkedState.hbsAltair.data.slot
|
||||||
|
epochPeriod = syncCommitteePeriod(epoch.compute_start_slot_at_epoch())
|
||||||
|
currentPeriod = syncCommitteePeriod(headSlot)
|
||||||
|
nextPeriod = currentPeriod + 1'u64
|
||||||
|
if epochPeriod == currentPeriod:
|
||||||
|
ok(@(forkedState.hbsAltair.data.current_sync_committee.pubkeys.data))
|
||||||
|
elif epochPeriod == nextPeriod:
|
||||||
|
ok(@(forkedState.hbsAltair.data.next_sync_committee.pubkeys.data))
|
||||||
|
else:
|
||||||
|
err("Epoch is outside the sync committee period of the state")
|
||||||
|
|
||||||
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:
|
||||||
|
@ -468,7 +486,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
none[Slot]()
|
none[Slot]()
|
||||||
node.withStateForBlockSlot(bslot):
|
node.withStateForBlockSlot(bslot):
|
||||||
proc getCommittee(slot: Slot,
|
proc getCommittee(slot: Slot,
|
||||||
index: CommitteeIndex): RestBeaconStatesCommittees =
|
index: CommitteeIndex): RestBeaconStatesCommittees =
|
||||||
let validators = get_beacon_committee(stateData.data, slot, index,
|
let validators = get_beacon_committee(stateData.data, slot, index,
|
||||||
cache).mapIt(it)
|
cache).mapIt(it)
|
||||||
RestBeaconStatesCommittees(index: index, slot: slot,
|
RestBeaconStatesCommittees(index: index, slot: slot,
|
||||||
|
@ -504,6 +522,85 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochSyncCommittees
|
||||||
|
router.api(MethodGet,
|
||||||
|
"/api/eth/v1/beacon/states/{state_id}/sync_committees") do (
|
||||||
|
state_id: StateIdent, epoch: Option[Epoch]) -> RestApiResponse:
|
||||||
|
let bslot =
|
||||||
|
block:
|
||||||
|
if state_id.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
||||||
|
$state_id.error())
|
||||||
|
let bres = node.getBlockSlot(state_id.get())
|
||||||
|
if bres.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
||||||
|
$bres.error())
|
||||||
|
bres.get()
|
||||||
|
|
||||||
|
let qepoch =
|
||||||
|
if epoch.isSome():
|
||||||
|
let repoch = epoch.get()
|
||||||
|
if repoch.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
|
||||||
|
$repoch.error())
|
||||||
|
let res = repoch.get()
|
||||||
|
if res > MaxEpoch:
|
||||||
|
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
|
||||||
|
if res < node.dag.cfg.ALTAIR_FORK_EPOCH:
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
EpochFromTheIncorrectForkError)
|
||||||
|
res
|
||||||
|
else:
|
||||||
|
# If ``epoch`` not present then the sync committees for the epoch of
|
||||||
|
# the state will be obtained.
|
||||||
|
bslot.slot.epoch()
|
||||||
|
|
||||||
|
node.withStateForBlockSlot(bslot):
|
||||||
|
let keys =
|
||||||
|
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
|
||||||
|
|
||||||
|
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)))
|
||||||
|
res
|
||||||
|
|
||||||
|
if len(indices) != len(keys):
|
||||||
|
return RestApiResponse.jsonError(Http500, InternalServerError,
|
||||||
|
"Could not get validator indices")
|
||||||
|
let aggregates =
|
||||||
|
block:
|
||||||
|
var
|
||||||
|
res: seq[seq[ValidatorIndex]]
|
||||||
|
offset = 0
|
||||||
|
while true:
|
||||||
|
let length = min(SYNC_SUBCOMMITTEE_SIZE, len(indices) - offset)
|
||||||
|
if length == 0:
|
||||||
|
break
|
||||||
|
res.add(@(indices.toOpenArray(offset, offset + length - 1)))
|
||||||
|
offset.inc(length)
|
||||||
|
res
|
||||||
|
|
||||||
|
return RestApiResponse.jsonResponse(GetEpochSyncCommitteesResponse(
|
||||||
|
data: RestEpochSyncCommittee(validators: indices,
|
||||||
|
validator_aggregates: aggregates)
|
||||||
|
))
|
||||||
|
|
||||||
|
return RestApiResponse.jsonError(Http400, "Could not get requested state")
|
||||||
|
|
||||||
# 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 (
|
||||||
slot: Option[Slot], parent_root: Option[Eth2Digest]) -> RestApiResponse:
|
slot: Option[Slot], parent_root: Option[Eth2Digest]) -> RestApiResponse:
|
||||||
|
@ -893,6 +990,37 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
$res.error())
|
$res.error())
|
||||||
return RestApiResponse.jsonMsgResponse(ProposerSlashingValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(ProposerSlashingValidationSuccess)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolSyncCommitteeSignatures
|
||||||
|
router.api(MethodPost, "/api/eth/v1/beacon/pool/sync_committees") do (
|
||||||
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
|
let messages =
|
||||||
|
block:
|
||||||
|
if contentBody.isNone():
|
||||||
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
||||||
|
let dres = decodeBody(seq[SyncCommitteeMessage], contentBody.get())
|
||||||
|
if dres.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidSyncCommitteeSignatureMessageError)
|
||||||
|
dres.get()
|
||||||
|
|
||||||
|
let results = await node.sendSyncCommitteeMessages(messages)
|
||||||
|
|
||||||
|
let failures =
|
||||||
|
block:
|
||||||
|
var res: seq[RestAttestationsFailure]
|
||||||
|
for index, item in results.pairs():
|
||||||
|
if item.isErr():
|
||||||
|
res.add(RestAttestationsFailure(index: uint64(index),
|
||||||
|
message: $item.error()))
|
||||||
|
res
|
||||||
|
if len(failures) > 0:
|
||||||
|
return RestApiResponse.jsonErrorList(Http400,
|
||||||
|
SyncCommitteeMessageValidationError,
|
||||||
|
failures)
|
||||||
|
else:
|
||||||
|
return RestApiResponse.jsonMsgResponse(
|
||||||
|
SyncCommitteeMessageValidationSuccess)
|
||||||
|
|
||||||
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolVoluntaryExits
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolVoluntaryExits
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/voluntary_exits") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/pool/voluntary_exits") do (
|
||||||
) -> RestApiResponse:
|
) -> RestApiResponse:
|
||||||
|
@ -965,6 +1093,11 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
"/eth/v1/beacon/states/{state_id}/committees",
|
"/eth/v1/beacon/states/{state_id}/committees",
|
||||||
"/api/eth/v1/beacon/states/{state_id}/committees"
|
"/api/eth/v1/beacon/states/{state_id}/committees"
|
||||||
)
|
)
|
||||||
|
router.redirect(
|
||||||
|
MethodGet,
|
||||||
|
"/eth/v1/beacon/states/{state_id}/sync_committees",
|
||||||
|
"/api/eth/v1/beacon/states/{state_id}/sync_committees"
|
||||||
|
)
|
||||||
router.redirect(
|
router.redirect(
|
||||||
MethodGet,
|
MethodGet,
|
||||||
"/eth/v1/beacon/headers",
|
"/eth/v1/beacon/headers",
|
||||||
|
@ -1030,6 +1163,11 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
"/eth/v1/beacon/pool/proposer_slashings",
|
"/eth/v1/beacon/pool/proposer_slashings",
|
||||||
"/api/eth/v1/beacon/pool/proposer_slashings"
|
"/api/eth/v1/beacon/pool/proposer_slashings"
|
||||||
)
|
)
|
||||||
|
router.redirect(
|
||||||
|
MethodPost,
|
||||||
|
"/eth/v1/beacon/pool/sync_committees",
|
||||||
|
"/api/eth/v1/beacon/pool/sync_committees"
|
||||||
|
)
|
||||||
router.redirect(
|
router.redirect(
|
||||||
MethodPost,
|
MethodPost,
|
||||||
"/eth/v1/beacon/pool/voluntary_exits",
|
"/eth/v1/beacon/pool/voluntary_exits",
|
||||||
|
|
|
@ -58,7 +58,9 @@ const
|
||||||
AggregateAndProofValidationSuccess* =
|
AggregateAndProofValidationSuccess* =
|
||||||
"Aggregate and proof object(s) was broadcasted"
|
"Aggregate and proof object(s) was broadcasted"
|
||||||
BeaconCommitteeSubscriptionSuccess* =
|
BeaconCommitteeSubscriptionSuccess* =
|
||||||
"Beacon node processed committee subscription request"
|
"Beacon node processed committee subscription request(s)"
|
||||||
|
SyncCommitteeSubscriptionSuccess* =
|
||||||
|
"Beacon node processed sync committee subscription request(s)"
|
||||||
InvalidParentRootValueError* =
|
InvalidParentRootValueError* =
|
||||||
"Invalid parent root value"
|
"Invalid parent root value"
|
||||||
MissingSlotValueError* =
|
MissingSlotValueError* =
|
||||||
|
@ -119,6 +121,10 @@ const
|
||||||
"Requested slot not in next wall-slot epoch"
|
"Requested slot not in next wall-slot epoch"
|
||||||
SlotFromThePastError* =
|
SlotFromThePastError* =
|
||||||
"Requested slot from the past"
|
"Requested slot from the past"
|
||||||
|
SlotFromTheIncorrectForkError* =
|
||||||
|
"Requested slot is from incorrect fork"
|
||||||
|
EpochFromTheIncorrectForkError* =
|
||||||
|
"Requested epoch is from incorrect fork"
|
||||||
ProposerNotFoundError* =
|
ProposerNotFoundError* =
|
||||||
"Could not find proposer for the head and slot"
|
"Could not find proposer for the head and slot"
|
||||||
NoHeadForSlotError* =
|
NoHeadForSlotError* =
|
||||||
|
@ -139,6 +145,32 @@ const
|
||||||
"Could not find out accepted content type"
|
"Could not find out accepted content type"
|
||||||
InvalidAcceptError* =
|
InvalidAcceptError* =
|
||||||
"Incorrect accept response type"
|
"Incorrect accept response type"
|
||||||
|
MissingSubCommitteeIndexValueError* =
|
||||||
|
"Missing `subcommittee_index` value"
|
||||||
|
InvalidSubCommitteeIndexValueError* =
|
||||||
|
"Invalid `subcommittee_index` value"
|
||||||
|
MissingBeaconBlockRootValueError* =
|
||||||
|
"Missing `beacon_block_root` value"
|
||||||
|
InvalidBeaconBlockRootValueError* =
|
||||||
|
"Invalid `beacon_block_root` value"
|
||||||
|
EpochOutsideSyncCommitteePeriodError* =
|
||||||
|
"Epoch is outside the sync committee period of the state"
|
||||||
|
InvalidSyncCommitteeSignatureMessageError* =
|
||||||
|
"Unable to decode sync committee message(s)"
|
||||||
|
InvalidSyncCommitteeSubscriptionRequestError* =
|
||||||
|
"Unable to decode sync committee subscription request(s)"
|
||||||
|
InvalidContributionAndProofMessageError* =
|
||||||
|
"Unable to decode contribute and proof message(s)"
|
||||||
|
SyncCommitteeMessageValidationError* =
|
||||||
|
"Some errors happened while validating sync committee message(s)"
|
||||||
|
SyncCommitteeMessageValidationSuccess* =
|
||||||
|
"Sync committee message(s) was broadcasted"
|
||||||
|
ContributionAndProofValidationError* =
|
||||||
|
"Some errors happened while validating contribution and proof(s)"
|
||||||
|
ContributionAndProofValidationSuccess* =
|
||||||
|
"Contribution and proof(s) was broadcasted"
|
||||||
|
ProduceContributionError* =
|
||||||
|
"Unable to produce contribution using the passed parameters"
|
||||||
InternalServerError* =
|
InternalServerError* =
|
||||||
"Internal server error"
|
"Internal server error"
|
||||||
NoImplementationError* =
|
NoImplementationError* =
|
||||||
|
|
|
@ -3,19 +3,18 @@
|
||||||
# * 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
|
import std/[typetraits, strutils]
|
||||||
std/[typetraits, strutils],
|
import stew/[results, base10], chronicles, json_serialization,
|
||||||
stew/[results, base10],
|
json_serialization/std/[options, net],
|
||||||
chronicles,
|
nimcrypto/utils as ncrutils
|
||||||
json_serialization, json_serialization/std/[options, net],
|
import ".."/[beacon_chain_db, beacon_node_common],
|
||||||
nimcrypto/utils as ncrutils,
|
".."/networking/eth2_network,
|
||||||
".."/[beacon_chain_db, beacon_node_common],
|
".."/consensus_object_pools/[blockchain_dag, spec_cache,
|
||||||
../networking/eth2_network,
|
attestation_pool, sync_committee_msg_pool],
|
||||||
../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool],
|
".."/validators/validator_duties,
|
||||||
../validators/validator_duties,
|
".."/spec/[forks, network],
|
||||||
../spec/[forks, network],
|
".."/spec/datatypes/[phase0, altair],
|
||||||
../spec/datatypes/[phase0, altair],
|
"."/rest_utils
|
||||||
./rest_utils
|
|
||||||
|
|
||||||
logScope: topics = "rest_validatorapi"
|
logScope: topics = "rest_validatorapi"
|
||||||
|
|
||||||
|
@ -383,7 +382,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
block:
|
block:
|
||||||
let idx = request.validator_index
|
let idx = request.validator_index
|
||||||
if uint64(idx) >=
|
if uint64(idx) >=
|
||||||
lenu64(getStateField(node.dag.headState.data, validators)):
|
lenu64(getStateField(node.dag.headState.data, validators)):
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
InvalidValidatorIndexValueError)
|
InvalidValidatorIndexValueError)
|
||||||
getStateField(node.dag.headState.data, validators)[idx].pubkey
|
getStateField(node.dag.headState.data, validators)[idx].pubkey
|
||||||
|
@ -410,6 +409,146 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
warn "Beacon committee subscription request served, but not implemented"
|
warn "Beacon committee subscription request served, but not implemented"
|
||||||
return RestApiResponse.jsonMsgResponse(BeaconCommitteeSubscriptionSuccess)
|
return RestApiResponse.jsonMsgResponse(BeaconCommitteeSubscriptionSuccess)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Validator/prepareSyncCommitteeSubnets
|
||||||
|
router.api(MethodPost,
|
||||||
|
"/api/eth/v1/validator/sync_committee_subscriptions") do (
|
||||||
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
|
let subscriptions =
|
||||||
|
block:
|
||||||
|
if contentBody.isNone():
|
||||||
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
||||||
|
let dres = decodeBody(seq[RestSyncCommitteeSubscription],
|
||||||
|
contentBody.get())
|
||||||
|
if dres.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidSyncCommitteeSubscriptionRequestError)
|
||||||
|
let subs = dres.get()
|
||||||
|
for item in subs:
|
||||||
|
if item.until_epoch > MaxEpoch:
|
||||||
|
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
|
||||||
|
if item.until_epoch < node.dag.cfg.ALTAIR_FORK_EPOCH:
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
EpochFromTheIncorrectForkError)
|
||||||
|
if uint64(item.validator_index) >=
|
||||||
|
lenu64(getStateField(node.dag.headState.data, validators)):
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidValidatorIndexValueError)
|
||||||
|
subs
|
||||||
|
|
||||||
|
warn "Sync committee subscription request served, but not implemented"
|
||||||
|
return RestApiResponse.jsonMsgResponse(SyncCommitteeSubscriptionSuccess)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Validator/produceSyncCommitteeContribution
|
||||||
|
router.api(MethodGet,
|
||||||
|
"/api/eth/v1/validator/sync_committee_contribution") do (
|
||||||
|
slot: Option[Slot], subcommittee_index: Option[uint64],
|
||||||
|
beacon_block_root: Option[Eth2Digest]) -> RestApiResponse:
|
||||||
|
# We doing this check to avoid any confusion in future.
|
||||||
|
static: doAssert(SYNC_COMMITTEE_SUBNET_COUNT <= high(uint8))
|
||||||
|
let qslot =
|
||||||
|
if slot.isNone():
|
||||||
|
return RestApiResponse.jsonError(Http400, MissingSlotValueError)
|
||||||
|
else:
|
||||||
|
let res = slot.get()
|
||||||
|
if res.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
||||||
|
$res.error())
|
||||||
|
let rslot = res.get()
|
||||||
|
if epoch(rslot) < node.dag.cfg.ALTAIR_FORK_EPOCH:
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
SlotFromTheIncorrectForkError)
|
||||||
|
rslot
|
||||||
|
let qindex =
|
||||||
|
if subcommittee_index.isNone():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
MissingSubCommitteeIndexValueError)
|
||||||
|
else:
|
||||||
|
let res = subcommittee_index.get()
|
||||||
|
if res.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidSubCommitteeIndexValueError,
|
||||||
|
$res.error())
|
||||||
|
let value = res.get()
|
||||||
|
if value >= SYNC_COMMITTEE_SUBNET_COUNT:
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidSubCommitteeIndexValueError,
|
||||||
|
"subcommittee_index exceeds " &
|
||||||
|
"maximum allowed value")
|
||||||
|
value
|
||||||
|
let qroot =
|
||||||
|
if beacon_block_root.isNone():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
MissingBeaconBlockRootValueError)
|
||||||
|
else:
|
||||||
|
let res = beacon_block_root.get()
|
||||||
|
if res.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidBeaconBlockRootValueError,
|
||||||
|
$res.error())
|
||||||
|
res.get()
|
||||||
|
|
||||||
|
# Check if node is fully synced.
|
||||||
|
let sres = node.getCurrentHead(qslot)
|
||||||
|
if sres.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
||||||
|
|
||||||
|
var contribution = SyncCommitteeContribution()
|
||||||
|
let res = node.syncCommitteeMsgPool[].produceContribution(
|
||||||
|
qslot, qroot, SyncCommitteeIndex(qindex), contribution)
|
||||||
|
if not(res):
|
||||||
|
return RestApiResponse.jsonError(Http400, ProduceContributionError)
|
||||||
|
return RestApiResponse.jsonResponse(contribution)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Validator/publishContributionAndProofs
|
||||||
|
router.api(MethodPost,
|
||||||
|
"/api/eth/v1/validator/contribution_and_proofs") do (
|
||||||
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
|
let proofs =
|
||||||
|
block:
|
||||||
|
if contentBody.isNone():
|
||||||
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
||||||
|
let dres = decodeBody(seq[SignedContributionAndProof],
|
||||||
|
contentBody.get())
|
||||||
|
if dres.isErr():
|
||||||
|
return RestApiResponse.jsonError(Http400,
|
||||||
|
InvalidContributionAndProofMessageError)
|
||||||
|
dres.get()
|
||||||
|
|
||||||
|
let pending =
|
||||||
|
block:
|
||||||
|
var res: seq[Future[SendResult]]
|
||||||
|
for proof in proofs:
|
||||||
|
res.add(node.sendSyncCommitteeContribution(proof, true))
|
||||||
|
res
|
||||||
|
|
||||||
|
let failures =
|
||||||
|
block:
|
||||||
|
var res: seq[RestAttestationsFailure]
|
||||||
|
await allFutures(pending)
|
||||||
|
for index, future in pending.pairs():
|
||||||
|
if future.done():
|
||||||
|
let fres = future.read()
|
||||||
|
if fres.isErr():
|
||||||
|
let failure = RestAttestationsFailure(index: uint64(index),
|
||||||
|
message: $fres.error())
|
||||||
|
res.add(failure)
|
||||||
|
elif future.failed() or future.cancelled():
|
||||||
|
# This is unexpected failure, so we log the error message.
|
||||||
|
let exc = future.readError()
|
||||||
|
let failure = RestAttestationsFailure(index: uint64(index),
|
||||||
|
message: $exc.msg)
|
||||||
|
res.add(failure)
|
||||||
|
res
|
||||||
|
|
||||||
|
if len(failures) > 0:
|
||||||
|
return RestApiResponse.jsonErrorList(Http400,
|
||||||
|
ContributionAndProofValidationError,
|
||||||
|
failures)
|
||||||
|
else:
|
||||||
|
return RestApiResponse.jsonMsgResponse(
|
||||||
|
ContributionAndProofValidationSuccess
|
||||||
|
)
|
||||||
|
|
||||||
router.redirect(
|
router.redirect(
|
||||||
MethodPost,
|
MethodPost,
|
||||||
"/eth/v1/validator/duties/attester/{epoch}",
|
"/eth/v1/validator/duties/attester/{epoch}",
|
||||||
|
@ -450,3 +589,18 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
"/eth/v1/validator/beacon_committee_subscriptions",
|
"/eth/v1/validator/beacon_committee_subscriptions",
|
||||||
"/api/eth/v1/validator/beacon_committee_subscriptions"
|
"/api/eth/v1/validator/beacon_committee_subscriptions"
|
||||||
)
|
)
|
||||||
|
router.redirect(
|
||||||
|
MethodPost,
|
||||||
|
"/eth/v1/validator/sync_committee_subscriptions",
|
||||||
|
"/api/eth/v1/validator/sync_committee_subscriptions"
|
||||||
|
)
|
||||||
|
router.redirect(
|
||||||
|
MethodGet,
|
||||||
|
"/eth/v1/validator/sync_committee_contribution",
|
||||||
|
"/api/eth/v1/validator/sync_committee_contribution"
|
||||||
|
)
|
||||||
|
router.redirect(
|
||||||
|
MethodPost,
|
||||||
|
"/eth/v1/validator/contribution_and_proofs",
|
||||||
|
"/api/eth/v1/validator/contribution_and_proofs"
|
||||||
|
)
|
||||||
|
|
|
@ -803,6 +803,22 @@ template toSszType*(v: BeaconStateFork): auto =
|
||||||
of BeaconStateFork.forkPhase0: Phase0Version
|
of BeaconStateFork.forkPhase0: Phase0Version
|
||||||
of BeaconStateFork.forkAltair: AltairVersion
|
of BeaconStateFork.forkAltair: AltairVersion
|
||||||
|
|
||||||
|
# SyncCommitteeIndex
|
||||||
|
proc writeValue*(writer: var JsonWriter[RestJson],
|
||||||
|
value: SyncCommitteeIndex) {.
|
||||||
|
raises: [IOError, Defect].} =
|
||||||
|
writeValue(writer, Base10.toString(uint8(value)))
|
||||||
|
|
||||||
|
proc readValue*(reader: var JsonReader[RestJson],
|
||||||
|
value: var SyncCommitteeIndex) {.
|
||||||
|
raises: [IOError, SerializationError, Defect].} =
|
||||||
|
let res = Base10.decode(uint8, reader.readValue(string))
|
||||||
|
if res.isOk():
|
||||||
|
# TODO (cheatfate): Here should be present check for maximum value.
|
||||||
|
value = SyncCommitteeIndex(res.get())
|
||||||
|
else:
|
||||||
|
reader.raiseUnexpectedValue($res.error())
|
||||||
|
|
||||||
proc parseRoot(value: string): Result[Eth2Digest, cstring] =
|
proc parseRoot(value: string): Result[Eth2Digest, cstring] =
|
||||||
try:
|
try:
|
||||||
ok(Eth2Digest(data: hexToByteArray[32](value)))
|
ok(Eth2Digest(data: hexToByteArray[32](value)))
|
||||||
|
@ -1030,6 +1046,10 @@ proc decodeString*(t: typedesc[Epoch], value: string): Result[Epoch, cstring] =
|
||||||
let res = ? Base10.decode(uint64, value)
|
let res = ? Base10.decode(uint64, value)
|
||||||
ok(Epoch(res))
|
ok(Epoch(res))
|
||||||
|
|
||||||
|
proc decodeString*(t: typedesc[uint64],
|
||||||
|
value: string): Result[uint64, cstring] =
|
||||||
|
Base10.decode(uint64, value)
|
||||||
|
|
||||||
proc decodeString*(t: typedesc[StateIdent],
|
proc decodeString*(t: typedesc[StateIdent],
|
||||||
value: string): Result[StateIdent, cstring] =
|
value: string): Result[StateIdent, cstring] =
|
||||||
if len(value) > 2:
|
if len(value) > 2:
|
||||||
|
|
|
@ -108,6 +108,11 @@ type
|
||||||
slot*: Slot
|
slot*: Slot
|
||||||
is_aggregator*: bool
|
is_aggregator*: bool
|
||||||
|
|
||||||
|
RestSyncCommitteeSubscription* = object
|
||||||
|
validator_index*: ValidatorIndex
|
||||||
|
sync_committee_indices*: seq[SyncCommitteeIndex]
|
||||||
|
until_epoch*: Epoch
|
||||||
|
|
||||||
RestBeaconStatesFinalityCheckpoints* = object
|
RestBeaconStatesFinalityCheckpoints* = object
|
||||||
previous_justified*: Checkpoint
|
previous_justified*: Checkpoint
|
||||||
current_justified*: Checkpoint
|
current_justified*: Checkpoint
|
||||||
|
@ -261,6 +266,10 @@ type
|
||||||
slot*: Slot
|
slot*: Slot
|
||||||
blck* {.serializedFieldName: "block".}: Eth2Digest
|
blck* {.serializedFieldName: "block".}: Eth2Digest
|
||||||
|
|
||||||
|
RestEpochSyncCommittee* = object
|
||||||
|
validators*: seq[ValidatorIndex]
|
||||||
|
validator_aggregates*: seq[seq[ValidatorIndex]]
|
||||||
|
|
||||||
DataEnclosedObject*[T] = object
|
DataEnclosedObject*[T] = object
|
||||||
data*: T
|
data*: T
|
||||||
|
|
||||||
|
@ -305,6 +314,7 @@ type
|
||||||
GetStateValidatorsResponse* = DataEnclosedObject[seq[RestValidator]]
|
GetStateValidatorsResponse* = DataEnclosedObject[seq[RestValidator]]
|
||||||
GetSyncingStatusResponse* = DataEnclosedObject[RestSyncInfo]
|
GetSyncingStatusResponse* = DataEnclosedObject[RestSyncInfo]
|
||||||
GetVersionResponse* = DataEnclosedObject[RestNodeVersion]
|
GetVersionResponse* = DataEnclosedObject[RestNodeVersion]
|
||||||
|
GetEpochSyncCommitteesResponse* = DataEnclosedObject[RestEpochSyncCommittee]
|
||||||
ProduceAttestationDataResponse* = DataEnclosedObject[AttestationData]
|
ProduceAttestationDataResponse* = DataEnclosedObject[AttestationData]
|
||||||
ProduceBlockResponse* = DataEnclosedObject[phase0.BeaconBlock]
|
ProduceBlockResponse* = DataEnclosedObject[phase0.BeaconBlock]
|
||||||
ProduceBlockResponseV2* = ForkedBeaconBlock
|
ProduceBlockResponseV2* = ForkedBeaconBlock
|
||||||
|
|
|
@ -200,28 +200,118 @@ proc sendAttestation*(
|
||||||
|
|
||||||
proc sendSyncCommitteeMessage*(
|
proc sendSyncCommitteeMessage*(
|
||||||
node: BeaconNode, msg: SyncCommitteeMessage,
|
node: BeaconNode, msg: SyncCommitteeMessage,
|
||||||
committeeIdx: SyncCommitteeIndex, checkSignature: bool): Future[bool] {.async.} =
|
committeeIdx: SyncCommitteeIndex,
|
||||||
|
checkSignature: bool): Future[SendResult] {.async.} =
|
||||||
# Validate sync committee message before sending it via gossip
|
# Validate sync committee message before sending it via gossip
|
||||||
# validation will also register the message with the sync committee
|
# validation will also register the message with the sync committee
|
||||||
# message pool. Notably, although libp2p calls the data handler for
|
# message pool. Notably, although libp2p calls the data handler for
|
||||||
# any subscription on the subnet topic, it does not perform validation.
|
# any subscription on the subnet topic, it does not perform validation.
|
||||||
let ok = node.processor.syncCommitteeMsgValidator(
|
let res = node.processor.syncCommitteeMsgValidator(msg, committeeIdx,
|
||||||
msg, committeeIdx, checkSignature)
|
checkSignature)
|
||||||
|
return
|
||||||
return case ok
|
case res
|
||||||
of ValidationResult.Accept:
|
of ValidationResult.Accept:
|
||||||
node.network.broadcastSyncCommitteeMessage(msg, committeeIdx)
|
node.network.broadcastSyncCommitteeMessage(msg, committeeIdx)
|
||||||
beacon_sync_committee_messages_sent.inc()
|
beacon_sync_committee_messages_sent.inc()
|
||||||
true
|
SendResult.ok()
|
||||||
else:
|
else:
|
||||||
notice "Produced sync committee message failed validation",
|
notice "Sync committee message failed validation",
|
||||||
msg, result = $ok
|
msg, result = $res
|
||||||
false
|
SendResult.err("Sync committee message failed validation")
|
||||||
|
|
||||||
|
proc sendSyncCommitteeMessages*(node: BeaconNode,
|
||||||
|
msgs: seq[SyncCommitteeMessage]
|
||||||
|
): Future[seq[SendResult]] {.async.} =
|
||||||
|
let validators = getStateField(node.dag.headState.data, validators)
|
||||||
|
var statuses = newSeq[Option[SendResult]](len(msgs))
|
||||||
|
|
||||||
|
let ranges =
|
||||||
|
block:
|
||||||
|
let
|
||||||
|
headSlot = getStateField(node.dag.headState.data, slot)
|
||||||
|
headCommitteePeriod = syncCommitteePeriod(headSlot)
|
||||||
|
currentStart = syncCommitteePeriodStartSlot(headCommitteePeriod)
|
||||||
|
currentFinish = currentStart + SLOTS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
nextStart = currentFinish
|
||||||
|
nextFinish = nextStart + SLOTS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
(curStart: Slot(currentStart), curFinish: Slot(currentFinish),
|
||||||
|
nxtStart: Slot(nextStart), nxtFinish: Slot(nextFinish))
|
||||||
|
|
||||||
|
let (keysCur, keysNxt) =
|
||||||
|
block:
|
||||||
|
var resCur: Table[ValidatorPubKey, int]
|
||||||
|
var resNxt: Table[ValidatorPubKey, int]
|
||||||
|
for index, msg in msgs.pairs():
|
||||||
|
if msg.validator_index < lenu64(validators):
|
||||||
|
if (msg.slot >= ranges.curStart) and (msg.slot < ranges.curFinish):
|
||||||
|
resCur[validators[msg.validator_index].pubkey] = index
|
||||||
|
elif (msg.slot >= ranges.nxtStart) and (msg.slot < ranges.nxtFinish):
|
||||||
|
resNxt[validators[msg.validator_index].pubkey] = index
|
||||||
|
else:
|
||||||
|
statuses[index] =
|
||||||
|
some(SendResult.err("Message's slot out of state's head range"))
|
||||||
|
else:
|
||||||
|
statuses[index] = some(SendResult.err("Incorrect validator's index"))
|
||||||
|
if (len(resCur) == 0) and (len(resNxt) == 0):
|
||||||
|
return statuses.mapIt(it.get())
|
||||||
|
(resCur, resNxt)
|
||||||
|
|
||||||
|
template curParticipants(): untyped =
|
||||||
|
node.dag.headState.data.hbsAltair.data.current_sync_committee.pubkeys.data
|
||||||
|
template nxtParticipants(): untyped =
|
||||||
|
node.dag.headState.data.hbsAltair.data.next_sync_committee.pubkeys.data
|
||||||
|
|
||||||
|
let (pending, indices) =
|
||||||
|
block:
|
||||||
|
var resFutures: seq[Future[SendResult]]
|
||||||
|
var resIndices: seq[int]
|
||||||
|
for committeeIdx in allSyncCommittees():
|
||||||
|
for valKey in syncSubcommittee(curParticipants(), committeeIdx):
|
||||||
|
let index = keysCur.getOrDefault(valKey, -1)
|
||||||
|
if index >= 0:
|
||||||
|
resIndices.add(index)
|
||||||
|
resFutures.add(node.sendSyncCommitteeMessage(msgs[index],
|
||||||
|
committeeIdx, true))
|
||||||
|
for committeeIdx in allSyncCommittees():
|
||||||
|
for valKey in syncSubcommittee(nxtParticipants(), committeeIdx):
|
||||||
|
let index = keysNxt.getOrDefault(valKey, -1)
|
||||||
|
if index >= 0:
|
||||||
|
resIndices.add(index)
|
||||||
|
resFutures.add(node.sendSyncCommitteeMessage(msgs[index],
|
||||||
|
committeeIdx, true))
|
||||||
|
(resFutures, resIndices)
|
||||||
|
|
||||||
|
await allFutures(pending)
|
||||||
|
|
||||||
|
for index, future in pending.pairs():
|
||||||
|
if future.done():
|
||||||
|
let fres = future.read()
|
||||||
|
if fres.isErr():
|
||||||
|
statuses[indices[index]] = some(SendResult.err(fres.error()))
|
||||||
|
else:
|
||||||
|
statuses[indices[index]] = some(SendResult.ok())
|
||||||
|
elif future.failed() or future.cancelled():
|
||||||
|
let exc = future.readError()
|
||||||
|
debug "Unexpected failure while sending committee message",
|
||||||
|
message = msgs[indices[index]], error = $exc.msg
|
||||||
|
statuses[indices[index]] = some(SendResult.err(
|
||||||
|
"Unexpected failure while sending committee message"))
|
||||||
|
|
||||||
|
let results =
|
||||||
|
block:
|
||||||
|
var res: seq[SendResult]
|
||||||
|
for item in statuses:
|
||||||
|
if item.isSome():
|
||||||
|
res.add(item.get())
|
||||||
|
else:
|
||||||
|
res.add(SendResult.err("Message validator not in sync committee"))
|
||||||
|
res
|
||||||
|
return results
|
||||||
|
|
||||||
proc sendSyncCommitteeContribution*(
|
proc sendSyncCommitteeContribution*(
|
||||||
node: BeaconNode,
|
node: BeaconNode,
|
||||||
msg: SignedContributionAndProof,
|
msg: SignedContributionAndProof,
|
||||||
checkSignature: bool): Future[bool] {.async.} =
|
checkSignature: bool): Future[SendResult] {.async.} =
|
||||||
let ok = node.processor.syncCommitteeContributionValidator(
|
let ok = node.processor.syncCommitteeContributionValidator(
|
||||||
msg, checkSignature)
|
msg, checkSignature)
|
||||||
|
|
||||||
|
@ -229,11 +319,11 @@ proc sendSyncCommitteeContribution*(
|
||||||
of ValidationResult.Accept:
|
of ValidationResult.Accept:
|
||||||
node.network.broadcastSignedContributionAndProof(msg)
|
node.network.broadcastSignedContributionAndProof(msg)
|
||||||
beacon_sync_committee_contributions_sent.inc()
|
beacon_sync_committee_contributions_sent.inc()
|
||||||
true
|
SendResult.ok()
|
||||||
else:
|
else:
|
||||||
notice "Produced sync committee contribution failed validation",
|
notice "Sync committee contribution failed validation",
|
||||||
msg, result = $ok
|
msg, result = $ok
|
||||||
false
|
SendResult.err("Sync committee contribution failed validation")
|
||||||
|
|
||||||
proc createAndSendAttestation(node: BeaconNode,
|
proc createAndSendAttestation(node: BeaconNode,
|
||||||
fork: Fork,
|
fork: Fork,
|
||||||
|
@ -598,9 +688,10 @@ proc createAndSendSyncCommitteeMessage(node: BeaconNode,
|
||||||
msg = await signSyncCommitteeMessage(validator, slot, fork,
|
msg = await signSyncCommitteeMessage(validator, slot, fork,
|
||||||
genesisValidatorsRoot, head.root)
|
genesisValidatorsRoot, head.root)
|
||||||
|
|
||||||
let ok = await node.sendSyncCommitteeMessage(
|
let res = await node.sendSyncCommitteeMessage(
|
||||||
msg, committeeIdx, checkSignature = false)
|
msg, committeeIdx, checkSignature = false)
|
||||||
if not ok: # Logged in sendSyncCommitteeMessage
|
if res.isErr():
|
||||||
|
# Logged in sendSyncCommitteeMessage
|
||||||
return
|
return
|
||||||
|
|
||||||
if node.config.dumpEnabled:
|
if node.config.dumpEnabled:
|
||||||
|
@ -635,7 +726,7 @@ proc handleSyncCommitteeMessages(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||||
for committeeIdx in allSyncCommittees():
|
for committeeIdx in allSyncCommittees():
|
||||||
for valKey in syncSubcommittee(syncCommittee, committeeIdx):
|
for valKey in syncSubcommittee(syncCommittee, committeeIdx):
|
||||||
let validator = node.getAttachedValidator(valKey)
|
let validator = node.getAttachedValidator(valKey)
|
||||||
if validator == nil or not validator.index.isSome():
|
if isNil(validator) or validator.index.isNone():
|
||||||
continue
|
continue
|
||||||
asyncSpawn createAndSendSyncCommitteeMessage(node, slot, validator,
|
asyncSpawn createAndSendSyncCommitteeMessage(node, slot, validator,
|
||||||
committeeIdx, head)
|
committeeIdx, head)
|
||||||
|
|
Loading…
Reference in New Issue