remove deprecated JSON-RPC server (#3656)
* remove deprecated JSON-RPC server * keep the command-line options around as no-ops, temporarily * service -> server; JSON-RPC is still used elsewhere
This commit is contained in:
parent
1101c745b9
commit
d35d408fa7
|
@ -53,7 +53,6 @@ type
|
||||||
lightClientPool*: ref LightClientPool
|
lightClientPool*: ref LightClientPool
|
||||||
exitPool*: ref ExitPool
|
exitPool*: ref ExitPool
|
||||||
eth1Monitor*: Eth1Monitor
|
eth1Monitor*: Eth1Monitor
|
||||||
rpcServer*: RpcServer
|
|
||||||
restServer*: RestServerRef
|
restServer*: RestServerRef
|
||||||
keymanagerServer*: RestServerRef
|
keymanagerServer*: RestServerRef
|
||||||
keymanagerToken*: Option[string]
|
keymanagerToken*: Option[string]
|
||||||
|
|
|
@ -338,18 +338,21 @@ type
|
||||||
name: "status-bar-contents" }: string
|
name: "status-bar-contents" }: string
|
||||||
|
|
||||||
rpcEnabled* {.
|
rpcEnabled* {.
|
||||||
desc: "Enable the JSON-RPC server (deprecated)"
|
hidden
|
||||||
|
desc: "Enable the JSON-RPC server (deprecated for removal)"
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
name: "rpc" }: bool
|
name: "rpc" }: bool
|
||||||
|
|
||||||
rpcPort* {.
|
rpcPort* {.
|
||||||
desc: "HTTP port for the JSON-RPC service"
|
hidden
|
||||||
defaultValue: defaultEth2RpcPort
|
desc: "HTTP port for the JSON-RPC service (deprecated for removal)"
|
||||||
|
defaultValue: 9190
|
||||||
defaultValueDesc: "9190"
|
defaultValueDesc: "9190"
|
||||||
name: "rpc-port" }: Port
|
name: "rpc-port" }: Port
|
||||||
|
|
||||||
rpcAddress* {.
|
rpcAddress* {.
|
||||||
desc: "Listening address of the RPC server"
|
hidden
|
||||||
|
desc: "Listening address of the RPC server (deprecated for removal)"
|
||||||
defaultValue: defaultAdminListenAddress
|
defaultValue: defaultAdminListenAddress
|
||||||
defaultValueDesc: "127.0.0.1"
|
defaultValueDesc: "127.0.0.1"
|
||||||
name: "rpc-address" }: ValidIpAddress
|
name: "rpc-address" }: ValidIpAddress
|
||||||
|
|
|
@ -14,7 +14,7 @@ import
|
||||||
stew/[byteutils, io2],
|
stew/[byteutils, io2],
|
||||||
eth/p2p/discoveryv5/[enr, random2],
|
eth/p2p/discoveryv5/[enr, random2],
|
||||||
eth/keys,
|
eth/keys,
|
||||||
./rpc/[rest_api, rpc_api, state_ttl_cache],
|
./rpc/[rest_api, state_ttl_cache],
|
||||||
./spec/datatypes/[altair, bellatrix, phase0],
|
./spec/datatypes/[altair, bellatrix, phase0],
|
||||||
./spec/[engine_authentication, weak_subjectivity],
|
./spec/[engine_authentication, weak_subjectivity],
|
||||||
./validators/[keystore_management, validator_duties],
|
./validators/[keystore_management, validator_duties],
|
||||||
|
@ -577,10 +577,8 @@ proc init*(T: type BeaconNode,
|
||||||
config.web3ForcePolling,
|
config.web3ForcePolling,
|
||||||
optJwtSecret)
|
optJwtSecret)
|
||||||
|
|
||||||
let rpcServer = if config.rpcEnabled:
|
if config.rpcEnabled:
|
||||||
RpcServer.init(config.rpcAddress, config.rpcPort)
|
warn "Nimbus's JSON-RPC server has been removed. This includes the --rpc, --rpc-port, and --rpc-address configuration options. https://nimbus.guide/rest-api.html shows how to enable and configure the REST Beacon API server which replaces it."
|
||||||
else:
|
|
||||||
nil
|
|
||||||
|
|
||||||
let restServer = if config.restEnabled:
|
let restServer = if config.restEnabled:
|
||||||
RestServerRef.init(
|
RestServerRef.init(
|
||||||
|
@ -676,7 +674,6 @@ proc init*(T: type BeaconNode,
|
||||||
config: config,
|
config: config,
|
||||||
attachedValidators: validatorPool,
|
attachedValidators: validatorPool,
|
||||||
eth1Monitor: eth1Monitor,
|
eth1Monitor: eth1Monitor,
|
||||||
rpcServer: rpcServer,
|
|
||||||
restServer: restServer,
|
restServer: restServer,
|
||||||
keymanagerServer: keymanagerServer,
|
keymanagerServer: keymanagerServer,
|
||||||
keymanagerToken: keymanagerToken,
|
keymanagerToken: keymanagerToken,
|
||||||
|
@ -1293,16 +1290,6 @@ proc runOnSecondLoop(node: BeaconNode) {.async.} =
|
||||||
func connectedPeersCount(node: BeaconNode): int =
|
func connectedPeersCount(node: BeaconNode): int =
|
||||||
len(node.network.peerPool)
|
len(node.network.peerPool)
|
||||||
|
|
||||||
proc installRpcHandlers(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.installBeaconApiHandlers(node)
|
|
||||||
rpcServer.installConfigApiHandlers(node)
|
|
||||||
rpcServer.installDebugApiHandlers(node)
|
|
||||||
rpcServer.installEventApiHandlers(node)
|
|
||||||
rpcServer.installNimbusApiHandlers(node)
|
|
||||||
rpcServer.installNodeApiHandlers(node)
|
|
||||||
rpcServer.installValidatorApiHandlers(node)
|
|
||||||
|
|
||||||
proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
|
proc installRestHandlers(restServer: RestServerRef, node: BeaconNode) =
|
||||||
restServer.router.installBeaconApiHandlers(node)
|
restServer.router.installBeaconApiHandlers(node)
|
||||||
restServer.router.installConfigApiHandlers(node)
|
restServer.router.installConfigApiHandlers(node)
|
||||||
|
@ -1465,10 +1452,6 @@ proc startBackfillTask(node: BeaconNode) {.async.} =
|
||||||
proc run(node: BeaconNode) {.raises: [Defect, CatchableError].} =
|
proc run(node: BeaconNode) {.raises: [Defect, CatchableError].} =
|
||||||
bnStatus = BeaconNodeStatus.Running
|
bnStatus = BeaconNodeStatus.Running
|
||||||
|
|
||||||
if not(isNil(node.rpcServer)):
|
|
||||||
node.rpcServer.installRpcHandlers(node)
|
|
||||||
node.rpcServer.start()
|
|
||||||
|
|
||||||
if not(isNil(node.restServer)):
|
if not(isNil(node.restServer)):
|
||||||
node.restServer.installRestHandlers(node)
|
node.restServer.installRestHandlers(node)
|
||||||
node.restServer.start()
|
node.restServer.start()
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
# Copyright (c) 2018-2021 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.
|
|
||||||
|
|
||||||
## The `rpc_api` module is a server implementation of a JSON-RPC-based API
|
|
||||||
## for Nimbus - it is based on the common REST API for Ethereum 2 found at
|
|
||||||
## https://ethereum.github.io/eth2.0-APIs/# but uses JSON-RPC as transport
|
|
||||||
## instead. There are also minor historical differences in encoding and data -
|
|
||||||
## these differences will likely remain so as to not break old tooling.
|
|
||||||
##
|
|
||||||
## The JSON-RPC is used by community utilities and tools and is kept for
|
|
||||||
## backwards compatibility mainly.
|
|
||||||
## A corresponding client can be found in the
|
|
||||||
## `spec/eth2_apis/rpc_beacon_client` module.
|
|
||||||
import
|
|
||||||
"."/[
|
|
||||||
rpc_beacon_api, rpc_config_api, rpc_debug_api, rpc_event_api,
|
|
||||||
rpc_nimbus_api, rpc_node_api, rpc_validator_api]
|
|
||||||
|
|
||||||
export
|
|
||||||
rpc_beacon_api, rpc_config_api, rpc_debug_api, rpc_event_api,
|
|
||||||
rpc_nimbus_api, rpc_node_api, rpc_validator_api
|
|
|
@ -1,513 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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/[parseutils, sequtils, strutils, sets],
|
|
||||||
stew/[byteutils, results],
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
chronicles,
|
|
||||||
../beacon_node,
|
|
||||||
../networking/eth2_network,
|
|
||||||
../validators/validator_duties,
|
|
||||||
../consensus_object_pools/blockchain_dag,
|
|
||||||
../spec/[eth2_merkleization, forks, network, validator],
|
|
||||||
../spec/datatypes/[phase0],
|
|
||||||
./rpc_utils
|
|
||||||
|
|
||||||
logScope: topics = "beaconapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer = RpcHttpServer
|
|
||||||
|
|
||||||
ValidatorQuery = object
|
|
||||||
# Security note / threat model:
|
|
||||||
# - The validator pubkey are stored in their raw bytes representation
|
|
||||||
# in the `keyset`.
|
|
||||||
# - While the input is from unknown source (as far as the beacon node is concerned),
|
|
||||||
# users are asked to not expose their
|
|
||||||
# RPC endpoints to untrusted network or use a reverse proxy in front.
|
|
||||||
# - At usage time, keys in the keyset are compared to keys registered
|
|
||||||
# in the Ethereum BeaconState which are valid
|
|
||||||
keyset: HashSet[ValidatorPubKey]
|
|
||||||
ids: seq[uint64]
|
|
||||||
|
|
||||||
StatusQuery = object
|
|
||||||
statset: HashSet[string]
|
|
||||||
|
|
||||||
template unimplemented() =
|
|
||||||
raise (ref CatchableError)(msg: "Unimplemented")
|
|
||||||
|
|
||||||
proc createIdQuery(ids: openArray[string]): Result[ValidatorQuery, cstring] =
|
|
||||||
# validatorIds array should have maximum 30 items, and all items should be
|
|
||||||
# unique.
|
|
||||||
if len(ids) > 30:
|
|
||||||
return err("The number of ids exceeds the limit")
|
|
||||||
|
|
||||||
# All ids in validatorIds must be unique.
|
|
||||||
if len(ids) != len(toHashSet(ids)):
|
|
||||||
return err("ids array must have unique item")
|
|
||||||
|
|
||||||
var res = ValidatorQuery(
|
|
||||||
keyset: initHashSet[ValidatorPubKey](),
|
|
||||||
ids: newSeq[uint64]()
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in ids:
|
|
||||||
if item.startsWith("0x"):
|
|
||||||
let pubkey = ? ValidatorPubKey.fromHex(item)
|
|
||||||
res.keyset.incl(pubkey)
|
|
||||||
else:
|
|
||||||
var tmp: uint64
|
|
||||||
try:
|
|
||||||
if parseBiggestUInt(item, tmp) != len(item):
|
|
||||||
return err("Incorrect index value")
|
|
||||||
except ValueError:
|
|
||||||
return err("Cannot parse index value")
|
|
||||||
res.ids.add(tmp)
|
|
||||||
ok(res)
|
|
||||||
|
|
||||||
proc createStatusQuery(status: openArray[string]): Result[StatusQuery, string] =
|
|
||||||
const AllowedStatuses = [
|
|
||||||
"pending", "pending_initialized", "pending_queued",
|
|
||||||
"active", "active_ongoing", "active_exiting", "active_slashed",
|
|
||||||
"exited", "exited_unslashed", "exited_slashed",
|
|
||||||
"withdrawal", "withdrawal_possible", "withdrawal_done"
|
|
||||||
]
|
|
||||||
|
|
||||||
if len(status) > len(AllowedStatuses):
|
|
||||||
return err("The number of statuses exceeds the limit")
|
|
||||||
|
|
||||||
var res = StatusQuery(statset: initHashSet[string]())
|
|
||||||
|
|
||||||
# All ids in validatorIds must be unique.
|
|
||||||
if len(status) != len(toHashSet(status)):
|
|
||||||
return err("Status array must have unique items")
|
|
||||||
|
|
||||||
for item in status:
|
|
||||||
if item notin AllowedStatuses:
|
|
||||||
return err("Invalid status requested")
|
|
||||||
case item
|
|
||||||
of "pending":
|
|
||||||
res.statset.incl("pending_initialized")
|
|
||||||
res.statset.incl("pending_queued")
|
|
||||||
of "active":
|
|
||||||
res.statset.incl("active_ongoing")
|
|
||||||
res.statset.incl("active_exiting")
|
|
||||||
res.statset.incl("active_slashed")
|
|
||||||
of "exited":
|
|
||||||
res.statset.incl("exited_unslashed")
|
|
||||||
res.statset.incl("exited_slashed")
|
|
||||||
of "withdrawal":
|
|
||||||
res.statset.incl("withdrawal_possible")
|
|
||||||
res.statset.incl("withdrawal_done")
|
|
||||||
else:
|
|
||||||
res.statset.incl(item)
|
|
||||||
|
|
||||||
proc getStatus(validator: Validator,
|
|
||||||
current_epoch: Epoch): Result[string, string] =
|
|
||||||
if validator.activation_epoch > current_epoch:
|
|
||||||
# pending
|
|
||||||
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH:
|
|
||||||
ok("pending_initialized")
|
|
||||||
else:
|
|
||||||
# validator.activation_eligibility_epoch < FAR_FUTURE_EPOCH:
|
|
||||||
ok("pending_queued")
|
|
||||||
elif (validator.activation_epoch <= current_epoch) and
|
|
||||||
(current_epoch < validator.exit_epoch):
|
|
||||||
# active
|
|
||||||
if validator.exit_epoch == FAR_FUTURE_EPOCH:
|
|
||||||
ok("active_ongoing")
|
|
||||||
elif not validator.slashed:
|
|
||||||
# validator.exit_epoch < FAR_FUTURE_EPOCH
|
|
||||||
ok("active_exiting")
|
|
||||||
else:
|
|
||||||
# validator.exit_epoch < FAR_FUTURE_EPOCH and validator.slashed:
|
|
||||||
ok("active_slashed")
|
|
||||||
elif (validator.exit_epoch <= current_epoch) and
|
|
||||||
(current_epoch < validator.withdrawable_epoch):
|
|
||||||
# exited
|
|
||||||
if not validator.slashed:
|
|
||||||
ok("exited_unslashed")
|
|
||||||
else:
|
|
||||||
# validator.slashed
|
|
||||||
ok("exited_slashed")
|
|
||||||
elif validator.withdrawable_epoch <= current_epoch:
|
|
||||||
# withdrawal
|
|
||||||
if validator.effective_balance != 0:
|
|
||||||
ok("withdrawal_possible")
|
|
||||||
else:
|
|
||||||
# validator.effective_balance == 0
|
|
||||||
ok("withdrawal_done")
|
|
||||||
else:
|
|
||||||
err("Invalid validator status")
|
|
||||||
|
|
||||||
proc getForkedBlockFromBlockId(
|
|
||||||
node: BeaconNode, blockId: string): ForkedTrustedSignedBeaconBlock {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
case blockId:
|
|
||||||
of "head":
|
|
||||||
node.dag.getForkedBlock(node.dag.head.bid).valueOr:
|
|
||||||
raise newException(CatchableError, "Block not found")
|
|
||||||
of "genesis":
|
|
||||||
node.dag.getForkedBlock(node.dag.genesis).valueOr:
|
|
||||||
raise newException(CatchableError, "Block not found")
|
|
||||||
of "finalized":
|
|
||||||
node.dag.getForkedBlock(node.dag.finalizedHead.blck.bid).valueOr:
|
|
||||||
raise newException(CatchableError, "Block not found")
|
|
||||||
else:
|
|
||||||
if blockId.startsWith("0x"):
|
|
||||||
let
|
|
||||||
blckRoot = parseRoot(blockId)
|
|
||||||
node.dag.getForkedBlock(blckRoot).valueOr:
|
|
||||||
raise newException(CatchableError, "Block not found")
|
|
||||||
else:
|
|
||||||
let bid = node.getBlockIdFromString(blockId)
|
|
||||||
node.dag.getForkedBlock(bid).valueOr:
|
|
||||||
raise newException(CatchableError, "Block not found")
|
|
||||||
|
|
||||||
proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.rpc("get_v1_beacon_genesis") do () -> RpcBeaconGenesis:
|
|
||||||
return (
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_root") do (stateId: string) -> Eth2Digest:
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
return stateRoot
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_fork") do (stateId: string) -> Fork:
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
return getStateField(state, fork)
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_finality_checkpoints") do (
|
|
||||||
stateId: string) -> RpcBeaconStatesFinalityCheckpoints:
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
return (previous_justified:
|
|
||||||
getStateField(state, previous_justified_checkpoint),
|
|
||||||
current_justified:
|
|
||||||
getStateField(state, current_justified_checkpoint),
|
|
||||||
finalized: getStateField(state, finalized_checkpoint))
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_stateId_validators") do (
|
|
||||||
stateId: string, validatorIds: Option[seq[string]],
|
|
||||||
status: Option[seq[string]]) -> seq[RpcBeaconStatesValidators]:
|
|
||||||
var vquery: ValidatorQuery
|
|
||||||
var squery: StatusQuery
|
|
||||||
let current_epoch = getStateField(node.dag.headState, slot).epoch
|
|
||||||
|
|
||||||
template statusCheck(status, statusQuery, vstatus, current_epoch): bool =
|
|
||||||
if status.isNone():
|
|
||||||
true
|
|
||||||
else:
|
|
||||||
if vstatus in squery.statset:
|
|
||||||
true
|
|
||||||
else:
|
|
||||||
false
|
|
||||||
|
|
||||||
var res: seq[RpcBeaconStatesValidators]
|
|
||||||
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
if status.isSome:
|
|
||||||
let sqres = createStatusQuery(status.get())
|
|
||||||
if sqres.isErr:
|
|
||||||
raise newException(CatchableError, sqres.error)
|
|
||||||
squery = sqres.get()
|
|
||||||
|
|
||||||
if validatorIds.isSome:
|
|
||||||
let vqres = createIdQuery(validatorIds.get())
|
|
||||||
if vqres.isErr:
|
|
||||||
raise newException(CatchableError, $vqres.error)
|
|
||||||
vquery = vqres.get()
|
|
||||||
|
|
||||||
if validatorIds.isNone():
|
|
||||||
for index, validator in getStateField(state, validators):
|
|
||||||
let sres = validator.getStatus(current_epoch)
|
|
||||||
if sres.isOk:
|
|
||||||
let vstatus = sres.get()
|
|
||||||
let includeFlag = statusCheck(status, squery, vstatus,
|
|
||||||
current_epoch)
|
|
||||||
if includeFlag:
|
|
||||||
res.add((validator: validator,
|
|
||||||
index: uint64(index),
|
|
||||||
status: vstatus,
|
|
||||||
balance: getStateField(state, balances).asSeq()[index]))
|
|
||||||
else:
|
|
||||||
for index in vquery.ids:
|
|
||||||
if index < lenu64(getStateField(state, validators)):
|
|
||||||
let validator = getStateField(state, validators).asSeq()[index]
|
|
||||||
let sres = validator.getStatus(current_epoch)
|
|
||||||
if sres.isOk:
|
|
||||||
let vstatus = sres.get()
|
|
||||||
let includeFlag = statusCheck(status, squery, vstatus,
|
|
||||||
current_epoch)
|
|
||||||
if includeFlag:
|
|
||||||
vquery.keyset.excl(validator.pubkey)
|
|
||||||
res.add((validator: validator,
|
|
||||||
index: uint64(index),
|
|
||||||
status: vstatus,
|
|
||||||
balance: getStateField(state, balances).asSeq()[index]))
|
|
||||||
|
|
||||||
for index, validator in getStateField(state, validators):
|
|
||||||
if validator.pubkey in vquery.keyset:
|
|
||||||
let sres = validator.getStatus(current_epoch)
|
|
||||||
if sres.isOk:
|
|
||||||
let vstatus = sres.get()
|
|
||||||
let includeFlag = statusCheck(status, squery, vstatus,
|
|
||||||
current_epoch)
|
|
||||||
if includeFlag:
|
|
||||||
res.add((validator: validator,
|
|
||||||
index: uint64(index),
|
|
||||||
status: vstatus,
|
|
||||||
balance: getStateField(state, balances).asSeq()[index]))
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_stateId_validators_validatorId") do (
|
|
||||||
stateId: string, validatorId: string) -> RpcBeaconStatesValidators:
|
|
||||||
let current_epoch = getStateField(node.dag.headState, slot).epoch
|
|
||||||
let vqres = createIdQuery([validatorId])
|
|
||||||
if vqres.isErr:
|
|
||||||
raise newException(CatchableError, $vqres.error)
|
|
||||||
let vquery = vqres.get()
|
|
||||||
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
if len(vquery.ids) > 0:
|
|
||||||
let index = vquery.ids[0]
|
|
||||||
if index < lenu64(getStateField(state, validators)):
|
|
||||||
let validator = getStateField(state, validators).asSeq()[index]
|
|
||||||
let sres = validator.getStatus(current_epoch)
|
|
||||||
if sres.isOk:
|
|
||||||
return (validator: validator, index: uint64(index),
|
|
||||||
status: sres.get(),
|
|
||||||
balance: getStateField(state, balances).asSeq()[index])
|
|
||||||
else:
|
|
||||||
raise newException(CatchableError, "Incorrect validator's state")
|
|
||||||
else:
|
|
||||||
for index, validator in getStateField(state, validators):
|
|
||||||
if validator.pubkey in vquery.keyset:
|
|
||||||
let sres = validator.getStatus(current_epoch)
|
|
||||||
if sres.isOk:
|
|
||||||
return (validator: validator, index: uint64(index),
|
|
||||||
status: sres.get(),
|
|
||||||
balance: getStateField(state, balances).asSeq()[index])
|
|
||||||
else:
|
|
||||||
raise newException(CatchableError, "Incorrect validator's state")
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_stateId_validator_balances") do (
|
|
||||||
stateId: string, validatorsId: Option[seq[string]]) -> seq[RpcBalance]:
|
|
||||||
|
|
||||||
var res: seq[RpcBalance]
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
if validatorsId.isNone():
|
|
||||||
for index, value in getStateField(state, balances):
|
|
||||||
let balance = (index: uint64(index), balance: value)
|
|
||||||
res.add(balance)
|
|
||||||
else:
|
|
||||||
let vqres = createIdQuery(validatorsId.get())
|
|
||||||
if vqres.isErr:
|
|
||||||
raise newException(CatchableError, $vqres.error)
|
|
||||||
|
|
||||||
var vquery = vqres.get()
|
|
||||||
for index in vquery.ids:
|
|
||||||
if index < lenu64(getStateField(state, validators)):
|
|
||||||
let validator = getStateField(state, validators).asSeq()[index]
|
|
||||||
vquery.keyset.excl(validator.pubkey)
|
|
||||||
let balance = (index: uint64(index),
|
|
||||||
balance: getStateField(state, balances).asSeq()[index])
|
|
||||||
res.add(balance)
|
|
||||||
|
|
||||||
for index, validator in getStateField(state, validators):
|
|
||||||
if validator.pubkey in vquery.keyset:
|
|
||||||
let balance = (index: uint64(index),
|
|
||||||
balance: getStateField(state, balances).asSeq()[index])
|
|
||||||
res.add(balance)
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_states_stateId_committees_epoch") do (
|
|
||||||
stateId: string, epoch: Option[uint64], index: Option[uint64],
|
|
||||||
slot: Option[uint64]) -> seq[RpcBeaconStatesCommittees]:
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
proc getCommittee(slot: Slot,
|
|
||||||
index: CommitteeIndex): RpcBeaconStatesCommittees =
|
|
||||||
let vals = get_beacon_committee(
|
|
||||||
state, slot, index, cache).mapIt(it.uint64)
|
|
||||||
return (index: index.uint64, slot: slot.uint64, validators: vals)
|
|
||||||
|
|
||||||
proc forSlot(slot: Slot, res: var seq[RpcBeaconStatesCommittees]) =
|
|
||||||
let committees_per_slot =
|
|
||||||
get_committee_count_per_slot(state, slot.epoch, cache)
|
|
||||||
|
|
||||||
if index.isNone:
|
|
||||||
for committee_index in get_committee_indices(committees_per_slot):
|
|
||||||
res.add(getCommittee(slot, committee_index))
|
|
||||||
else:
|
|
||||||
if index.get() < committees_per_slot:
|
|
||||||
let cindex = CommitteeIndex.init(index.get()).expect(
|
|
||||||
"valid because verified against committees_per_slot")
|
|
||||||
res.add(getCommittee(slot, cindex))
|
|
||||||
|
|
||||||
var res: seq[RpcBeaconStatesCommittees]
|
|
||||||
|
|
||||||
let qepoch =
|
|
||||||
if epoch.isNone:
|
|
||||||
epoch(getStateField(state, slot))
|
|
||||||
else:
|
|
||||||
Epoch(epoch.get())
|
|
||||||
|
|
||||||
if slot.isNone:
|
|
||||||
for slot in qepoch.slots():
|
|
||||||
forSlot(slot, res)
|
|
||||||
else:
|
|
||||||
forSlot(Slot(slot.get()), res)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_headers") do (
|
|
||||||
slot: Option[uint64], parent_root: Option[string]) ->
|
|
||||||
seq[RpcBeaconHeaders]:
|
|
||||||
unimplemented()
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_headers_blockId") do (
|
|
||||||
blockId: string) ->
|
|
||||||
tuple[canonical: bool, header: SignedBeaconBlockHeader]:
|
|
||||||
let bd = node.getForkedBlockFromBlockId(blockId)
|
|
||||||
return withBlck(bd):
|
|
||||||
static: doAssert blck.signature is TrustedSig and
|
|
||||||
sizeof(ValidatorSig) == sizeof(blck.signature)
|
|
||||||
(
|
|
||||||
canonical: node.dag.isCanonical(
|
|
||||||
BlockId(root: blck.root, slot: blck.message.slot)),
|
|
||||||
header: SignedBeaconBlockHeader(
|
|
||||||
message: blck.toBeaconBlockHeader
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_blocks") do (blck: phase0.SignedBeaconBlock) -> int:
|
|
||||||
let res = await sendBeaconBlock(node, ForkedSignedBeaconBlock.init(blck))
|
|
||||||
if res.isErr():
|
|
||||||
raise (ref CatchableError)(msg: $res.error())
|
|
||||||
|
|
||||||
if res.get():
|
|
||||||
# The block was validated successfully and has been broadcast.
|
|
||||||
# It has also been integrated into the beacon node's database.
|
|
||||||
return 200
|
|
||||||
else:
|
|
||||||
# The block failed validation, but was successfully broadcast anyway.
|
|
||||||
# It was not integrated into the beacon node''s database.
|
|
||||||
return 202
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_blocks_blockId") do (
|
|
||||||
blockId: string) -> phase0.TrustedSignedBeaconBlock:
|
|
||||||
let blck = node.getForkedBlockFromBlockId(blockId)
|
|
||||||
if blck.kind == BeaconBlockFork.Phase0:
|
|
||||||
return blck.phase0Data
|
|
||||||
else:
|
|
||||||
raiseNoAltairSupport()
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_blocks_blockId_root") do (
|
|
||||||
blockId: string) -> Eth2Digest:
|
|
||||||
return withBlck(node.getForkedBlockFromBlockId(blockId)):
|
|
||||||
blck.root
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_blocks_blockId_attestations") do (
|
|
||||||
blockId: string) -> seq[TrustedAttestation]:
|
|
||||||
return withBlck(node.getForkedBlockFromBlockId(blockId)):
|
|
||||||
blck.message.body.attestations.asSeq
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_attestations") do (
|
|
||||||
slot: Option[uint64], committee_index: Option[uint64]) ->
|
|
||||||
seq[RpcAttestation]:
|
|
||||||
|
|
||||||
var res: seq[RpcAttestation]
|
|
||||||
|
|
||||||
let qslot =
|
|
||||||
if slot.isSome():
|
|
||||||
some(Slot(slot.get()))
|
|
||||||
else:
|
|
||||||
none[Slot]()
|
|
||||||
|
|
||||||
let qindex =
|
|
||||||
if committee_index.isSome():
|
|
||||||
some(CommitteeIndex(committee_index.get()))
|
|
||||||
else:
|
|
||||||
none[CommitteeIndex]()
|
|
||||||
|
|
||||||
for item in node.attestationPool[].attestations(qslot, qindex):
|
|
||||||
let atuple = (
|
|
||||||
aggregation_bits: to0xHex(item.aggregation_bits.bytes),
|
|
||||||
data: item.data,
|
|
||||||
signature: item.signature
|
|
||||||
)
|
|
||||||
res.add(atuple)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_attestations") do (
|
|
||||||
attestation: Attestation) -> bool:
|
|
||||||
let res = await node.sendAttestation(attestation)
|
|
||||||
if not res.isOk():
|
|
||||||
raise (ref CatchableError)(msg: $res.error())
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_attester_slashings") do (
|
|
||||||
) -> seq[AttesterSlashing]:
|
|
||||||
var res: seq[AttesterSlashing]
|
|
||||||
if isNil(node.exitPool):
|
|
||||||
return res
|
|
||||||
let length = len(node.exitPool.attester_slashings)
|
|
||||||
res = newSeqOfCap[AttesterSlashing](length)
|
|
||||||
for item in node.exitPool.attester_slashings.items():
|
|
||||||
res.add(item)
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_attester_slashings") do (
|
|
||||||
slashing: AttesterSlashing) -> bool:
|
|
||||||
let res = node.sendAttesterSlashing(slashing)
|
|
||||||
if not res.isOk():
|
|
||||||
raise (ref CatchableError)(msg: $res.error())
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_proposer_slashings") do (
|
|
||||||
) -> seq[ProposerSlashing]:
|
|
||||||
var res: seq[ProposerSlashing]
|
|
||||||
if isNil(node.exitPool):
|
|
||||||
return res
|
|
||||||
let length = len(node.exitPool.proposer_slashings)
|
|
||||||
res = newSeqOfCap[ProposerSlashing](length)
|
|
||||||
for item in node.exitPool.proposer_slashings.items():
|
|
||||||
res.add(item)
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_proposer_slashings") do (
|
|
||||||
slashing: ProposerSlashing) -> bool:
|
|
||||||
let res = node.sendProposerSlashing(slashing)
|
|
||||||
if not res.isOk():
|
|
||||||
raise (ref CatchableError)(msg: $res.error())
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_beacon_pool_voluntary_exits") do (
|
|
||||||
) -> seq[SignedVoluntaryExit]:
|
|
||||||
var res: seq[SignedVoluntaryExit]
|
|
||||||
if isNil(node.exitPool):
|
|
||||||
return res
|
|
||||||
let length = len(node.exitPool.voluntary_exits)
|
|
||||||
res = newSeqOfCap[SignedVoluntaryExit](length)
|
|
||||||
for item in node.exitPool.voluntary_exits.items():
|
|
||||||
res.add(item)
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_beacon_pool_voluntary_exits") do (
|
|
||||||
exit: SignedVoluntaryExit) -> bool:
|
|
||||||
let res = node.sendVoluntaryExit(exit)
|
|
||||||
if not res.isOk():
|
|
||||||
raise (ref CatchableError)(msg: $res.error())
|
|
||||||
return true
|
|
|
@ -1,109 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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/[byteutils],
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
chronicles,
|
|
||||||
../beacon_node,
|
|
||||||
../eth1/eth1_monitor,
|
|
||||||
../spec/forks
|
|
||||||
|
|
||||||
logScope: topics = "configapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer = RpcHttpServer
|
|
||||||
|
|
||||||
proc installConfigApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.rpc("get_v1_config_fork_schedule") do () -> seq[Fork]:
|
|
||||||
return @[getStateField(node.dag.headState, fork)]
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_config_spec") do () -> JsonNode:
|
|
||||||
return %*{
|
|
||||||
# Note: This is intentionally only returning v1.0 config values.
|
|
||||||
# Please use the REST API /eth/v1/config/spec to retrieve the full config.
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/configs/mainnet/phase0.yaml
|
|
||||||
"MAX_COMMITTEES_PER_SLOT": $MAX_COMMITTEES_PER_SLOT,
|
|
||||||
"TARGET_COMMITTEE_SIZE": $TARGET_COMMITTEE_SIZE,
|
|
||||||
"MAX_VALIDATORS_PER_COMMITTEE": $MAX_VALIDATORS_PER_COMMITTEE,
|
|
||||||
"MIN_PER_EPOCH_CHURN_LIMIT": $node.dag.cfg.MIN_PER_EPOCH_CHURN_LIMIT,
|
|
||||||
"CHURN_LIMIT_QUOTIENT": $node.dag.cfg.CHURN_LIMIT_QUOTIENT,
|
|
||||||
"SHUFFLE_ROUND_COUNT": $SHUFFLE_ROUND_COUNT,
|
|
||||||
"MIN_GENESIS_ACTIVE_VALIDATOR_COUNT":
|
|
||||||
$node.dag.cfg.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT,
|
|
||||||
"MIN_GENESIS_TIME": $node.dag.cfg.MIN_GENESIS_TIME,
|
|
||||||
"HYSTERESIS_QUOTIENT": $HYSTERESIS_QUOTIENT,
|
|
||||||
"HYSTERESIS_DOWNWARD_MULTIPLIER": $HYSTERESIS_DOWNWARD_MULTIPLIER,
|
|
||||||
"HYSTERESIS_UPWARD_MULTIPLIER": $HYSTERESIS_UPWARD_MULTIPLIER,
|
|
||||||
"SAFE_SLOTS_TO_UPDATE_JUSTIFIED": $SAFE_SLOTS_TO_UPDATE_JUSTIFIED,
|
|
||||||
"ETH1_FOLLOW_DISTANCE": $node.dag.cfg.ETH1_FOLLOW_DISTANCE,
|
|
||||||
"TARGET_AGGREGATORS_PER_COMMITTEE": $TARGET_AGGREGATORS_PER_COMMITTEE,
|
|
||||||
"RANDOM_SUBNETS_PER_VALIDATOR": $RANDOM_SUBNETS_PER_VALIDATOR,
|
|
||||||
"EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION":
|
|
||||||
$EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION,
|
|
||||||
"SECONDS_PER_ETH1_BLOCK": $node.dag.cfg.SECONDS_PER_ETH1_BLOCK,
|
|
||||||
"DEPOSIT_CHAIN_ID": $node.dag.cfg.DEPOSIT_CHAIN_ID,
|
|
||||||
"DEPOSIT_NETWORK_ID": $node.dag.cfg.DEPOSIT_NETWORK_ID,
|
|
||||||
"DEPOSIT_CONTRACT_ADDRESS": $node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS,
|
|
||||||
"MIN_DEPOSIT_AMOUNT": $MIN_DEPOSIT_AMOUNT,
|
|
||||||
"MAX_EFFECTIVE_BALANCE": $MAX_EFFECTIVE_BALANCE,
|
|
||||||
"EJECTION_BALANCE": $node.dag.cfg.EJECTION_BALANCE,
|
|
||||||
"EFFECTIVE_BALANCE_INCREMENT": $EFFECTIVE_BALANCE_INCREMENT,
|
|
||||||
"GENESIS_FORK_VERSION":
|
|
||||||
"0x" & $node.dag.cfg.GENESIS_FORK_VERSION,
|
|
||||||
"BLS_WITHDRAWAL_PREFIX": to0xHex([BLS_WITHDRAWAL_PREFIX]),
|
|
||||||
"GENESIS_DELAY": $node.dag.cfg.GENESIS_DELAY,
|
|
||||||
"SECONDS_PER_SLOT": $SECONDS_PER_SLOT,
|
|
||||||
"MIN_ATTESTATION_INCLUSION_DELAY": $MIN_ATTESTATION_INCLUSION_DELAY,
|
|
||||||
"SLOTS_PER_EPOCH": $SLOTS_PER_EPOCH,
|
|
||||||
"MIN_SEED_LOOKAHEAD": $MIN_SEED_LOOKAHEAD,
|
|
||||||
"MAX_SEED_LOOKAHEAD": $MAX_SEED_LOOKAHEAD,
|
|
||||||
"EPOCHS_PER_ETH1_VOTING_PERIOD": $EPOCHS_PER_ETH1_VOTING_PERIOD,
|
|
||||||
"SLOTS_PER_HISTORICAL_ROOT": $SLOTS_PER_HISTORICAL_ROOT,
|
|
||||||
"MIN_VALIDATOR_WITHDRAWABILITY_DELAY":
|
|
||||||
$node.dag.cfg.MIN_VALIDATOR_WITHDRAWABILITY_DELAY,
|
|
||||||
"SHARD_COMMITTEE_PERIOD": $node.dag.cfg.SHARD_COMMITTEE_PERIOD,
|
|
||||||
"MIN_EPOCHS_TO_INACTIVITY_PENALTY": $MIN_EPOCHS_TO_INACTIVITY_PENALTY,
|
|
||||||
"EPOCHS_PER_HISTORICAL_VECTOR": $EPOCHS_PER_HISTORICAL_VECTOR,
|
|
||||||
"EPOCHS_PER_SLASHINGS_VECTOR": $EPOCHS_PER_SLASHINGS_VECTOR,
|
|
||||||
"HISTORICAL_ROOTS_LIMIT": $HISTORICAL_ROOTS_LIMIT,
|
|
||||||
"VALIDATOR_REGISTRY_LIMIT": $VALIDATOR_REGISTRY_LIMIT,
|
|
||||||
"BASE_REWARD_FACTOR": $BASE_REWARD_FACTOR,
|
|
||||||
"WHISTLEBLOWER_REWARD_QUOTIENT": $WHISTLEBLOWER_REWARD_QUOTIENT,
|
|
||||||
"PROPOSER_REWARD_QUOTIENT": $PROPOSER_REWARD_QUOTIENT,
|
|
||||||
"INACTIVITY_PENALTY_QUOTIENT": $INACTIVITY_PENALTY_QUOTIENT,
|
|
||||||
"MIN_SLASHING_PENALTY_QUOTIENT": $MIN_SLASHING_PENALTY_QUOTIENT,
|
|
||||||
"PROPORTIONAL_SLASHING_MULTIPLIER": $PROPORTIONAL_SLASHING_MULTIPLIER,
|
|
||||||
"MAX_PROPOSER_SLASHINGS": $MAX_PROPOSER_SLASHINGS,
|
|
||||||
"MAX_ATTESTER_SLASHINGS": $MAX_ATTESTER_SLASHINGS,
|
|
||||||
"MAX_ATTESTATIONS": $MAX_ATTESTATIONS,
|
|
||||||
"MAX_DEPOSITS": $MAX_DEPOSITS,
|
|
||||||
"MAX_VOLUNTARY_EXITS": $MAX_VOLUNTARY_EXITS,
|
|
||||||
"DOMAIN_BEACON_PROPOSER":
|
|
||||||
to0xHex(DOMAIN_BEACON_PROPOSER.data),
|
|
||||||
"DOMAIN_BEACON_ATTESTER":
|
|
||||||
to0xHex(DOMAIN_BEACON_ATTESTER.data),
|
|
||||||
"DOMAIN_RANDAO":
|
|
||||||
to0xHex(DOMAIN_RANDAO.data),
|
|
||||||
"DOMAIN_DEPOSIT":
|
|
||||||
to0xHex(DOMAIN_DEPOSIT.data),
|
|
||||||
"DOMAIN_VOLUNTARY_EXIT":
|
|
||||||
to0xHex(DOMAIN_VOLUNTARY_EXIT.data),
|
|
||||||
"DOMAIN_SELECTION_PROOF":
|
|
||||||
to0xHex(DOMAIN_SELECTION_PROOF.data),
|
|
||||||
"DOMAIN_AGGREGATE_AND_PROOF":
|
|
||||||
to0xHex(DOMAIN_AGGREGATE_AND_PROOF.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_config_deposit_contract") do () -> JsonNode:
|
|
||||||
return %*{
|
|
||||||
"chain_id": $node.dag.cfg.DEPOSIT_CHAIN_ID,
|
|
||||||
"address": node.dag.cfg.DEPOSIT_CONTRACT_ADDRESS
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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/sequtils,
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
chronicles,
|
|
||||||
../version, ../beacon_node,
|
|
||||||
../networking/[eth2_network, peer_pool],
|
|
||||||
../spec/datatypes/phase0,
|
|
||||||
./rpc_utils
|
|
||||||
|
|
||||||
logScope: topics = "debugapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer = RpcHttpServer
|
|
||||||
|
|
||||||
proc installDebugApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.rpc("get_v1_debug_beacon_states_stateId") do (
|
|
||||||
stateId: string) -> phase0.BeaconState:
|
|
||||||
withStateForStateId(stateId):
|
|
||||||
if state.kind == BeaconStateFork.Phase0:
|
|
||||||
return state.phase0Data.data
|
|
||||||
else:
|
|
||||||
raiseNoAltairSupport()
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_debug_beacon_heads") do () -> seq[tuple[root: Eth2Digest, slot: Slot]]:
|
|
||||||
return node.dag.heads.mapIt((it.root, it.slot))
|
|
|
@ -1,26 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
chronicles,
|
|
||||||
../beacon_node
|
|
||||||
|
|
||||||
logScope: topics = "eventapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer = RpcHttpServer
|
|
||||||
|
|
||||||
template unimplemented() =
|
|
||||||
raise (ref CatchableError)(msg: "Unimplemented")
|
|
||||||
|
|
||||||
proc installEventApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.rpc("get_v1_events") do () -> JsonNode:
|
|
||||||
unimplemented()
|
|
|
@ -1,230 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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/[deques, sequtils, sets],
|
|
||||||
chronos,
|
|
||||||
stew/byteutils,
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
libp2p/protocols/pubsub/pubsubpeer,
|
|
||||||
|
|
||||||
".."/[
|
|
||||||
beacon_node, nimbus_binary_common, networking/eth2_network,
|
|
||||||
eth1/eth1_monitor, validators/validator_duties],
|
|
||||||
../spec/datatypes/base,
|
|
||||||
../spec/[forks],
|
|
||||||
./rpc_utils
|
|
||||||
|
|
||||||
when defined(chronosFutureTracking):
|
|
||||||
import stew/base10
|
|
||||||
|
|
||||||
logScope: topics = "nimbusapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer = RpcHttpServer
|
|
||||||
|
|
||||||
FutureInfo* = object
|
|
||||||
id*: string
|
|
||||||
child_id*: string
|
|
||||||
procname*: string
|
|
||||||
filename*: string
|
|
||||||
line*: int
|
|
||||||
state*: string
|
|
||||||
|
|
||||||
proc installNimbusApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
## Install non-standard api handlers - some of these are used by 3rd-parties
|
|
||||||
## such as eth2stats, pending a full REST api
|
|
||||||
rpcServer.rpc("getBeaconHead") do () -> Slot:
|
|
||||||
return node.dag.head.slot
|
|
||||||
|
|
||||||
rpcServer.rpc("getChainHead") do () -> JsonNode:
|
|
||||||
let
|
|
||||||
head = node.dag.head
|
|
||||||
finalized = getStateField(node.dag.headState, finalized_checkpoint)
|
|
||||||
justified =
|
|
||||||
getStateField(node.dag.headState, current_justified_checkpoint)
|
|
||||||
return %* {
|
|
||||||
"head_slot": head.slot,
|
|
||||||
"head_block_root": head.root.data.toHex(),
|
|
||||||
"finalized_slot": finalized.epoch * SLOTS_PER_EPOCH,
|
|
||||||
"finalized_block_root": finalized.root.data.toHex(),
|
|
||||||
"justified_slot": justified.epoch * SLOTS_PER_EPOCH,
|
|
||||||
"justified_block_root": justified.root.data.toHex(),
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcServer.rpc("getSyncing") do () -> bool:
|
|
||||||
return node.syncManager.inProgress
|
|
||||||
|
|
||||||
rpcServer.rpc("getNetworkPeerId") do () -> string:
|
|
||||||
return $node.network.peerId()
|
|
||||||
|
|
||||||
rpcServer.rpc("getNetworkPeers") do () -> seq[string]:
|
|
||||||
for peerId, peer in node.network.peerPool:
|
|
||||||
result.add $peerId
|
|
||||||
|
|
||||||
rpcServer.rpc("getNodeVersion") do () -> string:
|
|
||||||
return "Nimbus/" & fullVersionStr
|
|
||||||
|
|
||||||
rpcServer.rpc("peers") do () -> JsonNode:
|
|
||||||
var res = newJObject()
|
|
||||||
var peers = newJArray()
|
|
||||||
for id, peer in node.network.peerPool:
|
|
||||||
peers.add(
|
|
||||||
%(
|
|
||||||
id: shortLog(peer.peerId),
|
|
||||||
connectionState: $peer.connectionState,
|
|
||||||
score: peer.score,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
res.add("peers", peers)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("setLogLevel") do (level: string) -> bool:
|
|
||||||
{.gcsafe.}: # It's probably not, actually. Hopefully we don't log from threads...
|
|
||||||
updateLogLevel(level)
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("setGraffiti") do (graffiti: string) -> bool:
|
|
||||||
node.graffitiBytes = GraffitiBytes.init(graffiti)
|
|
||||||
return true
|
|
||||||
|
|
||||||
rpcServer.rpc("getEth1Chain") do () -> seq[Eth1Block]:
|
|
||||||
result = if node.eth1Monitor != nil:
|
|
||||||
mapIt(node.eth1Monitor.depositChainBlocks, it)
|
|
||||||
else:
|
|
||||||
@[]
|
|
||||||
|
|
||||||
rpcServer.rpc("getEth1ProposalData") do () -> BlockProposalEth1Data:
|
|
||||||
let
|
|
||||||
wallSlot = node.beaconClock.now.slotOrZero
|
|
||||||
head = node.doChecksAndGetCurrentHead(wallSlot)
|
|
||||||
|
|
||||||
let proposalState = assignClone(node.dag.headState)
|
|
||||||
node.dag.withUpdatedState(
|
|
||||||
proposalState[],
|
|
||||||
head.atSlot(wallSlot).toBlockSlotId().expect("not nil")):
|
|
||||||
return node.getBlockProposalEth1Data(state)
|
|
||||||
do:
|
|
||||||
raise (ref CatchableError)(msg: "Trying to access pruned state")
|
|
||||||
|
|
||||||
rpcServer.rpc("debug_getChronosFutures") do () -> seq[FutureInfo]:
|
|
||||||
when defined(chronosFutureTracking):
|
|
||||||
var res: seq[FutureInfo]
|
|
||||||
|
|
||||||
for item in pendingFutures():
|
|
||||||
let loc = item.location[LocCreateIndex][]
|
|
||||||
let futureId = Base10.toString(item.id)
|
|
||||||
let childId =
|
|
||||||
if isNil(item.child): ""
|
|
||||||
else: Base10.toString(item.child.id)
|
|
||||||
res.add FutureInfo(
|
|
||||||
id: futureId,
|
|
||||||
child_id: childId,
|
|
||||||
procname: $loc.procedure,
|
|
||||||
filename: $loc.file,
|
|
||||||
line: loc.line,
|
|
||||||
state: $item.state
|
|
||||||
)
|
|
||||||
|
|
||||||
return res
|
|
||||||
else:
|
|
||||||
raise (ref CatchableError)(
|
|
||||||
msg: "Compile with '-d:chronosFutureTracking' to enable this request")
|
|
||||||
|
|
||||||
rpcServer.rpc("debug_getGossipSubPeers") do () -> JsonNode:
|
|
||||||
var res = newJObject()
|
|
||||||
var gossipsub = newJObject()
|
|
||||||
|
|
||||||
proc toNode(v: PubSubPeer, backoff: Moment): JsonNode =
|
|
||||||
%(
|
|
||||||
peerId: $v.peerId,
|
|
||||||
score: v.score,
|
|
||||||
iWantBudget: v.iWantBudget,
|
|
||||||
iHaveBudget: v.iHaveBudget,
|
|
||||||
outbound: v.outbound,
|
|
||||||
appScore: v.appScore,
|
|
||||||
behaviourPenalty: v.behaviourPenalty,
|
|
||||||
sendConnAvail: v.sendConn != nil,
|
|
||||||
closed: v.sendConn != nil and v.sendConn.closed,
|
|
||||||
atEof: v.sendConn != nil and v.sendConn.atEof,
|
|
||||||
address: if v.address.isSome():
|
|
||||||
$v.address.get()
|
|
||||||
else:
|
|
||||||
"<no address>",
|
|
||||||
backoff: $(backoff - Moment.now()),
|
|
||||||
agent: when defined(libp2p_agents_metrics):
|
|
||||||
v.shortAgent
|
|
||||||
else:
|
|
||||||
"unknown",
|
|
||||||
)
|
|
||||||
|
|
||||||
for topic, v in node.network.pubsub.gossipsub:
|
|
||||||
var peers = newJArray()
|
|
||||||
let backoff = node.network.pubsub.backingOff.getOrDefault(topic)
|
|
||||||
for peer in v:
|
|
||||||
peers.add(peer.toNode(backoff.getOrDefault(peer.peerId)))
|
|
||||||
|
|
||||||
gossipsub.add(topic, peers)
|
|
||||||
|
|
||||||
res.add("gossipsub", gossipsub)
|
|
||||||
|
|
||||||
var mesh = newJObject()
|
|
||||||
for topic, v in node.network.pubsub.mesh:
|
|
||||||
var peers = newJArray()
|
|
||||||
let backoff = node.network.pubsub.backingOff.getOrDefault(topic)
|
|
||||||
for peer in v:
|
|
||||||
peers.add(peer.toNode(backoff.getOrDefault(peer.peerId)))
|
|
||||||
|
|
||||||
mesh.add(topic, peers)
|
|
||||||
|
|
||||||
res.add("mesh", mesh)
|
|
||||||
|
|
||||||
var coloc = newJArray()
|
|
||||||
for k, v in node.network.pubsub.peersInIP:
|
|
||||||
var a = newJObject()
|
|
||||||
var peers = newJArray()
|
|
||||||
for p in v:
|
|
||||||
peers.add(%($p))
|
|
||||||
a.add($k, peers)
|
|
||||||
coloc.add(a)
|
|
||||||
|
|
||||||
res.add("colocationPeers", coloc)
|
|
||||||
|
|
||||||
var stats = newJArray()
|
|
||||||
for peerId, pstats in node.network.pubsub.peerStats:
|
|
||||||
let
|
|
||||||
peer = node.network.pubsub.peers.getOrDefault(peerId)
|
|
||||||
null = isNil(peer)
|
|
||||||
connected = if null:
|
|
||||||
false
|
|
||||||
else :
|
|
||||||
peer.connected()
|
|
||||||
|
|
||||||
stats.add(%(
|
|
||||||
peerId: $peerId,
|
|
||||||
null: null,
|
|
||||||
connected: connected,
|
|
||||||
expire: $(pstats.expire - Moment.now()),
|
|
||||||
score: pstats.score
|
|
||||||
))
|
|
||||||
|
|
||||||
res.add("peerStats", stats)
|
|
||||||
|
|
||||||
var peers = newJArray()
|
|
||||||
for peerId, peer in node.network.pubsub.peers:
|
|
||||||
peers.add(%(
|
|
||||||
connected: peer.connected,
|
|
||||||
peerId: $peerId
|
|
||||||
))
|
|
||||||
|
|
||||||
res.add("allPeers", peers)
|
|
||||||
|
|
||||||
return res
|
|
|
@ -1,256 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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, sequtils],
|
|
||||||
stew/byteutils,
|
|
||||||
chronicles,
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
eth/p2p/discoveryv5/enr,
|
|
||||||
libp2p/[multiaddress, multicodec, peerstore],
|
|
||||||
../beacon_node, ../version,
|
|
||||||
../networking/[eth2_network, peer_pool],
|
|
||||||
../sync/sync_manager,
|
|
||||||
../spec/datatypes/base,
|
|
||||||
./rpc_utils
|
|
||||||
|
|
||||||
logScope: topics = "nodeapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer = RpcHttpServer
|
|
||||||
|
|
||||||
proc validateState(state: Option[seq[string]]): Option[set[ConnectionState]] =
|
|
||||||
var res: set[ConnectionState]
|
|
||||||
if state.isSome():
|
|
||||||
let states = state.get()
|
|
||||||
for item in states:
|
|
||||||
case item
|
|
||||||
of "disconnected":
|
|
||||||
if ConnectionState.Disconnected notin res:
|
|
||||||
res.incl(ConnectionState.Disconnected)
|
|
||||||
else:
|
|
||||||
# `state` values should be unique
|
|
||||||
return none(set[ConnectionState])
|
|
||||||
of "connecting":
|
|
||||||
if ConnectionState.Disconnected notin res:
|
|
||||||
res.incl(ConnectionState.Connecting)
|
|
||||||
else:
|
|
||||||
# `state` values should be unique
|
|
||||||
return none(set[ConnectionState])
|
|
||||||
of "connected":
|
|
||||||
if ConnectionState.Connected notin res:
|
|
||||||
res.incl(ConnectionState.Connected)
|
|
||||||
else:
|
|
||||||
# `state` values should be unique
|
|
||||||
return none(set[ConnectionState])
|
|
||||||
of "disconnecting":
|
|
||||||
if ConnectionState.Disconnecting notin res:
|
|
||||||
res.incl(ConnectionState.Disconnecting)
|
|
||||||
else:
|
|
||||||
# `state` values should be unique
|
|
||||||
return none(set[ConnectionState])
|
|
||||||
else:
|
|
||||||
# Found incorrect `state` string value
|
|
||||||
return none(set[ConnectionState])
|
|
||||||
|
|
||||||
if res == {}:
|
|
||||||
res = {ConnectionState.Connecting, ConnectionState.Connected,
|
|
||||||
ConnectionState.Disconnecting, ConnectionState.Disconnected}
|
|
||||||
some(res)
|
|
||||||
|
|
||||||
proc validateDirection(direction: Option[seq[string]]): Option[set[PeerType]] =
|
|
||||||
var res: set[PeerType]
|
|
||||||
if direction.isSome():
|
|
||||||
let directions = direction.get()
|
|
||||||
for item in directions:
|
|
||||||
case item
|
|
||||||
of "inbound":
|
|
||||||
if PeerType.Incoming notin res:
|
|
||||||
res.incl(PeerType.Incoming)
|
|
||||||
else:
|
|
||||||
# `direction` values should be unique
|
|
||||||
return none(set[PeerType])
|
|
||||||
of "outbound":
|
|
||||||
if PeerType.Outgoing notin res:
|
|
||||||
res.incl(PeerType.Outgoing)
|
|
||||||
else:
|
|
||||||
# `direction` values should be unique
|
|
||||||
return none(set[PeerType])
|
|
||||||
else:
|
|
||||||
# Found incorrect `direction` string value
|
|
||||||
return none(set[PeerType])
|
|
||||||
|
|
||||||
if res == {}:
|
|
||||||
res = {PeerType.Incoming, PeerType.Outgoing}
|
|
||||||
some(res)
|
|
||||||
|
|
||||||
proc toString(state: ConnectionState): string =
|
|
||||||
case state
|
|
||||||
of ConnectionState.Disconnected:
|
|
||||||
"disconnected"
|
|
||||||
of ConnectionState.Connecting:
|
|
||||||
"connecting"
|
|
||||||
of ConnectionState.Connected:
|
|
||||||
"connected"
|
|
||||||
of ConnectionState.Disconnecting:
|
|
||||||
"disconnecting"
|
|
||||||
else:
|
|
||||||
""
|
|
||||||
|
|
||||||
proc toString(direction: PeerType): string =
|
|
||||||
case direction:
|
|
||||||
of PeerType.Incoming:
|
|
||||||
"inbound"
|
|
||||||
of PeerType.Outgoing:
|
|
||||||
"outbound"
|
|
||||||
|
|
||||||
proc getLastSeenAddress(node: BeaconNode, id: PeerId): string =
|
|
||||||
# TODO (cheatfate): We need to provide filter here, which will be able to
|
|
||||||
# filter such multiaddresses like `/ip4/0.0.0.0` or local addresses or
|
|
||||||
# addresses with peer ids.
|
|
||||||
let addrs = node.network.switch.peerStore.addressBook.get(id).toSeq()
|
|
||||||
if len(addrs) > 0:
|
|
||||||
$addrs[len(addrs) - 1]
|
|
||||||
else:
|
|
||||||
""
|
|
||||||
|
|
||||||
proc getDiscoveryAddresses(node: BeaconNode): Option[seq[string]] =
|
|
||||||
let restr = node.network.enrRecord().toTypedRecord()
|
|
||||||
if restr.isErr():
|
|
||||||
return none[seq[string]]()
|
|
||||||
let respa = restr.get().toPeerAddr(udpProtocol)
|
|
||||||
if respa.isErr():
|
|
||||||
return none[seq[string]]()
|
|
||||||
let pa = respa.get()
|
|
||||||
let mpa = MultiAddress.init(multiCodec("p2p"), pa.peerId)
|
|
||||||
if mpa.isErr():
|
|
||||||
return none[seq[string]]()
|
|
||||||
var addresses = newSeqOfCap[string](len(pa.addrs))
|
|
||||||
for item in pa.addrs:
|
|
||||||
let resa = concat(item, mpa.get())
|
|
||||||
if resa.isOk():
|
|
||||||
addresses.add($(resa.get()))
|
|
||||||
return some(addresses)
|
|
||||||
|
|
||||||
proc getP2PAddresses(node: BeaconNode): Option[seq[string]] =
|
|
||||||
let pinfo = node.network.switch.peerInfo
|
|
||||||
let mpa = MultiAddress.init(multiCodec("p2p"), pinfo.peerId)
|
|
||||||
if mpa.isErr():
|
|
||||||
return none[seq[string]]()
|
|
||||||
var addresses = newSeqOfCap[string](len(pinfo.addrs))
|
|
||||||
for item in pinfo.addrs:
|
|
||||||
let resa = concat(item, mpa.get())
|
|
||||||
if resa.isOk():
|
|
||||||
addresses.add($(resa.get()))
|
|
||||||
return some(addresses)
|
|
||||||
|
|
||||||
proc installNodeApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.rpc("get_v1_node_identity") do () -> RpcNodeIdentity:
|
|
||||||
let discoveryAddresses =
|
|
||||||
block:
|
|
||||||
let res = node.getDiscoveryAddresses()
|
|
||||||
if res.isSome():
|
|
||||||
res.get()
|
|
||||||
else:
|
|
||||||
newSeq[string](0)
|
|
||||||
|
|
||||||
let p2pAddresses =
|
|
||||||
block:
|
|
||||||
let res = node.getP2PAddresses()
|
|
||||||
if res.isSome():
|
|
||||||
res.get()
|
|
||||||
else:
|
|
||||||
newSeq[string]()
|
|
||||||
|
|
||||||
return (
|
|
||||||
peer_id: $node.network.peerId(),
|
|
||||||
enr: node.network.enrRecord().toURI(),
|
|
||||||
p2p_addresses: p2pAddresses,
|
|
||||||
discovery_addresses: discoveryAddresses,
|
|
||||||
metadata: (node.network.metadata.seq_number,
|
|
||||||
to0xHex(node.network.metadata.attnets.bytes))
|
|
||||||
)
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_node_peers") do (state: Option[seq[string]],
|
|
||||||
direction: Option[seq[string]]) -> seq[RpcNodePeer]:
|
|
||||||
var res = newSeq[RpcNodePeer]()
|
|
||||||
let rstates = validateState(state)
|
|
||||||
if rstates.isNone():
|
|
||||||
raise newException(CatchableError, "Incorrect state parameter")
|
|
||||||
let rdirs = validateDirection(direction)
|
|
||||||
if rdirs.isNone():
|
|
||||||
raise newException(CatchableError, "Incorrect direction parameter")
|
|
||||||
let states = rstates.get()
|
|
||||||
let dirs = rdirs.get()
|
|
||||||
for peer in node.network.peers.values():
|
|
||||||
if (peer.connectionState in states) and (peer.direction in dirs):
|
|
||||||
let resPeer = (
|
|
||||||
peer_id: $peer.peerId,
|
|
||||||
enr: if peer.enr.isSome(): peer.enr.get().toURI() else: "",
|
|
||||||
last_seen_p2p_address: getLastSeenAddress(node, peer.peerId),
|
|
||||||
state: peer.connectionState.toString(),
|
|
||||||
direction: peer.direction.toString(),
|
|
||||||
agent: node.network.switch.peerStore.agentBook.get(peer.peerId), # Fields `agent` and `proto` are not
|
|
||||||
proto: node.network.switch.peerStore.protoVersionBook.get(peer.peerId) # part of specification
|
|
||||||
)
|
|
||||||
res.add(resPeer)
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_node_peer_count") do () -> RpcNodePeerCount:
|
|
||||||
var res: RpcNodePeerCount
|
|
||||||
for item in node.network.peers.values():
|
|
||||||
case item.connectionState
|
|
||||||
of ConnectionState.Connecting:
|
|
||||||
inc(res.connecting)
|
|
||||||
of ConnectionState.Connected:
|
|
||||||
inc(res.connected)
|
|
||||||
of ConnectionState.Disconnecting:
|
|
||||||
inc(res.disconnecting)
|
|
||||||
of ConnectionState.Disconnected:
|
|
||||||
inc(res.disconnected)
|
|
||||||
of ConnectionState.None:
|
|
||||||
discard
|
|
||||||
return res
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_node_peers_peerId") do (
|
|
||||||
peer_id: string) -> RpcNodePeer:
|
|
||||||
let pres = PeerId.init(peer_id)
|
|
||||||
if pres.isErr():
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"The peer ID supplied could not be parsed")
|
|
||||||
let pid = pres.get()
|
|
||||||
let peer = node.network.peers.getOrDefault(pid)
|
|
||||||
if isNil(peer):
|
|
||||||
raise newException(CatchableError, "Peer not found")
|
|
||||||
|
|
||||||
return (
|
|
||||||
peer_id: $peer.peerId,
|
|
||||||
enr: if peer.enr.isSome(): peer.enr.get().toURI() else: "",
|
|
||||||
last_seen_p2p_address: getLastSeenAddress(node, peer.peerId),
|
|
||||||
state: peer.connectionState.toString(),
|
|
||||||
direction: peer.direction.toString(),
|
|
||||||
agent: node.network.switch.peerStore.agentBook.get(peer.peerId), # Fields `agent` and `proto` are not
|
|
||||||
proto: node.network.switch.peerStore.protoVersionBook.get(peer.peerId) # part of specification
|
|
||||||
)
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_node_version") do () -> JsonNode:
|
|
||||||
return %*{"version": "Nimbus/" & fullVersionStr}
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_node_syncing") do () -> RpcSyncInfo:
|
|
||||||
return node.syncManager.getInfo()
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_node_health") do () -> JsonNode:
|
|
||||||
# TODO: There currently no way to situation when we node has issues, so
|
|
||||||
# its impossible to return HTTP ERROR 503 according to specification.
|
|
||||||
if node.syncManager.inProgress:
|
|
||||||
# We need to return HTTP ERROR 206 according to specification
|
|
||||||
return %*{"health": 206}
|
|
||||||
else:
|
|
||||||
return %*{"health": 200}
|
|
|
@ -1,111 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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/[strutils, parseutils],
|
|
||||||
stew/byteutils,
|
|
||||||
../beacon_node, ../validators/validator_duties,
|
|
||||||
../consensus_object_pools/[block_pools_types, blockchain_dag],
|
|
||||||
../spec/datatypes/base,
|
|
||||||
../spec/[forks, helpers],
|
|
||||||
../spec/eth2_apis/[rpc_types, eth2_json_rpc_serialization]
|
|
||||||
|
|
||||||
export forks, rpc_types, eth2_json_rpc_serialization, blockchain_dag
|
|
||||||
|
|
||||||
template raiseNoAltairSupport*() =
|
|
||||||
raise (ref ValueError)(msg:
|
|
||||||
"The JSON-RPC interface does not support certain Altair operations due to changes in block structure - see https://nimbus.guide/rest-api.html for full altair support")
|
|
||||||
|
|
||||||
template withStateForStateId*(stateId: string, body: untyped): untyped =
|
|
||||||
let
|
|
||||||
bsi = node.stateIdToBlockSlotId(stateId)
|
|
||||||
|
|
||||||
template isState(state: ForkedHashedBeaconState): bool =
|
|
||||||
state.matches_block_slot(bsi.bid.root, bsi.slot)
|
|
||||||
|
|
||||||
if isState(node.dag.headState):
|
|
||||||
withStateVars(node.dag.headState):
|
|
||||||
var cache {.inject, used.}: StateCache
|
|
||||||
body
|
|
||||||
else:
|
|
||||||
let rpcState = assignClone(node.dag.headState)
|
|
||||||
node.dag.withUpdatedState(rpcState[], bsi) do:
|
|
||||||
body
|
|
||||||
do:
|
|
||||||
raise (ref CatchableError)(msg: "Trying to access pruned state")
|
|
||||||
|
|
||||||
proc parseRoot*(str: string): Eth2Digest {.raises: [Defect, ValueError].} =
|
|
||||||
Eth2Digest(data: hexToByteArray[32](str))
|
|
||||||
|
|
||||||
func checkEpochToSlotOverflow*(epoch: Epoch) {.raises: [Defect, ValueError].} =
|
|
||||||
const maxEpoch = epoch(FAR_FUTURE_SLOT)
|
|
||||||
if epoch >= maxEpoch:
|
|
||||||
raise newException(
|
|
||||||
ValueError, "Requesting epoch for which slot would overflow")
|
|
||||||
|
|
||||||
proc doChecksAndGetCurrentHead*(node: BeaconNode, slot: Slot): BlockRef {.raises: [Defect, CatchableError].} =
|
|
||||||
result = node.dag.head
|
|
||||||
if not node.isSynced(result):
|
|
||||||
raise newException(CatchableError, "Cannot fulfill request until node is synced")
|
|
||||||
# TODO for now we limit the requests arbitrarily by up to 2 epochs into the future
|
|
||||||
if result.slot + uint64(2 * SLOTS_PER_EPOCH) < slot:
|
|
||||||
raise newException(CatchableError, "Requesting way ahead of the current head")
|
|
||||||
|
|
||||||
proc doChecksAndGetCurrentHead*(node: BeaconNode, epoch: Epoch): BlockRef {.raises: [Defect, CatchableError].} =
|
|
||||||
checkEpochToSlotOverflow(epoch)
|
|
||||||
node.doChecksAndGetCurrentHead(epoch.start_slot())
|
|
||||||
|
|
||||||
proc parseSlot(slot: string): Slot {.raises: [Defect, CatchableError].} =
|
|
||||||
if slot.len == 0:
|
|
||||||
raise newException(ValueError, "Empty slot number not allowed")
|
|
||||||
var parsed: BiggestUInt
|
|
||||||
if parseBiggestUInt(slot, parsed) != slot.len:
|
|
||||||
raise newException(ValueError, "Not a valid slot number")
|
|
||||||
Slot parsed
|
|
||||||
|
|
||||||
proc getBlockSlotIdFromString*(node: BeaconNode, slot: string): BlockSlotId {.raises: [Defect, CatchableError].} =
|
|
||||||
let parsed = parseSlot(slot)
|
|
||||||
discard node.doChecksAndGetCurrentHead(parsed)
|
|
||||||
node.dag.getBlockIdAtSlot(parsed).valueOr:
|
|
||||||
raise newException(ValueError, "Block not found")
|
|
||||||
|
|
||||||
proc getBlockIdFromString*(node: BeaconNode, slot: string): BlockId {.raises: [Defect, CatchableError].} =
|
|
||||||
let parsed = parseSlot(slot)
|
|
||||||
discard node.doChecksAndGetCurrentHead(parsed)
|
|
||||||
let bsid = node.dag.getBlockIdAtSlot(parsed)
|
|
||||||
if bsid.isSome and bsid.get.isProposed():
|
|
||||||
bsid.get().bid
|
|
||||||
else:
|
|
||||||
raise (ref ValueError)(msg: "Block not found")
|
|
||||||
|
|
||||||
proc stateIdToBlockSlotId*(node: BeaconNode, stateId: string): BlockSlotId {.raises: [Defect, CatchableError].} =
|
|
||||||
case stateId:
|
|
||||||
of "head":
|
|
||||||
node.dag.head.bid.atSlot()
|
|
||||||
of "genesis":
|
|
||||||
node.dag.genesis.atSlot()
|
|
||||||
of "finalized":
|
|
||||||
node.dag.finalizedHead.toBlockSlotId().expect("not nil")
|
|
||||||
of "justified":
|
|
||||||
node.dag.head.atEpochStart(
|
|
||||||
getStateField(
|
|
||||||
node.dag.headState, current_justified_checkpoint).epoch).
|
|
||||||
toBlockSlotId().valueOr:
|
|
||||||
raise (ref ValueError)(msg: "State not found")
|
|
||||||
else:
|
|
||||||
if stateId.startsWith("0x"):
|
|
||||||
let stateRoot = parseRoot(stateId)
|
|
||||||
if stateRoot == getStateRoot(node.dag.headState):
|
|
||||||
node.dag.head.bid.atSlot()
|
|
||||||
else:
|
|
||||||
# We don't have a state root -> BlockSlot mapping
|
|
||||||
raise (ref ValueError)(msg: "State not found")
|
|
||||||
|
|
||||||
else: # Parse as slot number
|
|
||||||
node.getBlockSlotIdFromString(stateId)
|
|
|
@ -1,170 +0,0 @@
|
||||||
# beacon_chain
|
|
||||||
# Copyright (c) 2018-2021 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
|
|
||||||
# Standard library
|
|
||||||
std/tables,
|
|
||||||
|
|
||||||
# Nimble packages
|
|
||||||
stew/objects,
|
|
||||||
json_rpc/servers/httpserver,
|
|
||||||
chronicles,
|
|
||||||
|
|
||||||
# Local modules
|
|
||||||
../spec/[forks, helpers, network, signatures],
|
|
||||||
../spec/datatypes/phase0,
|
|
||||||
../spec/eth2_apis/rpc_types,
|
|
||||||
../consensus_object_pools/[blockchain_dag, spec_cache, attestation_pool],
|
|
||||||
../beacon_node,
|
|
||||||
../validators/validator_duties,
|
|
||||||
../networking/eth2_network,
|
|
||||||
./rpc_utils
|
|
||||||
|
|
||||||
logScope: topics = "valapi"
|
|
||||||
|
|
||||||
type
|
|
||||||
RpcServer* = RpcHttpServer
|
|
||||||
|
|
||||||
proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
|
||||||
raises: [Defect, CatchableError].} =
|
|
||||||
rpcServer.rpc("get_v1_validator_block") do (
|
|
||||||
slot: Slot, graffiti: GraffitiBytes, randao_reveal: ValidatorSig) -> phase0.BeaconBlock:
|
|
||||||
debug "get_v1_validator_block", slot = slot
|
|
||||||
let head = node.doChecksAndGetCurrentHead(slot)
|
|
||||||
let proposer = node.dag.getProposer(head, slot)
|
|
||||||
if proposer.isNone():
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"could not retrieve block for slot: " & $slot)
|
|
||||||
let res = await makeBeaconBlockForHeadAndSlot(
|
|
||||||
node, randao_reveal, proposer.get(), graffiti, head, slot)
|
|
||||||
if res.isErr():
|
|
||||||
raise newException(CatchableError, res.error())
|
|
||||||
let blck = res.get()
|
|
||||||
case blck.kind
|
|
||||||
of BeaconBlockFork.Phase0:
|
|
||||||
return blck.phase0Data
|
|
||||||
else:
|
|
||||||
raiseNoAltairSupport()
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_validator_attestation_data") do (
|
|
||||||
slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
|
|
||||||
debug "get_v1_validator_attestation_data", slot = slot
|
|
||||||
let
|
|
||||||
head = node.doChecksAndGetCurrentHead(slot)
|
|
||||||
epochRef = block:
|
|
||||||
let tmp = node.dag.getEpochRef(head, slot.epoch, true)
|
|
||||||
if isErr(tmp):
|
|
||||||
raise (ref CatchableError)(msg: "Trying to access pruned state")
|
|
||||||
tmp.get()
|
|
||||||
|
|
||||||
return makeAttestationData(epochRef, head.atSlot(slot), committee_index)
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_validator_aggregate_attestation") do (
|
|
||||||
slot: Slot, attestation_data_root: Eth2Digest)-> Attestation:
|
|
||||||
debug "get_v1_validator_aggregate_attestation"
|
|
||||||
let res = node.attestationPool[].getAggregatedAttestation(slot, attestation_data_root)
|
|
||||||
if res.isSome:
|
|
||||||
return res.get
|
|
||||||
raise newException(CatchableError, "Could not retrieve an aggregated attestation")
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_validator_aggregate_and_proofs") do (
|
|
||||||
payload: SignedAggregateAndProof) -> bool:
|
|
||||||
debug "post_v1_validator_aggregate_and_proofs"
|
|
||||||
return (await node.sendAggregateAndProof(payload)).isOk()
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_validator_duties_attester") do (
|
|
||||||
epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[RpcAttesterDuties]:
|
|
||||||
debug "get_v1_validator_duties_attester", epoch = epoch
|
|
||||||
let
|
|
||||||
head = node.doChecksAndGetCurrentHead(epoch)
|
|
||||||
epochRef = block:
|
|
||||||
let tmp = node.dag.getEpochRef(head, epoch, true)
|
|
||||||
if isErr(tmp):
|
|
||||||
raise (ref CatchableError)(msg: "Trying to access pruned state")
|
|
||||||
tmp.get()
|
|
||||||
|
|
||||||
let
|
|
||||||
committees_per_slot = get_committee_count_per_slot(epochRef)
|
|
||||||
for slot in epoch.slots():
|
|
||||||
for committee_index in get_committee_indices(committees_per_slot):
|
|
||||||
let committee = get_beacon_committee(epochRef, slot, committee_index)
|
|
||||||
for index_in_committee, validator_index in committee:
|
|
||||||
let curr_val_pubkey = epochRef.validatorKey(validator_index)
|
|
||||||
if curr_val_pubkey.isSome():
|
|
||||||
if public_keys.findIt(it == curr_val_pubkey.get().toPubKey()) != -1:
|
|
||||||
result.add((public_key: curr_val_pubkey.get().toPubKey(),
|
|
||||||
validator_index: validator_index,
|
|
||||||
committee_index: committee_index,
|
|
||||||
committee_length: committee.lenu64,
|
|
||||||
validator_committee_index: index_in_committee.uint64,
|
|
||||||
slot: slot))
|
|
||||||
|
|
||||||
rpcServer.rpc("get_v1_validator_duties_proposer") do (
|
|
||||||
epoch: Epoch) -> seq[RpcValidatorDuties]:
|
|
||||||
debug "get_v1_validator_duties_proposer", epoch = epoch
|
|
||||||
let
|
|
||||||
head = node.doChecksAndGetCurrentHead(epoch)
|
|
||||||
epochRef = block:
|
|
||||||
let tmp = node.dag.getEpochRef(head, epoch, true)
|
|
||||||
if isErr(tmp):
|
|
||||||
raise (ref CatchableError)(msg: "Trying to access pruned state")
|
|
||||||
tmp.get()
|
|
||||||
|
|
||||||
for i, bp in epochRef.beacon_proposers:
|
|
||||||
if bp.isSome():
|
|
||||||
result.add((public_key: epochRef.validatorKey(bp.get).get().toPubKey,
|
|
||||||
validator_index: bp.get(),
|
|
||||||
slot: epoch.start_slot() + i))
|
|
||||||
|
|
||||||
rpcServer.rpc("post_v1_validator_beacon_committee_subscriptions") do (
|
|
||||||
committee_index: CommitteeIndex, slot: Slot, aggregator: bool,
|
|
||||||
validator_pubkey: ValidatorPubKey, slot_signature: ValidatorSig) -> bool:
|
|
||||||
debug "post_v1_validator_beacon_committee_subscriptions",
|
|
||||||
committee_index, slot
|
|
||||||
if committee_index.uint64 >= MAX_COMMITTEES_PER_SLOT.uint64:
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"Invalid committee index")
|
|
||||||
|
|
||||||
if node.syncManager.inProgress:
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"Beacon node is currently syncing and not serving request on that endpoint")
|
|
||||||
|
|
||||||
let wallSlot = node.beaconClock.now.slotOrZero
|
|
||||||
if wallSlot > slot + 1:
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"Past slot requested")
|
|
||||||
|
|
||||||
let epoch = slot.epoch
|
|
||||||
if epoch >= wallSlot.epoch and epoch - wallSlot.epoch > 1:
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"Slot requested not in current or next wall-slot epoch")
|
|
||||||
|
|
||||||
if not verify_slot_signature(
|
|
||||||
getStateField(node.dag.headState, fork),
|
|
||||||
getStateField(node.dag.headState, genesis_validators_root),
|
|
||||||
slot, validator_pubkey, slot_signature):
|
|
||||||
raise newException(CatchableError,
|
|
||||||
"Invalid slot signature")
|
|
||||||
|
|
||||||
let
|
|
||||||
head = node.doChecksAndGetCurrentHead(epoch)
|
|
||||||
epochRef = block:
|
|
||||||
let tmp = node.dag.getEpochRef(head, epoch, true)
|
|
||||||
if isErr(tmp):
|
|
||||||
raise (ref CatchableError)(msg: "Trying to access pruned state")
|
|
||||||
tmp.get()
|
|
||||||
let
|
|
||||||
subnet_id = compute_subnet_for_attestation(
|
|
||||||
get_committee_count_per_slot(epochRef), slot, committee_index)
|
|
||||||
|
|
||||||
# The validator index here is invalid, but since JSON-RPC is on its way
|
|
||||||
# to deprecation, this is fine
|
|
||||||
node.registerDuty(
|
|
||||||
slot, subnet_id, 0.ValidatorIndex,
|
|
||||||
is_aggregator(epochRef, slot, committee_index, slot_signature))
|
|
|
@ -37,9 +37,6 @@ const
|
||||||
|
|
||||||
defaultEth2TcpPort* = 9000
|
defaultEth2TcpPort* = 9000
|
||||||
|
|
||||||
# This is not part of the spec yet! Keep in sync with BASE_RPC_PORT
|
|
||||||
defaultEth2RpcPort* = 9190
|
|
||||||
|
|
||||||
# This is not part of the spec! But its port which uses Lighthouse
|
# This is not part of the spec! But its port which uses Lighthouse
|
||||||
DefaultEth2RestPort* = 5052
|
DefaultEth2RestPort* = 5052
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue