nimbus-eth2/beacon_chain/rpc/rest_node_api.nim
Jacek Sieka f70ff38b53
enable styleCheck:usages (#3573)
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.
2022-04-08 16:22:49 +00:00

307 lines
10 KiB
Nim

import
std/[sequtils],
stew/[byteutils, results],
chronicles,
eth/p2p/discoveryv5/enr,
libp2p/[multiaddress, multicodec, peerstore],
../version, ../beacon_node, ../sync/sync_manager,
../networking/[eth2_network, peer_pool],
../spec/datatypes/base,
../spec/eth2_apis/rpc_types,
./rest_utils
export rest_utils
logScope: topics = "rest_node"
type
ConnectionStateSet* = set[ConnectionState]
PeerTypeSet* = set[PeerType]
RestNodePeerCount* = object
disconnected*: uint64
connecting*: uint64
connected*: uint64
disconnecting*: uint64
proc validateState(states: seq[PeerStateKind]): Result[ConnectionStateSet,
cstring] =
var res: set[ConnectionState]
for item in states:
case item
of PeerStateKind.Disconnected:
if ConnectionState.Disconnected in res:
return err("Peer connection states must be unique")
res.incl(ConnectionState.Disconnected)
of PeerStateKind.Connecting:
if ConnectionState.Connecting in res:
return err("Peer connection states must be unique")
res.incl(ConnectionState.Connecting)
of PeerStateKind.Connected:
if ConnectionState.Connected in res:
return err("Peer connection states must be unique")
res.incl(ConnectionState.Connected)
of PeerStateKind.Disconnecting:
if ConnectionState.Disconnecting in res:
return err("Peer connection states must be unique")
res.incl(ConnectionState.Disconnecting)
if res == {}:
res = {ConnectionState.Connecting, ConnectionState.Connected,
ConnectionState.Disconnecting, ConnectionState.Disconnected}
ok(res)
proc validateDirection(directions: seq[PeerDirectKind]): Result[PeerTypeSet,
cstring] =
var res: set[PeerType]
for item in directions:
case item
of PeerDirectKind.Inbound:
if PeerType.Incoming in res:
return err("Peer direction states must be unique")
res.incl(PeerType.Incoming)
of PeerDirectKind.Outbound:
if PeerType.Outgoing in res:
return err("Peer direction states must be unique")
res.incl(PeerType.Outgoing)
if res == {}:
res = {PeerType.Incoming, PeerType.Outgoing}
ok(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*(router: var RestRouter, node: BeaconNode) =
let
cachedVersion =
RestApiResponse.prepareJsonResponse((version: "Nimbus/" & fullVersionStr))
# https://ethereum.github.io/beacon-APIs/#/Node/getNetworkIdentity
router.api(MethodGet, "/eth/v1/node/identity") do () -> RestApiResponse:
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 RestApiResponse.jsonResponse(
(
peer_id: $node.network.peerId(),
enr: node.network.enrRecord().toURI(),
p2p_addresses: p2pAddresses,
discovery_addresses: discoveryAddresses,
metadata: (
seq_number: node.network.metadata.seq_number,
syncnets: to0xHex(node.network.metadata.syncnets.bytes),
attnets: to0xHex(node.network.metadata.attnets.bytes)
)
)
)
# https://ethereum.github.io/beacon-APIs/#/Node/getPeers
router.api(MethodGet, "/eth/v1/node/peers") do (
state: seq[PeerStateKind],
direction: seq[PeerDirectKind]) -> RestApiResponse:
let connectionMask =
block:
if state.isErr():
return RestApiResponse.jsonError(Http400, InvalidPeerStateValueError,
$state.error())
let sres = validateState(state.get())
if sres.isErr():
return RestApiResponse.jsonError(Http400, InvalidPeerStateValueError,
$sres.error())
sres.get()
let directionMask =
block:
if direction.isErr():
return RestApiResponse.jsonError(Http400,
InvalidPeerDirectionValueError,
$direction.error())
let dres = validateDirection(direction.get())
if dres.isErr():
return RestApiResponse.jsonError(Http400,
InvalidPeerDirectionValueError,
$dres.error())
dres.get()
var res: seq[RpcNodePeer]
for peer in node.network.peers.values():
if (peer.connectionState in connectionMask) and
(peer.direction in directionMask):
let peer = (
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(peer)
return RestApiResponse.jsonResponseWMeta(res, (count: uint64(len(res))))
# https://ethereum.github.io/beacon-APIs/#/Node/getPeerCount
router.api(MethodGet, "/eth/v1/node/peer_count") do () -> RestApiResponse:
var res: RestNodePeerCount
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 RestApiResponse.jsonResponse(res)
# https://ethereum.github.io/beacon-APIs/#/Node/getPeer
router.api(MethodGet, "/eth/v1/node/peers/{peer_id}") do (
peer_id: PeerId) -> RestApiResponse:
let peer =
block:
if peer_id.isErr():
return RestApiResponse.jsonError(Http400, InvalidPeerIdValueError,
$peer_id.error())
let res = node.network.peers.getOrDefault(peer_id.get())
if isNil(res):
return RestApiResponse.jsonError(Http404, PeerNotFoundError)
res
return RestApiResponse.jsonResponse(
(
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
)
)
# https://ethereum.github.io/beacon-APIs/#/Node/getNodeVersion
router.api(MethodGet, "/eth/v1/node/version") do () -> RestApiResponse:
return RestApiResponse.response(cachedVersion, Http200,
"application/json")
# https://ethereum.github.io/beacon-APIs/#/Node/getSyncingStatus
router.api(MethodGet, "/eth/v1/node/syncing") do () -> RestApiResponse:
return RestApiResponse.jsonResponse(node.syncManager.getInfo())
# https://ethereum.github.io/beacon-APIs/#/Node/getHealth
router.api(MethodGet, "/eth/v1/node/health") do () -> RestApiResponse:
# TODO: Add ability to detect node's issues and return 503 error according
# to specification.
let status =
if node.syncManager.inProgress:
Http206
else:
Http200
return RestApiResponse.response("", status, contentType = "")
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
# `/api` path component
router.redirect(
MethodGet,
"/api/eth/v1/node/identity",
"/eth/v1/node/identity"
)
router.redirect(
MethodGet,
"/api/eth/v1/node/peers",
"/eth/v1/node/peers"
)
router.redirect(
MethodGet,
"/api/eth/v1/node/peer_count",
"/eth/v1/node/peer_count"
)
router.redirect(
MethodGet,
"/api/eth/v1/node/peers/{peer_id}",
"/eth/v1/node/peers/{peer_id}"
)
router.redirect(
MethodGet,
"/api/eth/v1/node/version",
"/eth/v1/node/version"
)
router.redirect(
MethodGet,
"/api/eth/v1/node/syncing",
"/eth/v1/node/syncing"
)
router.redirect(
MethodGet,
"/api/eth/v1/node/health",
"/eth/v1/node/health"
)