adopt LC REST API with v0 suffix (without proofs) (#3775)

* adopt LC REST API with v0 suffix (without proofs)

Adopts the light client data REST API used by Lodestar as defined in
https://github.com/ethereum/beacon-APIs/pull/181 with a v0 suffix.

Requests:
- `/eth/v0/beacon/light_client/bootstrap/{block_root}`
- `/eth/v0/beacon/light_client/updates?start_period={start_period}&count={count}`
- `/eth/v0/beacon/light_client/finality_update`
- `/eth/v0/beacon/light_client/optimistic_update`

HTTP Server-Sent Events (SSE):
- `light_client_finality_update_v0`
- `light_client_optimistic_update_v0`

More work is needed to adopt the proofs endpoint, it is not included.

* initialize event queues

* register event topics
This commit is contained in:
Etan Kissling 2022-06-19 07:57:52 +02:00 committed by GitHub
parent 5439978a37
commit 61ee0611bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 194 additions and 49 deletions

View File

@ -21,7 +21,7 @@ import
./consensus_object_pools/[ ./consensus_object_pools/[
blockchain_dag, block_quarantine, exit_pool, attestation_pool, blockchain_dag, block_quarantine, exit_pool, attestation_pool,
sync_committee_msg_pool], sync_committee_msg_pool],
./spec/datatypes/base, ./spec/datatypes/[base, altair],
./sync/[optimistic_sync_light_client, sync_manager, request_manager], ./sync/[optimistic_sync_light_client, sync_manager, request_manager],
./validators/[action_tracker, validator_monitor, validator_pool], ./validators/[action_tracker, validator_monitor, validator_pool],
./rpc/state_ttl_cache ./rpc/state_ttl_cache
@ -41,6 +41,8 @@ type
blocksQueue*: AsyncEventQueue[ForkedTrustedSignedBeaconBlock] blocksQueue*: AsyncEventQueue[ForkedTrustedSignedBeaconBlock]
headQueue*: AsyncEventQueue[HeadChangeInfoObject] headQueue*: AsyncEventQueue[HeadChangeInfoObject]
reorgQueue*: AsyncEventQueue[ReorgInfoObject] reorgQueue*: AsyncEventQueue[ReorgInfoObject]
finUpdateQueue*: AsyncEventQueue[altair.LightClientFinalityUpdate]
optUpdateQueue*: AsyncEventQueue[altair.LightClientOptimisticUpdate]
attestQueue*: AsyncEventQueue[Attestation] attestQueue*: AsyncEventQueue[Attestation]
contribQueue*: AsyncEventQueue[SignedContributionAndProof] contribQueue*: AsyncEventQueue[SignedContributionAndProof]
exitQueue*: AsyncEventQueue[SignedVoluntaryExit] exitQueue*: AsyncEventQueue[SignedVoluntaryExit]

View File

@ -161,9 +161,9 @@ proc loadChainDag(
proc onChainReorg(data: ReorgInfoObject) = proc onChainReorg(data: ReorgInfoObject) =
eventBus.reorgQueue.emit(data) eventBus.reorgQueue.emit(data)
proc onLightClientFinalityUpdate(data: altair.LightClientFinalityUpdate) = proc onLightClientFinalityUpdate(data: altair.LightClientFinalityUpdate) =
discard eventBus.finUpdateQueue.emit(data)
proc onLightClientOptimisticUpdate(data: altair.LightClientOptimisticUpdate) = proc onLightClientOptimisticUpdate(data: altair.LightClientOptimisticUpdate) =
discard eventBus.optUpdateQueue.emit(data)
let let
chainDagFlags = chainDagFlags =
@ -374,6 +374,8 @@ proc init*(T: type BeaconNode,
blocksQueue: newAsyncEventQueue[ForkedTrustedSignedBeaconBlock](), blocksQueue: newAsyncEventQueue[ForkedTrustedSignedBeaconBlock](),
headQueue: newAsyncEventQueue[HeadChangeInfoObject](), headQueue: newAsyncEventQueue[HeadChangeInfoObject](),
reorgQueue: newAsyncEventQueue[ReorgInfoObject](), reorgQueue: newAsyncEventQueue[ReorgInfoObject](),
finUpdateQueue: newAsyncEventQueue[altair.LightClientFinalityUpdate](),
optUpdateQueue: newAsyncEventQueue[altair.LightClientOptimisticUpdate](),
attestQueue: newAsyncEventQueue[Attestation](), attestQueue: newAsyncEventQueue[Attestation](),
contribQueue: newAsyncEventQueue[SignedContributionAndProof](), contribQueue: newAsyncEventQueue[SignedContributionAndProof](),
exitQueue: newAsyncEventQueue[SignedVoluntaryExit](), exitQueue: newAsyncEventQueue[SignedVoluntaryExit](),
@ -1315,6 +1317,8 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
restServer.router.installNimbusApiHandlers(node) restServer.router.installNimbusApiHandlers(node)
restServer.router.installNodeApiHandlers(node) restServer.router.installNodeApiHandlers(node)
restServer.router.installValidatorApiHandlers(node) restServer.router.installValidatorApiHandlers(node)
if node.dag.lightClientDataServe:
restServer.router.installLightClientApiHandlers(node)
proc installMessageValidators(node: BeaconNode) = proc installMessageValidators(node: BeaconNode) =
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#attestations-and-aggregation # https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#attestations-and-aggregation

View File

@ -1,22 +1,28 @@
# Copyright (c) 2018-2021 Status Research & Development GmbH # beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
## The `rest_api` module is a server implementation for the common REST API for ## The `rest_api` module is a server implementation for the common REST API for
## Ethereum 2 found at https://ethereum.github.io/eth2.0-APIs/# ## Ethereum 2 found at https://ethereum.github.io/eth2.0-APIs/#
## along with several nimbus-specific extensions. It is used by the validator ## along with several nimbus-specific extensions. It is used by the validator
## client as well as many community utilities. ## client as well as many community utilities.
## A corresponding client can be found in the ## A corresponding client can be found in the
## `spec/eth2_apis/rest_beacon_client` module ## `spec/eth2_apis/rest_beacon_client` module
import import
"."/[ "."/[
rest_utils, rest_utils,
rest_beacon_api, rest_config_api, rest_debug_api, rest_event_api, rest_beacon_api, rest_config_api, rest_debug_api, rest_event_api,
rest_nimbus_api, rest_node_api, rest_validator_api, rest_key_management_api] rest_key_management_api, rest_light_client_api, rest_nimbus_api,
rest_node_api, rest_validator_api]
export export
rest_utils, rest_utils,
rest_beacon_api, rest_config_api, rest_debug_api, rest_event_api, rest_beacon_api, rest_config_api, rest_debug_api, rest_event_api,
rest_nimbus_api, rest_node_api, rest_validator_api, rest_key_management_api rest_key_management_api, rest_light_client_api, rest_nimbus_api,
rest_node_api, rest_validator_api

View File

@ -1,3 +1,12 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
import import
../spec/beacon_time ../spec/beacon_time
@ -190,3 +199,19 @@ const
"Invalid Authorization Header" "Invalid Authorization Header"
PrunedStateError* = PrunedStateError* =
"Trying to access a pruned historical state" "Trying to access a pruned historical state"
InvalidBlockRootValueError* =
"Invalid block root value"
InvalidSyncPeriodError* =
"Invalid sync committee period requested"
InvalidCountError* =
"Invalid count requested"
MissingStartPeriodValueError* =
"Missing `start_period` value"
MissingCountValueError* =
"Missing `count` value"
LCBootstrapUnavailable* =
"LC bootstrap unavailable"
LCFinUpdateUnavailable* =
"LC finality update unavailable"
LCOptUpdateUnavailable* =
"LC optimistic update unavailable"

View File

@ -1,9 +1,12 @@
# Copyright (c) 2018-2020 Status Research & Development GmbH # beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
import import
stew/results, stew/results,
chronicles, chronicles,
@ -14,40 +17,20 @@ export rest_utils
logScope: topics = "rest_eventapi" logScope: topics = "rest_eventapi"
proc validateEventTopics(events: seq[EventTopic]): Result[EventTopics, proc validateEventTopics(events: seq[EventTopic],
cstring] = withLightClient: bool): Result[EventTopics, cstring] =
const NonUniqueError = cstring("Event topics must be unique") const NonUniqueError = cstring("Event topics must be unique")
const UnsupportedError = cstring("Unsupported event topic value")
var res: set[EventTopic] var res: set[EventTopic]
for item in events: for item in events:
case item if item in res:
of EventTopic.Head: return err(NonUniqueError)
if EventTopic.Head in res: if not withLightClient and item in [
return err(NonUniqueError) EventTopic.LightClientFinalityUpdate,
res.incl(EventTopic.Head) EventTopic.LightClientOptimisticUpdate]:
of EventTopic.Block: return err(UnsupportedError)
if EventTopic.Block in res: res.incl(item)
return err(NonUniqueError)
res.incl(EventTopic.Block)
of EventTopic.Attestation:
if EventTopic.Attestation in res:
return err(NonUniqueError)
res.incl(EventTopic.Attestation)
of EventTopic.VoluntaryExit:
if EventTopic.VoluntaryExit in res:
return err(NonUniqueError)
res.incl(EventTopic.VoluntaryExit)
of EventTopic.FinalizedCheckpoint:
if EventTopic.FinalizedCheckpoint in res:
return err(NonUniqueError)
res.incl(EventTopic.FinalizedCheckpoint)
of EventTopic.ChainReorg:
if EventTopic.ChainReorg in res:
return err(NonUniqueError)
res.incl(EventTopic.ChainReorg)
of EventTopic.ContributionAndProof:
if EventTopic.ContributionAndProof in res:
return err(NonUniqueError)
res.incl(EventTopic.ContributionAndProof)
if res == {}: if res == {}:
err("Empty topics list") err("Empty topics list")
else: else:
@ -105,6 +88,7 @@ proc eventHandler*[T](response: HttpResponseRef,
proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) = proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Events/eventstream # https://ethereum.github.io/beacon-APIs/#/Events/eventstream
# https://github.com/ethereum/beacon-APIs/pull/181
router.api(MethodGet, "/eth/v1/events") do ( router.api(MethodGet, "/eth/v1/events") do (
topics: seq[EventTopic]) -> RestApiResponse: topics: seq[EventTopic]) -> RestApiResponse:
let eventTopics = let eventTopics =
@ -112,7 +96,8 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
if topics.isErr(): if topics.isErr():
return RestApiResponse.jsonError(Http400, "Invalid topics value", return RestApiResponse.jsonError(Http400, "Invalid topics value",
$topics.error()) $topics.error())
let res = validateEventTopics(topics.get()) let res = validateEventTopics(topics.get(),
node.dag.lightClientDataServe)
if res.isErr(): if res.isErr():
return RestApiResponse.jsonError(Http400, "Invalid topics value", return RestApiResponse.jsonError(Http400, "Invalid topics value",
$res.error()) $res.error())
@ -164,6 +149,16 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
let handler = response.eventHandler(node.eventBus.contribQueue, let handler = response.eventHandler(node.eventBus.contribQueue,
"contribution_and_proof") "contribution_and_proof")
res.add(handler) res.add(handler)
if EventTopic.LightClientFinalityUpdate in eventTopics:
doAssert node.dag.lightClientDataServe
let handler = response.eventHandler(node.eventBus.finUpdateQueue,
"light_client_finality_update_v0")
res.add(handler)
if EventTopic.LightClientOptimisticUpdate in eventTopics:
doAssert node.dag.lightClientDataServe
let handler = response.eventHandler(node.eventBus.optUpdateQueue,
"light_client_optimistic_update_v0")
res.add(handler)
res res
discard await one(handlers) discard await one(handlers)

View File

@ -0,0 +1,94 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
import chronicles
import ../beacon_node,
./rest_utils
logScope: topics = "rest_light_client"
proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://github.com/ethereum/beacon-APIs/pull/181
router.api(MethodGet,
"/eth/v0/beacon/light_client/bootstrap/{block_root}") do (
block_root: Eth2Digest) -> RestApiResponse:
doAssert node.dag.lightClientDataServe
let vroot = block:
if block_root.isErr():
return RestApiResponse.jsonError(Http400, InvalidBlockRootValueError,
$block_root.error())
block_root.get()
let bootstrap = node.dag.getLightClientBootstrap(vroot)
if bootstrap.isOk:
return RestApiResponse.jsonResponse(bootstrap)
else:
return RestApiResponse.jsonError(Http404, LCBootstrapUnavailable)
# https://github.com/ethereum/beacon-APIs/pull/181
router.api(MethodGet,
"/eth/v0/beacon/light_client/updates") do (
start_period: Option[SyncCommitteePeriod], count: Option[uint64]
) -> RestApiResponse:
doAssert node.dag.lightClientDataServe
let vstart = block:
if start_period.isNone():
return RestApiResponse.jsonError(Http400, MissingStartPeriodValueError)
let rstart = start_period.get()
if rstart.isErr():
return RestApiResponse.jsonError(Http400, InvalidSyncPeriodError,
$rstart.error())
rstart.get()
let vcount = block:
if count.isNone():
return RestApiResponse.jsonError(Http400, MissingCountValueError)
let rcount = count.get()
if rcount.isErr():
return RestApiResponse.jsonError(Http400, InvalidCountError,
$rcount.error())
rcount.get()
let
headPeriod = node.dag.head.slot.sync_committee_period
# Limit number of updates in response
maxSupportedCount =
if vstart > headPeriod:
0'u64
else:
min(headPeriod + 1 - vstart, MAX_REQUEST_LIGHT_CLIENT_UPDATES)
numPeriods = min(vcount, maxSupportedCount)
onePastPeriod = vstart + numPeriods
var updates = newSeqOfCap[LightClientUpdate](numPeriods)
for period in vstart..<onePastPeriod:
let update = node.dag.getLightClientUpdateForPeriod(period)
if update.isSome:
updates.add update.get
return RestApiResponse.jsonResponse(updates)
# https://github.com/ethereum/beacon-APIs/pull/181
router.api(MethodGet,
"/eth/v0/beacon/light_client/finality_update") do (
) -> RestApiResponse:
doAssert node.dag.lightClientDataServe
let finality_update = node.dag.getLightClientFinalityUpdate()
if finality_update.isSome:
return RestApiResponse.jsonResponse(finality_update)
else:
return RestApiResponse.jsonError(Http404, LCFinUpdateUnavailable)
# https://github.com/ethereum/beacon-APIs/pull/181
router.api(MethodGet,
"/eth/v0/beacon/light_client/optimistic_update") do (
) -> RestApiResponse:
doAssert node.dag.lightClientDataServe
let optimistic_update = node.dag.getLightClientOptimisticUpdate()
if optimistic_update.isSome:
return RestApiResponse.jsonResponse(optimistic_update)
else:
return RestApiResponse.jsonError(Http404, LCOptUpdateUnavailable)

View File

@ -1,3 +1,12 @@
# beacon_chain
# Copyright (c) 2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
import std/[options, macros], import std/[options, macros],
stew/byteutils, presto, stew/byteutils, presto,
../spec/[forks], ../spec/[forks],
@ -37,6 +46,8 @@ proc validate(key: string, value: string): int =
0 0
of "{validator_id}": of "{validator_id}":
0 0
of "{block_root}":
0
else: else:
1 1

View File

@ -1,3 +1,4 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH # Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
@ -2147,6 +2148,10 @@ proc decodeString*(t: typedesc[EventTopic],
ok(EventTopic.ChainReorg) ok(EventTopic.ChainReorg)
of "contribution_and_proof": of "contribution_and_proof":
ok(EventTopic.ContributionAndProof) ok(EventTopic.ContributionAndProof)
of "light_client_finality_update_v0":
ok(EventTopic.LightClientFinalityUpdate)
of "light_client_optimistic_update_v0":
ok(EventTopic.LightClientOptimisticUpdate)
else: else:
err("Incorrect event's topic value") err("Incorrect event's topic value")
@ -2186,6 +2191,11 @@ proc decodeString*(t: typedesc[Epoch], value: string): Result[Epoch, cstring] =
let res = ? Base10.decode(uint64, value) let res = ? Base10.decode(uint64, value)
ok(Epoch(res)) ok(Epoch(res))
proc decodeString*(t: typedesc[SyncCommitteePeriod],
value: string): Result[SyncCommitteePeriod, cstring] =
let res = ? Base10.decode(uint64, value)
ok(SyncCommitteePeriod(res))
proc decodeString*(t: typedesc[uint64], proc decodeString*(t: typedesc[uint64],
value: string): Result[uint64, cstring] = value: string): Result[uint64, cstring] =
Base10.decode(uint64, value) Base10.decode(uint64, value)

View File

@ -33,7 +33,7 @@ const
type type
EventTopic* {.pure.} = enum EventTopic* {.pure.} = enum
Head, Block, Attestation, VoluntaryExit, FinalizedCheckpoint, ChainReorg, Head, Block, Attestation, VoluntaryExit, FinalizedCheckpoint, ChainReorg,
ContributionAndProof ContributionAndProof, LightClientFinalityUpdate, LightClientOptimisticUpdate
EventTopics* = set[EventTopic] EventTopics* = set[EventTopic]

View File

@ -19,7 +19,8 @@ import
../spec/[helpers, forks, network], ../spec/[helpers, forks, network],
".."/[beacon_clock], ".."/[beacon_clock],
../networking/eth2_network, ../networking/eth2_network,
../consensus_object_pools/blockchain_dag ../consensus_object_pools/blockchain_dag,
../rpc/rest_constants
logScope: logScope:
topics = "sync" topics = "sync"
@ -548,8 +549,7 @@ p2pProtocol BeaconSync(version = 1,
await response.send(bootstrap.get, contextBytes) await response.send(bootstrap.get, contextBytes)
else: else:
peer.updateRequestQuota(lightClientEmptyResponseCost) peer.updateRequestQuota(lightClientEmptyResponseCost)
raise newException( raise newException(ResourceUnavailableError, LCBootstrapUnavailable)
ResourceUnavailableError, "LC bootstrap unavailable")
peer.updateRequestQuota(lightClientBootstrapResponseCost) peer.updateRequestQuota(lightClientBootstrapResponseCost)
@ -609,7 +609,7 @@ p2pProtocol BeaconSync(version = 1,
peer.awaitNonNegativeRequestQuota() peer.awaitNonNegativeRequestQuota()
let finality_update = dag.getLightClientFinalityUpdate let finality_update = dag.getLightClientFinalityUpdate()
if finality_update.isSome: if finality_update.isSome:
let let
contextEpoch = finality_update.get.attested_header.slot.epoch contextEpoch = finality_update.get.attested_header.slot.epoch
@ -617,8 +617,7 @@ p2pProtocol BeaconSync(version = 1,
await response.send(finality_update.get, contextBytes) await response.send(finality_update.get, contextBytes)
else: else:
peer.updateRequestQuota(lightClientEmptyResponseCost) peer.updateRequestQuota(lightClientEmptyResponseCost)
raise newException(ResourceUnavailableError, raise newException(ResourceUnavailableError, LCFinUpdateUnavailable)
"LC finality update unavailable")
peer.updateRequestQuota(lightClientFinalityUpdateResponseCost) peer.updateRequestQuota(lightClientFinalityUpdateResponseCost)
@ -636,7 +635,7 @@ p2pProtocol BeaconSync(version = 1,
peer.awaitNonNegativeRequestQuota() peer.awaitNonNegativeRequestQuota()
let optimistic_update = dag.getLightClientOptimisticUpdate let optimistic_update = dag.getLightClientOptimisticUpdate()
if optimistic_update.isSome: if optimistic_update.isSome:
let let
contextEpoch = optimistic_update.get.attested_header.slot.epoch contextEpoch = optimistic_update.get.attested_header.slot.epoch
@ -644,8 +643,7 @@ p2pProtocol BeaconSync(version = 1,
await response.send(optimistic_update.get, contextBytes) await response.send(optimistic_update.get, contextBytes)
else: else:
peer.updateRequestQuota(lightClientEmptyResponseCost) peer.updateRequestQuota(lightClientEmptyResponseCost)
raise newException(ResourceUnavailableError, raise newException(ResourceUnavailableError, LCOptUpdateUnavailable)
"LC optimistic update unavailable")
peer.updateRequestQuota(lightClientOptimisticUpdateResponseCost) peer.updateRequestQuota(lightClientOptimisticUpdateResponseCost)