go-waku/waku/v2/rendezvous/rendezvous.go

275 lines
6.3 KiB
Go
Raw Normal View History

2023-03-09 15:48:25 +00:00
package rendezvous
import (
"context"
2023-06-23 15:50:32 +00:00
"fmt"
2023-03-09 22:42:50 +00:00
"math"
2023-03-14 00:37:28 +00:00
"math/rand"
"sort"
2023-03-09 22:42:50 +00:00
"sync"
"time"
2023-03-09 15:48:25 +00:00
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/p2p/discovery/backoff"
2023-06-01 17:30:45 +00:00
rvs "github.com/waku-org/go-libp2p-rendezvous"
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/peers"
2023-06-23 15:50:32 +00:00
"github.com/waku-org/go-waku/waku/v2/protocol"
2023-03-09 15:48:25 +00:00
"go.uber.org/zap"
)
const RendezvousID = rvs.RendezvousProto
2023-03-14 00:37:28 +00:00
type rendezvousPoint struct {
sync.RWMutex
id peer.ID
cookie []byte
bkf backoff.BackoffStrategy
nextTry time.Time
2023-03-14 00:37:28 +00:00
}
type PeerConnector interface {
Subscribe(context.Context, <-chan v2.PeerData)
}
2023-03-09 15:48:25 +00:00
type Rendezvous struct {
2023-03-09 22:42:50 +00:00
host host.Host
enableServer bool
2023-03-09 15:48:25 +00:00
db *DB
rendezvousSvc *rvs.RendezvousService
2023-03-14 00:37:28 +00:00
rendezvousPoints []*rendezvousPoint
2023-03-09 22:42:50 +00:00
peerConnector PeerConnector
log *zap.Logger
wg sync.WaitGroup
cancel context.CancelFunc
2023-03-09 15:48:25 +00:00
}
// NewRendezvous creates an instance of a Rendezvous which might act as rendezvous point for other nodes, or act as a client node
2023-06-23 15:50:32 +00:00
func NewRendezvous(enableServer bool, db *DB, rendezvousPoints []peer.ID, peerConnector PeerConnector, log *zap.Logger) *Rendezvous {
2023-03-09 15:48:25 +00:00
logger := log.Named("rendezvous")
rngSrc := rand.NewSource(rand.Int63())
minBackoff, maxBackoff := time.Second*30, time.Hour
bkf := backoff.NewExponentialBackoff(minBackoff, maxBackoff, backoff.FullJitter, time.Second, 5.0, 0, rand.New(rngSrc))
2023-03-14 00:37:28 +00:00
var rendevousPoints []*rendezvousPoint
now := time.Now()
2023-03-14 00:37:28 +00:00
for _, rp := range rendezvousPoints {
rendevousPoints = append(rendevousPoints, &rendezvousPoint{
id: rp,
nextTry: now,
bkf: bkf(),
2023-03-14 00:37:28 +00:00
})
}
2023-03-09 15:48:25 +00:00
return &Rendezvous{
2023-03-09 22:42:50 +00:00
enableServer: enableServer,
db: db,
rendezvousPoints: rendevousPoints,
peerConnector: peerConnector,
log: logger,
2023-03-09 15:48:25 +00:00
}
}
2023-04-17 00:04:12 +00:00
// Sets the host to be able to mount or consume a protocol
func (r *Rendezvous) SetHost(h host.Host) {
r.host = h
}
2023-03-09 15:48:25 +00:00
func (r *Rendezvous) Start(ctx context.Context) error {
2023-03-09 22:42:50 +00:00
ctx, cancel := context.WithCancel(ctx)
r.cancel = cancel
if r.enableServer {
2023-03-14 00:37:28 +00:00
err := r.db.Start(ctx)
if err != nil {
cancel()
return err
}
2023-03-09 22:42:50 +00:00
r.rendezvousSvc = rvs.NewRendezvousService(r.host, r.db)
}
2023-03-09 15:48:25 +00:00
r.log.Info("rendezvous protocol started")
return nil
}
2023-03-09 22:42:50 +00:00
const registerBackoff = 200 * time.Millisecond
const registerMaxRetries = 7
func (r *Rendezvous) getRandomRendezvousPoint(ctx context.Context) <-chan *rendezvousPoint {
var dialableRP []*rendezvousPoint
now := time.Now()
for _, rp := range r.rendezvousPoints {
if now.After(rp.NextTry()) {
dialableRP = append(dialableRP, rp)
}
}
result := make(chan *rendezvousPoint, 1)
if len(dialableRP) > 0 {
result <- r.rendezvousPoints[rand.Intn(len(r.rendezvousPoints))] // nolint: gosec
} else {
if len(r.rendezvousPoints) > 0 {
sort.Slice(r.rendezvousPoints, func(i, j int) bool {
return r.rendezvousPoints[i].nextTry.Before(r.rendezvousPoints[j].nextTry)
})
tryIn := r.rendezvousPoints[0].NextTry().Sub(now)
timer := time.NewTimer(tryIn)
defer timer.Stop()
select {
case <-ctx.Done():
break
case <-timer.C:
result <- r.rendezvousPoints[0]
}
}
}
close(result)
return result
2023-03-14 00:37:28 +00:00
}
2023-06-23 15:50:32 +00:00
func (r *Rendezvous) Discover(ctx context.Context, topic string, numPeers int) {
2023-03-14 00:37:28 +00:00
for {
select {
case <-ctx.Done():
return
case server, ok := <-r.getRandomRendezvousPoint(ctx):
if !ok {
return
}
2023-03-14 00:37:28 +00:00
rendezvousClient := rvs.NewRendezvousClient(r.host, server.id)
2023-06-23 15:50:32 +00:00
addrInfo, cookie, err := rendezvousClient.Discover(ctx, topic, numPeers, server.cookie)
2023-03-14 00:37:28 +00:00
if err != nil {
r.log.Error("could not discover new peers", zap.Error(err))
server.Delay()
continue
2023-03-14 00:37:28 +00:00
}
if len(addrInfo) != 0 {
server.SetSuccess(cookie)
2023-03-14 00:37:28 +00:00
peerCh := make(chan v2.PeerData)
r.peerConnector.Subscribe(context.Background(), peerCh)
2023-03-14 00:37:28 +00:00
for _, addr := range addrInfo {
peer := v2.PeerData{
Origin: peers.Rendezvous,
AddrInfo: addr,
}
fmt.Println("PPPPPPPPPPPPPP")
select {
case peerCh <- peer:
fmt.Println("DISCOVERED")
case <-ctx.Done():
return
}
2023-03-14 00:37:28 +00:00
}
close(peerCh)
2023-03-14 00:37:28 +00:00
} else {
server.Delay()
2023-03-14 00:37:28 +00:00
}
}
}
}
2023-06-23 15:50:32 +00:00
func (r *Rendezvous) DiscoverShard(ctx context.Context, cluster uint16, shard uint16, numPeers int) {
namespace := ShardToNamespace(cluster, shard)
r.Discover(ctx, namespace, numPeers)
}
func (r *Rendezvous) callRegister(ctx context.Context, rendezvousClient rvs.RendezvousClient, topic string, retries int) (<-chan time.Time, int) {
ttl, err := rendezvousClient.Register(ctx, topic, rvs.DefaultTTL)
2023-03-09 22:42:50 +00:00
var t <-chan time.Time
if err != nil {
r.log.Error("registering rendezvous client", zap.Error(err))
backoff := registerBackoff * time.Duration(math.Exp2(float64(retries)))
t = time.After(backoff)
retries++
} else {
t = time.After(ttl)
}
return t, retries
}
2023-06-23 15:50:32 +00:00
func (r *Rendezvous) Register(ctx context.Context, topic string) {
2023-03-09 22:42:50 +00:00
for _, m := range r.rendezvousPoints {
r.wg.Add(1)
2023-03-14 00:37:28 +00:00
go func(m *rendezvousPoint) {
2023-03-09 22:42:50 +00:00
r.wg.Done()
2023-03-14 00:37:28 +00:00
rendezvousClient := rvs.NewRendezvousClient(r.host, m.id)
2023-03-09 22:42:50 +00:00
retries := 0
var t <-chan time.Time
2023-06-23 15:50:32 +00:00
t, retries = r.callRegister(ctx, rendezvousClient, topic, retries)
2023-03-09 22:42:50 +00:00
for {
select {
case <-ctx.Done():
return
case <-t:
2023-06-23 15:50:32 +00:00
t, retries = r.callRegister(ctx, rendezvousClient, topic, retries)
2023-03-09 22:42:50 +00:00
if retries >= registerMaxRetries {
return
}
}
}
}(m)
}
}
2023-06-23 15:50:32 +00:00
func (r *Rendezvous) RegisterShard(ctx context.Context, cluster uint16, shard uint16) {
namespace := ShardToNamespace(cluster, shard)
r.Register(ctx, namespace)
}
func (r *Rendezvous) RegisterRelayShards(ctx context.Context, rs protocol.RelayShards) {
for _, idx := range rs.Indices {
go r.RegisterShard(ctx, rs.Cluster, idx)
}
}
2023-03-09 15:48:25 +00:00
func (r *Rendezvous) Stop() {
2023-03-09 22:42:50 +00:00
r.cancel()
r.wg.Wait()
2023-03-09 15:48:25 +00:00
r.host.RemoveStreamHandler(rvs.RendezvousProto)
r.rendezvousSvc = nil
}
2023-06-23 15:50:32 +00:00
func ShardToNamespace(cluster uint16, shard uint16) string {
return fmt.Sprintf("rs/%d/%d", cluster, shard)
}
func (rp *rendezvousPoint) Delay() {
rp.Lock()
defer rp.Unlock()
rp.nextTry = time.Now().Add(rp.bkf.Delay())
}
func (rp *rendezvousPoint) SetSuccess(cookie []byte) {
rp.Lock()
defer rp.Unlock()
rp.bkf.Reset()
rp.cookie = cookie
}
func (rp *rendezvousPoint) NextTry() time.Time {
rp.RLock()
defer rp.RUnlock()
return rp.nextTry
}