nwaku/waku/waku_core/peers.nim
NagyZoltanPeter dac072f843
feat: HTTP REST API: Filter support v2 (#1890)
Filter v2 rest api support implemented 
Filter rest api documentation updated with v1 and v2 interface support.
Separated legacy filter rest interface
Fix code and tests of v2 Filter rest api
Filter v2 message push test added
Applied autoshard to Filter V2
Redesigned FilterPushHandling, code style, catch up apps and tests with filter v2 interface changes
Rename of FilterV1SubscriptionsRequest to FilterLegacySubscribeRequest, fix broken chat2 app, fix tests
Changed Filter v2 push handler subscription to simple register
Separate node's filterUnsubscribe and filterUnsubscribeAll
2023-09-14 21:28:57 +02:00

254 lines
8.0 KiB
Nim

when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import
std/[options, sequtils, strutils, uri],
stew/results,
stew/shims/net,
chronos,
eth/keys,
eth/p2p/discoveryv5/enr,
libp2p/crypto/crypto,
libp2p/crypto/secp,
libp2p/errors,
libp2p/multiaddress,
libp2p/multicodec,
libp2p/peerid,
libp2p/peerinfo,
libp2p/routing_record,
json_serialization
type
Connectedness* = enum
# NotConnected: default state for a new peer. No connection and no further information on connectedness.
NotConnected,
# CannotConnect: attempted to connect to peer, but failed.
CannotConnect,
# CanConnect: was recently connected to peer and disconnected gracefully.
CanConnect,
# Connected: actively connected to peer.
Connected
PeerOrigin* = enum
UnknownOrigin,
Discv5,
Static,
PeerExcahnge,
Dns
PeerDirection* = enum
UnknownDirection,
Inbound,
Outbound
type RemotePeerInfo* = ref object
peerId*: PeerID
addrs*: seq[MultiAddress]
enr*: Option[enr.Record]
protocols*: seq[string]
agent*: string
protoVersion*: string
publicKey*: crypto.PublicKey
connectedness*: Connectedness
disconnectTime*: int64
origin*: PeerOrigin
direction*: PeerDirection
lastFailedConn*: Moment
numberFailedConn*: int
func `$`*(remotePeerInfo: RemotePeerInfo): string =
$remotePeerInfo.peerId
proc writeValue*(w: var JsonWriter, value: RemotePeerInfo) {.inline, raises: [IOError].} =
w.writeValue $value
proc init*(
T: typedesc[RemotePeerInfo],
peerId: PeerID,
addrs: seq[MultiAddress] = @[],
enr: Option[enr.Record] = none(enr.Record),
protocols: seq[string] = @[]): T =
RemotePeerInfo(peerId: peerId, addrs: addrs, enr: enr, protocols: protocols)
proc init*(T: typedesc[RemotePeerInfo],
peerId: string,
addrs: seq[MultiAddress] = @[],
enr: Option[enr.Record] = none(enr.Record),
protocols: seq[string] = @[]): T
{.raises: [Defect, ResultError[cstring], LPError].} =
let peerId = PeerID.init(peerId).tryGet()
RemotePeerInfo(peerId: peerId, addrs: addrs, enr: enr, protocols: protocols)
## Parse
proc validWireAddr*(ma: MultiAddress): bool =
## Check if wire Address is supported
const
ValidTransports = mapOr(TCP, WebSockets)
return ValidTransports.match(ma)
proc parsePeerInfo*(peer: RemotePeerInfo): Result[RemotePeerInfo, string] =
## Parses a fully qualified peer multiaddr, in the
## format `(ip4|ip6)/tcp/p2p`, into dialable PeerInfo
ok(peer)
proc parsePeerInfo*(peer: MultiAddress): Result[RemotePeerInfo, string] =
## Parses a fully qualified peer multiaddr, in the
## format `(ip4|ip6)/tcp/p2p`, into dialable PeerInfo
var p2pPart: MultiAddress
var wireAddr = MultiAddress()
for addrPart in peer.items():
case addrPart[].protoName()[]
# All protocols listed here: https://github.com/multiformats/multiaddr/blob/b746a7d014e825221cc3aea6e57a92d78419990f/protocols.csv
of "p2p":
p2pPart = ? addrPart.mapErr(proc(err: string): string = "Error getting p2pPart [" & err & "]")
of "ip4", "ip6", "dns", "dnsaddr", "dns4", "dns6", "tcp", "ws", "wss":
let val = ? addrPart.mapErr(proc(err: string): string = "Error getting addrPart [" & err & "]")
? wireAddr.append(val).mapErr(proc(err: string): string = "Error appending addrPart [" & err & "]")
let p2pPartStr = p2pPart.toString().get()
if not p2pPartStr.contains("/"):
let msg = "Error in parsePeerInfo: p2p part should contain / [p2pPartStr:" & p2pPartStr & "] [peer:" & $peer & "]"
return err(msg)
let peerId = ? PeerID.init(p2pPartStr.split("/")[^1])
.mapErr(proc(e: cstring): string = $e)
if not wireAddr.validWireAddr():
return err("invalid multiaddress: no supported transport found")
return ok(RemotePeerInfo.init(peerId, @[wireAddr]))
proc parsePeerInfo*(peer: string): Result[RemotePeerInfo, string] =
## Parses a fully qualified peer multiaddr, in the
## format `(ip4|ip6)/tcp/p2p`, into dialable PeerInfo
let multiAddr = ? MultiAddress.init(peer)
.mapErr(proc(err: string): string = "MultiAddress.init [" & err & "]")
parsePeerInfo(multiAddr)
func getTransportProtocol(typedR: TypedRecord): Option[IpTransportProtocol] =
if typedR.tcp6.isSome() or typedR.tcp.isSome():
return some(IpTransportProtocol.tcpProtocol)
if typedR.udp6.isSome() or typedR.udp.isSome():
return some(IpTransportProtocol.udpProtocol)
return none(IpTransportProtocol)
proc parseUrlPeerAddr*(peerAddr: Option[string]): Result[Option[RemotePeerInfo], string] =
# Checks whether the peerAddr parameter represents a valid p2p multiaddress.
# The param must be in the format `(ip4|ip6)/tcp/p2p/$peerId` but URL-encoded
if not peerAddr.isSome() or peerAddr.get() == "":
return ok(none(RemotePeerInfo))
let parsedAddr = decodeUrl(peerAddr.get())
let parsedPeerInfo = parsePeerInfo(parsedAddr)
if parsedPeerInfo.isErr():
return err("Failed parsing remote peer info [" & parsedPeerInfo.error & "]")
return ok(some(parsedPeerInfo.value))
proc toRemotePeerInfo*(enr: enr.Record): Result[RemotePeerInfo, cstring] =
## Converts an ENR to dialable RemotePeerInfo
let typedR = ? enr.toTypedRecord()
if not typedR.secp256k1.isSome():
return err("enr: no secp256k1 key in record")
let
pubKey = ? keys.PublicKey.fromRaw(typedR.secp256k1.get())
peerId = ? PeerID.init(crypto.PublicKey(scheme: Secp256k1,
skkey: secp.SkPublicKey(pubKey)))
let transportProto = getTransportProtocol(typedR)
if transportProto.isNone():
return err("enr: could not determine transport protocol")
var addrs = newSeq[MultiAddress]()
case transportProto.get()
of tcpProtocol:
if typedR.ip.isSome() and typedR.tcp.isSome():
let ip = ipv4(typedR.ip.get())
addrs.add MultiAddress.init(ip, tcpProtocol, Port(typedR.tcp.get()))
if typedR.ip6.isSome():
let ip = ipv6(typedR.ip6.get())
if typedR.tcp6.isSome():
addrs.add MultiAddress.init(ip, tcpProtocol, Port(typedR.tcp6.get()))
elif typedR.tcp.isSome():
addrs.add MultiAddress.init(ip, tcpProtocol, Port(typedR.tcp.get()))
else:
discard
of udpProtocol:
if typedR.ip.isSome() and typedR.udp.isSome():
let ip = ipv4(typedR.ip.get())
addrs.add MultiAddress.init(ip, udpProtocol, Port(typedR.udp.get()))
if typedR.ip6.isSome():
let ip = ipv6(typedR.ip6.get())
if typedR.udp6.isSome():
addrs.add MultiAddress.init(ip, udpProtocol, Port(typedR.udp6.get()))
elif typedR.udp.isSome():
addrs.add MultiAddress.init(ip, udpProtocol, Port(typedR.udp.get()))
else:
discard
if addrs.len == 0:
return err("enr: no addresses in record")
return ok(RemotePeerInfo.init(peerId, addrs, some(enr)))
converter toRemotePeerInfo*(peerRecord: PeerRecord): RemotePeerInfo =
## Converts peer records to dialable RemotePeerInfo
## Useful if signed peer records have been received in an exchange
RemotePeerInfo.init(
peerRecord.peerId,
peerRecord.addresses.mapIt(it.address)
)
converter toRemotePeerInfo*(peerInfo: PeerInfo): RemotePeerInfo =
## Converts the local peerInfo to dialable RemotePeerInfo
## Useful for testing or internal connections
RemotePeerInfo.init(
peerInfo.peerId,
peerInfo.listenAddrs,
none(enr.Record),
peerInfo.protocols
)
proc hasProtocol*(ma: MultiAddress, proto: string): bool =
## Checks if a multiaddress contains a given protocol
## Useful for filtering multiaddresses based on their protocols
##
## Returns ``true`` if ``ma`` contains protocol ``proto``.
let proto = MultiCodec.codec(proto)
let protos = ma.protocols()
if protos.isErr():
return false
return protos.get().anyIt(it == proto)
func hasUdpPort*(peer: RemotePeerInfo): bool =
if peer.enr.isNone():
return false
let
enr = peer.enr.get()
typedEnrRes = enr.toTypedRecord()
if typedEnrRes.isErr():
return false
let typedEnr = typedEnrRes.get()
typedEnr.udp.isSome() or typedEnr.udp6.isSome()