2021-03-26 06:52:01 +00:00
|
|
|
# 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].}
|
|
|
|
|
2022-01-08 20:06:34 +00:00
|
|
|
import
|
|
|
|
std/[options, sequtils],
|
|
|
|
stew/byteutils,
|
2020-10-27 09:00:57 +00:00
|
|
|
chronicles,
|
2021-03-26 14:11:06 +00:00
|
|
|
json_rpc/servers/httpserver,
|
2020-11-26 19:23:45 +00:00
|
|
|
eth/p2p/discoveryv5/enr,
|
2021-10-21 11:01:29 +00:00
|
|
|
libp2p/[multiaddress, multicodec, peerstore],
|
2021-10-19 14:09:26 +00:00
|
|
|
../beacon_node, ../version,
|
2021-03-05 13:12:00 +00:00
|
|
|
../networking/[eth2_network, peer_pool],
|
|
|
|
../sync/sync_manager,
|
2021-06-23 14:43:18 +00:00
|
|
|
../spec/datatypes/base,
|
2021-08-03 15:17:11 +00:00
|
|
|
./rpc_utils
|
2020-10-27 09:00:57 +00:00
|
|
|
|
|
|
|
logScope: topics = "nodeapi"
|
|
|
|
|
|
|
|
type
|
|
|
|
RpcServer = RpcHttpServer
|
|
|
|
|
2020-11-26 19:23:45 +00:00
|
|
|
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"
|
|
|
|
|
2021-10-21 11:01:29 +00:00
|
|
|
proc getLastSeenAddress(node: BeaconNode, id: PeerId): string =
|
2020-11-26 19:23: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.
|
2021-10-21 11:01:29 +00:00
|
|
|
let addrs = node.network.switch.peerStore.addressBook.get(id).toSeq()
|
|
|
|
if len(addrs) > 0:
|
|
|
|
$addrs[len(addrs) - 1]
|
2020-11-26 19:23:45 +00:00
|
|
|
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)
|
|
|
|
|
2021-03-26 06:52:01 +00:00
|
|
|
proc installNodeApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
|
2021-08-27 09:00:06 +00:00
|
|
|
raises: [Defect, CatchableError].} =
|
2021-08-03 15:17:11 +00:00
|
|
|
rpcServer.rpc("get_v1_node_identity") do () -> RpcNodeIdentity:
|
2020-11-26 19:23:45 +00:00
|
|
|
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]()
|
|
|
|
|
2020-10-27 09:00:57 +00:00
|
|
|
return (
|
2020-11-26 19:23:45 +00:00
|
|
|
peer_id: $node.network.peerId(),
|
|
|
|
enr: node.network.enrRecord().toUri(),
|
|
|
|
p2p_addresses: p2pAddresses,
|
|
|
|
discovery_addresses: discoveryAddresses,
|
|
|
|
metadata: (node.network.metadata.seq_number,
|
2022-01-08 20:06:34 +00:00
|
|
|
to0xHex(node.network.metadata.attnets.bytes))
|
2020-10-27 09:00:57 +00:00
|
|
|
)
|
|
|
|
|
2020-11-26 19:23:45 +00:00
|
|
|
rpcServer.rpc("get_v1_node_peers") do (state: Option[seq[string]],
|
2021-08-03 15:17:11 +00:00
|
|
|
direction: Option[seq[string]]) -> seq[RpcNodePeer]:
|
|
|
|
var res = newSeq[RpcNodePeer]()
|
2020-11-26 19:23:45 +00:00
|
|
|
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()
|
2022-01-21 14:50:03 +00:00
|
|
|
let scores = node.network.getPeersScores()
|
2021-10-21 11:01:29 +00:00
|
|
|
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(),
|
2022-01-21 14:50:03 +00:00
|
|
|
agent: node.network.switch.peerStore.agentBook.get(peer.peerId), # Fields `agent`, `proto` and `score`
|
|
|
|
proto: node.network.switch.peerStore.protoVersionBook.get(peer.peerId),# are not part of specification
|
|
|
|
score: scores.getOrDefault(peer.peerId)
|
2020-11-26 19:23:45 +00:00
|
|
|
)
|
2021-10-21 11:01:29 +00:00
|
|
|
res.add(resPeer)
|
2020-11-26 19:23:45 +00:00
|
|
|
return res
|
2020-10-27 09:00:57 +00:00
|
|
|
|
2021-08-03 15:17:11 +00:00
|
|
|
rpcServer.rpc("get_v1_node_peer_count") do () -> RpcNodePeerCount:
|
|
|
|
var res: RpcNodePeerCount
|
2020-11-26 19:23:45 +00:00
|
|
|
for item in node.network.peers.values():
|
|
|
|
case item.connectionState
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Connecting:
|
2020-11-26 19:23:45 +00:00
|
|
|
inc(res.connecting)
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Connected:
|
2020-11-26 19:23:45 +00:00
|
|
|
inc(res.connected)
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Disconnecting:
|
2020-11-26 19:23:45 +00:00
|
|
|
inc(res.disconnecting)
|
2021-07-19 11:58:30 +00:00
|
|
|
of ConnectionState.Disconnected:
|
2020-11-26 19:23:45 +00:00
|
|
|
inc(res.disconnected)
|
|
|
|
of ConnectionState.None:
|
|
|
|
discard
|
|
|
|
return res
|
|
|
|
|
|
|
|
rpcServer.rpc("get_v1_node_peers_peerId") do (
|
2021-08-03 15:17:11 +00:00
|
|
|
peer_id: string) -> RpcNodePeer:
|
2020-11-26 19:23:45 +00:00
|
|
|
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 (
|
2021-10-21 11:01:29 +00:00
|
|
|
peer_id: $peer.peerId,
|
2020-11-26 19:23:45 +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),
|
2020-11-26 19:23:45 +00:00
|
|
|
state: peer.connectionState.toString(),
|
|
|
|
direction: peer.direction.toString(),
|
2022-01-21 14:50:03 +00:00
|
|
|
agent: node.network.switch.peerStore.agentBook.get(peer.peerId), # Fields `agent`, `proto` and `score`
|
|
|
|
proto: node.network.switch.peerStore.protoVersionBook.get(peer.peerId),# are not part of specification
|
|
|
|
score: -1 # Isn't available for this call
|
2020-11-26 19:23:45 +00:00
|
|
|
)
|
2020-10-27 09:00:57 +00:00
|
|
|
|
|
|
|
rpcServer.rpc("get_v1_node_version") do () -> JsonNode:
|
2020-11-26 19:23:45 +00:00
|
|
|
return %*{"version": "Nimbus/" & fullVersionStr}
|
2020-10-27 09:00:57 +00:00
|
|
|
|
2021-08-03 15:17:11 +00:00
|
|
|
rpcServer.rpc("get_v1_node_syncing") do () -> RpcSyncInfo:
|
2020-11-26 19:23:45 +00:00
|
|
|
return node.syncManager.getInfo()
|
2020-10-27 09:00:57 +00:00
|
|
|
|
|
|
|
rpcServer.rpc("get_v1_node_health") do () -> JsonNode:
|
2020-11-26 19:23:45 +00:00
|
|
|
# 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}
|