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
.. / .. / node / peer_manager / peer_manager ,
.. / .. / node / discv5 / waku_discv5 ,
.. / .. / utils / requests ,
.. / 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 :
topics = " wakupx "
const
# We add a 64kB safety buffer for protocol overhead.
# 10x-multiplier also for safety
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...
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 ]
enrCache : seq [ enr . Record ] # todo: next step: ring buffer; future: implement cache satisfying https://rfc.vac.dev/spec/34/
proc sendPeerExchangeRpcToPeer ( wpx : WakuPeerExchange , rpc : PeerExchangeRpc , peer : RemotePeerInfo | PeerId ) : Future [ WakuPeerExchangeResult [ void ] ] {. async , gcsafe . } =
let connOpt = await wpx . peerManager . dialPeer ( peer , WakuPeerExchangeCodec )
if connOpt . isNone ( ) :
return err ( dialFailure )
let connection = connOpt . get ( )
await connection . writeLP ( rpc . encode ( ) . buffer )
return ok ( )
proc request ( wpx : WakuPeerExchange , numPeers : uint64 , peer : RemotePeerInfo ) : Future [ WakuPeerExchangeResult [ void ] ] {. async , gcsafe . } =
let rpc = PeerExchangeRpc (
request : PeerExchangeRequest (
numPeers : numPeers
)
)
let res = await wpx . sendPeerExchangeRpcToPeer ( rpc , peer )
if res . isErr ( ) :
waku_px_errors . inc ( labelValues = [ res . error ( ) ] )
return err ( res . error ( ) )
return ok ( )
proc request * ( wpx : WakuPeerExchange , numPeers : uint64 ) : Future [ WakuPeerExchangeResult [ void ] ] {. async , gcsafe . } =
let peerOpt = wpx . peerManager . selectPeer ( WakuPeerExchangeCodec )
if peerOpt . isNone ( ) :
waku_px_errors . inc ( labelValues = [ peerNotFoundFailure ] )
return err ( peerNotFoundFailure )
return await wpx . request ( numPeers , peerOpt . get ( ) )
proc respond ( wpx : WakuPeerExchange , enrs : seq [ enr . Record ] , peer : RemotePeerInfo | PeerId ) : Future [ WakuPeerExchangeResult [ void ] ] {. async , gcsafe . } =
var peerInfos : seq [ PeerExchangePeerInfo ] = @ [ ]
for e in enrs :
let pi = PeerExchangePeerInfo (
enr : e . raw
)
peerInfos . add ( pi )
let rpc = PeerExchangeRpc (
response : PeerExchangeResponse (
peerInfos : peerInfos
)
)
let res = await wpx . sendPeerExchangeRpcToPeer ( rpc , peer )
if res . isErr ( ) :
waku_px_errors . inc ( labelValues = [ res . error ( ) ] )
return err ( res . error ( ) )
return ok ( )
2022-11-02 08:45:21 +00:00
proc respond ( wpx : WakuPeerExchange , enrs : seq [ enr . Record ] ) : Future [ WakuPeerExchangeResult [ void ] ] {. async , gcsafe . } =
2022-09-20 11:03:34 +00:00
let peerOpt = wpx . peerManager . selectPeer ( WakuPeerExchangeCodec )
if peerOpt . isNone ( ) :
waku_px_errors . inc ( labelValues = [ peerNotFoundFailure ] )
return err ( peerNotFoundFailure )
return await wpx . respond ( enrs , peerOpt . get ( ) )
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 ( ) )
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 . } =
2022-11-02 08:45:21 +00:00
let buff = await conn . readLp ( MaxRpcSize . int )
2022-09-20 11:03:34 +00:00
2022-11-02 08:45:21 +00:00
let res = PeerExchangeRpc . init ( buff )
2022-09-20 11:03:34 +00:00
if res . isErr ( ) :
waku_px_errors . inc ( labelValues = [ decodeRpcFailure ] )
return
let rpc = res . get ( )
# handle peer exchange request
if rpc . request ! = PeerExchangeRequest ( ) :
trace " peer exchange request received "
2022-11-02 08:45:21 +00:00
let enrs = wpx . getEnrsFromCache ( rpc . request . numPeers )
discard await wpx . respond ( enrs , conn . peerId )
2022-09-20 11:03:34 +00:00
waku_px_peers_sent . inc ( enrs . len ( ) . int64 ( ) )
# handle peer exchange response
if rpc . response ! = PeerExchangeResponse ( ) :
# todo: error handling
trace " peer exchange response received "
var record : enr . Record
var remotePeerInfoList : seq [ RemotePeerInfo ]
waku_px_peers_received_total . inc ( rpc . response . peerInfos . len ( ) . int64 ( ) )
for pi in rpc . response . peerInfos :
discard enr . fromBytes ( record , pi . enr )
remotePeerInfoList . add ( record . toRemotePeerInfo ( ) . get )
let newPeers = remotePeerInfoList . filterIt (
2022-11-02 08:45:21 +00:00
not wpx . peerManager . switch . isConnected ( it . peerId ) )
2022-09-20 11:03:34 +00:00
if newPeers . len ( ) > 0 :
waku_px_peers_received_unknown . inc ( newPeers . len ( ) . int64 ( ) )
debug " Connecting to newly discovered peers " , count = newPeers . len ( )
2022-11-02 08:45:21 +00:00
await wpx . peerManager . connectToNodes ( newPeers , WakuRelayCodec , source = " peer exchange " )
2022-09-20 11:03:34 +00:00
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