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:
Eugene Kabanov 2021-08-23 13:41:48 +03:00 committed by GitHub
parent 0a61d1112e
commit 66cb18d69b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 774 additions and 441 deletions

View File

@ -2061,8 +2061,8 @@ func forkDigestAtEpoch(node: Eth2Node, epoch: Epoch): ForkDigest =
proc getWallEpoch(node: Eth2Node): Epoch =
node.getBeaconTime().slotOrZero.epoch
proc sendAttestation*(
node: Eth2Node, subnet_id: SubnetId, attestation: Attestation) =
proc broadcastAttestation*(node: Eth2Node, subnet_id: SubnetId,
attestation: 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
# 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
# should be sent.
let forkPrefix = node.forkDigestAtEpoch(node.getWallEpoch)
node.broadcast(
getAttestationTopic(forkPrefix, subnet_id),
attestation)
let topic = getAttestationTopic(forkPrefix, subnet_id)
node.broadcast(topic, attestation)
proc sendVoluntaryExit*(node: Eth2Node, exit: SignedVoluntaryExit) =
proc broadcastVoluntaryExit*(node: Eth2Node, exit: SignedVoluntaryExit) =
let exitsTopic = getVoluntaryExitsTopic(
node.forkDigestAtEpoch(node.getWallEpoch))
node.broadcast(exitsTopic, exit)
proc sendAttesterSlashing*(node: Eth2Node, slashing: AttesterSlashing) =
proc broadcastAttesterSlashing*(node: Eth2Node, slashing: AttesterSlashing) =
let attesterSlashingsTopic = getAttesterSlashingsTopic(
node.forkDigestAtEpoch(node.getWallEpoch))
node.broadcast(attesterSlashingsTopic, slashing)
proc sendProposerSlashing*(node: Eth2Node, slashing: ProposerSlashing) =
proc broadcastProposerSlashing*(node: Eth2Node, slashing: ProposerSlashing) =
let proposerSlashingsTopic = getProposerSlashingsTopic(
node.forkDigestAtEpoch(node.getWallEpoch))
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)

View File

