2022-09-20 11:03:34 +00:00
|
|
|
|
import
|
2024-10-10 12:40:09 +00:00
|
|
|
|
std/[options, sequtils, random],
|
2024-07-09 11:14:28 +00:00
|
|
|
|
results,
|
2022-09-20 11:03:34 +00:00
|
|
|
|
chronicles,
|
|
|
|
|
chronos,
|
|
|
|
|
metrics,
|
|
|
|
|
libp2p/protocols/protocol,
|
|
|
|
|
libp2p/crypto/crypto,
|
|
|
|
|
eth/p2p/discoveryv5/enr
|
|
|
|
|
import
|
2023-08-09 17:11:50 +00:00
|
|
|
|
../common/nimchronos,
|
2023-04-18 13:22:10 +00:00
|
|
|
|
../node/peer_manager,
|
2023-04-19 11:29:23 +00:00
|
|
|
|
../waku_core,
|
2024-04-17 19:48:20 +00:00
|
|
|
|
../discovery/waku_discv5,
|
2022-09-20 11:03:34 +00:00
|
|
|
|
./rpc,
|
2024-09-18 13:58:07 +00:00
|
|
|
|
./rpc_codec,
|
|
|
|
|
../common/rate_limit/request_limiter
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2024-10-24 12:31:04 +00:00
|
|
|
|
from ../waku_core/codecs import WakuPeerExchangeCodec
|
|
|
|
|
export WakuPeerExchangeCodec
|
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
|
declarePublicGauge waku_px_peers_received_total,
|
|
|
|
|
"number of ENRs received via peer exchange"
|
|
|
|
|
declarePublicGauge waku_px_peers_received_unknown,
|
|
|
|
|
"number of previously unknown ENRs received via peer exchange"
|
2024-09-24 10:47:52 +00:00
|
|
|
|
declarePublicCounter waku_px_peers_sent,
|
|
|
|
|
"number of ENRs sent to peer exchange requesters"
|
2022-09-20 11:03:34 +00:00
|
|
|
|
declarePublicGauge waku_px_peers_cached, "number of peer exchange peer ENRs cached"
|
2024-09-24 10:47:52 +00:00
|
|
|
|
declarePublicCounter waku_px_errors, "number of peer exchange errors", ["type"]
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
|
|
|
|
logScope:
|
2022-11-03 15:36:24 +00:00
|
|
|
|
topics = "waku peer_exchange"
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
|
|
|
|
const
|
|
|
|
|
# We add a 64kB safety buffer for protocol overhead.
|
|
|
|
|
# 10x-multiplier also for safety
|
2024-04-20 03:40:52 +00:00
|
|
|
|
DefaultMaxRpcSize* = 10 * DefaultMaxWakuMessageSize + 64 * 1024
|
2024-03-15 23:08:47 +00:00
|
|
|
|
# TODO what is the expected size of a PX message? As currently specified, it can contain an arbitary number of ENRs...
|
2023-04-19 14:12:00 +00:00
|
|
|
|
MaxPeersCacheSize = 60
|
2024-08-23 18:01:30 +00:00
|
|
|
|
CacheRefreshInterval = 10.minutes
|
2024-10-16 14:04:27 +00:00
|
|
|
|
DefaultPXNumPeersReq* = 5.uint64()
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
|
|
|
|
# Error types (metric label values)
|
|
|
|
|
const
|
|
|
|
|
dialFailure = "dial_failure"
|
|
|
|
|
peerNotFoundFailure = "peer_not_found_failure"
|
|
|
|
|
decodeRpcFailure = "decode_rpc_failure"
|
2024-03-15 23:08:47 +00:00
|
|
|
|
retrievePeersDiscv5Error = "retrieve_peers_discv5_failure"
|
2022-09-20 11:03:34 +00:00
|
|
|
|
pxFailure = "px_failure"
|
|
|
|
|
|
|
|
|
|
type
|
2024-09-18 13:58:07 +00:00
|
|
|
|
WakuPeerExchangeResult*[T] = Result[T, PeerExchangeResponseStatus]
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
|
|
|
|
WakuPeerExchange* = ref object of LPProtocol
|
|
|
|
|
peerManager*: PeerManager
|
2024-03-15 23:08:47 +00:00
|
|
|
|
enrCache*: seq[enr.Record]
|
2024-07-29 19:53:43 +00:00
|
|
|
|
cluster*: Option[uint16]
|
2024-03-15 23:08:47 +00:00
|
|
|
|
# todo: next step: ring buffer; future: implement cache satisfying https://rfc.vac.dev/spec/34/
|
2024-09-18 13:58:07 +00:00
|
|
|
|
requestRateLimiter*: RequestRateLimiter
|
2024-10-30 10:51:04 +00:00
|
|
|
|
pxLoopHandle*: Future[void]
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
|
proc request*(
|
2024-10-16 14:04:27 +00:00
|
|
|
|
wpx: WakuPeerExchange, numPeers = DefaultPXNumPeersReq, conn: Connection
|
2024-05-03 12:07:15 +00:00
|
|
|
|
): Future[WakuPeerExchangeResult[PeerExchangeResponse]] {.async: (raises: []).} =
|
2024-09-18 13:58:07 +00:00
|
|
|
|
let rpc = PeerExchangeRpc.makeRequest(numPeers)
|
2023-02-09 15:59:29 +00:00
|
|
|
|
|
|
|
|
|
var buffer: seq[byte]
|
2024-09-18 13:58:07 +00:00
|
|
|
|
var callResult =
|
|
|
|
|
(status_code: PeerExchangeResponseStatusCode.SUCCESS, status_desc: none(string))
|
2023-02-09 15:59:29 +00:00
|
|
|
|
try:
|
|
|
|
|
await conn.writeLP(rpc.encode().buffer)
|
2024-04-20 03:40:52 +00:00
|
|
|
|
buffer = await conn.readLp(DefaultMaxRpcSize.int)
|
2023-02-09 15:59:29 +00:00
|
|
|
|
except CatchableError as exc:
|
|
|
|
|
waku_px_errors.inc(labelValues = [exc.msg])
|
2024-09-18 13:58:07 +00:00
|
|
|
|
callResult = (
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE,
|
|
|
|
|
status_desc: some($exc.msg),
|
|
|
|
|
)
|
2023-05-25 14:42:48 +00:00
|
|
|
|
finally:
|
|
|
|
|
# close, no more data is expected
|
|
|
|
|
await conn.closeWithEof()
|
|
|
|
|
|
2024-09-18 13:58:07 +00:00
|
|
|
|
if callResult.status_code != PeerExchangeResponseStatusCode.SUCCESS:
|
|
|
|
|
return err(callResult)
|
2023-02-09 15:59:29 +00:00
|
|
|
|
|
|
|
|
|
let decodedBuff = PeerExchangeRpc.decode(buffer)
|
|
|
|
|
if decodedBuff.isErr():
|
2024-09-18 13:58:07 +00:00
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.BAD_RESPONSE,
|
|
|
|
|
status_desc: some($decodedBuff.error),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if decodedBuff.get().response.status_code != PeerExchangeResponseStatusCode.SUCCESS:
|
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: decodedBuff.get().response.status_code,
|
|
|
|
|
status_desc: decodedBuff.get().response.status_desc,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2023-02-09 15:59:29 +00:00
|
|
|
|
return ok(decodedBuff.get().response)
|
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
|
proc request*(
|
2024-10-16 14:04:27 +00:00
|
|
|
|
wpx: WakuPeerExchange, numPeers = DefaultPXNumPeersReq, peer: RemotePeerInfo
|
2024-05-03 12:07:15 +00:00
|
|
|
|
): Future[WakuPeerExchangeResult[PeerExchangeResponse]] {.async: (raises: []).} =
|
|
|
|
|
try:
|
|
|
|
|
let connOpt = await wpx.peerManager.dialPeer(peer, WakuPeerExchangeCodec)
|
|
|
|
|
if connOpt.isNone():
|
2024-09-18 13:58:07 +00:00
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.DIAL_FAILURE,
|
|
|
|
|
status_desc: some(dialFailure),
|
|
|
|
|
)
|
|
|
|
|
)
|
2024-05-03 12:07:15 +00:00
|
|
|
|
return await wpx.request(numPeers, connOpt.get())
|
|
|
|
|
except CatchableError:
|
2024-09-18 13:58:07 +00:00
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.BAD_RESPONSE,
|
|
|
|
|
status_desc: some("exception dialing peer: " & getCurrentExceptionMsg()),
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
|
proc request*(
|
2024-10-16 14:04:27 +00:00
|
|
|
|
wpx: WakuPeerExchange, numPeers = DefaultPXNumPeersReq
|
2024-05-03 12:07:15 +00:00
|
|
|
|
): Future[WakuPeerExchangeResult[PeerExchangeResponse]] {.async: (raises: []).} =
|
2023-01-26 09:20:20 +00:00
|
|
|
|
let peerOpt = wpx.peerManager.selectPeer(WakuPeerExchangeCodec)
|
2022-09-20 11:03:34 +00:00
|
|
|
|
if peerOpt.isNone():
|
|
|
|
|
waku_px_errors.inc(labelValues = [peerNotFoundFailure])
|
2024-09-18 13:58:07 +00:00
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE,
|
|
|
|
|
status_desc: some(peerNotFoundFailure),
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-09-20 11:03:34 +00:00
|
|
|
|
return await wpx.request(numPeers, peerOpt.get())
|
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
|
proc respond(
|
|
|
|
|
wpx: WakuPeerExchange, enrs: seq[enr.Record], conn: Connection
|
|
|
|
|
): Future[WakuPeerExchangeResult[void]] {.async, gcsafe.} =
|
2024-09-18 13:58:07 +00:00
|
|
|
|
let rpc = PeerExchangeRpc.makeResponse(enrs.mapIt(PeerExchangePeerInfo(enr: it.raw)))
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2023-02-09 15:59:29 +00:00
|
|
|
|
try:
|
|
|
|
|
await conn.writeLP(rpc.encode().buffer)
|
|
|
|
|
except CatchableError as exc:
|
|
|
|
|
waku_px_errors.inc(labelValues = [exc.msg])
|
2024-09-18 13:58:07 +00:00
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.DIAL_FAILURE,
|
|
|
|
|
status_desc: some("exception dialing peer: " & exc.msg),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return ok()
|
|
|
|
|
|
|
|
|
|
proc respondError(
|
|
|
|
|
wpx: WakuPeerExchange,
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode,
|
|
|
|
|
status_desc: Option[string],
|
|
|
|
|
conn: Connection,
|
|
|
|
|
): Future[WakuPeerExchangeResult[void]] {.async, gcsafe.} =
|
|
|
|
|
let rpc = PeerExchangeRpc.makeErrorResponse(status_code, status_desc)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await conn.writeLP(rpc.encode().buffer)
|
|
|
|
|
except CatchableError as exc:
|
|
|
|
|
waku_px_errors.inc(labelValues = [exc.msg])
|
|
|
|
|
return err(
|
|
|
|
|
(
|
|
|
|
|
status_code: PeerExchangeResponseStatusCode.SERVICE_UNAVAILABLE,
|
|
|
|
|
status_desc: some("exception dialing peer: " & exc.msg),
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
|
|
|
|
return ok()
|
|
|
|
|
|
2024-03-15 23:08:47 +00:00
|
|
|
|
proc getEnrsFromCache(
|
|
|
|
|
wpx: WakuPeerExchange, numPeers: uint64
|
|
|
|
|
): seq[enr.Record] {.gcsafe.} =
|
2023-04-19 14:12:00 +00:00
|
|
|
|
if wpx.enrCache.len() == 0:
|
|
|
|
|
debug "peer exchange ENR cache is empty"
|
|
|
|
|
return @[]
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2023-04-19 14:12:00 +00:00
|
|
|
|
# copy and shuffle
|
|
|
|
|
randomize()
|
|
|
|
|
var shuffledCache = wpx.enrCache
|
|
|
|
|
shuffledCache.shuffle()
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2023-04-19 14:12:00 +00:00
|
|
|
|
# return numPeers or less if cache is smaller
|
2024-03-15 23:08:47 +00:00
|
|
|
|
return shuffledCache[0 ..< min(shuffledCache.len.int, numPeers.int)]
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2024-07-29 19:53:43 +00:00
|
|
|
|
proc poolFilter*(cluster: Option[uint16], peer: RemotePeerInfo): bool =
|
|
|
|
|
if peer.origin != Discv5:
|
|
|
|
|
trace "peer not from discv5", peer = $peer, origin = $peer.origin
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
if peer.enr.isNone():
|
|
|
|
|
trace "peer has no ENR", peer = $peer
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
if cluster.isSome() and peer.enr.get().isClusterMismatched(cluster.get()):
|
|
|
|
|
trace "peer has mismatching cluster", peer = $peer
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
2023-04-19 14:12:00 +00:00
|
|
|
|
proc populateEnrCache(wpx: WakuPeerExchange) =
|
2024-07-29 19:53:43 +00:00
|
|
|
|
# share only peers that i) are reachable ii) come from discv5 iii) share cluster
|
2024-09-27 12:46:46 +00:00
|
|
|
|
let withEnr = wpx.peerManager.wakuPeerStore.getReachablePeers().filterIt(
|
|
|
|
|
poolFilter(wpx.cluster, it)
|
|
|
|
|
)
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2023-04-19 14:12:00 +00:00
|
|
|
|
# either what we have or max cache size
|
|
|
|
|
var newEnrCache = newSeq[enr.Record](0)
|
2024-03-15 23:08:47 +00:00
|
|
|
|
for i in 0 ..< min(withEnr.len, MaxPeersCacheSize):
|
2023-04-19 14:12:00 +00:00
|
|
|
|
newEnrCache.add(withEnr[i].enr.get())
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2023-04-19 14:12:00 +00:00
|
|
|
|
# swap cache for new
|
|
|
|
|
wpx.enrCache = newEnrCache
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2023-04-19 14:12:00 +00:00
|
|
|
|
proc updatePxEnrCache(wpx: WakuPeerExchange) {.async.} =
|
|
|
|
|
# try more aggressively to fill the cache at startup
|
2024-10-24 07:33:32 +00:00
|
|
|
|
var attempts = 50
|
2024-09-25 09:51:50 +00:00
|
|
|
|
while wpx.enrCache.len < MaxPeersCacheSize and attempts > 0:
|
|
|
|
|
attempts -= 1
|
2023-04-19 14:12:00 +00:00
|
|
|
|
wpx.populateEnrCache()
|
2024-10-24 07:33:32 +00:00
|
|
|
|
await sleepAsync(1.seconds)
|
2023-04-19 14:12:00 +00:00
|
|
|
|
|
|
|
|
|
heartbeat "Updating px enr cache", CacheRefreshInterval:
|
|
|
|
|
wpx.populateEnrCache()
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2022-11-02 08:45:21 +00:00
|
|
|
|
proc initProtocolHandler(wpx: WakuPeerExchange) =
|
2022-09-20 11:03:34 +00:00
|
|
|
|
proc handler(conn: Connection, proto: string) {.async, gcsafe, closure.} =
|
2023-02-09 15:59:29 +00:00
|
|
|
|
var buffer: seq[byte]
|
2024-09-18 13:58:07 +00:00
|
|
|
|
wpx.requestRateLimiter.checkUsageLimit(WakuPeerExchangeCodec, conn):
|
|
|
|
|
try:
|
|
|
|
|
buffer = await conn.readLp(DefaultMaxRpcSize.int)
|
|
|
|
|
except CatchableError as exc:
|
|
|
|
|
waku_px_errors.inc(labelValues = [exc.msg])
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
await wpx.respondError(
|
|
|
|
|
PeerExchangeResponseStatusCode.BAD_REQUEST, some(exc.msg), conn
|
|
|
|
|
)
|
|
|
|
|
).isOkOr:
|
|
|
|
|
error "Failed to respond with BAD_REQUEST:", error = $error
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
let decBuf = PeerExchangeRpc.decode(buffer)
|
|
|
|
|
if decBuf.isErr():
|
|
|
|
|
waku_px_errors.inc(labelValues = [decodeRpcFailure])
|
|
|
|
|
error "Failed to decode PeerExchange request", error = $decBuf.error
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
await wpx.respondError(
|
|
|
|
|
PeerExchangeResponseStatusCode.BAD_REQUEST, some($decBuf.error), conn
|
|
|
|
|
)
|
|
|
|
|
).isOkOr:
|
|
|
|
|
error "Failed to respond with BAD_REQUEST:", error = $error
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
trace "peer exchange request received"
|
|
|
|
|
let enrs = wpx.getEnrsFromCache(decBuf.get().request.numPeers)
|
|
|
|
|
(await wpx.respond(enrs, conn)).isErrOr:
|
|
|
|
|
waku_px_peers_sent.inc(enrs.len().int64())
|
|
|
|
|
do:
|
|
|
|
|
(
|
|
|
|
|
await wpx.respondError(
|
|
|
|
|
PeerExchangeResponseStatusCode.TOO_MANY_REQUESTS, none(string), conn
|
|
|
|
|
)
|
|
|
|
|
).isOkOr:
|
|
|
|
|
error "Failed to respond with TOO_MANY_REQUESTS:", error = $error
|
2023-05-25 14:42:48 +00:00
|
|
|
|
# close, no data is expected
|
|
|
|
|
await conn.closeWithEof()
|
|
|
|
|
|
2022-11-02 08:45:21 +00:00
|
|
|
|
wpx.handler = handler
|
|
|
|
|
wpx.codec = WakuPeerExchangeCodec
|
2022-09-20 11:03:34 +00:00
|
|
|
|
|
2024-07-29 19:53:43 +00:00
|
|
|
|
proc new*(
|
|
|
|
|
T: type WakuPeerExchange,
|
|
|
|
|
peerManager: PeerManager,
|
|
|
|
|
cluster: Option[uint16] = none(uint16),
|
2024-09-18 13:58:07 +00:00
|
|
|
|
rateLimitSetting: Option[RateLimitSetting] = none[RateLimitSetting](),
|
2024-07-29 19:53:43 +00:00
|
|
|
|
): T =
|
2024-09-18 13:58:07 +00:00
|
|
|
|
let wpx = WakuPeerExchange(
|
|
|
|
|
peerManager: peerManager,
|
|
|
|
|
cluster: cluster,
|
|
|
|
|
requestRateLimiter: newRequestRateLimiter(rateLimitSetting),
|
|
|
|
|
)
|
2022-11-02 08:45:21 +00:00
|
|
|
|
wpx.initProtocolHandler()
|
2024-09-18 13:58:07 +00:00
|
|
|
|
setServiceLimitMetric(WakuPeerExchangeCodec, rateLimitSetting)
|
2023-04-19 14:12:00 +00:00
|
|
|
|
asyncSpawn wpx.updatePxEnrCache()
|
2022-11-02 08:45:21 +00:00
|
|
|
|
return wpx
|