Skip to content

Commit

Permalink
adopt LC REST API with v0 suffix (without proofs) (#3775)
Browse files Browse the repository at this point in the history
* adopt LC REST API with v0 suffix (without proofs)

Adopts the light client data REST API used by Lodestar as defined in
ethereum/beacon-APIs#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
  • Loading branch information
etan-status authored Jun 19, 2022
1 parent 5439978 commit 61ee061
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 49 deletions.
4 changes: 3 additions & 1 deletion beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import
./consensus_object_pools/[
blockchain_dag, block_quarantine, exit_pool, attestation_pool,
sync_committee_msg_pool],
./spec/datatypes/base,
./spec/datatypes/[base, altair],
./sync/[optimistic_sync_light_client, sync_manager, request_manager],
./validators/[action_tracker, validator_monitor, validator_pool],
./rpc/state_ttl_cache
Expand All @@ -41,6 +41,8 @@ type
blocksQueue*: AsyncEventQueue[ForkedTrustedSignedBeaconBlock]
headQueue*: AsyncEventQueue[HeadChangeInfoObject]
reorgQueue*: AsyncEventQueue[ReorgInfoObject]
finUpdateQueue*: AsyncEventQueue[altair.LightClientFinalityUpdate]
optUpdateQueue*: AsyncEventQueue[altair.LightClientOptimisticUpdate]
attestQueue*: AsyncEventQueue[Attestation]
contribQueue*: AsyncEventQueue[SignedContributionAndProof]
exitQueue*: AsyncEventQueue[SignedVoluntaryExit]
Expand Down
8 changes: 6 additions & 2 deletions beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ proc loadChainDag(
proc onChainReorg(data: ReorgInfoObject) =
eventBus.reorgQueue.emit(data)
proc onLightClientFinalityUpdate(data: altair.LightClientFinalityUpdate) =
discard
eventBus.finUpdateQueue.emit(data)
proc onLightClientOptimisticUpdate(data: altair.LightClientOptimisticUpdate) =
discard
eventBus.optUpdateQueue.emit(data)

let
chainDagFlags =
Expand Down Expand Up @@ -374,6 +374,8 @@ proc init*(T: type BeaconNode,
blocksQueue: newAsyncEventQueue[ForkedTrustedSignedBeaconBlock](),
headQueue: newAsyncEventQueue[HeadChangeInfoObject](),
reorgQueue: newAsyncEventQueue[ReorgInfoObject](),
finUpdateQueue: newAsyncEventQueue[altair.LightClientFinalityUpdate](),
optUpdateQueue: newAsyncEventQueue[altair.LightClientOptimisticUpdate](),
attestQueue: newAsyncEventQueue[Attestation](),
contribQueue: newAsyncEventQueue[SignedContributionAndProof](),
exitQueue: newAsyncEventQueue[SignedVoluntaryExit](),
Expand Down Expand Up @@ -1315,6 +1317,8 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
restServer.router.installNimbusApiHandlers(node)
restServer.router.installNodeApiHandlers(node)
restServer.router.installValidatorApiHandlers(node)
if node.dag.lightClientDataServe:
restServer.router.installLightClientApiHandlers(node)

proc installMessageValidators(node: BeaconNode) =
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#attestations-and-aggregation
Expand Down
12 changes: 9 additions & 3 deletions beacon_chain/rpc/rest_api.nim
Original file line number Diff line number Diff line change
@@ -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
# * 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].}

## 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/#
## along with several nimbus-specific extensions. It is used by the validator
## client as well as many community utilities.
## A corresponding client can be found in the
## `spec/eth2_apis/rest_beacon_client` module

import
"."/[
rest_utils,
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
rest_utils,
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
25 changes: 25 additions & 0 deletions beacon_chain/rpc/rest_constants.nim
Original file line number Diff line number Diff line change
@@ -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
../spec/beacon_time

Expand Down Expand Up @@ -190,3 +199,19 @@ const
"Invalid Authorization Header"
PrunedStateError* =
"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"
61 changes: 28 additions & 33 deletions beacon_chain/rpc/rest_event_api.nim
Original file line number Diff line number Diff line change
@@ -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
# * 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
stew/results,
chronicles,
Expand All @@ -14,40 +17,20 @@ export rest_utils

logScope: topics = "rest_eventapi"

proc validateEventTopics(events: seq[EventTopic]): Result[EventTopics,
cstring] =
proc validateEventTopics(events: seq[EventTopic],
withLightClient: bool): Result[EventTopics, cstring] =
const NonUniqueError = cstring("Event topics must be unique")
const UnsupportedError = cstring("Unsupported event topic value")
var res: set[EventTopic]
for item in events:
case item
of EventTopic.Head:
if EventTopic.Head in res:
return err(NonUniqueError)
res.incl(EventTopic.Head)
of EventTopic.Block:
if EventTopic.Block in res:
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 item in res:
return err(NonUniqueError)
if not withLightClient and item in [
EventTopic.LightClientFinalityUpdate,
EventTopic.LightClientOptimisticUpdate]:
return err(UnsupportedError)
res.incl(item)

if res == {}:
err("Empty topics list")
else:
Expand Down Expand Up @@ -105,14 +88,16 @@ proc eventHandler*[T](response: HttpResponseRef,

proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Events/eventstream
# https://github.com/ethereum/beacon-APIs/pull/181
router.api(MethodGet, "/eth/v1/events") do (
topics: seq[EventTopic]) -> RestApiResponse:
let eventTopics =
block:
if topics.isErr():
return RestApiResponse.jsonError(Http400, "Invalid topics value",
$topics.error())
let res = validateEventTopics(topics.get())
let res = validateEventTopics(topics.get(),
node.dag.lightClientDataServe)
if res.isErr():
return RestApiResponse.jsonError(Http400, "Invalid topics value",
$res.error())
Expand Down Expand Up @@ -164,6 +149,16 @@ proc installEventApiHandlers*(router: var RestRouter, node: BeaconNode) =
let handler = response.eventHandler(node.eventBus.contribQueue,
"contribution_and_proof")
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

discard await one(handlers)
Expand Down
94 changes: 94 additions & 0 deletions beacon_chain/rpc/rest_light_client_api.nim
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 11 additions & 0 deletions beacon_chain/rpc/rest_utils.nim
Original file line number Diff line number Diff line change
@@ -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],
stew/byteutils, presto,
../spec/[forks],
Expand Down Expand Up @@ -37,6 +46,8 @@ proc validate(key: string, value: string): int =
0
of "{validator_id}":
0
of "{block_root}":
0
else:
1

Expand Down
10 changes: 10 additions & 0 deletions beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# beacon_chain
# Copyright (c) 2018-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).
Expand Down Expand Up @@ -2147,6 +2148,10 @@ proc decodeString*(t: typedesc[EventTopic],
ok(EventTopic.ChainReorg)
of "contribution_and_proof":
ok(EventTopic.ContributionAndProof)
of "light_client_finality_update_v0":
ok(EventTopic.LightClientFinalityUpdate)
of "light_client_optimistic_update_v0":
ok(EventTopic.LightClientOptimisticUpdate)
else:
err("Incorrect event's topic value")

Expand Down Expand Up @@ -2186,6 +2191,11 @@ proc decodeString*(t: typedesc[Epoch], value: string): Result[Epoch, cstring] =
let res = ? Base10.decode(uint64, value)
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],
value: string): Result[uint64, cstring] =
Base10.decode(uint64, value)
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/eth2_apis/rest_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const
type
EventTopic* {.pure.} = enum
Head, Block, Attestation, VoluntaryExit, FinalizedCheckpoint, ChainReorg,
ContributionAndProof
ContributionAndProof, LightClientFinalityUpdate, LightClientOptimisticUpdate

EventTopics* = set[EventTopic]

Expand Down
Loading

0 comments on commit 61ee061

Please sign in to comment.