Skip to content

Commit

Permalink
adopt LC REST API with v0 suffix (without proofs)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
etan-status committed Jun 18, 2022
1 parent 2c623e5 commit 08bcd52
Show file tree
Hide file tree
Showing 10 changed files with 188 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
6 changes: 4 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 @@ -1315,6 +1315,8 @@ proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
restServer.router.installNimbusApiHandlers(node)
restServer.router.installNodeApiHandlers(node)
restServer.router.installValidatorApiHandlers(node)
if node.config.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 startPeriod..<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
6 changes: 6 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 @@ -2186,6 +2187,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 08bcd52

Please sign in to comment.