NagyZoltanPeter 1762548741
chore: clarify api folders (#3637)
* Rename waku_api to rest_api and underlying rest to endpoint for clearity
* Rename node/api to node/kernel_api to suggest that it is an internal accessor to node interface + make everything compile after renaming
* make waku api a top level import
* fix use of relative path imports and use default to root rather in case of waku and tools modules
2025-11-15 23:31:09 +01:00

474 lines
17 KiB
Nim
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{.push raises: [].}
import
std/[sets, strformat, sequtils, tables],
chronicles,
chronicles/topics_registry,
json_serialization,
presto/route,
libp2p/[peerinfo, switch, peerid, protocols/pubsub/pubsubpeer]
import
waku/[
waku_core,
waku_core/topics/pubsub_topic,
waku_store_legacy/common,
waku_store/common,
waku_filter_v2,
waku_lightpush_legacy/common,
waku_relay,
waku_peer_exchange,
waku_node,
node/peer_manager,
waku_enr/sharding,
],
../responses,
../serdes,
../rest_serdes,
./types
export types
logScope:
topics = "waku node rest admin api"
const ROUTE_ADMIN_V1_PEERS_STATS* = "/admin/v1/peers/stats" # provides peer statistics
const ROUTE_ADMIN_V1_PEERS* = "/admin/v1/peers" # returns all peers
const ROUTE_ADMIN_V1_SINGLE_PEER* = "/admin/v1/peer/{peerId}"
const ROUTE_ADMIN_V1_SERVICE_PEERS* = "/admin/v1/peers/service" # returns all peers
const ROUTE_ADMIN_V1_CONNECTED_PEERS* = "/admin/v1/peers/connected"
const ROUTE_ADMIN_V1_CONNECTED_PEERS_ON_SHARD* =
"/admin/v1/peers/connected/on/{shardId}"
const ROUTE_ADMIN_V1_RELAY_PEERS* = "/admin/v1/peers/relay"
const ROUTE_ADMIN_V1_RELAY_PEERS_ON_SHARD* = "/admin/v1/peers/relay/on/{shardId}"
const ROUTE_ADMIN_V1_MESH_PEERS* = "/admin/v1/peers/mesh"
const ROUTE_ADMIN_V1_MESH_PEERS_ON_SHARD* = "/admin/v1/peers/mesh/on/{shardId}"
const ROUTE_ADMIN_V1_FILTER_SUBS* = "/admin/v1/filter/subscriptions"
const ROUTE_ADMIN_V1_POST_LOG_LEVEL* = "/admin/v1/log-level/{logLevel}"
# sets the new log level for the node
type PeerProtocolTuple =
tuple[
multiaddr: string,
protocol: string,
shards: seq[uint16],
connected: Connectedness,
agent: string,
origin: PeerOrigin,
]
proc tuplesToWakuPeers(peers: var WakuPeers, peersTup: seq[PeerProtocolTuple]) =
for peer in peersTup:
peers.add(
peer.multiaddr, peer.protocol, peer.shards, peer.connected, peer.agent,
peer.origin,
)
proc populateAdminPeerInfo(
peers: var WakuPeers, node: WakuNode, codec: Option[string] = none[string]()
) =
if codec.isNone():
peers = node.peerManager.switch.peerStore.peers().mapIt(WakuPeer.init(it))
else:
let peersTuples = node.peerManager.switch.peerStore.peers(codec.get()).mapIt(
(
multiaddr: constructMultiaddrStr(it),
protocol: codec.get(),
shards: it.getShards(),
connected: it.connectedness,
agent: it.agent,
origin: it.origin,
)
)
tuplesToWakuPeers(peers, peersTuples)
proc populateAdminPeerInfoForAll(node: WakuNode): WakuPeers =
var peers: WakuPeers = @[]
populateAdminPeerInfo(peers, node)
return peers
proc populateAdminPeerInfoForCodecs(node: WakuNode, codecs: seq[string]): WakuPeers =
var peers: WakuPeers = @[]
for codec in codecs:
populateAdminPeerInfo(peers, node, some(codec))
return peers
proc getRelayPeers(node: WakuNode): PeersOfShards =
var relayPeers: PeersOfShards = @[]
if not node.wakuRelay.isNil():
for topic in node.wakuRelay.getSubscribedTopics():
let relayShard = RelayShard.parse(topic).valueOr:
error "Invalid subscribed topic", error = error, topic = topic
continue
let pubsubPeers =
node.wakuRelay.getConnectedPubSubPeers(topic).get(initHashSet[PubSubPeer](0))
relayPeers.add(
PeersOfShard(
shard: relayShard.shardId,
peers: toSeq(pubsubPeers).mapIt(WakuPeer.init(it, node.peerManager)),
)
)
return relayPeers
proc getMeshPeers(node: WakuNode): PeersOfShards =
var meshPeers: PeersOfShards = @[]
if not node.wakuRelay.isNil():
for topic in node.wakuRelay.getSubscribedTopics():
let relayShard = RelayShard.parse(topic).valueOr:
error "Invalid subscribed topic", error = error, topic = topic
continue
let peers =
node.wakuRelay.getPubSubPeersInMesh(topic).get(initHashSet[PubSubPeer](0))
meshPeers.add(
PeersOfShard(
shard: relayShard.shardId,
peers: toSeq(peers).mapIt(WakuPeer.init(it, node.peerManager)),
)
)
return meshPeers
proc installAdminV1GetPeersHandler(router: var RestRouter, node: WakuNode) =
router.api(MethodGet, ROUTE_ADMIN_V1_PEERS) do() -> RestApiResponse:
let peers = populateAdminPeerInfoForAll(node)
let resp = RestApiResponse.jsonResponse(peers, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_SINGLE_PEER) do(
peerId: string
) -> RestApiResponse:
let peerIdString = peerId.valueOr:
return RestApiResponse.badRequest("Invalid argument:" & $error)
let peerIdVal: PeerId = PeerId.init(peerIdString).valueOr:
return RestApiResponse.badRequest("Invalid argument:" & $error)
if node.peerManager.switch.peerStore.peerExists(peerIdVal):
let peerInfo = node.peerManager.switch.peerStore.getPeer(peerIdVal)
let peer = WakuPeer.init(peerInfo)
let resp = RestApiResponse.jsonResponse(peer, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
else:
return RestApiResponse.notFound(fmt("Peer with ID {peerId} not found"))
router.api(MethodGet, ROUTE_ADMIN_V1_SERVICE_PEERS) do() -> RestApiResponse:
let peers = populateAdminPeerInfoForCodecs(
node,
@[
WakuRelayCodec, WakuFilterSubscribeCodec, WakuStoreCodec, WakuLegacyStoreCodec,
WakuLegacyLightPushCodec, WakuLightPushCodec, WakuPeerExchangeCodec,
WakuReconciliationCodec, WakuTransferCodec,
],
)
let resp = RestApiResponse.jsonResponse(peers, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_CONNECTED_PEERS) do() -> RestApiResponse:
let allPeers = populateAdminPeerInfoForAll(node)
let connectedPeers = allPeers.filterIt(it.connected == Connectedness.Connected)
let resp = RestApiResponse.jsonResponse(connectedPeers, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_CONNECTED_PEERS_ON_SHARD) do(
shardId: uint16
) -> RestApiResponse:
let shard = shardId.valueOr:
return RestApiResponse.badRequest(fmt("Invalid shardId: {error}"))
let allPeers = populateAdminPeerInfoForAll(node)
let connectedPeers = allPeers.filterIt(
it.connected == Connectedness.Connected and it.shards.contains(shard)
)
let resp = RestApiResponse.jsonResponse(connectedPeers, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_RELAY_PEERS) do() -> RestApiResponse:
if node.wakuRelay.isNil():
return RestApiResponse.serviceUnavailable(
"Error: Relay Protocol is not mounted to the node"
)
var relayPeers: PeersOfShards = getRelayPeers(node)
let resp = RestApiResponse.jsonResponse(relayPeers, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_RELAY_PEERS_ON_SHARD) do(
shardId: uint16
) -> RestApiResponse:
let shard = shardId.valueOr:
return RestApiResponse.badRequest(fmt("Invalid shardId: {error}"))
if node.wakuMetadata.isNil():
return RestApiResponse.serviceUnavailable(
"Error: Metadata Protocol is not mounted to the node"
)
if node.wakuRelay.isNil():
return RestApiResponse.serviceUnavailable(
"Error: Relay Protocol is not mounted to the node"
)
# TODO: clusterId and shards should be uint16 across all codebase and probably be defined as a type
let topic = toPubsubTopic(
RelayShard(clusterId: node.wakuMetadata.clusterId.uint16, shardId: shard)
)
let pubsubPeers =
node.wakuRelay.getConnectedPubSubPeers(topic).get(initHashSet[PubSubPeer](0))
let relayPeer = PeersOfShard(
shard: shard, peers: toSeq(pubsubPeers).mapIt(WakuPeer.init(it, node.peerManager))
)
let resp = RestApiResponse.jsonResponse(relayPeer, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_MESH_PEERS) do() -> RestApiResponse:
if node.wakuRelay.isNil():
return RestApiResponse.serviceUnavailable(
"Error: Relay Protocol is not mounted to the node"
)
var meshPeers: PeersOfShards = getMeshPeers(node)
let resp = RestApiResponse.jsonResponse(meshPeers, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_MESH_PEERS_ON_SHARD) do(
shardId: uint16
) -> RestApiResponse:
let shard = shardId.valueOr:
return RestApiResponse.badRequest(fmt("Invalid shardId: {error}"))
if node.wakuMetadata.isNil():
return RestApiResponse.serviceUnavailable(
"Error: Metadata Protocol is not mounted to the node"
)
if node.wakuRelay.isNil():
return RestApiResponse.serviceUnavailable(
"Error: Relay Protocol is not mounted to the node"
)
let topic = toPubsubTopic(
RelayShard(clusterId: node.wakuMetadata.clusterId.uint16, shardId: shard)
)
let peers =
node.wakuRelay.getPubSubPeersInMesh(topic).get(initHashSet[PubSubPeer](0))
let relayPeer = PeersOfShard(
shard: shard, peers: toSeq(peers).mapIt(WakuPeer.init(it, node.peerManager))
)
let resp = RestApiResponse.jsonResponse(relayPeer, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
router.api(MethodGet, ROUTE_ADMIN_V1_PEERS_STATS) do() -> RestApiResponse:
let peers = populateAdminPeerInfoForAll(node)
var stats: PeerStats = initOrderedTable[string, OrderedTable[string, int]]()
stats["Sum"] = {"Total peers": peers.len()}.toOrderedTable()
# stats of connectedness
var connectednessStats = initOrderedTable[string, int]()
connectednessStats[$Connectedness.Connected] =
peers.countIt(it.connected == Connectedness.Connected)
connectednessStats[$Connectedness.NotConnected] =
peers.countIt(it.connected == Connectedness.NotConnected)
connectednessStats[$Connectedness.CannotConnect] =
peers.countIt(it.connected == Connectedness.CannotConnect)
connectednessStats[$Connectedness.CanConnect] =
peers.countIt(it.connected == Connectedness.CanConnect)
stats["By Connectedness"] = connectednessStats
# stats of relay peers
var totalRelayPeers = 0
stats["Relay peers"] = block:
let relayPeers = getRelayPeers(node)
var stat = initOrderedTable[string, int]()
for ps in relayPeers:
totalRelayPeers += ps.peers.len
stat[$ps.shard] = ps.peers.len
stat["Total relay peers"] = relayPeers.len
stat
# stats of mesh peers
stats["Mesh peers"] = block:
let meshPeers = getMeshPeers(node)
var totalMeshPeers = 0
var stat = initOrderedTable[string, int]()
for ps in meshPeers:
totalMeshPeers += ps.peers.len
stat[$ps.shard] = ps.peers.len
stat["Total mesh peers"] = meshPeers.len
stat
var protoStats = initOrderedTable[string, int]()
protoStats[WakuRelayCodec] = peers.countIt(it.protocols.contains(WakuRelayCodec))
protoStats[WakuFilterSubscribeCodec] =
peers.countIt(it.protocols.contains(WakuFilterSubscribeCodec))
protoStats[WakuFilterPushCodec] =
peers.countIt(it.protocols.contains(WakuFilterPushCodec))
protoStats[WakuStoreCodec] = peers.countIt(it.protocols.contains(WakuStoreCodec))
protoStats[WakuLegacyStoreCodec] =
peers.countIt(it.protocols.contains(WakuLegacyStoreCodec))
protoStats[WakuLightPushCodec] =
peers.countIt(it.protocols.contains(WakuLightPushCodec))
protoStats[WakuLegacyLightPushCodec] =
peers.countIt(it.protocols.contains(WakuLegacyLightPushCodec))
protoStats[WakuPeerExchangeCodec] =
peers.countIt(it.protocols.contains(WakuPeerExchangeCodec))
protoStats[WakuReconciliationCodec] =
peers.countIt(it.protocols.contains(WakuReconciliationCodec))
stats["By Protocols"] = protoStats
let resp = RestApiResponse.jsonResponse(stats, status = Http200).valueOr:
error "An error occurred while building the json response: ", error = error
return RestApiResponse.internalServerError(
fmt("An error occurred while building the json response: {error}")
)
return resp
proc installAdminV1PostPeersHandler(router: var RestRouter, node: WakuNode) =
router.api(MethodPost, ROUTE_ADMIN_V1_PEERS) do(
contentBody: Option[ContentBody]
) -> RestApiResponse:
let peers: seq[string] = decodeRequestBody[seq[string]](contentBody).valueOr:
let e = $error
return RestApiResponse.badRequest(fmt("Failed to decode request: {e}"))
for i, peer in peers:
let peerInfo = parsePeerInfo(peer).valueOr:
let e = $error
return RestApiResponse.badRequest(fmt("Couldn't parse remote peer info: {e}"))
if not (await node.peerManager.connectPeer(peerInfo, source = "rest")):
return RestApiResponse.badRequest(
fmt("Failed to connect to peer at index: {i} - {peer}")
)
return RestApiResponse.ok()
proc installAdminV1GetFilterSubsHandler(router: var RestRouter, node: WakuNode) =
router.api(MethodGet, ROUTE_ADMIN_V1_FILTER_SUBS) do() -> RestApiResponse:
if node.wakuFilter.isNil():
return
RestApiResponse.badRequest("Error: Filter Protocol is not mounted to the node")
var
subscriptions: seq[FilterSubscription] = @[]
filterCriteria: seq[FilterTopic]
for peerId in node.wakuFilter.subscriptions.peersSubscribed.keys:
filterCriteria = node.wakuFilter.subscriptions.getPeerSubscriptions(peerId).mapIt(
FilterTopic(pubsubTopic: it[0], contentTopic: it[1])
)
subscriptions.add(
FilterSubscription(peerId: $peerId, filterCriteria: filterCriteria)
)
let resp = RestApiResponse.jsonResponse(subscriptions, status = Http200).valueOr:
error "An error ocurred while building the json respose", error = error
return RestApiResponse.internalServerError(
fmt("An error ocurred while building the json respose: {error}")
)
return resp
proc installAdminV1PostLogLevelHandler(router: var RestRouter, node: WakuNode) =
router.api(MethodPost, ROUTE_ADMIN_V1_POST_LOG_LEVEL) do(
logLevel: string
) -> RestApiResponse:
when runtimeFilteringEnabled:
if logLevel.isErr() or logLevel.value().isEmptyOrWhitespace():
return RestApiResponse.badRequest("Invalid log-level, it cant be empty")
try:
let newLogLevel = parseEnum[LogLevel](logLevel.value().capitalizeAscii())
if newLogLevel < enabledLogLevel:
return RestApiResponse.badRequest(
fmt(
"Log level {newLogLevel} is lower than the lowest log level - {enabledLogLevel} - the binary is compiled with."
)
)
setLogLevel(newLogLevel)
except ValueError:
return RestApiResponse.badRequest(
fmt(
"Invalid log-level: {logLevel.value()}. Please specify one of TRACE, DEBUG, INFO, NOTICE, WARN, ERROR or FATAL"
)
)
return RestApiResponse.ok()
else:
return RestApiResponse.serviceUnavailable(
"Dynamic Log level management is not enabled in this build. Please recompile with `-d:chronicles_runtime_filtering:on`."
)
proc installAdminApiHandlers*(router: var RestRouter, node: WakuNode) =
installAdminV1GetPeersHandler(router, node)
installAdminV1PostPeersHandler(router, node)
installAdminV1GetFilterSubsHandler(router, node)
installAdminV1PostLogLevelHandler(router, node)