2024-01-06 14:26:56 +00:00
|
|
|
# Copyright (c) 2021-2024 Status Research & Development GmbH
|
2023-05-20 12:18:51 +00:00
|
|
|
# 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.
|
|
|
|
|
2024-06-11 02:34:56 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2021-03-17 18:46:45 +00:00
|
|
|
import
|
2024-06-11 02:34:56 +00:00
|
|
|
stew/byteutils,
|
2021-03-17 18:46:45 +00:00
|
|
|
chronicles,
|
|
|
|
eth/p2p/discoveryv5/enr,
|
2021-10-21 11:01:29 +00:00
|
|
|
libp2p/[multiaddress, multicodec, peerstore],
|
2021-10-19 14:09:26 +00:00
|
|
|
../version, ../beacon_node, ../sync/sync_manager,
|
2021-03-17 20:42:55 +00:00
|
|
|
../networking/[eth2_network, peer_pool],
|
2021-06-23 14:43:18 +00:00
|
|
|
../spec/datatypes/base,
|
2021-08-03 15:17:11 +00:00
|
|
|
./rest_utils
|
2021-03-17 18:46:45 +00:00
|
|
|
|
2021-10-27 12:01:11 +00:00
|
|
|
export rest_utils
|
|
|
|
|
2021-03-17 18:46:45 +00:00
|
|
|
logScope: topics = "rest_node"
|
|
|
|
|
|
|
|
type
|
|
|
|
ConnectionStateSet* = set[ConnectionState]
|
|
|
|
PeerTypeSet* = set[PeerType]
|
|
|
|
|
2021-05-20 17:56:12 +00:00
|
|
|
RestNodePeerCount* = object
|
|
|
|
disconnected*: uint64
|
|
|
|
connecting*: uint64
|
|
|
|
connected*: uint64
|
|
|
|
disconnecting*: uint64
|
2021-04-03 22:41:46 +00:00
|
|
|
|
2023-12-19 15:44:27 +00:00
|
|
|
RestJson.useDefaultSerializationFor(
|
|
|
|
RestNodePeerCount,
|
|
|
|
)
|
|
|
|
|
2021-03-17 18:46:45 +00:00
|
|
|
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"
|
|
|
|
|
2021-10-21 11:01:29 +00:00
|
|
|
proc getLastSeenAddress(node: BeaconNode, id: PeerId): string =
|
2021-03-17 18:46:45 +00:00
|
|
|
# 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.
|
2022-06-08 05:53:50 +00:00
|
|
|
let addrs = node.network.switch.peerStore[AddressBook][id]
|
2021-10-21 11:01:29 +00:00
|
|
|
if len(addrs) > 0:
|
|
|
|
$addrs[len(addrs) - 1]
|
2021-03-17 18:46:45 +00:00
|
|
|
else:
|
|
|
|
""
|
2024-07-11 15:39:38 +00:00
|
|
|
proc getDiscoveryAddresses(node: BeaconNode): seq[string] =
|
|
|
|
let
|
2024-07-12 15:18:24 +00:00
|
|
|
typedRec = TypedRecord.fromRecord(node.network.enrRecord())
|
2024-07-11 15:39:38 +00:00
|
|
|
peerAddr = typedRec.toPeerAddr(udpProtocol).valueOr:
|
|
|
|
return default(seq[string])
|
|
|
|
maddress = MultiAddress.init(multiCodec("p2p"), peerAddr.peerId).valueOr:
|
|
|
|
return default(seq[string])
|
|
|
|
|
|
|
|
var addresses: seq[string]
|
|
|
|
for item in peerAddr.addrs:
|
|
|
|
let res = concat(item, maddress)
|
|
|
|
if res.isOk():
|
|
|
|
addresses.add($(res.get()))
|
|
|
|
addresses
|
|
|
|
|
|
|
|
proc getP2PAddresses(node: BeaconNode): seq[string] =
|
|
|
|
let
|
|
|
|
pinfo = node.network.switch.peerInfo
|
|
|
|
maddress = MultiAddress.init(multiCodec("p2p"), pinfo.peerId).valueOr:
|
|
|
|
return default(seq[string])
|
|
|
|
|
|
|
|
var addresses: seq[string]
|
|
|
|
for item in node.network.announcedAddresses:
|
|
|
|
let res = concat(item, maddress)
|
|
|
|
if res.isOk():
|
|
|
|
addresses.add($(res.get()))
|
2021-03-17 18:46:45 +00:00
|
|
|
for item in pinfo.addrs:
|
2024-07-11 15:39:38 +00:00
|
|
|
let res = concat(item, maddress)
|
|
|
|
if res.isOk():
|
|
|
|
addresses.add($(res.get()))
|
|
|
|
addresses
|
2021-03-17 18:46:45 +00:00
|
|
|
|
|
|
|
proc installNodeApiHandlers*(router: var RestRouter, node: BeaconNode) =
|
2021-10-06 13:43:03 +00:00
|
|
|
let
|
|
|
|
cachedVersion =
|
2024-07-15 02:27:23 +00:00
|
|
|
RestApiResponse.prepareJsonResponse((version: nimbusAgentStr))
|
2021-10-06 13:43:03 +00:00
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getNetworkIdentity
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/identity") do () -> RestApiResponse:
|
|
|
|
RestApiResponse.jsonResponse(
|
2021-03-23 22:50:18 +00:00
|
|
|
(
|
2021-03-17 18:46:45 +00:00
|
|
|
peer_id: $node.network.peerId(),
|
2022-04-08 16:22:49 +00:00
|
|
|
enr: node.network.enrRecord().toURI(),
|
2024-07-11 15:39:38 +00:00
|
|
|
p2p_addresses: node.getP2PAddresses(),
|
|
|
|
discovery_addresses: node.getDiscoveryAddresses(),
|
2021-03-23 22:50:18 +00:00
|
|
|
metadata: (
|
|
|
|
seq_number: node.network.metadata.seq_number,
|
2022-01-08 20:06:34 +00:00
|
|
|
syncnets: to0xHex(node.network.metadata.syncnets.bytes),
|
|
|
|
attnets: to0xHex(node.network.metadata.attnets.bytes)
|
2021-03-23 22:50:18 +00:00
|
|
|
)
|
2021-03-17 18:46:45 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getPeers
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/peers") do (
|
2021-04-04 09:48:44 +00:00
|
|
|
state: seq[PeerStateKind],
|
|
|
|
direction: seq[PeerDirectKind]) -> RestApiResponse:
|
2021-03-23 22:50:18 +00:00
|
|
|
let connectionMask =
|
|
|
|
block:
|
2021-04-04 09:48:44 +00:00
|
|
|
if state.isErr():
|
2021-04-08 14:34:05 +00:00
|
|
|
return RestApiResponse.jsonError(Http400, InvalidPeerStateValueError,
|
2021-04-04 09:48:44 +00:00
|
|
|
$state.error())
|
2023-11-01 07:32:41 +00:00
|
|
|
validateState(state.get()).valueOr:
|
2021-04-08 14:34:05 +00:00
|
|
|
return RestApiResponse.jsonError(Http400, InvalidPeerStateValueError,
|
2023-11-01 07:32:41 +00:00
|
|
|
$error)
|
2021-03-23 22:50:18 +00:00
|
|
|
let directionMask =
|
|
|
|
block:
|
2021-04-04 09:48:44 +00:00
|
|
|
if direction.isErr():
|
2021-03-23 22:50:18 +00:00
|
|
|
return RestApiResponse.jsonError(Http400,
|
2021-04-08 14:34:05 +00:00
|
|
|
InvalidPeerDirectionValueError,
|
2021-04-04 09:48:44 +00:00
|
|
|
$direction.error())
|
2023-11-01 07:32:41 +00:00
|
|
|
validateDirection(direction.get()).valueOr:
|
2021-03-23 22:50:18 +00:00
|
|
|
return RestApiResponse.jsonError(Http400,
|
2021-04-08 14:34:05 +00:00
|
|
|
InvalidPeerDirectionValueError,
|
2023-11-01 07:32:41 +00:00
|
|
|
$error)
|
2022-06-02 09:39:08 +00:00
|
|
|
var res: seq[RestNodePeer]
|
2021-10-21 11:01:29 +00:00
|
|
|
for peer in node.network.peers.values():
|
|
|
|
if (peer.connectionState in connectionMask) and
|
|
|
|
(peer.direction in directionMask):
|
2022-06-02 09:39:08 +00:00
|
|
|
let peer = RestNodePeer(
|
2021-10-21 11:01:29 +00:00
|
|
|
peer_id: $peer.peerId,
|
2022-04-08 16:22:49 +00:00
|
|
|
enr: if peer.enr.isSome(): peer.enr.get().toURI() else: "",
|
2021-10-21 11:01:29 +00:00
|
|
|
last_seen_p2p_address: getLastSeenAddress(node, peer.peerId),
|
|
|
|
state: peer.connectionState.toString(),
|
|
|
|
direction: peer.direction.toString(),
|
2022-06-02 09:39:08 +00:00
|
|
|
# Fields `agent` and `proto` are not part of specification
|
2022-06-08 05:53:50 +00:00
|
|
|
agent: node.network.switch.peerStore[AgentBook][peer.peerId],
|
|
|
|
proto: node.network.switch.peerStore[ProtoVersionBook][peer.peerId]
|
2021-03-17 18:46:45 +00:00
|
|
|
)
|
|
|
|
res.add(peer)
|
2024-01-20 16:06:28 +00:00
|
|
|
RestApiResponse.jsonResponseWMeta(res, (count: RestNumeric(len(res))))
|
2021-03-17 18:46:45 +00:00
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getPeerCount
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/peer_count") do () -> RestApiResponse:
|
2021-05-20 17:56:12 +00:00
|
|
|
var res: RestNodePeerCount
|
2021-03-17 18:46:45 +00:00
|
|
|
for item in node.network.peers.values():
|
|
|
|
case item.connectionState
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Connecting:
|
2021-03-17 18:46:45 +00:00
|
|
|
inc(res.connecting)
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Connected:
|
2021-03-17 18:46:45 +00:00
|
|
|
inc(res.connected)
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Disconnecting:
|
2021-03-17 18:46:45 +00:00
|
|
|
inc(res.disconnecting)
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Disconnected:
|
2021-03-17 18:46:45 +00:00
|
|
|
inc(res.disconnected)
|
|
|
|
of ConnectionState.None:
|
|
|
|
discard
|
2024-01-20 16:06:28 +00:00
|
|
|
RestApiResponse.jsonResponse(res)
|
2021-03-17 18:46:45 +00:00
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getPeer
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/peers/{peer_id}") do (
|
2022-04-08 16:22:49 +00:00
|
|
|
peer_id: PeerId) -> RestApiResponse:
|
2021-03-23 22:50:18 +00:00
|
|
|
let peer =
|
|
|
|
block:
|
|
|
|
if peer_id.isErr():
|
2021-04-08 14:34:05 +00:00
|
|
|
return RestApiResponse.jsonError(Http400, InvalidPeerIdValueError,
|
2021-03-23 22:50:18 +00:00
|
|
|
$peer_id.error())
|
|
|
|
let res = node.network.peers.getOrDefault(peer_id.get())
|
|
|
|
if isNil(res):
|
2021-04-08 14:34:05 +00:00
|
|
|
return RestApiResponse.jsonError(Http404, PeerNotFoundError)
|
2021-03-23 22:50:18 +00:00
|
|
|
res
|
2024-01-20 16:06:28 +00:00
|
|
|
RestApiResponse.jsonResponse(
|
2021-03-23 22:50:18 +00:00
|
|
|
(
|
2021-10-21 11:01:29 +00:00
|
|
|
peer_id: $peer.peerId,
|
2022-04-08 16:22:49 +00:00
|
|
|
enr: if peer.enr.isSome(): peer.enr.get().toURI() else: "",
|
2021-10-21 11:01:29 +00:00
|
|
|
last_seen_p2p_address: getLastSeenAddress(node, peer.peerId),
|
2021-03-17 18:46:45 +00:00
|
|
|
state: peer.connectionState.toString(),
|
|
|
|
direction: peer.direction.toString(),
|
2024-01-20 16:06:28 +00:00
|
|
|
agent: node.network.switch.peerStore[AgentBook][peer.peerId],
|
|
|
|
# Fields `agent` and `proto` are not part of specification
|
|
|
|
proto: node.network.switch.peerStore[ProtoVersionBook][peer.peerId]
|
|
|
|
# Fields `agent` and `proto` are not part of specification
|
2021-03-17 18:46:45 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getNodeVersion
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/version") do () -> RestApiResponse:
|
|
|
|
RestApiResponse.response(cachedVersion, Http200, "application/json")
|
2021-03-17 18:46:45 +00:00
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getSyncingStatus
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/syncing") do () -> RestApiResponse:
|
2022-06-14 20:26:23 +00:00
|
|
|
let
|
|
|
|
wallSlot = node.beaconClock.now().slotOrZero()
|
|
|
|
headSlot = node.dag.head.slot
|
|
|
|
distance = wallSlot - headSlot
|
2022-06-20 05:53:39 +00:00
|
|
|
isSyncing =
|
|
|
|
if isNil(node.syncManager):
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
node.syncManager.inProgress
|
|
|
|
isOptimistic =
|
2022-06-28 10:21:16 +00:00
|
|
|
if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH:
|
2023-05-20 12:18:51 +00:00
|
|
|
some(not node.dag.head.executionValid)
|
2022-06-20 05:53:39 +00:00
|
|
|
else:
|
|
|
|
none[bool]()
|
2023-04-27 08:47:38 +00:00
|
|
|
elOffline =
|
|
|
|
if node.currentSlot().epoch() >= node.dag.cfg.CAPELLA_FORK_EPOCH:
|
|
|
|
some(not node.elManager.hasAnyWorkingConnection)
|
|
|
|
else:
|
|
|
|
none[bool]() # Added with ethereum/beacon-APIs v2.4.0
|
2022-06-20 05:53:39 +00:00
|
|
|
|
2022-06-14 20:26:23 +00:00
|
|
|
info = RestSyncInfo(
|
|
|
|
head_slot: headSlot, sync_distance: distance,
|
2023-04-27 08:47:38 +00:00
|
|
|
is_syncing: isSyncing, is_optimistic: isOptimistic,
|
|
|
|
el_offline: elOffline
|
2022-06-14 20:26:23 +00:00
|
|
|
)
|
2024-01-20 16:06:28 +00:00
|
|
|
RestApiResponse.jsonResponse(info)
|
2021-03-17 18:46:45 +00:00
|
|
|
|
2021-08-23 10:41:48 +00:00
|
|
|
# https://ethereum.github.io/beacon-APIs/#/Node/getHealth
|
2024-01-20 16:06:28 +00:00
|
|
|
router.api2(MethodGet, "/eth/v1/node/health") do () -> RestApiResponse:
|
2021-03-17 18:46:45 +00:00
|
|
|
# TODO: Add ability to detect node's issues and return 503 error according
|
|
|
|
# to specification.
|
2022-01-28 11:46:48 +00:00
|
|
|
let status =
|
2021-03-17 18:46:45 +00:00
|
|
|
if node.syncManager.inProgress:
|
2022-01-28 11:46:48 +00:00
|
|
|
Http206
|
2021-03-17 18:46:45 +00:00
|
|
|
else:
|
2022-01-28 11:46:48 +00:00
|
|
|
Http200
|
2024-07-15 02:27:23 +00:00
|
|
|
RestApiResponse.response(status)
|