mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-10 05:35:28 +00:00
Some upstream repos still need fixes, but this gets us close enough that style hints can be enabled by default. In general, "canonical" spellings are preferred even if they violate nep-1 - this applies in particular to spec-related stuff like `genesis_validators_root` which appears throughout the codebase.
387 lines
12 KiB
Nim
387 lines
12 KiB
Nim
# 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.
|
|
|
|
import
|
|
std/[sequtils],
|
|
stew/results,
|
|
chronicles,
|
|
libp2p/[multiaddress, multicodec, peerstore],
|
|
libp2p/protocols/pubsub/pubsubpeer,
|
|
./rest_utils,
|
|
../eth1/eth1_monitor,
|
|
../validators/validator_duties,
|
|
../spec/forks,
|
|
../beacon_node, ../nimbus_binary_common
|
|
|
|
export rest_utils
|
|
|
|
when defined(chronosFutureTracking):
|
|
import stew/base10
|
|
|
|
logScope: topics = "rest_nimbusapi"
|
|
|
|
type
|
|
RestPeerInfo* = object
|
|
peerId*: string
|
|
addrs*: seq[string]
|
|
protocols*: seq[string]
|
|
protoVersion*: string
|
|
agentVersion*: string
|
|
|
|
RestPeerInfoTuple* = tuple
|
|
peerId: string
|
|
addrs: seq[string]
|
|
protocols: seq[string]
|
|
protoVersion: string
|
|
agentVersion: string
|
|
|
|
RestSimplePeer* = object
|
|
info*: RestPeerInfo
|
|
connectionState*: string
|
|
score*: int
|
|
|
|
RestFutureInfo* = object
|
|
id*: string
|
|
child_id*: string
|
|
procname*: string
|
|
filename*: string
|
|
line*: int
|
|
state*: string
|
|
|
|
RestPubSubPeer* = object
|
|
peerId*: PeerId
|
|
score*: float64
|
|
iWantBudget*: int
|
|
iHaveBudget*: int
|
|
outbound*: bool
|
|
appScore*: float64
|
|
behaviourPenalty*: float64
|
|
sendConnAvail*: bool
|
|
closed*: bool
|
|
atEof*: bool
|
|
address*: string
|
|
backoff*: string
|
|
agent*: string
|
|
|
|
RestPeerStats* = object
|
|
peerId*: PeerId
|
|
null*: bool
|
|
connected*: bool
|
|
expire*: string
|
|
score*: float64
|
|
|
|
RestPeerStatus* = object
|
|
peerId*: PeerId
|
|
connected*: bool
|
|
|
|
proc toInfo(node: BeaconNode, peerId: PeerId): RestPeerInfo =
|
|
RestPeerInfo(
|
|
peerId: $peerId,
|
|
addrs: node.network.switch.peerStore.addressBook.get(peerId).toSeq().mapIt($it),
|
|
protocols: node.network.switch.peerStore.protoBook.get(peerId).toSeq(),
|
|
protoVersion: node.network.switch.peerStore.protoVersionBook.get(peerId),
|
|
agentVersion: node.network.switch.peerStore.agentBook.get(peerId)
|
|
)
|
|
|
|
proc toNode(v: PubSubPeer, backoff: Moment): RestPubSubPeer =
|
|
RestPubSubPeer(
|
|
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"
|
|
)
|
|
|
|
proc installNimbusApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
|
router.api(MethodGet, "/nimbus/v1/beacon/head") do () -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse(node.dag.head.slot)
|
|
|
|
router.api(MethodGet, "/nimbus/v1/chain/head") do() -> RestApiResponse:
|
|
let
|
|
head = node.dag.head
|
|
finalized = getStateField(node.dag.headState, finalized_checkpoint)
|
|
justified =
|
|
getStateField(node.dag.headState, current_justified_checkpoint)
|
|
return RestApiResponse.jsonResponse(
|
|
(
|
|
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()
|
|
)
|
|
)
|
|
|
|
router.api(MethodGet, "/nimbus/v1/syncmanager/status") do (
|
|
) -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse(node.syncManager.inProgress)
|
|
|
|
router.api(MethodGet, "/nimbus/v1/node/peerid") do (
|
|
) -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse((peerid: $node.network.peerId()))
|
|
|
|
router.api(MethodGet, "/nimbus/v1/node/version") do (
|
|
) -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse((version: "Nimbus/" & fullVersionStr))
|
|
|
|
router.api(MethodGet, "/nimbus/v1/network/ids") do (
|
|
) -> RestApiResponse:
|
|
var res: seq[PeerId]
|
|
for peerId, peer in node.network.peerPool:
|
|
res.add(peerId)
|
|
return RestApiResponse.jsonResponse((peerids: res))
|
|
|
|
router.api(MethodGet, "/nimbus/v1/network/peers") do (
|
|
) -> RestApiResponse:
|
|
var res: seq[RestSimplePeer]
|
|
for id, peer in node.network.peerPool:
|
|
res.add(
|
|
RestSimplePeer(
|
|
info: toInfo(node, id),
|
|
connectionState: $peer.connectionState,
|
|
score: peer.score
|
|
)
|
|
)
|
|
return RestApiResponse.jsonResponse((peers: res))
|
|
|
|
router.api(MethodPost, "/nimbus/v1/graffiti") do (
|
|
contentBody: Option[ContentBody]) -> RestApiResponse:
|
|
if contentBody.isNone:
|
|
return RestApiResponse.jsonError(Http400, EmptyRequestBodyError)
|
|
|
|
template setGraffitiAux(node: BeaconNode, graffitiStr: string): RestApiResponse =
|
|
node.graffitiBytes = try:
|
|
GraffitiBytes.init(graffitiStr)
|
|
except CatchableError as err:
|
|
return RestApiResponse.jsonError(Http400, InvalidGraffitiBytesValue,
|
|
err.msg)
|
|
RestApiResponse.jsonResponse((result: true))
|
|
|
|
case contentBody.get.contentType
|
|
of "application/json":
|
|
let graffitiBytes = decodeBody(GraffitiBytes, contentBody.get)
|
|
if graffitiBytes.isErr:
|
|
return RestApiResponse.jsonError(Http400, InvalidGraffitiBytesValue,
|
|
$graffitiBytes.error)
|
|
node.graffitiBytes = graffitiBytes.get
|
|
return RestApiResponse.jsonResponse((result: true))
|
|
of "text/plain":
|
|
return node.setGraffitiAux contentBody.get.strData
|
|
of "application/x-www-form-urlencoded":
|
|
return node.setGraffitiAux decodeUrl(contentBody.get.strData)
|
|
else:
|
|
return RestApiResponse.jsonError(Http400, "Unsupported content type: " &
|
|
$contentBody.get.contentType)
|
|
|
|
router.api(MethodGet, "/nimbus/v1/graffiti") do (
|
|
) -> RestApiResponse:
|
|
return RestApiResponse.jsonResponse(node.graffitiBytes)
|
|
|
|
router.api(MethodPost, "/nimbus/v1/chronicles/settings") do (
|
|
log_level: Option[string]) -> RestApiResponse:
|
|
if log_level.isSome():
|
|
let level =
|
|
block:
|
|
let res = log_level.get()
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http400, InvalidLogLevelValueError,
|
|
$res.error())
|
|
res.get()
|
|
{.gcsafe.}:
|
|
updateLogLevel(level)
|
|
return RestApiResponse.jsonResponse((result: true))
|
|
|
|
router.api(MethodGet, "/nimbus/v1/eth1/chain") do (
|
|
) -> RestApiResponse:
|
|
let res =
|
|
if not(isNil(node.eth1Monitor)):
|
|
mapIt(node.eth1Monitor.depositChainBlocks, it)
|
|
else:
|
|
@[]
|
|
return RestApiResponse.jsonResponse(res)
|
|
|
|
router.api(MethodGet, "/nimbus/v1/eth1/proposal_data") do (
|
|
) -> RestApiResponse:
|
|
let wallSlot = node.beaconClock.now.slotOrZero
|
|
let head =
|
|
block:
|
|
let res = node.getSyncedHead(wallSlot)
|
|
if res.isErr():
|
|
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
|
|
res.get()
|
|
let proposalState = assignClone(node.dag.headState)
|
|
node.dag.withUpdatedState(
|
|
proposalState[],
|
|
head.atSlot(wallSlot).toBlockSlotId().expect("not nil")):
|
|
return RestApiResponse.jsonResponse(
|
|
node.getBlockProposalEth1Data(state))
|
|
do:
|
|
return RestApiResponse.jsonError(Http400, PrunedStateError)
|
|
|
|
router.api(MethodGet, "/nimbus/v1/debug/chronos/futures") do (
|
|
) -> RestApiResponse:
|
|
when defined(chronosFutureTracking):
|
|
var res: seq[RestFutureInfo]
|
|
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(
|
|
RestFutureInfo(
|
|
id: futureId,
|
|
child_id: childId,
|
|
procname: $loc.procedure,
|
|
filename: $loc.file,
|
|
line: loc.line,
|
|
state: $item.state
|
|
)
|
|
)
|
|
return RestApiResponse.jsonResponse(res)
|
|
else:
|
|
return RestApiResponse.jsonError(Http503,
|
|
"Compile with '-d:chronosFutureTracking' to get this request working")
|
|
|
|
router.api(MethodGet, "/nimbus/v1/debug/gossip/peers") do (
|
|
) -> RestApiResponse:
|
|
|
|
let gossipPeers =
|
|
block:
|
|
var res: seq[tuple[topic: string, peers: seq[RestPubSubPeer]]]
|
|
for topic, v in node.network.pubsub.gossipsub:
|
|
var peers: seq[RestPubSubPeer]
|
|
let backoff = node.network.pubsub.backingOff.getOrDefault(topic)
|
|
for peer in v:
|
|
peers.add(peer.toNode(backoff.getOrDefault(peer.peerId)))
|
|
res.add((topic: topic, peers: peers))
|
|
res
|
|
let meshPeers =
|
|
block:
|
|
var res: seq[tuple[topic: string, peers: seq[RestPubSubPeer]]]
|
|
for topic, v in node.network.pubsub.mesh:
|
|
var peers: seq[RestPubSubPeer]
|
|
let backoff = node.network.pubsub.backingOff.getOrDefault(topic)
|
|
for peer in v:
|
|
peers.add(peer.toNode(backoff.getOrDefault(peer.peerId)))
|
|
res.add((topic: topic, peers: peers))
|
|
res
|
|
let colocationPeers =
|
|
block:
|
|
var res: seq[tuple[address: string, peerids: seq[PeerId]]]
|
|
for k, v in node.network.pubsub.peersInIP:
|
|
var peerids: seq[PeerId]
|
|
for id in v:
|
|
peerids.add(id)
|
|
res.add(($k, peerids))
|
|
res
|
|
let peerStats =
|
|
block:
|
|
var stats: seq[RestPeerStats]
|
|
for peerId, pstats in node.network.pubsub.peerStats:
|
|
let peer = node.network.pubsub.peers.getOrDefault(peerId)
|
|
stats.add(
|
|
RestPeerStats(
|
|
peerId: peerId,
|
|
null: isNil(peer),
|
|
connected: if isNil(peer): false else: peer.connected(),
|
|
expire: $(pstats.expire - Moment.now()),
|
|
score: pstats.score
|
|
)
|
|
)
|
|
stats
|
|
let allPeers =
|
|
block:
|
|
var peers: seq[RestPeerStatus]
|
|
for peerId, peer in node.network.pubsub.peers:
|
|
peers.add(RestPeerStatus(peerId: peerId, connected: peer.connected))
|
|
peers
|
|
return RestApiResponse.jsonResponse(
|
|
(
|
|
gossip_peers: gossipPeers,
|
|
mesh_peers: meshPeers,
|
|
colocation_peers: colocationPeers,
|
|
peer_stats: peerStats,
|
|
all_peers: allPeers
|
|
)
|
|
)
|
|
|
|
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
|
|
# `/api` path component
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/beacon/head",
|
|
"/nimbus/v1/beacon/head")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/chain/head",
|
|
"/nimbus/v1/chain/head")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/syncmanager/status",
|
|
"/nimbus/v1/syncmanager/status")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/node/peerid",
|
|
"/nimbus/v1/node/peerid")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/node/version",
|
|
"/nimbus/v1/node/version")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/network/ids",
|
|
"/nimbus/v1/network/ids")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/network/peers",
|
|
"/nimbus/v1/network/peers")
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/nimbus/v1/graffiti",
|
|
"/nimbus/v1/graffiti")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/graffiti",
|
|
"/nimbus/v1/graffiti")
|
|
router.redirect(
|
|
MethodPost,
|
|
"/api/nimbus/v1/chronicles/settings",
|
|
"/nimbus/v1/chronicles/settings")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/eth1/chain",
|
|
"/nimbus/v1/eth1/chain")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/eth1/proposal_data",
|
|
"/nimbus/v1/eth1/proposal_data")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/debug/chronos/futures",
|
|
"/nimbus/v1/debug/chronos/futures")
|
|
router.redirect(
|
|
MethodGet,
|
|
"/api/nimbus/v1/debug/gossip/peers",
|
|
"/nimbus/v1/debug/gossip/peers")
|