@ -9,8 +9,7 @@ import
chronicles,
nimcrypto/utils as ncrutils,
../beacon_node_common, ../networking/eth2_network,
../consensus_object_pools/[blockchain_dag, exit_pool],
../gossip_processing/gossip_validation,
../consensus_object_pools/[blockchain_dag, exit_pool, spec_cache],
../validators/validator_duties,
../spec/[eth2_merkleization, forks, network],
../spec/datatypes/[phase0, altair],
@ -105,7 +104,7 @@ proc getBeaconBlocksTopic(node: BeaconNode, kind: BeaconBlockFork): string =
getBeaconBlocksTopic(node.dag.forkDigests.altair)
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:
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 (
state_id: StateIdent) -> RestApiResponse:
let bslot =
@ -133,7 +132,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonResponse((root: stateRoot))
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 (
state_id: StateIdent) -> RestApiResponse:
let bslot =
@ -159,7 +158,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
)
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,
"/api/eth/v1/beacon/states/{state_id}/finality_checkpoints") do (
state_id: StateIdent) -> RestApiResponse:
@ -185,7 +184,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
)
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 (
state_id: StateIdent, id: seq[ValidatorIdent],
status: seq[ValidatorFilter]) -> RestApiResponse:
@ -276,7 +275,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
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,
"/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}") do (
state_id: StateIdent, validator_id: ValidatorIdent) -> RestApiResponse:
@ -353,7 +352,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
ValidatorStatusNotFoundError)
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,
"/api/eth/v1/beacon/states/{state_id}/validator_balances") do (
state_id: StateIdent, id: seq[ValidatorIdent]) -> RestApiResponse:
@ -427,7 +426,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
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,
"/api/eth/v1/beacon/states/{state_id}/committees") do (
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)
# 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 (
slot: Option[Slot], parent_root: Option[Eth2Digest]) -> RestApiResponse:
# 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 (
block_id: BlockIdent) -> RestApiResponse:
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 (
contentBody: Option[ContentBody]) -> RestApiResponse:
let blockData =
let forked =
block:
if contentBody.isNone():
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
@ -641,37 +640,14 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
$phase0res.error())
let head = node.dag.head
if not(node.isSynced(head)):
let res = await node.sendBeaconBlock(forked)
if res.isErr():
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
if head.slot >= blockData.slot():
let blocksTopic = node.getBeaconBlocksTopic(blockData.kind)
withBlck(blockData):
node.network.broadcast(blocksTopic, blck)
if not(res.get()):
return RestApiResponse.jsonError(Http202, BlockValidationError)
else:
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:
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
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
router.api(MethodGet, "/api/eth/v1/beacon/blocks/{block_id}") do (
block_id: BlockIdent) -> RestApiResponse:
let bdata =
@ -690,7 +666,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
of BeaconBlockFork.Altair:
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 (
block_id: BlockIdent) -> RestApiResponse:
let bdata =
@ -713,7 +689,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
(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 (
block_id: BlockIdent) -> RestApiResponse:
let bdata =
@ -729,7 +705,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
withBlck(bdata.data):
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,
"/api/eth/v1/beacon/blocks/{block_id}/attestations") do (
block_id: BlockIdent) -> RestApiResponse:
@ -746,7 +722,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
withBlck(bdata.data):
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 (
slot: Option[Slot],
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
@ -774,7 +750,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.add(item)
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 (
contentBody: Option[ContentBody]) -> RestApiResponse:
let attestations =
@ -788,13 +764,48 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
$dres.error())
dres.get()
var failures: seq[RestAttestationsFailure]
for atindex, attestation in attestations.pairs():
debug "Attestation for pool", attestation = attestation,
signature = $attestation.signature
if not await node.sendAttestation(attestation):
failures.add(RestAttestationsFailure(
index: uint64(atindex), message: "Attestation failed validation"))
proc processAttestation(a: Attestation): Future[SendResult] {.async.} =
let res = await node.sendAttestation(a)
if res.isErr():
return res
let
wallTime = node.processor.getCurrentBeaconTime()
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:
return RestApiResponse.jsonErrorList(Http400, AttestationValidationError,
@ -802,7 +813,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
else:
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 (
) -> RestApiResponse:
var res: seq[AttesterSlashing]
@ -814,7 +825,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.add(item)
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 (
contentBody: Option[ContentBody]) -> RestApiResponse:
let slashing =
@ -826,17 +837,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400,
InvalidAttesterSlashingObjectError,
$dres.error())
let res = dres.get()
let vres = node.exitPool[].validateAttesterSlashing(res)
if vres.isErr():
dres.get()
let res = node.sendAttesterSlashing(slashing)
if res.isErr():
return RestApiResponse.jsonError(Http400,
AttesterSlashingValidationError,
$vres.error())
res
node.network.sendAttesterSlashing(slashing)
$res.error())
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 (
) -> RestApiResponse:
var res: seq[ProposerSlashing]
@ -848,7 +857,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.add(item)
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 (
contentBody: Option[ContentBody]) -> RestApiResponse:
let slashing =
@ -860,17 +869,15 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400,
InvalidProposerSlashingObjectError,
$dres.error())
let res = dres.get()
let vres = node.exitPool[].validateProposerSlashing(res)
if vres.isErr():
dres.get()
let res = node.sendProposerSlashing(slashing)
if res.isErr():
return RestApiResponse.jsonError(Http400,
ProposerSlashingValidationError,
$vres.error())
res
node.network.sendProposerSlashing(slashing)
$res.error())
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 (
) -> RestApiResponse:
var res: seq[SignedVoluntaryExit]
@ -882,7 +889,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.add(item)
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 (
contentBody: Option[ContentBody]) -> RestApiResponse:
let exit =
@ -894,14 +901,12 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http400,
InvalidVoluntaryExitObjectError,
$dres.error())
let res = dres.get()
let vres = node.exitPool[].validateVoluntaryExit(res)
if vres.isErr():
dres.get()
let res = node.sendVoluntaryExit(exit)
if res.isErr():
return RestApiResponse.jsonError(Http400,
VoluntaryExitValidationError,
$vres.error())
res
node.network.sendVoluntaryExit(exit)
$res.error())
return RestApiResponse.jsonMsgResponse(VoluntaryExitValidationSuccess)
router.redirect(

View File

@ -4,29 +4,21 @@
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
stew/[endians2, base10],
presto,
chronicles,
nimcrypto/utils as ncrutils,
../beacon_node_common, ../eth1/eth1_monitor,
../spec/forks,
./rest_utils
import stew/[endians2, base10], presto, chronicles,
nimcrypto/utils as ncrutils
import ".."/beacon_node_common,
".."/eth1/eth1_monitor,
".."/spec/forks,
"."/rest_utils
logScope: topics = "rest_config"
proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
router.api(MethodGet,
"/api/eth/v1/config/fork_schedule") do () -> RestApiResponse:
# TODO: Implemenation needs a fix, when forks infrastructure will be
# established.
return RestApiResponse.jsonResponse(
[getStateField(node.dag.headState.data, fork)]
)
router.api(MethodGet,
"/api/eth/v1/config/spec") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(
let
cachedForkSchedule =
RestApiResponse.prepareJsonResponse(getForkSchedule(node.dag.cfg))
cachedConfigSpec =
RestApiResponse.prepareJsonResponse(
(
CONFIG_NAME:
const_preset,
@ -43,9 +35,7 @@ proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
SHUFFLE_ROUND_COUNT:
Base10.toString(SHUFFLE_ROUND_COUNT),
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
Base10.toString(
node.dag.cfg.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:
@ -101,8 +91,7 @@ proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
SLOTS_PER_HISTORICAL_ROOT:
Base10.toString(SLOTS_PER_HISTORICAL_ROOT),
MIN_VALIDATOR_WITHDRAWABILITY_DELAY:
Base10.toString(
node.dag.cfg.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:
@ -153,16 +142,32 @@ proc installConfigApiHandlers*(router: var RestRouter, node: BeaconNode) =
"0x" & ncrutils.toHex(uint32(DOMAIN_AGGREGATE_AND_PROOF).toBytesLE())
)
)
router.api(MethodGet,
"/api/eth/v1/config/deposit_contract") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(
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,
"/api/eth/v1/config/fork_schedule") do () -> RestApiResponse:
return RestApiResponse.response(cachedForkSchedule, Http200,
"application/json")
# https://ethereum.github.io/beacon-APIs/#/Config/getSpec
router.api(MethodGet,
"/api/eth/v1/config/spec") do () -> RestApiResponse:
return RestApiResponse.response(cachedConfigSpec, Http200,
"application/json")
# https://ethereum.github.io/beacon-APIs/#/Config/getDepositContract
router.api(MethodGet,
"/api/eth/v1/config/deposit_contract") do () -> RestApiResponse:
return RestApiResponse.response(cachedDepositContract, Http200,
"application/json")
router.redirect(
MethodGet,
"/eth/v1/config/fork_schedule",

View File

@ -1,13 +1,13 @@
import
std/sequtils,
presto,
chronicles,
../version, ../beacon_node_common,
./rest_utils
import std/sequtils
import presto, chronicles
import ".."/[version, beacon_node_common],
".."/spec/forks,
"."/rest_utils
logScope: topics = "rest_debug"
proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Debug/getState
router.api(MethodGet,
"/api/eth/v1/debug/beacon/states/{state_id}") do (
state_id: StateIdent) -> RestApiResponse:
@ -22,9 +22,40 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
$bres.error())
bres.get()
node.withStateForBlockSlot(bslot):
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)
# 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,
"/api/eth/v1/debug/beacon/heads") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(
@ -41,6 +72,11 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
"/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] {.
rest, endpoint: "/eth/v1/debug/beacon/heads",

View File

@ -49,6 +49,7 @@ proc validateEventTopics(events: seq[EventTopic]): Result[EventTopics,
ok(res)
proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Events/eventstream
router.api(MethodGet, "/api/eth/v1/events") do (
topics: seq[EventTopic]) -> RestApiResponse:
# TODO (cheatfate): This call is not fully implemented yet, because there

View File

@ -126,6 +126,7 @@ proc getP2PAddresses(node: BeaconNode): Option[seq[string]] =
return some(addresses)
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:
let discoveryAddresses =
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 (
state: seq[PeerStateKind],
direction: seq[PeerDirectKind]) -> RestApiResponse:
@ -198,6 +200,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.add(peer)
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:
var res: RestNodePeerCount
for item in node.network.peers.values():
@ -214,6 +217,7 @@ proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
discard
return RestApiResponse.jsonResponse(res)
# https://ethereum.github.io/beacon-APIs/#/Node/getPeer
router.api(MethodGet, "/api/eth/v1/node/peers/{peer_id}") do (
peer_id: PeerID) -> RestApiResponse:
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:
return RestApiResponse.jsonResponse(
(version: "Nimbus/" & fullVersionStr)
)
# https://ethereum.github.io/beacon-APIs/#/Node/getSyncingStatus
router.api(MethodGet, "/api/eth/v1/node/syncing") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(node.syncManager.getInfo())
# https://ethereum.github.io/beacon-APIs/#/Node/getHealth
router.api(MethodGet, "/api/eth/v1/node/health") do () -> RestApiResponse:
# TODO: Add ability to detect node's issues and return 503 error according
# to specification.

View File

@ -11,7 +11,6 @@ import
../spec/datatypes/[phase0],
../beacon_node_common, ../networking/eth2_network,
../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool],
../gossip_processing/gossip_validation,
../validators/validator_duties,
../spec/[forks, network],
./rest_utils
@ -19,7 +18,7 @@ import
logScope: topics = "rest_validatorapi"
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 (
epoch: Epoch, contentBody: Option[ContentBody]) -> RestApiResponse:
let indexList =
@ -98,7 +97,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
res
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 (
epoch: Epoch) -> RestApiResponse:
let qepoch =
@ -142,7 +141,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
res
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 (
slot: Slot, randao_reveal: Option[ValidatorSig],
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
@ -204,6 +203,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
of BeaconBlockFork.Altair:
return RestApiResponse.jsonError(Http400, BlockProduceError)
# https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV2
router.api(MethodGet, "/api/eth/v2/validator/blocks/{slot}") do (
slot: Slot, randao_reveal: Option[ValidatorSig],
graffiti: Option[GraffitiBytes]) -> RestApiResponse:
@ -271,7 +271,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
(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 (
slot: Option[Slot],
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
@ -307,7 +307,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
makeAttestationData(epochRef, qhead.atSlot(qslot), qindex)
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 (
attestation_data_root: Option[Eth2Digest],
slot: Option[Slot]) -> RestApiResponse:
@ -339,7 +339,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.get()
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 (
contentBody: Option[ContentBody]) -> RestApiResponse:
let proofs =
@ -352,25 +352,28 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) =
InvalidAggregateAndProofObjectError,
$dres.error())
dres.get()
for item in proofs:
let wallTime = node.processor.getCurrentBeaconTime()
let res = await node.attestationPool.validateAggregate(
node.processor.batchCrypto, item, wallTime
)
# Since our validation logic supports batch processing, we will submit all
# aggregated attestations for validation.
var pending =
block:
var res: seq[Future[SendResult]]
for proof in proofs:
res.add(node.sendAggregateAndProof(proof))
res
await allFutures(pending)
for future in pending:
if future.done():
let res = future.read()
if res.isErr():
return RestApiResponse.jsonError(Http400,
AggregateAndProofValidationError,
$res.error())
node.network.broadcast(
getAggregateAndProofsTopic(node.dag.forkDigests.phase0), item)
notice "Aggregated attestation sent",
attestation = shortLog(item.message.aggregate),
signature = shortLog(item.signature)
else:
return RestApiResponse.jsonError(Http500,
"Unexpected server failure, while sending aggregate and proof")
return RestApiResponse.jsonMsgResponse(AggregateAndProofValidationSuccess)
# https://ethereum.github.io/eth2.0-APIs/#/Validator/prepareBeaconCommitteeSubnet
# https://ethereum.github.io/beacon-APIs/#/Validator/prepareBeaconCommitteeSubnet
router.api(MethodPost,
"/api/eth/v1/validator/beacon_committee_subscriptions") do (
contentBody: Option[ContentBody]) -> RestApiResponse:

View File

@ -16,7 +16,6 @@ import
../beacon_node_common,
../networking/eth2_network,
../validators/validator_duties,
../gossip_processing/gossip_validation,
../consensus_object_pools/blockchain_dag,
../spec/[eth2_merkleization, forks, network],
../spec/datatypes/[phase0],
@ -470,7 +469,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
rpcServer.rpc("post_v1_beacon_pool_attestations") do (
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 (
) -> seq[AttesterSlashing]:
@ -485,14 +485,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
rpcServer.rpc("post_v1_beacon_pool_attester_slashings") do (
slashing: AttesterSlashing) -> bool:
if isNil(node.exitPool):
raise newException(CatchableError, "Exit pool is not yet available!")
let validity = node.exitPool[].validateAttesterSlashing(slashing)
if validity.isOk:
node.network.sendAttesterSlashing(slashing)
else:
raise newException(CatchableError, $(validity.error[1]))
return true
let res = node.sendAttesterSlashing(slashing)
return res.isOk()
rpcServer.rpc("get_v1_beacon_pool_proposer_slashings") do (
) -> seq[ProposerSlashing]:
@ -507,14 +501,8 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
rpcServer.rpc("post_v1_beacon_pool_proposer_slashings") do (
slashing: ProposerSlashing) -> bool:
if isNil(node.exitPool):
raise newException(CatchableError, "Exit pool is not yet available!")
let validity = node.exitPool[].validateProposerSlashing(slashing)
if validity.isOk:
node.network.sendProposerSlashing(slashing)
else:
raise newException(CatchableError, $(validity.error[1]))
return true
let res = node.sendProposerSlashing(slashing)
return res.isOk()
rpcServer.rpc("get_v1_beacon_pool_voluntary_exits") do (
) -> seq[SignedVoluntaryExit]:
@ -529,11 +517,5 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
rpcServer.rpc("post_v1_beacon_pool_voluntary_exits") do (
exit: SignedVoluntaryExit) -> bool:
if isNil(node.exitPool):
raise newException(CatchableError, "Exit pool is not yet available!")
let validity = node.exitPool[].validateVoluntaryExit(exit)
if validity.isOk:
node.network.sendVoluntaryExit(exit)
else:
raise newException(CatchableError, $(validity.error[1]))
return true
let res = node.sendVoluntaryExit(exit)
return res.isOk()

View File

@ -59,74 +59,129 @@ type
RestAttestationError |
RestGenericError
proc jsonResponseWRoot*(t: typedesc[RestApiResponse],
data: auto,
{.push raises: [Defect].}
proc prepareJsonResponse*(t: typedesc[RestApiResponse], d: auto): seq[byte] =
let res =
block:
var default: seq[byte]
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
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()
RestApiResponse.response(stream.getOutput(seq[byte]), Http200,
"application/json")
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/json")
proc jsonResponse*(t: typedesc[RestApiResponse],
data: auto): RestApiResponse =
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()
RestApiResponse.response(stream.getOutput(seq[byte]), Http200,
"application/json")
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/json")
proc jsonResponseWMeta*(t: typedesc[RestApiResponse],
data: auto, meta: 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.writeField("meta", meta)
writer.endRecord()
RestApiResponse.response(stream.getOutput(seq[byte]), Http200,
"application/json")
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(res, Http200, "application/json")
proc jsonMsgResponse*(t: typedesc[RestApiResponse],
msg: string = ""): RestApiResponse =
let data =
block:
var default: seq[string]
var default: seq[byte]
try:
var defstrings: seq[string]
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
writer.writeField("code", "200")
writer.writeField("message", msg)
writer.writeField("stacktrace", default)
writer.writeField("stacktrace", defstrings)
writer.endRecord()
stream.getOutput(seq[byte])
except SerializationError:
default
except IOError:
default
RestApiResponse.response(data, Http200, "application/json")
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
msg: string = ""): RestApiResponse =
let data =
block:
var default: seq[string]
var default: string
try:
var defstrings: seq[string]
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
writer.writeField("code", Base10.toString(uint64(status.toInt())))
writer.writeField("message", msg)
writer.writeField("stacktrace", default)
writer.writeField("stacktrace", defstrings)
writer.endRecord()
stream.getOutput(string)
except SerializationError:
default
except IOError:
default
RestApiResponse.error(status, data, "application/json")
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
msg: string = "", stacktrace: string): RestApiResponse =
let data =
block:
var default: seq[string]
var default: string
try:
var defstrings: seq[string]
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
@ -135,9 +190,13 @@ proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
if len(stacktrace) > 0:
writer.writeField("stacktrace", [stacktrace])
else:
writer.writeField("stacktrace", default)
writer.writeField("stacktrace", defstrings)
writer.endRecord()
stream.getOutput(string)
except SerializationError:
default
except IOError:
default
RestApiResponse.error(status, data, "application/json")
proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
@ -145,6 +204,8 @@ proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
stacktraces: openarray[string]): RestApiResponse =
let data =
block:
var default: string
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
@ -153,6 +214,10 @@ proc jsonError*(t: typedesc[RestApiResponse], status: HttpCode = Http200,
writer.writeField("stacktrace", stacktraces)
writer.endRecord()
stream.getOutput(string)
except SerializationError:
default
except IOError:
default
RestApiResponse.error(status, data, "application/json")
proc jsonErrorList*(t: typedesc[RestApiResponse],
@ -160,6 +225,8 @@ proc jsonErrorList*(t: typedesc[RestApiResponse],
msg: string = "", failures: auto): RestApiResponse =
let data =
block:
var default: string
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.beginRecord()
@ -168,13 +235,18 @@ proc jsonErrorList*(t: typedesc[RestApiResponse],
writer.writeField("failures", failures)
writer.endRecord()
stream.getOutput(string)
except SerializationError:
default
except IOError:
default
RestApiResponse.error(status, data, "application/json")
template hexOriginal(data: openarray[byte]): string =
"0x" & ncrutils.toHex(data, true)
## 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))
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())
## 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]
data[0] = value
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")
## 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()))
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()))
## 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)
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)
## Eth2Digest
@ -492,10 +568,18 @@ proc encodeBytes*[T: EncodeTypes](value: T,
contentType: string): RestResult[seq[byte]] =
case contentType
of "application/json":
let data =
block:
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.writeValue(value)
ok(stream.getOutput(seq[byte]))
stream.getOutput(seq[byte])
except IOError:
return err("Input/output error")
except SerializationError:
return err("Serialization error")
ok(data)
else:
err("Content-Type not supported")
@ -503,10 +587,18 @@ proc encodeBytes*[T: EncodeArrays](value: T,
contentType: string): RestResult[seq[byte]] =
case contentType
of "application/json":
let data =
block:
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
writer.writeArray(value)
ok(stream.getOutput(seq[byte]))
stream.getOutput(seq[byte])
except IOError:
return err("Input/output error")
except SerializationError:
return err("Serialization error")
ok(data)
else:
err("Content-Type not supported")

View File

@ -277,7 +277,7 @@ type
GetDebugChainHeadsResponse* = DataEnclosedObject[seq[RestChainHead]]
GetDepositContractResponse* = DataEnclosedObject[RestDepositContract]
GetEpochCommitteesResponse* = DataEnclosedObject[RestGenesis]
GetForkScheduleResponse* = DataEnclosedObject[Fork]
GetForkScheduleResponse* = DataEnclosedObject[seq[Fork]]
GetGenesisResponse* = DataEnclosedObject[RestGenesis]
GetNetworkIdentityResponse* = DataEnclosedObject[RestNetworkIdentity]
GetPeerCountResponse* = DataMetaEnclosedObject[RestPeerCount]

View File

@ -297,3 +297,11 @@ proc nextForkEpochAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Epoch =
cfg.ALTAIR_FORK_EPOCH
else:
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()]

View File

@ -343,9 +343,10 @@ template firstSuccessTimeout*(vc: ValidatorClientRef, respType: typedesc,
RestBeaconNodeStatus.NotSynced,
RestBeaconNodeStatus.Uninitalized}
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",
online_nodes = len(onlineNodes), offline_nodes = len(offlineNodes)
online_nodes = onlineNodesCount, offline_nodes = len(offlineNodes)
var checkFut = vc.checkNodes(offlineMask)
@ -475,6 +476,30 @@ proc getAttesterDuties*(vc: ValidatorClientRef, epoch: Epoch,
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.} =
logScope: request = "getHeadStateFork"
let stateIdent = StateIdent.init(StateIdentType.Head)

View File

@ -27,6 +27,7 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
let signingRoot =
compute_attestation_root(fork, vc.beaconGenesis.genesis_validators_root,
adata)
let attestationRoot = adata.hash_tree_root()
let vindex = validator.index.get()
let notSlashable = vc.attachedValidators.slashingProtection
@ -45,6 +46,11 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
Natural(duty.data.validator_committee_index),
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 =
try:
await vc.submitPoolAttestations(@[attestation])
@ -63,19 +69,17 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
return false
let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
let indexInCommittee = duty.data.validator_committee_index
if res:
notice "Attestation published", attestation = shortLog(attestation),
validator = shortLog(validator),
validator_index = vindex,
delay = delay,
indexInCommittee = indexInCommittee
attestation_root = attestationRoot
else:
warn "Attestation was not accepted by beacon node",
attestation = shortLog(attestation),
validator = shortLog(validator),
validator_index = vindex, delay = delay,
indexInCommittee = indexInCommittee
validator_index = vindex, delay = delay
return res
proc serveAggregateAndProof*(service: AttestationServiceRef,
@ -94,6 +98,13 @@ proc serveAggregateAndProof*(service: AttestationServiceRef,
let aggregationSlot = proof.aggregate.data.slot
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 =
try:
await vc.publishAggregateAndProofs(@[signedProof])
@ -277,24 +288,22 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef,
committee_index: CommitteeIndex,
duties: seq[DutyAndProof]) {.async.} =
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.
# TODO (cheatfate): Here should be present timeout.
let startTime = Moment.now()
await vc.waitForBlockPublished(slot)
try:
let timeout = seconds(int64(SECONDS_PER_SLOT) div 3) # 4.seconds in mainnet
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:
let delay = vc.getDelay(seconds(int64(SECONDS_PER_SLOT) div 3))
debug "Producing attestations", delay = delay, slot = slot,
committee_index = committee_index,
duties_count = len(duties)
let ad =
try:
await service.produceAndPublishAttestations(slot, committee_index, duties)
@ -308,6 +317,10 @@ proc publishAttestationsAndAggregates(service: AttestationServiceRef,
err_name = exc.name, err_msg = exc.msg
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:
await sleepAsync(aggregateTime)

View File

@ -53,6 +53,11 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
let signedBlock =
phase0.SignedBeaconBlock(message: beaconBlock, root: blockRoot,
signature: signature)
debug "Sending block", blck = shortLog(signedBlock.message),
signature = shortLog(signature), blockRoot = shortLog(blockRoot),
validator = shortLog(validator)
let res =
try:
await vc.publishBlock(signedBlock)

View File

@ -1,23 +1,70 @@
import common, api
import std/algorithm
import chronicles
import common, api
logScope: service = "fork_service"
proc validateForkSchedule(forks: openarray[Fork]): bool {.raises: [Defect].} =
# Check if `forks` list is linked list.
var current_version = forks[0].current_version
for index, item in forks.pairs():
if index > 0:
if item.previous_version != current_version:
return false
else:
if item.previous_version != item.current_version:
return false
current_version = item.current_version
true
proc getCurrentFork(forks: openarray[Fork],
epoch: Epoch): Result[Fork, cstring] {.raises: [Defect].} =
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 fork =
let sres = vc.getCurrentSlot()
if sres.isSome():
let
currentSlot = sres.get()
currentEpoch = currentSlot.epoch()
let forks =
try:
await vc.getHeadStateFork()
await vc.getForkSchedule()
except ValidatorApiError as exc:
error "Unable to retrieve head state's fork", reason = exc.msg
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
if vc.fork.isNone() or vc.fork.get() != fork:
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 success", fork = fork
notice "Fork update succeeded", fork = fork
proc waitForNextEpoch(service: ForkServiceRef) {.async.} =
let vc = service.client

View File

@ -54,6 +54,10 @@ declarePublicGauge(attached_validator_balance_total,
logScope: topics = "beacval"
type
SendResult* = Result[void, cstring]
SendBlockResult* = Result[bool, cstring]
proc findValidator(validators: auto, pubKey: ValidatorPubKey):
Option[ValidatorIndex] =
let idx = validators.findIt(it.pubKey == pubKey)
@ -170,7 +174,7 @@ proc sendAttestation*(
return case ok
of ValidationResult.Accept:
node.network.sendAttestation(subnet_id, attestation)
node.network.broadcastAttestation(subnet_id, attestation)
beacon_attestations_sent.inc()
true
else:
@ -179,22 +183,6 @@ proc sendAttestation*(
result = $ok
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,
fork: Fork,
genesis_validators_root: Eth2Digest,
@ -228,9 +216,9 @@ proc createAndSendAttestation(node: BeaconNode,
else:
($(wallTime - deadline), toFloatSeconds(wallTime - deadline))
notice "Attestation sent", attestation = shortLog(attestation),
validator = shortLog(validator), delay = delayStr,
indexInCommittee = indexInCommittee
notice "Attestation sent",
attestation = shortLog(attestation), validator = shortLog(validator),
delay = delayStr
beacon_attestation_sent_delay.observe(delaySecs)
except CatchableError as exc:
@ -731,3 +719,105 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
let finalizedEpochRef = node.dag.getFinalizedEpochRef()
discard node.eth1Monitor.trackFinalizedState(
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)