nimbus-eth2/beacon_chain/rpc/rest_debug_api.nim

181 lines
6.9 KiB
Nim
Raw Permalink Normal View History

# 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]
2021-03-17 18:46:45 +00:00
from ../fork_choice/proto_array import ProtoArrayItem, items
export rest_utils
2021-03-17 18:46:45 +00:00
logScope: topics = "rest_debug"
proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
# https://ethereum.github.io/beacon-APIs/#/Debug/getState
2021-03-17 18:46:45 +00:00
router.api(MethodGet,
"/eth/v1/debug/beacon/states/{state_id}") do (
2021-03-17 18:46:45 +00:00
state_id: StateIdent) -> RestApiResponse:
let bslot =
block:
if state_id.isErr():
2021-04-08 10:49:28 +00:00
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
$state_id.error())
Prune `BlockRef` on finalization (#3513) Up til now, the block dag has been using `BlockRef`, a structure adapted for a full DAG, to represent all of chain history. This is a correct and simple design, but does not exploit the linearity of the chain once parts of it finalize. By pruning the in-memory `BlockRef` structure at finalization, we save, at the time of writing, a cool ~250mb (or 25%:ish) chunk of memory landing us at a steady state of ~750mb normal memory usage for a validating node. Above all though, we prevent memory usage from growing proportionally with the length of the chain, something that would not be sustainable over time - instead, the steady state memory usage is roughly determined by the validator set size which grows much more slowly. With these changes, the core should remain sustainable memory-wise post-merge all the way to withdrawals (when the validator set is expected to grow). In-memory indices are still used for the "hot" unfinalized portion of the chain - this ensure that consensus performance remains unchanged. What changes is that for historical access, we use a db-based linear slot index which is cache-and-disk-friendly, keeping the cost for accessing historical data at a similar level as before, achieving the savings at no percievable cost to functionality or performance. A nice collateral benefit is the almost-instant startup since we no longer load any large indicies at dag init. The cost of this functionality instead can be found in the complexity of having to deal with two ways of traversing the chain - by `BlockRef` and by slot. * use `BlockId` instead of `BlockRef` where finalized / historical data may be required * simplify clearance pre-advancement * remove dag.finalizedBlocks (~50:ish mb) * remove `getBlockAtSlot` - use `getBlockIdAtSlot` instead * `parent` and `atSlot` for `BlockId` now require a `ChainDAGRef` instance, unlike `BlockRef` traversal * prune `BlockRef` parents on finality (~200:ish mb) * speed up ChainDAG init by not loading finalized history index * mess up light client server error handling - this need revisiting :)
2022-03-17 17:42:56 +00:00
let bres = node.getBlockSlotId(state_id.get())
if bres.isErr():
2021-04-08 10:49:28 +00:00
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()
Prune `BlockRef` on finalization (#3513) Up til now, the block dag has been using `BlockRef`, a structure adapted for a full DAG, to represent all of chain history. This is a correct and simple design, but does not exploit the linearity of the chain once parts of it finalize. By pruning the in-memory `BlockRef` structure at finalization, we save, at the time of writing, a cool ~250mb (or 25%:ish) chunk of memory landing us at a steady state of ~750mb normal memory usage for a validating node. Above all though, we prevent memory usage from growing proportionally with the length of the chain, something that would not be sustainable over time - instead, the steady state memory usage is roughly determined by the validator set size which grows much more slowly. With these changes, the core should remain sustainable memory-wise post-merge all the way to withdrawals (when the validator set is expected to grow). In-memory indices are still used for the "hot" unfinalized portion of the chain - this ensure that consensus performance remains unchanged. What changes is that for historical access, we use a db-based linear slot index which is cache-and-disk-friendly, keeping the cost for accessing historical data at a similar level as before, achieving the savings at no percievable cost to functionality or performance. A nice collateral benefit is the almost-instant startup since we no longer load any large indicies at dag init. The cost of this functionality instead can be found in the complexity of having to deal with two ways of traversing the chain - by `BlockRef` and by slot. * use `BlockId` instead of `BlockRef` where finalized / historical data may be required * simplify clearance pre-advancement * remove dag.finalizedBlocks (~50:ish mb) * remove `getBlockAtSlot` - use `getBlockIdAtSlot` instead * `parent` and `atSlot` for `BlockId` now require a `ChainDAGRef` instance, unlike `BlockRef` traversal * prune `BlockRef` parents on finality (~200:ish mb) * speed up ChainDAG init by not loading finalized history index * mess up light client server error handling - this need revisiting :)
2022-03-17 17:42:56 +00:00
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)
2021-03-17 18:46:45 +00:00
# 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())
Prune `BlockRef` on finalization (#3513) Up til now, the block dag has been using `BlockRef`, a structure adapted for a full DAG, to represent all of chain history. This is a correct and simple design, but does not exploit the linearity of the chain once parts of it finalize. By pruning the in-memory `BlockRef` structure at finalization, we save, at the time of writing, a cool ~250mb (or 25%:ish) chunk of memory landing us at a steady state of ~750mb normal memory usage for a validating node. Above all though, we prevent memory usage from growing proportionally with the length of the chain, something that would not be sustainable over time - instead, the steady state memory usage is roughly determined by the validator set size which grows much more slowly. With these changes, the core should remain sustainable memory-wise post-merge all the way to withdrawals (when the validator set is expected to grow). In-memory indices are still used for the "hot" unfinalized portion of the chain - this ensure that consensus performance remains unchanged. What changes is that for historical access, we use a db-based linear slot index which is cache-and-disk-friendly, keeping the cost for accessing historical data at a similar level as before, achieving the savings at no percievable cost to functionality or performance. A nice collateral benefit is the almost-instant startup since we no longer load any large indicies at dag init. The cost of this functionality instead can be found in the complexity of having to deal with two ways of traversing the chain - by `BlockRef` and by slot. * use `BlockId` instead of `BlockRef` where finalized / historical data may be required * simplify clearance pre-advancement * remove dag.finalizedBlocks (~50:ish mb) * remove `getBlockAtSlot` - use `getBlockIdAtSlot` instead * `parent` and `atSlot` for `BlockId` now require a `ChainDAGRef` instance, unlike `BlockRef` traversal * prune `BlockRef` parents on finality (~200:ish mb) * speed up ChainDAG init by not loading finalized history index * mess up light client server error handling - this need revisiting :)
2022-03-17 17:42:56 +00:00
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()
Prune `BlockRef` on finalization (#3513) Up til now, the block dag has been using `BlockRef`, a structure adapted for a full DAG, to represent all of chain history. This is a correct and simple design, but does not exploit the linearity of the chain once parts of it finalize. By pruning the in-memory `BlockRef` structure at finalization, we save, at the time of writing, a cool ~250mb (or 25%:ish) chunk of memory landing us at a steady state of ~750mb normal memory usage for a validating node. Above all though, we prevent memory usage from growing proportionally with the length of the chain, something that would not be sustainable over time - instead, the steady state memory usage is roughly determined by the validator set size which grows much more slowly. With these changes, the core should remain sustainable memory-wise post-merge all the way to withdrawals (when the validator set is expected to grow). In-memory indices are still used for the "hot" unfinalized portion of the chain - this ensure that consensus performance remains unchanged. What changes is that for historical access, we use a db-based linear slot index which is cache-and-disk-friendly, keeping the cost for accessing historical data at a similar level as before, achieving the savings at no percievable cost to functionality or performance. A nice collateral benefit is the almost-instant startup since we no longer load any large indicies at dag init. The cost of this functionality instead can be found in the complexity of having to deal with two ways of traversing the chain - by `BlockRef` and by slot. * use `BlockId` instead of `BlockRef` where finalized / historical data may be required * simplify clearance pre-advancement * remove dag.finalizedBlocks (~50:ish mb) * remove `getBlockAtSlot` - use `getBlockIdAtSlot` instead * `parent` and `atSlot` for `BlockId` now require a `ChainDAGRef` instance, unlike `BlockRef` traversal * prune `BlockRef` parents on finality (~200:ish mb) * speed up ChainDAG init by not loading finalized history index * mess up light client server error handling - this need revisiting :)
2022-03-17 17:42:56 +00:00
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(forkyState.data, headers)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
return RestApiResponse.jsonError(Http404, StateNotFoundError)
# https://ethereum.github.io/beacon-APIs/#/Debug/getDebugChainHeads
2021-03-17 18:46:45 +00:00
router.api(MethodGet,
"/eth/v1/debug/beacon/heads") do () -> RestApiResponse:
2021-03-17 18:46:45 +00:00
return RestApiResponse.jsonResponse(
node.dag.heads.mapIt((root: it.root, slot: it.slot))
2021-03-17 18:46:45 +00:00
)
2021-04-13 10:19:31 +00:00
# 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)