2022-10-23 13:13:43 +00:00
|
|
|
package peer_exchange
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"errors"
|
2023-01-27 17:36:52 +00:00
|
|
|
"fmt"
|
2022-10-23 13:13:43 +00:00
|
|
|
"math"
|
|
|
|
"math/rand"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
|
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
|
|
"github.com/libp2p/go-libp2p/core/host"
|
|
|
|
"github.com/libp2p/go-libp2p/core/network"
|
|
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
|
|
libp2pProtocol "github.com/libp2p/go-libp2p/core/protocol"
|
|
|
|
"github.com/libp2p/go-msgio/protoio"
|
2022-11-09 19:53:01 +00:00
|
|
|
"github.com/waku-org/go-waku/logging"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/discv5"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/metrics"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/utils"
|
2022-10-23 13:13:43 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PeerExchangeID_v20alpha1 is the current Waku Peer Exchange protocol identifier
|
|
|
|
const PeerExchangeID_v20alpha1 = libp2pProtocol.ID("/vac/waku/peer-exchange/2.0.0-alpha1")
|
|
|
|
const MaxCacheSize = 1000
|
|
|
|
const CacheCleanWindow = 200
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrNoPeersAvailable = errors.New("no suitable remote peers")
|
|
|
|
ErrInvalidId = errors.New("invalid request id")
|
|
|
|
)
|
|
|
|
|
|
|
|
type peerRecord struct {
|
2023-01-11 00:52:10 +00:00
|
|
|
node *enode.Node
|
2022-10-23 13:13:43 +00:00
|
|
|
idx int
|
|
|
|
}
|
|
|
|
|
|
|
|
type WakuPeerExchange struct {
|
|
|
|
h host.Host
|
|
|
|
disc *discv5.DiscoveryV5
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
log *zap.Logger
|
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
cancel context.CancelFunc
|
|
|
|
|
2023-01-11 00:52:10 +00:00
|
|
|
wg sync.WaitGroup
|
2023-01-13 23:58:22 +00:00
|
|
|
peerConnector PeerConnector
|
|
|
|
peerCh chan peer.AddrInfo
|
2022-10-23 13:13:43 +00:00
|
|
|
enrCache map[enode.ID]peerRecord // todo: next step: ring buffer; future: implement cache satisfying https://rfc.vac.dev/spec/34/
|
|
|
|
enrCacheMutex sync.RWMutex
|
|
|
|
rng *rand.Rand
|
|
|
|
}
|
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
type PeerConnector interface {
|
|
|
|
PeerChannel() chan<- peer.AddrInfo
|
|
|
|
}
|
|
|
|
|
2022-10-23 13:13:43 +00:00
|
|
|
// NewWakuPeerExchange returns a new instance of WakuPeerExchange struct
|
2023-01-13 23:58:22 +00:00
|
|
|
func NewWakuPeerExchange(h host.Host, disc *discv5.DiscoveryV5, peerConnector PeerConnector, log *zap.Logger) (*WakuPeerExchange, error) {
|
2022-10-23 13:13:43 +00:00
|
|
|
wakuPX := new(WakuPeerExchange)
|
|
|
|
wakuPX.h = h
|
|
|
|
wakuPX.disc = disc
|
|
|
|
wakuPX.log = log.Named("wakupx")
|
|
|
|
wakuPX.enrCache = make(map[enode.ID]peerRecord)
|
|
|
|
wakuPX.rng = rand.New(rand.NewSource(rand.Int63()))
|
2023-01-13 23:58:22 +00:00
|
|
|
wakuPX.peerConnector = peerConnector
|
2023-01-11 00:52:10 +00:00
|
|
|
return wakuPX, nil
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start inits the peer exchange protocol
|
2022-12-10 15:38:18 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) Start(ctx context.Context) error {
|
2023-01-13 23:58:22 +00:00
|
|
|
if wakuPX.cancel != nil {
|
2023-01-06 22:37:57 +00:00
|
|
|
return errors.New("peer exchange already started")
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
wakuPX.wg.Wait() // Waiting for any go routines to stop
|
2023-01-06 22:37:57 +00:00
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
wakuPX.cancel = cancel
|
2023-01-13 23:58:22 +00:00
|
|
|
wakuPX.peerCh = make(chan peer.AddrInfo)
|
2022-12-10 15:38:18 +00:00
|
|
|
|
|
|
|
wakuPX.h.SetStreamHandlerMatch(PeerExchangeID_v20alpha1, protocol.PrefixTextMatch(string(PeerExchangeID_v20alpha1)), wakuPX.onRequest(ctx))
|
2022-10-23 13:13:43 +00:00
|
|
|
wakuPX.log.Info("Peer exchange protocol started")
|
|
|
|
|
|
|
|
wakuPX.wg.Add(1)
|
2022-12-10 15:38:18 +00:00
|
|
|
go wakuPX.runPeerExchangeDiscv5Loop(ctx)
|
2022-10-23 13:13:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) handleResponse(ctx context.Context, response *pb.PeerExchangeResponse) error {
|
2022-10-23 13:13:43 +00:00
|
|
|
var peers []peer.AddrInfo
|
|
|
|
for _, p := range response.PeerInfos {
|
|
|
|
enrRecord := &enr.Record{}
|
|
|
|
buf := bytes.NewBuffer(p.ENR)
|
|
|
|
|
|
|
|
err := enrRecord.DecodeRLP(rlp.NewStream(buf, uint64(len(p.ENR))))
|
|
|
|
if err != nil {
|
|
|
|
wakuPX.log.Error("converting bytes to enr", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
enodeRecord, err := enode.New(enode.ValidSchemes, enrRecord)
|
|
|
|
if err != nil {
|
|
|
|
wakuPX.log.Error("creating enode record", zap.Error(err))
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
peerInfo, err := utils.EnodeToPeerInfo(enodeRecord)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
peers = append(peers, *peerInfo)
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(peers) != 0 {
|
|
|
|
log.Info("connecting to newly discovered peers", zap.Int("count", len(peers)))
|
2023-01-13 23:58:22 +00:00
|
|
|
wakuPX.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wakuPX.wg.Done()
|
|
|
|
for _, p := range peers {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case wakuPX.peerConnector.PeerChannel() <- p:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) onRequest(ctx context.Context) func(s network.Stream) {
|
|
|
|
return func(s network.Stream) {
|
|
|
|
defer s.Close()
|
|
|
|
logger := wakuPX.log.With(logging.HostID("peer", s.Conn().RemotePeer()))
|
|
|
|
requestRPC := &pb.PeerExchangeRPC{}
|
|
|
|
reader := protoio.NewDelimitedReader(s, math.MaxInt32)
|
|
|
|
err := reader.ReadMsg(requestRPC)
|
2022-10-23 13:13:43 +00:00
|
|
|
if err != nil {
|
2022-12-10 15:38:18 +00:00
|
|
|
logger.Error("reading request", zap.Error(err))
|
|
|
|
metrics.RecordPeerExchangeError(ctx, "decodeRpcFailure")
|
2022-10-23 13:13:43 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
if requestRPC.Query != nil {
|
|
|
|
logger.Info("request received")
|
|
|
|
err := wakuPX.respond(ctx, requestRPC.Query.NumPeers, s.Conn().RemotePeer())
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("responding", zap.Error(err))
|
|
|
|
metrics.RecordPeerExchangeError(ctx, "pxFailure")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if requestRPC.Response != nil {
|
|
|
|
logger.Info("response received")
|
|
|
|
err := wakuPX.handleResponse(ctx, requestRPC.Response)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("handling response", zap.Error(err))
|
|
|
|
metrics.RecordPeerExchangeError(ctx, "pxFailure")
|
|
|
|
return
|
|
|
|
}
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wakuPX *WakuPeerExchange) Request(ctx context.Context, numPeers int, opts ...PeerExchangeOption) error {
|
|
|
|
params := new(PeerExchangeParameters)
|
|
|
|
params.host = wakuPX.h
|
|
|
|
params.log = wakuPX.log
|
|
|
|
|
|
|
|
optList := DefaultOptions(wakuPX.h)
|
|
|
|
optList = append(optList, opts...)
|
|
|
|
for _, opt := range optList {
|
|
|
|
opt(params)
|
|
|
|
}
|
|
|
|
|
|
|
|
if params.selectedPeer == "" {
|
2022-12-10 15:38:18 +00:00
|
|
|
metrics.RecordPeerExchangeError(ctx, "dialError")
|
2022-10-23 13:13:43 +00:00
|
|
|
return ErrNoPeersAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
requestRPC := &pb.PeerExchangeRPC{
|
|
|
|
Query: &pb.PeerExchangeQuery{
|
|
|
|
NumPeers: uint64(numPeers),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
return wakuPX.sendPeerExchangeRPCToPeer(ctx, requestRPC, params.selectedPeer)
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop unmounts the peer exchange protocol
|
|
|
|
func (wakuPX *WakuPeerExchange) Stop() {
|
2023-01-06 22:37:57 +00:00
|
|
|
if wakuPX.cancel == nil {
|
|
|
|
return
|
|
|
|
}
|
2022-12-10 15:38:18 +00:00
|
|
|
wakuPX.h.RemoveStreamHandler(PeerExchangeID_v20alpha1)
|
2023-01-13 23:58:22 +00:00
|
|
|
wakuPX.cancel()
|
|
|
|
close(wakuPX.peerCh)
|
2022-12-10 15:38:18 +00:00
|
|
|
wakuPX.wg.Wait()
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) sendPeerExchangeRPCToPeer(ctx context.Context, rpc *pb.PeerExchangeRPC, peerID peer.ID) error {
|
2022-10-23 13:13:43 +00:00
|
|
|
logger := wakuPX.log.With(logging.HostID("peer", peerID))
|
|
|
|
|
|
|
|
// We connect first so dns4 addresses are resolved (NewStream does not do it)
|
2022-12-10 15:38:18 +00:00
|
|
|
err := wakuPX.h.Connect(ctx, wakuPX.h.Peerstore().PeerInfo(peerID))
|
2022-10-23 13:13:43 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Error("connecting peer", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
connOpt, err := wakuPX.h.NewStream(ctx, peerID, PeerExchangeID_v20alpha1)
|
2022-10-23 13:13:43 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Error("creating stream to peer", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer connOpt.Close()
|
|
|
|
|
|
|
|
writer := protoio.NewDelimitedWriter(connOpt)
|
|
|
|
err = writer.WriteMsg(rpc)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("writing response", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) respond(ctx context.Context, numPeers uint64, peerID peer.ID) error {
|
2022-10-23 13:13:43 +00:00
|
|
|
records, err := wakuPX.getENRsFromCache(numPeers)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
responseRPC := &pb.PeerExchangeRPC{}
|
|
|
|
responseRPC.Response = new(pb.PeerExchangeResponse)
|
|
|
|
responseRPC.Response.PeerInfos = records
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
return wakuPX.sendPeerExchangeRPCToPeer(ctx, responseRPC, peerID)
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (wakuPX *WakuPeerExchange) getENRsFromCache(numPeers uint64) ([]*pb.PeerInfo, error) {
|
|
|
|
wakuPX.enrCacheMutex.Lock()
|
|
|
|
defer wakuPX.enrCacheMutex.Unlock()
|
|
|
|
|
|
|
|
if len(wakuPX.enrCache) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
numItems := int(numPeers)
|
|
|
|
if len(wakuPX.enrCache) < int(numPeers) {
|
|
|
|
numItems = len(wakuPX.enrCache)
|
|
|
|
}
|
|
|
|
|
|
|
|
perm := wakuPX.rng.Perm(len(wakuPX.enrCache))[0:numItems]
|
|
|
|
permSet := make(map[int]int)
|
|
|
|
for i, v := range perm {
|
|
|
|
permSet[v] = i
|
|
|
|
}
|
|
|
|
|
|
|
|
var result []*pb.PeerInfo
|
|
|
|
iter := 0
|
|
|
|
for k := range wakuPX.enrCache {
|
|
|
|
if _, ok := permSet[iter]; ok {
|
|
|
|
var b bytes.Buffer
|
|
|
|
writer := bufio.NewWriter(&b)
|
|
|
|
enode := wakuPX.enrCache[k]
|
|
|
|
|
|
|
|
err := enode.node.Record().EncodeRLP(writer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
writer.Flush()
|
|
|
|
|
|
|
|
result = append(result, &pb.PeerInfo{
|
|
|
|
ENR: b.Bytes(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
iter++
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wakuPX *WakuPeerExchange) cleanCache() {
|
|
|
|
if len(wakuPX.enrCache) < MaxCacheSize {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r := make(map[enode.ID]peerRecord)
|
|
|
|
for k, v := range wakuPX.enrCache {
|
|
|
|
if v.idx > CacheCleanWindow {
|
|
|
|
v.idx -= CacheCleanWindow
|
|
|
|
r[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wakuPX.enrCache = r
|
|
|
|
}
|
|
|
|
|
2023-01-27 17:36:52 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) iterate(ctx context.Context) error {
|
2023-01-13 23:58:22 +00:00
|
|
|
iterator, err := wakuPX.disc.Iterator()
|
2022-10-23 13:13:43 +00:00
|
|
|
if err != nil {
|
2023-01-27 17:36:52 +00:00
|
|
|
return fmt.Errorf("obtaining iterator: %w", err)
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
2023-01-13 23:58:22 +00:00
|
|
|
defer iterator.Close()
|
2022-10-23 13:13:43 +00:00
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
for {
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
break
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
exists := iterator.Next()
|
|
|
|
if !exists {
|
|
|
|
break
|
|
|
|
}
|
2022-10-23 13:13:43 +00:00
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
addresses, err := utils.Multiaddress(iterator.Node())
|
|
|
|
if err != nil {
|
|
|
|
wakuPX.log.Error("extracting multiaddrs from enr", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(addresses) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
wakuPX.enrCacheMutex.Lock()
|
|
|
|
wakuPX.enrCache[iterator.Node().ID()] = peerRecord{
|
|
|
|
idx: len(wakuPX.enrCache),
|
|
|
|
node: iterator.Node(),
|
|
|
|
}
|
|
|
|
wakuPX.enrCacheMutex.Unlock()
|
|
|
|
}
|
2023-01-27 17:36:52 +00:00
|
|
|
|
|
|
|
return nil
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
|
2022-12-10 15:38:18 +00:00
|
|
|
func (wakuPX *WakuPeerExchange) runPeerExchangeDiscv5Loop(ctx context.Context) {
|
2022-10-23 13:13:43 +00:00
|
|
|
defer wakuPX.wg.Done()
|
|
|
|
|
|
|
|
// Runs a discv5 loop adding new peers to the px peer cache
|
|
|
|
if wakuPX.disc == nil {
|
|
|
|
wakuPX.log.Warn("trying to run discovery v5 (for PX) while it's disabled")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
ch := make(chan struct{}, 1)
|
|
|
|
ch <- struct{}{} // Initial execution
|
2022-10-23 13:13:43 +00:00
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
ticker := time.NewTicker(5 * time.Second)
|
2022-10-23 13:13:43 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
2023-01-13 23:58:22 +00:00
|
|
|
restartLoop:
|
2022-10-23 13:13:43 +00:00
|
|
|
for {
|
|
|
|
select {
|
2023-01-13 23:58:22 +00:00
|
|
|
case <-ch:
|
2023-01-27 17:36:52 +00:00
|
|
|
err := wakuPX.iterate(ctx)
|
|
|
|
if err != nil {
|
|
|
|
wakuPX.log.Debug("iterating peer exchange", zap.Error(err))
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
}
|
2023-01-13 23:58:22 +00:00
|
|
|
ch <- struct{}{}
|
2022-10-23 13:13:43 +00:00
|
|
|
case <-ticker.C:
|
2023-01-13 23:58:22 +00:00
|
|
|
wakuPX.cleanCache()
|
|
|
|
case <-ctx.Done():
|
|
|
|
close(ch)
|
|
|
|
break restartLoop
|
2022-10-23 13:13:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|