mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-24 21:40:03 +00:00
05ffe7b2bf
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 :)
1182 lines
46 KiB
Nim
1182 lines
46 KiB
Nim
# 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.
|
|
|
|
import
|
|
std/[typetraits, sequtils, strutils, sets],
|
|
stew/[results, base10],
|
|
chronicles,
|
|
./rest_utils,
|
|
../beacon_node, ../networking/eth2_network,
|
|
../consensus_object_pools/[blockchain_dag, exit_pool, spec_cache],
|
|
../validators/validator_duties,
|
|
../spec/[eth2_merkleization, forks, network, validator],
|
|
../spec/datatypes/[phase0, altair],
|
|
./state_ttl_cache
|
|
|
|
export rest_utils
|
|
|
|
logScope: topics = "rest_beaconapi"
|
|
|
|
proc validateFilter(filters: seq[ValidatorFilter]): Result[ValidatorFilter,
|
|
cstring] =
|
|
var res: ValidatorFilter
|
|
for item in filters:
|
|
if res * item != {}:
|
|
return err("Validator status must be unique")
|
|
res.incl(item)
|
|
|
|
if res == {}:
|
|
res = {ValidatorFilterKind.PendingInitialized,
|
|
ValidatorFilterKind.PendingQueued,
|
|
ValidatorFilterKind.ActiveOngoing,
|
|
ValidatorFilterKind.ActiveExiting,
|
|
ValidatorFilterKind.ActiveSlashed,
|
|
ValidatorFilterKind.ExitedUnslashed,
|
|
ValidatorFilterKind.ExitedSlashed,
|
|
ValidatorFilterKind.WithdrawalPossible,
|
|
ValidatorFilterKind.WithdrawalDone}
|
|
ok(res)
|
|
|
|
proc getStatus(validator: Validator,
|
|
current_epoch: Epoch): Result[ValidatorFilterKind, cstring] =
|
|
if validator.activation_epoch > current_epoch:
|
|
# pending
|
|
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH:
|
|
ok(ValidatorFilterKind.PendingInitialized)
|
|
else:
|
|
# validator.activation_eligibility_epoch < FAR_FUTURE_EPOCH:
|
|
ok(ValidatorFilterKind.PendingQueued)
|
|
elif (validator.activation_epoch <= current_epoch) and
|
|
(current_epoch < validator.exit_epoch):
|
|
# active
|
|
if validator.exit_epoch == FAR_FUTURE_EPOCH:
|
|
ok(ValidatorFilterKind.ActiveOngoing)
|
|
elif not validator.slashed:
|
|
# validator.exit_epoch < FAR_FUTURE_EPOCH
|
|
ok(ValidatorFilterKind.ActiveExiting)
|
|
else:
|
|
# validator.exit_epoch < FAR_FUTURE_EPOCH and validator.slashed:
|
|
ok(ValidatorFilterKind.ActiveSlashed)
|
|
elif (validator.exit_epoch <= current_epoch) and
|
|
(current_epoch < validator.withdrawable_epoch):
|
|
# exited
|
|
if not validator.slashed:
|
|
ok(ValidatorFilterKind.ExitedUnslashed)
|
|
else:
|
|
# validator.slashed
|
|
ok(ValidatorFilterKind.ExitedSlashed)
|
|
elif validator.withdrawable_epoch <= current_epoch:
|
|
# withdrawal
|
|
if validator.effective_balance != 0:
|
|
ok(ValidatorFilterKind.WithdrawalPossible)
|
|
else:
|
|
# validator.effective_balance == 0
|
|
ok(ValidatorFilterKind.WithdrawalDone)
|
|
else:
|
|
err("Invalid validator status")
|
|
|
|
proc toString*(kind: ValidatorFilterKind): string =
|
|
case kind
|
|
of ValidatorFilterKind.PendingInitialized:
|
|
"pending_initialized"
|
|
of ValidatorFilterKind.PendingQueued:
|
|
"pending_queued"
|
|
of ValidatorFilterKind.ActiveOngoing:
|
|
"active_ongoing"
|
|
of ValidatorFilterKind.ActiveExiting:
|
|
"active_exiting"
|
|
of ValidatorFilterKind.ActiveSlashed:
|
|
"active_slashed"
|
|
of ValidatorFilterKind.ExitedUnslashed:
|
|
"exited_unslashed"
|
|
of ValidatorFilterKind.ExitedSlashed:
|
|
"exited_slashed"
|
|
of ValidatorFilterKind.WithdrawalPossible:
|
|
"withdrawal_possible"
|
|
of ValidatorFilterKind.WithdrawalDone:
|
|
"withdrawal_done"
|
|
|
|
proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getGenesis
|
|
router.api(MethodGet, "/eth/v1/beacon/genesis") do () -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse(
|
|
(
|
|
genesis_time: getStateField(node.dag.headState, genesis_time),
|
|
genesis_validators_root:
|
|
getStateField(node.dag.headState, genesis_validators_root),
|
|
genesis_fork_version: node.dag.cfg.GENESIS_FORK_VERSION
|
|
)
|
|
)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateRoot
|
|
router.api(MethodGet, "/eth/v1/beacon/states/{state_id}/root") do (
|
|
state_id: StateIdent) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
return RestApiResponse.jsonResponse((root: stateRoot))
|
|
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork
|
|
router.api(MethodGet, "/eth/v1/beacon/states/{state_id}/fork") do (
|
|
state_id: StateIdent) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
return RestApiResponse.jsonResponse(
|
|
(
|
|
previous_version: getStateField(state, fork).previous_version,
|
|
current_version: getStateField(state, fork).current_version,
|
|
epoch: getStateField(state, fork).epoch
|
|
)
|
|
)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints
|
|
router.api(MethodGet,
|
|
"/eth/v1/beacon/states/{state_id}/finality_checkpoints") do (
|
|
state_id: StateIdent) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
return RestApiResponse.jsonResponse(
|
|
(
|
|
previous_justified: getStateField(state, previous_justified_checkpoint),
|
|
current_justified: getStateField(state, current_justified_checkpoint),
|
|
finalized: getStateField(state, finalized_checkpoint)
|
|
)
|
|
)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidators
|
|
router.api(MethodGet, "/eth/v1/beacon/states/{state_id}/validators") do (
|
|
state_id: StateIdent, id: seq[ValidatorIdent],
|
|
status: seq[ValidatorFilter]) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
let validatorIds =
|
|
block:
|
|
if id.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidValidatorIdValueError)
|
|
let ires = id.get()
|
|
if len(ires) > MaximumValidatorIds:
|
|
return RestApiResponse.jsonError(Http400,
|
|
MaximumNumberOfValidatorIdsError)
|
|
ires
|
|
|
|
let validatorsMask =
|
|
block:
|
|
if status.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidValidatorStatusValueError)
|
|
let res = validateFilter(status.get())
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidValidatorStatusValueError,
|
|
$res.error())
|
|
res.get()
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
let
|
|
current_epoch = getStateField(state, slot).epoch()
|
|
validatorsCount = lenu64(getStateField(state, validators))
|
|
|
|
let indices =
|
|
block:
|
|
var keyset: HashSet[ValidatorPubKey]
|
|
var indexset: HashSet[ValidatorIndex]
|
|
for item in validatorIds:
|
|
case item.kind
|
|
of ValidatorQueryKind.Key:
|
|
keyset.incl(item.key)
|
|
of ValidatorQueryKind.Index:
|
|
let vindex =
|
|
block:
|
|
let vres = item.index.toValidatorIndex()
|
|
if vres.isErr():
|
|
case vres.error()
|
|
of ValidatorIndexError.TooHighValue:
|
|
return RestApiResponse.jsonError(Http400,
|
|
TooHighValidatorIndexValueError)
|
|
of ValidatorIndexError.UnsupportedValue:
|
|
return RestApiResponse.jsonError(Http500,
|
|
UnsupportedValidatorIndexValueError)
|
|
let index = vres.get()
|
|
index
|
|
if uint64(vindex) < validatorsCount:
|
|
# We only adding validator indices which are present in
|
|
# validators list at this moment.
|
|
indexset.incl(vindex)
|
|
|
|
if len(keyset) > 0:
|
|
let optIndices = keysToIndices(node.restKeysCache, state,
|
|
keyset.toSeq())
|
|
# Remove all the duplicates.
|
|
for item in optIndices:
|
|
# We ignore missing keys.
|
|
if item.isSome():
|
|
indexset.incl(item.get())
|
|
indexset.toSeq()
|
|
|
|
let response =
|
|
block:
|
|
var res: seq[RestValidator]
|
|
if len(indices) == 0:
|
|
# Case when `len(indices) == 0 and len(validatorIds) != 0` means
|
|
# that we can't find validator identifiers in state, so we should
|
|
# return empty response.
|
|
if len(validatorIds) == 0:
|
|
# There is no indices, so we going to filter all the validators.
|
|
for index, validator in getStateField(state,
|
|
validators).pairs():
|
|
let
|
|
balance = getStateField(state, balances).asSeq()[index]
|
|
status =
|
|
block:
|
|
let sres = validator.getStatus(current_epoch)
|
|
if sres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
ValidatorStatusNotFoundError,
|
|
$sres.get())
|
|
sres.get()
|
|
if status in validatorsMask:
|
|
res.add(RestValidator.init(ValidatorIndex(index), balance,
|
|
toString(status), validator))
|
|
else:
|
|
for index in indices:
|
|
let
|
|
validator = getStateField(state, validators).asSeq()[index]
|
|
balance = getStateField(state, balances).asSeq()[index]
|
|
status =
|
|
block:
|
|
let sres = validator.getStatus(current_epoch)
|
|
if sres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
ValidatorStatusNotFoundError,
|
|
$sres.get())
|
|
sres.get()
|
|
if status in validatorsMask:
|
|
res.add(RestValidator.init(index, balance, toString(status),
|
|
validator))
|
|
res
|
|
return RestApiResponse.jsonResponse(response)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator
|
|
router.api(MethodGet,
|
|
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}") do (
|
|
state_id: StateIdent, validator_id: ValidatorIdent) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
vid = validator_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidValidatorIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
let
|
|
current_epoch = getStateField(state, slot).epoch()
|
|
validatorsCount = lenu64(getStateField(state, validators))
|
|
|
|
let vindex =
|
|
block:
|
|
let vid = validator_id.get()
|
|
case vid.kind
|
|
of ValidatorQueryKind.Key:
|
|
let optIndices = keysToIndices(node.restKeysCache, state, [vid.key])
|
|
if optIndices[0].isNone():
|
|
return RestApiResponse.jsonError(Http404, ValidatorNotFoundError)
|
|
optIndices[0].get()
|
|
of ValidatorQueryKind.Index:
|
|
let vres = vid.index.toValidatorIndex()
|
|
if vres.isErr():
|
|
case vres.error()
|
|
of ValidatorIndexError.TooHighValue:
|
|
return RestApiResponse.jsonError(Http400,
|
|
TooHighValidatorIndexValueError)
|
|
of ValidatorIndexError.UnsupportedValue:
|
|
return RestApiResponse.jsonError(Http500,
|
|
UnsupportedValidatorIndexValueError)
|
|
let index = vres.get()
|
|
if uint64(index) >= validatorsCount:
|
|
return RestApiResponse.jsonError(Http404, ValidatorNotFoundError)
|
|
index
|
|
|
|
let
|
|
validator = getStateField(state, validators).asSeq()[vindex]
|
|
balance = getStateField(state, balances).asSeq()[vindex]
|
|
status =
|
|
block:
|
|
let sres = validator.getStatus(current_epoch)
|
|
if sres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
ValidatorStatusNotFoundError,
|
|
$sres.get())
|
|
toString(sres.get())
|
|
return RestApiResponse.jsonResponse(
|
|
RestValidator.init(vindex, balance, status, validator)
|
|
)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidatorBalances
|
|
router.api(MethodGet,
|
|
"/eth/v1/beacon/states/{state_id}/validator_balances") do (
|
|
state_id: StateIdent, id: seq[ValidatorIdent]) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
let validatorIds =
|
|
block:
|
|
if id.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidValidatorIdValueError)
|
|
let ires = id.get()
|
|
if len(ires) > MaximumValidatorIds:
|
|
return RestApiResponse.jsonError(Http400,
|
|
MaximumNumberOfValidatorIdsError)
|
|
ires
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
let validatorsCount = lenu64(getStateField(state, validators))
|
|
|
|
let indices =
|
|
block:
|
|
var keyset: HashSet[ValidatorPubKey]
|
|
var indexset: HashSet[ValidatorIndex]
|
|
for item in validatorIds:
|
|
case item.kind
|
|
of ValidatorQueryKind.Key:
|
|
keyset.incl(item.key)
|
|
of ValidatorQueryKind.Index:
|
|
let vindex =
|
|
block:
|
|
let vres = item.index.toValidatorIndex()
|
|
if vres.isErr():
|
|
case vres.error()
|
|
of ValidatorIndexError.TooHighValue:
|
|
return RestApiResponse.jsonError(Http400,
|
|
TooHighValidatorIndexValueError)
|
|
of ValidatorIndexError.UnsupportedValue:
|
|
return RestApiResponse.jsonError(Http500,
|
|
UnsupportedValidatorIndexValueError)
|
|
vres.get()
|
|
# We only adding validator indices which are present in
|
|
# validators list at this moment.
|
|
if uint64(vindex) < validatorsCount:
|
|
indexset.incl(vindex)
|
|
|
|
if len(keyset) > 0:
|
|
let optIndices = keysToIndices(node.restKeysCache, state,
|
|
keyset.toSeq())
|
|
# Remove all the duplicates.
|
|
for item in optIndices:
|
|
# We ignore missing keys.
|
|
if item.isSome():
|
|
indexset.incl(item.get())
|
|
indexset.toSeq()
|
|
|
|
let response =
|
|
block:
|
|
var res: seq[RestValidatorBalance]
|
|
if len(indices) == 0:
|
|
# Case when `len(indices) == 0 and len(validatorIds) != 0` means
|
|
# that we can't find validator identifiers in state, so we should
|
|
# return empty response.
|
|
if len(validatorIds) == 0:
|
|
# There is no indices, so we going to return balances of all
|
|
# known validators.
|
|
for index, balance in getStateField(state, balances).pairs():
|
|
res.add(RestValidatorBalance.init(ValidatorIndex(index),
|
|
balance))
|
|
else:
|
|
for index in indices:
|
|
let balance = getStateField(state, balances).asSeq()[index]
|
|
res.add(RestValidatorBalance.init(index, balance))
|
|
res
|
|
return RestApiResponse.jsonResponse(response)
|
|
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochCommittees
|
|
router.api(MethodGet,
|
|
"/eth/v1/beacon/states/{state_id}/committees") do (
|
|
state_id: StateIdent, epoch: Option[Epoch], index: Option[CommitteeIndex],
|
|
slot: Option[Slot]) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
let vepoch =
|
|
if epoch.isSome():
|
|
let repoch = epoch.get()
|
|
if repoch.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
|
|
$repoch.error())
|
|
let res = repoch.get()
|
|
|
|
if res > bslot.slot.epoch + MIN_SEED_LOOKAHEAD:
|
|
return RestApiResponse.jsonError(
|
|
Http400, InvalidEpochValueError,
|
|
"Requested epoch more than 1 epoch past state epoch")
|
|
|
|
if res + EPOCHS_PER_HISTORICAL_VECTOR <
|
|
bslot.slot.epoch + MIN_SEED_LOOKAHEAD:
|
|
return RestApiResponse.jsonError(
|
|
Http400, InvalidEpochValueError,
|
|
"Requested epoch earlier than what committees can be computed for")
|
|
|
|
some(res)
|
|
else:
|
|
none[Epoch]()
|
|
let vindex =
|
|
if index.isSome():
|
|
let rindex = index.get()
|
|
if rindex.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidCommitteeIndexValueError,
|
|
$rindex.error())
|
|
some(rindex.get())
|
|
else:
|
|
none[CommitteeIndex]()
|
|
let vslot =
|
|
if slot.isSome():
|
|
let rslot = slot.get()
|
|
if rslot.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
|
$rslot.error())
|
|
let res = rslot.get()
|
|
if vepoch.isSome():
|
|
if res.epoch != vepoch.get():
|
|
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
|
"Slot does not match requested epoch")
|
|
else:
|
|
if res.epoch > bslot.slot.epoch + 1:
|
|
return RestApiResponse.jsonError(
|
|
Http400, InvalidEpochValueError,
|
|
"Requested slot more than 1 epoch past state epoch")
|
|
|
|
if res.epoch + EPOCHS_PER_HISTORICAL_VECTOR <
|
|
bslot.slot.epoch + MIN_SEED_LOOKAHEAD:
|
|
return RestApiResponse.jsonError(
|
|
Http400, InvalidEpochValueError,
|
|
"Requested slot earlier than what committees can be computed for")
|
|
|
|
some(res)
|
|
else:
|
|
none[Slot]()
|
|
node.withStateForBlockSlotId(bslot):
|
|
proc getCommittee(slot: Slot,
|
|
index: CommitteeIndex): RestBeaconStatesCommittees =
|
|
let validators = get_beacon_committee(state, slot, index, cache)
|
|
RestBeaconStatesCommittees(index: index, slot: slot,
|
|
validators: validators)
|
|
|
|
proc forSlot(slot: Slot, cindex: Option[CommitteeIndex],
|
|
res: var seq[RestBeaconStatesCommittees]) =
|
|
let committees_per_slot = get_committee_count_per_slot(
|
|
state, slot.epoch, cache)
|
|
|
|
if cindex.isNone:
|
|
for committee_index in get_committee_indices(committees_per_slot):
|
|
res.add(getCommittee(slot, committee_index))
|
|
else:
|
|
let
|
|
idx = cindex.get()
|
|
if idx < committees_per_slot:
|
|
res.add(getCommittee(slot, idx))
|
|
|
|
var res: seq[RestBeaconStatesCommittees]
|
|
let qepoch =
|
|
if vepoch.isNone:
|
|
epoch(getStateField(state, slot))
|
|
else:
|
|
vepoch.get()
|
|
|
|
if vslot.isNone():
|
|
for slot in qepoch.slots():
|
|
forSlot(slot, vindex, res)
|
|
else:
|
|
forSlot(vslot.get(), vindex, res)
|
|
|
|
return RestApiResponse.jsonResponse(res)
|
|
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getEpochSyncCommittees
|
|
router.api(MethodGet,
|
|
"/eth/v1/beacon/states/{state_id}/sync_committees") do (
|
|
state_id: StateIdent, epoch: Option[Epoch]) -> RestApiResponse:
|
|
let
|
|
sid = state_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidStateIdValueError,
|
|
$error)
|
|
bslot = node.getBlockSlotId(sid).valueOr:
|
|
if sid.kind == StateQueryKind.Root:
|
|
# TODO (cheatfate): Its impossible to retrieve state by `state_root`
|
|
# in current version of database.
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError,
|
|
$error)
|
|
|
|
let qepoch =
|
|
if epoch.isSome():
|
|
let repoch = epoch.get()
|
|
if repoch.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidEpochValueError,
|
|
$repoch.error())
|
|
let res = repoch.get()
|
|
if res > MaxEpoch:
|
|
return RestApiResponse.jsonError(Http400, EpochOverflowValueError)
|
|
if res < node.dag.cfg.ALTAIR_FORK_EPOCH:
|
|
return RestApiResponse.jsonError(Http400,
|
|
EpochFromTheIncorrectForkError)
|
|
res
|
|
else:
|
|
# If ``epoch`` not present then the sync committees for the epoch of
|
|
# the state will be obtained.
|
|
bslot.slot.epoch()
|
|
|
|
node.withStateForBlockSlotId(bslot):
|
|
let keys =
|
|
block:
|
|
let res = syncCommitteeParticipants(state, qepoch)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
$res.error())
|
|
let kres = res.get()
|
|
if len(kres) == 0:
|
|
return RestApiResponse.jsonError(Http500, InternalServerError,
|
|
"List of sync committee participants is empty")
|
|
kres
|
|
|
|
let indices =
|
|
block:
|
|
var res: seq[ValidatorIndex]
|
|
let optIndices = keysToIndices(node.restKeysCache, state, keys)
|
|
# Remove all the duplicates.
|
|
for item in optIndices:
|
|
if item.isNone():
|
|
# This should not be happened, because keys are from state.
|
|
return RestApiResponse.jsonError(Http500, InternalServerError,
|
|
"Could not get validator indices")
|
|
res.add(item.get())
|
|
res
|
|
|
|
let aggregates =
|
|
block:
|
|
var
|
|
res: seq[seq[ValidatorIndex]]
|
|
offset = 0
|
|
while true:
|
|
let length = min(SYNC_SUBCOMMITTEE_SIZE, len(indices) - offset)
|
|
if length == 0:
|
|
break
|
|
res.add(@(indices.toOpenArray(offset, offset + length - 1)))
|
|
offset.inc(length)
|
|
res
|
|
|
|
return RestApiResponse.jsonResponse(RestEpochSyncCommittee(
|
|
validators: indices, validator_aggregates: aggregates)
|
|
)
|
|
|
|
return RestApiResponse.jsonError(Http404, StateNotFoundError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeaders
|
|
router.api(MethodGet, "/eth/v1/beacon/headers") do (
|
|
slot: Option[Slot], parent_root: Option[Eth2Digest]) -> RestApiResponse:
|
|
# TODO (cheatfate): This call is incomplete, because structure
|
|
# of database do not allow to query blocks by `parent_root`.
|
|
let qslot =
|
|
if slot.isSome():
|
|
let rslot = slot.get()
|
|
if rslot.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
|
$rslot.error())
|
|
rslot.get()
|
|
else:
|
|
node.dag.head.slot
|
|
|
|
if parent_root.isSome():
|
|
let rroot = parent_root.get()
|
|
if rroot.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidParentRootValueError,
|
|
$rroot.error())
|
|
return RestApiResponse.jsonError(Http500, NoImplementationError)
|
|
|
|
let bdata = node.getForkedBlock(BlockIdent.init(qslot)).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
return
|
|
withBlck(bdata):
|
|
RestApiResponse.jsonResponse(
|
|
[
|
|
(
|
|
root: blck.root,
|
|
canonical: node.dag.isCanonical(
|
|
BlockId(root: blck.root, slot: blck.message.slot)),
|
|
header: (
|
|
message: blck.toBeaconBlockHeader,
|
|
signature: blck.signature
|
|
)
|
|
)
|
|
]
|
|
)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockHeader
|
|
router.api(MethodGet, "/eth/v1/beacon/headers/{block_id}") do (
|
|
block_id: BlockIdent) -> RestApiResponse:
|
|
let
|
|
bid = block_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
|
$error)
|
|
|
|
bdata = node.getForkedBlock(bid).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
return
|
|
withBlck(bdata):
|
|
RestApiResponse.jsonResponse(
|
|
(
|
|
root: blck.root,
|
|
canonical: node.dag.isCanonical(
|
|
BlockId(root: blck.root, slot: blck.message.slot)),
|
|
header: (
|
|
message: blck.toBeaconBlockHeader,
|
|
signature: blck.signature
|
|
)
|
|
)
|
|
)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/publishBlock
|
|
router.api(MethodPost, "/eth/v1/beacon/blocks") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
let forkedBlock =
|
|
block:
|
|
if contentBody.isNone():
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
let body = contentBody.get()
|
|
let res = decodeBody(RestPublishedSignedBeaconBlock, body)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError,
|
|
$res.error())
|
|
var forked = ForkedSignedBeaconBlock(res.get())
|
|
if forked.kind != node.dag.cfg.blockForkAtEpoch(
|
|
getForkedBlockField(forked, slot).epoch):
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockObjectError)
|
|
|
|
withBlck(forked):
|
|
blck.root = hash_tree_root(blck.message)
|
|
forked
|
|
|
|
let res = await node.sendBeaconBlock(forkedBlock)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
|
if not(res.get()):
|
|
return RestApiResponse.jsonError(Http202, BlockValidationError)
|
|
|
|
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock
|
|
router.api(MethodGet, "/eth/v1/beacon/blocks/{block_id}") do (
|
|
block_id: BlockIdent) -> RestApiResponse:
|
|
let
|
|
blockIdent = block_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
|
$error)
|
|
bid = node.getBlockId(blockIdent).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
if node.dag.cfg.blockForkAtEpoch(bid.slot.epoch) != BeaconBlockFork.Phase0:
|
|
return RestApiResponse.jsonError(
|
|
Http404, BlockNotFoundError, "v1 API supports only phase 0 blocks")
|
|
|
|
let contentType =
|
|
block:
|
|
let res = preferredContentType(jsonMediaType,
|
|
sszMediaType)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http406, ContentNotAcceptableError)
|
|
res.get()
|
|
|
|
return
|
|
if contentType == sszMediaType:
|
|
var data: seq[byte]
|
|
if not node.dag.getBlockSSZ(bid, data):
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
RestApiResponse.response(data, Http200, $sszMediaType)
|
|
elif contentType == jsonMediaType:
|
|
let bdata = node.dag.getForkedBlock(bid).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
if bdata.kind == BeaconBlockFork.Phase0:
|
|
RestApiResponse.jsonResponse(bdata.phase0Data.asSigned())
|
|
else:
|
|
# Shouldn't happen, but in case there's some weird block database
|
|
# issue..
|
|
RestApiResponse.jsonError(
|
|
Http404, BlockNotFoundError, "v1 API supports only phase 0 blocks")
|
|
else:
|
|
RestApiResponse.jsonError(Http500, InvalidAcceptError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2
|
|
router.api(MethodGet, "/eth/v2/beacon/blocks/{block_id}") do (
|
|
block_id: BlockIdent) -> RestApiResponse:
|
|
let
|
|
blockIdent = block_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
|
$error)
|
|
bid = node.getBlockId(blockIdent).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
let contentType =
|
|
block:
|
|
let res = preferredContentType(jsonMediaType,
|
|
sszMediaType)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http406, ContentNotAcceptableError)
|
|
res.get()
|
|
return
|
|
if contentType == sszMediaType:
|
|
var data: seq[byte]
|
|
if not node.dag.getBlockSSZ(bid, data):
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
RestApiResponse.response(data, Http200, $sszMediaType)
|
|
elif contentType == jsonMediaType:
|
|
let bdata = node.dag.getForkedBlock(bid).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
RestApiResponse.jsonResponsePlain(bdata.asSigned())
|
|
else:
|
|
RestApiResponse.jsonError(Http500, InvalidAcceptError)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockRoot
|
|
router.api(MethodGet, "/eth/v1/beacon/blocks/{block_id}/root") do (
|
|
block_id: BlockIdent) -> RestApiResponse:
|
|
let
|
|
blockIdent = block_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
|
$error)
|
|
|
|
bid = node.getBlockId(blockIdent).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
return RestApiResponse.jsonResponse((root: bid.root))
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockAttestations
|
|
router.api(MethodGet,
|
|
"/eth/v1/beacon/blocks/{block_id}/attestations") do (
|
|
block_id: BlockIdent) -> RestApiResponse:
|
|
let
|
|
bid = block_id.valueOr:
|
|
return RestApiResponse.jsonError(Http400, InvalidBlockIdValueError,
|
|
$error)
|
|
|
|
bdata = node.getForkedBlock(bid).valueOr:
|
|
return RestApiResponse.jsonError(Http404, BlockNotFoundError)
|
|
|
|
return
|
|
withBlck(bdata):
|
|
RestApiResponse.jsonResponse(blck.message.body.attestations.asSeq())
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttestations
|
|
router.api(MethodGet, "/eth/v1/beacon/pool/attestations") do (
|
|
slot: Option[Slot],
|
|
committee_index: Option[CommitteeIndex]) -> RestApiResponse:
|
|
let vindex =
|
|
if committee_index.isSome():
|
|
let rindex = committee_index.get()
|
|
if rindex.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidCommitteeIndexValueError,
|
|
$rindex.error())
|
|
some(rindex.get())
|
|
else:
|
|
none[CommitteeIndex]()
|
|
let vslot =
|
|
if slot.isSome():
|
|
let rslot = slot.get()
|
|
if rslot.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidSlotValueError,
|
|
$rslot.error())
|
|
some(rslot.get())
|
|
else:
|
|
none[Slot]()
|
|
var res: seq[Attestation]
|
|
for item in node.attestationPool[].attestations(vslot, vindex):
|
|
res.add(item)
|
|
return RestApiResponse.jsonResponse(res)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttestations
|
|
router.api(MethodPost, "/eth/v1/beacon/pool/attestations") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
let attestations =
|
|
block:
|
|
if contentBody.isNone():
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
let dres = decodeBody(seq[Attestation], contentBody.get())
|
|
if dres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidAttestationObjectError,
|
|
$dres.error())
|
|
dres.get()
|
|
|
|
# Since our validation logic supports batch processing, we will submit all
|
|
# attestations for validation.
|
|
let pending =
|
|
block:
|
|
var res: seq[Future[SendResult]]
|
|
for attestation in attestations:
|
|
res.add(node.sendAttestation(attestation))
|
|
res
|
|
let failures =
|
|
block:
|
|
var res: seq[RestAttestationsFailure]
|
|
await allFutures(pending)
|
|
for index, future in pending.pairs():
|
|
if future.done():
|
|
let fres = future.read()
|
|
if fres.isErr():
|
|
let failure = RestAttestationsFailure(index: uint64(index),
|
|
message: $fres.error())
|
|
res.add(failure)
|
|
elif future.failed() or future.cancelled():
|
|
# This is unexpected failure, so we log the error message.
|
|
let exc = future.readError()
|
|
let failure = RestAttestationsFailure(index: uint64(index),
|
|
message: $exc.msg)
|
|
res.add(failure)
|
|
res
|
|
|
|
if len(failures) > 0:
|
|
return RestApiResponse.jsonErrorList(Http400, AttestationValidationError,
|
|
failures)
|
|
else:
|
|
return RestApiResponse.jsonMsgResponse(AttestationValidationSuccess)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolAttesterSlashings
|
|
router.api(MethodGet, "/eth/v1/beacon/pool/attester_slashings") do (
|
|
) -> RestApiResponse:
|
|
var res: seq[AttesterSlashing]
|
|
if isNil(node.exitPool):
|
|
return RestApiResponse.jsonResponse(res)
|
|
let length = len(node.exitPool.attester_slashings)
|
|
res = newSeqOfCap[AttesterSlashing](length)
|
|
for item in node.exitPool.attester_slashings.items():
|
|
res.add(item)
|
|
return RestApiResponse.jsonResponse(res)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolAttesterSlashings
|
|
router.api(MethodPost, "/eth/v1/beacon/pool/attester_slashings") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
let slashing =
|
|
block:
|
|
if contentBody.isNone():
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
let dres = decodeBody(AttesterSlashing, contentBody.get())
|
|
if dres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidAttesterSlashingObjectError,
|
|
$dres.error())
|
|
dres.get()
|
|
let res = node.sendAttesterSlashing(slashing)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
AttesterSlashingValidationError,
|
|
$res.error())
|
|
return RestApiResponse.jsonMsgResponse(AttesterSlashingValidationSuccess)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolProposerSlashings
|
|
router.api(MethodGet, "/eth/v1/beacon/pool/proposer_slashings") do (
|
|
) -> RestApiResponse:
|
|
var res: seq[ProposerSlashing]
|
|
if isNil(node.exitPool):
|
|
return RestApiResponse.jsonResponse(res)
|
|
let length = len(node.exitPool.proposer_slashings)
|
|
res = newSeqOfCap[ProposerSlashing](length)
|
|
for item in node.exitPool.proposer_slashings.items():
|
|
res.add(item)
|
|
return RestApiResponse.jsonResponse(res)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolProposerSlashings
|
|
router.api(MethodPost, "/eth/v1/beacon/pool/proposer_slashings") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
let slashing =
|
|
block:
|
|
if contentBody.isNone():
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
let dres = decodeBody(ProposerSlashing, contentBody.get())
|
|
if dres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidProposerSlashingObjectError,
|
|
$dres.error())
|
|
dres.get()
|
|
let res = node.sendProposerSlashing(slashing)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
ProposerSlashingValidationError,
|
|
$res.error())
|
|
return RestApiResponse.jsonMsgResponse(ProposerSlashingValidationSuccess)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolSyncCommitteeSignatures
|
|
router.api(MethodPost, "/eth/v1/beacon/pool/sync_committees") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
let messages =
|
|
block:
|
|
if contentBody.isNone():
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
let dres = decodeBody(seq[SyncCommitteeMessage], contentBody.get())
|
|
if dres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidSyncCommitteeSignatureMessageError)
|
|
dres.get()
|
|
|
|
let results = await node.sendSyncCommitteeMessages(messages)
|
|
|
|
let failures =
|
|
block:
|
|
var res: seq[RestAttestationsFailure]
|
|
for index, item in results.pairs():
|
|
if item.isErr():
|
|
res.add(RestAttestationsFailure(index: uint64(index),
|
|
message: $item.error()))
|
|
res
|
|
if len(failures) > 0:
|
|
return RestApiResponse.jsonErrorList(Http400,
|
|
SyncCommitteeMessageValidationError,
|
|
failures)
|
|
else:
|
|
return RestApiResponse.jsonMsgResponse(
|
|
SyncCommitteeMessageValidationSuccess)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/getPoolVoluntaryExits
|
|
router.api(MethodGet, "/eth/v1/beacon/pool/voluntary_exits") do (
|
|
) -> RestApiResponse:
|
|
var res: seq[SignedVoluntaryExit]
|
|
if isNil(node.exitPool):
|
|
return RestApiResponse.jsonResponse(res)
|
|
let length = len(node.exitPool.voluntary_exits)
|
|
res = newSeqOfCap[SignedVoluntaryExit](length)
|
|
for item in node.exitPool.voluntary_exits.items():
|
|
res.add(item)
|
|
return RestApiResponse.jsonResponse(res)
|
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit
|
|
router.api(MethodPost, "/eth/v1/beacon/pool/voluntary_exits") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
let exit =
|
|
block:
|
|
if contentBody.isNone():
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
let dres = decodeBody(SignedVoluntaryExit, contentBody.get())
|
|
if dres.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
InvalidVoluntaryExitObjectError,
|
|
$dres.error())
|
|
dres.get()
|
|
let res = node.sendVoluntaryExit(exit)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400,
|
|
VoluntaryExitValidationError,
|
|
$res.error())
|
|
return RestApiResponse.jsonMsgResponse(VoluntaryExitValidationSuccess)
|
|
|
|
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
|
|
# `/api` path component
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/genesis",
|
|
"/eth/v1/beacon/genesis",
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/root",
|
|
"/eth/v1/beacon/states/{state_id}/root",
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/fork",
|
|
"/eth/v1/beacon/states/{state_id}/fork",
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/finality_checkpoints",
|
|
"/eth/v1/beacon/states/{state_id}/finality_checkpoints"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/validators",
|
|
"/eth/v1/beacon/states/{state_id}/validators"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/validators/{validator_id}",
|
|
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/validator_balances",
|
|
"/eth/v1/beacon/states/{state_id}/validator_balances"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/committees",
|
|
"/eth/v1/beacon/states/{state_id}/committees"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/states/{state_id}/sync_committees",
|
|
"/eth/v1/beacon/states/{state_id}/sync_committees"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/headers",
|
|
"/eth/v1/beacon/headers"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/headers/{block_id}",
|
|
"/eth/v1/beacon/headers/{block_id}"
|
|
)
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/eth/v1/beacon/blocks",
|
|
"/eth/v1/beacon/blocks"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/blocks/{block_id}",
|
|
"/eth/v1/beacon/blocks/{block_id}"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v2/beacon/blocks/{block_id}",
|
|
"/eth/v2/beacon/blocks/{block_id}"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/blocks/{block_id}/root",
|
|
"/eth/v1/beacon/blocks/{block_id}/root"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/blocks/{block_id}/attestations",
|
|
"/eth/v1/beacon/blocks/{block_id}/attestations"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/pool/attestations",
|
|
"/eth/v1/beacon/pool/attestations"
|
|
)
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/eth/v1/beacon/pool/attestations",
|
|
"/eth/v1/beacon/pool/attestations"
|
|
)
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/eth/v1/beacon/pool/attester_slashings",
|
|
"/eth/v1/beacon/pool/attester_slashings"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/pool/attester_slashings",
|
|
"/eth/v1/beacon/pool/attester_slashings"
|
|
)
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/eth/v1/beacon/pool/proposer_slashings",
|
|
"/eth/v1/beacon/pool/proposer_slashings"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/pool/proposer_slashings",
|
|
"/eth/v1/beacon/pool/proposer_slashings"
|
|
)
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/eth/v1/beacon/pool/sync_committees",
|
|
"/eth/v1/beacon/pool/sync_committees"
|
|
)
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/eth/v1/beacon/pool/voluntary_exits",
|
|
"/eth/v1/beacon/pool/voluntary_exits"
|
|
)
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/eth/v1/beacon/pool/voluntary_exits",
|
|
"/eth/v1/beacon/pool/voluntary_exits"
|
|
)
|