204 lines
7.4 KiB
Nim
204 lines
7.4 KiB
Nim
# 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.
|
|
|
|
import std/sequtils
|
|
import chronicles
|
|
import ".."/beacon_node,
|
|
".."/spec/forks,
|
|
"."/[rest_utils, state_ttl_cache]
|
|
|
|
from ../fork_choice/proto_array import ProtoArrayItem, items
|
|
|
|
export rest_utils
|
|
|
|
logScope: topics = "rest_debug"
|
|
|
|
proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getState
|
|
router.api(MethodGet,
|
|
"/eth/v1/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.getBlockSlotId(state_id.get())
|
|
if bres.isErr():
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$bres.error())
|
|
bres.get()
|
|
let contentType =
|
|
block:
|
|
let res = preferredContentType(jsonMediaType,
|
|
sszMediaType)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http406, ContentNotAcceptableError)
|
|
res.get()
|
|
node.withStateForBlockSlotId(bslot):
|
|
return
|
|
case state.kind
|
|
of BeaconStateFork.Phase0:
|
|
if contentType == sszMediaType:
|
|
RestApiResponse.sszResponse(state.phase0Data.data, [])
|
|
elif contentType == jsonMediaType:
|
|
RestApiResponse.jsonResponse(state.phase0Data.data)
|
|
else:
|
|
RestApiResponse.jsonError(Http500, InvalidAcceptError)
|
|
of BeaconStateFork.Altair, BeaconStateFork.Bellatrix:
|
|
RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getStateV2
|
|
router.api(MethodGet,
|
|
"/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.getBlockSlotId(state_id.get())
|
|
if bres.isErr():
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$bres.error())
|
|
bres.get()
|
|
let contentType =
|
|
block:
|
|
let res = preferredContentType(jsonMediaType,
|
|
sszMediaType)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http406, ContentNotAcceptableError)
|
|
res.get()
|
|
node.withStateForBlockSlotId(bslot):
|
|
return
|
|
if contentType == jsonMediaType:
|
|
RestApiResponse.jsonResponseState(
|
|
state,
|
|
node.getStateOptimistic(state)
|
|
)
|
|
elif contentType == sszMediaType:
|
|
let headers = [("eth-consensus-version", state.kind.toString())]
|
|
withState(state):
|
|
RestApiResponse.sszResponse(state.data, headers)
|
|
else:
|
|
RestApiResponse.jsonError(Http500, InvalidAcceptError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getDebugChainHeads
|
|
router.api(MethodGet,
|
|
"/eth/v1/debug/beacon/heads") do () -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse(
|
|
node.dag.heads.mapIt((root: it.root, slot: it.slot))
|
|
)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Debug/getDebugChainHeadsV2
|
|
router.api(MethodGet,
|
|
"/eth/v2/debug/beacon/heads") do () -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse(
|
|
node.dag.heads.mapIt(
|
|
(
|
|
root: it.root,
|
|
slot: it.slot,
|
|
execution_optimistic: node.getBlockRefOptimistic(it)
|
|
)
|
|
)
|
|
)
|
|
|
|
# https://github.com/ethereum/beacon-APIs/pull/232
|
|
if node.config.debugForkChoice:
|
|
router.api(MethodGet,
|
|
"/eth/v1/debug/fork_choice") do () -> RestApiResponse:
|
|
type
|
|
ForkChoiceResponseExtraData = object
|
|
justified_root: Eth2Digest
|
|
finalized_root: Eth2Digest
|
|
u_justified_checkpoint: Option[Checkpoint]
|
|
u_finalized_checkpoint: Option[Checkpoint]
|
|
best_child: Eth2Digest
|
|
best_descendant: Eth2Digest
|
|
invalid: bool
|
|
|
|
ForkChoiceResponse = object
|
|
slot: Slot
|
|
block_root: Eth2Digest
|
|
parent_root: Eth2Digest
|
|
justified_epoch: Epoch
|
|
finalized_epoch: Epoch
|
|
weight: uint64
|
|
execution_optimistic: bool
|
|
execution_payload_root: Eth2Digest
|
|
extra_data: Option[ForkChoiceResponseExtraData]
|
|
|
|
var responses: seq[ForkChoiceResponse]
|
|
for item in node.attestationPool[].forkChoice.backend.proto_array:
|
|
let
|
|
bid = node.dag.getBlockId(item.root)
|
|
slot =
|
|
if bid.isOk:
|
|
bid.unsafeGet.slot
|
|
else:
|
|
FAR_FUTURE_SLOT
|
|
executionPayloadRoot =
|
|
if bid.isOk:
|
|
node.dag.loadExecutionBlockRoot(bid.unsafeGet)
|
|
else:
|
|
ZERO_HASH
|
|
unrealized = item.unrealized.get(item.checkpoints)
|
|
u_justified_checkpoint =
|
|
if unrealized.justified != item.checkpoints.justified:
|
|
some unrealized.justified
|
|
else:
|
|
none(Checkpoint)
|
|
u_finalized_checkpoint =
|
|
if unrealized.finalized != item.checkpoints.finalized:
|
|
some unrealized.finalized
|
|
else:
|
|
none(Checkpoint)
|
|
|
|
responses.add ForkChoiceResponse(
|
|
slot: slot,
|
|
block_root: item.root,
|
|
parent_root: item.parent,
|
|
justified_epoch: item.checkpoints.justified.epoch,
|
|
finalized_epoch: item.checkpoints.finalized.epoch,
|
|
weight: cast[uint64](item.weight),
|
|
execution_optimistic: node.dag.is_optimistic(item.root),
|
|
execution_payload_root: executionPayloadRoot,
|
|
extra_data: some ForkChoiceResponseExtraData(
|
|
justified_root: item.checkpoints.justified.root,
|
|
finalized_root: item.checkpoints.finalized.root,
|
|
u_justified_checkpoint: u_justified_checkpoint,
|
|
u_finalized_checkpoint: u_finalized_checkpoint,
|
|
best_child: item.bestChild,
|
|
bestDescendant: item.bestDescendant,
|
|
invalid: item.invalid))
|
|
return RestApiResponse.jsonResponse(responses)
|
|
|
|
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
|
|
# `/api` path component
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/debug/beacon/states/{state_id}",
|
|
"/eth/v1/debug/beacon/states/{state_id}"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v2/debug/beacon/states/{state_id}",
|
|
"/eth/v2/debug/beacon/states/{state_id}"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/debug/beacon/heads",
|
|
"/eth/v1/debug/beacon/heads"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v2/debug/beacon/heads",
|
|
"/eth/v2/debug/beacon/heads"
|
|
)
|