2022-09-20 11:03:34 +00:00
import
2023-02-13 14:02:34 +00:00
std / [ options , sequtils , random ] ,
2022-09-20 11:03:34 +00:00
stew / results ,
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 ,
2023-03-06 16:18:41 +00:00
.. / waku_discv5 ,
2022-09-20 11:03:34 +00:00
. / 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...
2023-04-19 14:12:00 +00:00
MaxPeersCacheSize = 60
CacheRefreshInterval = 15 . minutes
2022-09-20 11:03:34 +00:00
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
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 ]
2023-05-25 14:42:48 +00:00
var error : string
2023-02-09 15:59:29 +00:00
try :
await conn . writeLP ( rpc . encode ( ) . buffer )
buffer = await conn . readLp ( MaxRpcSize . int )
except CatchableError as exc :
waku_px_errors . inc ( labelValues = [ exc . msg ] )
2023-05-25 14:42:48 +00:00
error = $ exc . msg
finally :
# close, no more data is expected
await conn . closeWithEof ( )
if error . len > 0 :
return err ( " write/read failed: " & error )
2023-02-09 15:59:29 +00:00
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 ( )
2023-04-19 14:12:00 +00:00
proc getEnrsFromCache ( wpx : WakuPeerExchange , numPeers : uint64 ) : seq [ enr . Record ] {. gcsafe . } =
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
return shuffledCache [ 0 .. < min ( shuffledCache . len . int , numPeers . int ) ]
2022-09-20 11:03:34 +00:00
2023-04-19 14:12:00 +00:00
proc populateEnrCache ( wpx : WakuPeerExchange ) =
# share only peers that i) are reachable ii) come from discv5
let withEnr = wpx . peerManager . peerStore
. getReachablePeers ( )
. filterIt ( it . origin = = Discv5 )
. filterIt ( it . enr . isSome )
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 )
for i in 0 .. < min ( withEnr . len , MaxPeersCacheSize ) :
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
while wpx . enrCache . len < MaxPeersCacheSize :
wpx . populateEnrCache ( )
await sleepAsync ( 5 . seconds )
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 ]
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 ( ) )
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
2022-11-02 08:45:21 +00:00
proc new * ( T : type WakuPeerExchange ,
2023-04-19 14:12:00 +00:00
peerManager : PeerManager ) : T =
2022-11-02 08:45:21 +00:00
let wpx = WakuPeerExchange (
2022-09-20 11:03:34 +00:00
peerManager : peerManager ,
)
2022-11-02 08:45:21 +00:00
wpx . initProtocolHandler ( )
2023-04-19 14:12:00 +00:00
asyncSpawn wpx . updatePxEnrCache ( )
2022-11-02 08:45:21 +00:00
return wpx