Number of REST fixes for Altair. (#2790)
* Fix getForkSchedule call. Create cache of all configuration endpoints at node startup. Add prepareJsonResponse() call to create cached responses. Mark all procedures with `raises`. * Add getForkSchedule to VC. Fix getForkSchedule return type for API. More `raises` annotations. Fix VC fork_service.nim. * Use `push raises` instead of inline `raises`. * Improvements for REST API aggregated attestations and attestations processing. * Rename eth2_network.sendXXX procedures to eth2_network.broadcastXXX. Add broadcastBeaconBlock() and broadcastAggregateAndProof(). Fix links to specification in REST API declarations. Add implementation for v2 getStateV2(). Add validator_duties.sendXXX procedures which not only broadcast data, but also validate it. Fix JSON-RPC/REST to use new validator_duties.sendXXX procedures instead of own implementations. * Fix validator_client online nodes count incorrect value. Fix aggregate and proof attestation could be sent too late. * Adding timeout for block wait in attestations processing. Fix compilation errors. * Attempt to debug aggregate and proofs. * Fix Beacon AIP to use `sendAttestation`. Add link comment to produceBlockV2. * Add debug logs before publish operation for blocks, attestations and aggregated attestations. Fix attestations publishing issue. * logging fixes `indexInCommnittee` already logged in attestation Co-authored-by: Jacek Sieka <jacek@status.im>
This commit is contained in:
parent
0a61d1112e
commit
66cb18d69b
|
@ -2061,8 +2061,8 @@ func forkDigestAtEpoch(node: Eth2Node, epoch: Epoch): ForkDigest =
|
||||||
proc getWallEpoch(node: Eth2Node): Epoch =
|
proc getWallEpoch(node: Eth2Node): Epoch =
|
||||||
node.getBeaconTime().slotOrZero.epoch
|
node.getBeaconTime().slotOrZero.epoch
|
||||||
|
|
||||||
proc sendAttestation*(
|
proc broadcastAttestation*(node: Eth2Node, subnet_id: SubnetId,
|
||||||
node: Eth2Node, subnet_id: SubnetId, attestation: Attestation) =
|
attestation: Attestation) =
|
||||||
# Regardless of the contents of the attestation,
|
# Regardless of the contents of the attestation,
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.2/specs/altair/p2p-interface.md#transitioning-the-gossip
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.2/specs/altair/p2p-interface.md#transitioning-the-gossip
|
||||||
# implies that pre-fork, messages using post-fork digests might be
|
# implies that pre-fork, messages using post-fork digests might be
|
||||||
|
@ -2070,21 +2070,35 @@ proc sendAttestation*(
|
||||||
# timer unsubscription point that means no new pre-fork-forkdigest
|
# timer unsubscription point that means no new pre-fork-forkdigest
|
||||||
# should be sent.
|
# should be sent.
|
||||||
let forkPrefix = node.forkDigestAtEpoch(node.getWallEpoch)
|
let forkPrefix = node.forkDigestAtEpoch(node.getWallEpoch)
|
||||||
node.broadcast(
|
let topic = getAttestationTopic(forkPrefix, subnet_id)
|
||||||
getAttestationTopic(forkPrefix, subnet_id),
|
node.broadcast(topic, attestation)
|
||||||
attestation)
|
|
||||||
|
|
||||||
proc sendVoluntaryExit*(node: Eth2Node, exit: SignedVoluntaryExit) =
|
proc broadcastVoluntaryExit*(node: Eth2Node, exit: SignedVoluntaryExit) =
|
||||||
let exitsTopic = getVoluntaryExitsTopic(
|
let exitsTopic = getVoluntaryExitsTopic(
|
||||||
node.forkDigestAtEpoch(node.getWallEpoch))
|
node.forkDigestAtEpoch(node.getWallEpoch))
|
||||||
node.broadcast(exitsTopic, exit)
|
node.broadcast(exitsTopic, exit)
|
||||||
|
|
||||||
proc sendAttesterSlashing*(node: Eth2Node, slashing: AttesterSlashing) =
|
proc broadcastAttesterSlashing*(node: Eth2Node, slashing: AttesterSlashing) =
|
||||||
let attesterSlashingsTopic = getAttesterSlashingsTopic(
|
let attesterSlashingsTopic = getAttesterSlashingsTopic(
|
||||||
node.forkDigestAtEpoch(node.getWallEpoch))
|
node.forkDigestAtEpoch(node.getWallEpoch))
|
||||||
node.broadcast(attesterSlashingsTopic, slashing)
|
node.broadcast(attesterSlashingsTopic, slashing)
|
||||||
|
|
||||||
proc sendProposerSlashing*(node: Eth2Node, slashing: ProposerSlashing) =
|
proc broadcastProposerSlashing*(node: Eth2Node, slashing: ProposerSlashing) =
|
||||||
let proposerSlashingsTopic = getProposerSlashingsTopic(
|
let proposerSlashingsTopic = getProposerSlashingsTopic(
|
||||||
node.forkDigestAtEpoch(node.getWallEpoch))
|
node.forkDigestAtEpoch(node.getWallEpoch))
|
||||||
node.broadcast(proposerSlashingsTopic, slashing)
|
node.broadcast(proposerSlashingsTopic, slashing)
|
||||||
|
|
||||||
|
proc broadcastAggregateAndProof*(node: Eth2Node,
|
||||||
|
proof: SignedAggregateAndProof) =
|
||||||
|
let proofTopic = getAggregateAndProofsTopic(
|
||||||
|
node.forkDigestAtEpoch(node.getWallEpoch))
|
||||||
|
node.broadcast(proofTopic, proof)
|
||||||
|
|
||||||
|
proc broadcastBeaconBlock*(node: Eth2Node, forked: ForkedSignedBeaconBlock) =
|
||||||
|
case forked.kind
|
||||||
|
of BeaconBlockFork.Phase0:
|
||||||
|
let topic = getBeaconBlocksTopic(node.forkDigests.phase0)
|
||||||
|
node.broadcast(topic, forked.phase0Block)
|
||||||
|
of BeaconBlockFork.Altair:
|
||||||
|
let topic = getBeaconBlocksTopic(node.forkDigests.altair)
|
||||||
|
node.broadcast(topic, forked.altairBlock)
|
||||||
|
|
|
@ -9,8 +9,7 @@ import
|
||||||
chronicles,
|
chronicles,
|
||||||
nimcrypto/utils as ncrutils,
|
nimcrypto/utils as ncrutils,
|
||||||
../beacon_node_common, ../networking/eth2_network,
|
../beacon_node_common, ../networking/eth2_network,
|
||||||
../consensus_object_pools/[blockchain_dag, exit_pool],
|
../consensus_object_pools/[blockchain_dag, exit_pool, spec_cache],
|
||||||
../gossip_processing/gossip_validation,
|
|
||||||
../validators/validator_duties,
|
../validators/validator_duties,
|
||||||
../spec/[eth2_merkleization, forks, network],
|
../spec/[eth2_merkleization, forks, network],
|
||||||
../spec/datatypes/[phase0, altair],
|
../spec/datatypes/[phase0, altair],
|
||||||
|
@ -105,7 +104,7 @@ proc getBeaconBlocksTopic(node: BeaconNode, kind: BeaconBlockFork): string =
|
||||||
getBeaconBlocksTopic(node.dag.forkDigests.altair)
|
getBeaconBlocksTopic(node.dag.forkDigests.altair)
|
||||||
|
|
||||||
proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
# https://ethereum.github.io/eth2.0-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:
|
||||||
return RestApiResponse.jsonResponse(
|
return RestApiResponse.jsonResponse(
|
||||||
(
|
(
|
||||||
|
@ -116,7 +115,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateRoot
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateRoot
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/root") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/states/{state_id}/root") do (
|
||||||
state_id: StateIdent) -> RestApiResponse:
|
state_id: StateIdent) -> RestApiResponse:
|
||||||
let bslot =
|
let bslot =
|
||||||
|
@ -133,7 +132,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
return RestApiResponse.jsonResponse((root: stateRoot))
|
return RestApiResponse.jsonResponse((root: stateRoot))
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-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 (
|
||||||
state_id: StateIdent) -> RestApiResponse:
|
state_id: StateIdent) -> RestApiResponse:
|
||||||
let bslot =
|
let bslot =
|
||||||
|
@ -159,7 +158,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
)
|
)
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateFinalityCheckpoints
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/beacon/states/{state_id}/finality_checkpoints") do (
|
"/api/eth/v1/beacon/states/{state_id}/finality_checkpoints") do (
|
||||||
state_id: StateIdent) -> RestApiResponse:
|
state_id: StateIdent) -> RestApiResponse:
|
||||||
|
@ -185,7 +184,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
)
|
)
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-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 (
|
||||||
state_id: StateIdent, id: seq[ValidatorIdent],
|
state_id: StateIdent, id: seq[ValidatorIdent],
|
||||||
status: seq[ValidatorFilter]) -> RestApiResponse:
|
status: seq[ValidatorFilter]) -> RestApiResponse:
|
||||||
|
@ -276,7 +275,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidator
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}") do (
|
"/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}") do (
|
||||||
state_id: StateIdent, validator_id: ValidatorIdent) -> RestApiResponse:
|
state_id: StateIdent, validator_id: ValidatorIdent) -> RestApiResponse:
|
||||||
|
@ -353,7 +352,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
ValidatorStatusNotFoundError)
|
ValidatorStatusNotFoundError)
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getStateValidatorBalances
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidatorBalances
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/beacon/states/{state_id}/validator_balances") do (
|
"/api/eth/v1/beacon/states/{state_id}/validator_balances") do (
|
||||||
state_id: StateIdent, id: seq[ValidatorIdent]) -> RestApiResponse:
|
state_id: StateIdent, id: seq[ValidatorIdent]) -> RestApiResponse:
|
||||||
|
@ -427,7 +426,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getEpochCommittees
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/beacon/states/{state_id}/committees") do (
|
"/api/eth/v1/beacon/states/{state_id}/committees") do (
|
||||||
state_id: StateIdent, epoch: Option[Epoch], index: Option[CommitteeIndex],
|
state_id: StateIdent, epoch: Option[Epoch], index: Option[CommitteeIndex],
|
||||||
|
@ -511,7 +510,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-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:
|
||||||
# TODO (cheatfate): This call is incomplete, because structure
|
# TODO (cheatfate): This call is incomplete, because structure
|
||||||
|
@ -568,7 +567,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockHeader
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/headers/{block_id}") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/headers/{block_id}") do (
|
||||||
block_id: BlockIdent) -> RestApiResponse:
|
block_id: BlockIdent) -> RestApiResponse:
|
||||||
let bdata =
|
let bdata =
|
||||||
|
@ -600,10 +599,10 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/publishBlock
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlock
|
||||||
router.api(MethodPost, "/api/eth/v1/beacon/blocks") do (
|
router.api(MethodPost, "/api/eth/v1/beacon/blocks") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let blockData =
|
let forked =
|
||||||
block:
|
block:
|
||||||
if contentBody.isNone():
|
if contentBody.isNone():
|
||||||
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
||||||
|
@ -641,37 +640,14 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
|
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
|
||||||
$phase0res.error())
|
$phase0res.error())
|
||||||
|
|
||||||
let head = node.dag.head
|
let res = await node.sendBeaconBlock(forked)
|
||||||
if not(node.isSynced(head)):
|
if res.isErr():
|
||||||
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
||||||
|
if not(res.get()):
|
||||||
if head.slot >= blockData.slot():
|
|
||||||
let blocksTopic = node.getBeaconBlocksTopic(blockData.kind)
|
|
||||||
withBlck(blockData):
|
|
||||||
node.network.broadcast(blocksTopic, blck)
|
|
||||||
return RestApiResponse.jsonError(Http202, BlockValidationError)
|
return RestApiResponse.jsonError(Http202, BlockValidationError)
|
||||||
else:
|
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
|
||||||
let res =
|
|
||||||
when compiles(node.proposeSignedBlock(head, AttachedValidator(),
|
|
||||||
blockData)):
|
|
||||||
await node.proposeSignedBlock(head, AttachedValidator(), blockData)
|
|
||||||
else:
|
|
||||||
case blockData.kind
|
|
||||||
of BeaconBlockFork.Phase0:
|
|
||||||
await node.proposeSignedBlock(head, AttachedValidator(),
|
|
||||||
blockData.phase0Block)
|
|
||||||
of BeaconBlockFork.Altair:
|
|
||||||
head
|
|
||||||
|
|
||||||
if res == head:
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
|
||||||
let blocksTopic = node.getBeaconBlocksTopic(blockData.kind)
|
|
||||||
withBlck(blockData):
|
|
||||||
node.network.broadcast(blocksTopic, blck)
|
|
||||||
return RestApiResponse.jsonError(Http202, BlockValidationError)
|
|
||||||
else:
|
|
||||||
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
|
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlock
|
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}") do (
|
||||||
block_id: BlockIdent) -> RestApiResponse:
|
block_id: BlockIdent) -> RestApiResponse:
|
||||||
let bdata =
|
let bdata =
|
||||||
|
@ -690,7 +666,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
of BeaconBlockFork.Altair:
|
of BeaconBlockFork.Altair:
|
||||||
RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockV2
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2
|
||||||
router.api(MethodGet, "/api/eth/v2/beacon/blocks/{block_id}") do (
|
router.api(MethodGet, "/api/eth/v2/beacon/blocks/{block_id}") do (
|
||||||
block_id: BlockIdent) -> RestApiResponse:
|
block_id: BlockIdent) -> RestApiResponse:
|
||||||
let bdata =
|
let bdata =
|
||||||
|
@ -713,7 +689,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
(version: "altair", data: bdata.data.altairBlock)
|
(version: "altair", data: bdata.data.altairBlock)
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockRoot
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockRoot
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}/root") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}/root") do (
|
||||||
block_id: BlockIdent) -> RestApiResponse:
|
block_id: BlockIdent) -> RestApiResponse:
|
||||||
let bdata =
|
let bdata =
|
||||||
|
@ -729,7 +705,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
withBlck(bdata.data):
|
withBlck(bdata.data):
|
||||||
RestApiResponse.jsonResponse((root: blck.root))
|
RestApiResponse.jsonResponse((root: blck.root))
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getBlockAttestations
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockAttestations
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/beacon/blocks/{block_id}/attestations") do (
|
"/api/eth/v1/beacon/blocks/{block_id}/attestations") do (
|
||||||
block_id: BlockIdent) -> RestApiResponse:
|
block_id: BlockIdent) -> RestApiResponse:
|
||||||
|
@ -746,7 +722,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
withBlck(bdata.data):
|
withBlck(bdata.data):
|
||||||
RestApiResponse.jsonResponse(blck.message.body.attestations.asSeq())
|
RestApiResponse.jsonResponse(blck.message.body.attestations.asSeq())
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttestations
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttestations
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/attestations") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/pool/attestations") do (
|
||||||
slot: Option[Slot],
|
slot: Option[Slot],
|
||||||
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
|
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
|
||||||
|
@ -774,7 +750,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res.add(item)
|
res.add(item)
|
||||||
return RestApiResponse.jsonResponse(res)
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttestations
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttestations
|
||||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/attestations") do (
|
router.api(MethodPost, "/api/eth/v1/beacon/pool/attestations") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let attestations =
|
let attestations =
|
||||||
|
@ -788,13 +764,48 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
$dres.error())
|
$dres.error())
|
||||||
dres.get()
|
dres.get()
|
||||||
|
|
||||||
var failures: seq[RestAttestationsFailure]
|
proc processAttestation(a: Attestation): Future[SendResult] {.async.} =
|
||||||
for atindex, attestation in attestations.pairs():
|
let res = await node.sendAttestation(a)
|
||||||
debug "Attestation for pool", attestation = attestation,
|
if res.isErr():
|
||||||
signature = $attestation.signature
|
return res
|
||||||
if not await node.sendAttestation(attestation):
|
let
|
||||||
failures.add(RestAttestationsFailure(
|
wallTime = node.processor.getCurrentBeaconTime()
|
||||||
index: uint64(atindex), message: "Attestation failed validation"))
|
deadline = a.data.slot.toBeaconTime() +
|
||||||
|
seconds(int(SECONDS_PER_SLOT div 3))
|
||||||
|
(delayStr, delaySecs) =
|
||||||
|
if wallTime < deadline:
|
||||||
|
("-" & $(deadline - wallTime), -toFloatSeconds(deadline - wallTime))
|
||||||
|
else:
|
||||||
|
($(wallTime - deadline), toFloatSeconds(wallTime - deadline))
|
||||||
|
notice "Attestation sent", attestation = shortLog(a), delay = delayStr
|
||||||
|
return res
|
||||||
|
|
||||||
|
# Since our validation logic supports batch processing, we will submit all
|
||||||
|
# attestations for validation.
|
||||||
|
let pending =
|
||||||
|
block:
|
||||||
|
var res: seq[Future[SendResult]]
|
||||||
|
for attestation in attestations:
|
||||||
|
res.add(processAttestation(attestation))
|
||||||
|
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:
|
if len(failures) > 0:
|
||||||
return RestApiResponse.jsonErrorList(Http400, AttestationValidationError,
|
return RestApiResponse.jsonErrorList(Http400, AttestationValidationError,
|
||||||
|
@ -802,7 +813,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
else:
|
else:
|
||||||
return RestApiResponse.jsonMsgResponse(AttestationValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(AttestationValidationSuccess)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolAttesterSlashings
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttesterSlashings
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/attester_slashings") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/pool/attester_slashings") do (
|
||||||
) -> RestApiResponse:
|
) -> RestApiResponse:
|
||||||
var res: seq[AttesterSlashing]
|
var res: seq[AttesterSlashing]
|
||||||
|
@ -814,7 +825,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res.add(item)
|
res.add(item)
|
||||||
return RestApiResponse.jsonResponse(res)
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttesterSlashings
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttesterSlashings
|
||||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/attester_slashings") do (
|
router.api(MethodPost, "/api/eth/v1/beacon/pool/attester_slashings") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let slashing =
|
let slashing =
|
||||||
|
@ -826,17 +837,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
InvalidAttesterSlashingObjectError,
|
InvalidAttesterSlashingObjectError,
|
||||||
$dres.error())
|
$dres.error())
|
||||||
let res = dres.get()
|
dres.get()
|
||||||
let vres = node.exitPool[].validateAttesterSlashing(res)
|
let res = node.sendAttesterSlashing(slashing)
|
||||||
if vres.isErr():
|
if res.isErr():
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
AttesterSlashingValidationError,
|
AttesterSlashingValidationError,
|
||||||
$vres.error())
|
$res.error())
|
||||||
res
|
|
||||||
node.network.sendAttesterSlashing(slashing)
|
|
||||||
return RestApiResponse.jsonMsgResponse(AttesterSlashingValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(AttesterSlashingValidationSuccess)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/getPoolProposerSlashings
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolProposerSlashings
|
||||||
router.api(MethodGet, "/api/eth/v1/beacon/pool/proposer_slashings") do (
|
router.api(MethodGet, "/api/eth/v1/beacon/pool/proposer_slashings") do (
|
||||||
) -> RestApiResponse:
|
) -> RestApiResponse:
|
||||||
var res: seq[ProposerSlashing]
|
var res: seq[ProposerSlashing]
|
||||||
|
@ -848,7 +857,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res.add(item)
|
res.add(item)
|
||||||
return RestApiResponse.jsonResponse(res)
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolProposerSlashings
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolProposerSlashings
|
||||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/proposer_slashings") do (
|
router.api(MethodPost, "/api/eth/v1/beacon/pool/proposer_slashings") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let slashing =
|
let slashing =
|
||||||
|
@ -860,17 +869,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
InvalidProposerSlashingObjectError,
|
InvalidProposerSlashingObjectError,
|
||||||
$dres.error())
|
$dres.error())
|
||||||
let res = dres.get()
|
dres.get()
|
||||||
let vres = node.exitPool[].validateProposerSlashing(res)
|
let res = node.sendProposerSlashing(slashing)
|
||||||
if vres.isErr():
|
if res.isErr():
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
ProposerSlashingValidationError,
|
ProposerSlashingValidationError,
|
||||||
$vres.error())
|
$res.error())
|
||||||
res
|
|
||||||
node.network.sendProposerSlashing(slashing)
|
|
||||||
return RestApiResponse.jsonMsgResponse(ProposerSlashingValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(ProposerSlashingValidationSuccess)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-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:
|
||||||
var res: seq[SignedVoluntaryExit]
|
var res: seq[SignedVoluntaryExit]
|
||||||
|
@ -882,7 +889,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res.add(item)
|
res.add(item)
|
||||||
return RestApiResponse.jsonResponse(res)
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolVoluntaryExit
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit
|
||||||
router.api(MethodPost, "/api/eth/v1/beacon/pool/voluntary_exits") do (
|
router.api(MethodPost, "/api/eth/v1/beacon/pool/voluntary_exits") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let exit =
|
let exit =
|
||||||
|
@ -894,14 +901,12 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
InvalidVoluntaryExitObjectError,
|
InvalidVoluntaryExitObjectError,
|
||||||
$dres.error())
|
$dres.error())
|
||||||
let res = dres.get()
|
dres.get()
|
||||||
let vres = node.exitPool[].validateVoluntaryExit(res)
|
let res = node.sendVoluntaryExit(exit)
|
||||||
if vres.isErr():
|
if res.isErr():
|
||||||
return RestApiResponse.jsonError(Http400,
|
return RestApiResponse.jsonError(Http400,
|
||||||
VoluntaryExitValidationError,
|
VoluntaryExitValidationError,
|
||||||
$vres.error())
|
$res.error())
|
||||||
res
|
|
||||||
node.network.sendVoluntaryExit(exit)
|
|
||||||
return RestApiResponse.jsonMsgResponse(VoluntaryExitValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(VoluntaryExitValidationSuccess)
|
||||||
|
|
||||||
router.redirect(
|
router.redirect(
|
||||||
|
|
|
@ -4,164 +4,169 @@
|
||||||
# * 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 stew/[endians2, base10], presto, chronicles,
|
||||||
stew/[endians2, base10],
|
nimcrypto/utils as ncrutils
|
||||||
presto,
|
import ".."/beacon_node_common,
|
||||||
chronicles,
|
".."/eth1/eth1_monitor,
|
||||||
nimcrypto/utils as ncrutils,
|
".."/spec/forks,
|
||||||
../beacon_node_common, ../eth1/eth1_monitor,
|
"."/rest_utils
|
||||||
../spec/forks,
|
|
||||||
./rest_utils
|
|
||||||
|
|
||||||
logScope: topics = "rest_config"
|
logScope: topics = "rest_config"
|
||||||
|
|
||||||
proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
let
|
||||||
|
cachedForkSchedule =
|
||||||
|
RestApiResponse.prepareJsonResponse(getForkSchedule(node.dag.cfg))
|
||||||
|
cachedConfigSpec =
|
||||||
|
RestApiResponse.prepareJsonResponse(
|
||||||
|
(
|
||||||
|
CONFIG_NAME:
|
||||||
|
const_preset,
|
||||||
|
MAX_COMMITTEES_PER_SLOT:
|
||||||
|
Base10.toString(MAX_COMMITTEES_PER_SLOT),
|
||||||
|
TARGET_COMMITTEE_SIZE:
|
||||||
|
Base10.toString(TARGET_COMMITTEE_SIZE),
|
||||||
|
MAX_VALIDATORS_PER_COMMITTEE:
|
||||||
|
Base10.toString(MAX_VALIDATORS_PER_COMMITTEE),
|
||||||
|
MIN_PER_EPOCH_CHURN_LIMIT:
|
||||||
|
Base10.toString(node.dag.cfg.MIN_PER_EPOCH_CHURN_LIMIT),
|
||||||
|
CHURN_LIMIT_QUOTIENT:
|
||||||
|
Base10.toString(node.dag.cfg.CHURN_LIMIT_QUOTIENT),
|
||||||
|
SHUFFLE_ROUND_COUNT:
|
||||||
|
Base10.toString(SHUFFLE_ROUND_COUNT),
|
||||||
|
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
|
||||||
|
Base10.toString(node.dag.cfg.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT),
|
||||||
|
MIN_GENESIS_TIME:
|
||||||
|
Base10.toString(node.dag.cfg.MIN_GENESIS_TIME),
|
||||||
|
HYSTERESIS_QUOTIENT:
|
||||||
|
Base10.toString(HYSTERESIS_QUOTIENT),
|
||||||
|
HYSTERESIS_DOWNWARD_MULTIPLIER:
|
||||||
|
Base10.toString(HYSTERESIS_DOWNWARD_MULTIPLIER),
|
||||||
|
HYSTERESIS_UPWARD_MULTIPLIER:
|
||||||
|
Base10.toString(HYSTERESIS_UPWARD_MULTIPLIER),
|
||||||
|
SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
|
||||||
|
Base10.toString(SAFE_SLOTS_TO_UPDATE_JUSTIFIED),
|
||||||
|
ETH1_FOLLOW_DISTANCE:
|
||||||
|
Base10.toString(node.dag.cfg.ETH1_FOLLOW_DISTANCE),
|
||||||
|
TARGET_AGGREGATORS_PER_COMMITTEE:
|
||||||
|
Base10.toString(TARGET_AGGREGATORS_PER_COMMITTEE),
|
||||||
|
RANDOM_SUBNETS_PER_VALIDATOR:
|
||||||
|
Base10.toString(RANDOM_SUBNETS_PER_VALIDATOR),
|
||||||
|
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION:
|
||||||
|
Base10.toString(EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION),
|
||||||
|
SECONDS_PER_ETH1_BLOCK:
|
||||||
|
Base10.toString(node.dag.cfg.SECONDS_PER_ETH1_BLOCK),
|
||||||
|
DEPOSIT_CHAIN_ID:
|
||||||
|
Base10.toString(uint64(node.dag.cfg.DEPOSIT_CHAIN_ID)),
|
||||||
|
DEPOSIT_NETWORK_ID:
|
||||||
|
Base10.toString(uint64(node.dag.cfg.DEPOSIT_NETWORK_ID)),
|
||||||
|
DEPOSIT_CONTRACT_ADDRESS:
|
||||||
|
$node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS,
|
||||||
|
MIN_DEPOSIT_AMOUNT:
|
||||||
|
Base10.toString(MIN_DEPOSIT_AMOUNT),
|
||||||
|
MAX_EFFECTIVE_BALANCE:
|
||||||
|
Base10.toString(MAX_EFFECTIVE_BALANCE),
|
||||||
|
EJECTION_BALANCE:
|
||||||
|
Base10.toString(node.dag.cfg.EJECTION_BALANCE),
|
||||||
|
EFFECTIVE_BALANCE_INCREMENT:
|
||||||
|
Base10.toString(EFFECTIVE_BALANCE_INCREMENT),
|
||||||
|
GENESIS_FORK_VERSION:
|
||||||
|
"0x" & $node.dag.cfg.GENESIS_FORK_VERSION,
|
||||||
|
BLS_WITHDRAWAL_PREFIX:
|
||||||
|
"0x" & ncrutils.toHex([BLS_WITHDRAWAL_PREFIX]),
|
||||||
|
GENESIS_DELAY:
|
||||||
|
Base10.toString(node.dag.cfg.GENESIS_DELAY),
|
||||||
|
SECONDS_PER_SLOT:
|
||||||
|
Base10.toString(uint64(SECONDS_PER_SLOT)),
|
||||||
|
MIN_ATTESTATION_INCLUSION_DELAY:
|
||||||
|
Base10.toString(MIN_ATTESTATION_INCLUSION_DELAY),
|
||||||
|
SLOTS_PER_EPOCH:
|
||||||
|
Base10.toString(SLOTS_PER_EPOCH),
|
||||||
|
MIN_SEED_LOOKAHEAD:
|
||||||
|
Base10.toString(MIN_SEED_LOOKAHEAD),
|
||||||
|
MAX_SEED_LOOKAHEAD:
|
||||||
|
Base10.toString(MAX_SEED_LOOKAHEAD),
|
||||||
|
EPOCHS_PER_ETH1_VOTING_PERIOD:
|
||||||
|
Base10.toString(EPOCHS_PER_ETH1_VOTING_PERIOD),
|
||||||
|
SLOTS_PER_HISTORICAL_ROOT:
|
||||||
|
Base10.toString(SLOTS_PER_HISTORICAL_ROOT),
|
||||||
|
MIN_VALIDATOR_WITHDRAWABILITY_DELAY:
|
||||||
|
Base10.toString(node.dag.cfg.MIN_VALIDATOR_WITHDRAWABILITY_DELAY),
|
||||||
|
SHARD_COMMITTEE_PERIOD:
|
||||||
|
Base10.toString(node.dag.cfg.SHARD_COMMITTEE_PERIOD),
|
||||||
|
MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
||||||
|
Base10.toString(MIN_EPOCHS_TO_INACTIVITY_PENALTY),
|
||||||
|
EPOCHS_PER_HISTORICAL_VECTOR:
|
||||||
|
Base10.toString(EPOCHS_PER_HISTORICAL_VECTOR),
|
||||||
|
EPOCHS_PER_SLASHINGS_VECTOR:
|
||||||
|
Base10.toString(EPOCHS_PER_SLASHINGS_VECTOR),
|
||||||
|
HISTORICAL_ROOTS_LIMIT:
|
||||||
|
Base10.toString(HISTORICAL_ROOTS_LIMIT),
|
||||||
|
VALIDATOR_REGISTRY_LIMIT:
|
||||||
|
Base10.toString(VALIDATOR_REGISTRY_LIMIT),
|
||||||
|
BASE_REWARD_FACTOR:
|
||||||
|
Base10.toString(BASE_REWARD_FACTOR),
|
||||||
|
WHISTLEBLOWER_REWARD_QUOTIENT:
|
||||||
|
Base10.toString(WHISTLEBLOWER_REWARD_QUOTIENT),
|
||||||
|
PROPOSER_REWARD_QUOTIENT:
|
||||||
|
Base10.toString(PROPOSER_REWARD_QUOTIENT),
|
||||||
|
INACTIVITY_PENALTY_QUOTIENT:
|
||||||
|
Base10.toString(INACTIVITY_PENALTY_QUOTIENT),
|
||||||
|
MIN_SLASHING_PENALTY_QUOTIENT:
|
||||||
|
Base10.toString(MIN_SLASHING_PENALTY_QUOTIENT),
|
||||||
|
PROPORTIONAL_SLASHING_MULTIPLIER:
|
||||||
|
Base10.toString(PROPORTIONAL_SLASHING_MULTIPLIER),
|
||||||
|
MAX_PROPOSER_SLASHINGS:
|
||||||
|
Base10.toString(MAX_PROPOSER_SLASHINGS),
|
||||||
|
MAX_ATTESTER_SLASHINGS:
|
||||||
|
Base10.toString(MAX_ATTESTER_SLASHINGS),
|
||||||
|
MAX_ATTESTATIONS:
|
||||||
|
Base10.toString(MAX_ATTESTATIONS),
|
||||||
|
MAX_DEPOSITS:
|
||||||
|
Base10.toString(MAX_DEPOSITS),
|
||||||
|
MAX_VOLUNTARY_EXITS:
|
||||||
|
Base10.toString(MAX_VOLUNTARY_EXITS),
|
||||||
|
DOMAIN_BEACON_PROPOSER:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_PROPOSER).toBytesLE()),
|
||||||
|
DOMAIN_BEACON_ATTESTER:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_ATTESTER).toBytesLE()),
|
||||||
|
DOMAIN_RANDAO:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_RANDAO).toBytesLE()),
|
||||||
|
DOMAIN_DEPOSIT:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_DEPOSIT).toBytesLE()),
|
||||||
|
DOMAIN_VOLUNTARY_EXIT:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_VOLUNTARY_EXIT).toBytesLE()),
|
||||||
|
DOMAIN_SELECTION_PROOF:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_SELECTION_PROOF).toBytesLE()),
|
||||||
|
DOMAIN_AGGREGATE_AND_PROOF:
|
||||||
|
"0x" & ncrutils.toHex(uint32(DOMAIN_AGGREGATE_AND_PROOF).toBytesLE())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cachedDepositContract =
|
||||||
|
RestApiResponse.prepareJsonResponse(
|
||||||
|
(
|
||||||
|
chain_id: $node.dag.cfg.DEPOSIT_CHAIN_ID,
|
||||||
|
address: $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Config/getForkSchedule
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/config/fork_schedule") do () -> RestApiResponse:
|
"/api/eth/v1/config/fork_schedule") do () -> RestApiResponse:
|
||||||
# TODO: Implemenation needs a fix, when forks infrastructure will be
|
return RestApiResponse.response(cachedForkSchedule, Http200,
|
||||||
# established.
|
"application/json")
|
||||||
return RestApiResponse.jsonResponse(
|
|
||||||
[getStateField(node.dag.headState.data, fork)]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Config/getSpec
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/config/spec") do () -> RestApiResponse:
|
"/api/eth/v1/config/spec") do () -> RestApiResponse:
|
||||||
return RestApiResponse.jsonResponse(
|
return RestApiResponse.response(cachedConfigSpec, Http200,
|
||||||
(
|
"application/json")
|
||||||
CONFIG_NAME:
|
|
||||||
const_preset,
|
|
||||||
MAX_COMMITTEES_PER_SLOT:
|
|
||||||
Base10.toString(MAX_COMMITTEES_PER_SLOT),
|
|
||||||
TARGET_COMMITTEE_SIZE:
|
|
||||||
Base10.toString(TARGET_COMMITTEE_SIZE),
|
|
||||||
MAX_VALIDATORS_PER_COMMITTEE:
|
|
||||||
Base10.toString(MAX_VALIDATORS_PER_COMMITTEE),
|
|
||||||
MIN_PER_EPOCH_CHURN_LIMIT:
|
|
||||||
Base10.toString(node.dag.cfg.MIN_PER_EPOCH_CHURN_LIMIT),
|
|
||||||
CHURN_LIMIT_QUOTIENT:
|
|
||||||
Base10.toString(node.dag.cfg.CHURN_LIMIT_QUOTIENT),
|
|
||||||
SHUFFLE_ROUND_COUNT:
|
|
||||||
Base10.toString(SHUFFLE_ROUND_COUNT),
|
|
||||||
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
|
|
||||||
Base10.toString(
|
|
||||||
node.dag.cfg.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
|
|
||||||
),
|
|
||||||
MIN_GENESIS_TIME:
|
|
||||||
Base10.toString(node.dag.cfg.MIN_GENESIS_TIME),
|
|
||||||
HYSTERESIS_QUOTIENT:
|
|
||||||
Base10.toString(HYSTERESIS_QUOTIENT),
|
|
||||||
HYSTERESIS_DOWNWARD_MULTIPLIER:
|
|
||||||
Base10.toString(HYSTERESIS_DOWNWARD_MULTIPLIER),
|
|
||||||
HYSTERESIS_UPWARD_MULTIPLIER:
|
|
||||||
Base10.toString(HYSTERESIS_UPWARD_MULTIPLIER),
|
|
||||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
|
|
||||||
Base10.toString(SAFE_SLOTS_TO_UPDATE_JUSTIFIED),
|
|
||||||
ETH1_FOLLOW_DISTANCE:
|
|
||||||
Base10.toString(node.dag.cfg.ETH1_FOLLOW_DISTANCE),
|
|
||||||
TARGET_AGGREGATORS_PER_COMMITTEE:
|
|
||||||
Base10.toString(TARGET_AGGREGATORS_PER_COMMITTEE),
|
|
||||||
RANDOM_SUBNETS_PER_VALIDATOR:
|
|
||||||
Base10.toString(RANDOM_SUBNETS_PER_VALIDATOR),
|
|
||||||
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION:
|
|
||||||
Base10.toString(EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION),
|
|
||||||
SECONDS_PER_ETH1_BLOCK:
|
|
||||||
Base10.toString(node.dag.cfg.SECONDS_PER_ETH1_BLOCK),
|
|
||||||
DEPOSIT_CHAIN_ID:
|
|
||||||
Base10.toString(uint64(node.dag.cfg.DEPOSIT_CHAIN_ID)),
|
|
||||||
DEPOSIT_NETWORK_ID:
|
|
||||||
Base10.toString(uint64(node.dag.cfg.DEPOSIT_NETWORK_ID)),
|
|
||||||
DEPOSIT_CONTRACT_ADDRESS:
|
|
||||||
$node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS,
|
|
||||||
MIN_DEPOSIT_AMOUNT:
|
|
||||||
Base10.toString(MIN_DEPOSIT_AMOUNT),
|
|
||||||
MAX_EFFECTIVE_BALANCE:
|
|
||||||
Base10.toString(MAX_EFFECTIVE_BALANCE),
|
|
||||||
EJECTION_BALANCE:
|
|
||||||
Base10.toString(node.dag.cfg.EJECTION_BALANCE),
|
|
||||||
EFFECTIVE_BALANCE_INCREMENT:
|
|
||||||
Base10.toString(EFFECTIVE_BALANCE_INCREMENT),
|
|
||||||
GENESIS_FORK_VERSION:
|
|
||||||
"0x" & $node.dag.cfg.GENESIS_FORK_VERSION,
|
|
||||||
BLS_WITHDRAWAL_PREFIX:
|
|
||||||
"0x" & ncrutils.toHex([BLS_WITHDRAWAL_PREFIX]),
|
|
||||||
GENESIS_DELAY:
|
|
||||||
Base10.toString(node.dag.cfg.GENESIS_DELAY),
|
|
||||||
SECONDS_PER_SLOT:
|
|
||||||
Base10.toString(uint64(SECONDS_PER_SLOT)),
|
|
||||||
MIN_ATTESTATION_INCLUSION_DELAY:
|
|
||||||
Base10.toString(MIN_ATTESTATION_INCLUSION_DELAY),
|
|
||||||
SLOTS_PER_EPOCH:
|
|
||||||
Base10.toString(SLOTS_PER_EPOCH),
|
|
||||||
MIN_SEED_LOOKAHEAD:
|
|
||||||
Base10.toString(MIN_SEED_LOOKAHEAD),
|
|
||||||
MAX_SEED_LOOKAHEAD:
|
|
||||||
Base10.toString(MAX_SEED_LOOKAHEAD),
|
|
||||||
EPOCHS_PER_ETH1_VOTING_PERIOD:
|
|
||||||
Base10.toString(EPOCHS_PER_ETH1_VOTING_PERIOD),
|
|
||||||
SLOTS_PER_HISTORICAL_ROOT:
|
|
||||||
Base10.toString(SLOTS_PER_HISTORICAL_ROOT),
|
|
||||||
MIN_VALIDATOR_WITHDRAWABILITY_DELAY:
|
|
||||||
Base10.toString(
|
|
||||||
node.dag.cfg.MIN_VALIDATOR_WITHDRAWABILITY_DELAY),
|
|
||||||
SHARD_COMMITTEE_PERIOD:
|
|
||||||
Base10.toString(node.dag.cfg.SHARD_COMMITTEE_PERIOD),
|
|
||||||
MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
|
||||||
Base10.toString(MIN_EPOCHS_TO_INACTIVITY_PENALTY),
|
|
||||||
EPOCHS_PER_HISTORICAL_VECTOR:
|
|
||||||
Base10.toString(EPOCHS_PER_HISTORICAL_VECTOR),
|
|
||||||
EPOCHS_PER_SLASHINGS_VECTOR:
|
|
||||||
Base10.toString(EPOCHS_PER_SLASHINGS_VECTOR),
|
|
||||||
HISTORICAL_ROOTS_LIMIT:
|
|
||||||
Base10.toString(HISTORICAL_ROOTS_LIMIT),
|
|
||||||
VALIDATOR_REGISTRY_LIMIT:
|
|
||||||
Base10.toString(VALIDATOR_REGISTRY_LIMIT),
|
|
||||||
BASE_REWARD_FACTOR:
|
|
||||||
Base10.toString(BASE_REWARD_FACTOR),
|
|
||||||
WHISTLEBLOWER_REWARD_QUOTIENT:
|
|
||||||
Base10.toString(WHISTLEBLOWER_REWARD_QUOTIENT),
|
|
||||||
PROPOSER_REWARD_QUOTIENT:
|
|
||||||
Base10.toString(PROPOSER_REWARD_QUOTIENT),
|
|
||||||
INACTIVITY_PENALTY_QUOTIENT:
|
|
||||||
Base10.toString(INACTIVITY_PENALTY_QUOTIENT),
|
|
||||||
MIN_SLASHING_PENALTY_QUOTIENT:
|
|
||||||
Base10.toString(MIN_SLASHING_PENALTY_QUOTIENT),
|
|
||||||
PROPORTIONAL_SLASHING_MULTIPLIER:
|
|
||||||
Base10.toString(PROPORTIONAL_SLASHING_MULTIPLIER),
|
|
||||||
MAX_PROPOSER_SLASHINGS:
|
|
||||||
Base10.toString(MAX_PROPOSER_SLASHINGS),
|
|
||||||
MAX_ATTESTER_SLASHINGS:
|
|
||||||
Base10.toString(MAX_ATTESTER_SLASHINGS),
|
|
||||||
MAX_ATTESTATIONS:
|
|
||||||
Base10.toString(MAX_ATTESTATIONS),
|
|
||||||
MAX_DEPOSITS:
|
|
||||||
Base10.toString(MAX_DEPOSITS),
|
|
||||||
MAX_VOLUNTARY_EXITS:
|
|
||||||
Base10.toString(MAX_VOLUNTARY_EXITS),
|
|
||||||
DOMAIN_BEACON_PROPOSER:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_PROPOSER).toBytesLE()),
|
|
||||||
DOMAIN_BEACON_ATTESTER:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_BEACON_ATTESTER).toBytesLE()),
|
|
||||||
DOMAIN_RANDAO:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_RANDAO).toBytesLE()),
|
|
||||||
DOMAIN_DEPOSIT:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_DEPOSIT).toBytesLE()),
|
|
||||||
DOMAIN_VOLUNTARY_EXIT:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_VOLUNTARY_EXIT).toBytesLE()),
|
|
||||||
DOMAIN_SELECTION_PROOF:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_SELECTION_PROOF).toBytesLE()),
|
|
||||||
DOMAIN_AGGREGATE_AND_PROOF:
|
|
||||||
"0x" & ncrutils.toHex(uint32(DOMAIN_AGGREGATE_AND_PROOF).toBytesLE())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Config/getDepositContract
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/config/deposit_contract") do () -> RestApiResponse:
|
"/api/eth/v1/config/deposit_contract") do () -> RestApiResponse:
|
||||||
return RestApiResponse.jsonResponse(
|
return RestApiResponse.response(cachedDepositContract, Http200,
|
||||||
(
|
"application/json")
|
||||||
chain_id: $node.dag.cfg.DEPOSIT_CHAIN_ID,
|
|
||||||
address: $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
router.redirect(
|
router.redirect(
|
||||||
MethodGet,
|
MethodGet,
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import
|
import std/sequtils
|
||||||
std/sequtils,
|
import presto, chronicles
|
||||||
presto,
|
import ".."/[version, beacon_node_common],
|
||||||
chronicles,
|
".."/spec/forks,
|
||||||
../version, ../beacon_node_common,
|
"."/rest_utils
|
||||||
./rest_utils
|
|
||||||
|
|
||||||
logScope: topics = "rest_debug"
|
logScope: topics = "rest_debug"
|
||||||
|
|
||||||
proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getState
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/debug/beacon/states/{state_id}") do (
|
"/api/eth/v1/debug/beacon/states/{state_id}") do (
|
||||||
state_id: StateIdent) -> RestApiResponse:
|
state_id: StateIdent) -> RestApiResponse:
|
||||||
|
@ -22,9 +22,40 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
$bres.error())
|
$bres.error())
|
||||||
bres.get()
|
bres.get()
|
||||||
node.withStateForBlockSlot(bslot):
|
node.withStateForBlockSlot(bslot):
|
||||||
return RestApiResponse.jsonResponse(stateData.data.hbsPhase0.data)
|
case stateData.data.beaconStateFork
|
||||||
|
of BeaconStateFork.forkPhase0:
|
||||||
|
return RestApiResponse.jsonResponse(stateData.data.hbsPhase0.data)
|
||||||
|
of BeaconStateFork.forkAltair:
|
||||||
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
||||||
return RestApiResponse.jsonError(Http500, InternalServerError)
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getStateV2
|
||||||
|
router.api(MethodGet,
|
||||||
|
"/api/eth/v2/debug/beacon/states/{state_id}") do (
|
||||||
|
state_id: StateIdent) -> 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()
|
||||||
|
node.withStateForBlockSlot(bslot):
|
||||||
|
case stateData.data.beaconStateFork
|
||||||
|
of BeaconStateFork.forkPhase0:
|
||||||
|
return RestApiResponse.jsonResponse(
|
||||||
|
(version: "phase0", data: stateData.data.hbsPhase0.data)
|
||||||
|
)
|
||||||
|
of BeaconStateFork.forkAltair:
|
||||||
|
return RestApiResponse.jsonResponse(
|
||||||
|
(version: "altair", data: stateData.data.hbsAltair.data)
|
||||||
|
)
|
||||||
|
return RestApiResponse.jsonError(Http500, InternalServerError)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getDebugChainHeads
|
||||||
router.api(MethodGet,
|
router.api(MethodGet,
|
||||||
"/api/eth/v1/debug/beacon/heads") do () -> RestApiResponse:
|
"/api/eth/v1/debug/beacon/heads") do () -> RestApiResponse:
|
||||||
return RestApiResponse.jsonResponse(
|
return RestApiResponse.jsonResponse(
|
||||||
|
@ -41,6 +72,11 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
"/eth/v1/debug/beacon/heads",
|
"/eth/v1/debug/beacon/heads",
|
||||||
"/api/eth/v1/debug/beacon/heads"
|
"/api/eth/v1/debug/beacon/heads"
|
||||||
)
|
)
|
||||||
|
router.redirect(
|
||||||
|
MethodGet,
|
||||||
|
"/eth/v2/debug/beacon/heads",
|
||||||
|
"/api/eth/v2/debug/beacon/heads"
|
||||||
|
)
|
||||||
|
|
||||||
proc getDebugChainHeads*(): RestResponse[GetDebugChainHeadsResponse] {.
|
proc getDebugChainHeads*(): RestResponse[GetDebugChainHeadsResponse] {.
|
||||||
rest, endpoint: "/eth/v1/debug/beacon/heads",
|
rest, endpoint: "/eth/v1/debug/beacon/heads",
|
||||||
|
|
|
@ -49,6 +49,7 @@ proc validateEventTopics(events: seq[EventTopic]): Result[EventTopics,
|
||||||
ok(res)
|
ok(res)
|
||||||
|
|
||||||
proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Events/eventstream
|
||||||
router.api(MethodGet, "/api/eth/v1/events") do (
|
router.api(MethodGet, "/api/eth/v1/events") do (
|
||||||
topics: seq[EventTopic]) -> RestApiResponse:
|
topics: seq[EventTopic]) -> RestApiResponse:
|
||||||
# TODO (cheatfate): This call is not fully implemented yet, because there
|
# TODO (cheatfate): This call is not fully implemented yet, because there
|
||||||
|
|
|
@ -126,6 +126,7 @@ proc getP2PAddresses(node: BeaconNode): Option[seq[string]] =
|
||||||
return some(addresses)
|
return some(addresses)
|
||||||
|
|
||||||
proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getNetworkIdentity
|
||||||
router.api(MethodGet, "/api/eth/v1/node/identity") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/node/identity") do () -> RestApiResponse:
|
||||||
let discoveryAddresses =
|
let discoveryAddresses =
|
||||||
block:
|
block:
|
||||||
|
@ -156,6 +157,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getPeers
|
||||||
router.api(MethodGet, "/api/eth/v1/node/peers") do (
|
router.api(MethodGet, "/api/eth/v1/node/peers") do (
|
||||||
state: seq[PeerStateKind],
|
state: seq[PeerStateKind],
|
||||||
direction: seq[PeerDirectKind]) -> RestApiResponse:
|
direction: seq[PeerDirectKind]) -> RestApiResponse:
|
||||||
|
@ -198,6 +200,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res.add(peer)
|
res.add(peer)
|
||||||
return RestApiResponse.jsonResponseWMeta(res, (count: uint64(len(res))))
|
return RestApiResponse.jsonResponseWMeta(res, (count: uint64(len(res))))
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getPeerCount
|
||||||
router.api(MethodGet, "/api/eth/v1/node/peer_count") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/node/peer_count") do () -> RestApiResponse:
|
||||||
var res: RestNodePeerCount
|
var res: RestNodePeerCount
|
||||||
for item in node.network.peers.values():
|
for item in node.network.peers.values():
|
||||||
|
@ -214,6 +217,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
discard
|
discard
|
||||||
return RestApiResponse.jsonResponse(res)
|
return RestApiResponse.jsonResponse(res)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getPeer
|
||||||
router.api(MethodGet, "/api/eth/v1/node/peers/{peer_id}") do (
|
router.api(MethodGet, "/api/eth/v1/node/peers/{peer_id}") do (
|
||||||
peer_id: PeerID) -> RestApiResponse:
|
peer_id: PeerID) -> RestApiResponse:
|
||||||
let peer =
|
let peer =
|
||||||
|
@ -237,14 +241,17 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getNodeVersion
|
||||||
router.api(MethodGet, "/api/eth/v1/node/version") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/node/version") do () -> RestApiResponse:
|
||||||
return RestApiResponse.jsonResponse(
|
return RestApiResponse.jsonResponse(
|
||||||
(version: "Nimbus/" & fullVersionStr)
|
(version: "Nimbus/" & fullVersionStr)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getSyncingStatus
|
||||||
router.api(MethodGet, "/api/eth/v1/node/syncing") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/node/syncing") do () -> RestApiResponse:
|
||||||
return RestApiResponse.jsonResponse(node.syncManager.getInfo())
|
return RestApiResponse.jsonResponse(node.syncManager.getInfo())
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Node/getHealth
|
||||||
router.api(MethodGet, "/api/eth/v1/node/health") do () -> RestApiResponse:
|
router.api(MethodGet, "/api/eth/v1/node/health") do () -> RestApiResponse:
|
||||||
# TODO: Add ability to detect node's issues and return 503 error according
|
# TODO: Add ability to detect node's issues and return 503 error according
|
||||||
# to specification.
|
# to specification.
|
||||||
|
|
|
@ -11,7 +11,6 @@ import
|
||||||
../spec/datatypes/[phase0],
|
../spec/datatypes/[phase0],
|
||||||
../beacon_node_common, ../networking/eth2_network,
|
../beacon_node_common, ../networking/eth2_network,
|
||||||
../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool],
|
../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool],
|
||||||
../gossip_processing/gossip_validation,
|
|
||||||
../validators/validator_duties,
|
../validators/validator_duties,
|
||||||
../spec/[forks, network],
|
../spec/[forks, network],
|
||||||
./rest_utils
|
./rest_utils
|
||||||
|
@ -19,7 +18,7 @@ import
|
||||||
logScope: topics = "rest_validatorapi"
|
logScope: topics = "rest_validatorapi"
|
||||||
|
|
||||||
proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/getAttesterDuties
|
# https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties
|
||||||
router.api(MethodPost, "/api/eth/v1/validator/duties/attester/{epoch}") do (
|
router.api(MethodPost, "/api/eth/v1/validator/duties/attester/{epoch}") do (
|
||||||
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
|
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let indexList =
|
let indexList =
|
||||||
|
@ -98,7 +97,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res
|
res
|
||||||
return RestApiResponse.jsonResponseWRoot(duties, droot)
|
return RestApiResponse.jsonResponseWRoot(duties, droot)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/getProposerDuties
|
# https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties
|
||||||
router.api(MethodGet, "/api/eth/v1/validator/duties/proposer/{epoch}") do (
|
router.api(MethodGet, "/api/eth/v1/validator/duties/proposer/{epoch}") do (
|
||||||
epoch: Epoch) -> RestApiResponse:
|
epoch: Epoch) -> RestApiResponse:
|
||||||
let qepoch =
|
let qepoch =
|
||||||
|
@ -142,7 +141,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res
|
res
|
||||||
return RestApiResponse.jsonResponseWRoot(duties, droot)
|
return RestApiResponse.jsonResponseWRoot(duties, droot)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-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],
|
||||||
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
|
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
|
||||||
|
@ -204,6 +203,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
of BeaconBlockFork.Altair:
|
of BeaconBlockFork.Altair:
|
||||||
return RestApiResponse.jsonError(Http400, BlockProduceError)
|
return RestApiResponse.jsonError(Http400, BlockProduceError)
|
||||||
|
|
||||||
|
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2
|
||||||
router.api(MethodGet, "/api/eth/v2/validator/blocks/{slot}") do (
|
router.api(MethodGet, "/api/eth/v2/validator/blocks/{slot}") do (
|
||||||
slot: Slot, randao_reveal: Option[ValidatorSig],
|
slot: Slot, randao_reveal: Option[ValidatorSig],
|
||||||
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
|
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
|
||||||
|
@ -271,7 +271,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
(version: "altair", data: message.altairBlock.message)
|
(version: "altair", data: message.altairBlock.message)
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/produceAttestationData
|
# https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData
|
||||||
router.api(MethodGet, "/api/eth/v1/validator/attestation_data") do (
|
router.api(MethodGet, "/api/eth/v1/validator/attestation_data") do (
|
||||||
slot: Option[Slot],
|
slot: Option[Slot],
|
||||||
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
|
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
|
||||||
|
@ -307,7 +307,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
makeAttestationData(epochRef, qhead.atSlot(qslot), qindex)
|
makeAttestationData(epochRef, qhead.atSlot(qslot), qindex)
|
||||||
return RestApiResponse.jsonResponse(adata)
|
return RestApiResponse.jsonResponse(adata)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/getAggregatedAttestation
|
# https://ethereum.github.io/beacon-APIs/#/Validator/getAggregatedAttestation
|
||||||
router.api(MethodGet, "/api/eth/v1/validator/aggregate_attestation") do (
|
router.api(MethodGet, "/api/eth/v1/validator/aggregate_attestation") do (
|
||||||
attestation_data_root: Option[Eth2Digest],
|
attestation_data_root: Option[Eth2Digest],
|
||||||
slot: Option[Slot]) -> RestApiResponse:
|
slot: Option[Slot]) -> RestApiResponse:
|
||||||
|
@ -339,7 +339,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
res.get()
|
res.get()
|
||||||
return RestApiResponse.jsonResponse(attestation)
|
return RestApiResponse.jsonResponse(attestation)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/publishAggregateAndProofs
|
# https://ethereum.github.io/beacon-APIs/#/Validator/publishAggregateAndProofs
|
||||||
router.api(MethodPost, "/api/eth/v1/validator/aggregate_and_proofs") do (
|
router.api(MethodPost, "/api/eth/v1/validator/aggregate_and_proofs") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
let proofs =
|
let proofs =
|
||||||
|
@ -352,25 +352,28 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
||||||
InvalidAggregateAndProofObjectError,
|
InvalidAggregateAndProofObjectError,
|
||||||
$dres.error())
|
$dres.error())
|
||||||
dres.get()
|
dres.get()
|
||||||
|
# Since our validation logic supports batch processing, we will submit all
|
||||||
for item in proofs:
|
# aggregated attestations for validation.
|
||||||
let wallTime = node.processor.getCurrentBeaconTime()
|
var pending =
|
||||||
let res = await node.attestationPool.validateAggregate(
|
block:
|
||||||
node.processor.batchCrypto, item, wallTime
|
var res: seq[Future[SendResult]]
|
||||||
)
|
for proof in proofs:
|
||||||
if res.isErr():
|
res.add(node.sendAggregateAndProof(proof))
|
||||||
return RestApiResponse.jsonError(Http400,
|
res
|
||||||
AggregateAndProofValidationError,
|
await allFutures(pending)
|
||||||
$res.error())
|
for future in pending:
|
||||||
node.network.broadcast(
|
if future.done():
|
||||||
getAggregateAndProofsTopic(node.dag.forkDigests.phase0), item)
|
let res = future.read()
|
||||||
notice "Aggregated attestation sent",
|
if res.isErr():
|
||||||
attestation = shortLog(item.message.aggregate),
|
return RestApiResponse.jsonError(Http400,
|
||||||
signature = shortLog(item.signature)
|
AggregateAndProofValidationError,
|
||||||
|
$res.error())
|
||||||
|
else:
|
||||||
|
return RestApiResponse.jsonError(Http500,
|
||||||
|
"Unexpected server failure, while sending aggregate and proof")
|
||||||
return RestApiResponse.jsonMsgResponse(AggregateAndProofValidationSuccess)
|
return RestApiResponse.jsonMsgResponse(AggregateAndProofValidationSuccess)
|
||||||
|
|
||||||
# https://ethereum.github.io/eth2.0-APIs/#/Validator/prepareBeaconCommitteeSubnet
|
# https://ethereum.github.io/beacon-APIs/#/Validator/prepareBeaconCommitteeSubnet
|
||||||
router.api(MethodPost,
|
router.api(MethodPost,
|
||||||
"/api/eth/v1/validator/beacon_committee_subscriptions") do (
|
"/api/eth/v1/validator/beacon_committee_subscriptions") do (
|
||||||
contentBody: Option[ContentBody]) -> RestApiResponse:
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
||||||
|
|
|
@ -16,7 +16,6 @@ import
|
||||||
../beacon_node_common,
|
../beacon_node_common,
|
||||||
../networking/eth2_network,
|
../networking/eth2_network,
|
||||||
../validators/validator_duties,
|
../validators/validator_duties,
|
||||||
../gossip_processing/gossip_validation,
|
|
||||||
../consensus_object_pools/blockchain_dag,
|
../consensus_object_pools/blockchain_dag,
|
||||||
../spec/[eth2_merkleization, forks, network],
|
../spec/[eth2_merkleization, forks, network],
|
||||||
../spec/datatypes/[phase0],
|
../spec/datatypes/[phase0],
|
||||||
|
@ -470,7 +469,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_attestations") do (
|
rpcServer.rpc("post_v1_beacon_pool_attestations") do (
|
||||||
attestation: Attestation) -> bool:
|
attestation: Attestation) -> bool:
|
||||||
return await node.sendAttestation(attestation)
|
let res = await node.sendAttestation(attestation)
|
||||||
|
return res.isOk()
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_attester_slashings") do (
|
rpcServer.rpc("get_v1_beacon_pool_attester_slashings") do (
|
||||||
) -> seq[AttesterSlashing]:
|
) -> seq[AttesterSlashing]:
|
||||||
|
@ -485,14 +485,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_attester_slashings") do (
|
rpcServer.rpc("post_v1_beacon_pool_attester_slashings") do (
|
||||||
slashing: AttesterSlashing) -> bool:
|
slashing: AttesterSlashing) -> bool:
|
||||||
if isNil(node.exitPool):
|
let res = node.sendAttesterSlashing(slashing)
|
||||||
raise newException(CatchableError, "Exit pool is not yet available!")
|
return res.isOk()
|
||||||
let validity = node.exitPool[].validateAttesterSlashing(slashing)
|
|
||||||
if validity.isOk:
|
|
||||||
node.network.sendAttesterSlashing(slashing)
|
|
||||||
else:
|
|
||||||
raise newException(CatchableError, $(validity.error[1]))
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_proposer_slashings") do (
|
rpcServer.rpc("get_v1_beacon_pool_proposer_slashings") do (
|
||||||
) -> seq[ProposerSlashing]:
|
) -> seq[ProposerSlashing]:
|
||||||
|
@ -507,14 +501,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_proposer_slashings") do (
|
rpcServer.rpc("post_v1_beacon_pool_proposer_slashings") do (
|
||||||
slashing: ProposerSlashing) -> bool:
|
slashing: ProposerSlashing) -> bool:
|
||||||
if isNil(node.exitPool):
|
let res = node.sendProposerSlashing(slashing)
|
||||||
raise newException(CatchableError, "Exit pool is not yet available!")
|
return res.isOk()
|
||||||
let validity = node.exitPool[].validateProposerSlashing(slashing)
|
|
||||||
if validity.isOk:
|
|
||||||
node.network.sendProposerSlashing(slashing)
|
|
||||||
else:
|
|
||||||
raise newException(CatchableError, $(validity.error[1]))
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_voluntary_exits") do (
|
rpcServer.rpc("get_v1_beacon_pool_voluntary_exits") do (
|
||||||
) -> seq[SignedVoluntaryExit]:
|
) -> seq[SignedVoluntaryExit]:
|
||||||
|
@ -529,11 +517,5 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_voluntary_exits") do (
|
rpcServer.rpc("post_v1_beacon_pool_voluntary_exits") do (
|
||||||
exit: SignedVoluntaryExit) -> bool:
|
exit: SignedVoluntaryExit) -> bool:
|
||||||
if isNil(node.exitPool):
|
let res = node.sendVoluntaryExit(exit)
|
||||||
raise newException(CatchableError, "Exit pool is not yet available!")
|
return res.isOk()
|
||||||
let validity = node.exitPool[].validateVoluntaryExit(exit)
|
|
||||||
if validity.isOk:
|
|
||||||
node.network.sendVoluntaryExit(exit)
|
|
||||||
else:
|
|
||||||
raise newException(CatchableError, $(validity.error[1]))
|
|
||||||
return true
|
|
||||||
|
|
|
@ -59,85 +59,144 @@ type
|
||||||
RestAttestationError |
|
RestAttestationError |
|
||||||
RestGenericError
|
RestGenericError
|
||||||
|
|
||||||
proc jsonResponseWRoot*(t: typedesc[RestApiResponse],
|
{.push raises: [Defect].}
|
||||||
data: auto,
|
|
||||||
dependent_root: Eth2Digest): RestApiResponse =
|
|
||||||
var stream = memoryOutput()
|
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
|
||||||
writer.beginRecord()
|
|
||||||
writer.writeField("dependent_root", dependent_root)
|
|
||||||
writer.writeField("data", data)
|
|
||||||
writer.endRecord()
|
|
||||||
RestApiResponse.response(stream.getOutput(seq[byte]), Http200,
|
|
||||||
"application/json")
|
|
||||||
|
|
||||||
proc jsonResponse*(t: typedesc[RestApiResponse],
|
proc prepareJsonResponse*(t: typedesc[RestApiResponse], d: auto): seq[byte] =
|
||||||
data: auto): RestApiResponse =
|
let res =
|
||||||
var stream = memoryOutput()
|
block:
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
var default: seq[byte]
|
||||||
writer.beginRecord()
|
try:
|
||||||
writer.writeField("data", data)
|
var stream = memoryOutput()
|
||||||
writer.endRecord()
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
RestApiResponse.response(stream.getOutput(seq[byte]), Http200,
|
writer.beginRecord()
|
||||||
"application/json")
|
writer.writeField("data", d)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
|
res
|
||||||
|
|
||||||
|
proc jsonResponseWRoot*(t: typedesc[RestApiResponse], data: auto,
|
||||||
|
dependent_root: Eth2Digest): RestApiResponse =
|
||||||
|
let res =
|
||||||
|
block:
|
||||||
|
var default: seq[byte]
|
||||||
|
try:
|
||||||
|
var stream = memoryOutput()
|
||||||
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
|
writer.beginRecord()
|
||||||
|
writer.writeField("dependent_root", dependent_root)
|
||||||
|
writer.writeField("data", data)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
|
RestApiResponse.response(res, Http200, "application/json")
|
||||||
|
|
||||||
|
proc jsonResponse*(t: typedesc[RestApiResponse], data: auto): RestApiResponse =
|
||||||
|
let res =
|
||||||
|
block:
|
||||||
|
var default: seq[byte]
|
||||||
|
try:
|
||||||
|
var stream = memoryOutput()
|
||||||
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
|
writer.beginRecord()
|
||||||
|
writer.writeField("data", data)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
|
RestApiResponse.response(res, Http200, "application/json")
|
||||||
|
|
||||||
proc jsonResponseWMeta*(t: typedesc[RestApiResponse],
|
proc jsonResponseWMeta*(t: typedesc[RestApiResponse],
|
||||||
data: auto, meta: auto): RestApiResponse =
|
data: auto, meta: auto): RestApiResponse =
|
||||||
var stream = memoryOutput()
|
let res =
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
block:
|
||||||
writer.beginRecord()
|
var default: seq[byte]
|
||||||
writer.writeField("data", data)
|
try:
|
||||||
writer.writeField("meta", meta)
|
var stream = memoryOutput()
|
||||||
writer.endRecord()
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
RestApiResponse.response(stream.getOutput(seq[byte]), Http200,
|
writer.beginRecord()
|
||||||
"application/json")
|
writer.writeField("data", data)
|
||||||
|
writer.writeField("meta", meta)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
|
RestApiResponse.response(res, Http200, "application/json")
|
||||||
|
|
||||||
proc jsonMsgResponse*(t: typedesc[RestApiResponse],
|
proc jsonMsgResponse*(t: typedesc[RestApiResponse],
|
||||||
msg: string = ""): RestApiResponse =
|
msg: string = ""): RestApiResponse =
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
var default: seq[string]
|
var default: seq[byte]
|
||||||
var stream = memoryOutput()
|
try:
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
var defstrings: seq[string]
|
||||||
writer.beginRecord()
|
var stream = memoryOutput()
|
||||||
writer.writeField("code", "200")
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
writer.writeField("message", msg)
|
writer.beginRecord()
|
||||||
writer.writeField("stacktrace", default)
|
writer.writeField("code", "200")
|
||||||
writer.endRecord()
|
writer.writeField("message", msg)
|
||||||
stream.getOutput(seq[byte])
|
writer.writeField("stacktrace", defstrings)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
RestApiResponse.response(data, Http200, "application/json")
|
RestApiResponse.response(data, Http200, "application/json")
|
||||||
|
|
||||||
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
||||||
msg: string = ""): RestApiResponse =
|
msg: string = ""): RestApiResponse =
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
var default: seq[string]
|
var default: string
|
||||||
var stream = memoryOutput()
|
try:
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
var defstrings: seq[string]
|
||||||
writer.beginRecord()
|
var stream = memoryOutput()
|
||||||
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
writer.writeField("message", msg)
|
writer.beginRecord()
|
||||||
writer.writeField("stacktrace", default)
|
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
||||||
writer.endRecord()
|
writer.writeField("message", msg)
|
||||||
stream.getOutput(string)
|
writer.writeField("stacktrace", defstrings)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(string)
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
RestApiResponse.error(status, data, "application/json")
|
RestApiResponse.error(status, data, "application/json")
|
||||||
|
|
||||||
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
||||||
msg: string = "", stacktrace: string): RestApiResponse =
|
msg: string = "", stacktrace: string): RestApiResponse =
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
var default: seq[string]
|
var default: string
|
||||||
var stream = memoryOutput()
|
try:
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
var defstrings: seq[string]
|
||||||
writer.beginRecord()
|
var stream = memoryOutput()
|
||||||
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
writer.writeField("message", msg)
|
writer.beginRecord()
|
||||||
if len(stacktrace) > 0:
|
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
||||||
writer.writeField("stacktrace", [stacktrace])
|
writer.writeField("message", msg)
|
||||||
else:
|
if len(stacktrace) > 0:
|
||||||
writer.writeField("stacktrace", default)
|
writer.writeField("stacktrace", [stacktrace])
|
||||||
writer.endRecord()
|
else:
|
||||||
stream.getOutput(string)
|
writer.writeField("stacktrace", defstrings)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(string)
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
RestApiResponse.error(status, data, "application/json")
|
RestApiResponse.error(status, data, "application/json")
|
||||||
|
|
||||||
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
||||||
|
@ -145,14 +204,20 @@ proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
|
||||||
stacktraces: openarray[string]): RestApiResponse =
|
stacktraces: openarray[string]): RestApiResponse =
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
var stream = memoryOutput()
|
var default: string
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
try:
|
||||||
writer.beginRecord()
|
var stream = memoryOutput()
|
||||||
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
writer.writeField("message", msg)
|
writer.beginRecord()
|
||||||
writer.writeField("stacktrace", stacktraces)
|
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
||||||
writer.endRecord()
|
writer.writeField("message", msg)
|
||||||
stream.getOutput(string)
|
writer.writeField("stacktrace", stacktraces)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(string)
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
RestApiResponse.error(status, data, "application/json")
|
RestApiResponse.error(status, data, "application/json")
|
||||||
|
|
||||||
proc jsonErrorList*(t: typedesc[RestApiResponse],
|
proc jsonErrorList*(t: typedesc[RestApiResponse],
|
||||||
|
@ -160,21 +225,28 @@ proc jsonErrorList*(t: typedesc[RestApiResponse],
|
||||||
msg: string = "", failures: auto): RestApiResponse =
|
msg: string = "", failures: auto): RestApiResponse =
|
||||||
let data =
|
let data =
|
||||||
block:
|
block:
|
||||||
var stream = memoryOutput()
|
var default: string
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
try:
|
||||||
writer.beginRecord()
|
var stream = memoryOutput()
|
||||||
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
writer.writeField("message", msg)
|
writer.beginRecord()
|
||||||
writer.writeField("failures", failures)
|
writer.writeField("code", Base10.toString(uint64(status.toInt())))
|
||||||
writer.endRecord()
|
writer.writeField("message", msg)
|
||||||
stream.getOutput(string)
|
writer.writeField("failures", failures)
|
||||||
|
writer.endRecord()
|
||||||
|
stream.getOutput(string)
|
||||||
|
except SerializationError:
|
||||||
|
default
|
||||||
|
except IOError:
|
||||||
|
default
|
||||||
RestApiResponse.error(status, data, "application/json")
|
RestApiResponse.error(status, data, "application/json")
|
||||||
|
|
||||||
template hexOriginal(data: openarray[byte]): string =
|
template hexOriginal(data: openarray[byte]): string =
|
||||||
"0x" & ncrutils.toHex(data, true)
|
"0x" & ncrutils.toHex(data, true)
|
||||||
|
|
||||||
## uint64
|
## uint64
|
||||||
proc writeValue*(w: var JsonWriter[RestJson], value: uint64) =
|
proc writeValue*(w: var JsonWriter[RestJson], value: uint64) {.
|
||||||
|
raises: [IOError, Defect].} =
|
||||||
writeValue(w, Base10.toString(value))
|
writeValue(w, Base10.toString(value))
|
||||||
|
|
||||||
proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {.
|
proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {.
|
||||||
|
@ -187,7 +259,8 @@ proc readValue*(reader: var JsonReader[RestJson], value: var uint64) {.
|
||||||
reader.raiseUnexpectedValue($res.error())
|
reader.raiseUnexpectedValue($res.error())
|
||||||
|
|
||||||
## byte
|
## byte
|
||||||
proc writeValue*(w: var JsonWriter[RestJson], value: byte) =
|
proc writeValue*(w: var JsonWriter[RestJson], value: byte) {.
|
||||||
|
raises: [IOError, Defect].} =
|
||||||
var data: array[1, byte]
|
var data: array[1, byte]
|
||||||
data[0] = value
|
data[0] = value
|
||||||
writeValue(w, hexOriginal(data))
|
writeValue(w, hexOriginal(data))
|
||||||
|
@ -203,7 +276,8 @@ proc readValue*(reader: var JsonReader[RestJson], value: var byte) {.
|
||||||
"byte value should be a valid hex string")
|
"byte value should be a valid hex string")
|
||||||
|
|
||||||
## DomainType
|
## DomainType
|
||||||
proc writeValue*(w: var JsonWriter[RestJson], value: DomainType) =
|
proc writeValue*(w: var JsonWriter[RestJson], value: DomainType) {.
|
||||||
|
raises: [IOError, Defect].} =
|
||||||
writeValue(w, hexOriginal(uint32(value).toBytesLE()))
|
writeValue(w, hexOriginal(uint32(value).toBytesLE()))
|
||||||
|
|
||||||
proc readValue*(reader: var JsonReader[RestJson], value: var DomainType) {.
|
proc readValue*(reader: var JsonReader[RestJson], value: var DomainType) {.
|
||||||
|
@ -353,11 +427,13 @@ proc writeValue*(writer: var JsonWriter[RestJson], value: BitSeq) {.
|
||||||
writeValue(writer, hexOriginal(value.bytes()))
|
writeValue(writer, hexOriginal(value.bytes()))
|
||||||
|
|
||||||
## BitList
|
## BitList
|
||||||
proc readValue*(reader: var JsonReader[RestJson], value: var BitList) =
|
proc readValue*(reader: var JsonReader[RestJson], value: var BitList) {.
|
||||||
|
raises: [IOError, SerializationError, Defect].} =
|
||||||
type T = type(value)
|
type T = type(value)
|
||||||
value = T readValue(reader, BitSeq)
|
value = T readValue(reader, BitSeq)
|
||||||
|
|
||||||
proc writeValue*(writer: var JsonWriter[RestJson], value: BitList) =
|
proc writeValue*(writer: var JsonWriter[RestJson], value: BitList) {.
|
||||||
|
raises: [IOError, Defect].} =
|
||||||
writeValue(writer, BitSeq value)
|
writeValue(writer, BitSeq value)
|
||||||
|
|
||||||
## Eth2Digest
|
## Eth2Digest
|
||||||
|
@ -489,13 +565,21 @@ RestJson.useCustomSerialization(phase0.BeaconState.justification_bits):
|
||||||
writer.writeValue "0x" & toHex([value])
|
writer.writeValue "0x" & toHex([value])
|
||||||
|
|
||||||
proc encodeBytes*[T: EncodeTypes](value: T,
|
proc encodeBytes*[T: EncodeTypes](value: T,
|
||||||
contentType: string): RestResult[seq[byte]] =
|
contentType: string): RestResult[seq[byte]] =
|
||||||
case contentType
|
case contentType
|
||||||
of "application/json":
|
of "application/json":
|
||||||
var stream = memoryOutput()
|
let data =
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
block:
|
||||||
writer.writeValue(value)
|
try:
|
||||||
ok(stream.getOutput(seq[byte]))
|
var stream = memoryOutput()
|
||||||
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
|
writer.writeValue(value)
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except IOError:
|
||||||
|
return err("Input/output error")
|
||||||
|
except SerializationError:
|
||||||
|
return err("Serialization error")
|
||||||
|
ok(data)
|
||||||
else:
|
else:
|
||||||
err("Content-Type not supported")
|
err("Content-Type not supported")
|
||||||
|
|
||||||
|
@ -503,10 +587,18 @@ proc encodeBytes*[T: EncodeArrays](value: T,
|
||||||
contentType: string): RestResult[seq[byte]] =
|
contentType: string): RestResult[seq[byte]] =
|
||||||
case contentType
|
case contentType
|
||||||
of "application/json":
|
of "application/json":
|
||||||
var stream = memoryOutput()
|
let data =
|
||||||
var writer = JsonWriter[RestJson].init(stream)
|
block:
|
||||||
writer.writeArray(value)
|
try:
|
||||||
ok(stream.getOutput(seq[byte]))
|
var stream = memoryOutput()
|
||||||
|
var writer = JsonWriter[RestJson].init(stream)
|
||||||
|
writer.writeArray(value)
|
||||||
|
stream.getOutput(seq[byte])
|
||||||
|
except IOError:
|
||||||
|
return err("Input/output error")
|
||||||
|
except SerializationError:
|
||||||
|
return err("Serialization error")
|
||||||
|
ok(data)
|
||||||
else:
|
else:
|
||||||
err("Content-Type not supported")
|
err("Content-Type not supported")
|
||||||
|
|
||||||
|
|
|
@ -277,7 +277,7 @@ type
|
||||||
GetDebugChainHeadsResponse* = DataEnclosedObject[seq[RestChainHead]]
|
GetDebugChainHeadsResponse* = DataEnclosedObject[seq[RestChainHead]]
|
||||||
GetDepositContractResponse* = DataEnclosedObject[RestDepositContract]
|
GetDepositContractResponse* = DataEnclosedObject[RestDepositContract]
|
||||||
GetEpochCommitteesResponse* = DataEnclosedObject[RestGenesis]
|
GetEpochCommitteesResponse* = DataEnclosedObject[RestGenesis]
|
||||||
GetForkScheduleResponse* = DataEnclosedObject[Fork]
|
GetForkScheduleResponse* = DataEnclosedObject[seq[Fork]]
|
||||||
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
|
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
|
||||||
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
|
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
|
||||||
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]
|
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]
|
||||||
|
|
|
@ -297,3 +297,11 @@ proc nextForkEpochAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Epoch =
|
||||||
cfg.ALTAIR_FORK_EPOCH
|
cfg.ALTAIR_FORK_EPOCH
|
||||||
else:
|
else:
|
||||||
FAR_FUTURE_EPOCH
|
FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
func getForkSchedule*(cfg: RuntimeConfig): array[2, Fork] =
|
||||||
|
## This procedure returns list of known and/or scheduled forks.
|
||||||
|
##
|
||||||
|
## This procedure is used by HTTP REST framework and validator client.
|
||||||
|
##
|
||||||
|
## NOTE: Update this procedure when new fork will be scheduled.
|
||||||
|
[cfg.genesisFork(), cfg.altairFork()]
|
||||||
|
|
|
@ -343,9 +343,10 @@ template firstSuccessTimeout*(vc: ValidatorClientRef, respType: typedesc,
|
||||||
RestBeaconNodeStatus.NotSynced,
|
RestBeaconNodeStatus.NotSynced,
|
||||||
RestBeaconNodeStatus.Uninitalized}
|
RestBeaconNodeStatus.Uninitalized}
|
||||||
let offlineNodes = vc.beaconNodes.filterIt(it.status in offlineMask)
|
let offlineNodes = vc.beaconNodes.filterIt(it.status in offlineMask)
|
||||||
|
let onlineNodesCount = len(vc.beaconNodes) - len(offlineNodes)
|
||||||
|
|
||||||
warn "No working beacon nodes available, refreshing nodes status",
|
warn "No working beacon nodes available, refreshing nodes status",
|
||||||
online_nodes = len(onlineNodes), offline_nodes = len(offlineNodes)
|
online_nodes = onlineNodesCount, offline_nodes = len(offlineNodes)
|
||||||
|
|
||||||
var checkFut = vc.checkNodes(offlineMask)
|
var checkFut = vc.checkNodes(offlineMask)
|
||||||
|
|
||||||
|
@ -475,6 +476,30 @@ proc getAttesterDuties*(vc: ValidatorClientRef, epoch: Epoch,
|
||||||
|
|
||||||
raise newException(ValidatorApiError, "Unable to retrieve attester duties")
|
raise newException(ValidatorApiError, "Unable to retrieve attester duties")
|
||||||
|
|
||||||
|
proc getForkSchedule*(vc: ValidatorClientRef): Future[seq[Fork]] {.async.} =
|
||||||
|
logScope: request = "getForkSchedule"
|
||||||
|
vc.firstSuccessTimeout(RestResponse[GetForkScheduleResponse], SlotDuration,
|
||||||
|
getForkSchedule(it)):
|
||||||
|
if apiResponse.isErr():
|
||||||
|
debug "Unable to retrieve head state's fork", endpoint = node,
|
||||||
|
error = apiResponse.error()
|
||||||
|
RestBeaconNodeStatus.Offline
|
||||||
|
else:
|
||||||
|
let response = apiResponse.get()
|
||||||
|
case response.status
|
||||||
|
of 200:
|
||||||
|
debug "Received successfull response", endpoint = node
|
||||||
|
return response.data.data
|
||||||
|
of 500:
|
||||||
|
debug "Received internal error response",
|
||||||
|
response_code = response.status, endpoint = node
|
||||||
|
RestBeaconNodeStatus.Offline
|
||||||
|
else:
|
||||||
|
debug "Received unexpected error response",
|
||||||
|
response_code = response.status, endpoint = node
|
||||||
|
RestBeaconNodeStatus.Offline
|
||||||
|
raise newException(ValidatorApiError, "Unable to retrieve fork schedule")
|
||||||
|
|
||||||
proc getHeadStateFork*(vc: ValidatorClientRef): Future[Fork] {.async.} =
|
proc getHeadStateFork*(vc: ValidatorClientRef): Future[Fork] {.async.} =
|
||||||
logScope: request = "getHeadStateFork"
|
logScope: request = "getHeadStateFork"
|
||||||
let stateIdent = StateIdent.init(StateIdentType.Head)
|
let stateIdent = StateIdent.init(StateIdentType.Head)
|
||||||
|
|
|
@ -27,6 +27,7 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
|
||||||
let signingRoot =
|
let signingRoot =
|
||||||
compute_attestation_root(fork, vc.beaconGenesis.genesis_validators_root,
|
compute_attestation_root(fork, vc.beaconGenesis.genesis_validators_root,
|
||||||
adata)
|
adata)
|
||||||
|
let attestationRoot = adata.hash_tree_root()
|
||||||
|
|
||||||
let vindex = validator.index.get()
|
let vindex = validator.index.get()
|
||||||
let notSlashable = vc.attachedValidators.slashingProtection
|
let notSlashable = vc.attachedValidators.slashingProtection
|
||||||
|
@ -45,6 +46,11 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
|
||||||
Natural(duty.data.validator_committee_index),
|
Natural(duty.data.validator_committee_index),
|
||||||
fork, vc.beaconGenesis.genesis_validators_root)
|
fork, vc.beaconGenesis.genesis_validators_root)
|
||||||
|
|
||||||
|
debug "Sending attestation", attestation = shortLog(attestation),
|
||||||
|
validator = shortLog(validator), validator_index = vindex,
|
||||||
|
attestation_root = shortLog(attestationRoot),
|
||||||
|
delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
try:
|
try:
|
||||||
await vc.submitPoolAttestations(@[attestation])
|
await vc.submitPoolAttestations(@[attestation])
|
||||||
|
@ -63,19 +69,17 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
|
||||||
return false
|
return false
|
||||||
|
|
||||||
let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
|
let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
|
||||||
let indexInCommittee = duty.data.validator_committee_index
|
|
||||||
if res:
|
if res:
|
||||||
notice "Attestation published", attestation = shortLog(attestation),
|
notice "Attestation published", attestation = shortLog(attestation),
|
||||||
validator = shortLog(validator),
|
validator = shortLog(validator),
|
||||||
validator_index = vindex,
|
validator_index = vindex,
|
||||||
delay = delay,
|
delay = delay,
|
||||||
indexInCommittee = indexInCommittee
|
attestation_root = attestationRoot
|
||||||
else:
|
else:
|
||||||
warn "Attestation was not accepted by beacon node",
|
warn "Attestation was not accepted by beacon node",
|
||||||
attestation = shortLog(attestation),
|
attestation = shortLog(attestation),
|
||||||
validator = shortLog(validator),
|
validator = shortLog(validator),
|
||||||
validator_index = vindex, delay = delay,
|
validator_index = vindex, delay = delay
|
||||||
indexInCommittee = indexInCommittee
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
proc serveAggregateAndProof*(service: AttestationServiceRef,
|
proc serveAggregateAndProof*(service: AttestationServiceRef,
|
||||||
|
@ -94,6 +98,13 @@ proc serveAggregateAndProof*(service: AttestationServiceRef,
|
||||||
|
|
||||||
let aggregationSlot = proof.aggregate.data.slot
|
let aggregationSlot = proof.aggregate.data.slot
|
||||||
let vindex = validator.index.get()
|
let vindex = validator.index.get()
|
||||||
|
|
||||||
|
debug "Sending aggregated attestation",
|
||||||
|
attestation = shortLog(signedProof.message.aggregate),
|
||||||
|
validator = shortLog(validator), validator_index = vindex,
|
||||||
|
aggregationSlot = aggregationSlot,
|
||||||
|
delay = vc.getDelay(seconds((int64(SECONDS_PER_SLOT) div 3) * 2))
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
try:
|
try:
|
||||||
await vc.publishAggregateAndProofs(@[signedProof])
|
await vc.publishAggregateAndProofs(@[signedProof])
|
||||||
|
@ -277,24 +288,22 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef,
|
||||||
committee_index: CommitteeIndex,
|
committee_index: CommitteeIndex,
|
||||||
duties: seq[DutyAndProof]) {.async.} =
|
duties: seq[DutyAndProof]) {.async.} =
|
||||||
let vc = service.client
|
let vc = service.client
|
||||||
let aggregateTime =
|
|
||||||
# chronos.Duration substraction could not return negative value, in such
|
|
||||||
# case it will return `ZeroDuration`.
|
|
||||||
vc.beaconClock.durationToNextSlot() - seconds(int64(SECONDS_PER_SLOT) div 3)
|
|
||||||
|
|
||||||
# Waiting for blocks to be published before attesting.
|
# Waiting for blocks to be published before attesting.
|
||||||
# TODO (cheatfate): Here should be present timeout.
|
|
||||||
let startTime = Moment.now()
|
let startTime = Moment.now()
|
||||||
await vc.waitForBlockPublished(slot)
|
try:
|
||||||
let dur = Moment.now() - startTime
|
let timeout = seconds(int64(SECONDS_PER_SLOT) div 3) # 4.seconds in mainnet
|
||||||
debug "Block proposal awaited", slot = slot, duration = dur
|
await vc.waitForBlockPublished(slot).wait(timeout)
|
||||||
|
let dur = Moment.now() - startTime
|
||||||
|
debug "Block proposal awaited", slot = slot, duration = dur
|
||||||
|
except AsyncTimeoutError:
|
||||||
|
let dur = Moment.now() - startTime
|
||||||
|
debug "Block was not produced in time", slot = slot, duration = dur
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
|
let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
|
||||||
debug "Producing attestations", delay = delay, slot = slot,
|
debug "Producing attestations", delay = delay, slot = slot,
|
||||||
committee_index = committee_index,
|
committee_index = committee_index,
|
||||||
duties_count = len(duties)
|
duties_count = len(duties)
|
||||||
|
|
||||||
let ad =
|
let ad =
|
||||||
try:
|
try:
|
||||||
await service.produceAndPublishAttestations(slot, committee_index, duties)
|
await service.produceAndPublishAttestations(slot, committee_index, duties)
|
||||||
|
@ -308,6 +317,10 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef,
|
||||||
err_name = exc.name, err_msg = exc.msg
|
err_name = exc.name, err_msg = exc.msg
|
||||||
return
|
return
|
||||||
|
|
||||||
|
let aggregateTime =
|
||||||
|
# chronos.Duration substraction could not return negative value, in such
|
||||||
|
# case it will return `ZeroDuration`.
|
||||||
|
vc.beaconClock.durationToNextSlot() - seconds(int64(SECONDS_PER_SLOT) div 3)
|
||||||
if aggregateTime != ZeroDuration:
|
if aggregateTime != ZeroDuration:
|
||||||
await sleepAsync(aggregateTime)
|
await sleepAsync(aggregateTime)
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,11 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
|
||||||
let signedBlock =
|
let signedBlock =
|
||||||
phase0.SignedBeaconBlock(message: beaconBlock, root: blockRoot,
|
phase0.SignedBeaconBlock(message: beaconBlock, root: blockRoot,
|
||||||
signature: signature)
|
signature: signature)
|
||||||
|
|
||||||
|
debug "Sending block", blck = shortLog(signedBlock.message),
|
||||||
|
signature = shortLog(signature), blockRoot = shortLog(blockRoot),
|
||||||
|
validator = shortLog(validator)
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
try:
|
try:
|
||||||
await vc.publishBlock(signedBlock)
|
await vc.publishBlock(signedBlock)
|
||||||
|
@ -69,15 +74,15 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
|
||||||
return
|
return
|
||||||
if res:
|
if res:
|
||||||
notice "Block published", blck = shortLog(signedBlock.message),
|
notice "Block published", blck = shortLog(signedBlock.message),
|
||||||
blockRoot = shortLog(blockRoot), validator = shortLog(validator),
|
blockRoot = shortLog(blockRoot), validator = shortLog(validator),
|
||||||
validator_index = validator.index.get()
|
validator_index = validator.index.get()
|
||||||
else:
|
else:
|
||||||
warn "Block was not accepted by beacon node",
|
warn "Block was not accepted by beacon node",
|
||||||
blck = shortLog(signedBlock.message),
|
blck = shortLog(signedBlock.message),
|
||||||
blockRoot = shortLog(blockRoot),
|
blockRoot = shortLog(blockRoot),
|
||||||
validator = shortLog(validator),
|
validator = shortLog(validator),
|
||||||
validator_index = validator.index.get(),
|
validator_index = validator.index.get(),
|
||||||
wall_slot = currentSlot
|
wall_slot = currentSlot
|
||||||
else:
|
else:
|
||||||
warn "Slashing protection activated for block proposal",
|
warn "Slashing protection activated for block proposal",
|
||||||
blck = shortLog(beaconBlock), blockRoot = shortLog(blockRoot),
|
blck = shortLog(beaconBlock), blockRoot = shortLog(blockRoot),
|
||||||
|
|
|
@ -1,23 +1,70 @@
|
||||||
import common, api
|
import std/algorithm
|
||||||
import chronicles
|
import chronicles
|
||||||
|
import common, api
|
||||||
|
|
||||||
logScope: service = "fork_service"
|
logScope: service = "fork_service"
|
||||||
|
|
||||||
proc pollForFork(vc: ValidatorClientRef) {.async.} =
|
proc validateForkSchedule(forks: openarray[Fork]): bool {.raises: [Defect].} =
|
||||||
let fork =
|
# Check if `forks` list is linked list.
|
||||||
try:
|
var current_version = forks[0].current_version
|
||||||
await vc.getHeadStateFork()
|
for index, item in forks.pairs():
|
||||||
except ValidatorApiError as exc:
|
if index > 0:
|
||||||
error "Unable to retrieve head state's fork", reason = exc.msg
|
if item.previous_version != current_version:
|
||||||
return
|
return false
|
||||||
except CatchableError as exc:
|
else:
|
||||||
error "Unexpected error occured while getting fork information",
|
if item.previous_version != item.current_version:
|
||||||
err_name = exc.name, err_msg = exc.msg
|
return false
|
||||||
return
|
current_version = item.current_version
|
||||||
|
true
|
||||||
|
|
||||||
if vc.fork.isNone() or vc.fork.get() != fork:
|
proc getCurrentFork(forks: openarray[Fork],
|
||||||
vc.fork = some(fork)
|
epoch: Epoch): Result[Fork, cstring] {.raises: [Defect].} =
|
||||||
notice "Fork update success", fork = fork
|
proc cmp(x, y: Fork): int {.closure.} =
|
||||||
|
if uint64(x.epoch) == uint64(y.epoch): return 0
|
||||||
|
if uint64(x.epoch) < uint64(y.epoch): return -1
|
||||||
|
return 1
|
||||||
|
|
||||||
|
let sortedForks = sorted(forks, cmp)
|
||||||
|
if len(sortedForks) == 0:
|
||||||
|
return err("Empty fork schedule")
|
||||||
|
if not(validateForkSchedule(sortedForks)):
|
||||||
|
return err("Invalid fork schedule")
|
||||||
|
var res: Fork
|
||||||
|
for item in sortedForks:
|
||||||
|
res = item
|
||||||
|
if item.epoch > epoch:
|
||||||
|
break
|
||||||
|
ok(res)
|
||||||
|
|
||||||
|
proc pollForFork(vc: ValidatorClientRef) {.async.} =
|
||||||
|
let sres = vc.getCurrentSlot()
|
||||||
|
if sres.isSome():
|
||||||
|
let
|
||||||
|
currentSlot = sres.get()
|
||||||
|
currentEpoch = currentSlot.epoch()
|
||||||
|
|
||||||
|
let forks =
|
||||||
|
try:
|
||||||
|
await vc.getForkSchedule()
|
||||||
|
except ValidatorApiError as exc:
|
||||||
|
error "Unable to retrieve fork schedule", reason = exc.msg
|
||||||
|
return
|
||||||
|
except CatchableError as exc:
|
||||||
|
error "Unexpected error occured while getting fork information",
|
||||||
|
err_name = exc.name, err_msg = exc.msg
|
||||||
|
return
|
||||||
|
|
||||||
|
let fork =
|
||||||
|
block:
|
||||||
|
let res = getCurrentFork(forks, currentEpoch)
|
||||||
|
if res.isErr():
|
||||||
|
error "Invalid fork schedule received", reason = res.error()
|
||||||
|
return
|
||||||
|
res.get()
|
||||||
|
|
||||||
|
if vc.fork.isNone() or (vc.fork.get() != fork):
|
||||||
|
vc.fork = some(fork)
|
||||||
|
notice "Fork update succeeded", fork = fork
|
||||||
|
|
||||||
proc waitForNextEpoch(service: ForkServiceRef) {.async.} =
|
proc waitForNextEpoch(service: ForkServiceRef) {.async.} =
|
||||||
let vc = service.client
|
let vc = service.client
|
||||||
|
|
|
@ -54,6 +54,10 @@ declarePublicGauge(attached_validator_balance_total,
|
||||||
|
|
||||||
logScope: topics = "beacval"
|
logScope: topics = "beacval"
|
||||||
|
|
||||||
|
type
|
||||||
|
SendResult* = Result[void, cstring]
|
||||||
|
SendBlockResult* = Result[bool, cstring]
|
||||||
|
|
||||||
proc findValidator(validators: auto, pubKey: ValidatorPubKey):
|
proc findValidator(validators: auto, pubKey: ValidatorPubKey):
|
||||||
Option[ValidatorIndex] =
|
Option[ValidatorIndex] =
|
||||||
let idx = validators.findIt(it.pubKey == pubKey)
|
let idx = validators.findIt(it.pubKey == pubKey)
|
||||||
|
@ -170,7 +174,7 @@ proc sendAttestation*(
|
||||||
|
|
||||||
return case ok
|
return case ok
|
||||||
of ValidationResult.Accept:
|
of ValidationResult.Accept:
|
||||||
node.network.sendAttestation(subnet_id, attestation)
|
node.network.broadcastAttestation(subnet_id, attestation)
|
||||||
beacon_attestations_sent.inc()
|
beacon_attestations_sent.inc()
|
||||||
true
|
true
|
||||||
else:
|
else:
|
||||||
|
@ -179,22 +183,6 @@ proc sendAttestation*(
|
||||||
result = $ok
|
result = $ok
|
||||||
false
|
false
|
||||||
|
|
||||||
proc sendAttestation*(node: BeaconNode, attestation: Attestation): Future[bool] =
|
|
||||||
# For the validator API, which doesn't supply the subnet id.
|
|
||||||
let attestationBlck =
|
|
||||||
node.dag.getRef(attestation.data.beacon_block_root)
|
|
||||||
if attestationBlck.isNil:
|
|
||||||
debug "Attempt to send attestation without corresponding block"
|
|
||||||
return
|
|
||||||
let
|
|
||||||
epochRef = node.dag.getEpochRef(
|
|
||||||
attestationBlck, attestation.data.target.epoch)
|
|
||||||
subnet_id = compute_subnet_for_attestation(
|
|
||||||
get_committee_count_per_slot(epochRef), attestation.data.slot,
|
|
||||||
attestation.data.index.CommitteeIndex)
|
|
||||||
|
|
||||||
node.sendAttestation(attestation, subnet_id, checkSignature = true)
|
|
||||||
|
|
||||||
proc createAndSendAttestation(node: BeaconNode,
|
proc createAndSendAttestation(node: BeaconNode,
|
||||||
fork: Fork,
|
fork: Fork,
|
||||||
genesis_validators_root: Eth2Digest,
|
genesis_validators_root: Eth2Digest,
|
||||||
|
@ -228,9 +216,9 @@ proc createAndSendAttestation(node: BeaconNode,
|
||||||
else:
|
else:
|
||||||
($(wallTime - deadline), toFloatSeconds(wallTime - deadline))
|
($(wallTime - deadline), toFloatSeconds(wallTime - deadline))
|
||||||
|
|
||||||
notice "Attestation sent", attestation = shortLog(attestation),
|
notice "Attestation sent",
|
||||||
validator = shortLog(validator), delay = delayStr,
|
attestation = shortLog(attestation), validator = shortLog(validator),
|
||||||
indexInCommittee = indexInCommittee
|
delay = delayStr
|
||||||
|
|
||||||
beacon_attestation_sent_delay.observe(delaySecs)
|
beacon_attestation_sent_delay.observe(delaySecs)
|
||||||
except CatchableError as exc:
|
except CatchableError as exc:
|
||||||
|
@ -731,3 +719,105 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
|
||||||
let finalizedEpochRef = node.dag.getFinalizedEpochRef()
|
let finalizedEpochRef = node.dag.getFinalizedEpochRef()
|
||||||
discard node.eth1Monitor.trackFinalizedState(
|
discard node.eth1Monitor.trackFinalizedState(
|
||||||
finalizedEpochRef.eth1_data, finalizedEpochRef.eth1_deposit_index)
|
finalizedEpochRef.eth1_data, finalizedEpochRef.eth1_deposit_index)
|
||||||
|
|
||||||
|
proc sendAttestation*(node: BeaconNode,
|
||||||
|
attestation: Attestation): Future[SendResult] {.async.} =
|
||||||
|
# REST/JSON-RPC API helper procedure.
|
||||||
|
let attestationBlock =
|
||||||
|
block:
|
||||||
|
let res = node.dag.getRef(attestation.data.beacon_block_root)
|
||||||
|
if isNil(res):
|
||||||
|
debug "Attempt to send attestation without corresponding block",
|
||||||
|
attestation = shortLog(attestation)
|
||||||
|
return SendResult.err(
|
||||||
|
"Attempt to send attestation without corresponding block")
|
||||||
|
res
|
||||||
|
let
|
||||||
|
epochRef = node.dag.getEpochRef(
|
||||||
|
attestationBlock, attestation.data.target.epoch)
|
||||||
|
subnet_id = compute_subnet_for_attestation(
|
||||||
|
get_committee_count_per_slot(epochRef), attestation.data.slot,
|
||||||
|
attestation.data.index.CommitteeIndex)
|
||||||
|
res = await node.sendAttestation(attestation, subnet_id,
|
||||||
|
checkSignature = true)
|
||||||
|
if not(res):
|
||||||
|
return SendResult.err("Attestation failed validation")
|
||||||
|
return SendResult.ok()
|
||||||
|
|
||||||
|
proc sendAggregateAndProof*(node: BeaconNode,
|
||||||
|
proof: SignedAggregateAndProof): Future[SendResult] {.
|
||||||
|
async.} =
|
||||||
|
# REST/JSON-RPC API helper procedure.
|
||||||
|
let res = await node.processor.aggregateValidator(proof)
|
||||||
|
case res
|
||||||
|
of ValidationResult.Accept:
|
||||||
|
node.network.broadcastAggregateAndProof(proof)
|
||||||
|
return SendResult.ok()
|
||||||
|
else:
|
||||||
|
notice "Aggregate and proof failed validation",
|
||||||
|
proof = shortLog(proof.message.aggregate), result = $res
|
||||||
|
return SendResult.err("Aggregate and proof failed validation")
|
||||||
|
|
||||||
|
proc sendVoluntaryExit*(node: BeaconNode,
|
||||||
|
exit: SignedVoluntaryExit): SendResult =
|
||||||
|
# REST/JSON-RPC API helper procedure.
|
||||||
|
let res = node.processor[].voluntaryExitValidator(exit)
|
||||||
|
case res
|
||||||
|
of ValidationResult.Accept:
|
||||||
|
node.network.broadcastVoluntaryExit(exit)
|
||||||
|
ok()
|
||||||
|
else:
|
||||||
|
notice "Voluntary exit request failed validation",
|
||||||
|
exit = shortLog(exit.message), result = $res
|
||||||
|
err("Voluntary exit request failed validation")
|
||||||
|
|
||||||
|
proc sendAttesterSlashing*(node: BeaconNode,
|
||||||
|
slashing: AttesterSlashing): SendResult =
|
||||||
|
# REST/JSON-RPC API helper procedure.
|
||||||
|
let res = node.processor[].attesterSlashingValidator(slashing)
|
||||||
|
case res
|
||||||
|
of ValidationResult.Accept:
|
||||||
|
node.network.broadcastAttesterSlashing(slashing)
|
||||||
|
ok()
|
||||||
|
else:
|
||||||
|
notice "Attester slashing request failed validation",
|
||||||
|
slashing = shortLog(slashing), result = $res
|
||||||
|
err("Attester slashing request failed validation")
|
||||||
|
|
||||||
|
proc sendProposerSlashing*(node: BeaconNode,
|
||||||
|
slashing: ProposerSlashing): SendResult =
|
||||||
|
# REST/JSON-RPC API helper procedure.
|
||||||
|
let res = node.processor[].proposerSlashingValidator(slashing)
|
||||||
|
case res
|
||||||
|
of ValidationResult.Accept:
|
||||||
|
node.network.broadcastProposerSlashing(slashing)
|
||||||
|
else:
|
||||||
|
notice "Proposer slashing request failed validation",
|
||||||
|
slashing = shortLog(slashing), result = $res
|
||||||
|
return SendResult.err("Proposer slashing request failed validation")
|
||||||
|
|
||||||
|
proc sendBeaconBlock*(node: BeaconNode, forked: ForkedSignedBeaconBlock
|
||||||
|
): Future[SendBlockResult] {.async.} =
|
||||||
|
# REST/JSON-RPC API helper procedure.
|
||||||
|
let head = node.dag.head
|
||||||
|
if not(node.isSynced(head)):
|
||||||
|
return SendBlockResult.err("Beacon node is currently syncing")
|
||||||
|
if head.slot >= forked.slot():
|
||||||
|
node.network.broadcastBeaconBlock(forked)
|
||||||
|
return SendBlockResult.ok(false)
|
||||||
|
let res =
|
||||||
|
case forked.kind
|
||||||
|
of BeaconBlockFork.Phase0:
|
||||||
|
await node.proposeSignedBlock(head, AttachedValidator(),
|
||||||
|
forked.phase0Block)
|
||||||
|
of BeaconBlockFork.Altair:
|
||||||
|
# TODO altair-transition
|
||||||
|
# await node.proposeSignedBlock(head, AttachedValidator(),
|
||||||
|
# forked.altairBlock)
|
||||||
|
head
|
||||||
|
if res == head:
|
||||||
|
# `res == head` means failure, in such case we need to broadcast block
|
||||||
|
# manually because of the specification.
|
||||||
|
node.network.broadcastBeaconBlock(forked)
|
||||||
|
return SendBlockResult.ok(false)
|
||||||
|
return SendBlockResult.ok(true)
|
||||||
|
|
Loading…
Reference in New Issue