2022-09-20 11:03:34 +00:00
import
std / [ options , sets , tables , sequtils , random ] ,
stew / results ,
chronicles ,
chronos ,
metrics ,
libp2p / protocols / protocol ,
libp2p / crypto / crypto ,
eth / p2p / discoveryv5 / enr
import
2023-02-06 09:03:30 +00:00
.. / .. / node / peer_manager ,
2022-09-20 11:03:34 +00:00
.. / .. / node / discv5 / waku_discv5 ,
.. / waku_message ,
.. / waku_relay ,
. / rpc ,
. / rpc_codec
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 "
declarePublicGauge waku_px_peers_sent , " number of ENRs sent to peer exchange requesters "
declarePublicGauge waku_px_peers_cached , " number of peer exchange peer ENRs cached "
declarePublicGauge waku_px_errors , " number of peer exchange errors " , [ " type " ]
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
2023-02-09 15:59:29 +00:00
MaxRpcSize * = 10 * MaxWakuMessageSize + 64 * 1024 # TODO what is the expected size of a PX message? As currently specified, it can contain an arbitary number of ENRs...
2022-09-20 11:03:34 +00:00
MaxCacheSize = 1000
CacheCleanWindow = 200
WakuPeerExchangeCodec * = " /vac/waku/peer-exchange/2.0.0-alpha1 "
# Error types (metric label values)
const
dialFailure = " dial_failure "
peerNotFoundFailure = " peer_not_found_failure "
decodeRpcFailure = " decode_rpc_failure "
retrievePeersDiscv5Error = " retrieve_peers_discv5_failure "
pxFailure = " px_failure "
type
WakuPeerExchangeResult * [ T ] = Result [ T , string ]
WakuPeerExchange * = ref object of LPProtocol
peerManager * : PeerManager
wakuDiscv5 : Option [ WakuDiscoveryV5 ]
2023-02-09 15:59:29 +00:00
enrCache * : seq [ enr . Record ] # todo: next step: ring buffer; future: implement cache satisfying https://rfc.vac.dev/spec/34/
2022-09-20 11:03:34 +00:00
2023-02-09 15:59:29 +00:00
proc request * ( wpx : WakuPeerExchange , numPeers : uint64 , conn : Connection ) : Future [ WakuPeerExchangeResult [ PeerExchangeResponse ] ] {. async , gcsafe . } =
let rpc = PeerExchangeRpc (
request : PeerExchangeRequest ( numPeers : numPeers ) )
var buffer : seq [ byte ]
try :
await conn . writeLP ( rpc . encode ( ) . buffer )
buffer = await conn . readLp ( MaxRpcSize . int )
except CatchableError as exc :
waku_px_errors . inc ( labelValues = [ exc . msg ] )
return err ( " write/read failed: " & $ exc . msg )
let decodedBuff = PeerExchangeRpc . decode ( buffer )
if decodedBuff . isErr ( ) :
return err ( " decode failed: " & $ decodedBuff . error )
return ok ( decodedBuff . get ( ) . response )
proc request * ( wpx : WakuPeerExchange , numPeers : uint64 , peer : RemotePeerInfo ) : Future [ WakuPeerExchangeResult [ PeerExchangeResponse ] ] {. async , gcsafe . } =
2022-09-20 11:03:34 +00:00
let connOpt = await wpx . peerManager . dialPeer ( peer , WakuPeerExchangeCodec )
if connOpt . isNone ( ) :
return err ( dialFailure )
2023-02-09 15:59:29 +00:00
return await wpx . request ( numPeers , connOpt . get ( ) )
2022-09-20 11:03:34 +00:00
2023-02-09 15:59:29 +00:00
proc request * ( wpx : WakuPeerExchange , numPeers : uint64 ) : Future [ WakuPeerExchangeResult [ PeerExchangeResponse ] ] {. async , gcsafe . } =
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 ] )
return err ( peerNotFoundFailure )
return await wpx . request ( numPeers , peerOpt . get ( ) )
2023-02-09 15:59:29 +00:00
proc respond ( wpx : WakuPeerExchange , enrs : seq [ enr . Record ] , conn : Connection ) : Future [ WakuPeerExchangeResult [ void ] ] {. async , gcsafe . } =
2022-09-20 11:03:34 +00:00
let rpc = PeerExchangeRpc (
response : PeerExchangeResponse (
2023-02-09 15:59:29 +00:00
peerInfos : 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 ] )
return err ( exc . msg )
2022-09-20 11:03:34 +00:00
return ok ( )
2022-11-02 08:45:21 +00:00
proc cleanCache ( wpx : WakuPeerExchange ) {. gcsafe . } =
wpx . enrCache . delete ( 0 .. CacheCleanWindow - 1 )
2022-09-20 11:03:34 +00:00
2022-11-02 08:45:21 +00:00
proc runPeerExchangeDiscv5Loop * ( wpx : WakuPeerExchange ) {. async , gcsafe . } =
2022-09-20 11:03:34 +00:00
## Runs a discv5 loop adding new peers to the px peer cache
2022-11-02 08:45:21 +00:00
if wpx . wakuDiscv5 . isNone ( ) :
2022-09-20 11:03:34 +00:00
warn " Trying to run discovery v5 (for PX) while it ' s disabled "
return
info " Starting peer exchange discovery v5 loop "
2022-11-02 08:45:21 +00:00
while wpx . wakuDiscv5 . get ( ) . listening :
2022-09-20 11:03:34 +00:00
trace " Running px discv5 discovery loop "
2022-11-02 08:45:21 +00:00
let discoveredPeers = await wpx . wakuDiscv5 . get ( ) . findRandomPeers ( )
2022-09-20 11:03:34 +00:00
info " Discovered px peers via discv5 " , count = discoveredPeers . get ( ) . len ( )
2022-11-02 08:45:21 +00:00
if discoveredPeers . isOk ( ) :
2022-09-20 11:03:34 +00:00
for dp in discoveredPeers . get ( ) :
2022-11-02 08:45:21 +00:00
if dp . enr . isSome ( ) and not wpx . enrCache . contains ( dp . enr . get ( ) ) :
wpx . enrCache . add ( dp . enr . get ( ) )
2022-09-20 11:03:34 +00:00
2022-11-02 08:45:21 +00:00
if wpx . enrCache . len ( ) > = MaxCacheSize :
wpx . cleanCache ( )
2022-09-20 11:03:34 +00:00
## This loop "competes" with the loop in wakunode2
## For the purpose of collecting px peers, 30 sec intervals should be enough
await sleepAsync ( 30 . seconds )
2022-11-02 08:45:21 +00:00
proc getEnrsFromCache ( wpx : WakuPeerExchange , numPeers : uint64 ) : seq [ enr . Record ] {. gcsafe . } =
2022-09-20 11:03:34 +00:00
randomize ( )
2022-11-02 08:45:21 +00:00
if wpx . enrCache . len ( ) = = 0 :
2022-09-20 11:03:34 +00:00
debug " peer exchange ENR cache is empty "
return @ [ ]
2022-11-02 08:45:21 +00:00
for i in 0 .. < min ( numPeers , wpx . enrCache . len ( ) . uint64 ( ) ) :
let ri = rand ( 0 .. < wpx . enrCache . len ( ) )
2023-02-09 15:59:29 +00:00
# TODO: Note that duplicated peers can be returned here
2022-11-02 08:45:21 +00:00
result . add ( wpx . enrCache [ ri ] )
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 ]
try :
buffer = await conn . readLp ( MaxRpcSize . int )
except CatchableError as exc :
waku_px_errors . inc ( labelValues = [ exc . msg ] )
return
2022-09-20 11:03:34 +00:00
2023-02-09 15:59:29 +00:00
let decBuf = PeerExchangeRpc . decode ( buffer )
if decBuf . isErr ( ) :
2022-09-20 11:03:34 +00:00
waku_px_errors . inc ( labelValues = [ decodeRpcFailure ] )
return
2023-02-09 15:59:29 +00:00
let rpc = decBuf . get ( )
trace " peer exchange request received "
let enrs = wpx . getEnrsFromCache ( rpc . request . numPeers )
let res = await wpx . respond ( enrs , conn )
if res . isErr :
waku_px_errors . inc ( labelValues = [ res . error ] )
else :
2022-09-20 11:03:34 +00:00
waku_px_peers_sent . inc ( enrs . len ( ) . int64 ( ) )
2022-11-02 08:45:21 +00:00
wpx . handler = handler
wpx . codec = WakuPeerExchangeCodec
2022-09-20 11:03:34 +00:00
2022-11-02 08:45:21 +00:00
proc new * ( T : type WakuPeerExchange ,
peerManager : PeerManager ,
wakuDiscv5 : Option [ WakuDiscoveryV5 ] = none ( WakuDiscoveryV5 ) ) : T =
let wpx = WakuPeerExchange (
2022-09-20 11:03:34 +00:00
peerManager : peerManager ,
wakuDiscv5 : wakuDiscv5
)
2022-11-02 08:45:21 +00:00
wpx . initProtocolHandler ( )
return wpx