Refactor filter health checks; add debugging logs

This commit is contained in:
Vitaliy Vlasov 2023-09-13 13:50:23 +03:00
parent 9d374bcadc
commit 356b2f5ca3
101 changed files with 3231 additions and 1708 deletions

View File

@ -35,6 +35,7 @@ GIT_COMMIT = $(shell git rev-parse --short HEAD)
AUTHOR ?= $(shell git config user.email || echo $$USER) AUTHOR ?= $(shell git config user.email || echo $$USER)
ENABLE_METRICS ?= true ENABLE_METRICS ?= true
BUILD_TAGS ?= gowaku_no_rln
BUILD_FLAGS ?= $(shell echo "-ldflags='\ BUILD_FLAGS ?= $(shell echo "-ldflags='\
-X github.com/status-im/status-go/params.Version=$(RELEASE_TAG:v%=%) \ -X github.com/status-im/status-go/params.Version=$(RELEASE_TAG:v%=%) \
-X github.com/status-im/status-go/params.GitCommit=$(GIT_COMMIT) \ -X github.com/status-im/status-go/params.GitCommit=$(GIT_COMMIT) \

View File

@ -1 +1 @@
0.167.5 0.167.6

View File

@ -162,11 +162,11 @@ func (w *gethWakuV2Wrapper) UnsubscribeMany(ids []string) error {
func (w *gethWakuV2Wrapper) createFilterWrapper(id string, keyAsym *ecdsa.PrivateKey, keySym []byte, pow float64, pubsubTopic string, topics [][]byte) (types.Filter, error) { func (w *gethWakuV2Wrapper) createFilterWrapper(id string, keyAsym *ecdsa.PrivateKey, keySym []byte, pow float64, pubsubTopic string, topics [][]byte) (types.Filter, error) {
return NewWakuV2FilterWrapper(&wakucommon.Filter{ return NewWakuV2FilterWrapper(&wakucommon.Filter{
KeyAsym: keyAsym, KeyAsym: keyAsym,
KeySym: keySym, KeySym: keySym,
Topics: topics, ContentTopics: wakucommon.NewTopicSetFromBytes(topics),
PubsubTopic: pubsubTopic, PubsubTopic: pubsubTopic,
Messages: wakucommon.NewMemoryMessageStore(), Messages: wakucommon.NewMemoryMessageStore(),
}, id), nil }, id), nil
} }

10
go.mod
View File

@ -84,7 +84,7 @@ require (
github.com/mutecomm/go-sqlcipher/v4 v4.4.2 github.com/mutecomm/go-sqlcipher/v4 v4.4.2
github.com/schollz/peerdiscovery v1.7.0 github.com/schollz/peerdiscovery v1.7.0
github.com/siphiuel/lc-proxy-wrapper v0.0.0-20230516150924-246507cee8c7 github.com/siphiuel/lc-proxy-wrapper v0.0.0-20230516150924-246507cee8c7
github.com/waku-org/go-waku v0.7.1-0.20230907093131-092811658ea3 github.com/waku-org/go-waku v0.8.1-0.20230930175749-dcc828749f67
github.com/wk8/go-ordered-map/v2 v2.1.7 github.com/wk8/go-ordered-map/v2 v2.1.7
github.com/yeqown/go-qrcode/v2 v2.2.1 github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1 github.com/yeqown/go-qrcode/writer/standard v1.2.1
@ -257,10 +257,10 @@ require (
github.com/urfave/cli/v2 v2.24.4 // indirect github.com/urfave/cli/v2 v2.24.4 // indirect
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 // indirect github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 // indirect
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7 // indirect github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7 // indirect
github.com/waku-org/go-zerokit-rln v0.1.14-0.20230905214645-ca686a02e816 // indirect github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd // indirect
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230905213302-1d6d18a03e7c // indirect github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b // indirect
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230905183322-05f4cda61468 // indirect github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 // indirect
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230905182930-2b11e72ef866 // indirect github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 // indirect
github.com/wk8/go-ordered-map v1.0.0 // indirect github.com/wk8/go-ordered-map v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect

20
go.sum
View File

@ -2094,16 +2094,16 @@ github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 h1:xwY0kW5XZF
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98/go.mod h1:eBHgM6T4EG0RZzxpxKy+rGz/6Dw2Nd8DWxS0lm9ESDw= github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98/go.mod h1:eBHgM6T4EG0RZzxpxKy+rGz/6Dw2Nd8DWxS0lm9ESDw=
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7 h1:0e1h+p84yBp0IN7AqgbZlV7lgFBjm214lgSOE7CeJmE= github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7 h1:0e1h+p84yBp0IN7AqgbZlV7lgFBjm214lgSOE7CeJmE=
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7/go.mod h1:pFvOZ9YTFsW0o5zJW7a0B5tr1owAijRWJctXJ2toL04= github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7/go.mod h1:pFvOZ9YTFsW0o5zJW7a0B5tr1owAijRWJctXJ2toL04=
github.com/waku-org/go-waku v0.7.1-0.20230907093131-092811658ea3 h1:lwXUUy6XWnWr/svnQG30H/FlWKOvPAGjAFn3pwwjWbY= github.com/waku-org/go-waku v0.8.1-0.20230930175749-dcc828749f67 h1:EL0KljfCIFPXbY1IfT0JjVIjJekuF951ys1WL2WnWyM=
github.com/waku-org/go-waku v0.7.1-0.20230907093131-092811658ea3/go.mod h1:HW6QoUlzw3tLUbLzhHCGCEVIFcAWIjqCF6+JU0pSyus= github.com/waku-org/go-waku v0.8.1-0.20230930175749-dcc828749f67/go.mod h1:MnMLFtym7XUt+GNN4zTkjm5NJCsm7TERLWVPOV/Ct6w=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20230905214645-ca686a02e816 h1:M5skPFmapY5i5a9jSiGWft9PZMiQr2nCi8uzJc2IfBI= github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd h1:cu7CsUo7BK6ac/v193RIaqAzUcmpa6MNY4xYW9AenQI=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20230905214645-ca686a02e816/go.mod h1:zc3FBSLP6vy2sOjAnqIju3yKLRq1WkcxsS1Lh9w0CuA= github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd/go.mod h1:1PdBdPzyTaKt3VnpAHk3zj+r9dXPFOr3IHZP9nFle6E=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230905213302-1d6d18a03e7c h1:aDn17iEMrdXeQ6dp+Cv3ywJYStkomkvKWv8I00iy79c= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230905213302-1d6d18a03e7c/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230905183322-05f4cda61468 h1:yNRDUyWJu/wHEPLps5D/Zce24mu/5ax2u1pXsMwRPbg= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 h1:Sd7QD/1Yo2o2M1MY49F8Zr4KNBPUEK5cz5HoXQVJbrs=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230905183322-05f4cda61468/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230905182930-2b11e72ef866 h1:dURzhyGtPrpmBJcnY4hpY83dW81cZimkZ8U+S89ANd0= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 h1:4HSdWMFMufpRo3ECTX6BrvA+VzKhXZf7mS0rTa5cCWU=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230905182930-2b11e72ef866/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
github.com/wealdtech/go-ens/v3 v3.5.0 h1:Huc9GxBgiGweCOGTYomvsg07K2QggAqZpZ5SuiZdC8o= github.com/wealdtech/go-ens/v3 v3.5.0 h1:Huc9GxBgiGweCOGTYomvsg07K2QggAqZpZ5SuiZdC8o=
github.com/wealdtech/go-ens/v3 v3.5.0/go.mod h1:bVuYoWYEEeEu7Zy95rIMjPR34QFJarxt8p84ywSo0YM= github.com/wealdtech/go-ens/v3 v3.5.0/go.mod h1:bVuYoWYEEeEu7Zy95rIMjPR34QFJarxt8p84ywSo0YM=
github.com/wealdtech/go-multicodec v1.4.0 h1:iq5PgxwssxnXGGPTIK1srvt6U5bJwIp7k6kBrudIWxg= github.com/wealdtech/go-multicodec v1.4.0 h1:iq5PgxwssxnXGGPTIK1srvt6U5bJwIp7k6kBrudIWxg=

View File

@ -68,7 +68,7 @@ func HostID(key string, id peer.ID) zapcore.Field {
return zap.Stringer(key, hostID(id)) return zap.Stringer(key, hostID(id))
} }
func (id hostID) String() string { return peer.Encode(peer.ID(id)) } func (id hostID) String() string { return peer.ID(id).String() }
// Time - Waku uses Nanosecond Unix Time // Time - Waku uses Nanosecond Unix Time
type timestamp int64 type timestamp int64

View File

@ -410,6 +410,10 @@ func (d *DBStore) prepareQuerySQL(query *pb.HistoryQuery) (string, []interface{}
paramCnt++ paramCnt++
sqlQuery += fmt.Sprintf("LIMIT $%d", paramCnt) sqlQuery += fmt.Sprintf("LIMIT $%d", paramCnt)
// Always search for _max page size_ + 1. If the extra row does not exist, do not return pagination info.
pageSize := query.PagingInfo.PageSize + 1
parameters = append(parameters, pageSize)
sqlQuery = fmt.Sprintf(sqlQuery, conditionStr, orderDirection, orderDirection, orderDirection, orderDirection) sqlQuery = fmt.Sprintf(sqlQuery, conditionStr, orderDirection, orderDirection, orderDirection, orderDirection)
d.log.Info(fmt.Sprintf("sqlQuery: %s", sqlQuery)) d.log.Info(fmt.Sprintf("sqlQuery: %s", sqlQuery))
@ -434,10 +438,7 @@ func (d *DBStore) Query(query *pb.HistoryQuery) (*pb.Index, []StoredMessage, err
return nil, nil, err return nil, nil, err
} }
defer stmt.Close() defer stmt.Close()
pageSize := query.PagingInfo.PageSize + 1 //
parameters = append(parameters, pageSize)
measurementStart := time.Now() measurementStart := time.Now()
rows, err := stmt.Query(parameters...) rows, err := stmt.Query(parameters...)
if err != nil { if err != nil {
@ -458,6 +459,7 @@ func (d *DBStore) Query(query *pb.HistoryQuery) (*pb.Index, []StoredMessage, err
var cursor *pb.Index var cursor *pb.Index
if len(result) != 0 { if len(result) != 0 {
// since there are more rows than pagingInfo.PageSize, we need to return a cursor, for pagination
if len(result) > int(query.PagingInfo.PageSize) { if len(result) > int(query.PagingInfo.PageSize) {
result = result[0:query.PagingInfo.PageSize] result = result[0:query.PagingInfo.PageSize]
lastMsgIdx := len(result) - 1 lastMsgIdx := len(result) - 1

View File

@ -6,8 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sync"
"sync/atomic"
"time" "time"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
@ -34,23 +32,20 @@ type PeerConnector interface {
} }
type DiscoveryV5 struct { type DiscoveryV5 struct {
params *discV5Parameters params *discV5Parameters
host host.Host host host.Host
config discover.Config config discover.Config
udpAddr *net.UDPAddr udpAddr *net.UDPAddr
listener *discover.UDPv5 listener *discover.UDPv5
localnode *enode.LocalNode localnode *enode.LocalNode
metrics Metrics metrics Metrics
peerChannel *peerChannel
peerConnector PeerConnector peerConnector PeerConnector
NAT nat.Interface NAT nat.Interface
log *zap.Logger log *zap.Logger
started atomic.Bool *peermanager.CommonDiscoveryService
cancel context.CancelFunc
wg *sync.WaitGroup
} }
type discV5Parameters struct { type discV5Parameters struct {
@ -76,6 +71,7 @@ func WithAutoUpdate(autoUpdate bool) DiscoveryV5Option {
} }
} }
// WithBootnodes is an option used to specify the bootstrap nodes to use with DiscV5
func WithBootnodes(bootnodes []*enode.Node) DiscoveryV5Option { func WithBootnodes(bootnodes []*enode.Node) DiscoveryV5Option {
return func(params *discV5Parameters) { return func(params *discV5Parameters) {
params.bootnodes = bootnodes params.bootnodes = bootnodes
@ -106,6 +102,7 @@ func WithAutoFindPeers(find bool) DiscoveryV5Option {
} }
} }
// DefaultOptions contains the default list of options used when setting up DiscoveryV5
func DefaultOptions() []DiscoveryV5Option { func DefaultOptions() []DiscoveryV5Option {
return []DiscoveryV5Option{ return []DiscoveryV5Option{
WithUDPPort(9000), WithUDPPort(9000),
@ -124,19 +121,18 @@ func NewDiscoveryV5(priv *ecdsa.PrivateKey, localnode *enode.LocalNode, peerConn
logger := log.Named("discv5") logger := log.Named("discv5")
var NAT nat.Interface = nil var NAT nat.Interface
if params.advertiseAddr == nil { if params.advertiseAddr == nil {
NAT = nat.Any() NAT = nat.Any()
} }
return &DiscoveryV5{ return &DiscoveryV5{
params: params, params: params,
peerConnector: peerConnector, peerConnector: peerConnector,
NAT: NAT, NAT: NAT,
wg: &sync.WaitGroup{}, CommonDiscoveryService: peermanager.NewCommonDiscoveryService(),
peerChannel: &peerChannel{}, localnode: localnode,
localnode: localnode, metrics: newMetrics(reg),
metrics: newMetrics(reg),
config: discover.Config{ config: discover.Config{
PrivateKey: priv, PrivateKey: priv,
Bootnodes: params.bootnodes, Bootnodes: params.bootnodes,
@ -165,9 +161,9 @@ func (d *DiscoveryV5) listen(ctx context.Context) error {
d.udpAddr = conn.LocalAddr().(*net.UDPAddr) d.udpAddr = conn.LocalAddr().(*net.UDPAddr)
if d.NAT != nil && !d.udpAddr.IP.IsLoopback() { if d.NAT != nil && !d.udpAddr.IP.IsLoopback() {
d.wg.Add(1) d.WaitGroup().Add(1)
go func() { go func() {
defer d.wg.Done() defer d.WaitGroup().Done()
nat.Map(d.NAT, ctx.Done(), "udp", d.udpAddr.Port, d.udpAddr.Port, "go-waku discv5 discovery") nat.Map(d.NAT, ctx.Done(), "udp", d.udpAddr.Port, d.udpAddr.Port, "go-waku discv5 discovery")
}() }()
@ -195,80 +191,31 @@ func (d *DiscoveryV5) SetHost(h host.Host) {
d.host = h d.host = h
} }
type peerChannel struct {
mutex sync.Mutex
channel chan peermanager.PeerData
started bool
ctx context.Context
}
func (p *peerChannel) Start(ctx context.Context) {
p.mutex.Lock()
defer p.mutex.Unlock()
p.started = true
p.ctx = ctx
p.channel = make(chan peermanager.PeerData)
}
func (p *peerChannel) Stop() {
p.mutex.Lock()
defer p.mutex.Unlock()
if !p.started {
return
}
p.started = false
close(p.channel)
}
func (p *peerChannel) Subscribe() chan peermanager.PeerData {
return p.channel
}
func (p *peerChannel) Publish(peer peermanager.PeerData) bool {
p.mutex.Lock()
defer p.mutex.Unlock()
if !p.started {
return false
}
select {
case p.channel <- peer:
case <-p.ctx.Done():
return false
}
return true
}
// only works if the discovery v5 hasn't been started yet. // only works if the discovery v5 hasn't been started yet.
func (d *DiscoveryV5) Start(ctx context.Context) error { func (d *DiscoveryV5) Start(ctx context.Context) error {
// compare and swap sets the discovery v5 to `started` state return d.CommonDiscoveryService.Start(ctx, d.start)
// and prevents multiple calls to the start method by being atomic. }
if !d.started.CompareAndSwap(false, true) {
return nil
}
ctx, cancel := context.WithCancel(ctx) func (d *DiscoveryV5) start() error {
d.cancel = cancel d.peerConnector.Subscribe(d.Context(), d.GetListeningChan())
d.peerChannel.Start(ctx) err := d.listen(d.Context())
d.peerConnector.Subscribe(ctx, d.peerChannel.Subscribe())
err := d.listen(ctx)
if err != nil { if err != nil {
return err return err
} }
if d.params.autoFindPeers { if d.params.autoFindPeers {
d.wg.Add(1) d.WaitGroup().Add(1)
go func() { go func() {
defer d.wg.Done() defer d.WaitGroup().Done()
d.runDiscoveryV5Loop(ctx) d.runDiscoveryV5Loop(d.Context())
}() }()
} }
return nil return nil
} }
// SetBootnodes is used to setup the bootstrap nodes to use for discovering new peers
func (d *DiscoveryV5) SetBootnodes(nodes []*enode.Node) error { func (d *DiscoveryV5) SetBootnodes(nodes []*enode.Node) error {
if d.listener == nil { if d.listener == nil {
return ErrNoDiscV5Listener return ErrNoDiscV5Listener
@ -277,30 +224,22 @@ func (d *DiscoveryV5) SetBootnodes(nodes []*enode.Node) error {
return d.listener.SetFallbackNodes(nodes) return d.listener.SetFallbackNodes(nodes)
} }
// Stop is a function that stops the execution of DiscV5.
// only works if the discovery v5 is in running state // only works if the discovery v5 is in running state
// so we can assume that cancel method is set // so we can assume that cancel method is set
func (d *DiscoveryV5) Stop() { func (d *DiscoveryV5) Stop() {
if !d.started.CompareAndSwap(true, false) { // if Discoveryv5 is running, set started to false
return
}
d.cancel()
if d.listener != nil {
d.listener.Close()
d.listener = nil
d.log.Info("stopped Discovery V5")
}
d.wg.Wait()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
d.log.Info("recovering from panic and quitting") d.log.Info("recovering from panic and quitting")
} }
}() }()
d.CommonDiscoveryService.Stop(func() {
d.peerChannel.Stop() if d.listener != nil {
d.listener.Close()
d.listener = nil
d.log.Info("stopped Discovery V5")
}
})
} }
/* /*
@ -491,7 +430,7 @@ func (d *DiscoveryV5) peerLoop(ctx context.Context) error {
ENR: n, ENR: n,
} }
if d.peerChannel.Publish(peer) { if d.PushToChan(peer) {
d.log.Debug("published peer into peer channel", logging.HostID("peerID", peer.AddrInfo.ID)) d.log.Debug("published peer into peer channel", logging.HostID("peerID", peer.AddrInfo.ID))
} else { } else {
d.log.Debug("could not publish peer into peer channel", logging.HostID("peerID", peer.AddrInfo.ID)) d.log.Debug("could not publish peer into peer channel", logging.HostID("peerID", peer.AddrInfo.ID))
@ -523,7 +462,3 @@ restartLoop:
} }
d.log.Warn("Discv5 loop stopped") d.log.Warn("Discv5 loop stopped")
} }
func (d *DiscoveryV5) IsStarted() bool {
return d.started.Load()
}

View File

@ -19,7 +19,7 @@ func FilterPredicate(predicate func(*enode.Node) bool) Predicate {
} }
// FilterShard creates a Predicate that filters nodes that belong to a specific shard // FilterShard creates a Predicate that filters nodes that belong to a specific shard
func FilterShard(iterator enode.Iterator, cluster, index uint16) Predicate { func FilterShard(cluster, index uint16) Predicate {
return func(iterator enode.Iterator) enode.Iterator { return func(iterator enode.Iterator) enode.Iterator {
predicate := func(node *enode.Node) bool { predicate := func(node *enode.Node) bool {
rs, err := wenr.RelaySharding(node.Record()) rs, err := wenr.RelaySharding(node.Record())
@ -33,7 +33,7 @@ func FilterShard(iterator enode.Iterator, cluster, index uint16) Predicate {
} }
// FilterCapabilities creates a Predicate to filter nodes that support specific protocols // FilterCapabilities creates a Predicate to filter nodes that support specific protocols
func FilterCapabilities(iterator enode.Iterator, flags wenr.WakuEnrBitfield) Predicate { func FilterCapabilities(flags wenr.WakuEnrBitfield) Predicate {
return func(iterator enode.Iterator) enode.Iterator { return func(iterator enode.Iterator) enode.Iterator {
predicate := func(node *enode.Node) bool { predicate := func(node *enode.Node) bool {
enrField := new(wenr.WakuEnrBitfield) enrField := new(wenr.WakuEnrBitfield)

View File

@ -17,10 +17,10 @@ type dnsDiscoveryParameters struct {
nameserver string nameserver string
} }
type DnsDiscoveryOption func(*dnsDiscoveryParameters) type DNSDiscoveryOption func(*dnsDiscoveryParameters)
// WithNameserver is a DnsDiscoveryOption that configures the nameserver to use // WithNameserver is a DnsDiscoveryOption that configures the nameserver to use
func WithNameserver(nameserver string) DnsDiscoveryOption { func WithNameserver(nameserver string) DNSDiscoveryOption {
return func(params *dnsDiscoveryParameters) { return func(params *dnsDiscoveryParameters) {
params.nameserver = nameserver params.nameserver = nameserver
} }
@ -32,7 +32,7 @@ type DiscoveredNode struct {
ENR *enode.Node ENR *enode.Node
} }
var metrics Metrics = nil var metrics Metrics
// SetPrometheusRegisterer is used to setup a custom prometheus registerer for metrics // SetPrometheusRegisterer is used to setup a custom prometheus registerer for metrics
func SetPrometheusRegisterer(reg prometheus.Registerer, logger *zap.Logger) { func SetPrometheusRegisterer(reg prometheus.Registerer, logger *zap.Logger) {
@ -44,7 +44,7 @@ func init() {
} }
// RetrieveNodes returns a list of multiaddress given a url to a DNS discoverable ENR tree // RetrieveNodes returns a list of multiaddress given a url to a DNS discoverable ENR tree
func RetrieveNodes(ctx context.Context, url string, opts ...DnsDiscoveryOption) ([]DiscoveredNode, error) { func RetrieveNodes(ctx context.Context, url string, opts ...DNSDiscoveryOption) ([]DiscoveredNode, error) {
var discoveredNodes []DiscoveredNode var discoveredNodes []DiscoveredNode
params := new(dnsDiscoveryParameters) params := new(dnsDiscoveryParameters)

View File

@ -10,6 +10,7 @@ var sha256Pool = sync.Pool{New: func() interface{} {
return sha256.New() return sha256.New()
}} }}
// SHA256 generates the SHA256 hash from the input data
func SHA256(data ...[]byte) []byte { func SHA256(data ...[]byte) []byte {
h, ok := sha256Pool.Get().(hash.Hash) h, ok := sha256Pool.Get().(hash.Hash)
if !ok { if !ok {

View File

@ -16,7 +16,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags wenr.WakuEnrBitfield, advertiseAddr []ma.Multiaddr, shouldAutoUpdate bool, log *zap.Logger) error { func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags wenr.WakuEnrBitfield, advertiseAddr []ma.Multiaddr, shouldAutoUpdate bool) error {
var options []wenr.ENROption var options []wenr.ENROption
options = append(options, wenr.WithUDPPort(udpPort)) options = append(options, wenr.WithUDPPort(udpPort))
options = append(options, wenr.WithWakuBitfield(wakuFlags)) options = append(options, wenr.WithWakuBitfield(wakuFlags))
@ -268,7 +268,7 @@ func (w *WakuNode) setupENR(ctx context.Context, addrs []ma.Multiaddr) error {
return err return err
} }
err = w.updateLocalNode(w.localNode, multiaddresses, ipAddr, w.opts.udpPort, w.wakuFlag, w.opts.advertiseAddrs, w.opts.discV5autoUpdate, w.log) err = w.updateLocalNode(w.localNode, multiaddresses, ipAddr, w.opts.udpPort, w.wakuFlag, w.opts.advertiseAddrs, w.opts.discV5autoUpdate)
if err != nil { if err != nil {
w.log.Error("updating localnode ENR record", zap.Error(err)) w.log.Error("updating localnode ENR record", zap.Error(err))
return err return err
@ -281,6 +281,8 @@ func (w *WakuNode) setupENR(ctx context.Context, addrs []ma.Multiaddr) error {
} }
} }
w.enrChangeCh <- struct{}{}
return nil return nil
} }

View File

@ -10,7 +10,6 @@ import (
backoffv4 "github.com/cenkalti/backoff/v4" backoffv4 "github.com/cenkalti/backoff/v4"
golog "github.com/ipfs/go-log/v2" golog "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -32,6 +31,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/discv5" "github.com/waku-org/go-waku/waku/v2/discv5"
"github.com/waku-org/go-waku/waku/v2/peermanager" "github.com/waku-org/go-waku/waku/v2/peermanager"
wps "github.com/waku-org/go-waku/waku/v2/peerstore" wps "github.com/waku-org/go-waku/waku/v2/peerstore"
wakuprotocol "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/enr" "github.com/waku-org/go-waku/waku/v2/protocol/enr"
"github.com/waku-org/go-waku/waku/v2/protocol/filter" "github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter" "github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
@ -66,15 +66,16 @@ type IdentityCredential = struct {
IDCommitment byte32 `json:"idCommitment"` IDCommitment byte32 `json:"idCommitment"`
} }
type SpamHandler = func(message *pb.WakuMessage) error type SpamHandler = func(message *pb.WakuMessage, topic string) error
type RLNRelay interface { type RLNRelay interface {
IdentityCredential() (IdentityCredential, error) IdentityCredential() (IdentityCredential, error)
MembershipIndex() uint MembershipIndex() uint
AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Time) error AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Time) error
Validator(spamHandler SpamHandler) func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool Validator(spamHandler SpamHandler) func(ctx context.Context, message *pb.WakuMessage, topic string) bool
Start(ctx context.Context) error Start(ctx context.Context) error
Stop() error Stop() error
IsReady(ctx context.Context) (bool, error)
} }
type WakuNode struct { type WakuNode struct {
@ -236,7 +237,8 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
}() }()
return r return r
}, },
autorelay.WithMinInterval(2*time.Second), autorelay.WithMinInterval(params.circuitRelayMinInterval),
autorelay.WithBootDelay(params.circuitRelayBootDelay),
)) ))
if params.enableNTP { if params.enableNTP {
@ -251,7 +253,7 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
} }
//Initialize peer manager. //Initialize peer manager.
w.peermanager = peermanager.NewPeerManager(w.opts.maxPeerConnections, w.log) w.peermanager = peermanager.NewPeerManager(w.opts.maxPeerConnections, w.opts.peerStoreCapacity, w.log)
w.peerConnector, err = peermanager.NewPeerConnectionStrategy(w.peermanager, discoveryConnectTimeout, w.log) w.peerConnector, err = peermanager.NewPeerConnectionStrategy(w.peermanager, discoveryConnectTimeout, w.log)
if err != nil { if err != nil {
@ -272,6 +274,8 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
w.rendezvous = rendezvous.NewRendezvous(w.opts.rendezvousDB, w.peerConnector, w.log) w.rendezvous = rendezvous.NewRendezvous(w.opts.rendezvousDB, w.peerConnector, w.log)
w.relay = relay.NewWakuRelay(w.bcaster, w.opts.minRelayPeersToPublish, w.timesource, w.opts.prometheusReg, w.log, w.opts.pubsubOpts...)
if w.opts.enableRelay { if w.opts.enableRelay {
err = w.setupRLNRelay() err = w.setupRLNRelay()
if err != nil { if err != nil {
@ -279,7 +283,6 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
} }
} }
w.relay = relay.NewWakuRelay(w.bcaster, w.opts.minRelayPeersToPublish, w.timesource, w.opts.prometheusReg, w.log, w.opts.pubsubOpts...)
w.legacyFilter = legacy_filter.NewWakuFilter(w.bcaster, w.opts.isLegacyFilterFullNode, w.timesource, w.opts.prometheusReg, w.log, w.opts.legacyFilterOpts...) w.legacyFilter = legacy_filter.NewWakuFilter(w.bcaster, w.opts.isLegacyFilterFullNode, w.timesource, w.opts.prometheusReg, w.log, w.opts.legacyFilterOpts...)
w.filterFullNode = filter.NewWakuFilterFullNode(w.timesource, w.opts.prometheusReg, w.log, w.opts.filterOpts...) w.filterFullNode = filter.NewWakuFilterFullNode(w.timesource, w.opts.prometheusReg, w.log, w.opts.filterOpts...)
w.filterLightNode = filter.NewWakuFilterLightNode(w.bcaster, w.peermanager, w.timesource, w.opts.prometheusReg, w.log) w.filterLightNode = filter.NewWakuFilterLightNode(w.bcaster, w.peermanager, w.timesource, w.opts.prometheusReg, w.log)
@ -310,7 +313,6 @@ func (w *WakuNode) watchMultiaddressChanges(ctx context.Context) {
return return
case <-first: case <-first:
w.log.Info("listening", logging.MultiAddrs("multiaddr", addrs...)) w.log.Info("listening", logging.MultiAddrs("multiaddr", addrs...))
w.enrChangeCh <- struct{}{}
case <-w.addressChangesSub.Out(): case <-w.addressChangesSub.Out():
newAddrs := w.ListenAddresses() newAddrs := w.ListenAddresses()
diff := false diff := false
@ -327,8 +329,10 @@ func (w *WakuNode) watchMultiaddressChanges(ctx context.Context) {
if diff { if diff {
addrs = newAddrs addrs = newAddrs
w.log.Info("listening addresses update received", logging.MultiAddrs("multiaddr", addrs...)) w.log.Info("listening addresses update received", logging.MultiAddrs("multiaddr", addrs...))
_ = w.setupENR(ctx, addrs) err := w.setupENR(ctx, addrs)
w.enrChangeCh <- struct{}{} if err != nil {
w.log.Warn("could not update ENR", zap.Error(err))
}
} }
} }
} }
@ -417,6 +421,10 @@ func (w *WakuNode) Start(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
err = w.peermanager.SubscribeToRelayEvtBus(w.relay.(*relay.WakuRelay).Events())
if err != nil {
return err
}
w.peermanager.Start(ctx) w.peermanager.Start(ctx)
w.registerAndMonitorReachability(ctx) w.registerAndMonitorReachability(ctx)
} }
@ -687,20 +695,22 @@ func (w *WakuNode) startStore(ctx context.Context, sub relay.Subscription) error
} }
// AddPeer is used to add a peer and the protocols it support to the node peerstore // AddPeer is used to add a peer and the protocols it support to the node peerstore
func (w *WakuNode) AddPeer(address ma.Multiaddr, origin wps.Origin, protocols ...protocol.ID) (peer.ID, error) { // TODO: Need to update this for autosharding, to only take contentTopics and optional pubSubTopics or provide an alternate API only for contentTopics.
return w.peermanager.AddPeer(address, origin, protocols...) func (w *WakuNode) AddPeer(address ma.Multiaddr, origin wps.Origin, pubSubTopics []string, protocols ...protocol.ID) (peer.ID, error) {
return w.peermanager.AddPeer(address, origin, pubSubTopics, protocols...)
} }
// AddDiscoveredPeer to add a discovered peer to the node peerStore // AddDiscoveredPeer to add a discovered peer to the node peerStore
func (w *WakuNode) AddDiscoveredPeer(ID peer.ID, addrs []ma.Multiaddr, origin wps.Origin) { func (w *WakuNode) AddDiscoveredPeer(ID peer.ID, addrs []ma.Multiaddr, origin wps.Origin, pubsubTopics []string, connectNow bool) {
p := peermanager.PeerData{ p := peermanager.PeerData{
Origin: origin, Origin: origin,
AddrInfo: peer.AddrInfo{ AddrInfo: peer.AddrInfo{
ID: ID, ID: ID,
Addrs: addrs, Addrs: addrs,
}, },
PubSubTopics: pubsubTopics,
} }
w.peermanager.AddDiscoveredPeer(p) w.peermanager.AddDiscoveredPeer(p, connectNow)
} }
// DialPeerWithMultiAddress is used to connect to a peer using a multiaddress // DialPeerWithMultiAddress is used to connect to a peer using a multiaddress
@ -745,12 +755,12 @@ func (w *WakuNode) connect(ctx context.Context, info peer.AddrInfo) error {
// host.Connect adds the addresses with a TempAddressTTL // host.Connect adds the addresses with a TempAddressTTL
// however, identify will filter out all non IP addresses // however, identify will filter out all non IP addresses
// and expire all temporary addrs. So in the meantime, let's // and expire all temporary addrs. So in the meantime, let's
// store dns4 addresses with a connectedAddressTTL, otherwise // store dns4 addresses with a RecentlyConnectedAddrTTL, otherwise
// it will have trouble with the status fleet circuit relay addresses // it will have trouble with the status fleet circuit relay addresses
// See https://github.com/libp2p/go-libp2p/issues/2550 // See https://github.com/libp2p/go-libp2p/issues/2550
_, err := addr.ValueForProtocol(ma.P_DNS4) _, err := addr.ValueForProtocol(ma.P_DNS4)
if err == nil { if err == nil {
w.host.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.ConnectedAddrTTL) w.host.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.RecentlyConnectedAddrTTL)
} }
} }
@ -837,6 +847,21 @@ func (w *WakuNode) Peers() ([]*Peer, error) {
return peers, nil return peers, nil
} }
// PeersByShard filters peers based on shard information following static sharding
func (w *WakuNode) PeersByStaticShard(cluster uint16, shard uint16) peer.IDSlice {
pTopic := wakuprotocol.NewStaticShardingPubsubTopic(cluster, shard).String()
return w.peerstore.(wps.WakuPeerstore).PeersByPubSubTopic(pTopic)
}
// PeersByContentTopics filters peers based on contentTopic
func (w *WakuNode) PeersByContentTopic(contentTopic string) peer.IDSlice {
pTopic, err := wakuprotocol.GetPubSubTopicFromContentTopic(contentTopic)
if err != nil {
return nil
}
return w.peerstore.(wps.WakuPeerstore).PeersByPubSubTopic(pTopic)
}
func (w *WakuNode) findRelayNodes(ctx context.Context) { func (w *WakuNode) findRelayNodes(ctx context.Context) {
defer w.wg.Done() defer w.wg.Done()

View File

@ -1,10 +1,11 @@
//go:build !gowaku_rln //go:build gowaku_no_rln
// +build !gowaku_rln // +build gowaku_no_rln
package node package node
import "context" import "context"
// RLNRelay is used to access any operation related to Waku RLN protocol
func (w *WakuNode) RLNRelay() RLNRelay { func (w *WakuNode) RLNRelay() RLNRelay {
return nil return nil
} }

View File

@ -1,5 +1,5 @@
//go:build gowaku_rln //go:build !gowaku_no_rln
// +build gowaku_rln // +build !gowaku_no_rln
package node package node
@ -8,8 +8,8 @@ import (
"context" "context"
"errors" "errors"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/waku-org/go-waku/waku/v2/protocol/rln" "github.com/waku-org/go-waku/waku/v2/protocol/rln"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/dynamic" "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/dynamic"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/static" "github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager/static"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore" "github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
@ -23,31 +23,48 @@ func (w *WakuNode) RLNRelay() RLNRelay {
func (w *WakuNode) setupRLNRelay() error { func (w *WakuNode) setupRLNRelay() error {
var err error var err error
var groupManager rln.GroupManager
if !w.opts.enableRLN { if !w.opts.enableRLN {
return nil return nil
} }
if !w.opts.enableRelay {
return errors.New("rln requires relay")
}
var groupManager group_manager.GroupManager
rlnInstance, rootTracker, err := rln.GetRLNInstanceAndRootTracker(w.opts.rlnTreePath)
if err != nil {
return err
}
if !w.opts.rlnRelayDynamic { if !w.opts.rlnRelayDynamic {
w.log.Info("setting up waku-rln-relay in off-chain mode") w.log.Info("setting up waku-rln-relay in off-chain mode")
index := uint(0)
if w.opts.rlnRelayMemIndex != nil {
index = *w.opts.rlnRelayMemIndex
}
// set up rln relay inputs // set up rln relay inputs
groupKeys, idCredential, err := static.Setup(w.opts.rlnRelayMemIndex) groupKeys, idCredential, err := static.Setup(index)
if err != nil { if err != nil {
return err return err
} }
groupManager, err = static.NewStaticGroupManager(groupKeys, idCredential, w.opts.rlnRelayMemIndex, w.log) groupManager, err = static.NewStaticGroupManager(groupKeys, idCredential, index, rlnInstance, rootTracker, w.log)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
w.log.Info("setting up waku-rln-relay in on-chain mode") w.log.Info("setting up waku-rln-relay in on-chain mode")
appKeystore, err := keystore.New(w.opts.keystorePath, dynamic.RLNAppInfo, w.log) var appKeystore *keystore.AppKeystore
if err != nil { if w.opts.keystorePath != "" {
return err appKeystore, err = keystore.New(w.opts.keystorePath, dynamic.RLNAppInfo, w.log)
if err != nil {
return err
}
} }
groupManager, err = dynamic.NewDynamicGroupManager( groupManager, err = dynamic.NewDynamicGroupManager(
@ -57,6 +74,8 @@ func (w *WakuNode) setupRLNRelay() error {
appKeystore, appKeystore,
w.opts.keystorePassword, w.opts.keystorePassword,
w.opts.prometheusReg, w.opts.prometheusReg,
rlnInstance,
rootTracker,
w.log, w.log,
) )
if err != nil { if err != nil {
@ -64,15 +83,15 @@ func (w *WakuNode) setupRLNRelay() error {
} }
} }
rlnRelay, err := rln.New(groupManager, w.opts.rlnTreePath, w.timesource, w.opts.prometheusReg, w.log) rlnRelay := rln.New(group_manager.Details{
if err != nil { GroupManager: groupManager,
return err RootTracker: rootTracker,
} RLN: rlnInstance,
}, w.timesource, w.opts.prometheusReg, w.log)
w.rlnRelay = rlnRelay w.rlnRelay = rlnRelay
// Adding RLN as a default validator w.Relay().RegisterDefaultValidator(w.rlnRelay.Validator(w.opts.rlnSpamHandler))
w.opts.pubsubOpts = append(w.opts.pubsubOpts, pubsub.WithDefaultValidator(rlnRelay.Validator(w.opts.rlnSpamHandler)))
return nil return nil
} }

View File

@ -55,6 +55,9 @@ type WakuNodeParameters struct {
peerstore peerstore.Peerstore peerstore peerstore.Peerstore
prometheusReg prometheus.Registerer prometheusReg prometheus.Registerer
circuitRelayMinInterval time.Duration
circuitRelayBootDelay time.Duration
enableNTP bool enableNTP bool
ntpURLs []string ntpURLs []string
@ -85,6 +88,7 @@ type WakuNodeParameters struct {
rendezvousDB *rendezvous.DB rendezvousDB *rendezvous.DB
maxPeerConnections int maxPeerConnections int
peerStoreCapacity int
enableDiscV5 bool enableDiscV5 bool
udpPort uint udpPort uint
@ -94,9 +98,9 @@ type WakuNodeParameters struct {
enablePeerExchange bool enablePeerExchange bool
enableRLN bool enableRLN bool
rlnRelayMemIndex uint rlnRelayMemIndex *uint
rlnRelayDynamic bool rlnRelayDynamic bool
rlnSpamHandler func(message *pb.WakuMessage) error rlnSpamHandler func(message *pb.WakuMessage, topic string) error
rlnETHClientAddress string rlnETHClientAddress string
keystorePath string keystorePath string
keystorePassword string keystorePassword string
@ -119,6 +123,7 @@ type WakuNodeOption func(*WakuNodeParameters) error
var DefaultWakuNodeOptions = []WakuNodeOption{ var DefaultWakuNodeOptions = []WakuNodeOption{
WithPrometheusRegisterer(prometheus.NewRegistry()), WithPrometheusRegisterer(prometheus.NewRegistry()),
WithMaxPeerConnections(50), WithMaxPeerConnections(50),
WithCircuitRelayParams(2*time.Second, 3*time.Minute),
} }
// MultiAddresses return the list of multiaddresses configured in the node // MultiAddresses return the list of multiaddresses configured in the node
@ -171,8 +176,8 @@ func WithPrometheusRegisterer(reg prometheus.Registerer) WakuNodeOption {
} }
} }
// WithDns4Domain is a WakuNodeOption that adds a custom domain name to listen // WithDNS4Domain is a WakuNodeOption that adds a custom domain name to listen
func WithDns4Domain(dns4Domain string) WakuNodeOption { func WithDNS4Domain(dns4Domain string) WakuNodeOption {
return func(params *WakuNodeParameters) error { return func(params *WakuNodeParameters) error {
params.dns4Domain = dns4Domain params.dns4Domain = dns4Domain
previousAddrFactory := params.addressFactory previousAddrFactory := params.addressFactory
@ -190,8 +195,11 @@ func WithDns4Domain(dns4Domain string) WakuNodeOption {
if params.enableWS || params.enableWSS { if params.enableWS || params.enableWSS {
if params.enableWSS { if params.enableWSS {
// WSS is deprecated in https://github.com/multiformats/multiaddr/pull/109
wss, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/tcp/%d/wss", params.wssPort)) wss, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/tcp/%d/wss", params.wssPort))
addresses = append(addresses, hostAddrMA.Encapsulate(wss)) addresses = append(addresses, hostAddrMA.Encapsulate(wss))
tlsws, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/tcp/%d/tls/ws", params.wssPort))
addresses = append(addresses, hostAddrMA.Encapsulate(tlsws))
} else { } else {
ws, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/tcp/%d/ws", params.wsPort)) ws, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/tcp/%d/ws", params.wsPort))
addresses = append(addresses, hostAddrMA.Encapsulate(ws)) addresses = append(addresses, hostAddrMA.Encapsulate(ws))
@ -200,9 +208,9 @@ func WithDns4Domain(dns4Domain string) WakuNodeOption {
if previousAddrFactory != nil { if previousAddrFactory != nil {
return previousAddrFactory(addresses) return previousAddrFactory(addresses)
} else {
return addresses
} }
return addresses
} }
return nil return nil
@ -349,6 +357,13 @@ func WithMaxPeerConnections(maxPeers int) WakuNodeOption {
} }
} }
func WithPeerStoreCapacity(capacity int) WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.peerStoreCapacity = capacity
return nil
}
}
// WithDiscoveryV5 is a WakuOption used to enable DiscV5 peer discovery // WithDiscoveryV5 is a WakuOption used to enable DiscV5 peer discovery
func WithDiscoveryV5(udpPort uint, bootnodes []*enode.Node, autoUpdate bool) WakuNodeOption { func WithDiscoveryV5(udpPort uint, bootnodes []*enode.Node, autoUpdate bool) WakuNodeOption {
return func(params *WakuNodeParameters) error { return func(params *WakuNodeParameters) error {
@ -514,6 +529,14 @@ func WithSecureWebsockets(address string, port int, certPath string, keyPath str
} }
} }
func WithCircuitRelayParams(minInterval time.Duration, bootDelay time.Duration) WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.circuitRelayBootDelay = bootDelay
params.circuitRelayMinInterval = minInterval
return nil
}
}
// Default options used in the libp2p node // Default options used in the libp2p node
var DefaultLibP2POptions = []libp2p.Option{ var DefaultLibP2POptions = []libp2p.Option{
libp2p.ChainOptions( libp2p.ChainOptions(

View File

@ -1,5 +1,5 @@
//go:build gowaku_rln //go:build !gowaku_no_rln
// +build gowaku_rln // +build !gowaku_no_rln
package node package node
@ -10,8 +10,7 @@ import (
) )
// WithStaticRLNRelay enables the Waku V2 RLN protocol in offchain mode // WithStaticRLNRelay enables the Waku V2 RLN protocol in offchain mode
// Requires the `gowaku_rln` build constrain (or the env variable RLN=true if building go-waku) func WithStaticRLNRelay(memberIndex *r.MembershipIndex, spamHandler rln.SpamHandler) WakuNodeOption {
func WithStaticRLNRelay(memberIndex r.MembershipIndex, spamHandler rln.SpamHandler) WakuNodeOption {
return func(params *WakuNodeParameters) error { return func(params *WakuNodeParameters) error {
params.enableRLN = true params.enableRLN = true
params.rlnRelayDynamic = false params.rlnRelayDynamic = false
@ -22,8 +21,7 @@ func WithStaticRLNRelay(memberIndex r.MembershipIndex, spamHandler rln.SpamHandl
} }
// WithDynamicRLNRelay enables the Waku V2 RLN protocol in onchain mode. // WithDynamicRLNRelay enables the Waku V2 RLN protocol in onchain mode.
// Requires the `gowaku_rln` build constrain (or the env variable RLN=true if building go-waku) func WithDynamicRLNRelay(keystorePath string, keystorePassword string, treePath string, membershipContract common.Address, membershipIndex *uint, spamHandler rln.SpamHandler, ethClientAddress string) WakuNodeOption {
func WithDynamicRLNRelay(keystorePath string, keystorePassword string, treePath string, membershipContract common.Address, membershipIndex uint, spamHandler rln.SpamHandler, ethClientAddress string) WakuNodeOption {
return func(params *WakuNodeParameters) error { return func(params *WakuNodeParameters) error {
params.enableRLN = true params.enableRLN = true
params.rlnRelayDynamic = true params.rlnRelayDynamic = true

View File

@ -72,16 +72,15 @@ func (payload Payload) Encode(version uint32) ([]byte, error) {
encoded, err := encryptSymmetric(data, payload.Key.SymKey) encoded, err := encryptSymmetric(data, payload.Key.SymKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't encrypt using symmetric key: %w", err) return nil, fmt.Errorf("couldn't encrypt using symmetric key: %w", err)
} else {
return encoded, nil
} }
return encoded, nil
case Asymmetric: case Asymmetric:
encoded, err := encryptAsymmetric(data, &payload.Key.PubKey) encoded, err := encryptAsymmetric(data, &payload.Key.PubKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't encrypt using asymmetric key: %w", err) return nil, fmt.Errorf("couldn't encrypt using asymmetric key: %w", err)
} else {
return encoded, nil
} }
return encoded, nil
case None: case None:
return nil, errors.New("non supported KeyKind") return nil, errors.New("non supported KeyKind")
} }

View File

@ -0,0 +1,81 @@
package peermanager
import (
"context"
"sync"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/libp2p/go-libp2p/core/peer"
wps "github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol"
)
// PeerData contains information about a peer useful in establishing connections with it.
type PeerData struct {
Origin wps.Origin
AddrInfo peer.AddrInfo
ENR *enode.Node
PubSubTopics []string
}
type CommonDiscoveryService struct {
commonService *protocol.CommonService
channel chan PeerData
}
func NewCommonDiscoveryService() *CommonDiscoveryService {
return &CommonDiscoveryService{
commonService: protocol.NewCommonService(),
}
}
func (sp *CommonDiscoveryService) Start(ctx context.Context, fn func() error) error {
return sp.commonService.Start(ctx, func() error {
// currently is used in discv5,peerConnector,rendevzous for returning new discovered Peers to peerConnector for connecting with them
// mutex protection for this operation
sp.channel = make(chan PeerData)
return fn()
})
}
func (sp *CommonDiscoveryService) Stop(stopFn func()) {
sp.commonService.Stop(func() {
stopFn()
sp.WaitGroup().Wait() // waitgroup is waited here so that channel can be closed after all the go rountines have stopped in service.
// there is a wait in the CommonService too
close(sp.channel)
})
}
func (sp *CommonDiscoveryService) GetListeningChan() <-chan PeerData {
return sp.channel
}
func (sp *CommonDiscoveryService) PushToChan(data PeerData) bool {
sp.RLock()
defer sp.RUnlock()
if err := sp.ErrOnNotRunning(); err != nil {
return false
}
select {
case sp.channel <- data:
return true
case <-sp.Context().Done():
return false
}
}
func (sp *CommonDiscoveryService) RLock() {
sp.commonService.RLock()
}
func (sp *CommonDiscoveryService) RUnlock() {
sp.commonService.RUnlock()
}
func (sp *CommonDiscoveryService) Context() context.Context {
return sp.commonService.Context()
}
func (sp *CommonDiscoveryService) ErrOnNotRunning() error {
return sp.commonService.ErrOnNotRunning()
}
func (sp *CommonDiscoveryService) WaitGroup() *sync.WaitGroup {
return sp.commonService.WaitGroup()
}

View File

@ -25,10 +25,15 @@ func NewTestPeerDiscoverer() *TestPeerDiscoverer {
// Subscribe is for subscribing to peer discoverer // Subscribe is for subscribing to peer discoverer
func (t *TestPeerDiscoverer) Subscribe(ctx context.Context, ch <-chan PeerData) { func (t *TestPeerDiscoverer) Subscribe(ctx context.Context, ch <-chan PeerData) {
go func() { go func() {
for p := range ch { for {
t.Lock() select {
t.peerMap[p.AddrInfo.ID] = struct{}{} case <-ctx.Done():
t.Unlock() return
case p := <-ch:
t.Lock()
t.peerMap[p.AddrInfo.ID] = struct{}{}
t.Unlock()
}
} }
}() }()
} }

View File

@ -7,9 +7,9 @@ import (
"errors" "errors"
"math/rand" "math/rand"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
@ -17,40 +17,27 @@ import (
"github.com/libp2p/go-libp2p/p2p/discovery/backoff" "github.com/libp2p/go-libp2p/p2p/discovery/backoff"
"github.com/waku-org/go-waku/logging" "github.com/waku-org/go-waku/logging"
wps "github.com/waku-org/go-waku/waku/v2/peerstore" wps "github.com/waku-org/go-waku/waku/v2/peerstore"
waku_proto "github.com/waku-org/go-waku/waku/v2/protocol"
"sync/atomic"
"go.uber.org/zap" "go.uber.org/zap"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
) )
// PeerData contains information about a peer useful in establishing connections with it.
type PeerData struct {
Origin wps.Origin
AddrInfo peer.AddrInfo
ENR *enode.Node
}
// PeerConnectionStrategy is a utility to connect to peers, // PeerConnectionStrategy is a utility to connect to peers,
// but only if we have not recently tried connecting to them already // but only if we have not recently tried connecting to them already
type PeerConnectionStrategy struct { type PeerConnectionStrategy struct {
sync.RWMutex mux sync.Mutex
cache *lru.TwoQueueCache
host host.Host
pm *PeerManager
cache *lru.TwoQueueCache paused atomic.Bool
host host.Host dialTimeout time.Duration
pm *PeerManager *CommonDiscoveryService
cancel context.CancelFunc
paused atomic.Bool
wg sync.WaitGroup
dialTimeout time.Duration
dialCh chan peer.AddrInfo
subscriptions []<-chan PeerData subscriptions []<-chan PeerData
backoff backoff.BackoffFactory backoff backoff.BackoffFactory
mux sync.Mutex
logger *zap.Logger logger *zap.Logger
} }
@ -77,12 +64,12 @@ func NewPeerConnectionStrategy(pm *PeerManager,
} }
// //
pc := &PeerConnectionStrategy{ pc := &PeerConnectionStrategy{
cache: cache, cache: cache,
wg: sync.WaitGroup{}, dialTimeout: dialTimeout,
dialTimeout: dialTimeout, CommonDiscoveryService: NewCommonDiscoveryService(),
pm: pm, pm: pm,
backoff: getBackOff(), backoff: getBackOff(),
logger: logger.Named("discovery-connector"), logger: logger.Named("discovery-connector"),
} }
pm.SetPeerConnector(pc) pm.SetPeerConnector(pc)
return pc, nil return pc, nil
@ -95,36 +82,46 @@ type connCacheData struct {
// Subscribe receives channels on which discovered peers should be pushed // Subscribe receives channels on which discovered peers should be pushed
func (c *PeerConnectionStrategy) Subscribe(ctx context.Context, ch <-chan PeerData) { func (c *PeerConnectionStrategy) Subscribe(ctx context.Context, ch <-chan PeerData) {
if c.cancel != nil { // if not running yet, store the subscription and return
c.wg.Add(1) if err := c.ErrOnNotRunning(); err != nil {
go func() { c.mux.Lock()
defer c.wg.Done()
c.consumeSubscription(ctx, ch)
}()
} else {
c.subscriptions = append(c.subscriptions, ch) c.subscriptions = append(c.subscriptions, ch)
c.mux.Unlock()
return
} }
// if running start a goroutine to consume the subscription
c.WaitGroup().Add(1)
go func() {
defer c.WaitGroup().Done()
c.consumeSubscription(ch)
}()
} }
func (c *PeerConnectionStrategy) consumeSubscription(ctx context.Context, ch <-chan PeerData) { func (c *PeerConnectionStrategy) consumeSubscription(ch <-chan PeerData) {
for { for {
// for returning from the loop when peerConnector is paused. // for returning from the loop when peerConnector is paused.
select { select {
case <-ctx.Done(): case <-c.Context().Done():
return return
default: default:
} }
// //
if !c.isPaused() { if !c.isPaused() {
select { select {
case <-ctx.Done(): case <-c.Context().Done():
return return
case p, ok := <-ch: case p, ok := <-ch:
if !ok { if !ok {
return return
} }
c.pm.AddDiscoveredPeer(p) triggerImmediateConnection := false
c.publishWork(ctx, p.AddrInfo) //Not connecting to peer as soon as it is discovered,
// rather expecting this to be pushed from PeerManager based on the need.
if len(c.host.Network().Peers()) < waku_proto.GossipSubOptimalFullMeshSize {
triggerImmediateConnection = true
}
c.pm.AddDiscoveredPeer(p, triggerImmediateConnection)
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
// This timeout is to not lock the goroutine // This timeout is to not lock the goroutine
break break
@ -143,76 +140,40 @@ func (c *PeerConnectionStrategy) SetHost(h host.Host) {
// Start attempts to connect to the peers passed in by peerCh. // Start attempts to connect to the peers passed in by peerCh.
// Will not connect to peers if they are within the backoff period. // Will not connect to peers if they are within the backoff period.
func (c *PeerConnectionStrategy) Start(ctx context.Context) error { func (c *PeerConnectionStrategy) Start(ctx context.Context) error {
if c.cancel != nil { return c.CommonDiscoveryService.Start(ctx, c.start)
return errors.New("already started")
}
ctx, cancel := context.WithCancel(ctx) }
c.cancel = cancel func (c *PeerConnectionStrategy) start() error {
c.dialCh = make(chan peer.AddrInfo) c.WaitGroup().Add(1)
c.wg.Add(2) go c.dialPeers()
go c.shouldDialPeers(ctx)
go c.dialPeers(ctx)
c.consumeSubscriptions(ctx) c.consumeSubscriptions()
return nil return nil
} }
// Stop terminates the peer-connector // Stop terminates the peer-connector
func (c *PeerConnectionStrategy) Stop() { func (c *PeerConnectionStrategy) Stop() {
if c.cancel == nil { c.CommonDiscoveryService.Stop(func() {})
return
}
c.cancel()
c.cancel = nil
c.wg.Wait()
close(c.dialCh)
} }
func (c *PeerConnectionStrategy) isPaused() bool { func (c *PeerConnectionStrategy) isPaused() bool {
return c.paused.Load() return c.paused.Load()
} }
func (c *PeerConnectionStrategy) shouldDialPeers(ctx context.Context) {
defer c.wg.Done()
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
_, outRelayPeers := c.pm.getRelayPeers()
c.paused.Store(outRelayPeers.Len() >= c.pm.OutRelayPeersTarget) // pause if no of OutPeers more than or eq to target
}
}
}
// it might happen Subscribe is called before peerConnector has started so store these subscriptions in subscriptions array and custom after c.cancel is set. // it might happen Subscribe is called before peerConnector has started so store these subscriptions in subscriptions array and custom after c.cancel is set.
func (c *PeerConnectionStrategy) consumeSubscriptions(ctx context.Context) { func (c *PeerConnectionStrategy) consumeSubscriptions() {
for _, subs := range c.subscriptions { for _, subs := range c.subscriptions {
c.wg.Add(1) c.WaitGroup().Add(1)
go func(s <-chan PeerData) { go func(s <-chan PeerData) {
defer c.wg.Done() defer c.WaitGroup().Done()
c.consumeSubscription(ctx, s) c.consumeSubscription(s)
}(subs) }(subs)
} }
c.subscriptions = nil c.subscriptions = nil
} }
func (c *PeerConnectionStrategy) publishWork(ctx context.Context, p peer.AddrInfo) {
select {
case c.dialCh <- p:
case <-ctx.Done():
return
}
}
const maxActiveDials = 5 const maxActiveDials = 5
// c.cache is thread safe // c.cache is thread safe
@ -238,8 +199,8 @@ func (c *PeerConnectionStrategy) canDialPeer(pi peer.AddrInfo) bool {
return true return true
} }
func (c *PeerConnectionStrategy) dialPeers(ctx context.Context) { func (c *PeerConnectionStrategy) dialPeers() {
defer c.wg.Done() defer c.WaitGroup().Done()
maxGoRoutines := c.pm.OutRelayPeersTarget maxGoRoutines := c.pm.OutRelayPeersTarget
if maxGoRoutines > maxActiveDials { if maxGoRoutines > maxActiveDials {
@ -250,30 +211,31 @@ func (c *PeerConnectionStrategy) dialPeers(ctx context.Context) {
for { for {
select { select {
case pi, ok := <-c.dialCh: case pd, ok := <-c.GetListeningChan():
if !ok { if !ok {
return return
} }
addrInfo := pd.AddrInfo
if pi.ID == c.host.ID() || pi.ID == "" || if addrInfo.ID == c.host.ID() || addrInfo.ID == "" ||
c.host.Network().Connectedness(pi.ID) == network.Connected { c.host.Network().Connectedness(addrInfo.ID) == network.Connected {
continue continue
} }
if c.canDialPeer(pi) { if c.canDialPeer(addrInfo) {
sem <- struct{}{} sem <- struct{}{}
c.wg.Add(1) c.WaitGroup().Add(1)
go c.dialPeer(ctx, pi, sem) go c.dialPeer(addrInfo, sem)
} }
case <-ctx.Done(): case <-c.Context().Done():
return return
} }
} }
} }
func (c *PeerConnectionStrategy) dialPeer(ctx context.Context, pi peer.AddrInfo, sem chan struct{}) { func (c *PeerConnectionStrategy) dialPeer(pi peer.AddrInfo, sem chan struct{}) {
defer c.wg.Done() defer c.WaitGroup().Done()
ctx, cancel := context.WithTimeout(ctx, c.dialTimeout) ctx, cancel := context.WithTimeout(c.Context(), c.dialTimeout)
defer cancel() defer cancel()
err := c.host.Connect(ctx, pi) err := c.host.Connect(ctx, pi)
if err != nil && !errors.Is(err, context.Canceled) { if err != nil && !errors.Is(err, context.Canceled) {

View File

@ -2,8 +2,12 @@ package peermanager
import ( import (
"context" "context"
"errors"
"sync"
"time" "time"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
@ -12,18 +16,23 @@ import (
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/logging" "github.com/waku-org/go-waku/logging"
wps "github.com/waku-org/go-waku/waku/v2/peerstore" wps "github.com/waku-org/go-waku/waku/v2/peerstore"
waku_proto "github.com/waku-org/go-waku/waku/v2/protocol"
wenr "github.com/waku-org/go-waku/waku/v2/protocol/enr"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/utils" "github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap" "go.uber.org/zap"
) )
// WakuRelayIDv200 is protocol ID for Waku v2 relay protocol // NodeTopicDetails stores pubSubTopic related data like topicHandle for the node.
// TODO: Move all the protocol IDs to a common location. type NodeTopicDetails struct {
const WakuRelayIDv200 = protocol.ID("/vac/waku/relay/2.0.0") topic *pubsub.Topic
}
// PeerManager applies various controls and manage connections towards peers. // PeerManager applies various controls and manage connections towards peers.
type PeerManager struct { type PeerManager struct {
peerConnector *PeerConnectionStrategy peerConnector *PeerConnectionStrategy
maxPeers int
maxRelayPeers int maxRelayPeers int
logger *zap.Logger logger *zap.Logger
InRelayPeersTarget int InRelayPeersTarget int
@ -31,9 +40,13 @@ type PeerManager struct {
host host.Host host host.Host
serviceSlots *ServiceSlots serviceSlots *ServiceSlots
ctx context.Context ctx context.Context
sub event.Subscription
topicMutex sync.RWMutex
subRelayTopics map[string]*NodeTopicDetails
} }
const peerConnectivityLoopSecs = 15 const peerConnectivityLoopSecs = 15
const maxConnsToPeerRatio = 5
// 80% relay peers 20% service peers // 80% relay peers 20% service peers
func relayAndServicePeers(maxConnections int) (int, int) { func relayAndServicePeers(maxConnections int) (int, int) {
@ -52,22 +65,29 @@ func inAndOutRelayPeers(relayPeers int) (int, int) {
} }
// NewPeerManager creates a new peerManager instance. // NewPeerManager creates a new peerManager instance.
func NewPeerManager(maxConnections int, logger *zap.Logger) *PeerManager { func NewPeerManager(maxConnections int, maxPeers int, logger *zap.Logger) *PeerManager {
maxRelayPeers, _ := relayAndServicePeers(maxConnections) maxRelayPeers, _ := relayAndServicePeers(maxConnections)
inRelayPeersTarget, outRelayPeersTarget := inAndOutRelayPeers(maxRelayPeers) inRelayPeersTarget, outRelayPeersTarget := inAndOutRelayPeers(maxRelayPeers)
if maxPeers == 0 || maxConnections > maxPeers {
maxPeers = maxConnsToPeerRatio * maxConnections
}
pm := &PeerManager{ pm := &PeerManager{
logger: logger.Named("peer-manager"), logger: logger.Named("peer-manager"),
maxRelayPeers: maxRelayPeers, maxRelayPeers: maxRelayPeers,
InRelayPeersTarget: inRelayPeersTarget, InRelayPeersTarget: inRelayPeersTarget,
OutRelayPeersTarget: outRelayPeersTarget, OutRelayPeersTarget: outRelayPeersTarget,
serviceSlots: NewServiceSlot(), serviceSlots: NewServiceSlot(),
subRelayTopics: make(map[string]*NodeTopicDetails),
maxPeers: maxPeers,
} }
logger.Info("PeerManager init values", zap.Int("maxConnections", maxConnections), logger.Info("PeerManager init values", zap.Int("maxConnections", maxConnections),
zap.Int("maxRelayPeers", maxRelayPeers), zap.Int("maxRelayPeers", maxRelayPeers),
zap.Int("outRelayPeersTarget", outRelayPeersTarget), zap.Int("outRelayPeersTarget", outRelayPeersTarget),
zap.Int("inRelayPeersTarget", pm.InRelayPeersTarget)) zap.Int("inRelayPeersTarget", pm.InRelayPeersTarget),
zap.Int("maxPeers", maxPeers))
return pm return pm
} }
@ -85,11 +105,15 @@ func (pm *PeerManager) SetPeerConnector(pc *PeerConnectionStrategy) {
// Start starts the processing to be done by peer manager. // Start starts the processing to be done by peer manager.
func (pm *PeerManager) Start(ctx context.Context) { func (pm *PeerManager) Start(ctx context.Context) {
pm.ctx = ctx pm.ctx = ctx
if pm.sub != nil {
go pm.peerEventLoop(ctx)
}
go pm.connectivityLoop(ctx) go pm.connectivityLoop(ctx)
} }
// This is a connectivity loop, which currently checks and prunes inbound connections. // This is a connectivity loop, which currently checks and prunes inbound connections.
func (pm *PeerManager) connectivityLoop(ctx context.Context) { func (pm *PeerManager) connectivityLoop(ctx context.Context) {
pm.connectToRelayPeers()
t := time.NewTicker(peerConnectivityLoopSecs * time.Second) t := time.NewTicker(peerConnectivityLoopSecs * time.Second)
defer t.Stop() defer t.Stop()
for { for {
@ -103,10 +127,12 @@ func (pm *PeerManager) connectivityLoop(ctx context.Context) {
} }
// GroupPeersByDirection returns all the connected peers in peer store grouped by Inbound or outBound direction // GroupPeersByDirection returns all the connected peers in peer store grouped by Inbound or outBound direction
func (pm *PeerManager) GroupPeersByDirection() (inPeers peer.IDSlice, outPeers peer.IDSlice, err error) { func (pm *PeerManager) GroupPeersByDirection(specificPeers ...peer.ID) (inPeers peer.IDSlice, outPeers peer.IDSlice, err error) {
peers := pm.host.Network().Peers() if len(specificPeers) == 0 {
specificPeers = pm.host.Network().Peers()
}
for _, p := range peers { for _, p := range specificPeers {
direction, err := pm.host.Peerstore().(wps.WakuPeerstore).Direction(p) direction, err := pm.host.Peerstore().(wps.WakuPeerstore).Direction(p)
if err == nil { if err == nil {
if direction == network.DirInbound { if direction == network.DirInbound {
@ -122,9 +148,11 @@ func (pm *PeerManager) GroupPeersByDirection() (inPeers peer.IDSlice, outPeers p
return inPeers, outPeers, nil return inPeers, outPeers, nil
} }
func (pm *PeerManager) getRelayPeers() (inRelayPeers peer.IDSlice, outRelayPeers peer.IDSlice) { // getRelayPeers - Returns list of in and out peers supporting WakuRelayProtocol within specifiedPeers.
// If specifiedPeers is empty, it checks within all peers in peerStore.
func (pm *PeerManager) getRelayPeers(specificPeers ...peer.ID) (inRelayPeers peer.IDSlice, outRelayPeers peer.IDSlice) {
//Group peers by their connected direction inbound or outbound. //Group peers by their connected direction inbound or outbound.
inPeers, outPeers, err := pm.GroupPeersByDirection() inPeers, outPeers, err := pm.GroupPeersByDirection(specificPeers...)
if err != nil { if err != nil {
return return
} }
@ -133,59 +161,99 @@ func (pm *PeerManager) getRelayPeers() (inRelayPeers peer.IDSlice, outRelayPeers
//Need to filter peers to check if they support relay //Need to filter peers to check if they support relay
if inPeers.Len() != 0 { if inPeers.Len() != 0 {
inRelayPeers, _ = utils.FilterPeersByProto(pm.host, inPeers, WakuRelayIDv200) inRelayPeers, _ = utils.FilterPeersByProto(pm.host, inPeers, relay.WakuRelayID_v200)
} }
if outPeers.Len() != 0 { if outPeers.Len() != 0 {
outRelayPeers, _ = utils.FilterPeersByProto(pm.host, outPeers, WakuRelayIDv200) outRelayPeers, _ = utils.FilterPeersByProto(pm.host, outPeers, relay.WakuRelayID_v200)
} }
return return
} }
func (pm *PeerManager) connectToRelayPeers() { // ensureMinRelayConnsPerTopic makes sure there are min of D conns per pubsubTopic.
// If not it will look into peerStore to initiate more connections.
// If peerStore doesn't have enough peers, will wait for discv5 to find more and try in next cycle
func (pm *PeerManager) ensureMinRelayConnsPerTopic() {
pm.topicMutex.RLock()
defer pm.topicMutex.RUnlock()
for topicStr, topicInst := range pm.subRelayTopics {
curPeers := topicInst.topic.ListPeers()
curPeerLen := len(curPeers)
if curPeerLen < waku_proto.GossipSubOptimalFullMeshSize {
pm.logger.Info("Subscribed topic is unhealthy, initiating more connections to maintain health",
zap.String("pubSubTopic", topicStr), zap.Int("connectedPeerCount", curPeerLen),
zap.Int("optimumPeers", waku_proto.GossipSubOptimalFullMeshSize))
//Find not connected peers.
notConnectedPeers := pm.getNotConnectedPers(topicStr)
if notConnectedPeers.Len() == 0 {
//TODO: Trigger on-demand discovery for this topic.
continue
}
//Connect to eligible peers.
numPeersToConnect := waku_proto.GossipSubOptimalFullMeshSize - curPeerLen
if numPeersToConnect > notConnectedPeers.Len() {
numPeersToConnect = notConnectedPeers.Len()
}
pm.connectToPeers(notConnectedPeers[0:numPeersToConnect])
}
}
}
// connectToRelayPeers ensures minimum D connections are there for each pubSubTopic.
// If not, initiates connections to additional peers.
// It also checks for incoming relay connections and prunes once they cross inRelayTarget
func (pm *PeerManager) connectToRelayPeers() {
//Check for out peer connections and connect to more peers. //Check for out peer connections and connect to more peers.
pm.ensureMinRelayConnsPerTopic()
inRelayPeers, outRelayPeers := pm.getRelayPeers() inRelayPeers, outRelayPeers := pm.getRelayPeers()
pm.logger.Info("Number of Relay peers connected", zap.Int("inRelayPeers", inRelayPeers.Len()), pm.logger.Info("number of relay peers connected",
zap.Int("outRelayPeers", outRelayPeers.Len())) zap.Int("in", inRelayPeers.Len()),
zap.Int("out", outRelayPeers.Len()))
if inRelayPeers.Len() > 0 && if inRelayPeers.Len() > 0 &&
inRelayPeers.Len() > pm.InRelayPeersTarget { inRelayPeers.Len() > pm.InRelayPeersTarget {
pm.pruneInRelayConns(inRelayPeers) pm.pruneInRelayConns(inRelayPeers)
} }
if outRelayPeers.Len() > pm.OutRelayPeersTarget {
return
}
totalRelayPeers := inRelayPeers.Len() + outRelayPeers.Len()
// Establish additional connections connected peers are lesser than target.
//What if the not connected peers in peerstore are not relay peers???
if totalRelayPeers < pm.maxRelayPeers {
//Find not connected peers.
notConnectedPeers := pm.getNotConnectedPers()
if notConnectedPeers.Len() == 0 {
return
}
//Connect to eligible peers.
numPeersToConnect := pm.maxRelayPeers - totalRelayPeers
if numPeersToConnect > notConnectedPeers.Len() {
numPeersToConnect = notConnectedPeers.Len()
}
pm.connectToPeers(notConnectedPeers[0:numPeersToConnect])
} //Else: Should we raise some sort of unhealthy event??
} }
// addrInfoToPeerData returns addressinfo for a peer
// If addresses are expired, it removes the peer from host peerStore and returns nil.
func addrInfoToPeerData(origin wps.Origin, peerID peer.ID, host host.Host) *PeerData {
addrs := host.Peerstore().Addrs(peerID)
if len(addrs) == 0 {
//Addresses expired, remove peer from peerStore
host.Peerstore().RemovePeer(peerID)
return nil
}
return &PeerData{
Origin: origin,
AddrInfo: peer.AddrInfo{
ID: peerID,
Addrs: addrs,
},
}
}
// connectToPeers connects to peers provided in the list if the addresses have not expired.
func (pm *PeerManager) connectToPeers(peers peer.IDSlice) { func (pm *PeerManager) connectToPeers(peers peer.IDSlice) {
for _, peerID := range peers { for _, peerID := range peers {
peerInfo := peer.AddrInfo{ peerData := addrInfoToPeerData(wps.PeerManager, peerID, pm.host)
ID: peerID, if peerData == nil {
Addrs: pm.host.Peerstore().Addrs(peerID), continue
} }
pm.peerConnector.publishWork(pm.ctx, peerInfo) pm.peerConnector.PushToChan(*peerData)
} }
} }
func (pm *PeerManager) getNotConnectedPers() (notConnectedPeers peer.IDSlice) { // getNotConnectedPers returns peers for a pubSubTopic that are not connected.
for _, peerID := range pm.host.Peerstore().Peers() { func (pm *PeerManager) getNotConnectedPers(pubsubTopic string) (notConnectedPeers peer.IDSlice) {
var peerList peer.IDSlice
if pubsubTopic == "" {
peerList = pm.host.Peerstore().Peers()
} else {
peerList = pm.host.Peerstore().(*wps.WakuPeerstoreImpl).PeersByPubSubTopic(pubsubTopic)
}
for _, peerID := range peerList {
if pm.host.Network().Connectedness(peerID) != network.Connected { if pm.host.Network().Connectedness(peerID) != network.Connected {
notConnectedPeers = append(notConnectedPeers, peerID) notConnectedPeers = append(notConnectedPeers, peerID)
} }
@ -193,13 +261,15 @@ func (pm *PeerManager) getNotConnectedPers() (notConnectedPeers peer.IDSlice) {
return return
} }
// pruneInRelayConns prune any incoming relay connections crossing derived inrelayPeerTarget
func (pm *PeerManager) pruneInRelayConns(inRelayPeers peer.IDSlice) { func (pm *PeerManager) pruneInRelayConns(inRelayPeers peer.IDSlice) {
//Start disconnecting peers, based on what? //Start disconnecting peers, based on what?
//For now, just disconnect most recently connected peers //For now no preference is used
//TODO: Need to have more intelligent way of doing this, maybe peer scores. //TODO: Need to have more intelligent way of doing this, maybe peer scores.
pm.logger.Info("Number of in peer connections exceed targer relay peers, hence pruning", //TODO: Keep optimalPeersRequired for a pubSubTopic in mind while pruning connections to peers.
zap.Int("inRelayPeers", inRelayPeers.Len()), zap.Int("inRelayPeersTarget", pm.InRelayPeersTarget)) pm.logger.Info("peer connections exceed target relay peers, hence pruning",
zap.Int("cnt", inRelayPeers.Len()), zap.Int("target", pm.InRelayPeersTarget))
for pruningStartIndex := pm.InRelayPeersTarget; pruningStartIndex < inRelayPeers.Len(); pruningStartIndex++ { for pruningStartIndex := pm.InRelayPeersTarget; pruningStartIndex < inRelayPeers.Len(); pruningStartIndex++ {
p := inRelayPeers[pruningStartIndex] p := inRelayPeers[pruningStartIndex]
err := pm.host.Network().ClosePeer(p) err := pm.host.Network().ClosePeer(p)
@ -215,9 +285,38 @@ func (pm *PeerManager) pruneInRelayConns(inRelayPeers peer.IDSlice) {
// AddDiscoveredPeer to add dynamically discovered peers. // AddDiscoveredPeer to add dynamically discovered peers.
// Note that these peers will not be set in service-slots. // Note that these peers will not be set in service-slots.
// TODO: It maybe good to set in service-slots based on services supported in the ENR // TODO: It maybe good to set in service-slots based on services supported in the ENR
func (pm *PeerManager) AddDiscoveredPeer(p PeerData) { func (pm *PeerManager) AddDiscoveredPeer(p PeerData, connectNow bool) {
//Doing this check again inside addPeer, in order to avoid additional complexity of rollingBack other changes.
if pm.maxPeers <= pm.host.Peerstore().Peers().Len() {
return
}
//Check if the peer is already present, if so skip adding
_, err := pm.host.Peerstore().(wps.WakuPeerstore).Origin(p.AddrInfo.ID)
if err == nil {
pm.logger.Debug("Found discovered peer already in peerStore", logging.HostID("peer", p.AddrInfo.ID))
return
}
// Try to fetch shard info from ENR to arrive at pubSub topics.
if len(p.PubSubTopics) == 0 && p.ENR != nil {
shards, err := wenr.RelaySharding(p.ENR.Record())
if err != nil {
pm.logger.Error("Could not derive relayShards from ENR", zap.Error(err),
logging.HostID("peer", p.AddrInfo.ID), zap.String("enr", p.ENR.String()))
} else {
if shards != nil {
p.PubSubTopics = make([]string, 0)
topics := shards.Topics()
for _, topic := range topics {
topicStr := topic.String()
p.PubSubTopics = append(p.PubSubTopics, topicStr)
}
} else {
pm.logger.Debug("ENR doesn't have relay shards", logging.HostID("peer", p.AddrInfo.ID))
}
}
}
_ = pm.addPeer(p.AddrInfo.ID, p.AddrInfo.Addrs, p.Origin) _ = pm.addPeer(p.AddrInfo.ID, p.AddrInfo.Addrs, p.Origin, p.PubSubTopics)
if p.ENR != nil { if p.ENR != nil {
err := pm.host.Peerstore().(wps.WakuPeerstore).SetENR(p.AddrInfo.ID, p.ENR) err := pm.host.Peerstore().(wps.WakuPeerstore).SetENR(p.AddrInfo.ID, p.ENR)
@ -226,13 +325,25 @@ func (pm *PeerManager) AddDiscoveredPeer(p PeerData) {
logging.HostID("peer", p.AddrInfo.ID), zap.String("enr", p.ENR.String())) logging.HostID("peer", p.AddrInfo.ID), zap.String("enr", p.ENR.String()))
} }
} }
if connectNow {
pm.peerConnector.PushToChan(p)
}
} }
// addPeer adds peer to only the peerStore. // addPeer adds peer to only the peerStore.
// It also sets additional metadata such as origin, ENR and supported protocols // It also sets additional metadata such as origin, ENR and supported protocols
func (pm *PeerManager) addPeer(ID peer.ID, addrs []ma.Multiaddr, origin wps.Origin, protocols ...protocol.ID) error { func (pm *PeerManager) addPeer(ID peer.ID, addrs []ma.Multiaddr, origin wps.Origin, pubSubTopics []string, protocols ...protocol.ID) error {
if pm.maxPeers <= pm.host.Peerstore().Peers().Len() {
return errors.New("peer store capacity reached")
}
pm.logger.Info("adding peer to peerstore", logging.HostID("peer", ID)) pm.logger.Info("adding peer to peerstore", logging.HostID("peer", ID))
pm.host.Peerstore().AddAddrs(ID, addrs, peerstore.AddressTTL) if origin == wps.Static {
pm.host.Peerstore().AddAddrs(ID, addrs, peerstore.PermanentAddrTTL)
} else {
//Need to re-evaluate the address expiry
// For now expiring them with default addressTTL which is an hour.
pm.host.Peerstore().AddAddrs(ID, addrs, peerstore.AddressTTL)
}
err := pm.host.Peerstore().(wps.WakuPeerstore).SetOrigin(ID, origin) err := pm.host.Peerstore().(wps.WakuPeerstore).SetOrigin(ID, origin)
if err != nil { if err != nil {
pm.logger.Error("could not set origin", zap.Error(err), logging.HostID("peer", ID)) pm.logger.Error("could not set origin", zap.Error(err), logging.HostID("peer", ID))
@ -245,11 +356,21 @@ func (pm *PeerManager) addPeer(ID peer.ID, addrs []ma.Multiaddr, origin wps.Orig
return err return err
} }
} }
if len(pubSubTopics) == 0 {
// Probably the peer is discovered via DNSDiscovery (for which we don't have pubSubTopic info)
//If pubSubTopic and enr is empty or no shard info in ENR,then set to defaultPubSubTopic
pubSubTopics = []string{relay.DefaultWakuTopic}
}
err = pm.host.Peerstore().(wps.WakuPeerstore).SetPubSubTopics(ID, pubSubTopics)
if err != nil {
pm.logger.Error("could not store pubSubTopic", zap.Error(err),
logging.HostID("peer", ID), zap.Strings("topics", pubSubTopics))
}
return nil return nil
} }
// AddPeer adds peer to the peerStore and also to service slots // AddPeer adds peer to the peerStore and also to service slots
func (pm *PeerManager) AddPeer(address ma.Multiaddr, origin wps.Origin, protocols ...protocol.ID) (peer.ID, error) { func (pm *PeerManager) AddPeer(address ma.Multiaddr, origin wps.Origin, pubSubTopics []string, protocols ...protocol.ID) (peer.ID, error) {
//Assuming all addresses have peerId //Assuming all addresses have peerId
info, err := peer.AddrInfoFromP2pAddr(address) info, err := peer.AddrInfoFromP2pAddr(address)
if err != nil { if err != nil {
@ -262,7 +383,7 @@ func (pm *PeerManager) AddPeer(address ma.Multiaddr, origin wps.Origin, protocol
} }
//Add to the peer-store //Add to the peer-store
err = pm.addPeer(info.ID, info.Addrs, origin, protocols...) err = pm.addPeer(info.ID, info.Addrs, origin, pubSubTopics, protocols...)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -283,7 +404,7 @@ func (pm *PeerManager) RemovePeer(peerID peer.ID) {
// Adding to peerStore is expected to be already done by caller. // Adding to peerStore is expected to be already done by caller.
// If relay proto is passed, it is not added to serviceSlot. // If relay proto is passed, it is not added to serviceSlot.
func (pm *PeerManager) addPeerToServiceSlot(proto protocol.ID, peerID peer.ID) { func (pm *PeerManager) addPeerToServiceSlot(proto protocol.ID, peerID peer.ID) {
if proto == WakuRelayIDv200 { if proto == relay.WakuRelayID_v200 {
pm.logger.Warn("Cannot add Relay peer to service peer slots") pm.logger.Warn("Cannot add Relay peer to service peer slots")
return return
} }
@ -296,22 +417,32 @@ func (pm *PeerManager) addPeerToServiceSlot(proto protocol.ID, peerID peer.ID) {
pm.serviceSlots.getPeers(proto).add(peerID) pm.serviceSlots.getPeers(proto).add(peerID)
} }
// SelectPeerByContentTopic is used to return a random peer that supports a given protocol for given contentTopic.
// If a list of specific peers is passed, the peer will be chosen from that list assuming
// it supports the chosen protocol and contentTopic, otherwise it will chose a peer from the service slot.
// If a peer cannot be found in the service slot, a peer will be selected from node peerstore
func (pm *PeerManager) SelectPeerByContentTopic(proto protocol.ID, contentTopic string, specificPeers ...peer.ID) (peer.ID, error) {
pubsubTopic, err := waku_proto.GetPubSubTopicFromContentTopic(contentTopic)
if err != nil {
return "", err
}
return pm.SelectPeer(proto, pubsubTopic, specificPeers...)
}
// SelectPeer is used to return a random peer that supports a given protocol. // SelectPeer is used to return a random peer that supports a given protocol.
// If a list of specific peers is passed, the peer will be chosen from that list assuming // If a list of specific peers is passed, the peer will be chosen from that list assuming
// it supports the chosen protocol, otherwise it will chose a peer from the service slot. // it supports the chosen protocol, otherwise it will chose a peer from the service slot.
// If a peer cannot be found in the service slot, a peer will be selected from node peerstore // If a peer cannot be found in the service slot, a peer will be selected from node peerstore
func (pm *PeerManager) SelectPeer(proto protocol.ID, specificPeers []peer.ID, logger *zap.Logger) (peer.ID, error) { // if pubSubTopic is specified, peer is selected from list that support the pubSubTopic
func (pm *PeerManager) SelectPeer(proto protocol.ID, pubSubTopic string, specificPeers ...peer.ID) (peer.ID, error) {
// @TODO We need to be more strategic about which peers we dial. Right now we just set one on the service. // @TODO We need to be more strategic about which peers we dial. Right now we just set one on the service.
// Ideally depending on the query and our set of peers we take a subset of ideal peers. // Ideally depending on the query and our set of peers we take a subset of ideal peers.
// This will require us to check for various factors such as: // This will require us to check for various factors such as:
// - which topics they track // - which topics they track
// - latency? // - latency?
//Try to fetch from serviceSlot if peerID := pm.selectServicePeer(proto, pubSubTopic, specificPeers...); peerID != nil {
if slot := pm.serviceSlots.getPeers(proto); slot != nil { return *peerID, nil
if peerID, err := slot.getRandom(); err == nil {
return peerID, nil
}
} }
// if not found in serviceSlots or proto == WakuRelayIDv200 // if not found in serviceSlots or proto == WakuRelayIDv200
@ -319,5 +450,36 @@ func (pm *PeerManager) SelectPeer(proto protocol.ID, specificPeers []peer.ID, lo
if err != nil { if err != nil {
return "", err return "", err
} }
if pubSubTopic != "" {
filteredPeers = pm.host.Peerstore().(wps.WakuPeerstore).PeersByPubSubTopic(pubSubTopic, filteredPeers...)
}
return utils.SelectRandomPeer(filteredPeers, pm.logger) return utils.SelectRandomPeer(filteredPeers, pm.logger)
} }
func (pm *PeerManager) selectServicePeer(proto protocol.ID, pubSubTopic string, specificPeers ...peer.ID) (peerIDPtr *peer.ID) {
peerIDPtr = nil
//Try to fetch from serviceSlot
if slot := pm.serviceSlots.getPeers(proto); slot != nil {
if pubSubTopic == "" {
if peerID, err := slot.getRandom(); err == nil {
peerIDPtr = &peerID
} else {
pm.logger.Debug("could not retrieve random peer from slot", zap.Error(err))
}
} else { //PubsubTopic based selection
keys := make([]peer.ID, 0, len(slot.m))
for i := range slot.m {
keys = append(keys, i)
}
selectedPeers := pm.host.Peerstore().(wps.WakuPeerstore).PeersByPubSubTopic(pubSubTopic, keys...)
peerID, err := utils.SelectRandomPeer(selectedPeers, pm.logger)
if err == nil {
peerIDPtr = &peerID
} else {
pm.logger.Debug("could not select random peer", zap.Error(err))
}
}
}
return
}

View File

@ -5,6 +5,7 @@ import (
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/utils" "github.com/waku-org/go-waku/waku/v2/utils"
) )
@ -57,7 +58,7 @@ func NewServiceSlot() *ServiceSlots {
// getPeers for getting all the peers for a given protocol // getPeers for getting all the peers for a given protocol
// since peerMap is only used in peerManager that's why it is unexported // since peerMap is only used in peerManager that's why it is unexported
func (slots *ServiceSlots) getPeers(proto protocol.ID) *peerMap { func (slots *ServiceSlots) getPeers(proto protocol.ID) *peerMap {
if proto == WakuRelayIDv200 { if proto == relay.WakuRelayID_v200 {
return nil return nil
} }
slots.mu.Lock() slots.mu.Lock()

View File

@ -0,0 +1,166 @@
package peermanager
import (
"context"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/logging"
wps "github.com/waku-org/go-waku/waku/v2/peerstore"
waku_proto "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"go.uber.org/zap"
)
func (pm *PeerManager) SubscribeToRelayEvtBus(bus event.Bus) error {
var err error
pm.sub, err = bus.Subscribe([]interface{}{new(relay.EvtPeerTopic), new(relay.EvtRelaySubscribed), new(relay.EvtRelayUnsubscribed)})
return err
}
func (pm *PeerManager) handleNewRelayTopicSubscription(pubsubTopic string, topicInst *pubsub.Topic) {
pm.logger.Info("handleNewRelayTopicSubscription", zap.String("pubSubTopic", pubsubTopic))
pm.topicMutex.Lock()
defer pm.topicMutex.Unlock()
_, ok := pm.subRelayTopics[pubsubTopic]
if ok {
//Nothing to be done, as we are already subscribed to this topic.
return
}
pm.subRelayTopics[pubsubTopic] = &NodeTopicDetails{topicInst}
//Check how many relay peers we are connected to that subscribe to this topic, if less than D find peers in peerstore and connect.
//If no peers in peerStore, trigger discovery for this topic?
relevantPeersForPubSubTopic := pm.host.Peerstore().(*wps.WakuPeerstoreImpl).PeersByPubSubTopic(pubsubTopic)
var notConnectedPeers peer.IDSlice
connectedPeers := 0
for _, peer := range relevantPeersForPubSubTopic {
if pm.host.Network().Connectedness(peer) == network.Connected {
connectedPeers++
} else {
notConnectedPeers = append(notConnectedPeers, peer)
}
}
if connectedPeers >= waku_proto.GossipSubOptimalFullMeshSize { //TODO: Use a config rather than hard-coding.
// Should we use optimal number or define some sort of a config for the node to choose from?
// A desktop node may choose this to be 4-6, whereas a service node may choose this to be 8-12 based on resources it has
// or bandwidth it can support.
// Should we link this to bandwidth management somehow or just depend on some sort of config profile?
pm.logger.Info("Optimal required relay peers for new pubSubTopic are already connected ", zap.String("pubSubTopic", pubsubTopic),
zap.Int("connectedPeerCount", connectedPeers))
return
}
triggerDiscovery := false
if notConnectedPeers.Len() > 0 {
numPeersToConnect := notConnectedPeers.Len() - connectedPeers
if numPeersToConnect < 0 {
numPeersToConnect = notConnectedPeers.Len()
} else if numPeersToConnect-connectedPeers > waku_proto.GossipSubOptimalFullMeshSize {
numPeersToConnect = waku_proto.GossipSubOptimalFullMeshSize - connectedPeers
}
if numPeersToConnect+connectedPeers < waku_proto.GossipSubOptimalFullMeshSize {
triggerDiscovery = true
}
//For now all peers are being given same priority,
// Later we may want to choose peers that have more shards in common over others.
pm.connectToPeers(notConnectedPeers[0:numPeersToConnect])
} else {
triggerDiscovery = true
}
if triggerDiscovery {
//TODO: Initiate on-demand discovery for this pubSubTopic.
// Use peer-exchange and rendevouz?
//Should we query discoverycache to find out if there are any more peers before triggering discovery?
return
}
}
func (pm *PeerManager) handleNewRelayTopicUnSubscription(pubsubTopic string) {
pm.logger.Info("handleNewRelayTopicUnSubscription", zap.String("pubSubTopic", pubsubTopic))
pm.topicMutex.Lock()
defer pm.topicMutex.Unlock()
_, ok := pm.subRelayTopics[pubsubTopic]
if !ok {
//Nothing to be done, as we are already unsubscribed from this topic.
return
}
delete(pm.subRelayTopics, pubsubTopic)
//If there are peers only subscribed to this topic, disconnect them.
relevantPeersForPubSubTopic := pm.host.Peerstore().(*wps.WakuPeerstoreImpl).PeersByPubSubTopic(pubsubTopic)
for _, peer := range relevantPeersForPubSubTopic {
if pm.host.Network().Connectedness(peer) == network.Connected {
peerTopics, err := pm.host.Peerstore().(*wps.WakuPeerstoreImpl).PubSubTopics(peer)
if err != nil {
pm.logger.Error("Could not retrieve pubsub topics for peer", zap.Error(err),
logging.HostID("peerID", peer))
continue
}
if len(peerTopics) == 1 && peerTopics[0] == pubsubTopic {
err := pm.host.Network().ClosePeer(peer)
if err != nil {
pm.logger.Warn("Failed to disconnect connection towards peer",
logging.HostID("peerID", peer))
continue
}
pm.logger.Debug("Successfully disconnected connection towards peer",
logging.HostID("peerID", peer))
}
}
}
}
func (pm *PeerManager) handlerPeerTopicEvent(peerEvt relay.EvtPeerTopic) {
wps := pm.host.Peerstore().(*wps.WakuPeerstoreImpl)
peerID := peerEvt.PeerID
if peerEvt.State == relay.PEER_JOINED {
err := wps.AddPubSubTopic(peerID, peerEvt.PubsubTopic)
if err != nil {
pm.logger.Error("failed to add pubSubTopic for peer",
logging.HostID("peerID", peerID), zap.String("topic", peerEvt.PubsubTopic), zap.Error(err))
}
} else if peerEvt.State == relay.PEER_LEFT {
err := wps.RemovePubSubTopic(peerID, peerEvt.PubsubTopic)
if err != nil {
pm.logger.Error("failed to remove pubSubTopic for peer",
logging.HostID("peerID", peerID), zap.Error(err))
}
} else {
pm.logger.Error("unknown peer event received", zap.Int("eventState", int(peerEvt.State)))
}
}
func (pm *PeerManager) peerEventLoop(ctx context.Context) {
defer pm.sub.Close()
for {
select {
case e := <-pm.sub.Out():
switch e := e.(type) {
case relay.EvtPeerTopic:
{
peerEvt := (relay.EvtPeerTopic)(e)
pm.handlerPeerTopicEvent(peerEvt)
}
case relay.EvtRelaySubscribed:
{
eventDetails := (relay.EvtRelaySubscribed)(e)
pm.handleNewRelayTopicSubscription(eventDetails.Topic, eventDetails.TopicInst)
}
case relay.EvtRelayUnsubscribed:
{
eventDetails := (relay.EvtRelayUnsubscribed)(e)
pm.handleNewRelayTopicUnSubscription(eventDetails.Topic)
}
default:
pm.logger.Error("unsupported event type", zap.Any("eventType", e))
}
case <-ctx.Done():
return
}
}
}

View File

@ -1,6 +1,7 @@
package peerstore package peerstore
import ( import (
"errors"
"sync" "sync"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
@ -18,13 +19,15 @@ const (
Discv5 Discv5
Static Static
PeerExchange PeerExchange
DnsDiscovery DNSDiscovery
Rendezvous Rendezvous
PeerManager
) )
const peerOrigin = "origin" const peerOrigin = "origin"
const peerENR = "enr" const peerENR = "enr"
const peerDirection = "direction" const peerDirection = "direction"
const peerPubSubTopics = "pubSubTopics"
// ConnectionFailures contains connection failure information towards all peers // ConnectionFailures contains connection failure information towards all peers
type ConnectionFailures struct { type ConnectionFailures struct {
@ -51,6 +54,12 @@ type WakuPeerstore interface {
SetDirection(p peer.ID, direction network.Direction) error SetDirection(p peer.ID, direction network.Direction) error
Direction(p peer.ID) (network.Direction, error) Direction(p peer.ID) (network.Direction, error)
AddPubSubTopic(p peer.ID, topic string) error
RemovePubSubTopic(p peer.ID, topic string) error
PubSubTopics(p peer.ID) ([]string, error)
SetPubSubTopics(p peer.ID, topics []string) error
PeersByPubSubTopic(pubSubTopic string, specificPeers ...peer.ID) peer.IDSlice
} }
// NewWakuPeerstore creates a new WakuPeerStore object // NewWakuPeerstore creates a new WakuPeerStore object
@ -139,3 +148,81 @@ func (ps *WakuPeerstoreImpl) Direction(p peer.ID) (network.Direction, error) {
return result.(network.Direction), nil return result.(network.Direction), nil
} }
// AddPubSubTopic adds a new pubSubTopic for a peer
func (ps *WakuPeerstoreImpl) AddPubSubTopic(p peer.ID, topic string) error {
existingTopics, err := ps.PubSubTopics(p)
if err != nil {
return err
}
for _, t := range existingTopics {
if t == topic {
return nil
}
}
existingTopics = append(existingTopics, topic)
return ps.peerStore.Put(p, peerPubSubTopics, existingTopics)
}
// RemovePubSubTopic removes a pubSubTopic from the peer
func (ps *WakuPeerstoreImpl) RemovePubSubTopic(p peer.ID, topic string) error {
existingTopics, err := ps.PubSubTopics(p)
if err != nil {
return err
}
if len(existingTopics) == 0 {
return nil
}
for i := range existingTopics {
if existingTopics[i] == topic {
existingTopics = append(existingTopics[:i], existingTopics[i+1:]...)
break
}
}
err = ps.SetPubSubTopics(p, existingTopics)
if err != nil {
return err
}
return nil
}
// SetPubSubTopics sets pubSubTopics for a peer, it also overrides existing ones that were set previously..
func (ps *WakuPeerstoreImpl) SetPubSubTopics(p peer.ID, topics []string) error {
return ps.peerStore.Put(p, peerPubSubTopics, topics)
}
// PubSubTopics fetches list of pubSubTopics for a peer
func (ps *WakuPeerstoreImpl) PubSubTopics(p peer.ID) ([]string, error) {
result, err := ps.peerStore.Get(p, peerPubSubTopics)
if err != nil {
if errors.Is(err, peerstore.ErrNotFound) {
return nil, nil
} else {
return nil, err
}
}
return result.([]string), nil
}
// PeersByPubSubTopic Returns list of peers by pubSubTopic
// If specifiPeers are listed, filtering is done from them otherwise from all peers in peerstore
func (ps *WakuPeerstoreImpl) PeersByPubSubTopic(pubSubTopic string, specificPeers ...peer.ID) peer.IDSlice {
if specificPeers == nil {
specificPeers = ps.Peers()
}
var result peer.IDSlice
for _, p := range specificPeers {
topics, err := ps.PubSubTopics(p)
if err == nil {
for _, topic := range topics {
if topic == pubSubTopic {
result = append(result, p)
}
}
} //Note: skipping a peer in case of an error as there would be others available.
}
return result
}

View File

@ -0,0 +1,73 @@
package protocol
import (
"context"
"errors"
"sync"
)
// this is common layout for all the services that require mutex protection and a guarantee that all running goroutines will be finished before stop finishes execution. This guarantee comes from waitGroup all one has to use CommonService.WaitGroup() in the goroutines that should finish by the end of stop function.
type CommonService struct {
sync.RWMutex
cancel context.CancelFunc
ctx context.Context
wg sync.WaitGroup
started bool
}
func NewCommonService() *CommonService {
return &CommonService{
wg: sync.WaitGroup{},
RWMutex: sync.RWMutex{},
}
}
// mutex protected start function
// creates internal context over provided context and runs fn safely
// fn is excerpt to be executed to start the protocol
func (sp *CommonService) Start(ctx context.Context, fn func() error) error {
sp.Lock()
defer sp.Unlock()
if sp.started {
return ErrAlreadyStarted
}
sp.started = true
sp.ctx, sp.cancel = context.WithCancel(ctx)
if err := fn(); err != nil {
sp.started = false
sp.cancel()
return err
}
return nil
}
var ErrAlreadyStarted = errors.New("already started")
var ErrNotStarted = errors.New("not started")
// mutex protected stop function
func (sp *CommonService) Stop(fn func()) {
sp.Lock()
defer sp.Unlock()
if !sp.started {
return
}
sp.cancel()
fn()
sp.wg.Wait()
sp.started = false
}
// This is not a mutex protected function, it is up to the caller to use it in a mutex protected context
func (sp *CommonService) ErrOnNotRunning() error {
if !sp.started {
return ErrNotStarted
}
return nil
}
func (sp *CommonService) Context() context.Context {
return sp.ctx
}
func (sp *CommonService) WaitGroup() *sync.WaitGroup {
return &sp.wg
}

View File

@ -0,0 +1,30 @@
package protocol
import "golang.org/x/exp/maps"
type ContentTopicSet map[string]struct{}
func NewContentTopicSet(contentTopics ...string) ContentTopicSet {
s := make(ContentTopicSet, len(contentTopics))
for _, ct := range contentTopics {
s[ct] = struct{}{}
}
return s
}
// ContentFilter is used to specify the filter to be applied for a FilterNode.
// Topic means pubSubTopic - optional in case of using contentTopics that following Auto sharding, mandatory in case of named or static sharding.
// ContentTopics - Specify list of content topics to be filtered under a pubSubTopic (for named and static sharding), or a list of contentTopics (in case ofAuto sharding)
// If pubSub topic is not specified, then content-topics are used to derive the shard and corresponding pubSubTopic using autosharding algorithm
type ContentFilter struct {
PubsubTopic string
ContentTopics ContentTopicSet
}
func (cf ContentFilter) ContentTopicsList() []string {
return maps.Keys(cf.ContentTopics)
}
func NewContentFilter(pubsubTopic string, contentTopics ...string) ContentFilter {
return ContentFilter{pubsubTopic, NewContentTopicSet(contentTopics...)}
}

View File

@ -28,7 +28,7 @@ type WakuEnrBitfield = uint8
// NewWakuEnrBitfield creates a WakuEnrBitField whose value will depend on which protocols are enabled in the node // NewWakuEnrBitfield creates a WakuEnrBitField whose value will depend on which protocols are enabled in the node
func NewWakuEnrBitfield(lightpush, filter, store, relay bool) WakuEnrBitfield { func NewWakuEnrBitfield(lightpush, filter, store, relay bool) WakuEnrBitfield {
var v uint8 = 0 var v uint8
if lightpush { if lightpush {
v |= (1 << 3) v |= (1 << 3)
@ -91,10 +91,9 @@ func Multiaddress(node *enode.Node) (peer.ID, []multiaddr.Multiaddr, error) {
if err := node.Record().Load(enr.WithEntry(MultiaddrENRField, &multiaddrRaw)); err != nil { if err := node.Record().Load(enr.WithEntry(MultiaddrENRField, &multiaddrRaw)); err != nil {
if !enr.IsNotFound(err) { if !enr.IsNotFound(err) {
return "", nil, err return "", nil, err
} else {
// No multiaddr entry on enr
return peerID, result, nil
} }
// No multiaddr entry on enr
return peerID, result, nil
} }
if len(multiaddrRaw) < 2 { if len(multiaddrRaw) < 2 {

View File

@ -35,15 +35,14 @@ func WithMultiaddress(multiaddrs ...multiaddr.Multiaddr) ENROption {
failedOnceWritingENR := false failedOnceWritingENR := false
couldWriteENRatLeastOnce := false couldWriteENRatLeastOnce := false
successIdx := -1 successIdx := -1
for i := len(multiaddrs) - 1; i >= 0; i-- { for i := len(multiaddrs); i > 0; i-- {
err = writeMultiaddressField(localnode, multiaddrs[0:i]) err = writeMultiaddressField(localnode, multiaddrs[0:i])
if err == nil { if err == nil {
couldWriteENRatLeastOnce = true couldWriteENRatLeastOnce = true
successIdx = i successIdx = i
break break
} else {
failedOnceWritingENR = true
} }
failedOnceWritingENR = true
} }
if failedOnceWritingENR && couldWriteENRatLeastOnce { if failedOnceWritingENR && couldWriteENRatLeastOnce {

View File

@ -37,9 +37,9 @@ func WithWakuRelaySharding(rs protocol.RelayShards) ENROption {
return func(localnode *enode.LocalNode) error { return func(localnode *enode.LocalNode) error {
if len(rs.Indices) >= 64 { if len(rs.Indices) >= 64 {
return WithWakuRelayShardingBitVector(rs)(localnode) return WithWakuRelayShardingBitVector(rs)(localnode)
} else {
return WithWakuRelayShardingIndicesList(rs)(localnode)
} }
return WithWakuRelayShardingIndicesList(rs)(localnode)
} }
} }

View File

@ -20,12 +20,12 @@ type Envelope struct {
// as well as generating a hash based on the bytes that compose the message // as well as generating a hash based on the bytes that compose the message
func NewEnvelope(msg *wpb.WakuMessage, receiverTime int64, pubSubTopic string) *Envelope { func NewEnvelope(msg *wpb.WakuMessage, receiverTime int64, pubSubTopic string) *Envelope {
messageHash := msg.Hash(pubSubTopic) messageHash := msg.Hash(pubSubTopic)
hash := hash.SHA256([]byte(msg.ContentTopic), msg.Payload) digest := hash.SHA256([]byte(msg.ContentTopic), msg.Payload)
return &Envelope{ return &Envelope{
msg: msg, msg: msg,
hash: messageHash, hash: messageHash,
index: &pb.Index{ index: &pb.Index{
Digest: hash[:], Digest: digest[:],
ReceiverTime: receiverTime, ReceiverTime: receiverTime,
SenderTime: msg.Timestamp, SenderTime: msg.Timestamp,
PubsubTopic: pubSubTopic, PubsubTopic: pubSubTopic,
@ -48,6 +48,6 @@ func (e *Envelope) Hash() []byte {
return e.hash return e.hash
} }
func (env *Envelope) Index() *pb.Index { func (e *Envelope) Index() *pb.Index {
return env.index return e.index
} }

View File

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
"sync" "strings"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
@ -21,6 +21,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/protocol/filter/pb" "github.com/waku-org/go-waku/waku/v2/protocol/filter/pb"
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb" wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay" "github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/protocol/subscription"
"github.com/waku-org/go-waku/waku/v2/timesource" "github.com/waku-org/go-waku/waku/v2/timesource"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -34,35 +35,23 @@ var (
) )
type WakuFilterLightNode struct { type WakuFilterLightNode struct {
sync.RWMutex *protocol.CommonService
started bool
cancel context.CancelFunc
ctx context.Context
h host.Host h host.Host
broadcaster relay.Broadcaster broadcaster relay.Broadcaster //TODO: Move the broadcast functionality outside of relay client to a higher SDK layer.s
timesource timesource.Timesource timesource timesource.Timesource
metrics Metrics metrics Metrics
wg *sync.WaitGroup
log *zap.Logger log *zap.Logger
subscriptions *SubscriptionsMap subscriptions *subscription.SubscriptionsMap
pm *peermanager.PeerManager pm *peermanager.PeerManager
} }
type ContentFilter struct {
Topic string
ContentTopics []string
}
type WakuFilterPushResult struct { type WakuFilterPushResult struct {
Err error Err error
PeerID peer.ID PeerID peer.ID
} }
var errNotStarted = errors.New("not started")
var errAlreadyStarted = errors.New("already started")
// NewWakuFilterLightnode returns a new instance of Waku Filter struct setup according to the chosen parameter and options // NewWakuFilterLightnode returns a new instance of Waku Filter struct setup according to the chosen parameter and options
// Note that broadcaster is optional.
// Takes an optional peermanager if WakuFilterLightnode is being created along with WakuNode. // Takes an optional peermanager if WakuFilterLightnode is being created along with WakuNode.
// If using libp2p host, then pass peermanager as nil // If using libp2p host, then pass peermanager as nil
func NewWakuFilterLightNode(broadcaster relay.Broadcaster, pm *peermanager.PeerManager, func NewWakuFilterLightNode(broadcaster relay.Broadcaster, pm *peermanager.PeerManager,
@ -71,8 +60,8 @@ func NewWakuFilterLightNode(broadcaster relay.Broadcaster, pm *peermanager.PeerM
wf.log = log.Named("filterv2-lightnode") wf.log = log.Named("filterv2-lightnode")
wf.broadcaster = broadcaster wf.broadcaster = broadcaster
wf.timesource = timesource wf.timesource = timesource
wf.wg = &sync.WaitGroup{}
wf.pm = pm wf.pm = pm
wf.CommonService = protocol.NewCommonService()
wf.metrics = newMetrics(reg) wf.metrics = newMetrics(reg)
return wf return wf
@ -84,66 +73,42 @@ func (wf *WakuFilterLightNode) SetHost(h host.Host) {
} }
func (wf *WakuFilterLightNode) Start(ctx context.Context) error { func (wf *WakuFilterLightNode) Start(ctx context.Context) error {
wf.Lock() return wf.CommonService.Start(ctx, wf.start)
defer wf.Unlock()
if wf.started { }
return errAlreadyStarted
}
wf.wg.Wait() // Wait for any goroutines to stop func (wf *WakuFilterLightNode) start() error {
wf.subscriptions = subscription.NewSubscriptionMap(wf.log)
ctx, cancel := context.WithCancel(ctx) wf.h.SetStreamHandlerMatch(FilterPushID_v20beta1, protocol.PrefixTextMatch(string(FilterPushID_v20beta1)), wf.onRequest(wf.Context()))
wf.cancel = cancel
wf.ctx = ctx
wf.subscriptions = NewSubscriptionMap(wf.log)
wf.started = true
wf.h.SetStreamHandlerMatch(FilterPushID_v20beta1, protocol.PrefixTextMatch(string(FilterPushID_v20beta1)), wf.onRequest(ctx))
wf.log.Info("filter-push protocol started") wf.log.Info("filter-push protocol started")
return nil return nil
} }
// Stop unmounts the filter protocol // Stop unmounts the filter protocol
func (wf *WakuFilterLightNode) Stop() { func (wf *WakuFilterLightNode) Stop() {
wf.Lock() wf.CommonService.Stop(func() {
defer wf.Unlock() wf.h.RemoveStreamHandler(FilterPushID_v20beta1)
res, err := wf.unsubscribeAll(wf.Context())
if !wf.started { if err != nil {
return wf.log.Warn("unsubscribing from full nodes", zap.Error(err))
}
wf.cancel()
wf.h.RemoveStreamHandler(FilterPushID_v20beta1)
res, err := wf.unsubscribeAll(wf.ctx)
if err != nil {
wf.log.Warn("unsubscribing from full nodes", zap.Error(err))
}
for r := range res {
if r.Err != nil {
wf.log.Warn("unsubscribing from full nodes", zap.Error(r.Err), logging.HostID("peerID", r.PeerID))
} }
} for r := range res {
if r.Err != nil {
wf.log.Warn("unsubscribing from full nodes", zap.Error(r.Err), logging.HostID("peerID", r.PeerID))
}
wf.subscriptions.Clear() }
//
wf.started = false wf.subscriptions.Clear()
wf.cancel = nil })
wf.wg.Wait()
} }
func (wf *WakuFilterLightNode) onRequest(ctx context.Context) func(s network.Stream) { func (wf *WakuFilterLightNode) onRequest(ctx context.Context) func(s network.Stream) {
return func(s network.Stream) { return func(s network.Stream) {
defer s.Close() defer s.Close()
logger := wf.log.With(logging.HostID("peer", s.Conn().RemotePeer())) logger := wf.log.With(logging.HostID("peer", s.Conn().RemotePeer()))
if !wf.subscriptions.IsSubscribedTo(s.Conn().RemotePeer()) { if !wf.subscriptions.IsSubscribedTo(s.Conn().RemotePeer()) {
logger.Warn("received message push from unknown peer", logging.HostID("peerID", s.Conn().RemotePeer())) logger.Warn("received message push from unknown peer", logging.HostID("peerID", s.Conn().RemotePeer()))
wf.metrics.RecordError(unknownPeerMessagePush) wf.metrics.RecordError(unknownPeerMessagePush)
@ -159,16 +124,29 @@ func (wf *WakuFilterLightNode) onRequest(ctx context.Context) func(s network.Str
wf.metrics.RecordError(decodeRPCFailure) wf.metrics.RecordError(decodeRPCFailure)
return return
} }
pubSubTopic := ""
if !wf.subscriptions.Has(s.Conn().RemotePeer(), messagePush.PubsubTopic, messagePush.WakuMessage.ContentTopic) { //For now returning failure, this will get addressed with autosharding changes for filter.
logger.Warn("received messagepush with invalid subscription parameters", logging.HostID("peerID", s.Conn().RemotePeer()), zap.String("topic", messagePush.PubsubTopic), zap.String("contentTopic", messagePush.WakuMessage.ContentTopic)) if messagePush.PubsubTopic == nil {
pubSubTopic, err = protocol.GetPubSubTopicFromContentTopic(messagePush.WakuMessage.ContentTopic)
if err != nil {
logger.Error("could not derive pubSubTopic from contentTopic", zap.Error(err))
wf.metrics.RecordError(decodeRPCFailure)
return
}
} else {
pubSubTopic = *messagePush.PubsubTopic
}
if !wf.subscriptions.Has(s.Conn().RemotePeer(), protocol.NewContentFilter(pubSubTopic, messagePush.WakuMessage.ContentTopic)) {
logger.Warn("received messagepush with invalid subscription parameters",
logging.HostID("peerID", s.Conn().RemotePeer()), zap.String("topic", pubSubTopic),
zap.String("contentTopic", messagePush.WakuMessage.ContentTopic))
wf.metrics.RecordError(invalidSubscriptionMessage) wf.metrics.RecordError(invalidSubscriptionMessage)
return return
} }
wf.metrics.RecordMessage() wf.metrics.RecordMessage()
wf.notify(s.Conn().RemotePeer(), messagePush.PubsubTopic, messagePush.WakuMessage) wf.notify(s.Conn().RemotePeer(), pubSubTopic, messagePush.WakuMessage)
logger.Info("received message push") logger.Info("received message push")
} }
@ -177,14 +155,16 @@ func (wf *WakuFilterLightNode) onRequest(ctx context.Context) func(s network.Str
func (wf *WakuFilterLightNode) notify(remotePeerID peer.ID, pubsubTopic string, msg *wpb.WakuMessage) { func (wf *WakuFilterLightNode) notify(remotePeerID peer.ID, pubsubTopic string, msg *wpb.WakuMessage) {
envelope := protocol.NewEnvelope(msg, wf.timesource.Now().UnixNano(), pubsubTopic) envelope := protocol.NewEnvelope(msg, wf.timesource.Now().UnixNano(), pubsubTopic)
// Broadcasting message so it's stored if wf.broadcaster != nil {
wf.broadcaster.Submit(envelope) // Broadcasting message so it's stored
wf.broadcaster.Submit(envelope)
}
// Notify filter subscribers // Notify filter subscribers
wf.subscriptions.Notify(remotePeerID, envelope) wf.subscriptions.Notify(remotePeerID, envelope)
} }
func (wf *WakuFilterLightNode) request(ctx context.Context, params *FilterSubscribeParameters, reqType pb.FilterSubscribeRequest_FilterSubscribeType, contentFilter ContentFilter) error { func (wf *WakuFilterLightNode) request(ctx context.Context, params *FilterSubscribeParameters,
reqType pb.FilterSubscribeRequest_FilterSubscribeType, contentFilter protocol.ContentFilter) error {
conn, err := wf.h.NewStream(ctx, params.selectedPeer, FilterSubscribeID_v20beta1) conn, err := wf.h.NewStream(ctx, params.selectedPeer, FilterSubscribeID_v20beta1)
if err != nil { if err != nil {
wf.metrics.RecordError(dialFailure) wf.metrics.RecordError(dialFailure)
@ -198,8 +178,8 @@ func (wf *WakuFilterLightNode) request(ctx context.Context, params *FilterSubscr
request := &pb.FilterSubscribeRequest{ request := &pb.FilterSubscribeRequest{
RequestId: hex.EncodeToString(params.requestID), RequestId: hex.EncodeToString(params.requestID),
FilterSubscribeType: reqType, FilterSubscribeType: reqType,
PubsubTopic: contentFilter.Topic, PubsubTopic: &contentFilter.PubsubTopic,
ContentTopics: contentFilter.ContentTopics, ContentTopics: contentFilter.ContentTopicsList(),
} }
wf.log.Debug("sending FilterSubscribeRequest", zap.Stringer("request", request)) wf.log.Debug("sending FilterSubscribeRequest", zap.Stringer("request", request))
@ -217,7 +197,6 @@ func (wf *WakuFilterLightNode) request(ctx context.Context, params *FilterSubscr
wf.metrics.RecordError(decodeRPCFailure) wf.metrics.RecordError(decodeRPCFailure)
return err return err
} }
if filterSubscribeResponse.RequestId != request.RequestId { if filterSubscribeResponse.RequestId != request.RequestId {
wf.log.Error("requestID mismatch", zap.String("expected", request.RequestId), zap.String("received", filterSubscribeResponse.RequestId)) wf.log.Error("requestID mismatch", zap.String("expected", request.RequestId), zap.String("received", filterSubscribeResponse.RequestId))
wf.metrics.RecordError(requestIDMismatch) wf.metrics.RecordError(requestIDMismatch)
@ -234,17 +213,38 @@ func (wf *WakuFilterLightNode) request(ctx context.Context, params *FilterSubscr
return nil return nil
} }
// This function converts a contentFilter into a map of pubSubTopics and corresponding contentTopics
func contentFilterToPubSubTopicMap(contentFilter protocol.ContentFilter) (map[string][]string, error) {
pubSubTopicMap := make(map[string][]string)
if contentFilter.PubsubTopic != "" {
pubSubTopicMap[contentFilter.PubsubTopic] = contentFilter.ContentTopicsList()
} else {
//Parse the content-Topics to figure out shards.
for _, cTopicString := range contentFilter.ContentTopicsList() {
pTopicStr, err := protocol.GetPubSubTopicFromContentTopic(cTopicString)
if err != nil {
return nil, err
}
_, ok := pubSubTopicMap[pTopicStr]
if !ok {
pubSubTopicMap[pTopicStr] = []string{}
}
pubSubTopicMap[pTopicStr] = append(pubSubTopicMap[pTopicStr], cTopicString)
}
}
return pubSubTopicMap, nil
}
// Subscribe setups a subscription to receive messages that match a specific content filter // Subscribe setups a subscription to receive messages that match a specific content filter
func (wf *WakuFilterLightNode) Subscribe(ctx context.Context, contentFilter ContentFilter, opts ...FilterSubscribeOption) (*SubscriptionDetails, error) { // If contentTopics passed result in different pubSub topics (due to Auto/Static sharding), then multiple subscription requests are sent to the peer.
// This may change if Filterv2 protocol is updated to handle such a scenario in a single request.
// Note: In case of partial failure, results are returned for successful subscriptions along with error indicating failed contentTopics.
func (wf *WakuFilterLightNode) Subscribe(ctx context.Context, contentFilter protocol.ContentFilter, opts ...FilterSubscribeOption) ([]*subscription.SubscriptionDetails, error) {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return nil, err
return nil, errNotStarted
}
if contentFilter.Topic == "" {
return nil, errors.New("topic is required")
} }
if len(contentFilter.ContentTopics) == 0 { if len(contentFilter.ContentTopics) == 0 {
@ -271,32 +271,49 @@ func (wf *WakuFilterLightNode) Subscribe(ctx context.Context, contentFilter Cont
return nil, ErrNoPeersAvailable return nil, ErrNoPeersAvailable
} }
err := wf.request(ctx, params, pb.FilterSubscribeRequest_SUBSCRIBE, contentFilter) pubSubTopicMap, err := contentFilterToPubSubTopicMap(contentFilter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
failedContentTopics := []string{}
subscriptions := make([]*subscription.SubscriptionDetails, 0)
for pubSubTopic, cTopics := range pubSubTopicMap {
var cFilter protocol.ContentFilter
cFilter.PubsubTopic = pubSubTopic
cFilter.ContentTopics = protocol.NewContentTopicSet(cTopics...)
err := wf.request(ctx, params, pb.FilterSubscribeRequest_SUBSCRIBE, cFilter)
if err != nil {
wf.log.Error("Failed to subscribe", zap.String("pubSubTopic", pubSubTopic), zap.Strings("contentTopics", cTopics),
zap.Error(err))
failedContentTopics = append(failedContentTopics, cTopics...)
}
subscriptions = append(subscriptions, wf.subscriptions.NewSubscription(params.selectedPeer, cFilter))
}
return wf.subscriptions.NewSubscription(params.selectedPeer, contentFilter.Topic, contentFilter.ContentTopics), nil if len(failedContentTopics) > 0 {
return subscriptions, fmt.Errorf("subscriptions failed for contentTopics: %s", strings.Join(failedContentTopics, ","))
} else {
return subscriptions, nil
}
} }
// FilterSubscription is used to obtain an object from which you could receive messages received via filter protocol // FilterSubscription is used to obtain an object from which you could receive messages received via filter protocol
func (wf *WakuFilterLightNode) FilterSubscription(peerID peer.ID, contentFilter ContentFilter) (*SubscriptionDetails, error) { func (wf *WakuFilterLightNode) FilterSubscription(peerID peer.ID, contentFilter protocol.ContentFilter) (*subscription.SubscriptionDetails, error) {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return nil, err
return nil, errNotStarted
} }
if !wf.subscriptions.Has(peerID, contentFilter.Topic, contentFilter.ContentTopics...) { if !wf.subscriptions.Has(peerID, contentFilter) {
return nil, errors.New("subscription does not exist") return nil, errors.New("subscription does not exist")
} }
return wf.subscriptions.NewSubscription(peerID, contentFilter.Topic, contentFilter.ContentTopics), nil return wf.subscriptions.NewSubscription(peerID, contentFilter), nil
} }
func (wf *WakuFilterLightNode) getUnsubscribeParameters(opts ...FilterUnsubscribeOption) (*FilterUnsubscribeParameters, error) { func (wf *WakuFilterLightNode) getUnsubscribeParameters(opts ...FilterSubscribeOption) (*FilterSubscribeParameters, error) {
params := new(FilterUnsubscribeParameters) params := new(FilterSubscribeParameters)
params.log = wf.log params.log = wf.log
opts = append(DefaultUnsubscribeOptions(), opts...) opts = append(DefaultUnsubscribeOptions(), opts...)
for _, opt := range opts { for _, opt := range opts {
@ -309,45 +326,42 @@ func (wf *WakuFilterLightNode) getUnsubscribeParameters(opts ...FilterUnsubscrib
func (wf *WakuFilterLightNode) Ping(ctx context.Context, peerID peer.ID) error { func (wf *WakuFilterLightNode) Ping(ctx context.Context, peerID peer.ID) error {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return err
return errNotStarted
} }
return wf.request( return wf.request(
ctx, ctx,
&FilterSubscribeParameters{selectedPeer: peerID}, &FilterSubscribeParameters{selectedPeer: peerID, requestID: protocol.GenerateRequestID()},
pb.FilterSubscribeRequest_SUBSCRIBER_PING, pb.FilterSubscribeRequest_SUBSCRIBER_PING,
ContentFilter{}) protocol.ContentFilter{})
} }
func (wf *WakuFilterLightNode) IsSubscriptionAlive(ctx context.Context, subscription *SubscriptionDetails) error { func (wf *WakuFilterLightNode) IsSubscriptionAlive(ctx context.Context, subscription *subscription.SubscriptionDetails) error {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return err
return errNotStarted
} }
return wf.Ping(ctx, subscription.PeerID) return wf.Ping(ctx, subscription.PeerID)
} }
func (wf *WakuFilterLightNode) Subscriptions() []*SubscriptionDetails { func (wf *WakuFilterLightNode) Subscriptions() []*subscription.SubscriptionDetails {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started {
return nil return nil
} }
wf.subscriptions.RLock() wf.subscriptions.RLock()
defer wf.subscriptions.RUnlock() defer wf.subscriptions.RUnlock()
var output []*SubscriptionDetails var output []*subscription.SubscriptionDetails
for _, peerSubscription := range wf.subscriptions.items { for _, peerSubscription := range wf.subscriptions.Items {
for _, subscriptionPerTopic := range peerSubscription.subscriptionsPerTopic { for _, subscriptions := range peerSubscription.SubsPerPubsubTopic {
for _, subscriptionDetail := range subscriptionPerTopic { for _, subscriptionDetail := range subscriptions {
output = append(output, subscriptionDetail) output = append(output, subscriptionDetail)
} }
} }
@ -356,48 +370,40 @@ func (wf *WakuFilterLightNode) Subscriptions() []*SubscriptionDetails {
return output return output
} }
func (wf *WakuFilterLightNode) cleanupSubscriptions(peerID peer.ID, contentFilter ContentFilter) { func (wf *WakuFilterLightNode) cleanupSubscriptions(peerID peer.ID, contentFilter protocol.ContentFilter) {
wf.subscriptions.Lock() wf.subscriptions.Lock()
defer wf.subscriptions.Unlock() defer wf.subscriptions.Unlock()
peerSubscription, ok := wf.subscriptions.items[peerID] peerSubscription, ok := wf.subscriptions.Items[peerID]
if !ok { if !ok {
return return
} }
subscriptionDetailList, ok := peerSubscription.subscriptionsPerTopic[contentFilter.Topic] subscriptionDetailList, ok := peerSubscription.SubsPerPubsubTopic[contentFilter.PubsubTopic]
if !ok { if !ok {
return return
} }
for subscriptionDetailID, subscriptionDetail := range subscriptionDetailList { for subscriptionDetailID, subscriptionDetail := range subscriptionDetailList {
subscriptionDetail.Remove(contentFilter.ContentTopics...) subscriptionDetail.Remove(contentFilter.ContentTopicsList()...)
if len(subscriptionDetail.ContentTopics) == 0 { if len(subscriptionDetail.ContentFilter.ContentTopics) == 0 {
delete(subscriptionDetailList, subscriptionDetailID) delete(subscriptionDetailList, subscriptionDetailID)
} else { subscriptionDetail.CloseC()
subscriptionDetailList[subscriptionDetailID] = subscriptionDetail
} }
} }
if len(subscriptionDetailList) == 0 { if len(subscriptionDetailList) == 0 {
delete(wf.subscriptions.items[peerID].subscriptionsPerTopic, contentFilter.Topic) delete(wf.subscriptions.Items[peerID].SubsPerPubsubTopic, contentFilter.PubsubTopic)
} else {
wf.subscriptions.items[peerID].subscriptionsPerTopic[contentFilter.Topic] = subscriptionDetailList
} }
} }
// Unsubscribe is used to stop receiving messages from a peer that match a content filter // Unsubscribe is used to stop receiving messages from a peer that match a content filter
func (wf *WakuFilterLightNode) Unsubscribe(ctx context.Context, contentFilter ContentFilter, opts ...FilterUnsubscribeOption) (<-chan WakuFilterPushResult, error) { func (wf *WakuFilterLightNode) Unsubscribe(ctx context.Context, contentFilter protocol.ContentFilter, opts ...FilterSubscribeOption) (<-chan WakuFilterPushResult, error) {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return nil, err
return nil, errNotStarted
}
if contentFilter.Topic == "" {
return nil, errors.New("topic is required")
} }
if len(contentFilter.ContentTopics) == 0 { if len(contentFilter.ContentTopics) == 0 {
@ -413,57 +419,49 @@ func (wf *WakuFilterLightNode) Unsubscribe(ctx context.Context, contentFilter Co
return nil, err return nil, err
} }
resultChan := make(chan WakuFilterPushResult, len(wf.subscriptions.items)) pubSubTopicMap, err := contentFilterToPubSubTopicMap(contentFilter)
for peerID := range wf.subscriptions.items { if err != nil {
if params.selectedPeer != "" && peerID != params.selectedPeer { return nil, err
continue }
} resultChan := make(chan WakuFilterPushResult, len(wf.subscriptions.Items))
for pTopic, cTopics := range pubSubTopicMap {
cFilter := protocol.NewContentFilter(pTopic, cTopics...)
for peerID := range wf.subscriptions.Items {
if params.selectedPeer != "" && peerID != params.selectedPeer {
continue
}
subscriptions, ok := wf.subscriptions.items[peerID] subscriptions, ok := wf.subscriptions.Items[peerID]
if !ok || subscriptions == nil { if !ok || subscriptions == nil {
continue continue
} }
wf.cleanupSubscriptions(peerID, contentFilter) wf.cleanupSubscriptions(peerID, cFilter)
if len(subscriptions.subscriptionsPerTopic) == 0 { if len(subscriptions.SubsPerPubsubTopic) == 0 {
delete(wf.subscriptions.items, peerID) delete(wf.subscriptions.Items, peerID)
}
if params.wg != nil {
params.wg.Add(1)
}
go func(peerID peer.ID) {
defer func() {
if params.wg != nil {
params.wg.Done()
}
}()
err := wf.request(
ctx,
&FilterSubscribeParameters{selectedPeer: peerID, requestID: params.requestID},
pb.FilterSubscribeRequest_UNSUBSCRIBE,
contentFilter)
if err != nil {
ferr, ok := err.(*FilterError)
if ok && ferr.Code == http.StatusNotFound {
wf.log.Warn("peer does not have a subscription", logging.HostID("peerID", peerID), zap.Error(err))
} else {
wf.log.Error("could not unsubscribe from peer", logging.HostID("peerID", peerID), zap.Error(err))
return
}
} }
if params.wg != nil { if params.wg != nil {
resultChan <- WakuFilterPushResult{ params.wg.Add(1)
Err: err,
PeerID: peerID,
}
} }
}(peerID)
}
go func(peerID peer.ID) {
defer func() {
if params.wg != nil {
params.wg.Done()
}
}()
err := wf.unsubscribeFromServer(ctx, &FilterSubscribeParameters{selectedPeer: peerID, requestID: params.requestID}, cFilter)
if params.wg != nil {
resultChan <- WakuFilterPushResult{
Err: err,
PeerID: peerID,
}
}
}(peerID)
}
}
if params.wg != nil { if params.wg != nil {
params.wg.Wait() params.wg.Wait()
} }
@ -473,26 +471,55 @@ func (wf *WakuFilterLightNode) Unsubscribe(ctx context.Context, contentFilter Co
return resultChan, nil return resultChan, nil
} }
// Unsubscribe is used to stop receiving messages from a peer that match a content filter // UnsubscribeWithSubscription is used to close a particular subscription
func (wf *WakuFilterLightNode) UnsubscribeWithSubscription(ctx context.Context, sub *SubscriptionDetails, opts ...FilterUnsubscribeOption) (<-chan WakuFilterPushResult, error) { // If there are no more subscriptions matching the passed [peer, contentFilter] pair,
// server unsubscribe is also performed
func (wf *WakuFilterLightNode) UnsubscribeWithSubscription(ctx context.Context, sub *subscription.SubscriptionDetails,
opts ...FilterSubscribeOption) (<-chan WakuFilterPushResult, error) {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return nil, err
return nil, errNotStarted
} }
var contentTopics []string params, err := wf.getUnsubscribeParameters(opts...)
for k := range sub.ContentTopics { if err != nil {
contentTopics = append(contentTopics, k) return nil, err
} }
opts = append(opts, Peer(sub.PeerID)) // Close this sub
sub.Close()
resultChan := make(chan WakuFilterPushResult, 1)
if !wf.subscriptions.Has(sub.PeerID, sub.ContentFilter) {
// Last sub for this [peer, contentFilter] pair
err = wf.unsubscribeFromServer(ctx, &FilterSubscribeParameters{selectedPeer: sub.PeerID, requestID: params.requestID}, sub.ContentFilter)
resultChan <- WakuFilterPushResult{
Err: err,
PeerID: sub.PeerID,
}
}
close(resultChan)
return resultChan, err
return wf.Unsubscribe(ctx, ContentFilter{Topic: sub.PubsubTopic, ContentTopics: contentTopics}, opts...)
} }
func (wf *WakuFilterLightNode) unsubscribeAll(ctx context.Context, opts ...FilterUnsubscribeOption) (<-chan WakuFilterPushResult, error) { func (wf *WakuFilterLightNode) unsubscribeFromServer(ctx context.Context, params *FilterSubscribeParameters, cFilter protocol.ContentFilter) error {
err := wf.request(ctx, params, pb.FilterSubscribeRequest_UNSUBSCRIBE, cFilter)
if err != nil {
ferr, ok := err.(*FilterError)
if ok && ferr.Code == http.StatusNotFound {
wf.log.Warn("peer does not have a subscription", logging.HostID("peerID", params.selectedPeer), zap.Error(err))
} else {
wf.log.Error("could not unsubscribe from peer", logging.HostID("peerID", params.selectedPeer), zap.Error(err))
}
}
return err
}
func (wf *WakuFilterLightNode) unsubscribeAll(ctx context.Context, opts ...FilterSubscribeOption) (<-chan WakuFilterPushResult, error) {
params, err := wf.getUnsubscribeParameters(opts...) params, err := wf.getUnsubscribeParameters(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -501,14 +528,14 @@ func (wf *WakuFilterLightNode) unsubscribeAll(ctx context.Context, opts ...Filte
wf.subscriptions.Lock() wf.subscriptions.Lock()
defer wf.subscriptions.Unlock() defer wf.subscriptions.Unlock()
resultChan := make(chan WakuFilterPushResult, len(wf.subscriptions.items)) resultChan := make(chan WakuFilterPushResult, len(wf.subscriptions.Items))
for peerID := range wf.subscriptions.items { for peerID := range wf.subscriptions.Items {
if params.selectedPeer != "" && peerID != params.selectedPeer { if params.selectedPeer != "" && peerID != params.selectedPeer {
continue continue
} }
delete(wf.subscriptions.items, peerID) delete(wf.subscriptions.Items, peerID)
if params.wg != nil { if params.wg != nil {
params.wg.Add(1) params.wg.Add(1)
@ -525,7 +552,7 @@ func (wf *WakuFilterLightNode) unsubscribeAll(ctx context.Context, opts ...Filte
ctx, ctx,
&FilterSubscribeParameters{selectedPeer: peerID, requestID: params.requestID}, &FilterSubscribeParameters{selectedPeer: peerID, requestID: params.requestID},
pb.FilterSubscribeRequest_UNSUBSCRIBE_ALL, pb.FilterSubscribeRequest_UNSUBSCRIBE_ALL,
ContentFilter{}) protocol.ContentFilter{})
if err != nil { if err != nil {
wf.log.Error("could not unsubscribe from peer", logging.HostID("peerID", peerID), zap.Error(err)) wf.log.Error("could not unsubscribe from peer", logging.HostID("peerID", peerID), zap.Error(err))
} }
@ -548,12 +575,11 @@ func (wf *WakuFilterLightNode) unsubscribeAll(ctx context.Context, opts ...Filte
} }
// UnsubscribeAll is used to stop receiving messages from peer(s). It does not close subscriptions // UnsubscribeAll is used to stop receiving messages from peer(s). It does not close subscriptions
func (wf *WakuFilterLightNode) UnsubscribeAll(ctx context.Context, opts ...FilterUnsubscribeOption) (<-chan WakuFilterPushResult, error) { func (wf *WakuFilterLightNode) UnsubscribeAll(ctx context.Context, opts ...FilterSubscribeOption) (<-chan WakuFilterPushResult, error) {
wf.RLock() wf.RLock()
defer wf.RUnlock() defer wf.RUnlock()
if err := wf.ErrOnNotRunning(); err != nil {
if !wf.started { return nil, err
return nil, errNotStarted
} }
return wf.unsubscribeAll(ctx, opts...) return wf.unsubscribeAll(ctx, opts...)

View File

@ -15,18 +15,16 @@ import (
type ( type (
FilterSubscribeParameters struct { FilterSubscribeParameters struct {
host host.Host
selectedPeer peer.ID selectedPeer peer.ID
pm *peermanager.PeerManager
requestID []byte requestID []byte
log *zap.Logger log *zap.Logger
}
FilterUnsubscribeParameters struct { // Subscribe-specific
host host.Host
pm *peermanager.PeerManager
// Unsubscribe-specific
unsubscribeAll bool unsubscribeAll bool
selectedPeer peer.ID
requestID []byte
log *zap.Logger
wg *sync.WaitGroup wg *sync.WaitGroup
} }
@ -37,8 +35,7 @@ type (
Option func(*FilterParameters) Option func(*FilterParameters)
FilterSubscribeOption func(*FilterSubscribeParameters) FilterSubscribeOption func(*FilterSubscribeParameters)
FilterUnsubscribeOption func(*FilterUnsubscribeParameters)
) )
func WithTimeout(timeout time.Duration) Option { func WithTimeout(timeout time.Duration) Option {
@ -63,7 +60,7 @@ func WithAutomaticPeerSelection(fromThesePeers ...peer.ID) FilterSubscribeOption
if params.pm == nil { if params.pm == nil {
p, err = utils.SelectPeer(params.host, FilterSubscribeID_v20beta1, fromThesePeers, params.log) p, err = utils.SelectPeer(params.host, FilterSubscribeID_v20beta1, fromThesePeers, params.log)
} else { } else {
p, err = params.pm.SelectPeer(FilterSubscribeID_v20beta1, fromThesePeers, params.log) p, err = params.pm.SelectPeer(FilterSubscribeID_v20beta1, "", fromThesePeers...)
} }
if err == nil { if err == nil {
params.selectedPeer = p params.selectedPeer = p
@ -89,7 +86,7 @@ func WithFastestPeerSelection(ctx context.Context, fromThesePeers ...peer.ID) Fi
} }
// WithRequestID is an option to set a specific request ID to be used when // WithRequestID is an option to set a specific request ID to be used when
// creating a filter subscription // creating/removing a filter subscription
func WithRequestID(requestID []byte) FilterSubscribeOption { func WithRequestID(requestID []byte) FilterSubscribeOption {
return func(params *FilterSubscribeParameters) { return func(params *FilterSubscribeParameters) {
params.requestID = requestID params.requestID = requestID
@ -100,7 +97,7 @@ func WithRequestID(requestID []byte) FilterSubscribeOption {
// when creating a filter subscription // when creating a filter subscription
func WithAutomaticRequestID() FilterSubscribeOption { func WithAutomaticRequestID() FilterSubscribeOption {
return func(params *FilterSubscribeParameters) { return func(params *FilterSubscribeParameters) {
params.requestID = protocol.GenerateRequestId() params.requestID = protocol.GenerateRequestID()
} }
} }
@ -111,51 +108,31 @@ func DefaultSubscriptionOptions() []FilterSubscribeOption {
} }
} }
func UnsubscribeAll() FilterUnsubscribeOption { func UnsubscribeAll() FilterSubscribeOption {
return func(params *FilterUnsubscribeParameters) { return func(params *FilterSubscribeParameters) {
params.unsubscribeAll = true params.unsubscribeAll = true
} }
} }
func Peer(p peer.ID) FilterUnsubscribeOption { // WithWaitGroup allows specifying a waitgroup to wait until all
return func(params *FilterUnsubscribeParameters) {
params.selectedPeer = p
}
}
// RequestID is an option to set a specific request ID to be used when
// removing a subscription from a filter node
func RequestID(requestID []byte) FilterUnsubscribeOption {
return func(params *FilterUnsubscribeParameters) {
params.requestID = requestID
}
}
func AutomaticRequestId() FilterUnsubscribeOption {
return func(params *FilterUnsubscribeParameters) {
params.requestID = protocol.GenerateRequestId()
}
}
// WithWaitGroup allos specigying a waitgroup to wait until all
// unsubscribe requests are complete before the function is complete // unsubscribe requests are complete before the function is complete
func WithWaitGroup(wg *sync.WaitGroup) FilterUnsubscribeOption { func WithWaitGroup(wg *sync.WaitGroup) FilterSubscribeOption {
return func(params *FilterUnsubscribeParameters) { return func(params *FilterSubscribeParameters) {
params.wg = wg params.wg = wg
} }
} }
// DontWait is used to fire and forget an unsubscription, and don't // DontWait is used to fire and forget an unsubscription, and don't
// care about the results of it // care about the results of it
func DontWait() FilterUnsubscribeOption { func DontWait() FilterSubscribeOption {
return func(params *FilterUnsubscribeParameters) { return func(params *FilterSubscribeParameters) {
params.wg = nil params.wg = nil
} }
} }
func DefaultUnsubscribeOptions() []FilterUnsubscribeOption { func DefaultUnsubscribeOptions() []FilterSubscribeOption {
return []FilterUnsubscribeOption{ return []FilterSubscribeOption{
AutomaticRequestId(), WithAutomaticRequestID(),
WithWaitGroup(&sync.WaitGroup{}), WithWaitGroup(&sync.WaitGroup{}),
} }
} }

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.31.0
// protoc v3.21.12 // protoc v4.23.4
// source: waku_filter_v2.proto // source: waku_filter_v2.proto
// 12/WAKU2-FILTER rfc: https://rfc.vac.dev/spec/12/ // 12/WAKU2-FILTER rfc: https://rfc.vac.dev/spec/12/
@ -84,7 +84,7 @@ type FilterSubscribeRequest struct {
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
FilterSubscribeType FilterSubscribeRequest_FilterSubscribeType `protobuf:"varint,2,opt,name=filter_subscribe_type,json=filterSubscribeType,proto3,enum=pb.FilterSubscribeRequest_FilterSubscribeType" json:"filter_subscribe_type,omitempty"` FilterSubscribeType FilterSubscribeRequest_FilterSubscribeType `protobuf:"varint,2,opt,name=filter_subscribe_type,json=filterSubscribeType,proto3,enum=pb.FilterSubscribeRequest_FilterSubscribeType" json:"filter_subscribe_type,omitempty"`
// Filter criteria // Filter criteria
PubsubTopic string `protobuf:"bytes,10,opt,name=pubsub_topic,json=pubsubTopic,proto3" json:"pubsub_topic,omitempty"` PubsubTopic *string `protobuf:"bytes,10,opt,name=pubsub_topic,json=pubsubTopic,proto3,oneof" json:"pubsub_topic,omitempty"`
ContentTopics []string `protobuf:"bytes,11,rep,name=content_topics,json=contentTopics,proto3" json:"content_topics,omitempty"` ContentTopics []string `protobuf:"bytes,11,rep,name=content_topics,json=contentTopics,proto3" json:"content_topics,omitempty"`
} }
@ -135,8 +135,8 @@ func (x *FilterSubscribeRequest) GetFilterSubscribeType() FilterSubscribeRequest
} }
func (x *FilterSubscribeRequest) GetPubsubTopic() string { func (x *FilterSubscribeRequest) GetPubsubTopic() string {
if x != nil { if x != nil && x.PubsubTopic != nil {
return x.PubsubTopic return *x.PubsubTopic
} }
return "" return ""
} }
@ -218,7 +218,7 @@ type MessagePushV2 struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
WakuMessage *pb.WakuMessage `protobuf:"bytes,1,opt,name=waku_message,json=wakuMessage,proto3" json:"waku_message,omitempty"` WakuMessage *pb.WakuMessage `protobuf:"bytes,1,opt,name=waku_message,json=wakuMessage,proto3" json:"waku_message,omitempty"`
PubsubTopic string `protobuf:"bytes,2,opt,name=pubsub_topic,json=pubsubTopic,proto3" json:"pubsub_topic,omitempty"` PubsubTopic *string `protobuf:"bytes,2,opt,name=pubsub_topic,json=pubsubTopic,proto3,oneof" json:"pubsub_topic,omitempty"`
} }
func (x *MessagePushV2) Reset() { func (x *MessagePushV2) Reset() {
@ -261,8 +261,8 @@ func (x *MessagePushV2) GetWakuMessage() *pb.WakuMessage {
} }
func (x *MessagePushV2) GetPubsubTopic() string { func (x *MessagePushV2) GetPubsubTopic() string {
if x != nil { if x != nil && x.PubsubTopic != nil {
return x.PubsubTopic return *x.PubsubTopic
} }
return "" return ""
} }
@ -272,7 +272,7 @@ var File_waku_filter_v2_proto protoreflect.FileDescriptor
var file_waku_filter_v2_proto_rawDesc = []byte{ var file_waku_filter_v2_proto_rawDesc = []byte{
0x0a, 0x14, 0x77, 0x61, 0x6b, 0x75, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x32, 0x0a, 0x14, 0x77, 0x61, 0x6b, 0x75, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x32,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x1a, 0x12, 0x77, 0x61, 0x6b, 0x75, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x1a, 0x12, 0x77, 0x61, 0x6b, 0x75,
0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdc,
0x02, 0x0a, 0x16, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x02, 0x0a, 0x16, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72,
@ -282,33 +282,35 @@ var file_waku_filter_v2_proto_rawDesc = []byte{
0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x13, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x69, 0x62, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x13, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53,
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0c,
0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x12, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x54, 0x6f, 0x70, 0x69,
0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x63, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f,
0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f,
0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x22, 0x5f, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x22, 0x5f, 0x0a, 0x13, 0x46,
0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x79,
0x0f, 0x53, 0x55, 0x42, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x52, 0x5f, 0x50, 0x49, 0x4e, 0x47, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x55, 0x42, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x52,
0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x42, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x10, 0x5f, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x42, 0x53, 0x43,
0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x55, 0x42, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x52, 0x49, 0x42, 0x45, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x55, 0x42, 0x53,
0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x53, 0x55, 0x42, 0x53, 0x43, 0x52, 0x49, 0x42, 0x43, 0x52, 0x49, 0x42, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x53, 0x55, 0x42,
0x45, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x03, 0x22, 0x7a, 0x0a, 0x17, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x53, 0x43, 0x52, 0x49, 0x42, 0x45, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x03, 0x42, 0x0f, 0x0a, 0x0d,
0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x5f, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0x7a, 0x0a,
0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x17, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75,
0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75,
0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x64, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x74,
0x63, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74,
0x65, 0x73, 0x63, 0x22, 0x66, 0x0a, 0x0d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x50, 0x75, 0x75, 0x73, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73,
0x73, 0x68, 0x56, 0x32, 0x12, 0x32, 0x0a, 0x0c, 0x77, 0x61, 0x6b, 0x75, 0x5f, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x73, 0x63, 0x22, 0x7c, 0x0a, 0x0d, 0x4d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x73, 0x61, 0x67, 0x65, 0x50, 0x75, 0x73, 0x68, 0x56, 0x32, 0x12, 0x32, 0x0a, 0x0c, 0x77, 0x61,
0x57, 0x61, 0x6b, 0x75, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0b, 0x77, 0x61, 0x6b, 0x6b, 0x75, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x75, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x73, 0x32, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x57, 0x61, 0x6b, 0x75, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x75, 0x62, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x52, 0x0b, 0x77, 0x61, 0x6b, 0x75, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26,
0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x54, 0x6f, 0x70, 0x69, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x02,
0x74, 0x6f, 0x33, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x54, 0x6f,
0x70, 0x69, 0x63, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x70, 0x75, 0x62, 0x73, 0x75,
0x62, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -385,6 +387,8 @@ func file_waku_filter_v2_proto_init() {
} }
} }
} }
file_waku_filter_v2_proto_msgTypes[0].OneofWrappers = []interface{}{}
file_waku_filter_v2_proto_msgTypes[2].OneofWrappers = []interface{}{}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{

View File

@ -19,7 +19,7 @@ message FilterSubscribeRequest {
FilterSubscribeType filter_subscribe_type = 2; FilterSubscribeType filter_subscribe_type = 2;
// Filter criteria // Filter criteria
string pubsub_topic = 10; optional string pubsub_topic = 10;
repeated string content_topics = 11; repeated string content_topics = 11;
} }
@ -32,5 +32,5 @@ message FilterSubscribeResponse {
// Protocol identifier: /vac/waku/filter-push/2.0.0-beta1 // Protocol identifier: /vac/waku/filter-push/2.0.0-beta1
message MessagePushV2 { message MessagePushV2 {
WakuMessage waku_message = 1; WakuMessage waku_message = 1;
string pubsub_topic = 2; optional string pubsub_topic = 2;
} }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
"sync"
"time" "time"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
@ -31,13 +30,11 @@ const peerHasNoSubscription = "peer has no subscriptions"
type ( type (
WakuFilterFullNode struct { WakuFilterFullNode struct {
cancel context.CancelFunc
h host.Host h host.Host
msgSub relay.Subscription msgSub relay.Subscription
metrics Metrics metrics Metrics
wg *sync.WaitGroup
log *zap.Logger log *zap.Logger
*protocol.CommonService
subscriptions *SubscribersMap subscriptions *SubscribersMap
maxSubscriptions int maxSubscriptions int
@ -56,7 +53,7 @@ func NewWakuFilterFullNode(timesource timesource.Timesource, reg prometheus.Regi
opt(params) opt(params)
} }
wf.wg = &sync.WaitGroup{} wf.CommonService = protocol.NewCommonService()
wf.metrics = newMetrics(reg) wf.metrics = newMetrics(reg)
wf.subscriptions = NewSubscribersMap(params.Timeout) wf.subscriptions = NewSubscribersMap(params.Timeout)
wf.maxSubscriptions = params.MaxSubscribers wf.maxSubscriptions = params.MaxSubscribers
@ -70,19 +67,19 @@ func (wf *WakuFilterFullNode) SetHost(h host.Host) {
} }
func (wf *WakuFilterFullNode) Start(ctx context.Context, sub relay.Subscription) error { func (wf *WakuFilterFullNode) Start(ctx context.Context, sub relay.Subscription) error {
wf.wg.Wait() // Wait for any goroutines to stop return wf.CommonService.Start(ctx, func() error {
return wf.start(sub)
})
}
ctx, cancel := context.WithCancel(ctx) func (wf *WakuFilterFullNode) start(sub relay.Subscription) error {
wf.h.SetStreamHandlerMatch(FilterSubscribeID_v20beta1, protocol.PrefixTextMatch(string(FilterSubscribeID_v20beta1)), wf.onRequest(wf.Context()))
wf.h.SetStreamHandlerMatch(FilterSubscribeID_v20beta1, protocol.PrefixTextMatch(string(FilterSubscribeID_v20beta1)), wf.onRequest(ctx))
wf.cancel = cancel
wf.msgSub = sub wf.msgSub = sub
wf.wg.Add(1) wf.WaitGroup().Add(1)
go wf.filterListener(ctx) go wf.filterListener(wf.Context())
wf.log.Info("filter-subscriber protocol started") wf.log.Info("filter-subscriber protocol started")
return nil return nil
} }
@ -107,13 +104,13 @@ func (wf *WakuFilterFullNode) onRequest(ctx context.Context) func(s network.Stre
switch subscribeRequest.FilterSubscribeType { switch subscribeRequest.FilterSubscribeType {
case pb.FilterSubscribeRequest_SUBSCRIBE: case pb.FilterSubscribeRequest_SUBSCRIBE:
wf.subscribe(ctx, s, logger, subscribeRequest) wf.subscribe(ctx, s, subscribeRequest)
case pb.FilterSubscribeRequest_SUBSCRIBER_PING: case pb.FilterSubscribeRequest_SUBSCRIBER_PING:
wf.ping(ctx, s, logger, subscribeRequest) wf.ping(ctx, s, subscribeRequest)
case pb.FilterSubscribeRequest_UNSUBSCRIBE: case pb.FilterSubscribeRequest_UNSUBSCRIBE:
wf.unsubscribe(ctx, s, logger, subscribeRequest) wf.unsubscribe(ctx, s, subscribeRequest)
case pb.FilterSubscribeRequest_UNSUBSCRIBE_ALL: case pb.FilterSubscribeRequest_UNSUBSCRIBE_ALL:
wf.unsubscribeAll(ctx, s, logger, subscribeRequest) wf.unsubscribeAll(ctx, s, subscribeRequest)
} }
wf.metrics.RecordRequest(subscribeRequest.FilterSubscribeType.String(), time.Since(start)) wf.metrics.RecordRequest(subscribeRequest.FilterSubscribeType.String(), time.Since(start))
@ -142,7 +139,7 @@ func (wf *WakuFilterFullNode) reply(ctx context.Context, s network.Stream, reque
} }
} }
func (wf *WakuFilterFullNode) ping(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) { func (wf *WakuFilterFullNode) ping(ctx context.Context, s network.Stream, request *pb.FilterSubscribeRequest) {
exists := wf.subscriptions.Has(s.Conn().RemotePeer()) exists := wf.subscriptions.Has(s.Conn().RemotePeer())
if exists { if exists {
@ -152,8 +149,8 @@ func (wf *WakuFilterFullNode) ping(ctx context.Context, s network.Stream, logger
} }
} }
func (wf *WakuFilterFullNode) subscribe(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) { func (wf *WakuFilterFullNode) subscribe(ctx context.Context, s network.Stream, request *pb.FilterSubscribeRequest) {
if request.PubsubTopic == "" { if request.PubsubTopic == nil {
wf.reply(ctx, s, request, http.StatusBadRequest, "pubsubtopic can't be empty") wf.reply(ctx, s, request, http.StatusBadRequest, "pubsubtopic can't be empty")
return return
} }
@ -186,14 +183,14 @@ func (wf *WakuFilterFullNode) subscribe(ctx context.Context, s network.Stream, l
} }
} }
wf.subscriptions.Set(peerID, request.PubsubTopic, request.ContentTopics) wf.subscriptions.Set(peerID, *request.PubsubTopic, request.ContentTopics)
wf.metrics.RecordSubscriptions(wf.subscriptions.Count()) wf.metrics.RecordSubscriptions(wf.subscriptions.Count())
wf.reply(ctx, s, request, http.StatusOK) wf.reply(ctx, s, request, http.StatusOK)
} }
func (wf *WakuFilterFullNode) unsubscribe(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) { func (wf *WakuFilterFullNode) unsubscribe(ctx context.Context, s network.Stream, request *pb.FilterSubscribeRequest) {
if request.PubsubTopic == "" { if request.PubsubTopic == nil {
wf.reply(ctx, s, request, http.StatusBadRequest, "pubsubtopic can't be empty") wf.reply(ctx, s, request, http.StatusBadRequest, "pubsubtopic can't be empty")
return return
} }
@ -207,7 +204,7 @@ func (wf *WakuFilterFullNode) unsubscribe(ctx context.Context, s network.Stream,
wf.reply(ctx, s, request, http.StatusBadRequest, fmt.Sprintf("exceeds maximum content topics: %d", MaxContentTopicsPerRequest)) wf.reply(ctx, s, request, http.StatusBadRequest, fmt.Sprintf("exceeds maximum content topics: %d", MaxContentTopicsPerRequest))
} }
err := wf.subscriptions.Delete(s.Conn().RemotePeer(), request.PubsubTopic, request.ContentTopics) err := wf.subscriptions.Delete(s.Conn().RemotePeer(), *request.PubsubTopic, request.ContentTopics)
if err != nil { if err != nil {
wf.reply(ctx, s, request, http.StatusNotFound, peerHasNoSubscription) wf.reply(ctx, s, request, http.StatusNotFound, peerHasNoSubscription)
} else { } else {
@ -216,7 +213,7 @@ func (wf *WakuFilterFullNode) unsubscribe(ctx context.Context, s network.Stream,
} }
} }
func (wf *WakuFilterFullNode) unsubscribeAll(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) { func (wf *WakuFilterFullNode) unsubscribeAll(ctx context.Context, s network.Stream, request *pb.FilterSubscribeRequest) {
err := wf.subscriptions.DeleteAll(s.Conn().RemotePeer()) err := wf.subscriptions.DeleteAll(s.Conn().RemotePeer())
if err != nil { if err != nil {
wf.reply(ctx, s, request, http.StatusNotFound, peerHasNoSubscription) wf.reply(ctx, s, request, http.StatusNotFound, peerHasNoSubscription)
@ -227,7 +224,7 @@ func (wf *WakuFilterFullNode) unsubscribeAll(ctx context.Context, s network.Stre
} }
func (wf *WakuFilterFullNode) filterListener(ctx context.Context) { func (wf *WakuFilterFullNode) filterListener(ctx context.Context) {
defer wf.wg.Done() defer wf.WaitGroup().Done()
// This function is invoked for each message received // This function is invoked for each message received
// on the full node in context of Waku2-Filter // on the full node in context of Waku2-Filter
@ -243,9 +240,9 @@ func (wf *WakuFilterFullNode) filterListener(ctx context.Context) {
subscriber := subscriber // https://golang.org/doc/faq#closures_and_goroutines subscriber := subscriber // https://golang.org/doc/faq#closures_and_goroutines
// Do a message push to light node // Do a message push to light node
logger.Info("pushing message to light node") logger.Info("pushing message to light node")
wf.wg.Add(1) wf.WaitGroup().Add(1)
go func(subscriber peer.ID) { go func(subscriber peer.ID) {
defer wf.wg.Done() defer wf.WaitGroup().Done()
start := time.Now() start := time.Now()
err := wf.pushMessage(ctx, subscriber, envelope) err := wf.pushMessage(ctx, subscriber, envelope)
if err != nil { if err != nil {
@ -273,9 +270,9 @@ func (wf *WakuFilterFullNode) pushMessage(ctx context.Context, peerID peer.ID, e
zap.String("pubsubTopic", env.PubsubTopic()), zap.String("pubsubTopic", env.PubsubTopic()),
zap.String("contentTopic", env.Message().ContentTopic), zap.String("contentTopic", env.Message().ContentTopic),
) )
pubSubTopic := env.PubsubTopic()
messagePush := &pb.MessagePushV2{ messagePush := &pb.MessagePushV2{
PubsubTopic: env.PubsubTopic(), PubsubTopic: &pubSubTopic,
WakuMessage: env.Message(), WakuMessage: env.Message(),
} }
@ -317,15 +314,8 @@ func (wf *WakuFilterFullNode) pushMessage(ctx context.Context, peerID peer.ID, e
// Stop unmounts the filter protocol // Stop unmounts the filter protocol
func (wf *WakuFilterFullNode) Stop() { func (wf *WakuFilterFullNode) Stop() {
if wf.cancel == nil { wf.CommonService.Stop(func() {
return wf.h.RemoveStreamHandler(FilterSubscribeID_v20beta1)
} wf.msgSub.Unsubscribe()
})
wf.h.RemoveStreamHandler(FilterSubscribeID_v20beta1)
wf.cancel()
wf.msgSub.Unsubscribe()
wf.wg.Wait()
} }

View File

@ -8,15 +8,14 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/protocol"
) )
var ErrNotFound = errors.New("not found")
type ContentTopicSet map[string]struct{}
type PeerSet map[peer.ID]struct{} type PeerSet map[peer.ID]struct{}
type PubsubTopics map[string]ContentTopicSet // pubsubTopic => contentTopics type PubsubTopics map[string]protocol.ContentTopicSet // pubsubTopic => contentTopics
var errNotFound = errors.New("not found")
type SubscribersMap struct { type SubscribersMap struct {
sync.RWMutex sync.RWMutex
@ -57,7 +56,7 @@ func (sub *SubscribersMap) Set(peerID peer.ID, pubsubTopic string, contentTopics
contentTopicsMap, ok := pubsubTopicMap[pubsubTopic] contentTopicsMap, ok := pubsubTopicMap[pubsubTopic]
if !ok { if !ok {
contentTopicsMap = make(ContentTopicSet) contentTopicsMap = make(protocol.ContentTopicSet)
} }
for _, c := range contentTopics { for _, c := range contentTopics {
@ -98,12 +97,12 @@ func (sub *SubscribersMap) Delete(peerID peer.ID, pubsubTopic string, contentTop
pubsubTopicMap, ok := sub.items[peerID] pubsubTopicMap, ok := sub.items[peerID]
if !ok { if !ok {
return ErrNotFound return errNotFound
} }
contentTopicsMap, ok := pubsubTopicMap[pubsubTopic] contentTopicsMap, ok := pubsubTopicMap[pubsubTopic]
if !ok { if !ok {
return ErrNotFound return errNotFound
} }
// Removing content topics individually // Removing content topics individually
@ -132,7 +131,7 @@ func (sub *SubscribersMap) Delete(peerID peer.ID, pubsubTopic string, contentTop
func (sub *SubscribersMap) deleteAll(peerID peer.ID) error { func (sub *SubscribersMap) deleteAll(peerID peer.ID) error {
pubsubTopicMap, ok := sub.items[peerID] pubsubTopicMap, ok := sub.items[peerID]
if !ok { if !ok {
return ErrNotFound return errNotFound
} }
for pubsubTopic, contentTopicsMap := range pubsubTopicMap { for pubsubTopic, contentTopicsMap := range pubsubTopicMap {

View File

@ -155,9 +155,9 @@ func (sub *Subscribers) RemoveContentFilters(peerID peer.ID, requestID string, c
// make sure we delete the subscriber // make sure we delete the subscriber
// if no more content filters left // if no more content filters left
for _, peerId := range peerIdsToRemove { for _, peerID := range peerIdsToRemove {
for i, s := range sub.subscribers { for i, s := range sub.subscribers {
if s.peer == peerId && s.requestID == requestID { if s.peer == peerID && s.requestID == requestID {
l := len(sub.subscribers) - 1 l := len(sub.subscribers) - 1
sub.subscribers[i] = sub.subscribers[l] sub.subscribers[i] = sub.subscribers[l]
sub.subscribers = sub.subscribers[:l] sub.subscribers = sub.subscribers[:l]

View File

@ -5,7 +5,6 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"math" "math"
"sync"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
@ -47,12 +46,11 @@ type (
} }
WakuFilter struct { WakuFilter struct {
cancel context.CancelFunc *protocol.CommonService
h host.Host h host.Host
isFullNode bool isFullNode bool
msgSub relay.Subscription msgSub relay.Subscription
metrics Metrics metrics Metrics
wg *sync.WaitGroup
log *zap.Logger log *zap.Logger
filters *FilterMap filters *FilterMap
@ -75,8 +73,8 @@ func NewWakuFilter(broadcaster relay.Broadcaster, isFullNode bool, timesource ti
opt(params) opt(params)
} }
wf.wg = &sync.WaitGroup{}
wf.isFullNode = isFullNode wf.isFullNode = isFullNode
wf.CommonService = protocol.NewCommonService()
wf.filters = NewFilterMap(broadcaster, timesource) wf.filters = NewFilterMap(broadcaster, timesource)
wf.subscribers = NewSubscribers(params.Timeout) wf.subscribers = NewSubscribers(params.Timeout)
wf.metrics = newMetrics(reg) wf.metrics = newMetrics(reg)
@ -90,23 +88,19 @@ func (wf *WakuFilter) SetHost(h host.Host) {
} }
func (wf *WakuFilter) Start(ctx context.Context, sub relay.Subscription) error { func (wf *WakuFilter) Start(ctx context.Context, sub relay.Subscription) error {
wf.wg.Wait() // Wait for any goroutines to stop return wf.CommonService.Start(ctx, func() error {
return wf.start(sub)
ctx, cancel := context.WithCancel(ctx) })
wf.h.SetStreamHandlerMatch(FilterID_v20beta1, protocol.PrefixTextMatch(string(FilterID_v20beta1)), wf.onRequest(ctx))
wf.cancel = cancel
wf.msgSub = sub
wf.wg.Add(1)
go wf.filterListener(ctx)
wf.log.Info("filter protocol started")
return nil
} }
func (wf *WakuFilter) start(sub relay.Subscription) error {
wf.h.SetStreamHandlerMatch(FilterID_v20beta1, protocol.PrefixTextMatch(string(FilterID_v20beta1)), wf.onRequest(wf.Context()))
wf.msgSub = sub
wf.WaitGroup().Add(1)
go wf.filterListener(wf.Context())
wf.log.Info("filter protocol started")
return nil
}
func (wf *WakuFilter) onRequest(ctx context.Context) func(s network.Stream) { func (wf *WakuFilter) onRequest(ctx context.Context) func(s network.Stream) {
return func(s network.Stream) { return func(s network.Stream) {
defer s.Close() defer s.Close()
@ -143,13 +137,13 @@ func (wf *WakuFilter) onRequest(ctx context.Context) func(s network.Stream) {
subscriber.filter.Topic = relay.DefaultWakuTopic subscriber.filter.Topic = relay.DefaultWakuTopic
} }
len := wf.subscribers.Append(subscriber) subscribersLen := wf.subscribers.Append(subscriber)
logger.Info("adding subscriber") logger.Info("adding subscriber")
wf.metrics.RecordSubscribers(len) wf.metrics.RecordSubscribers(subscribersLen)
} else { } else {
peerId := s.Conn().RemotePeer() peerID := s.Conn().RemotePeer()
wf.subscribers.RemoveContentFilters(peerId, filterRPCRequest.RequestId, filterRPCRequest.Request.ContentFilters) wf.subscribers.RemoveContentFilters(peerID, filterRPCRequest.RequestId, filterRPCRequest.Request.ContentFilters)
logger.Info("removing subscriber") logger.Info("removing subscriber")
wf.metrics.RecordSubscribers(wf.subscribers.Length()) wf.metrics.RecordSubscribers(wf.subscribers.Length())
@ -188,7 +182,7 @@ func (wf *WakuFilter) pushMessage(ctx context.Context, subscriber Subscriber, ms
} }
func (wf *WakuFilter) filterListener(ctx context.Context) { func (wf *WakuFilter) filterListener(ctx context.Context) {
defer wf.wg.Done() defer wf.WaitGroup().Done()
// This function is invoked for each message received // This function is invoked for each message received
// on the full node in context of Waku2-Filter // on the full node in context of Waku2-Filter
@ -270,7 +264,7 @@ func (wf *WakuFilter) requestSubscription(ctx context.Context, filter ContentFil
defer conn.Close() defer conn.Close()
// This is the only successful path to subscription // This is the only successful path to subscription
requestID := hex.EncodeToString(protocol.GenerateRequestId()) requestID := hex.EncodeToString(protocol.GenerateRequestID())
writer := pbio.NewDelimitedWriter(conn) writer := pbio.NewDelimitedWriter(conn)
filterRPC := &pb.FilterRPC{RequestId: requestID, Request: request} filterRPC := &pb.FilterRPC{RequestId: requestID, Request: request}
@ -301,7 +295,7 @@ func (wf *WakuFilter) Unsubscribe(ctx context.Context, contentFilter ContentFilt
defer conn.Close() defer conn.Close()
// This is the only successful path to subscription // This is the only successful path to subscription
id := protocol.GenerateRequestId() id := protocol.GenerateRequestID()
var contentFilters []*pb.FilterRequest_ContentFilter var contentFilters []*pb.FilterRequest_ContentFilter
for _, ct := range contentFilter.ContentTopics { for _, ct := range contentFilter.ContentTopics {
@ -327,19 +321,13 @@ func (wf *WakuFilter) Unsubscribe(ctx context.Context, contentFilter ContentFilt
// Stop unmounts the filter protocol // Stop unmounts the filter protocol
func (wf *WakuFilter) Stop() { func (wf *WakuFilter) Stop() {
if wf.cancel == nil { wf.CommonService.Stop(func() {
return wf.msgSub.Unsubscribe()
}
wf.cancel() wf.h.RemoveStreamHandler(FilterID_v20beta1)
wf.filters.RemoveAll()
wf.msgSub.Unsubscribe() wf.subscribers.Clear()
})
wf.h.RemoveStreamHandler(FilterID_v20beta1)
wf.filters.RemoveAll()
wf.subscribers.Clear()
wf.wg.Wait()
} }
// Subscribe setups a subscription to receive messages that match a specific content filter // Subscribe setups a subscription to receive messages that match a specific content filter
@ -444,8 +432,8 @@ func (wf *WakuFilter) UnsubscribeFilter(ctx context.Context, cf ContentFilter) e
} }
} }
for rId := range idsToRemove { for rID := range idsToRemove {
wf.filters.Delete(rId) wf.filters.Delete(rID)
} }
return nil return nil

View File

@ -25,7 +25,7 @@ const LightPushID_v20beta1 = libp2pProtocol.ID("/vac/waku/lightpush/2.0.0-beta1"
var ( var (
ErrNoPeersAvailable = errors.New("no suitable remote peers") ErrNoPeersAvailable = errors.New("no suitable remote peers")
ErrInvalidId = errors.New("invalid request id") ErrInvalidID = errors.New("invalid request id")
) )
// WakuLightPush is the implementation of the Waku LightPush protocol // WakuLightPush is the implementation of the Waku LightPush protocol
@ -72,8 +72,8 @@ func (wakuLP *WakuLightPush) Start(ctx context.Context) error {
} }
// relayIsNotAvailable determines if this node supports relaying messages for other lightpush clients // relayIsNotAvailable determines if this node supports relaying messages for other lightpush clients
func (wakuLp *WakuLightPush) relayIsNotAvailable() bool { func (wakuLP *WakuLightPush) relayIsNotAvailable() bool {
return wakuLp.relay == nil return wakuLP.relay == nil
} }
func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Stream) { func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Stream) {
@ -144,15 +144,10 @@ func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Strea
} }
} }
func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, opts ...Option) (*pb.PushResponse, error) { // request sends a message via lightPush protocol to either a specified peer or peer that is selected.
params := new(lightPushParameters) func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, params *lightPushParameters) (*pb.PushResponse, error) {
params.host = wakuLP.h if params == nil {
params.log = wakuLP.log return nil, errors.New("lightpush params are mandatory")
params.pm = wakuLP.pm
optList := append(DefaultOptions(wakuLP.h), opts...)
for _, opt := range optList {
opt(params)
} }
if params.selectedPeer == "" { if params.selectedPeer == "" {
@ -161,7 +156,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
} }
if len(params.requestID) == 0 { if len(params.requestID) == 0 {
return nil, ErrInvalidId return nil, ErrInvalidID
} }
logger := wakuLP.log.With(logging.HostID("peer", params.selectedPeer)) logger := wakuLP.log.With(logging.HostID("peer", params.selectedPeer))
@ -215,31 +210,49 @@ func (wakuLP *WakuLightPush) Stop() {
wakuLP.h.RemoveStreamHandler(LightPushID_v20beta1) wakuLP.h.RemoveStreamHandler(LightPushID_v20beta1)
} }
// PublishToTopic is used to broadcast a WakuMessage to a pubsub topic via lightpush protocol // Optional PublishToTopic is used to broadcast a WakuMessage to a pubsub topic via lightpush protocol
func (wakuLP *WakuLightPush) PublishToTopic(ctx context.Context, message *wpb.WakuMessage, topic string, opts ...Option) ([]byte, error) { // If pubSubTopic is not provided, then contentTopic is use to derive the relevant pubSubTopic via autosharding.
func (wakuLP *WakuLightPush) PublishToTopic(ctx context.Context, message *wpb.WakuMessage, opts ...Option) ([]byte, error) {
if message == nil { if message == nil {
return nil, errors.New("message can't be null") return nil, errors.New("message can't be null")
} }
params := new(lightPushParameters)
params.host = wakuLP.h
params.log = wakuLP.log
params.pm = wakuLP.pm
optList := append(DefaultOptions(wakuLP.h), opts...)
for _, opt := range optList {
opt(params)
}
if params.pubsubTopic == "" {
var err error
params.pubsubTopic, err = protocol.GetPubSubTopicFromContentTopic(message.ContentTopic)
if err != nil {
return nil, err
}
}
req := new(pb.PushRequest) req := new(pb.PushRequest)
req.Message = message req.Message = message
req.PubsubTopic = topic req.PubsubTopic = params.pubsubTopic
response, err := wakuLP.request(ctx, req, opts...) response, err := wakuLP.request(ctx, req, params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if response.IsSuccess { if response.IsSuccess {
hash := message.Hash(topic) hash := message.Hash(params.pubsubTopic)
wakuLP.log.Info("waku.lightpush published", logging.HexString("hash", hash)) wakuLP.log.Info("waku.lightpush published", logging.HexString("hash", hash))
return hash, nil return hash, nil
} else {
return nil, errors.New(response.Info)
} }
return nil, errors.New(response.Info)
} }
// Publish is used to broadcast a WakuMessage to the default waku pubsub topic via lightpush protocol // Publish is used to broadcast a WakuMessage to the pubSubTopic (which is derived from the contentTopic) via lightpush protocol
// If auto-sharding is not to be used, then PublishToTopic API should be used
func (wakuLP *WakuLightPush) Publish(ctx context.Context, message *wpb.WakuMessage, opts ...Option) ([]byte, error) { func (wakuLP *WakuLightPush) Publish(ctx context.Context, message *wpb.WakuMessage, opts ...Option) ([]byte, error) {
return wakuLP.PublishToTopic(ctx, message, relay.DefaultWakuTopic, opts...) return wakuLP.PublishToTopic(ctx, message, opts...)
} }

View File

@ -17,6 +17,7 @@ type lightPushParameters struct {
requestID []byte requestID []byte
pm *peermanager.PeerManager pm *peermanager.PeerManager
log *zap.Logger log *zap.Logger
pubsubTopic string
} }
// Option is the type of options accepted when performing LightPush protocol requests // Option is the type of options accepted when performing LightPush protocol requests
@ -40,7 +41,7 @@ func WithAutomaticPeerSelection(fromThesePeers ...peer.ID) Option {
if params.pm == nil { if params.pm == nil {
p, err = utils.SelectPeer(params.host, LightPushID_v20beta1, fromThesePeers, params.log) p, err = utils.SelectPeer(params.host, LightPushID_v20beta1, fromThesePeers, params.log)
} else { } else {
p, err = params.pm.SelectPeer(LightPushID_v20beta1, fromThesePeers, params.log) p, err = params.pm.SelectPeer(LightPushID_v20beta1, "", fromThesePeers...)
} }
if err == nil { if err == nil {
params.selectedPeer = p params.selectedPeer = p
@ -50,6 +51,12 @@ func WithAutomaticPeerSelection(fromThesePeers ...peer.ID) Option {
} }
} }
func WithPubSubTopic(pubsubTopic string) Option {
return func(params *lightPushParameters) {
params.pubsubTopic = pubsubTopic
}
}
// WithFastestPeerSelection is an option used to select a peer from the peer store // WithFastestPeerSelection is an option used to select a peer from the peer store
// with the lowest ping. If a list of specific peers is passed, the peer will be chosen // with the lowest ping. If a list of specific peers is passed, the peer will be chosen
// from that list assuming it supports the chosen protocol, otherwise it will chose a peer // from that list assuming it supports the chosen protocol, otherwise it will chose a peer
@ -77,7 +84,7 @@ func WithRequestID(requestID []byte) Option {
// when publishing a message // when publishing a message
func WithAutomaticRequestID() Option { func WithAutomaticRequestID() Option {
return func(params *lightPushParameters) { return func(params *lightPushParameters) {
params.requestID = protocol.GenerateRequestId() params.requestID = protocol.GenerateRequestID()
} }
} }

View File

@ -100,9 +100,9 @@ func (wakuPX *WakuPeerExchange) handleResponse(ctx context.Context, response *pb
if len(discoveredPeers) != 0 { if len(discoveredPeers) != 0 {
wakuPX.log.Info("connecting to newly discovered peers", zap.Int("count", len(discoveredPeers))) wakuPX.log.Info("connecting to newly discovered peers", zap.Int("count", len(discoveredPeers)))
wakuPX.wg.Add(1) wakuPX.WaitGroup().Add(1)
go func() { go func() {
defer wakuPX.wg.Done() defer wakuPX.WaitGroup().Done()
peerCh := make(chan peermanager.PeerData) peerCh := make(chan peermanager.PeerData)
defer close(peerCh) defer close(peerCh)

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"sync"
"time" "time"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
@ -28,7 +27,7 @@ const MaxCacheSize = 1000
var ( var (
ErrNoPeersAvailable = errors.New("no suitable remote peers") ErrNoPeersAvailable = errors.New("no suitable remote peers")
ErrInvalidId = errors.New("invalid request id") ErrInvalidID = errors.New("invalid request id")
) )
// PeerConnector will subscribe to a channel containing the information for all peers found by this discovery protocol // PeerConnector will subscribe to a channel containing the information for all peers found by this discovery protocol
@ -43,9 +42,8 @@ type WakuPeerExchange struct {
metrics Metrics metrics Metrics
log *zap.Logger log *zap.Logger
cancel context.CancelFunc *protocol.CommonService
wg sync.WaitGroup
peerConnector PeerConnector peerConnector PeerConnector
enrCache *enrCache enrCache *enrCache
} }
@ -65,35 +63,31 @@ func NewWakuPeerExchange(disc *discv5.DiscoveryV5, peerConnector PeerConnector,
wakuPX.enrCache = newEnrCache wakuPX.enrCache = newEnrCache
wakuPX.peerConnector = peerConnector wakuPX.peerConnector = peerConnector
wakuPX.pm = pm wakuPX.pm = pm
wakuPX.CommonService = protocol.NewCommonService()
return wakuPX, nil return wakuPX, nil
} }
// Sets the host to be able to mount or consume a protocol // SetHost sets the host to be able to mount or consume a protocol
func (wakuPX *WakuPeerExchange) SetHost(h host.Host) { func (wakuPX *WakuPeerExchange) SetHost(h host.Host) {
wakuPX.h = h wakuPX.h = h
} }
// Start inits the peer exchange protocol // Start inits the peer exchange protocol
func (wakuPX *WakuPeerExchange) Start(ctx context.Context) error { func (wakuPX *WakuPeerExchange) Start(ctx context.Context) error {
if wakuPX.cancel != nil { return wakuPX.CommonService.Start(ctx, wakuPX.start)
return errors.New("peer exchange already started") }
}
wakuPX.wg.Wait() // Waiting for any go routines to stop func (wakuPX *WakuPeerExchange) start() error {
wakuPX.h.SetStreamHandlerMatch(PeerExchangeID_v20alpha1, protocol.PrefixTextMatch(string(PeerExchangeID_v20alpha1)), wakuPX.onRequest())
ctx, cancel := context.WithCancel(ctx) wakuPX.WaitGroup().Add(1)
wakuPX.cancel = cancel go wakuPX.runPeerExchangeDiscv5Loop(wakuPX.Context())
wakuPX.h.SetStreamHandlerMatch(PeerExchangeID_v20alpha1, protocol.PrefixTextMatch(string(PeerExchangeID_v20alpha1)), wakuPX.onRequest(ctx))
wakuPX.log.Info("Peer exchange protocol started") wakuPX.log.Info("Peer exchange protocol started")
wakuPX.wg.Add(1)
go wakuPX.runPeerExchangeDiscv5Loop(ctx)
return nil return nil
} }
func (wakuPX *WakuPeerExchange) onRequest(ctx context.Context) func(s network.Stream) { func (wakuPX *WakuPeerExchange) onRequest() func(s network.Stream) {
return func(s network.Stream) { return func(s network.Stream) {
defer s.Close() defer s.Close()
logger := wakuPX.log.With(logging.HostID("peer", s.Conn().RemotePeer())) logger := wakuPX.log.With(logging.HostID("peer", s.Conn().RemotePeer()))
@ -133,12 +127,9 @@ func (wakuPX *WakuPeerExchange) onRequest(ctx context.Context) func(s network.St
// Stop unmounts the peer exchange protocol // Stop unmounts the peer exchange protocol
func (wakuPX *WakuPeerExchange) Stop() { func (wakuPX *WakuPeerExchange) Stop() {
if wakuPX.cancel == nil { wakuPX.CommonService.Stop(func() {
return wakuPX.h.RemoveStreamHandler(PeerExchangeID_v20alpha1)
} })
wakuPX.h.RemoveStreamHandler(PeerExchangeID_v20alpha1)
wakuPX.cancel()
wakuPX.wg.Wait()
} }
func (wakuPX *WakuPeerExchange) iterate(ctx context.Context) error { func (wakuPX *WakuPeerExchange) iterate(ctx context.Context) error {
@ -173,7 +164,7 @@ func (wakuPX *WakuPeerExchange) iterate(ctx context.Context) error {
} }
func (wakuPX *WakuPeerExchange) runPeerExchangeDiscv5Loop(ctx context.Context) { func (wakuPX *WakuPeerExchange) runPeerExchangeDiscv5Loop(ctx context.Context) {
defer wakuPX.wg.Done() defer wakuPX.WaitGroup().Done()
// Runs a discv5 loop adding new peers to the px peer cache // Runs a discv5 loop adding new peers to the px peer cache
if wakuPX.disc == nil { if wakuPX.disc == nil {

View File

@ -37,7 +37,7 @@ func WithAutomaticPeerSelection(fromThesePeers ...peer.ID) PeerExchangeOption {
if params.pm == nil { if params.pm == nil {
p, err = utils.SelectPeer(params.host, PeerExchangeID_v20alpha1, fromThesePeers, params.log) p, err = utils.SelectPeer(params.host, PeerExchangeID_v20alpha1, fromThesePeers, params.log)
} else { } else {
p, err = params.pm.SelectPeer(PeerExchangeID_v20alpha1, fromThesePeers, params.log) p, err = params.pm.SelectPeer(PeerExchangeID_v20alpha1, "", fromThesePeers...)
} }
if err == nil { if err == nil {
params.selectedPeer = p params.selectedPeer = p

View File

@ -7,15 +7,23 @@ import (
"strings" "strings"
) )
// Waku2PubsubTopicPrefix is the expected prefix to be used for pubsub topics
const Waku2PubsubTopicPrefix = "/waku/2" const Waku2PubsubTopicPrefix = "/waku/2"
// StaticShardingPubsubTopicPrefix is the expected prefix to be used for static sharding pubsub topics
const StaticShardingPubsubTopicPrefix = Waku2PubsubTopicPrefix + "/rs" const StaticShardingPubsubTopicPrefix = Waku2PubsubTopicPrefix + "/rs"
// ErrInvalidStructure indicates that the pubsub topic is malformed
var ErrInvalidStructure = errors.New("invalid topic structure") var ErrInvalidStructure = errors.New("invalid topic structure")
// ErrInvalidTopicPrefix indicates that the pubsub topic is missing the prefix /waku/2
var ErrInvalidTopicPrefix = errors.New("must start with " + Waku2PubsubTopicPrefix) var ErrInvalidTopicPrefix = errors.New("must start with " + Waku2PubsubTopicPrefix)
var ErrMissingTopicName = errors.New("missing topic-name") var ErrMissingTopicName = errors.New("missing topic-name")
var ErrInvalidShardedTopicPrefix = errors.New("must start with " + StaticShardingPubsubTopicPrefix) var ErrInvalidShardedTopicPrefix = errors.New("must start with " + StaticShardingPubsubTopicPrefix)
var ErrMissingClusterIndex = errors.New("missing shard_cluster_index") var ErrMissingClusterIndex = errors.New("missing shard_cluster_index")
var ErrMissingShardNumber = errors.New("missing shard_number") var ErrMissingShardNumber = errors.New("missing shard_number")
// ErrInvalidNumberFormat indicates that a number exceeds the allowed range
var ErrInvalidNumberFormat = errors.New("only 2^16 numbers are allowed") var ErrInvalidNumberFormat = errors.New("only 2^16 numbers are allowed")
// NamespacedPubsubTopicKind used to represent kind of NamespacedPubsubTopicKind // NamespacedPubsubTopicKind used to represent kind of NamespacedPubsubTopicKind
@ -107,7 +115,7 @@ func (s StaticShardingPubsubTopic) Cluster() uint16 {
return s.cluster return s.cluster
} }
// Cluster returns the shard number // Shard returns the shard number
func (s StaticShardingPubsubTopic) Shard() uint16 { func (s StaticShardingPubsubTopic) Shard() uint16 {
return s.shard return s.shard
} }
@ -174,14 +182,14 @@ func ToShardedPubsubTopic(topic string) (NamespacedPubsubTopic, error) {
return nil, err return nil, err
} }
return s, nil return s, nil
} else {
s := NamedShardingPubsubTopic{}
err := s.Parse(topic)
if err != nil {
return nil, err
}
return s, nil
} }
s := NamedShardingPubsubTopic{}
err := s.Parse(topic)
if err != nil {
return nil, err
}
return s, nil
} }
// DefaultPubsubTopic is the default pubSub topic used in waku // DefaultPubsubTopic is the default pubSub topic used in waku

View File

@ -75,15 +75,15 @@ func (s *chStore) broadcast(ctx context.Context, m *protocol.Envelope) {
} }
} }
func (b *chStore) close() { func (s *chStore) close() {
b.mu.Lock() s.mu.Lock()
defer b.mu.Unlock() defer s.mu.Unlock()
for _, chans := range b.topicToChans { for _, chans := range s.topicToChans {
for _, ch := range chans { for _, ch := range chans {
close(ch) close(ch)
} }
} }
b.topicToChans = nil s.topicToChans = nil
} }
// Broadcaster is used to create a fanout for an envelope that will be received by any subscriber interested in the topic of the message // Broadcaster is used to create a fanout for an envelope that will be received by any subscriber interested in the topic of the message

View File

@ -10,14 +10,14 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/crypto/secp256k1"
pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
proto "google.golang.org/protobuf/proto"
"github.com/waku-org/go-waku/waku/v2/hash" "github.com/waku-org/go-waku/waku/v2/hash"
"github.com/waku-org/go-waku/waku/v2/protocol/pb" "github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/timesource" "github.com/waku-org/go-waku/waku/v2/timesource"
"go.uber.org/zap" "go.uber.org/zap"
proto "google.golang.org/protobuf/proto"
) )
func msgHash(pubSubTopic string, msg *pb.WakuMessage) []byte { func msgHash(pubSubTopic string, msg *pb.WakuMessage) []byte {
@ -38,6 +38,68 @@ func msgHash(pubSubTopic string, msg *pb.WakuMessage) []byte {
) )
} }
type validatorFn = func(ctx context.Context, msg *pb.WakuMessage, topic string) bool
func (w *WakuRelay) RegisterDefaultValidator(fn validatorFn) {
w.topicValidatorMutex.Lock()
defer w.topicValidatorMutex.Unlock()
w.defaultTopicValidators = append(w.defaultTopicValidators, fn)
}
func (w *WakuRelay) RegisterTopicValidator(topic string, fn validatorFn) {
w.topicValidatorMutex.Lock()
defer w.topicValidatorMutex.Unlock()
w.topicValidators[topic] = append(w.topicValidators[topic], fn)
}
func (w *WakuRelay) RemoveTopicValidator(topic string) {
w.topicValidatorMutex.Lock()
defer w.topicValidatorMutex.Unlock()
delete(w.topicValidators, topic)
}
func (w *WakuRelay) topicValidator(topic string) func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
msg := new(pb.WakuMessage)
err := proto.Unmarshal(message.Data, msg)
if err != nil {
return false
}
w.topicValidatorMutex.RLock()
validators, exists := w.topicValidators[topic]
validators = append(validators, w.defaultTopicValidators...)
w.topicValidatorMutex.RUnlock()
if exists {
for _, v := range validators {
if !v(ctx, msg, topic) {
return false
}
}
}
return true
}
}
// AddSignedTopicValidator registers a gossipsub validator for a topic which will check that messages Meta field contains a valid ECDSA signature for the specified pubsub topic. This is used as a DoS prevention mechanism
func (w *WakuRelay) AddSignedTopicValidator(topic string, publicKey *ecdsa.PublicKey) error {
w.log.Info("adding validator to signed topic", zap.String("topic", topic), zap.String("publicKey", hex.EncodeToString(elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y))))
fn := signedTopicBuilder(w.timesource, publicKey)
w.RegisterTopicValidator(topic, fn)
if !w.IsSubscribed(topic) {
w.log.Warn("relay is not subscribed to signed topic", zap.String("topic", topic))
}
return nil
}
const messageWindowDuration = time.Minute * 5 const messageWindowDuration = time.Minute * 5
func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool { func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool {
@ -51,17 +113,9 @@ func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool {
return now.Sub(msgTime).Abs() <= messageWindowDuration return now.Sub(msgTime).Abs() <= messageWindowDuration
} }
type validatorFn = func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool func signedTopicBuilder(t timesource.Timesource, publicKey *ecdsa.PublicKey) validatorFn {
func validatorFnBuilder(t timesource.Timesource, topic string, publicKey *ecdsa.PublicKey) (validatorFn, error) {
publicKeyBytes := crypto.FromECDSAPub(publicKey) publicKeyBytes := crypto.FromECDSAPub(publicKey)
return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool { return func(ctx context.Context, msg *pb.WakuMessage, topic string) bool {
msg := new(pb.WakuMessage)
err := proto.Unmarshal(message.Data, msg)
if err != nil {
return false
}
if !withinTimeWindow(t, msg) { if !withinTimeWindow(t, msg) {
return false return false
} }
@ -70,28 +124,7 @@ func validatorFnBuilder(t timesource.Timesource, topic string, publicKey *ecdsa.
signature := msg.Meta signature := msg.Meta
return secp256k1.VerifySignature(publicKeyBytes, msgHash, signature) return secp256k1.VerifySignature(publicKeyBytes, msgHash, signature)
}, nil
}
// AddSignedTopicValidator registers a gossipsub validator for a topic which will check that messages Meta field contains a valid ECDSA signature for the specified pubsub topic. This is used as a DoS prevention mechanism
func (w *WakuRelay) AddSignedTopicValidator(topic string, publicKey *ecdsa.PublicKey) error {
w.log.Info("adding validator to signed topic", zap.String("topic", topic), zap.String("publicKey", hex.EncodeToString(elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y))))
fn, err := validatorFnBuilder(w.timesource, topic, publicKey)
if err != nil {
return err
} }
err = w.pubsub.RegisterTopicValidator(topic, fn)
if err != nil {
return err
}
if !w.IsSubscribed(topic) {
w.log.Warn("relay is not subscribed to signed topic", zap.String("topic", topic))
}
return nil
} }
// SignMessage adds an ECDSA signature to a WakuMessage as an opt-in mechanism for DoS prevention // SignMessage adds an ECDSA signature to a WakuMessage as an opt-in mechanism for DoS prevention

View File

@ -49,25 +49,30 @@ type WakuRelay struct {
minPeersToPublish int minPeersToPublish int
topicValidatorMutex sync.RWMutex
topicValidators map[string][]validatorFn
defaultTopicValidators []validatorFn
// TODO: convert to concurrent maps // TODO: convert to concurrent maps
topicsMutex sync.Mutex topicsMutex sync.RWMutex
wakuRelayTopics map[string]*pubsub.Topic wakuRelayTopics map[string]*pubsub.Topic
relaySubs map[string]*pubsub.Subscription relaySubs map[string]*pubsub.Subscription
topicEvtHanders map[string]*pubsub.TopicEventHandler
events event.Bus events event.Bus
emitters struct { emitters struct {
EvtRelaySubscribed event.Emitter EvtRelaySubscribed event.Emitter
EvtRelayUnsubscribed event.Emitter EvtRelayUnsubscribed event.Emitter
EvtPeerTopic event.Emitter
} }
ctx context.Context *waku_proto.CommonService
cancel context.CancelFunc
wg sync.WaitGroup
} }
// EvtRelaySubscribed is an event emitted when a new subscription to a pubsub topic is created // EvtRelaySubscribed is an event emitted when a new subscription to a pubsub topic is created
type EvtRelaySubscribed struct { type EvtRelaySubscribed struct {
Topic string Topic string
TopicInst *pubsub.Topic
} }
// EvtRelayUnsubscribed is an event emitted when a subscription to a pubsub topic is closed // EvtRelayUnsubscribed is an event emitted when a subscription to a pubsub topic is closed
@ -75,7 +80,20 @@ type EvtRelayUnsubscribed struct {
Topic string Topic string
} }
func msgIdFn(pmsg *pubsub_pb.Message) string { type PeerTopicState int
const (
PEER_JOINED = iota
PEER_LEFT
)
type EvtPeerTopic struct {
PubsubTopic string
PeerID peer.ID
State PeerTopicState
}
func msgIDFn(pmsg *pubsub_pb.Message) string {
return string(hash.SHA256(pmsg.Data)) return string(hash.SHA256(pmsg.Data))
} }
@ -85,9 +103,11 @@ func NewWakuRelay(bcaster Broadcaster, minPeersToPublish int, timesource timesou
w.timesource = timesource w.timesource = timesource
w.wakuRelayTopics = make(map[string]*pubsub.Topic) w.wakuRelayTopics = make(map[string]*pubsub.Topic)
w.relaySubs = make(map[string]*pubsub.Subscription) w.relaySubs = make(map[string]*pubsub.Subscription)
w.topicEvtHanders = make(map[string]*pubsub.TopicEventHandler)
w.topicValidators = make(map[string][]validatorFn)
w.bcaster = bcaster w.bcaster = bcaster
w.minPeersToPublish = minPeersToPublish w.minPeersToPublish = minPeersToPublish
w.wg = sync.WaitGroup{} w.CommonService = waku_proto.NewCommonService()
w.log = log.Named("relay") w.log = log.Named("relay")
w.events = eventbus.NewBus() w.events = eventbus.NewBus()
w.metrics = newMetrics(reg, w.log) w.metrics = newMetrics(reg, w.log)
@ -96,11 +116,11 @@ func NewWakuRelay(bcaster Broadcaster, minPeersToPublish int, timesource timesou
cfg.PruneBackoff = time.Minute cfg.PruneBackoff = time.Minute
cfg.UnsubscribeBackoff = 5 * time.Second cfg.UnsubscribeBackoff = 5 * time.Second
cfg.GossipFactor = 0.25 cfg.GossipFactor = 0.25
cfg.D = 6 cfg.D = waku_proto.GossipSubOptimalFullMeshSize
cfg.Dlo = 4 cfg.Dlo = 4
cfg.Dhi = 12 cfg.Dhi = 12
cfg.Dout = 3 cfg.Dout = 3
cfg.Dlazy = 6 cfg.Dlazy = waku_proto.GossipSubOptimalFullMeshSize
cfg.HeartbeatInterval = time.Second cfg.HeartbeatInterval = time.Second
cfg.HistoryLength = 6 cfg.HistoryLength = 6
cfg.HistoryGossip = 3 cfg.HistoryGossip = 3
@ -160,7 +180,7 @@ func NewWakuRelay(bcaster Broadcaster, minPeersToPublish int, timesource timesou
w.opts = append([]pubsub.Option{ w.opts = append([]pubsub.Option{
pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign), pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign),
pubsub.WithNoAuthor(), pubsub.WithNoAuthor(),
pubsub.WithMessageIdFn(msgIdFn), pubsub.WithMessageIdFn(msgIDFn),
pubsub.WithGossipSubProtocols( pubsub.WithGossipSubProtocols(
[]protocol.ID{WakuRelayID_v200, pubsub.GossipSubID_v11, pubsub.GossipSubID_v10, pubsub.FloodSubID}, []protocol.ID{WakuRelayID_v200, pubsub.GossipSubID_v11, pubsub.GossipSubID_v10, pubsub.FloodSubID},
func(feat pubsub.GossipSubFeature, proto protocol.ID) bool { func(feat pubsub.GossipSubFeature, proto protocol.ID) bool {
@ -179,12 +199,6 @@ func NewWakuRelay(bcaster Broadcaster, minPeersToPublish int, timesource timesou
pubsub.WithSeenMessagesTTL(2 * time.Minute), pubsub.WithSeenMessagesTTL(2 * time.Minute),
pubsub.WithPeerScore(w.peerScoreParams, w.peerScoreThresholds), pubsub.WithPeerScore(w.peerScoreParams, w.peerScoreThresholds),
pubsub.WithPeerScoreInspect(w.peerScoreInspector, 6*time.Second), pubsub.WithPeerScoreInspect(w.peerScoreInspector, 6*time.Second),
// TODO: to improve - setup default validator only if no default validator has been set.
pubsub.WithDefaultValidator(func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool {
msg := new(pb.WakuMessage)
err := proto.Unmarshal(message.Data, msg)
return err == nil
}),
}, opts...) }, opts...)
return w return w
@ -213,12 +227,11 @@ func (w *WakuRelay) SetHost(h host.Host) {
// Start initiates the WakuRelay protocol // Start initiates the WakuRelay protocol
func (w *WakuRelay) Start(ctx context.Context) error { func (w *WakuRelay) Start(ctx context.Context) error {
w.wg.Wait() return w.CommonService.Start(ctx, w.start)
ctx, cancel := context.WithCancel(ctx) }
w.ctx = ctx // TODO: create worker for creating subscriptions instead of storing context
w.cancel = cancel
ps, err := pubsub.NewGossipSub(ctx, w.host, w.opts...) func (w *WakuRelay) start() error {
ps, err := pubsub.NewGossipSub(w.Context(), w.host, w.opts...)
if err != nil { if err != nil {
return err return err
} }
@ -233,6 +246,11 @@ func (w *WakuRelay) Start(ctx context.Context) error {
return err return err
} }
w.emitters.EvtPeerTopic, err = w.events.Emitter(new(EvtPeerTopic))
if err != nil {
return err
}
w.log.Info("Relay protocol started") w.log.Info("Relay protocol started")
return nil return nil
} }
@ -244,8 +262,8 @@ func (w *WakuRelay) PubSub() *pubsub.PubSub {
// Topics returns a list of all the pubsub topics currently subscribed to // Topics returns a list of all the pubsub topics currently subscribed to
func (w *WakuRelay) Topics() []string { func (w *WakuRelay) Topics() []string {
defer w.topicsMutex.Unlock() defer w.topicsMutex.RUnlock()
w.topicsMutex.Lock() w.topicsMutex.RLock()
var result []string var result []string
for topic := range w.relaySubs { for topic := range w.relaySubs {
@ -256,8 +274,8 @@ func (w *WakuRelay) Topics() []string {
// IsSubscribed indicates whether the node is subscribed to a pubsub topic or not // IsSubscribed indicates whether the node is subscribed to a pubsub topic or not
func (w *WakuRelay) IsSubscribed(topic string) bool { func (w *WakuRelay) IsSubscribed(topic string) bool {
defer w.topicsMutex.Unlock() w.topicsMutex.RLock()
w.topicsMutex.Lock() defer w.topicsMutex.RUnlock()
_, ok := w.relaySubs[topic] _, ok := w.relaySubs[topic]
return ok return ok
} }
@ -273,6 +291,11 @@ func (w *WakuRelay) upsertTopic(topic string) (*pubsub.Topic, error) {
pubSubTopic, ok := w.wakuRelayTopics[topic] pubSubTopic, ok := w.wakuRelayTopics[topic]
if !ok { // Joins topic if node hasn't joined yet if !ok { // Joins topic if node hasn't joined yet
err := w.pubsub.RegisterTopicValidator(topic, w.topicValidator(topic))
if err != nil {
return nil, err
}
newTopic, err := w.pubsub.Join(string(topic)) newTopic, err := w.pubsub.Join(string(topic))
if err != nil { if err != nil {
return nil, err return nil, err
@ -302,15 +325,20 @@ func (w *WakuRelay) subscribe(topic string) (subs *pubsub.Subscription, err erro
return nil, err return nil, err
} }
evtHandler, err := w.addPeerTopicEventListener(pubSubTopic)
if err != nil {
return nil, err
}
w.topicEvtHanders[topic] = evtHandler
w.relaySubs[topic] = sub w.relaySubs[topic] = sub
err = w.emitters.EvtRelaySubscribed.Emit(EvtRelaySubscribed{topic}) err = w.emitters.EvtRelaySubscribed.Emit(EvtRelaySubscribed{topic, pubSubTopic})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if w.bcaster != nil { if w.bcaster != nil {
w.wg.Add(1) w.WaitGroup().Add(1)
go w.subscribeToTopic(topic, sub) go w.subscribeToTopic(topic, sub)
} }
w.log.Info("subscribing to topic", zap.String("topic", sub.Topic())) w.log.Info("subscribing to topic", zap.String("topic", sub.Topic()))
@ -364,15 +392,11 @@ func (w *WakuRelay) Publish(ctx context.Context, message *pb.WakuMessage) ([]byt
// Stop unmounts the relay protocol and stops all subscriptions // Stop unmounts the relay protocol and stops all subscriptions
func (w *WakuRelay) Stop() { func (w *WakuRelay) Stop() {
if w.cancel == nil { w.CommonService.Stop(func() {
return // Not started w.host.RemoveStreamHandler(WakuRelayID_v200)
} w.emitters.EvtRelaySubscribed.Close()
w.emitters.EvtRelayUnsubscribed.Close()
w.host.RemoveStreamHandler(WakuRelayID_v200) })
w.emitters.EvtRelaySubscribed.Close()
w.emitters.EvtRelayUnsubscribed.Close()
w.cancel()
w.wg.Wait()
} }
// EnoughPeersToPublish returns whether there are enough peers connected in the default waku pubsub topic // EnoughPeersToPublish returns whether there are enough peers connected in the default waku pubsub topic
@ -404,13 +428,16 @@ func (w *WakuRelay) SubscribeToTopic(ctx context.Context, topic string) (*Subscr
return &subscription, nil return &subscription, nil
} }
// SubscribeToTopic returns a Subscription to receive messages from the default waku pubsub topic // Subscribe returns a Subscription to receive messages from the default waku pubsub topic
func (w *WakuRelay) Subscribe(ctx context.Context) (*Subscription, error) { func (w *WakuRelay) Subscribe(ctx context.Context) (*Subscription, error) {
return w.SubscribeToTopic(ctx, DefaultWakuTopic) return w.SubscribeToTopic(ctx, DefaultWakuTopic)
} }
// Unsubscribe closes a subscription to a pubsub topic // Unsubscribe closes a subscription to a pubsub topic
func (w *WakuRelay) Unsubscribe(ctx context.Context, topic string) error { func (w *WakuRelay) Unsubscribe(ctx context.Context, topic string) error {
w.topicsMutex.Lock()
defer w.topicsMutex.Unlock()
sub, ok := w.relaySubs[topic] sub, ok := w.relaySubs[topic]
if !ok { if !ok {
return fmt.Errorf("not subscribed to topic") return fmt.Errorf("not subscribed to topic")
@ -420,12 +447,20 @@ func (w *WakuRelay) Unsubscribe(ctx context.Context, topic string) error {
w.relaySubs[topic].Cancel() w.relaySubs[topic].Cancel()
delete(w.relaySubs, topic) delete(w.relaySubs, topic)
evtHandler, ok := w.topicEvtHanders[topic]
if ok {
evtHandler.Cancel()
delete(w.topicEvtHanders, topic)
}
err := w.wakuRelayTopics[topic].Close() err := w.wakuRelayTopics[topic].Close()
if err != nil { if err != nil {
return err return err
} }
delete(w.wakuRelayTopics, topic) delete(w.wakuRelayTopics, topic)
w.RemoveTopicValidator(topic)
err = w.emitters.EvtRelayUnsubscribed.Emit(EvtRelayUnsubscribed{topic}) err = w.emitters.EvtRelayUnsubscribed.Emit(EvtRelayUnsubscribed{topic})
if err != nil { if err != nil {
return err return err
@ -454,12 +489,12 @@ func (w *WakuRelay) nextMessage(ctx context.Context, sub *pubsub.Subscription) <
} }
func (w *WakuRelay) subscribeToTopic(pubsubTopic string, sub *pubsub.Subscription) { func (w *WakuRelay) subscribeToTopic(pubsubTopic string, sub *pubsub.Subscription) {
defer w.wg.Done() defer w.WaitGroup().Done()
subChannel := w.nextMessage(w.ctx, sub) subChannel := w.nextMessage(w.Context(), sub)
for { for {
select { select {
case <-w.ctx.Done(): case <-w.Context().Done():
return return
// TODO: if there are no more relay subscriptions, close the pubsub subscription // TODO: if there are no more relay subscriptions, close the pubsub subscription
case msg, ok := <-subChannel: case msg, ok := <-subChannel:
@ -493,3 +528,46 @@ func (w *WakuRelay) Params() pubsub.GossipSubParams {
func (w *WakuRelay) Events() event.Bus { func (w *WakuRelay) Events() event.Bus {
return w.events return w.events
} }
func (w *WakuRelay) addPeerTopicEventListener(topic *pubsub.Topic) (*pubsub.TopicEventHandler, error) {
handler, err := topic.EventHandler()
if err != nil {
return nil, err
}
w.WaitGroup().Add(1)
go w.topicEventPoll(topic.String(), handler)
return handler, nil
}
func (w *WakuRelay) topicEventPoll(topic string, handler *pubsub.TopicEventHandler) {
defer w.WaitGroup().Done()
for {
evt, err := handler.NextPeerEvent(w.Context())
if err != nil {
if err == context.Canceled {
break
}
w.log.Error("failed to get next peer event", zap.String("topic", topic), zap.Error(err))
continue
}
if evt.Peer.Validate() != nil { //Empty peerEvent is returned when context passed in done.
break
}
if evt.Type == pubsub.PeerJoin {
w.log.Debug("received a PeerJoin event", zap.String("topic", topic), logging.HostID("peerID", evt.Peer))
err = w.emitters.EvtPeerTopic.Emit(EvtPeerTopic{PubsubTopic: topic, PeerID: evt.Peer, State: PEER_JOINED})
if err != nil {
w.log.Error("failed to emit PeerJoin", zap.String("topic", topic), zap.Error(err))
}
} else if evt.Type == pubsub.PeerLeave {
w.log.Debug("received a PeerLeave event", zap.String("topic", topic), logging.HostID("peerID", evt.Peer))
err = w.emitters.EvtPeerTopic.Emit(EvtPeerTopic{PubsubTopic: topic, PeerID: evt.Peer, State: PEER_LEFT})
if err != nil {
w.log.Error("failed to emit PeerLeave", zap.String("topic", topic), zap.Error(err))
}
} else {
w.log.Error("unknown event type received", zap.String("topic", topic),
zap.Int("eventType", int(evt.Type)))
}
}
}

View File

@ -18,9 +18,9 @@ var brHmacDrbgPool = sync.Pool{New: func() interface{} {
return hmacdrbg.NewHmacDrbg(256, seed, nil) return hmacdrbg.NewHmacDrbg(256, seed, nil)
}} }}
// GenerateRequestId generates a random 32 byte slice that can be used for // GenerateRequestID generates a random 32 byte slice that can be used for
// creating requests inf the filter, store and lightpush protocols // creating requests inf the filter, store and lightpush protocols
func GenerateRequestId() []byte { func GenerateRequestID() []byte {
rng := brHmacDrbgPool.Get().(*hmacdrbg.HmacDrbg) rng := brHmacDrbgPool.Get().(*hmacdrbg.HmacDrbg)
defer brHmacDrbgPool.Put(rng) defer brHmacDrbgPool.Put(rng)

View File

@ -26,7 +26,7 @@ const acceptableRootWindowSize = 5
type RegistrationHandler = func(tx *types.Transaction) type RegistrationHandler = func(tx *types.Transaction)
type SpamHandler = func(message *pb.WakuMessage) error type SpamHandler = func(msg *pb.WakuMessage, topic string) error
func toRLNSignal(wakuMessage *pb.WakuMessage) []byte { func toRLNSignal(wakuMessage *pb.WakuMessage) []byte {
if wakuMessage == nil { if wakuMessage == nil {

View File

@ -2,27 +2,27 @@
"language": "Solidity", "language": "Solidity",
"sources": { "sources": {
"WakuRln.sol": { "WakuRln.sol": {
"urls": ["./waku-rln-contract/contracts/WakuRln.sol"] "urls": ["../../../../../libs/waku-rln-contract/contracts/WakuRln.sol"]
}, },
"WakuRlnRegistry.sol": { "WakuRlnRegistry.sol": {
"urls": ["./waku-rln-contract/contracts/WakuRlnRegistry.sol"] "urls": ["../../../../../libs/waku-rln-contract/contracts/WakuRlnRegistry.sol"]
}, },
"rln-contract/PoseidonHasher.sol": { "rln-contract/PoseidonHasher.sol": {
"urls": [ "urls": [
"./waku-rln-contract/lib/rln-contract/contracts/PoseidonHasher.sol" "../../../../../libs/waku-rln-contract/lib/rln-contract/contracts/PoseidonHasher.sol"
] ]
}, },
"rln-contract/RlnBase.sol": { "rln-contract/RlnBase.sol": {
"urls": ["./waku-rln-contract/lib/rln-contract/contracts/RlnBase.sol"] "urls": ["../../../../../libs/waku-rln-contract/lib/rln-contract/contracts/RlnBase.sol"]
}, },
"rln-contract/IVerifier.sol": { "rln-contract/IVerifier.sol": {
"urls": ["./waku-rln-contract/lib/rln-contract/contracts/IVerifier.sol"] "urls": ["../../../../../libs/waku-rln-contract/lib/rln-contract/contracts/IVerifier.sol"]
}, },
"openzeppelin-contracts/contracts/access/Ownable.sol": { "openzeppelin-contracts/contracts/access/Ownable.sol": {
"urls": ["./waku-rln-contract/lib/openzeppelin-contracts/contracts/access/Ownable.sol"] "urls": ["../../../../../libs/waku-rln-contract/lib/openzeppelin-contracts/contracts/access/Ownable.sol"]
}, },
"openzeppelin-contracts/contracts/utils/Context.sol": { "openzeppelin-contracts/contracts/utils/Context.sol": {
"urls": ["./waku-rln-contract/lib/openzeppelin-contracts/contracts/utils/Context.sol"] "urls": ["../../../../../libs/waku-rln-contract/lib/openzeppelin-contracts/contracts/utils/Context.sol"]
} }
}, },
"settings": { "settings": {

View File

@ -3,6 +3,7 @@ package dynamic
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math/big" "math/big"
"sync" "sync"
"time" "time"
@ -28,28 +29,26 @@ var RLNAppInfo = keystore.AppInfo{
} }
type DynamicGroupManager struct { type DynamicGroupManager struct {
rln *rln.RLN MembershipFetcher
log *zap.Logger
metrics Metrics metrics Metrics
cancel context.CancelFunc cancel context.CancelFunc
wg sync.WaitGroup
identityCredential *rln.IdentityCredential identityCredential *rln.IdentityCredential
membershipIndex rln.MembershipIndex membershipIndex rln.MembershipIndex
web3Config *web3.Config lastBlockProcessedMutex sync.RWMutex
lastBlockProcessed uint64 lastBlockProcessed uint64
eventHandler RegistrationEventHandler appKeystore *keystore.AppKeystore
keystorePassword string
appKeystore *keystore.AppKeystore membershipIndexToLoad *uint
keystorePassword string
rootTracker *group_manager.MerkleRootTracker
} }
func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) error { func (gm *DynamicGroupManager) handler(events []*contracts.RLNMemberRegistered) error {
gm.lastBlockProcessedMutex.Lock()
defer gm.lastBlockProcessedMutex.Unlock()
toRemoveTable := om.New() toRemoveTable := om.New()
toInsertTable := om.New() toInsertTable := om.New()
@ -57,17 +56,17 @@ func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) e
for _, event := range events { for _, event := range events {
if event.Raw.Removed { if event.Raw.Removed {
var indexes []uint var indexes []uint
i_idx, ok := toRemoveTable.Get(event.Raw.BlockNumber) iIdx, ok := toRemoveTable.Get(event.Raw.BlockNumber)
if ok { if ok {
indexes = i_idx.([]uint) indexes = iIdx.([]uint)
} }
indexes = append(indexes, uint(event.Index.Uint64())) indexes = append(indexes, uint(event.Index.Uint64()))
toRemoveTable.Set(event.Raw.BlockNumber, indexes) toRemoveTable.Set(event.Raw.BlockNumber, indexes)
} else { } else {
var eventsPerBlock []*contracts.RLNMemberRegistered var eventsPerBlock []*contracts.RLNMemberRegistered
i_evt, ok := toInsertTable.Get(event.Raw.BlockNumber) iEvt, ok := toInsertTable.Get(event.Raw.BlockNumber)
if ok { if ok {
eventsPerBlock = i_evt.([]*contracts.RLNMemberRegistered) eventsPerBlock = iEvt.([]*contracts.RLNMemberRegistered)
} }
eventsPerBlock = append(eventsPerBlock, event) eventsPerBlock = append(eventsPerBlock, event)
toInsertTable.Set(event.Raw.BlockNumber, eventsPerBlock) toInsertTable.Set(event.Raw.BlockNumber, eventsPerBlock)
@ -88,8 +87,6 @@ func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) e
return err return err
} }
gm.metrics.RecordRegisteredMembership(toInsertTable.Len() - toRemoveTable.Len())
gm.lastBlockProcessed = lastBlockProcessed gm.lastBlockProcessed = lastBlockProcessed
err = gm.SetMetadata(RLNMetadata{ err = gm.SetMetadata(RLNMetadata{
LastProcessedBlock: gm.lastBlockProcessed, LastProcessedBlock: gm.lastBlockProcessed,
@ -101,7 +98,7 @@ func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) e
// this is not a fatal error, hence we don't raise an exception // this is not a fatal error, hence we don't raise an exception
gm.log.Warn("failed to persist rln metadata", zap.Error(err)) gm.log.Warn("failed to persist rln metadata", zap.Error(err))
} else { } else {
gm.log.Debug("rln metadata persisted", zap.Uint64("lastProcessedBlock", gm.lastBlockProcessed), zap.Uint64("chainID", gm.web3Config.ChainID.Uint64()), logging.HexBytes("contractAddress", gm.web3Config.RegistryContract.Address.Bytes())) gm.log.Debug("rln metadata persisted", zap.Uint64("lastBlockProcessed", gm.lastBlockProcessed), zap.Uint64("chainID", gm.web3Config.ChainID.Uint64()), logging.HexBytes("contractAddress", gm.web3Config.RegistryContract.Address.Bytes()))
} }
return nil return nil
@ -112,34 +109,43 @@ type RegistrationHandler = func(tx *types.Transaction)
func NewDynamicGroupManager( func NewDynamicGroupManager(
ethClientAddr string, ethClientAddr string,
memContractAddr common.Address, memContractAddr common.Address,
membershipIndex uint, membershipIndexToLoad *uint,
appKeystore *keystore.AppKeystore, appKeystore *keystore.AppKeystore,
keystorePassword string, keystorePassword string,
reg prometheus.Registerer, reg prometheus.Registerer,
rlnInstance *rln.RLN,
rootTracker *group_manager.MerkleRootTracker,
log *zap.Logger, log *zap.Logger,
) (*DynamicGroupManager, error) { ) (*DynamicGroupManager, error) {
log = log.Named("rln-dynamic") log = log.Named("rln-dynamic")
web3Config := web3.NewConfig(ethClientAddr, memContractAddr)
return &DynamicGroupManager{ return &DynamicGroupManager{
membershipIndex: membershipIndex, membershipIndexToLoad: membershipIndexToLoad,
web3Config: web3.NewConfig(ethClientAddr, memContractAddr), appKeystore: appKeystore,
eventHandler: handler, keystorePassword: keystorePassword,
appKeystore: appKeystore, MembershipFetcher: NewMembershipFetcher(web3Config, rlnInstance, rootTracker, log),
keystorePassword: keystorePassword, metrics: newMetrics(reg),
log: log,
metrics: newMetrics(reg),
}, nil }, nil
} }
func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) { func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) {
return gm.web3Config.RLNContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx}) fee, err := gm.web3Config.RLNContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx})
if err != nil {
return nil, fmt.Errorf("could not check if credential exits in contract: %w", err)
}
return fee, nil
} }
func (gm *DynamicGroupManager) memberExists(ctx context.Context, idCommitment rln.IDCommitment) (bool, error) { func (gm *DynamicGroupManager) memberExists(ctx context.Context, idCommitment rln.IDCommitment) (bool, error) {
return gm.web3Config.RLNContract.MemberExists(&bind.CallOpts{Context: ctx}, rln.Bytes32ToBigInt(idCommitment)) exists, err := gm.web3Config.RLNContract.MemberExists(&bind.CallOpts{Context: ctx}, rln.Bytes32ToBigInt(idCommitment))
if err != nil {
return false, fmt.Errorf("could not check if credential exits in contract: %w", err)
}
return exists, nil
} }
func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error { func (gm *DynamicGroupManager) Start(ctx context.Context) error {
if gm.cancel != nil { if gm.cancel != nil {
return errors.New("already started") return errors.New("already started")
} }
@ -154,9 +160,6 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
return err return err
} }
gm.rln = rlnInstance
gm.rootTracker = rootTracker
// check if the contract exists by calling a static function // check if the contract exists by calling a static function
_, err = gm.getMembershipFee(ctx) _, err = gm.getMembershipFee(ctx)
if err != nil { if err != nil {
@ -168,19 +171,26 @@ func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN,
return err return err
} }
if err = gm.HandleGroupUpdates(ctx, gm.eventHandler); err != nil { err = gm.MembershipFetcher.HandleGroupUpdates(ctx, gm.handler)
if err != nil {
return err return err
} }
gm.metrics.RecordRegisteredMembership(gm.rln.LeavesSet())
return nil return nil
} }
func (gm *DynamicGroupManager) loadCredential(ctx context.Context) error { func (gm *DynamicGroupManager) loadCredential(ctx context.Context) error {
if gm.appKeystore == nil {
gm.log.Warn("no credentials were loaded. Node will only validate messages, but wont be able to generate proofs and attach them to messages")
return nil
}
start := time.Now() start := time.Now()
credentials, err := gm.appKeystore.GetMembershipCredentials( credentials, err := gm.appKeystore.GetMembershipCredentials(
gm.keystorePassword, gm.keystorePassword,
gm.membershipIndex, gm.membershipIndexToLoad,
keystore.NewMembershipContractInfo(gm.web3Config.ChainID, gm.web3Config.RegistryContract.Address)) keystore.NewMembershipContractInfo(gm.web3Config.ChainID, gm.web3Config.RegistryContract.Address))
if err != nil { if err != nil {
return err return err
@ -201,6 +211,7 @@ func (gm *DynamicGroupManager) loadCredential(ctx context.Context) error {
} }
gm.identityCredential = credentials.IdentityCredential gm.identityCredential = credentials.IdentityCredential
gm.membershipIndex = credentials.TreeIndex
return nil return nil
} }
@ -231,6 +242,8 @@ func (gm *DynamicGroupManager) InsertMembers(toInsert *om.OrderedMap) error {
} }
gm.metrics.RecordMembershipInsertionDuration(time.Since(start)) gm.metrics.RecordMembershipInsertionDuration(time.Since(start))
gm.metrics.RecordRegisteredMembership(gm.rln.LeavesSet())
_, err = gm.rootTracker.UpdateLatestRoot(pair.Key.(uint64)) _, err = gm.rootTracker.UpdateLatestRoot(pair.Key.(uint64))
if err != nil { if err != nil {
return err return err
@ -278,9 +291,29 @@ func (gm *DynamicGroupManager) Stop() error {
return err return err
} }
gm.web3Config.ETHClient.Close() gm.MembershipFetcher.Stop()
gm.wg.Wait()
return nil return nil
} }
func (gm *DynamicGroupManager) IsReady(ctx context.Context) (bool, error) {
latestBlockNumber, err := gm.latestBlockNumber(ctx)
if err != nil {
return false, fmt.Errorf("could not retrieve latest block: %w", err)
}
gm.lastBlockProcessedMutex.RLock()
allBlocksProcessed := gm.lastBlockProcessed >= latestBlockNumber
gm.lastBlockProcessedMutex.RUnlock()
if !allBlocksProcessed {
return false, nil
}
syncProgress, err := gm.web3Config.ETHClient.SyncProgress(ctx)
if err != nil {
return false, fmt.Errorf("could not retrieve sync state: %w", err)
}
return syncProgress == nil, nil // syncProgress only has a value while node is syncing
}

View File

@ -0,0 +1,246 @@
package dynamic
import (
"bytes"
"context"
"errors"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/web3"
"github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
// RegistrationEventHandler represents the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface
type RegistrationEventHandler = func([]*contracts.RLNMemberRegistered) error
// MembershipFetcher is used for getting membershipRegsitered Events from the eth rpc
type MembershipFetcher struct {
web3Config *web3.Config
rln *rln.RLN
log *zap.Logger
rootTracker *group_manager.MerkleRootTracker
wg sync.WaitGroup
}
func NewMembershipFetcher(web3Config *web3.Config, rln *rln.RLN, rootTracker *group_manager.MerkleRootTracker, log *zap.Logger) MembershipFetcher {
return MembershipFetcher{
web3Config: web3Config,
rln: rln,
log: log,
rootTracker: rootTracker,
}
}
// HandleGroupUpdates mounts the supplied handler for the registration events emitting from the membership contract
// It connects to the eth client, subscribes to the `MemberRegistered` event emitted from the `MembershipContract`
// and collects all the events, for every received event, it calls the `handler`
func (mf *MembershipFetcher) HandleGroupUpdates(ctx context.Context, handler RegistrationEventHandler) error {
fromBlock := mf.web3Config.RLNContract.DeployedBlockNumber
metadata, err := mf.GetMetadata()
if err != nil {
mf.log.Warn("could not load last processed block from metadata. Starting onchain sync from deployment block", zap.Error(err), zap.Uint64("deploymentBlock", mf.web3Config.RLNContract.DeployedBlockNumber))
} else {
if mf.web3Config.ChainID.Cmp(metadata.ChainID) != 0 {
return errors.New("persisted data: chain id mismatch")
}
if !bytes.Equal(mf.web3Config.RegistryContract.Address.Bytes(), metadata.ContractAddress.Bytes()) {
return errors.New("persisted data: contract address mismatch")
}
fromBlock = metadata.LastProcessedBlock + 1
mf.log.Info("resuming onchain sync", zap.Uint64("fromBlock", fromBlock))
}
mf.rootTracker.SetValidRootsPerBlock(metadata.ValidRootsPerBlock)
//
latestBlockNumber, err := mf.latestBlockNumber(ctx)
if err != nil {
return err
}
//
mf.log.Info("loading old events...")
t := time.Now()
err = mf.loadOldEvents(ctx, fromBlock, latestBlockNumber, handler)
if err != nil {
return err
}
mf.log.Info("events loaded", zap.Duration("timeToLoad", time.Since(t)))
errCh := make(chan error)
mf.wg.Add(1)
go mf.watchNewEvents(ctx, latestBlockNumber+1, handler, errCh) // we have already fetched the events for latestBlocNumber in oldEvents
return <-errCh
}
func (mf *MembershipFetcher) loadOldEvents(ctx context.Context, fromBlock, toBlock uint64, handler RegistrationEventHandler) error {
for ; fromBlock+maxBatchSize < toBlock; fromBlock += maxBatchSize + 1 { // check if the end of the batch is within the toBlock range
t1 := time.Now()
events, err := mf.getEvents(ctx, fromBlock, fromBlock+maxBatchSize)
if err != nil {
return err
}
t1Since := time.Since(t1)
t2 := time.Now()
if err := handler(events); err != nil {
return err
}
mf.log.Info("fetching events", zap.Uint64("from", fromBlock), zap.Uint64("to", fromBlock+maxBatchSize), zap.Int("numEvents", len(events)), zap.Duration("timeToFetch", t1Since), zap.Duration("timeToProcess", time.Since(t2)))
}
t1 := time.Now()
events, err := mf.getEvents(ctx, fromBlock, toBlock)
if err != nil {
return err
}
t1Since := time.Since(t1)
// process all the fetched events
t2 := time.Now()
err = handler(events)
if err != nil {
return err
}
mf.log.Info("fetching events", zap.Uint64("from", fromBlock), zap.Uint64("to", fromBlock+maxBatchSize), zap.Int("numEvents", len(events)), zap.Duration("timeToFetch", t1Since), zap.Duration("timeToProcess", time.Since(t2)))
return nil
}
func (mf *MembershipFetcher) watchNewEvents(ctx context.Context, fromBlock uint64, handler RegistrationEventHandler, errCh chan<- error) {
defer mf.wg.Done()
// Watch for new events
firstErr := true
headerCh := make(chan *types.Header)
subs := event.Resubscribe(2*time.Second, func(ctx context.Context) (event.Subscription, error) {
s, err := mf.web3Config.ETHClient.SubscribeNewHead(ctx, headerCh)
if err != nil {
if err == rpc.ErrNotificationsUnsupported {
err = errors.New("notifications not supported. The node must support websockets")
}
mf.log.Error("subscribing to rln events", zap.Error(err))
}
if firstErr { // errCh can be closed only once
errCh <- err
close(errCh)
firstErr = false
}
return s, err
})
defer subs.Unsubscribe()
defer close(headerCh)
for {
select {
case h := <-headerCh:
toBlock := h.Number.Uint64()
events, err := mf.getEvents(ctx, fromBlock, toBlock)
if err != nil {
mf.log.Error("obtaining rln events", zap.Error(err))
} else {
// update the last processed block
fromBlock = toBlock + 1
}
err = handler(events)
if err != nil {
mf.log.Error("processing rln log", zap.Error(err))
}
case <-ctx.Done():
return
case err := <-subs.Err():
if err != nil {
mf.log.Error("watching new events", zap.Error(err))
}
return
}
}
}
const maxBatchSize = uint64(5000)
func tooMuchDataRequestedError(err error) bool {
// this error is only infura specific (other providers might have different error messages)
return err.Error() == "query returned more than 10000 results"
}
func (mf *MembershipFetcher) latestBlockNumber(ctx context.Context) (uint64, error) {
block, err := mf.web3Config.ETHClient.BlockByNumber(ctx, nil)
if err != nil {
return 0, err
}
return block.Number().Uint64(), nil
}
func (mf *MembershipFetcher) getEvents(ctx context.Context, fromBlock uint64, toBlock uint64) ([]*contracts.RLNMemberRegistered, error) {
evts, err := mf.fetchEvents(ctx, fromBlock, toBlock)
if err != nil {
if tooMuchDataRequestedError(err) { // divide the range and try again
mid := (fromBlock + toBlock) / 2
firstHalfEvents, err := mf.getEvents(ctx, fromBlock, mid)
if err != nil {
return nil, err
}
secondHalfEvents, err := mf.getEvents(ctx, mid+1, toBlock)
if err != nil {
return nil, err
}
return append(firstHalfEvents, secondHalfEvents...), nil
}
return nil, err
}
return evts, nil
}
func (mf *MembershipFetcher) fetchEvents(ctx context.Context, from uint64, to uint64) ([]*contracts.RLNMemberRegistered, error) {
logIterator, err := mf.web3Config.RLNContract.FilterMemberRegistered(&bind.FilterOpts{Start: from, End: &to, Context: ctx})
if err != nil {
return nil, err
}
var results []*contracts.RLNMemberRegistered
for {
if !logIterator.Next() {
break
}
if logIterator.Error() != nil {
return nil, logIterator.Error()
}
results = append(results, logIterator.Event)
}
return results, nil
}
// GetMetadata retrieves metadata from the zerokit's RLN database
func (mf *MembershipFetcher) GetMetadata() (RLNMetadata, error) {
b, err := mf.rln.GetMetadata()
if err != nil {
return RLNMetadata{}, err
}
return DeserializeMetadata(b)
}
func (mf *MembershipFetcher) Stop() {
mf.web3Config.ETHClient.Close()
// wait for the watchNewEvents goroutine to finish
mf.wg.Wait()
}

View File

@ -0,0 +1,100 @@
{
"blocks": {
"5": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:1",
"bigint:1"
]
}
],
"5005": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:2",
"bigint:2"
]
}
],
"5006": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:3",
"bigint:3"
]
}
],
"5007": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:4",
"bigint:4"
]
}
],
"10005": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:5",
"bigint:5"
]
}
],
"10010": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:6",
"bigint:6"
]
}
],
"10011": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:7",
"bigint:7"
]
}
],
"10012": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"MemberRegistered(uint256,uint256)"
],
"data": [
"bigint:8",
"bigint:8"
]
}
]
}
}

View File

@ -79,13 +79,3 @@ func (gm *DynamicGroupManager) SetMetadata(meta RLNMetadata) error {
b := meta.Serialize() b := meta.Serialize()
return gm.rln.SetMetadata(b) return gm.rln.SetMetadata(b)
} }
// GetMetadata retrieves metadata from the zerokit's RLN database
func (gm *DynamicGroupManager) GetMetadata() (RLNMetadata, error) {
b, err := gm.rln.GetMetadata()
if err != nil {
return RLNMetadata{}, err
}
return DeserializeMetadata(b)
}

View File

@ -7,8 +7,8 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
var numberRegisteredMemberships = prometheus.NewCounter( var numberRegisteredMemberships = prometheus.NewGauge(
prometheus.CounterOpts{ prometheus.GaugeOpts{
Name: "waku_rln_number_registered_memberships", Name: "waku_rln_number_registered_memberships",
Help: "number of registered and active rln memberships", Help: "number of registered and active rln memberships",
}) })
@ -33,7 +33,7 @@ var collectors = []prometheus.Collector{
// Metrics exposes the functions required to update prometheus metrics for lightpush protocol // Metrics exposes the functions required to update prometheus metrics for lightpush protocol
type Metrics interface { type Metrics interface {
RecordRegisteredMembership(num int) RecordRegisteredMembership(num uint)
RecordMembershipInsertionDuration(duration time.Duration) RecordMembershipInsertionDuration(duration time.Duration)
RecordMembershipCredentialsImportDuration(duration time.Duration) RecordMembershipCredentialsImportDuration(duration time.Duration)
} }
@ -60,10 +60,6 @@ func (m *metricsImpl) RecordMembershipCredentialsImportDuration(duration time.Du
} }
// RecordRegisteredMembership records the number of registered memberships // RecordRegisteredMembership records the number of registered memberships
func (m *metricsImpl) RecordRegisteredMembership(num int) { func (m *metricsImpl) RecordRegisteredMembership(num uint) {
if num < 0 { numberRegisteredMemberships.Set(float64(num))
return
}
numberRegisteredMemberships.Add(float64(num))
} }

View File

@ -0,0 +1,81 @@
package dynamic
import (
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
// MockBlockChain is currently a chain of events for different block numbers
// it is used internal by mock client for returning events for a given block number or range in FilterLog rpc call.
type MockBlockChain struct {
Blocks map[int64]*MockBlock `json:"blocks"`
}
type MockBlock []MockEvent
func containsEntry[T common.Hash | common.Address](topics []T, topicA T) bool {
for _, topic := range topics {
if topic == topicA {
return true
}
}
return false
}
func Topic(topic string) common.Hash {
return crypto.Keccak256Hash([]byte(topic))
}
func (b MockBlock) getLogs(blockNum uint64, addrs []common.Address, topicA []common.Hash) (txLogs []types.Log) {
for ind, event := range b {
txLog := event.GetLog()
if containsEntry(addrs, txLog.Address) && (len(topicA) == 0 || containsEntry(topicA, txLog.Topics[0])) {
txLog.BlockNumber = blockNum
txLog.Index = uint(ind)
txLogs = append(txLogs, txLog)
}
}
return
}
type MockEvent struct {
Address common.Address `json:"address"`
Topics []string `json:"topics"`
Txhash common.Hash `json:"txhash"`
Data []string `json:"data"`
}
func (e MockEvent) GetLog() types.Log {
topics := []common.Hash{Topic(e.Topics[0])}
for _, topic := range e.Topics[1:] {
topics = append(topics, parseData(topic))
}
//
var data []byte
for _, entry := range e.Data {
data = append(data, parseData(entry).Bytes()...)
}
return types.Log{
Address: e.Address,
Topics: topics,
TxHash: e.Txhash,
Data: data,
}
}
func parseData(data string) common.Hash {
splits := strings.Split(data, ":")
switch splits[0] {
case "bigint":
bigInt, ok := new(big.Int).SetString(splits[1], 10)
if !ok {
panic("invalid big int")
}
return common.BytesToHash(bigInt.Bytes())
default:
panic("invalid data type")
}
}

View File

@ -0,0 +1,118 @@
package dynamic
import (
"context"
"encoding/json"
"math/big"
"os"
"sort"
"sync/atomic"
"testing"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
type ErrCount struct {
err error
count int
}
type MockClient struct {
ethclient.Client
blockChain MockBlockChain
latestBlockNum atomic.Int64
errOnBlock map[int64]*ErrCount
}
func (c *MockClient) SetLatestBlockNumber(num int64) {
c.latestBlockNum.Store(num)
}
func (c *MockClient) Close() {
}
func (c *MockClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
return types.NewBlock(&types.Header{Number: big.NewInt(c.latestBlockNum.Load())}, nil, nil, nil, nil), nil
}
func NewMockClient(t *testing.T, blockFile string) *MockClient {
blockChain := MockBlockChain{}
data, err := os.ReadFile(blockFile)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(data, &blockChain); err != nil {
t.Fatal(err)
}
return &MockClient{blockChain: blockChain, errOnBlock: map[int64]*ErrCount{}}
}
func (c *MockClient) SetErrorOnBlock(blockNum int64, err error, count int) {
c.errOnBlock[blockNum] = &ErrCount{err: err, count: count}
}
func (c *MockClient) getFromAndToRange(query ethereum.FilterQuery) (int64, int64) {
var fromBlock int64
if query.FromBlock == nil {
fromBlock = 0
} else {
fromBlock = query.FromBlock.Int64()
}
var toBlock int64
if query.ToBlock == nil {
toBlock = 0
} else {
toBlock = query.ToBlock.Int64()
}
return fromBlock, toBlock
}
func (c *MockClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) (allTxLogs []types.Log, err error) {
fromBlock, toBlock := c.getFromAndToRange(query)
for block, details := range c.blockChain.Blocks {
if block >= fromBlock && block <= toBlock {
if txLogs := details.getLogs(uint64(block), query.Addresses, query.Topics[0]); len(txLogs) != 0 {
allTxLogs = append(allTxLogs, txLogs...)
}
if errCount, ok := c.errOnBlock[block]; ok && errCount.count != 0 {
errCount.count--
return nil, errCount.err
}
}
}
sort.Slice(allTxLogs, func(i, j int) bool {
return allTxLogs[i].BlockNumber < allTxLogs[j].BlockNumber ||
(allTxLogs[i].BlockNumber == allTxLogs[j].BlockNumber && allTxLogs[i].Index < allTxLogs[j].Index)
})
return allTxLogs, nil
}
func (c *MockClient) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
for {
next := c.latestBlockNum.Load() + 1
if c.blockChain.Blocks[next] != nil {
ch <- &types.Header{Number: big.NewInt(next)}
c.latestBlockNum.Store(next)
} else {
break
}
}
return testNoopSub{}, nil
}
type testNoopSub struct {
}
func (testNoopSub) Unsubscribe() {
}
// Err returns the subscription error channel. The error channel receives
// a value if there is an issue with the subscription (e.g. the network connection
// delivering the events has been closed). Only one value will ever be sent.
// The error channel is closed by Unsubscribe.
func (testNoopSub) Err() <-chan error {
ch := make(chan error)
return ch
}

View File

@ -1,209 +0,0 @@
package dynamic
import (
"bytes"
"context"
"errors"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
"go.uber.org/zap"
)
// the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface
type RegistrationEventHandler = func(*DynamicGroupManager, []*contracts.RLNMemberRegistered) error
// HandleGroupUpdates mounts the supplied handler for the registration events emitting from the membership contract
// It connects to the eth client, subscribes to the `MemberRegistered` event emitted from the `MembershipContract`
// and collects all the events, for every received event, it calls the `handler`
func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler RegistrationEventHandler) error {
fromBlock := gm.web3Config.RLNContract.DeployedBlockNumber
metadata, err := gm.GetMetadata()
if err != nil {
gm.log.Warn("could not load last processed block from metadata. Starting onchain sync from deployment block", zap.Error(err), zap.Uint64("deploymentBlock", gm.web3Config.RLNContract.DeployedBlockNumber))
} else {
if gm.web3Config.ChainID.Cmp(metadata.ChainID) != 0 {
return errors.New("persisted data: chain id mismatch")
}
if !bytes.Equal(gm.web3Config.RegistryContract.Address.Bytes(), metadata.ContractAddress.Bytes()) {
return errors.New("persisted data: contract address mismatch")
}
fromBlock = metadata.LastProcessedBlock
gm.log.Info("resuming onchain sync", zap.Uint64("fromBlock", fromBlock))
}
gm.rootTracker.SetValidRootsPerBlock(metadata.ValidRootsPerBlock)
err = gm.loadOldEvents(ctx, fromBlock, handler)
if err != nil {
return err
}
errCh := make(chan error)
gm.wg.Add(1)
go gm.watchNewEvents(ctx, handler, gm.log, errCh)
return <-errCh
}
func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, fromBlock uint64, handler RegistrationEventHandler) error {
events, err := gm.getEvents(ctx, fromBlock, nil)
if err != nil {
return err
}
return handler(gm, events)
}
func (gm *DynamicGroupManager) watchNewEvents(ctx context.Context, handler RegistrationEventHandler, log *zap.Logger, errCh chan<- error) {
defer gm.wg.Done()
// Watch for new events
firstErr := true
headerCh := make(chan *types.Header)
subs := event.Resubscribe(2*time.Second, func(ctx context.Context) (event.Subscription, error) {
s, err := gm.web3Config.ETHClient.SubscribeNewHead(ctx, headerCh)
if err != nil {
if err == rpc.ErrNotificationsUnsupported {
err = errors.New("notifications not supported. The node must support websockets")
}
if firstErr {
errCh <- err
}
gm.log.Error("subscribing to rln events", zap.Error(err))
}
firstErr = false
close(errCh)
return s, err
})
defer subs.Unsubscribe()
defer close(headerCh)
for {
select {
case h := <-headerCh:
blk := h.Number.Uint64()
events, err := gm.getEvents(ctx, blk, &blk)
if err != nil {
gm.log.Error("obtaining rln events", zap.Error(err))
}
err = handler(gm, events)
if err != nil {
gm.log.Error("processing rln log", zap.Error(err))
}
case <-ctx.Done():
return
case err := <-subs.Err():
if err != nil {
gm.log.Error("watching new events", zap.Error(err))
}
return
}
}
}
const maxBatchSize = uint64(5000)
const additiveFactorMultiplier = 0.10
const multiplicativeDecreaseDivisor = 2
func tooMuchDataRequestedError(err error) bool {
// this error is only infura specific (other providers might have different error messages)
return err.Error() == "query returned more than 10000 results"
}
func (gm *DynamicGroupManager) getEvents(ctx context.Context, from uint64, to *uint64) ([]*contracts.RLNMemberRegistered, error) {
var results []*contracts.RLNMemberRegistered
// Adapted from prysm logic for fetching historical logs
toBlock := to
if to == nil {
block, err := gm.web3Config.ETHClient.BlockByNumber(ctx, nil)
if err != nil {
return nil, err
}
blockNumber := block.Number().Uint64()
toBlock = &blockNumber
}
if from == *toBlock { // Only loading a single block
return gm.fetchEvents(ctx, from, toBlock)
}
// Fetching blocks in batches
batchSize := maxBatchSize
additiveFactor := uint64(float64(batchSize) * additiveFactorMultiplier)
currentBlockNum := from
for currentBlockNum < *toBlock {
start := currentBlockNum
end := currentBlockNum + batchSize
if end > *toBlock {
end = *toBlock
}
gm.log.Info("loading events...", zap.Uint64("fromBlock", start), zap.Uint64("toBlock", end))
evts, err := gm.fetchEvents(ctx, start, &end)
if err != nil {
if tooMuchDataRequestedError(err) {
if batchSize == 0 {
return nil, errors.New("batch size is zero")
}
// multiplicative decrease
batchSize = batchSize / multiplicativeDecreaseDivisor
gm.log.Warn("too many logs requested!, retrying with a smaller chunk size", zap.Uint64("batchSize", batchSize))
continue
}
return nil, err
}
results = append(results, evts...)
currentBlockNum = end
if batchSize < maxBatchSize {
// update the batchSize with additive increase
batchSize = batchSize + additiveFactor
if batchSize > maxBatchSize {
batchSize = maxBatchSize
}
}
}
return results, nil
}
func (gm *DynamicGroupManager) fetchEvents(ctx context.Context, from uint64, to *uint64) ([]*contracts.RLNMemberRegistered, error) {
logIterator, err := gm.web3Config.RLNContract.FilterMemberRegistered(&bind.FilterOpts{Start: from, End: to, Context: ctx})
if err != nil {
return nil, err
}
var results []*contracts.RLNMemberRegistered
for {
if !logIterator.Next() {
break
}
if logIterator.Error() != nil {
return nil, logIterator.Error()
}
results = append(results, logIterator.Event)
}
return results, nil
}

View File

@ -1 +1,22 @@
package group_manager package group_manager
import (
"context"
"github.com/waku-org/go-zerokit-rln/rln"
)
type GroupManager interface {
Start(ctx context.Context) error
IdentityCredentials() (rln.IdentityCredential, error)
MembershipIndex() rln.MembershipIndex
Stop() error
IsReady(ctx context.Context) (bool, error)
}
type Details struct {
GroupManager GroupManager
RootTracker *MerkleRootTracker
RLN *rln.RLN
}

View File

@ -25,6 +25,8 @@ func NewStaticGroupManager(
group []rln.IDCommitment, group []rln.IDCommitment,
identityCredential rln.IdentityCredential, identityCredential rln.IdentityCredential,
index rln.MembershipIndex, index rln.MembershipIndex,
rlnInstance *rln.RLN,
rootTracker *group_manager.MerkleRootTracker,
log *zap.Logger, log *zap.Logger,
) (*StaticGroupManager, error) { ) (*StaticGroupManager, error) {
// check the peer's index and the inclusion of user's identity commitment in the group // check the peer's index and the inclusion of user's identity commitment in the group
@ -37,15 +39,14 @@ func NewStaticGroupManager(
group: group, group: group,
identityCredential: &identityCredential, identityCredential: &identityCredential,
membershipIndex: index, membershipIndex: index,
rln: rlnInstance,
rootTracker: rootTracker,
}, nil }, nil
} }
func (gm *StaticGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error { func (gm *StaticGroupManager) Start(ctx context.Context) error {
gm.log.Info("mounting rln-relay in off-chain/static mode") gm.log.Info("mounting rln-relay in off-chain/static mode")
gm.rln = rlnInstance
gm.rootTracker = rootTracker
// add members to the Merkle tree // add members to the Merkle tree
err := gm.insertMembers(gm.group) err := gm.insertMembers(gm.group)
@ -94,3 +95,7 @@ func (gm *StaticGroupManager) Stop() error {
// Do nothing // Do nothing
return nil return nil
} }
func (gm *StaticGroupManager) IsReady(ctx context.Context) (bool, error) {
return true, nil
}

View File

@ -15,21 +15,10 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// DefaultCredentialsFilename is the suggested default filename for the rln credentials keystore
const DefaultCredentialsFilename = "./rlnKeystore.json"
// DefaultCredentialsPassword is the suggested default password for the rln credentials store
const DefaultCredentialsPassword = "password"
// New creates a new instance of a rln credentials keystore // New creates a new instance of a rln credentials keystore
func New(path string, appInfo AppInfo, logger *zap.Logger) (*AppKeystore, error) { func New(path string, appInfo AppInfo, logger *zap.Logger) (*AppKeystore, error) {
logger = logger.Named("rln-keystore") logger = logger.Named("rln-keystore")
if path == "" {
logger.Warn("keystore: no credentials path set, using default path", zap.String("path", DefaultCredentialsFilename))
path = DefaultCredentialsFilename
}
_, err := os.Stat(path) _, err := os.Stat(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -81,10 +70,36 @@ func getKey(treeIndex rln.MembershipIndex, filterMembershipContract MembershipCo
} }
// GetMembershipCredentials decrypts and retrieves membership credentials from the keystore applying filters // GetMembershipCredentials decrypts and retrieves membership credentials from the keystore applying filters
func (k *AppKeystore) GetMembershipCredentials(keystorePassword string, treeIndex rln.MembershipIndex, filterMembershipContract MembershipContractInfo) (*MembershipCredentials, error) { func (k *AppKeystore) GetMembershipCredentials(keystorePassword string, index *rln.MembershipIndex, filterMembershipContract MembershipContractInfo) (*MembershipCredentials, error) {
key, err := getKey(treeIndex, filterMembershipContract) // If there is only one, and index to laod nil, assume 0,
if err != nil { // if there is more than one, complain if the index to load is nil
return nil, err
var key Key
var err error
if len(k.Credentials) == 0 {
return nil, nil
}
if len(k.Credentials) == 1 {
// Only one credential, the tree index does not matter.
k.logger.Warn("automatically loading the only credential found on the keystore")
for k := range k.Credentials {
key = k // Obtain the first c
break
}
} else {
treeIndex := uint(0)
if index != nil {
treeIndex = *index
} else {
return nil, errors.New("the index of the onchain commitment to use was not specified")
}
key, err = getKey(treeIndex, filterMembershipContract)
if err != nil {
return nil, err
}
} }
credential, ok := k.Credentials[key] credential, ok := k.Credentials[key]
@ -108,7 +123,7 @@ func (k *AppKeystore) GetMembershipCredentials(keystorePassword string, treeInde
// AddMembershipCredentials inserts a membership credential to the keystore matching the application, appIdentifier and version filters. // AddMembershipCredentials inserts a membership credential to the keystore matching the application, appIdentifier and version filters.
func (k *AppKeystore) AddMembershipCredentials(newCredential MembershipCredentials, password string) error { func (k *AppKeystore) AddMembershipCredentials(newCredential MembershipCredentials, password string) error {
credentials, err := k.GetMembershipCredentials(password, newCredential.TreeIndex, newCredential.MembershipContractInfo) credentials, err := k.GetMembershipCredentials(password, &newCredential.TreeIndex, newCredential.MembershipContractInfo)
if err != nil { if err != nil {
return err return err
} }
@ -118,7 +133,7 @@ func (k *AppKeystore) AddMembershipCredentials(newCredential MembershipCredentia
return err return err
} }
if credentials != nil { if credentials != nil && credentials.TreeIndex == newCredential.TreeIndex && credentials.MembershipContractInfo.Equals(newCredential.MembershipContractInfo) {
return errors.New("credential already present") return errors.New("credential already present")
} }

View File

@ -0,0 +1,120 @@
package rln
import (
"bytes"
"context"
"errors"
"sync"
"time"
"github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
// NullifierLog is the log of nullifiers and Shamir shares of the past messages grouped per epoch
type NullifierLog struct {
sync.RWMutex
log *zap.Logger
nullifierLog map[rln.Nullifier][]rln.ProofMetadata // Might make sense to replace this map by a shrinkable map due to https://github.com/golang/go/issues/20135.
nullifierQueue []rln.Nullifier
}
// NewNullifierLog creates an instance of NullifierLog
func NewNullifierLog(ctx context.Context, log *zap.Logger) *NullifierLog {
result := &NullifierLog{
nullifierLog: make(map[rln.Nullifier][]rln.ProofMetadata),
log: log,
}
go result.cleanup(ctx)
return result
}
var errAlreadyExists = errors.New("proof already exists")
// Insert stores a proof in the nullifier log only if it doesnt exist already
func (n *NullifierLog) Insert(proofMD rln.ProofMetadata) error {
n.Lock()
defer n.Unlock()
proofs, ok := n.nullifierLog[proofMD.ExternalNullifier]
if ok {
// check if an identical record exists
for _, p := range proofs {
if p.Equals(proofMD) {
// TODO: slashing logic
return errAlreadyExists
}
}
}
n.nullifierLog[proofMD.ExternalNullifier] = append(proofs, proofMD)
n.nullifierQueue = append(n.nullifierQueue, proofMD.ExternalNullifier)
return nil
}
// HasDuplicate returns true if there is another message in the `nullifierLog` with the same
// epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares
// otherwise, returns false
func (n *NullifierLog) HasDuplicate(proofMD rln.ProofMetadata) (bool, error) {
n.RLock()
defer n.RUnlock()
proofs, ok := n.nullifierLog[proofMD.ExternalNullifier]
if !ok {
// epoch does not exist
return false, nil
}
for _, p := range proofs {
if p.Equals(proofMD) {
// there is an identical record, ignore the msg
return true, nil
}
}
// check for a message with the same nullifier but different secret shares
matched := false
for _, it := range proofs {
if bytes.Equal(it.Nullifier[:], proofMD.Nullifier[:]) && (!bytes.Equal(it.ShareX[:], proofMD.ShareX[:]) || !bytes.Equal(it.ShareY[:], proofMD.ShareY[:])) {
matched = true
break
}
}
return matched, nil
}
// cleanup cleans up the log every time there are more than MaxEpochGap epochs stored in it
func (n *NullifierLog) cleanup(ctx context.Context) {
t := time.NewTicker(1 * time.Minute) // TODO: tune this
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
func() {
n.Lock()
defer n.Unlock()
if int64(len(n.nullifierQueue)) < maxEpochGap {
return
}
n.log.Debug("clearing epochs from the nullifier log", zap.Int64("count", maxEpochGap))
toDelete := n.nullifierQueue[0:maxEpochGap]
for _, l := range toDelete {
delete(n.nullifierLog, l)
}
n.nullifierQueue = n.nullifierQueue[maxEpochGap:]
}()
}
}
}

View File

@ -1,17 +1,12 @@
package rln package rln
import ( import (
"bytes"
"context" "context"
"encoding/hex"
"errors" "errors"
"math" "math"
"sync"
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/waku-org/go-waku/logging" "github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/v2/protocol/pb" "github.com/waku-org/go-waku/waku/v2/protocol/pb"
@ -19,48 +14,26 @@ import (
"github.com/waku-org/go-waku/waku/v2/timesource" "github.com/waku-org/go-waku/waku/v2/timesource"
"github.com/waku-org/go-zerokit-rln/rln" "github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap" "go.uber.org/zap"
proto "google.golang.org/protobuf/proto"
) )
type GroupManager interface {
Start(ctx context.Context, rln *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error
IdentityCredentials() (rln.IdentityCredential, error)
MembershipIndex() rln.MembershipIndex
Stop() error
}
type WakuRLNRelay struct { type WakuRLNRelay struct {
timesource timesource.Timesource timesource timesource.Timesource
metrics Metrics metrics Metrics
groupManager GroupManager group_manager.Details
rootTracker *group_manager.MerkleRootTracker
RLN *rln.RLN nullifierLog *NullifierLog
// the log of nullifiers and Shamir shares of the past messages grouped per epoch
nullifierLogLock sync.RWMutex
nullifierLog map[rln.Nullifier][]rln.ProofMetadata
log *zap.Logger log *zap.Logger
} }
const rlnDefaultTreePath = "./rln_tree.db" const rlnDefaultTreePath = "./rln_tree.db"
func New( func GetRLNInstanceAndRootTracker(treePath string) (*rln.RLN, *group_manager.MerkleRootTracker, error) {
groupManager GroupManager,
treePath string,
timesource timesource.Timesource,
reg prometheus.Registerer,
log *zap.Logger) (*WakuRLNRelay, error) {
if treePath == "" { if treePath == "" {
treePath = rlnDefaultTreePath treePath = rlnDefaultTreePath
} }
metrics := newMetrics(reg)
start := time.Now()
rlnInstance, err := rln.NewWithConfig(rln.DefaultTreeDepth, &rln.TreeConfig{ rlnInstance, err := rln.NewWithConfig(rln.DefaultTreeDepth, &rln.TreeConfig{
CacheCapacity: 15000, CacheCapacity: 15000,
Mode: rln.HighThroughput, Mode: rln.HighThroughput,
@ -69,31 +42,36 @@ func New(
Path: treePath, Path: treePath,
}) })
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
metrics.RecordInstanceCreation(time.Since(start))
rootTracker, err := group_manager.NewMerkleRootTracker(acceptableRootWindowSize, rlnInstance) rootTracker, err := group_manager.NewMerkleRootTracker(acceptableRootWindowSize, rlnInstance)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return rlnInstance, rootTracker, nil
}
func New(
Details group_manager.Details,
timesource timesource.Timesource,
reg prometheus.Registerer,
log *zap.Logger) *WakuRLNRelay {
// create the WakuRLNRelay // create the WakuRLNRelay
rlnPeer := &WakuRLNRelay{ rlnPeer := &WakuRLNRelay{
RLN: rlnInstance, Details: Details,
groupManager: groupManager, metrics: newMetrics(reg),
rootTracker: rootTracker, log: log,
metrics: metrics, timesource: timesource,
log: log,
timesource: timesource,
nullifierLog: make(map[rln.MerkleNode][]rln.ProofMetadata),
} }
return rlnPeer, nil return rlnPeer
} }
func (rlnRelay *WakuRLNRelay) Start(ctx context.Context) error { func (rlnRelay *WakuRLNRelay) Start(ctx context.Context) error {
err := rlnRelay.groupManager.Start(ctx, rlnRelay.RLN, rlnRelay.rootTracker) rlnRelay.nullifierLog = NewNullifierLog(ctx, rlnRelay.log)
err := rlnRelay.GroupManager.Start(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -105,66 +83,7 @@ func (rlnRelay *WakuRLNRelay) Start(ctx context.Context) error {
// Stop will stop any operation or goroutine started while using WakuRLNRelay // Stop will stop any operation or goroutine started while using WakuRLNRelay
func (rlnRelay *WakuRLNRelay) Stop() error { func (rlnRelay *WakuRLNRelay) Stop() error {
return rlnRelay.groupManager.Stop() return rlnRelay.GroupManager.Stop()
}
func (rlnRelay *WakuRLNRelay) HasDuplicate(proofMD rln.ProofMetadata) (bool, error) {
// returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same
// epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares
// otherwise, returns false
rlnRelay.nullifierLogLock.RLock()
proofs, ok := rlnRelay.nullifierLog[proofMD.ExternalNullifier]
rlnRelay.nullifierLogLock.RUnlock()
// check if the epoch exists
if !ok {
return false, nil
}
for _, p := range proofs {
if p.Equals(proofMD) {
// there is an identical record, ignore rhe mag
return true, nil
}
}
// check for a message with the same nullifier but different secret shares
matched := false
for _, it := range proofs {
if bytes.Equal(it.Nullifier[:], proofMD.Nullifier[:]) && (!bytes.Equal(it.ShareX[:], proofMD.ShareX[:]) || !bytes.Equal(it.ShareY[:], proofMD.ShareY[:])) {
matched = true
break
}
}
return matched, nil
}
func (rlnRelay *WakuRLNRelay) updateLog(proofMD rln.ProofMetadata) (bool, error) {
rlnRelay.nullifierLogLock.Lock()
defer rlnRelay.nullifierLogLock.Unlock()
proofs, ok := rlnRelay.nullifierLog[proofMD.ExternalNullifier]
// check if the epoch exists
if !ok {
rlnRelay.nullifierLog[proofMD.ExternalNullifier] = []rln.ProofMetadata{proofMD}
return true, nil
}
// check if an identical record exists
for _, p := range proofs {
if p.Equals(proofMD) {
// TODO: slashing logic
return true, nil
}
}
// add proofMD to the log
proofs = append(proofs, proofMD)
rlnRelay.nullifierLog[proofMD.ExternalNullifier] = proofs
return true, nil
} }
// ValidateMessage validates the supplied message based on the waku-rln-relay routing protocol i.e., // ValidateMessage validates the supplied message based on the waku-rln-relay routing protocol i.e.,
@ -173,7 +92,6 @@ func (rlnRelay *WakuRLNRelay) updateLog(proofMD rln.ProofMetadata) (bool, error)
// the message's does not violate the rate limit // the message's does not violate the rate limit
// if `optionalTime` is supplied, then the current epoch is calculated based on that, otherwise the current time will be used // if `optionalTime` is supplied, then the current epoch is calculated based on that, otherwise the current time will be used
func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time.Time) (messageValidationResult, error) { func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime *time.Time) (messageValidationResult, error) {
//
if msg == nil { if msg == nil {
return validationError, errors.New("nil message") return validationError, errors.New("nil message")
} }
@ -214,7 +132,7 @@ func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime
return invalidMessage, nil return invalidMessage, nil
} }
if !(rlnRelay.rootTracker.ContainsRoot(msgProof.MerkleRoot)) { if !(rlnRelay.RootTracker.ContainsRoot(msgProof.MerkleRoot)) {
rlnRelay.log.Debug("invalid message: unexpected root", logging.HexBytes("msgRoot", msg.RateLimitProof.MerkleRoot)) rlnRelay.log.Debug("invalid message: unexpected root", logging.HexBytes("msgRoot", msg.RateLimitProof.MerkleRoot))
rlnRelay.metrics.RecordInvalidMessage(invalidRoot) rlnRelay.metrics.RecordInvalidMessage(invalidRoot)
return invalidMessage, nil return invalidMessage, nil
@ -237,7 +155,7 @@ func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime
} }
// check if double messaging has happened // check if double messaging has happened
hasDup, err := rlnRelay.HasDuplicate(proofMD) hasDup, err := rlnRelay.nullifierLog.HasDuplicate(proofMD)
if err != nil { if err != nil {
rlnRelay.log.Debug("validation error", zap.Error(err)) rlnRelay.log.Debug("validation error", zap.Error(err))
rlnRelay.metrics.RecordError(duplicateCheckErr) rlnRelay.metrics.RecordError(duplicateCheckErr)
@ -249,10 +167,7 @@ func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime
return spamMessage, nil return spamMessage, nil
} }
// insert the message to the log err = rlnRelay.nullifierLog.Insert(proofMD)
// the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e.,
// it will never error out
_, err = rlnRelay.updateLog(proofMD)
if err != nil { if err != nil {
rlnRelay.log.Debug("could not insert proof into log") rlnRelay.log.Debug("could not insert proof into log")
rlnRelay.metrics.RecordError(logInsertionErr) rlnRelay.metrics.RecordError(logInsertionErr)
@ -261,7 +176,7 @@ func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime
rlnRelay.log.Debug("message is valid") rlnRelay.log.Debug("message is valid")
rootIndex := rlnRelay.rootTracker.IndexOf(msgProof.MerkleRoot) rootIndex := rlnRelay.RootTracker.IndexOf(msgProof.MerkleRoot)
rlnRelay.metrics.RecordValidMessages(rootIndex) rlnRelay.metrics.RecordValidMessages(rootIndex)
return validMessage, nil return validMessage, nil
@ -270,7 +185,7 @@ func (rlnRelay *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime
func (rlnRelay *WakuRLNRelay) verifyProof(msg *pb.WakuMessage, proof *rln.RateLimitProof) (bool, error) { func (rlnRelay *WakuRLNRelay) verifyProof(msg *pb.WakuMessage, proof *rln.RateLimitProof) (bool, error) {
contentTopicBytes := []byte(msg.ContentTopic) contentTopicBytes := []byte(msg.ContentTopic)
input := append(msg.Payload, contentTopicBytes...) input := append(msg.Payload, contentTopicBytes...)
return rlnRelay.RLN.Verify(input, *proof, rlnRelay.rootTracker.Roots()...) return rlnRelay.RLN.Verify(input, *proof, rlnRelay.RootTracker.Roots()...)
} }
func (rlnRelay *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Time) error { func (rlnRelay *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Time) error {
@ -299,64 +214,61 @@ func (rlnRelay *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTim
// Validator returns a validator for the waku messages. // Validator returns a validator for the waku messages.
// The message validation logic is according to https://rfc.vac.dev/spec/17/ // The message validation logic is according to https://rfc.vac.dev/spec/17/
func (rlnRelay *WakuRLNRelay) Validator( func (rlnRelay *WakuRLNRelay) Validator(
spamHandler SpamHandler) func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool { spamHandler SpamHandler) func(ctx context.Context, msg *pb.WakuMessage, topic string) bool {
return func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool { return func(ctx context.Context, msg *pb.WakuMessage, topic string) bool {
rlnRelay.log.Debug("rln-relay topic validator called")
hash := msg.Hash(topic)
log := rlnRelay.log.With(
logging.HexBytes("hash", hash),
zap.String("pubsubTopic", topic),
zap.String("contentTopic", msg.ContentTopic),
)
log.Debug("rln-relay topic validator called")
rlnRelay.metrics.RecordMessage() rlnRelay.metrics.RecordMessage()
wakuMessage := &pb.WakuMessage{}
if err := proto.Unmarshal(message.Data, wakuMessage); err != nil {
rlnRelay.log.Debug("could not unmarshal message")
return true
}
// validate the message // validate the message
validationRes, err := rlnRelay.ValidateMessage(wakuMessage, nil) validationRes, err := rlnRelay.ValidateMessage(msg, nil)
if err != nil { if err != nil {
rlnRelay.log.Debug("validating message", zap.Error(err)) log.Debug("validating message", zap.Error(err))
return false return false
} }
switch validationRes { switch validationRes {
case validMessage: case validMessage:
rlnRelay.log.Debug("message verified", log.Debug("message verified")
zap.String("id", hex.EncodeToString([]byte(message.ID))),
)
return true return true
case invalidMessage: case invalidMessage:
rlnRelay.log.Debug("message could not be verified", log.Debug("message could not be verified")
zap.String("id", hex.EncodeToString([]byte(message.ID))),
)
return false return false
case spamMessage: case spamMessage:
rlnRelay.log.Debug("spam message found", log.Debug("spam message found")
zap.String("id", hex.EncodeToString([]byte(message.ID))),
)
rlnRelay.metrics.RecordSpam(wakuMessage.ContentTopic) rlnRelay.metrics.RecordSpam(msg.ContentTopic)
if spamHandler != nil { if spamHandler != nil {
if err := spamHandler(wakuMessage); err != nil { if err := spamHandler(msg, topic); err != nil {
rlnRelay.log.Error("executing spam handler", zap.Error(err)) log.Error("executing spam handler", zap.Error(err))
} }
} }
return false return false
default: default:
rlnRelay.log.Debug("unhandled validation result", zap.Int("validationResult", int(validationRes))) log.Debug("unhandled validation result", zap.Int("validationResult", int(validationRes)))
return false return false
} }
} }
} }
func (rlnRelay *WakuRLNRelay) generateProof(input []byte, epoch rln.Epoch) (*pb.RateLimitProof, error) { func (rlnRelay *WakuRLNRelay) generateProof(input []byte, epoch rln.Epoch) (*pb.RateLimitProof, error) {
identityCredentials, err := rlnRelay.groupManager.IdentityCredentials() identityCredentials, err := rlnRelay.GroupManager.IdentityCredentials()
if err != nil { if err != nil {
return nil, err return nil, err
} }
membershipIndex := rlnRelay.groupManager.MembershipIndex() membershipIndex := rlnRelay.GroupManager.MembershipIndex()
proof, err := rlnRelay.RLN.GenerateProof(input, identityCredentials, membershipIndex, epoch) proof, err := rlnRelay.RLN.GenerateProof(input, identityCredentials, membershipIndex, epoch)
if err != nil { if err != nil {
@ -375,9 +287,14 @@ func (rlnRelay *WakuRLNRelay) generateProof(input []byte, epoch rln.Epoch) (*pb.
} }
func (rlnRelay *WakuRLNRelay) IdentityCredential() (rln.IdentityCredential, error) { func (rlnRelay *WakuRLNRelay) IdentityCredential() (rln.IdentityCredential, error) {
return rlnRelay.groupManager.IdentityCredentials() return rlnRelay.GroupManager.IdentityCredentials()
} }
func (rlnRelay *WakuRLNRelay) MembershipIndex() uint { func (rlnRelay *WakuRLNRelay) MembershipIndex() uint {
return rlnRelay.groupManager.MembershipIndex() return rlnRelay.GroupManager.MembershipIndex()
}
// IsReady returns true if the RLN Relay protocol is ready to relay messages
func (rlnRelay *WakuRLNRelay) IsReady(ctx context.Context) (bool, error) {
return rlnRelay.GroupManager.IsReady(ctx)
} }

View File

@ -5,8 +5,10 @@ import (
"errors" "errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts" "github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
) )
@ -26,12 +28,22 @@ type RLNContract struct {
DeployedBlockNumber uint64 DeployedBlockNumber uint64
} }
// EthClient is an interface for the ethclient.Client, so that we can pass mock client for testing
type EthClient interface {
bind.ContractBackend
SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error)
Close()
}
// Config is a helper struct that contains attributes for interaction with RLN smart contracts // Config is a helper struct that contains attributes for interaction with RLN smart contracts
type Config struct { type Config struct {
configured bool configured bool
ETHClientAddress string ETHClientAddress string
ETHClient *ethclient.Client ETHClient EthClient
ChainID *big.Int ChainID *big.Int
RegistryContract RegistryContract RegistryContract RegistryContract
RLNContract RLNContract RLNContract RLNContract

View File

@ -194,6 +194,7 @@ func (rs RelayShards) BitVector() []byte {
return append(result, vec...) return append(result, vec...)
} }
// Generate a RelayShards from a byte slice
func FromBitVector(buf []byte) (RelayShards, error) { func FromBitVector(buf []byte) (RelayShards, error) {
if len(buf) != 130 { if len(buf) != 130 {
return RelayShards{}, errors.New("invalid data: expected 130 bytes") return RelayShards{}, errors.New("invalid data: expected 130 bytes")
@ -229,3 +230,13 @@ func GetShardFromContentTopic(topic ContentTopic, shardCount int) StaticSharding
return NewStaticShardingPubsubTopic(ClusterIndex, uint16(shard)) return NewStaticShardingPubsubTopic(ClusterIndex, uint16(shard))
} }
func GetPubSubTopicFromContentTopic(cTopicString string) (string, error) {
cTopic, err := StringToContentTopic(cTopicString)
if err != nil {
return "", fmt.Errorf("%s : %s", err.Error(), cTopicString)
}
pTopic := GetShardFromContentTopic(cTopic, GenerationZeroShardsCount)
return pTopic.String(), nil
}

View File

@ -31,7 +31,7 @@ type Result struct {
store Store store Store
query *pb.HistoryQuery query *pb.HistoryQuery
cursor *pb.Index cursor *pb.Index
peerId peer.ID peerID peer.ID
} }
func (r *Result) Cursor() *pb.Index { func (r *Result) Cursor() *pb.Index {
@ -43,7 +43,7 @@ func (r *Result) IsComplete() bool {
} }
func (r *Result) PeerID() peer.ID { func (r *Result) PeerID() peer.ID {
return r.peerId return r.peerID
} }
func (r *Result) Query() *pb.HistoryQuery { func (r *Result) Query() *pb.HistoryQuery {
@ -111,7 +111,7 @@ func WithAutomaticPeerSelection(fromThesePeers ...peer.ID) HistoryRequestOption
if params.s.pm == nil { if params.s.pm == nil {
p, err = utils.SelectPeer(params.s.h, StoreID_v20beta4, fromThesePeers, params.s.log) p, err = utils.SelectPeer(params.s.h, StoreID_v20beta4, fromThesePeers, params.s.log)
} else { } else {
p, err = params.s.pm.SelectPeer(StoreID_v20beta4, fromThesePeers, params.s.log) p, err = params.s.pm.SelectPeer(StoreID_v20beta4, "", fromThesePeers...)
} }
if err == nil { if err == nil {
params.selectedPeer = p params.selectedPeer = p
@ -148,7 +148,7 @@ func WithRequestID(requestID []byte) HistoryRequestOption {
// when creating a store request // when creating a store request
func WithAutomaticRequestID() HistoryRequestOption { func WithAutomaticRequestID() HistoryRequestOption {
return func(params *HistoryRequestParameters) { return func(params *HistoryRequestParameters) {
params.requestID = protocol.GenerateRequestId() params.requestID = protocol.GenerateRequestID()
} }
} }
@ -282,7 +282,7 @@ func (store *WakuStore) Query(ctx context.Context, query Query, opts ...HistoryR
} }
if len(params.requestID) == 0 { if len(params.requestID) == 0 {
return nil, ErrInvalidId return nil, ErrInvalidID
} }
if params.cursor != nil { if params.cursor != nil {
@ -321,7 +321,7 @@ func (store *WakuStore) Query(ctx context.Context, query Query, opts ...HistoryR
store: store, store: store,
Messages: response.Messages, Messages: response.Messages,
query: q, query: q,
peerId: params.selectedPeer, peerID: params.selectedPeer,
} }
if response.PagingInfo != nil { if response.PagingInfo != nil {
@ -379,7 +379,7 @@ func (store *WakuStore) Next(ctx context.Context, r *Result) (*Result, error) {
Messages: []*wpb.WakuMessage{}, Messages: []*wpb.WakuMessage{},
cursor: nil, cursor: nil,
query: r.query, query: r.query,
peerId: r.PeerID(), peerID: r.PeerID(),
}, nil }, nil
} }
@ -400,7 +400,7 @@ func (store *WakuStore) Next(ctx context.Context, r *Result) (*Result, error) {
}, },
} }
response, err := store.queryFrom(ctx, q, r.PeerID(), protocol.GenerateRequestId()) response, err := store.queryFrom(ctx, q, r.PeerID(), protocol.GenerateRequestID())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -414,7 +414,7 @@ func (store *WakuStore) Next(ctx context.Context, r *Result) (*Result, error) {
store: store, store: store,
Messages: response.Messages, Messages: response.Messages,
query: q, query: q,
peerId: r.PeerID(), peerID: r.PeerID(),
} }
if response.PagingInfo != nil { if response.PagingInfo != nil {

View File

@ -32,8 +32,8 @@ var (
// that could be used to retrieve message history // that could be used to retrieve message history
ErrNoPeersAvailable = errors.New("no suitable remote peers") ErrNoPeersAvailable = errors.New("no suitable remote peers")
// ErrInvalidId is returned when no RequestID is given // ErrInvalidID is returned when no RequestID is given
ErrInvalidId = errors.New("invalid request id") ErrInvalidID = errors.New("invalid request id")
// ErrFailedToResumeHistory is returned when the node attempted to retrieve historic // ErrFailedToResumeHistory is returned when the node attempted to retrieve historic
// messages to fill its own message history but for some reason it failed // messages to fill its own message history but for some reason it failed

View File

@ -243,7 +243,7 @@ func (store *WakuStore) queryLoop(ctx context.Context, query *pb.HistoryQuery, c
for _, peer := range candidateList { for _, peer := range candidateList {
func() { func() {
defer queryWg.Done() defer queryWg.Done()
result, err := store.queryFrom(ctx, query, peer, protocol.GenerateRequestId()) result, err := store.queryFrom(ctx, query, peer, protocol.GenerateRequestID())
if err == nil { if err == nil {
resultChan <- result resultChan <- result
return return
@ -298,7 +298,7 @@ func (store *WakuStore) Resume(ctx context.Context, pubsubTopic string, peerList
return 0, err return 0, err
} }
var offset int64 = int64(20 * time.Nanosecond) offset := int64(20 * time.Nanosecond)
currentTime := store.timesource.Now().UnixNano() + offset currentTime := store.timesource.Now().UnixNano() + offset
lastSeenTime = max(lastSeenTime-offset, 0) lastSeenTime = max(lastSeenTime-offset, 0)

View File

@ -1,13 +1,15 @@
package filter package subscription
import ( import (
"encoding/json" "encoding/json"
"errors"
"sync" "sync"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/protocol" "github.com/waku-org/go-waku/waku/v2/protocol"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/exp/maps"
) )
type SubscriptionDetails struct { type SubscriptionDetails struct {
@ -19,63 +21,60 @@ type SubscriptionDetails struct {
once sync.Once once sync.Once
PeerID peer.ID PeerID peer.ID
PubsubTopic string ContentFilter protocol.ContentFilter
ContentTopics map[string]struct{}
C chan *protocol.Envelope C chan *protocol.Envelope
} }
// Map of SubscriptionDetails.ID to subscriptions
type SubscriptionSet map[string]*SubscriptionDetails type SubscriptionSet map[string]*SubscriptionDetails
type PeerSubscription struct { type PeerSubscription struct {
peerID peer.ID PeerID peer.ID
subscriptionsPerTopic map[string]SubscriptionSet SubsPerPubsubTopic map[string]SubscriptionSet
} }
type SubscriptionsMap struct { type SubscriptionsMap struct {
sync.RWMutex sync.RWMutex
logger *zap.Logger logger *zap.Logger
items map[peer.ID]*PeerSubscription Items map[peer.ID]*PeerSubscription
} }
var ErrNotFound = errors.New("not found")
func NewSubscriptionMap(logger *zap.Logger) *SubscriptionsMap { func NewSubscriptionMap(logger *zap.Logger) *SubscriptionsMap {
return &SubscriptionsMap{ return &SubscriptionsMap{
logger: logger.Named("subscription-map"), logger: logger.Named("subscription-map"),
items: make(map[peer.ID]*PeerSubscription), Items: make(map[peer.ID]*PeerSubscription),
} }
} }
func (sub *SubscriptionsMap) NewSubscription(peerID peer.ID, topic string, contentTopics []string) *SubscriptionDetails { func (sub *SubscriptionsMap) NewSubscription(peerID peer.ID, cf protocol.ContentFilter) *SubscriptionDetails {
sub.Lock() sub.Lock()
defer sub.Unlock() defer sub.Unlock()
peerSubscription, ok := sub.items[peerID] peerSubscription, ok := sub.Items[peerID]
if !ok { if !ok {
peerSubscription = &PeerSubscription{ peerSubscription = &PeerSubscription{
peerID: peerID, PeerID: peerID,
subscriptionsPerTopic: make(map[string]SubscriptionSet), SubsPerPubsubTopic: make(map[string]SubscriptionSet),
} }
sub.items[peerID] = peerSubscription sub.Items[peerID] = peerSubscription
} }
_, ok = peerSubscription.subscriptionsPerTopic[topic] _, ok = peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic]
if !ok { if !ok {
peerSubscription.subscriptionsPerTopic[topic] = make(SubscriptionSet) peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic] = make(SubscriptionSet)
} }
details := &SubscriptionDetails{ details := &SubscriptionDetails{
ID: uuid.NewString(), ID: uuid.NewString(),
mapRef: sub, mapRef: sub,
PeerID: peerID, PeerID: peerID,
PubsubTopic: topic,
C: make(chan *protocol.Envelope, 1024), C: make(chan *protocol.Envelope, 1024),
ContentTopics: make(map[string]struct{}), ContentFilter: protocol.ContentFilter{PubsubTopic: cf.PubsubTopic, ContentTopics: maps.Clone(cf.ContentTopics)},
} }
for _, ct := range contentTopics { sub.Items[peerID].SubsPerPubsubTopic[cf.PubsubTopic][details.ID] = details
details.ContentTopics[ct] = struct{}{}
}
sub.items[peerID].subscriptionsPerTopic[topic][details.ID] = details
return details return details
} }
@ -84,31 +83,32 @@ func (sub *SubscriptionsMap) IsSubscribedTo(peerID peer.ID) bool {
sub.RLock() sub.RLock()
defer sub.RUnlock() defer sub.RUnlock()
_, ok := sub.items[peerID] _, ok := sub.Items[peerID]
return ok return ok
} }
func (sub *SubscriptionsMap) Has(peerID peer.ID, topic string, contentTopics ...string) bool { // Check if we have subscriptions for all (pubsubTopic, contentTopics[i]) pairs provided
func (sub *SubscriptionsMap) Has(peerID peer.ID, cf protocol.ContentFilter) bool {
sub.RLock() sub.RLock()
defer sub.RUnlock() defer sub.RUnlock()
// Check if peer exits // Check if peer exits
peerSubscription, ok := sub.items[peerID] peerSubscription, ok := sub.Items[peerID]
if !ok { if !ok {
return false return false
} }
//TODO: Handle pubsubTopic as null
// Check if pubsub topic exists // Check if pubsub topic exists
subscriptions, ok := peerSubscription.subscriptionsPerTopic[topic] subscriptions, ok := peerSubscription.SubsPerPubsubTopic[cf.PubsubTopic]
if !ok { if !ok {
return false return false
} }
// Check if the content topic exists within the list of subscriptions for this peer // Check if the content topic exists within the list of subscriptions for this peer
for _, ct := range contentTopics { for _, ct := range cf.ContentTopicsList() {
found := false found := false
for _, subscription := range subscriptions { for _, subscription := range subscriptions {
_, exists := subscription.ContentTopics[ct] _, exists := subscription.ContentFilter.ContentTopics[ct]
if exists { if exists {
found = true found = true
break break
@ -121,17 +121,16 @@ func (sub *SubscriptionsMap) Has(peerID peer.ID, topic string, contentTopics ...
return true return true
} }
func (sub *SubscriptionsMap) Delete(subscription *SubscriptionDetails) error { func (sub *SubscriptionsMap) Delete(subscription *SubscriptionDetails) error {
sub.Lock() sub.Lock()
defer sub.Unlock() defer sub.Unlock()
peerSubscription, ok := sub.items[subscription.PeerID] peerSubscription, ok := sub.Items[subscription.PeerID]
if !ok { if !ok {
return ErrNotFound return ErrNotFound
} }
delete(peerSubscription.subscriptionsPerTopic[subscription.PubsubTopic], subscription.ID) delete(peerSubscription.SubsPerPubsubTopic[subscription.ContentFilter.PubsubTopic], subscription.ID)
return nil return nil
} }
@ -141,7 +140,7 @@ func (s *SubscriptionDetails) Add(contentTopics ...string) {
defer s.Unlock() defer s.Unlock()
for _, ct := range contentTopics { for _, ct := range contentTopics {
s.ContentTopics[ct] = struct{}{} s.ContentFilter.ContentTopics[ct] = struct{}{}
} }
} }
@ -150,11 +149,11 @@ func (s *SubscriptionDetails) Remove(contentTopics ...string) {
defer s.Unlock() defer s.Unlock()
for _, ct := range contentTopics { for _, ct := range contentTopics {
delete(s.ContentTopics, ct) delete(s.ContentFilter.ContentTopics, ct)
} }
} }
func (s *SubscriptionDetails) closeC() { func (s *SubscriptionDetails) CloseC() {
s.once.Do(func() { s.once.Do(func() {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
@ -165,7 +164,7 @@ func (s *SubscriptionDetails) closeC() {
} }
func (s *SubscriptionDetails) Close() error { func (s *SubscriptionDetails) Close() error {
s.closeC() s.CloseC()
return s.mapRef.Delete(s) return s.mapRef.Delete(s)
} }
@ -178,28 +177,23 @@ func (s *SubscriptionDetails) Clone() *SubscriptionDetails {
mapRef: s.mapRef, mapRef: s.mapRef,
Closed: false, Closed: false,
PeerID: s.PeerID, PeerID: s.PeerID,
PubsubTopic: s.PubsubTopic, ContentFilter: protocol.ContentFilter{PubsubTopic: s.ContentFilter.PubsubTopic, ContentTopics: maps.Clone(s.ContentFilter.ContentTopics)},
ContentTopics: make(map[string]struct{}),
C: make(chan *protocol.Envelope), C: make(chan *protocol.Envelope),
} }
for k := range s.ContentTopics {
result.ContentTopics[k] = struct{}{}
}
return result return result
} }
func (sub *SubscriptionsMap) clear() { func (sub *SubscriptionsMap) clear() {
for _, peerSubscription := range sub.items { for _, peerSubscription := range sub.Items {
for _, subscriptionSet := range peerSubscription.subscriptionsPerTopic { for _, subscriptionSet := range peerSubscription.SubsPerPubsubTopic {
for _, subscription := range subscriptionSet { for _, subscription := range subscriptionSet {
subscription.closeC() subscription.CloseC()
} }
} }
} }
sub.items = make(map[peer.ID]*PeerSubscription) sub.Items = make(map[peer.ID]*PeerSubscription)
} }
func (sub *SubscriptionsMap) Clear() { func (sub *SubscriptionsMap) Clear() {
@ -212,7 +206,7 @@ func (sub *SubscriptionsMap) Notify(peerID peer.ID, envelope *protocol.Envelope)
sub.RLock() sub.RLock()
defer sub.RUnlock() defer sub.RUnlock()
subscriptions, ok := sub.items[peerID].subscriptionsPerTopic[envelope.PubsubTopic()] subscriptions, ok := sub.Items[peerID].SubsPerPubsubTopic[envelope.PubsubTopic()]
if ok { if ok {
iterateSubscriptionSet(sub.logger, subscriptions, envelope) iterateSubscriptionSet(sub.logger, subscriptions, envelope)
} }
@ -224,7 +218,7 @@ func iterateSubscriptionSet(logger *zap.Logger, subscriptions SubscriptionSet, e
subscription.RLock() subscription.RLock()
defer subscription.RUnlock() defer subscription.RUnlock()
_, ok := subscription.ContentTopics[envelope.Message().ContentTopic] _, ok := subscription.ContentFilter.ContentTopics[envelope.Message().ContentTopic]
if !ok { // only send the msg to subscriptions that have matching contentTopic if !ok { // only send the msg to subscriptions that have matching contentTopic
return return
} }
@ -249,10 +243,10 @@ func (s *SubscriptionDetails) MarshalJSON() ([]byte, error) {
result := resultType{ result := resultType{
PeerID: s.PeerID.Pretty(), PeerID: s.PeerID.Pretty(),
PubsubTopic: s.PubsubTopic, PubsubTopic: s.ContentFilter.PubsubTopic,
} }
for c := range s.ContentTopics { for c := range s.ContentFilter.ContentTopics {
result.ContentTopics = append(result.ContentTopics, c) result.ContentTopics = append(result.ContentTopics, c)
} }

View File

@ -6,6 +6,8 @@ import (
"github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/protocol"
) )
const GossipSubOptimalFullMeshSize = 6
// FulltextMatch is the default matching function used for checking if a peer // FulltextMatch is the default matching function used for checking if a peer
// supports a protocol or not // supports a protocol or not
func FulltextMatch(expectedProtocol string) func(string) bool { func FulltextMatch(expectedProtocol string) func(string) bool {

View File

@ -36,7 +36,7 @@ type DB struct {
cancel func() cancel func()
} }
func NewDB(ctx context.Context, db *sql.DB, logger *zap.Logger) *DB { func NewDB(db *sql.DB, logger *zap.Logger) *DB {
rdb := &DB{ rdb := &DB{
db: db, db: db,
logger: logger.Named("rendezvous/db"), logger: logger.Named("rendezvous/db"),

View File

@ -2,10 +2,8 @@ package rendezvous
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math" "math"
"sync"
"time" "time"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
@ -32,9 +30,8 @@ type Rendezvous struct {
peerConnector PeerConnector peerConnector PeerConnector
log *zap.Logger log *zap.Logger
wg sync.WaitGroup *peermanager.CommonDiscoveryService
cancel context.CancelFunc
} }
// PeerConnector will subscribe to a channel containing the information for all peers found by this discovery protocol // PeerConnector will subscribe to a channel containing the information for all peers found by this discovery protocol
@ -46,9 +43,10 @@ type PeerConnector interface {
func NewRendezvous(db *DB, peerConnector PeerConnector, log *zap.Logger) *Rendezvous { func NewRendezvous(db *DB, peerConnector PeerConnector, log *zap.Logger) *Rendezvous {
logger := log.Named("rendezvous") logger := log.Named("rendezvous")
return &Rendezvous{ return &Rendezvous{
db: db, db: db,
peerConnector: peerConnector, peerConnector: peerConnector,
log: logger, log: logger,
CommonDiscoveryService: peermanager.NewCommonDiscoveryService(),
} }
} }
@ -58,17 +56,17 @@ func (r *Rendezvous) SetHost(h host.Host) {
} }
func (r *Rendezvous) Start(ctx context.Context) error { func (r *Rendezvous) Start(ctx context.Context) error {
if r.cancel != nil { return r.CommonDiscoveryService.Start(ctx, r.start)
return errors.New("already started") }
func (r *Rendezvous) start() error {
if r.db != nil {
if err := r.db.Start(r.Context()); err != nil {
return err
}
} }
if r.peerConnector != nil {
ctx, cancel := context.WithCancel(ctx) r.peerConnector.Subscribe(r.Context(), r.GetListeningChan())
r.cancel = cancel
err := r.db.Start(ctx)
if err != nil {
cancel()
return err
} }
r.rendezvousSvc = rvs.NewRendezvousService(r.host, r.db) r.rendezvousSvc = rvs.NewRendezvousService(r.host, r.db)
@ -105,18 +103,15 @@ func (r *Rendezvous) DiscoverWithNamespace(ctx context.Context, namespace string
if len(addrInfo) != 0 { if len(addrInfo) != 0 {
rp.SetSuccess(cookie) rp.SetSuccess(cookie)
peerCh := make(chan peermanager.PeerData)
defer close(peerCh)
r.peerConnector.Subscribe(ctx, peerCh)
for _, p := range addrInfo { for _, p := range addrInfo {
peer := peermanager.PeerData{ peer := peermanager.PeerData{
Origin: peerstore.Rendezvous, Origin: peerstore.Rendezvous,
AddrInfo: p, AddrInfo: p,
PubSubTopics: []string{namespace},
} }
select { if !r.PushToChan(peer) {
case <-ctx.Done(): r.log.Error("could push to closed channel/context completed")
return return
case peerCh <- peer:
} }
} }
} else { } else {
@ -161,9 +156,9 @@ func (r *Rendezvous) RegisterRelayShards(ctx context.Context, rs protocol.RelayS
// RegisterWithNamespace registers the node in the rendezvous point by using an specific namespace (usually a pubsub topic) // RegisterWithNamespace registers the node in the rendezvous point by using an specific namespace (usually a pubsub topic)
func (r *Rendezvous) RegisterWithNamespace(ctx context.Context, namespace string, rendezvousPoints []*RendezvousPoint) { func (r *Rendezvous) RegisterWithNamespace(ctx context.Context, namespace string, rendezvousPoints []*RendezvousPoint) {
for _, m := range rendezvousPoints { for _, m := range rendezvousPoints {
r.wg.Add(1) r.WaitGroup().Add(1)
go func(m *RendezvousPoint) { go func(m *RendezvousPoint) {
r.wg.Done() r.WaitGroup().Done()
rendezvousClient := rvs.NewRendezvousClient(r.host, m.id) rendezvousClient := rvs.NewRendezvousClient(r.host, m.id)
retries := 0 retries := 0
@ -186,14 +181,10 @@ func (r *Rendezvous) RegisterWithNamespace(ctx context.Context, namespace string
} }
func (r *Rendezvous) Stop() { func (r *Rendezvous) Stop() {
if r.cancel == nil { r.CommonDiscoveryService.Stop(func() {
return r.host.RemoveStreamHandler(rvs.RendezvousProto)
} r.rendezvousSvc = nil
})
r.cancel()
r.wg.Wait()
r.host.RemoveStreamHandler(rvs.RendezvousProto)
r.rendezvousSvc = nil
} }
// ShardToNamespace translates a cluster and shard index into a rendezvous namespace // ShardToNamespace translates a cluster and shard index into a rendezvous namespace

View File

@ -113,6 +113,7 @@ func computeOffset(timeQuery ntpQuery, servers []string, allowedFailures int) (t
return offsets[mid], nil return offsets[mid], nil
} }
// NewNTPTimesource creates a timesource that uses NTP
func NewNTPTimesource(ntpServers []string, log *zap.Logger) *NTPTimeSource { func NewNTPTimesource(ntpServers []string, log *zap.Logger) *NTPTimeSource {
return &NTPTimeSource{ return &NTPTimeSource{
servers: ntpServers, servers: ntpServers,

View File

@ -16,7 +16,7 @@ func EcdsaPubKeyToSecp256k1PublicKey(pubKey *ecdsa.PublicKey) *crypto.Secp256k1P
return (*crypto.Secp256k1PublicKey)(btcec.NewPublicKey(xFieldVal, yFieldVal)) return (*crypto.Secp256k1PublicKey)(btcec.NewPublicKey(xFieldVal, yFieldVal))
} }
// EcdsaPubKeyToSecp256k1PublicKey converts an `ecdsa.PrivateKey` into a libp2p `crypto.Secp256k1PrivateKey“ // EcdsaPrivKeyToSecp256k1PrivKey converts an `ecdsa.PrivateKey` into a libp2p `crypto.Secp256k1PrivateKey“
func EcdsaPrivKeyToSecp256k1PrivKey(privKey *ecdsa.PrivateKey) *crypto.Secp256k1PrivateKey { func EcdsaPrivKeyToSecp256k1PrivKey(privKey *ecdsa.PrivateKey) *crypto.Secp256k1PrivateKey {
privK, _ := btcec.PrivKeyFromBytes(privKey.D.Bytes()) privK, _ := btcec.PrivKeyFromBytes(privKey.D.Bytes())
return (*crypto.Secp256k1PrivateKey)(privK) return (*crypto.Secp256k1PrivateKey)(privK)

View File

@ -8,7 +8,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
var log *zap.Logger = nil var log *zap.Logger
// Logger creates a zap.Logger with some reasonable defaults // Logger creates a zap.Logger with some reasonable defaults
func Logger() *zap.Logger { func Logger() *zap.Logger {

View File

@ -12,7 +12,6 @@ import (
"github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/p2p/protocol/ping" "github.com/libp2p/go-libp2p/p2p/protocol/ping"
"github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -20,6 +19,7 @@ import (
// some protocol // some protocol
var ErrNoPeersAvailable = errors.New("no suitable peers found") var ErrNoPeersAvailable = errors.New("no suitable peers found")
// GetPeerID is used to extract the peerID from a multiaddress
func GetPeerID(m multiaddr.Multiaddr) (peer.ID, error) { func GetPeerID(m multiaddr.Multiaddr) (peer.ID, error) {
peerIDStr, err := m.ValueForProtocol(multiaddr.P_P2P) peerIDStr, err := m.ValueForProtocol(multiaddr.P_P2P)
if err != nil { if err != nil {
@ -61,7 +61,6 @@ func SelectRandomPeer(peers peer.IDSlice, log *zap.Logger) (peer.ID, error) {
if len(peers) >= 1 { if len(peers) >= 1 {
peerID := peers[rand.Intn(len(peers))] peerID := peers[rand.Intn(len(peers))]
// TODO: proper heuristic here that compares peer scores and selects "best" one. For now a random peer for the given protocol is returned // TODO: proper heuristic here that compares peer scores and selects "best" one. For now a random peer for the given protocol is returned
log.Info("Got random peer from peerstore", logging.HostID("peer", peerID))
return peerID, nil // nolint: gosec return peerID, nil // nolint: gosec
} }
@ -72,7 +71,7 @@ func SelectRandomPeer(peers peer.IDSlice, log *zap.Logger) (peer.ID, error) {
// Note: Use this method only if WakuNode is not being initialized, otherwise use peermanager.SelectPeer. // Note: Use this method only if WakuNode is not being initialized, otherwise use peermanager.SelectPeer.
// If a list of specific peers is passed, the peer will be chosen from that list assuming // If a list of specific peers is passed, the peer will be chosen from that list assuming
// it supports the chosen protocol, otherwise it will chose a peer from the node peerstore // it supports the chosen protocol, otherwise it will chose a peer from the node peerstore
func SelectPeer(host host.Host, protocolId protocol.ID, specificPeers []peer.ID, log *zap.Logger) (peer.ID, error) { func SelectPeer(host host.Host, protocolID protocol.ID, specificPeers []peer.ID, log *zap.Logger) (peer.ID, error) {
// @TODO We need to be more strategic about which peers we dial. Right now we just set one on the service. // @TODO We need to be more strategic about which peers we dial. Right now we just set one on the service.
// Ideally depending on the query and our set of peers we take a subset of ideal peers. // Ideally depending on the query and our set of peers we take a subset of ideal peers.
// This will require us to check for various factors such as: // This will require us to check for various factors such as:
@ -80,7 +79,7 @@ func SelectPeer(host host.Host, protocolId protocol.ID, specificPeers []peer.ID,
// - latency? // - latency?
// - default store peer? // - default store peer?
peers, err := FilterPeersByProto(host, specificPeers, protocolId) peers, err := FilterPeersByProto(host, specificPeers, protocolID)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -96,7 +95,7 @@ type pingResult struct {
// SelectPeerWithLowestRTT will select a peer that supports a specific protocol with the lowest reply time // SelectPeerWithLowestRTT will select a peer that supports a specific protocol with the lowest reply time
// If a list of specific peers is passed, the peer will be chosen from that list assuming // If a list of specific peers is passed, the peer will be chosen from that list assuming
// it supports the chosen protocol, otherwise it will chose a peer from the node peerstore // it supports the chosen protocol, otherwise it will chose a peer from the node peerstore
func SelectPeerWithLowestRTT(ctx context.Context, host host.Host, protocolId protocol.ID, specificPeers []peer.ID, log *zap.Logger) (peer.ID, error) { func SelectPeerWithLowestRTT(ctx context.Context, host host.Host, protocolID protocol.ID, specificPeers []peer.ID, _ *zap.Logger) (peer.ID, error) {
var peers peer.IDSlice var peers peer.IDSlice
peerSet := specificPeers peerSet := specificPeers
@ -105,7 +104,7 @@ func SelectPeerWithLowestRTT(ctx context.Context, host host.Host, protocolId pro
} }
for _, peer := range peerSet { for _, peer := range peerSet {
protocols, err := host.Peerstore().SupportsProtocols(peer, protocolId) protocols, err := host.Peerstore().SupportsProtocols(peer, protocolID)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -19,7 +19,7 @@ type Timesource interface {
func GetUnixEpoch(timesource ...Timesource) int64 { func GetUnixEpoch(timesource ...Timesource) int64 {
if len(timesource) != 0 { if len(timesource) != 0 {
return GetUnixEpochFrom(timesource[0].Now()) return GetUnixEpochFrom(timesource[0].Now())
} else {
return GetUnixEpochFrom(time.Now())
} }
return GetUnixEpochFrom(time.Now())
} }

View File

@ -42,6 +42,8 @@ bool set_leaf(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffe
bool get_leaf(struct RLN *ctx, uintptr_t index, struct Buffer *output_buffer); bool get_leaf(struct RLN *ctx, uintptr_t index, struct Buffer *output_buffer);
uintptr_t leaves_set(struct RLN *ctx);
bool set_next_leaf(struct RLN *ctx, const struct Buffer *input_buffer); bool set_next_leaf(struct RLN *ctx, const struct Buffer *input_buffer);
bool set_leaves_from(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffer); bool set_leaves_from(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffer);

View File

@ -1,7 +1,12 @@
package rln package rln
import (
_ "github.com/waku-org/go-zerokit-rln-apple/libs/aarch64-apple-darwin"
_ "github.com/waku-org/go-zerokit-rln-apple/libs/x86_64-apple-darwin"
)
/* /*
#cgo LDFLAGS:-lrln -ldl -lm #cgo LDFLAGS: -lrln -ldl -lm
#cgo darwin,386,!ios LDFLAGS:-L${SRCDIR}/../libs/i686-apple-darwin #cgo darwin,386,!ios LDFLAGS:-L${SRCDIR}/../libs/i686-apple-darwin
#cgo darwin,arm64,!ios LDFLAGS:-L${SRCDIR}/../libs/aarch64-apple-darwin #cgo darwin,arm64,!ios LDFLAGS:-L${SRCDIR}/../libs/aarch64-apple-darwin
#cgo darwin,amd64,!ios LDFLAGS:-L${SRCDIR}/../libs/x86_64-apple-darwin #cgo darwin,amd64,!ios LDFLAGS:-L${SRCDIR}/../libs/x86_64-apple-darwin

View File

@ -244,3 +244,7 @@ func (r *RLN) GetLeaf(index uint) ([]byte, error) {
return C.GoBytes(unsafe.Pointer(out.ptr), C.int(out.len)), nil return C.GoBytes(unsafe.Pointer(out.ptr), C.int(out.len)), nil
} }
func (r *RLN) LeavesSet() uint {
return uint(C.leaves_set(r.ptr))
}

View File

@ -42,6 +42,8 @@ bool set_leaf(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffe
bool get_leaf(struct RLN *ctx, uintptr_t index, struct Buffer *output_buffer); bool get_leaf(struct RLN *ctx, uintptr_t index, struct Buffer *output_buffer);
uintptr_t leaves_set(struct RLN *ctx);
bool set_next_leaf(struct RLN *ctx, const struct Buffer *input_buffer); bool set_next_leaf(struct RLN *ctx, const struct Buffer *input_buffer);
bool set_leaves_from(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffer); bool set_leaves_from(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffer);

View File

@ -1,7 +1,17 @@
package rln package rln
import (
_ "github.com/waku-org/go-zerokit-rln-arm/libs/aarch64-linux-android"
_ "github.com/waku-org/go-zerokit-rln-arm/libs/aarch64-unknown-linux-gnu"
_ "github.com/waku-org/go-zerokit-rln-arm/libs/arm-linux-androideabi"
_ "github.com/waku-org/go-zerokit-rln-arm/libs/arm-unknown-linux-gnueabi"
_ "github.com/waku-org/go-zerokit-rln-arm/libs/arm-unknown-linux-gnueabihf"
_ "github.com/waku-org/go-zerokit-rln-arm/libs/armv7-linux-androideabi"
_ "github.com/waku-org/go-zerokit-rln-arm/libs/armv7a-linux-androideabi"
)
/* /*
#cgo LDFLAGS:-lrln -ldl -lm #cgo LDFLAGS: -lrln -ldl -lm
#cgo linux,arm LDFLAGS:-L${SRCDIR}/../libs/armv7-linux-androideabi #cgo linux,arm LDFLAGS:-L${SRCDIR}/../libs/armv7-linux-androideabi
#cgo linux,arm64 LDFLAGS:-L${SRCDIR}/../libs/aarch64-unknown-linux-gnu #cgo linux,arm64 LDFLAGS:-L${SRCDIR}/../libs/aarch64-unknown-linux-gnu

View File

@ -244,3 +244,7 @@ func (r *RLN) GetLeaf(index uint) ([]byte, error) {
return C.GoBytes(unsafe.Pointer(out.ptr), C.int(out.len)), nil return C.GoBytes(unsafe.Pointer(out.ptr), C.int(out.len)), nil
} }
func (r *RLN) LeavesSet() uint {
return uint(C.leaves_set(r.ptr))
}

View File

@ -42,6 +42,8 @@ bool set_leaf(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffe
bool get_leaf(struct RLN *ctx, uintptr_t index, struct Buffer *output_buffer); bool get_leaf(struct RLN *ctx, uintptr_t index, struct Buffer *output_buffer);
uintptr_t leaves_set(struct RLN *ctx);
bool set_next_leaf(struct RLN *ctx, const struct Buffer *input_buffer); bool set_next_leaf(struct RLN *ctx, const struct Buffer *input_buffer);
bool set_leaves_from(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffer); bool set_leaves_from(struct RLN *ctx, uintptr_t index, const struct Buffer *input_buffer);

View File

@ -1,7 +1,13 @@
package rln package rln
import (
_ "github.com/waku-org/go-zerokit-rln-x86_64/libs/x86_64-pc-windows-gnu"
_ "github.com/waku-org/go-zerokit-rln-x86_64/libs/x86_64-unknown-linux-gnu"
_ "github.com/waku-org/go-zerokit-rln-x86_64/libs/x86_64-unknown-linux-musl"
)
/* /*
#cgo LDFLAGS:-lrln -ldl -lm #cgo LDFLAGS: -lrln -ldl -lm
#cgo linux,amd64,musl,!android LDFLAGS:-L${SRCDIR}/../libs/x86_64-unknown-linux-musl #cgo linux,amd64,musl,!android LDFLAGS:-L${SRCDIR}/../libs/x86_64-unknown-linux-musl
#cgo linux,amd64,!musl,!android LDFLAGS:-L${SRCDIR}/../libs/x86_64-unknown-linux-gnu #cgo linux,amd64,!musl,!android LDFLAGS:-L${SRCDIR}/../libs/x86_64-unknown-linux-gnu
#cgo windows,amd64 LDFLAGS:-L${SRCDIR}/../libs/x86_64-pc-windows-gnu -lrln -lm -lws2_32 -luserenv #cgo windows,amd64 LDFLAGS:-L${SRCDIR}/../libs/x86_64-pc-windows-gnu -lrln -lm -lws2_32 -luserenv

View File

@ -244,3 +244,7 @@ func (r *RLN) GetLeaf(index uint) ([]byte, error) {
return C.GoBytes(unsafe.Pointer(out.ptr), C.int(out.len)), nil return C.GoBytes(unsafe.Pointer(out.ptr), C.int(out.len)), nil
} }
func (r *RLN) LeavesSet() uint {
return uint(C.leaves_set(r.ptr))
}

View File

@ -114,3 +114,7 @@ func (i RLNWrapper) GetMetadata() ([]byte, error) {
func (i RLNWrapper) Flush() bool { func (i RLNWrapper) Flush() bool {
return i.ffi.Flush() return i.ffi.Flush()
} }
func (i RLNWrapper) LeavesSet() uint {
return i.ffi.LeavesSet()
}

View File

@ -113,3 +113,7 @@ func (i RLNWrapper) GetMetadata() ([]byte, error) {
func (i RLNWrapper) Flush() bool { func (i RLNWrapper) Flush() bool {
return i.ffi.Flush() return i.ffi.Flush()
} }
func (i RLNWrapper) LeavesSet() uint {
return i.ffi.LeavesSet()
}

View File

@ -114,3 +114,7 @@ func (i RLNWrapper) GetMetadata() ([]byte, error) {
func (i RLNWrapper) Flush() bool { func (i RLNWrapper) Flush() bool {
return i.ffi.Flush() return i.ffi.Flush()
} }
func (i RLNWrapper) LeavesSet() uint {
return i.ffi.LeavesSet()
}

View File

@ -484,3 +484,8 @@ func (r *RLN) Flush() error {
} }
return nil return nil
} }
// LeavesSet indicates how many elements have been inserted in the merkle tree
func (r *RLN) LeavesSet() uint {
return r.w.LeavesSet()
}

94
vendor/golang.org/x/exp/maps/maps.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package maps defines various functions useful with maps of any type.
package maps
// Keys returns the keys of the map m.
// The keys will be in an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
// Values returns the values of the map m.
// The values will be in an indeterminate order.
func Values[M ~map[K]V, K comparable, V any](m M) []V {
r := make([]V, 0, len(m))
for _, v := range m {
r = append(r, v)
}
return r
}
// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
// EqualFunc is like Equal, but compares values using eq.
// Keys are still compared with ==.
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || !eq(v1, v2) {
return false
}
}
return true
}
// Clear removes all entries from m, leaving it empty.
func Clear[M ~map[K]V, K comparable, V any](m M) {
for k := range m {
delete(m, k)
}
}
// Clone returns a copy of m. This is a shallow clone:
// the new keys and values are set using ordinary assignment.
func Clone[M ~map[K]V, K comparable, V any](m M) M {
// Preserve nil in case it matters.
if m == nil {
return nil
}
r := make(M, len(m))
for k, v := range m {
r[k] = v
}
return r
}
// Copy copies all key/value pairs in src adding them to dst.
// When a key in src is already present in dst,
// the value in dst will be overwritten by the value associated
// with the key in src.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
for k, v := range src {
dst[k] = v
}
}
// DeleteFunc deletes any key/value pairs from m for which del returns true.
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
for k, v := range m {
if del(k, v) {
delete(m, k)
}
}
}

24
vendor/modules.txt vendored
View File

@ -1008,7 +1008,7 @@ github.com/waku-org/go-discover/discover/v5wire
github.com/waku-org/go-libp2p-rendezvous github.com/waku-org/go-libp2p-rendezvous
github.com/waku-org/go-libp2p-rendezvous/db github.com/waku-org/go-libp2p-rendezvous/db
github.com/waku-org/go-libp2p-rendezvous/pb github.com/waku-org/go-libp2p-rendezvous/pb
# github.com/waku-org/go-waku v0.7.1-0.20230907093131-092811658ea3 # github.com/waku-org/go-waku v0.8.1-0.20230930175749-dcc828749f67
## explicit; go 1.19 ## explicit; go 1.19
github.com/waku-org/go-waku/logging github.com/waku-org/go-waku/logging
github.com/waku-org/go-waku/waku/persistence github.com/waku-org/go-waku/waku/persistence
@ -1040,21 +1040,34 @@ github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore
github.com/waku-org/go-waku/waku/v2/protocol/rln/web3 github.com/waku-org/go-waku/waku/v2/protocol/rln/web3
github.com/waku-org/go-waku/waku/v2/protocol/store github.com/waku-org/go-waku/waku/v2/protocol/store
github.com/waku-org/go-waku/waku/v2/protocol/store/pb github.com/waku-org/go-waku/waku/v2/protocol/store/pb
github.com/waku-org/go-waku/waku/v2/protocol/subscription
github.com/waku-org/go-waku/waku/v2/rendezvous github.com/waku-org/go-waku/waku/v2/rendezvous
github.com/waku-org/go-waku/waku/v2/timesource github.com/waku-org/go-waku/waku/v2/timesource
github.com/waku-org/go-waku/waku/v2/utils github.com/waku-org/go-waku/waku/v2/utils
# github.com/waku-org/go-zerokit-rln v0.1.14-0.20230905214645-ca686a02e816 # github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd
## explicit; go 1.18 ## explicit; go 1.18
github.com/waku-org/go-zerokit-rln/rln github.com/waku-org/go-zerokit-rln/rln
github.com/waku-org/go-zerokit-rln/rln/link github.com/waku-org/go-zerokit-rln/rln/link
# github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230905213302-1d6d18a03e7c # github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b
## explicit; go 1.19 ## explicit; go 1.19
github.com/waku-org/go-zerokit-rln-apple/libs/aarch64-apple-darwin
github.com/waku-org/go-zerokit-rln-apple/libs/x86_64-apple-darwin
github.com/waku-org/go-zerokit-rln-apple/rln github.com/waku-org/go-zerokit-rln-apple/rln
# github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230905183322-05f4cda61468 # github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065
## explicit; go 1.19 ## explicit; go 1.19
github.com/waku-org/go-zerokit-rln-arm/libs/aarch64-linux-android
github.com/waku-org/go-zerokit-rln-arm/libs/aarch64-unknown-linux-gnu
github.com/waku-org/go-zerokit-rln-arm/libs/arm-linux-androideabi
github.com/waku-org/go-zerokit-rln-arm/libs/arm-unknown-linux-gnueabi
github.com/waku-org/go-zerokit-rln-arm/libs/arm-unknown-linux-gnueabihf
github.com/waku-org/go-zerokit-rln-arm/libs/armv7-linux-androideabi
github.com/waku-org/go-zerokit-rln-arm/libs/armv7a-linux-androideabi
github.com/waku-org/go-zerokit-rln-arm/rln github.com/waku-org/go-zerokit-rln-arm/rln
# github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230905182930-2b11e72ef866 # github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1
## explicit; go 1.19 ## explicit; go 1.19
github.com/waku-org/go-zerokit-rln-x86_64/libs/x86_64-pc-windows-gnu
github.com/waku-org/go-zerokit-rln-x86_64/libs/x86_64-unknown-linux-gnu
github.com/waku-org/go-zerokit-rln-x86_64/libs/x86_64-unknown-linux-musl
github.com/waku-org/go-zerokit-rln-x86_64/rln github.com/waku-org/go-zerokit-rln-x86_64/rln
# github.com/wealdtech/go-ens/v3 v3.5.0 # github.com/wealdtech/go-ens/v3 v3.5.0
## explicit; go 1.12 ## explicit; go 1.12
@ -1161,6 +1174,7 @@ golang.org/x/crypto/ssh/terminal
# golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 # golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
## explicit; go 1.20 ## explicit; go 1.20
golang.org/x/exp/constraints golang.org/x/exp/constraints
golang.org/x/exp/maps
golang.org/x/exp/slices golang.org/x/exp/slices
# golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb # golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
## explicit; go 1.12 ## explicit; go 1.12

View File

@ -312,14 +312,11 @@ func (api *PublicWakuAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Sub
} }
filter.PubsubTopic = crit.PubsubTopic filter.PubsubTopic = crit.PubsubTopic
filter.ContentTopics = common.NewTopicSet(crit.ContentTopics)
for _, bt := range crit.ContentTopics {
filter.Topics = append(filter.Topics, bt[:])
}
// listen for message that are encrypted with the given symmetric key // listen for message that are encrypted with the given symmetric key
if symKeyGiven { if symKeyGiven {
if len(filter.Topics) == 0 { if len(filter.ContentTopics) == 0 {
return nil, ErrNoTopics return nil, ErrNoTopics
} }
key, err := api.w.GetSymKey(crit.SymKeyID) key, err := api.w.GetSymKey(crit.SymKeyID)
@ -461,7 +458,6 @@ func (api *PublicWakuAPI) NewMessageFilter(req Criteria) (string, error) {
src *ecdsa.PublicKey src *ecdsa.PublicKey
keySym []byte keySym []byte
keyAsym *ecdsa.PrivateKey keyAsym *ecdsa.PrivateKey
topics [][]byte
symKeyGiven = len(req.SymKeyID) > 0 symKeyGiven = len(req.SymKeyID) > 0
asymKeyGiven = len(req.PrivateKeyID) > 0 asymKeyGiven = len(req.PrivateKeyID) > 0
@ -495,21 +491,13 @@ func (api *PublicWakuAPI) NewMessageFilter(req Criteria) (string, error) {
} }
} }
if len(req.ContentTopics) > 0 {
topics = make([][]byte, len(req.ContentTopics))
for i, topic := range req.ContentTopics {
topics[i] = make([]byte, common.TopicLength)
copy(topics[i], topic[:])
}
}
f := &common.Filter{ f := &common.Filter{
Src: src, Src: src,
KeySym: keySym, KeySym: keySym,
KeyAsym: keyAsym, KeyAsym: keyAsym,
PubsubTopic: req.PubsubTopic, PubsubTopic: req.PubsubTopic,
Topics: topics, ContentTopics: common.NewTopicSet(req.ContentTopics),
Messages: common.NewMemoryMessageStore(), Messages: common.NewMemoryMessageStore(),
} }
id, err := api.w.Subscribe(f) id, err := api.w.Subscribe(f)

View File

@ -19,11 +19,11 @@
package wakuv2 package wakuv2
import ( import (
"bytes"
"testing" "testing"
"time" "time"
"github.com/waku-org/go-waku/waku/v2/protocol/relay" "github.com/waku-org/go-waku/waku/v2/protocol/relay"
"golang.org/x/exp/maps"
"github.com/status-im/status-go/wakuv2/common" "github.com/status-im/status-go/wakuv2/common"
) )
@ -43,12 +43,12 @@ func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) {
lastUsed: make(map[string]time.Time), lastUsed: make(map[string]time.Time),
} }
t1 := [4]byte{0xde, 0xea, 0xbe, 0xef} t1 := common.TopicType([4]byte{0xde, 0xea, 0xbe, 0xef})
t2 := [4]byte{0xca, 0xfe, 0xde, 0xca} t2 := common.TopicType([4]byte{0xca, 0xfe, 0xde, 0xca})
crit := Criteria{ crit := Criteria{
SymKeyID: keyID, SymKeyID: keyID,
ContentTopics: []common.TopicType{common.TopicType(t1), common.TopicType(t2)}, ContentTopics: []common.TopicType{t1, t2},
} }
_, err = api.NewMessageFilter(crit) _, err = api.NewMessageFilter(crit)
@ -59,10 +59,9 @@ func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) {
found := false found := false
candidates := w.filters.GetWatchersByTopic(relay.DefaultWakuTopic, t1) candidates := w.filters.GetWatchersByTopic(relay.DefaultWakuTopic, t1)
for _, f := range candidates { for _, f := range candidates {
if len(f.Topics) == 2 { if maps.Equal(f.ContentTopics, common.NewTopicSet(crit.ContentTopics)) {
if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) { found = true
found = true break
}
} }
} }

View File

@ -24,6 +24,8 @@ import (
"sync" "sync"
"github.com/waku-org/go-waku/waku/v2/protocol/relay" "github.com/waku-org/go-waku/waku/v2/protocol/relay"
"go.uber.org/zap"
"golang.org/x/exp/maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -32,13 +34,13 @@ import (
// Filter represents a Waku message filter // Filter represents a Waku message filter
type Filter struct { type Filter struct {
Src *ecdsa.PublicKey // Sender of the message Src *ecdsa.PublicKey // Sender of the message
KeyAsym *ecdsa.PrivateKey // Private Key of recipient KeyAsym *ecdsa.PrivateKey // Private Key of recipient
KeySym []byte // Key associated with the Topic KeySym []byte // Key associated with the Topic
PubsubTopic string // Pubsub topic used to filter messages with PubsubTopic string // Pubsub topic used to filter messages with
Topics [][]byte // ContentTopics to filter messages with ContentTopics TopicSet // ContentTopics to filter messages with
SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization
id string // unique identifier id string // unique identifier
Messages MessageStore Messages MessageStore
} }
@ -49,20 +51,27 @@ type PubsubTopicToContentTopic = map[string]ContentTopicToFilter
// Filters represents a collection of filters // Filters represents a collection of filters
type Filters struct { type Filters struct {
// Map of random ID to Filter
watchers map[string]*Filter watchers map[string]*Filter
topicMatcher PubsubTopicToContentTopic // map a topic to the filters that are interested in being notified when a message matches that topic // map a topic to the filters that are interested in being notified when a message matches that topic
allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is topicMatcher PubsubTopicToContentTopic
mutex sync.RWMutex // list all the filters that will be notified of a new message, no matter what its topic is
allTopicsMatcher map[*Filter]struct{}
logger *zap.Logger
sync.RWMutex
} }
// NewFilters returns a newly created filter collection // NewFilters returns a newly created filter collection
func NewFilters() *Filters { func NewFilters(logger *zap.Logger) *Filters {
return &Filters{ return &Filters{
watchers: make(map[string]*Filter), watchers: make(map[string]*Filter),
topicMatcher: make(PubsubTopicToContentTopic), topicMatcher: make(PubsubTopicToContentTopic),
allTopicsMatcher: make(map[*Filter]struct{}), allTopicsMatcher: make(map[*Filter]struct{}),
logger: logger,
} }
} }
@ -77,8 +86,8 @@ func (fs *Filters) Install(watcher *Filter) (string, error) {
return "", err return "", err
} }
fs.mutex.Lock() fs.Lock()
defer fs.mutex.Unlock() defer fs.Unlock()
if fs.watchers[id] != nil { if fs.watchers[id] != nil {
return "", fmt.Errorf("failed to generate unique ID") return "", fmt.Errorf("failed to generate unique ID")
@ -89,19 +98,25 @@ func (fs *Filters) Install(watcher *Filter) (string, error) {
} }
watcher.id = id watcher.id = id
fs.watchers[id] = watcher fs.watchers[id] = watcher
fs.addTopicMatcher(watcher) fs.addTopicMatcher(watcher)
fs.logger.Debug("filters install", zap.String("id", id))
return id, err return id, err
} }
// Uninstall will remove a filter whose id has been specified from // Uninstall will remove a filter whose id has been specified from
// the filter collection // the filter collection
func (fs *Filters) Uninstall(id string) bool { func (fs *Filters) Uninstall(id string) bool {
fs.mutex.Lock() fs.Lock()
defer fs.mutex.Unlock() defer fs.Unlock()
if fs.watchers[id] != nil { watcher := fs.watchers[id]
fs.removeFromTopicMatchers(fs.watchers[id]) if watcher != nil {
fs.removeFromTopicMatchers(watcher)
delete(fs.watchers, id) delete(fs.watchers, id)
fs.logger.Debug("filters uninstall", zap.String("id", id))
return true return true
} }
return false return false
@ -109,8 +124,8 @@ func (fs *Filters) Uninstall(id string) bool {
func (fs *Filters) AllTopics() []TopicType { func (fs *Filters) AllTopics() []TopicType {
var topics []TopicType var topics []TopicType
fs.mutex.Lock() fs.Lock()
defer fs.mutex.Unlock() defer fs.Unlock()
for _, topicsPerPubsubTopic := range fs.topicMatcher { for _, topicsPerPubsubTopic := range fs.topicMatcher {
for t := range topicsPerPubsubTopic { for t := range topicsPerPubsubTopic {
topics = append(topics, t) topics = append(topics, t)
@ -124,7 +139,7 @@ func (fs *Filters) AllTopics() []TopicType {
// If the filter's Topics array is empty, it will be tried on every topic. // If the filter's Topics array is empty, it will be tried on every topic.
// Otherwise, it will be tried on the topics specified. // Otherwise, it will be tried on the topics specified.
func (fs *Filters) addTopicMatcher(watcher *Filter) { func (fs *Filters) addTopicMatcher(watcher *Filter) {
if len(watcher.Topics) == 0 && (watcher.PubsubTopic == relay.DefaultWakuTopic || watcher.PubsubTopic == "") { if len(watcher.ContentTopics) == 0 && (watcher.PubsubTopic == relay.DefaultWakuTopic || watcher.PubsubTopic == "") {
fs.allTopicsMatcher[watcher] = struct{}{} fs.allTopicsMatcher[watcher] = struct{}{}
} else { } else {
filtersPerContentTopic, ok := fs.topicMatcher[watcher.PubsubTopic] filtersPerContentTopic, ok := fs.topicMatcher[watcher.PubsubTopic]
@ -132,8 +147,7 @@ func (fs *Filters) addTopicMatcher(watcher *Filter) {
filtersPerContentTopic = make(ContentTopicToFilter) filtersPerContentTopic = make(ContentTopicToFilter)
} }
for _, t := range watcher.Topics { for topic := range watcher.ContentTopics {
topic := BytesToTopic(t)
if filtersPerContentTopic[topic] == nil { if filtersPerContentTopic[topic] == nil {
filtersPerContentTopic[topic] = make(FilterSet) filtersPerContentTopic[topic] = make(FilterSet)
} }
@ -153,8 +167,7 @@ func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
return return
} }
for _, t := range watcher.Topics { for topic := range watcher.ContentTopics {
topic := BytesToTopic(t)
delete(filtersPerContentTopic[topic], watcher) delete(filtersPerContentTopic[topic], watcher)
} }
@ -182,18 +195,24 @@ func (fs *Filters) GetWatchersByTopic(pubsubTopic string, contentTopic TopicType
// Get returns a filter from the collection with a specific ID // Get returns a filter from the collection with a specific ID
func (fs *Filters) Get(id string) *Filter { func (fs *Filters) Get(id string) *Filter {
fs.mutex.RLock() fs.RLock()
defer fs.mutex.RUnlock() defer fs.RUnlock()
return fs.watchers[id] return fs.watchers[id]
} }
func (fs *Filters) GetFilters() map[string]*Filter {
fs.RLock()
defer fs.RUnlock()
return maps.Clone(fs.watchers)
}
// NotifyWatchers notifies any filter that has declared interest // NotifyWatchers notifies any filter that has declared interest
// for the envelope's topic. // for the envelope's topic.
func (fs *Filters) NotifyWatchers(recvMessage *ReceivedMessage) bool { func (fs *Filters) NotifyWatchers(recvMessage *ReceivedMessage) bool {
var decodedMsg *ReceivedMessage var decodedMsg *ReceivedMessage
fs.mutex.RLock() fs.RLock()
defer fs.mutex.RUnlock() defer fs.RUnlock()
var matched bool var matched bool
candidates := fs.GetWatchersByTopic(recvMessage.PubsubTopic, recvMessage.ContentTopic) candidates := fs.GetWatchersByTopic(recvMessage.PubsubTopic, recvMessage.ContentTopic)

View File

@ -30,6 +30,24 @@ import (
// SHA3 hash of some arbitrary data given by the original author of the message. // SHA3 hash of some arbitrary data given by the original author of the message.
type TopicType [TopicLength]byte type TopicType [TopicLength]byte
type TopicSet map[TopicType]struct{}
func NewTopicSet(topics []TopicType) TopicSet {
s := make(TopicSet, len(topics))
for _, t := range topics {
s[t] = struct{}{}
}
return s
}
func NewTopicSetFromBytes(byteArrays [][]byte) TopicSet {
topics := make([]TopicType, len(byteArrays))
for i, byteArr := range byteArrays {
topics[i] = BytesToTopic(byteArr)
}
return NewTopicSet(topics)
}
// BytesToTopic converts from the byte array representation of a topic // BytesToTopic converts from the byte array representation of a topic
// into the TopicType type. // into the TopicType type.
func BytesToTopic(b []byte) (t TopicType) { func BytesToTopic(b []byte) (t TopicType) {

View File

@ -54,7 +54,7 @@ var DefaultConfig = Config{
KeepAliveInterval: 10, // second KeepAliveInterval: 10, // second
DiscoveryLimit: 20, DiscoveryLimit: 20,
MinPeersForRelay: 1, // TODO: determine correct value with Vac team MinPeersForRelay: 1, // TODO: determine correct value with Vac team
MinPeersForFilter: 1, // TODO: determine correct value with Vac team and via testing MinPeersForFilter: 2, // TODO: determine correct value with Vac team and via testing
AutoUpdate: false, AutoUpdate: false,
} }

364
wakuv2/filter_manager.go Normal file
View File

@ -0,0 +1,364 @@
package wakuv2
import (
"context"
"crypto/rand"
"errors"
"math/big"
"sync"
"time"
"github.com/google/uuid"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/status-im/status-go/wakuv2/common"
node "github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/protocol/subscription"
"go.uber.org/zap"
"golang.org/x/exp/maps"
)
const (
FilterEventAdded = iota
FilterEventRemoved
FilterEventPingResult
FilterEventSubscribeResult
FilterEventUnsubscribeResult
FilterEventGetStats
)
const pingTimeout = 10 * time.Second
type FilterSubs map[string]subscription.SubscriptionSet
type FilterEvent struct {
eventType int
filterID string
success bool
peerID peer.ID
tempID string
sub *subscription.SubscriptionDetails
ch chan FilterSubs
}
// Methods on FilterManager maintain filter peer health
//
// runFilterLoop is the main event loop
//
// Filter Install/Uninstall events are pushed onto eventChan
// Subscribe, UnsubscribeWithSubscription, IsSubscriptionAlive calls
// are invoked from goroutines and request results pushed onto eventChan
//
// filterSubs is the map of filter IDs to subscriptions
type FilterManager struct {
ctx context.Context
filterSubs FilterSubs
eventChan chan (FilterEvent)
isFilterSubAlive func(sub *subscription.SubscriptionDetails) error
getFilter func(string) *common.Filter
onNewEnvelopes func(env *protocol.Envelope) error
peers []peer.ID
logger *zap.Logger
settings settings
node *node.WakuNode
}
func newFilterManager(ctx context.Context, logger *zap.Logger, getFilterFn func(string) *common.Filter, settings settings, onNewEnvelopes func(env *protocol.Envelope) error, node *node.WakuNode) *FilterManager {
// This fn is being mocked in test
mgr := new(FilterManager)
mgr.ctx = ctx
mgr.logger = logger
mgr.getFilter = getFilterFn
mgr.onNewEnvelopes = onNewEnvelopes
mgr.filterSubs = make(FilterSubs)
mgr.eventChan = make(chan FilterEvent, 100)
mgr.peers = make([]peer.ID, 0)
mgr.settings = settings
mgr.node = node
mgr.isFilterSubAlive = func(sub *subscription.SubscriptionDetails) error {
ctx, cancel := context.WithTimeout(ctx, pingTimeout)
defer cancel()
return mgr.node.FilterLightnode().IsSubscriptionAlive(ctx, sub)
}
return mgr
}
func (mgr *FilterManager) runFilterLoop(wg *sync.WaitGroup) {
defer wg.Done()
// Use it to ping filter peer(s) periodically
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
// Populate filter peers initially
mgr.peers = mgr.findFilterPeers() // ordered list of peers to select from
for {
select {
case <-mgr.ctx.Done():
return
case <-ticker.C:
mgr.peers = mgr.findFilterPeers()
mgr.pingPeers()
case ev := <-mgr.eventChan:
mgr.processEvents(&ev)
}
}
}
func (mgr *FilterManager) processEvents(ev *FilterEvent) {
switch ev.eventType {
case FilterEventAdded:
mgr.filterSubs[ev.filterID] = make(subscription.SubscriptionSet)
mgr.resubscribe(ev.filterID)
case FilterEventRemoved:
for _, sub := range mgr.filterSubs[ev.filterID] {
if sub == nil {
// Skip temp subs
continue
}
go mgr.unsubscribeFromFilter(ev.filterID, sub)
}
delete(mgr.filterSubs, ev.filterID)
case FilterEventPingResult:
if ev.success {
break
}
// filterID field is only set when there are no subs to check for this filter,
// therefore no particular peers that could be unreachable.
if ev.filterID != "" {
// Trigger full resubscribe, filter has no peers
mgr.logger.Debug("filter has no subs", zap.String("filterId", ev.filterID))
mgr.resubscribe(ev.filterID)
break
}
// Remove peer from list
for i, p := range mgr.peers {
if ev.peerID == p {
mgr.peers = append(mgr.peers[:i], mgr.peers[i+1:]...)
break
}
}
// Delete subs for removed peer
for filterID, subs := range mgr.filterSubs {
for _, sub := range subs {
if sub == nil {
// Skip temp subs
continue
}
if sub.PeerID == ev.peerID {
mgr.logger.Debug("filter sub is inactive", zap.String("filterId", filterID), zap.String("subID", sub.ID))
delete(subs, sub.ID)
go mgr.unsubscribeFromFilter(filterID, sub)
}
}
mgr.resubscribe(filterID)
}
case FilterEventSubscribeResult:
subs, found := mgr.filterSubs[ev.filterID]
if ev.success {
if found {
subs[ev.sub.ID] = ev.sub
go mgr.runFilterSubscriptionLoop(ev.sub)
} else {
// We subscribed to a filter that is already uninstalled; invoke unsubscribe
go mgr.unsubscribeFromFilter(ev.filterID, ev.sub)
}
}
if found {
// Delete temp subscription record
delete(subs, ev.tempID)
}
case FilterEventUnsubscribeResult:
mgr.logger.Debug("filter event unsubscribe_result", zap.String("filterId", ev.filterID), zap.Stringer("peerID", ev.sub.PeerID))
case FilterEventGetStats:
stats := make(FilterSubs)
for id, subs := range mgr.filterSubs {
stats[id] = make(subscription.SubscriptionSet)
for subID, sub := range subs {
if sub == nil {
// Skip temp subs
continue
}
stats[id][subID] = sub
}
}
ev.ch <- stats
}
}
func (mgr *FilterManager) subscribeToFilter(filterID string, peer peer.ID, tempID string) {
f := mgr.getFilter(filterID)
if f == nil {
mgr.logger.Error("filter subscribeToFilter: No filter found", zap.String("id", filterID))
mgr.eventChan <- FilterEvent{eventType: FilterEventSubscribeResult, filterID: filterID, tempID: tempID, success: false}
return
}
contentFilter := mgr.buildContentFilter(f.PubsubTopic, f.ContentTopics)
mgr.logger.Debug("filter subscribe to filter node", zap.Stringer("peer", peer), zap.String("pubsubTopic", contentFilter.PubsubTopic), zap.Strings("contentTopics", contentFilter.ContentTopicsList()))
ctx, cancel := context.WithTimeout(mgr.ctx, requestTimeout)
defer cancel()
subDetails, err := mgr.node.FilterLightnode().Subscribe(ctx, contentFilter, filter.WithPeer(peer))
var sub *subscription.SubscriptionDetails
if err != nil {
mgr.logger.Warn("filter could not add wakuv2 filter for peer", zap.String("filterId", filterID), zap.Stringer("peer", peer), zap.Error(err))
} else {
mgr.logger.Debug("filter subscription success", zap.String("filterId", filterID), zap.Stringer("peer", peer), zap.String("pubsubTopic", contentFilter.PubsubTopic), zap.Strings("contentTopics", contentFilter.ContentTopicsList()))
sub = subDetails[0]
}
success := err == nil
mgr.eventChan <- FilterEvent{eventType: FilterEventSubscribeResult, filterID: filterID, tempID: tempID, sub: sub, success: success}
}
func (mgr *FilterManager) unsubscribeFromFilter(filterID string, sub *subscription.SubscriptionDetails) {
mgr.logger.Debug("filter unsubscribe from filter node", zap.String("filterId", filterID), zap.String("subId", sub.ID), zap.Stringer("peer", sub.PeerID))
// Unsubscribe on light node
ctx, cancel := context.WithTimeout(mgr.ctx, requestTimeout)
defer cancel()
_, err := mgr.node.FilterLightnode().UnsubscribeWithSubscription(ctx, sub)
if err != nil {
mgr.logger.Warn("could not unsubscribe wakuv2 filter for peer", zap.String("filterId", filterID), zap.String("subId", sub.ID), zap.Error(err))
}
success := err == nil
mgr.eventChan <- FilterEvent{eventType: FilterEventUnsubscribeResult, filterID: filterID, success: success, sub: sub}
}
// Check whether each of the installed filters
// has enough alive subscriptions to peers
func (mgr *FilterManager) pingPeers() {
mgr.logger.Debug("filter pingPeers")
distinctPeers := make(map[peer.ID]struct{})
for filterID, subs := range mgr.filterSubs {
if len(subs) == 0 {
// No subs found, trigger full resubscribe
mgr.logger.Debug("filter ping peer no subs", zap.String("filterId", filterID))
go func() {
mgr.eventChan <- FilterEvent{eventType: FilterEventPingResult, filterID: filterID, success: false}
}()
continue
}
for _, sub := range subs {
if sub == nil {
// Skip temp subs
continue
}
_, found := distinctPeers[sub.PeerID]
if found {
continue
}
distinctPeers[sub.PeerID] = struct{}{}
mgr.logger.Debug("filter ping peer", zap.Stringer("peerId", sub.PeerID))
go func(sub *subscription.SubscriptionDetails) {
err := mgr.isFilterSubAlive(sub)
alive := err == nil
if alive {
mgr.logger.Debug("filter aliveness check succeeded", zap.Stringer("peerId", sub.PeerID))
} else {
mgr.logger.Debug("filter aliveness check failed", zap.Stringer("peerId", sub.PeerID), zap.Error(err))
}
mgr.eventChan <- FilterEvent{eventType: FilterEventPingResult, peerID: sub.PeerID, success: alive}
}(sub)
}
}
}
func (mgr *FilterManager) buildContentFilter(pubsubTopic string, contentTopicSet common.TopicSet) protocol.ContentFilter {
contentTopics := make([]string, len(contentTopicSet))
for i, ct := range maps.Keys(contentTopicSet) {
contentTopics[i] = ct.ContentTopic()
}
return protocol.ContentFilter{
PubsubTopic: pubsubTopic,
ContentTopics: protocol.NewContentTopicSet(contentTopics...),
}
}
// Find suitable peer(s)
func (mgr *FilterManager) findFilterPeers() []peer.ID {
allPeers := mgr.node.Host().Peerstore().Peers()
peers := make([]peer.ID, 0)
for _, peer := range allPeers {
protocols, err := mgr.node.Host().Peerstore().SupportsProtocols(peer, filter.FilterSubscribeID_v20beta1, relay.WakuRelayID_v200)
if err != nil {
mgr.logger.Debug("SupportsProtocols error", zap.Error(err))
continue
}
if len(protocols) == 2 {
peers = append(peers, peer)
}
}
mgr.logger.Debug("Filtered peers", zap.Int("cnt", len(peers)))
return peers
}
func (mgr *FilterManager) findPeerCandidate() (peer.ID, error) {
if len(mgr.peers) == 0 {
return "", errors.New("filter could not select a suitable peer")
}
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(mgr.peers))))
return mgr.peers[n.Int64()], nil
}
func (mgr *FilterManager) resubscribe(filterID string) {
subs, found := mgr.filterSubs[filterID]
if !found {
mgr.logger.Error("resubscribe filter not found", zap.String("filterId", filterID))
return
}
mgr.logger.Debug("filter active subscriptions count:", zap.String("filterId", filterID), zap.Int("len", len(subs)))
for i := len(subs); i < mgr.settings.MinPeersForFilter; i++ {
mgr.logger.Debug("filter check not passed, try subscribing to peers", zap.String("filterId", filterID))
peer, err := mgr.findPeerCandidate()
if err == nil {
// Create sub placeholder in order to avoid potentially too many subs
tempID := uuid.NewString()
subs[tempID] = nil
go mgr.subscribeToFilter(filterID, peer, tempID)
} else {
mgr.logger.Error("filter resubscribe findPeer error", zap.Error(err))
}
}
}
func (mgr *FilterManager) runFilterSubscriptionLoop(sub *subscription.SubscriptionDetails) {
for {
select {
case <-mgr.ctx.Done():
return
case env, ok := <-sub.C:
if ok {
err := (mgr.onNewEnvelopes)(env)
if err != nil {
mgr.logger.Error("OnNewEnvelopes error", zap.Error(err))
}
} else {
mgr.logger.Debug("filter sub is closed", zap.String("id", sub.ID))
return
}
}
}
}

View File

@ -28,7 +28,6 @@ import (
"math" "math"
"net" "net"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -57,7 +56,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/dnsdisc" "github.com/waku-org/go-waku/waku/v2/dnsdisc"
wps "github.com/waku-org/go-waku/waku/v2/peerstore" wps "github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol" "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter" "github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange" "github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange"
"github.com/waku-org/go-waku/waku/v2/protocol/relay" "github.com/waku-org/go-waku/waku/v2/protocol/relay"
@ -102,11 +101,8 @@ type Waku struct {
dnsAddressCacheLock *sync.RWMutex // lock to handle access to the map dnsAddressCacheLock *sync.RWMutex // lock to handle access to the map
// Filter-related // Filter-related
filters *common.Filters // Message filters installed with Subscribe function filters *common.Filters // Message filters installed with Subscribe function
filterSubscriptions map[*common.Filter]map[string]*filter.SubscriptionDetails // wakuv2 filter subscription details filterManager *FilterManager
filterPeerDisconnectMap map[peer.ID]int64
isFilterSubAlive func(sub *filter.SubscriptionDetails) error
privateKeys map[string]*ecdsa.PrivateKey // Private key storage privateKeys map[string]*ecdsa.PrivateKey // Private key storage
symKeys map[string][]byte // Symmetric key storage symKeys map[string][]byte // Symmetric key storage
@ -196,7 +192,7 @@ func New(nodeKey string, fleet string, cfg *Config, logger *zap.Logger, appDB *s
} }
} }
logger.Debug("starting wakuv2 with config", zap.Any("config", cfg)) logger.Info("starting wakuv2 with config", zap.Any("config", cfg))
waku := &Waku{ waku := &Waku{
appDB: appDB, appDB: appDB,
@ -213,8 +209,6 @@ func New(nodeKey string, fleet string, cfg *Config, logger *zap.Logger, appDB *s
dnsAddressCache: make(map[string][]dnsdisc.DiscoveredNode), dnsAddressCache: make(map[string][]dnsdisc.DiscoveredNode),
dnsAddressCacheLock: &sync.RWMutex{}, dnsAddressCacheLock: &sync.RWMutex{},
storeMsgIDs: make(map[gethcommon.Hash]bool), storeMsgIDs: make(map[gethcommon.Hash]bool),
filterPeerDisconnectMap: make(map[peer.ID]int64),
filterSubscriptions: make(map[*common.Filter]map[string]*filter.SubscriptionDetails),
timesource: ts, timesource: ts,
storeMsgIDsMu: sync.RWMutex{}, storeMsgIDsMu: sync.RWMutex{},
logger: logger, logger: logger,
@ -223,11 +217,6 @@ func New(nodeKey string, fleet string, cfg *Config, logger *zap.Logger, appDB *s
onPeerStats: onPeerStats, onPeerStats: onPeerStats,
} }
// This fn is being mocked in test
waku.isFilterSubAlive = func(sub *filter.SubscriptionDetails) error {
return waku.node.FilterLightnode().IsSubscriptionAlive(waku.ctx, sub)
}
waku.settings = settings{ waku.settings = settings{
MaxMsgSize: cfg.MaxMessageSize, MaxMsgSize: cfg.MaxMessageSize,
LightClient: cfg.LightClient, LightClient: cfg.LightClient,
@ -239,7 +228,7 @@ func New(nodeKey string, fleet string, cfg *Config, logger *zap.Logger, appDB *s
EnableDiscV5: cfg.EnableDiscV5, EnableDiscV5: cfg.EnableDiscV5,
} }
waku.filters = common.NewFilters() waku.filters = common.NewFilters(waku.logger)
waku.bandwidthCounter = metrics.NewBandwidthCounter() waku.bandwidthCounter = metrics.NewBandwidthCounter()
var privateKey *ecdsa.PrivateKey var privateKey *ecdsa.PrivateKey
@ -389,7 +378,7 @@ func (w *Waku) dnsDiscover(ctx context.Context, enrtreeAddress string, apply fnA
nameserver := w.settings.Nameserver nameserver := w.settings.Nameserver
w.settingsMu.RUnlock() w.settingsMu.RUnlock()
var opts []dnsdisc.DnsDiscoveryOption var opts []dnsdisc.DNSDiscoveryOption
if nameserver != "" { if nameserver != "" {
opts = append(opts, dnsdisc.WithNameserver(nameserver)) opts = append(opts, dnsdisc.WithNameserver(nameserver))
} }
@ -479,21 +468,21 @@ func (w *Waku) identifyAndConnect(ctx context.Context, isLightClient bool, ma mu
if isLightClient { if isLightClient {
err = w.node.Host().Network().ClosePeer(peerInfo.ID) err = w.node.Host().Network().ClosePeer(peerInfo.ID)
if err != nil { if err != nil {
w.logger.Error("could not close connections to peer", zap.Any("peer", peerInfo.ID), zap.Error(err)) w.logger.Error("could not close connections to peer", zap.Stringer("peer", peerInfo.ID), zap.Error(err))
} }
return return
} }
supportedProtocols, err := w.node.Host().Peerstore().SupportsProtocols(peerInfo.ID, relay.WakuRelayID_v200) supportedProtocols, err := w.node.Host().Peerstore().SupportsProtocols(peerInfo.ID, relay.WakuRelayID_v200)
if err != nil { if err != nil {
w.logger.Error("could not obtain protocols", zap.Any("peer", peerInfo.ID), zap.Error(err)) w.logger.Error("could not obtain protocols", zap.Stringer("peer", peerInfo.ID), zap.Error(err))
return return
} }
if len(supportedProtocols) == 0 { if len(supportedProtocols) == 0 {
err = w.node.Host().Network().ClosePeer(peerInfo.ID) err = w.node.Host().Network().ClosePeer(peerInfo.ID)
if err != nil { if err != nil {
w.logger.Error("could not close connections to peer", zap.Any("peer", peerInfo.ID), zap.Error(err)) w.logger.Error("could not close connections to peer", zap.Stringer("peer", peerInfo.ID), zap.Error(err))
} }
} }
} }
@ -553,7 +542,7 @@ func (w *Waku) runPeerExchangeLoop() {
case <-w.ctx.Done(): case <-w.ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
w.logger.Debug("Running peer exchange loop") w.logger.Info("Running peer exchange loop")
connectedPeers := w.node.Host().Network().Peers() connectedPeers := w.node.Host().Network().Peers()
peersWithRelay := 0 peersWithRelay := 0
@ -644,13 +633,10 @@ func (w *Waku) subscribeToPubsubTopicWithWakuRelay(topic string, pubkey *ecdsa.P
sub.Unsubscribe() sub.Unsubscribe()
return return
case env := <-sub.Ch: case env := <-sub.Ch:
envelopeErrors, err := w.OnNewEnvelopes(env, common.RelayedMessageType) err := w.OnNewEnvelopes(env, common.RelayedMessageType)
if err != nil { if err != nil {
w.logger.Error("onNewEnvelope error", zap.Error(err)) w.logger.Error("OnNewEnvelopes error", zap.Error(err))
} }
// TODO: should these be handled?
_ = envelopeErrors
_ = err
} }
} }
}() }()
@ -658,97 +644,6 @@ func (w *Waku) subscribeToPubsubTopicWithWakuRelay(topic string, pubkey *ecdsa.P
return nil return nil
} }
func (w *Waku) runFilterSubscriptionLoop(sub *filter.SubscriptionDetails) {
for {
select {
case <-w.ctx.Done():
return
case env, ok := <-sub.C:
if ok {
envelopeErrors, err := w.OnNewEnvelopes(env, common.RelayedMessageType)
// TODO: should these be handled?
_ = envelopeErrors
_ = err
} else {
return
}
}
}
}
func (w *Waku) runFilterMsgLoop() {
defer w.wg.Done()
if !w.settings.LightClient {
return
}
// Use it to ping filter peer(s) periodically
ticker := time.NewTicker(time.Duration(w.cfg.KeepAliveInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-w.ctx.Done():
return
case <-ticker.C:
for f, subMap := range w.filterSubscriptions {
if len(subMap) == 0 {
// All peers have disconnected on previous iteration,
// attempt full reconnect
err := w.subscribeToFilter(f)
if err != nil {
w.logger.Error("Failed to subscribe to filter")
}
break
}
for id, sub := range subMap {
err := w.isFilterSubAlive(sub)
if err != nil {
// Unsubscribe on light node
contentFilter := w.buildContentFilter(f.PubsubTopic, f.Topics)
// TODO Better return value handling for WakuFilterPushResult
_, err := w.node.FilterLightnode().Unsubscribe(w.ctx, contentFilter, filter.Peer(sub.PeerID))
if err != nil {
w.logger.Warn("could not unsubscribe wakuv2 filter for peer", zap.Any("peer", sub.PeerID))
continue
}
// Remove entry from maps
w.filterPeerDisconnectMap[sub.PeerID] = time.Now().Unix()
delete(subMap, id)
// Re-subscribe
peers := w.findFilterPeers()
if len(peers) > 0 && len(subMap) < w.settings.MinPeersForFilter {
subDetails, err := w.node.FilterLightnode().Subscribe(w.ctx, contentFilter, filter.WithPeer(peers[0]))
if err != nil {
w.logger.Warn("could not add wakuv2 filter for peer", zap.Any("peer", peers[0]))
break
}
subMap[subDetails.ID] = subDetails
go w.runFilterSubscriptionLoop(subDetails)
break
}
}
}
}
}
}
}
func (w *Waku) buildContentFilter(pubsubTopic string, topics [][]byte) filter.ContentFilter {
contentFilter := filter.ContentFilter{
Topic: pubsubTopic,
}
for _, topic := range topics {
contentFilter.ContentTopics = append(contentFilter.ContentTopics, common.BytesToTopic(topic).ContentTopic())
}
return contentFilter
}
// MaxMessageSize returns the maximum accepted message size. // MaxMessageSize returns the maximum accepted message size.
func (w *Waku) MaxMessageSize() uint32 { func (w *Waku) MaxMessageSize() uint32 {
w.settingsMu.RLock() w.settingsMu.RLock()
@ -1038,43 +933,41 @@ func (w *Waku) Subscribe(f *common.Filter) (string, error) {
f.PubsubTopic = relay.DefaultWakuTopic f.PubsubTopic = relay.DefaultWakuTopic
} }
s, err := w.filters.Install(f) id, err := w.filters.Install(f)
if err != nil { if err != nil {
return s, err return id, err
} }
if w.settings.LightClient { if w.settings.LightClient {
go func() { w.filterManager.eventChan <- FilterEvent{eventType: FilterEventAdded, filterID: id}
ticker := time.NewTicker(1 * time.Second)
for range ticker.C {
err := w.subscribeToFilter(f)
if err == nil {
break
}
}
}()
} }
return s, nil return id, nil
} }
// Unsubscribe removes an installed message handler. // Unsubscribe removes an installed message handler.
func (w *Waku) Unsubscribe(ctx context.Context, id string) error { func (w *Waku) Unsubscribe(ctx context.Context, id string) error {
f := w.filters.Get(id)
if f != nil && w.settings.LightClient {
contentFilter := w.buildContentFilter(f.PubsubTopic, f.Topics)
if _, err := w.node.FilterLightnode().Unsubscribe(ctx, contentFilter); err != nil {
return fmt.Errorf("failed to unsubscribe: %w", err)
}
}
ok := w.filters.Uninstall(id) ok := w.filters.Uninstall(id)
if !ok { if !ok {
return fmt.Errorf("failed to unsubscribe: invalid ID '%s'", id) return fmt.Errorf("failed to unsubscribe: invalid ID '%s'", id)
} }
if w.settings.LightClient {
w.filterManager.eventChan <- FilterEvent{eventType: FilterEventRemoved, filterID: id}
}
return nil return nil
} }
// Used for testing
func (w *Waku) getFilterStats() FilterSubs {
ch := make(chan FilterSubs)
w.filterManager.eventChan <- FilterEvent{eventType: FilterEventGetStats, ch: ch}
stats := <-ch
return stats
}
// GetFilter returns the filter by id. // GetFilter returns the filter by id.
func (w *Waku) GetFilter(id string) *common.Filter { func (w *Waku) GetFilter(id string) *common.Filter {
return w.filters.Get(id) return w.filters.Get(id)
@ -1083,7 +976,7 @@ func (w *Waku) GetFilter(id string) *common.Filter {
// Unsubscribe removes an installed message handler. // Unsubscribe removes an installed message handler.
func (w *Waku) UnsubscribeMany(ids []string) error { func (w *Waku) UnsubscribeMany(ids []string) error {
for _, id := range ids { for _, id := range ids {
w.logger.Debug("cleaning up filter", zap.String("id", id)) w.logger.Info("cleaning up filter", zap.String("id", id))
ok := w.filters.Uninstall(id) ok := w.filters.Uninstall(id)
if !ok { if !ok {
w.logger.Warn("could not remove filter with id", zap.String("id", id)) w.logger.Warn("could not remove filter with id", zap.String("id", id))
@ -1099,7 +992,7 @@ func (w *Waku) broadcast() {
var err error var err error
if w.settings.LightClient { if w.settings.LightClient {
w.logger.Info("publishing message via lightpush", zap.String("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", envelope.PubsubTopic())) w.logger.Info("publishing message via lightpush", zap.String("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", envelope.PubsubTopic()))
_, err = w.node.Lightpush().PublishToTopic(context.Background(), envelope.Message(), envelope.PubsubTopic()) _, err = w.node.Lightpush().PublishToTopic(context.Background(), envelope.Message(), lightpush.WithPubSubTopic(envelope.PubsubTopic()))
} else { } else {
w.logger.Info("publishing message via relay", zap.String("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", envelope.PubsubTopic())) w.logger.Info("publishing message via relay", zap.String("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", envelope.PubsubTopic()))
_, err = w.node.Relay().PublishToTopic(context.Background(), envelope.Message(), envelope.PubsubTopic()) _, err = w.node.Relay().PublishToTopic(context.Background(), envelope.Message(), envelope.PubsubTopic())
@ -1184,7 +1077,7 @@ func (w *Waku) query(ctx context.Context, peerID peer.ID, pubsubTopic string, to
} }
func (w *Waku) Query(ctx context.Context, peerID peer.ID, pubsubTopic string, topics []common.TopicType, from uint64, to uint64, opts []store.HistoryRequestOption) (cursor *storepb.Index, err error) { func (w *Waku) Query(ctx context.Context, peerID peer.ID, pubsubTopic string, topics []common.TopicType, from uint64, to uint64, opts []store.HistoryRequestOption) (cursor *storepb.Index, err error) {
requestID := protocol.GenerateRequestId() requestID := protocol.GenerateRequestID()
opts = append(opts, store.WithRequestID(requestID)) opts = append(opts, store.WithRequestID(requestID))
result, err := w.query(ctx, peerID, pubsubTopic, topics, from, to, opts) result, err := w.query(ctx, peerID, pubsubTopic, topics, from, to, opts)
if err != nil { if err != nil {
@ -1202,7 +1095,7 @@ func (w *Waku) Query(ctx context.Context, peerID peer.ID, pubsubTopic string, to
envelope := protocol.NewEnvelope(msg, msg.Timestamp, pubsubTopic) envelope := protocol.NewEnvelope(msg, msg.Timestamp, pubsubTopic)
w.logger.Info("received waku2 store message", zap.Any("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", pubsubTopic)) w.logger.Info("received waku2 store message", zap.Any("envelopeHash", hexutil.Encode(envelope.Hash())), zap.String("pubsubTopic", pubsubTopic))
_, err = w.OnNewEnvelopes(envelope, common.StoreMessageType) err = w.OnNewEnvelopes(envelope, common.StoreMessageType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1254,7 +1147,7 @@ func (w *Waku) Start() error {
} }
} }
w.wg.Add(3) w.wg.Add(2)
go func() { go func() {
defer w.wg.Done() defer w.wg.Done()
@ -1267,6 +1160,7 @@ func (w *Waku) Start() error {
case c := <-w.connStatusChan: case c := <-w.connStatusChan:
w.connStatusMu.Lock() w.connStatusMu.Lock()
latestConnStatus := formatConnStatus(w.node, c) latestConnStatus := formatConnStatus(w.node, c)
w.logger.Debug("PeerStats", zap.Any("stats", latestConnStatus))
for k, subs := range w.connStatusSubscriptions { for k, subs := range w.connStatusSubscriptions {
if subs.Active() { if subs.Active() {
subs.C <- latestConnStatus subs.C <- latestConnStatus
@ -1282,13 +1176,13 @@ func (w *Waku) Start() error {
if w.cfg.EnableDiscV5 { if w.cfg.EnableDiscV5 {
// Restarting DiscV5 // Restarting DiscV5
if !latestConnStatus.IsOnline && isConnected { if !latestConnStatus.IsOnline && isConnected {
w.logger.Debug("Restarting DiscV5: offline and is connected") w.logger.Info("Restarting DiscV5: offline and is connected")
isConnected = false isConnected = false
w.node.DiscV5().Stop() w.node.DiscV5().Stop()
} else if latestConnStatus.IsOnline && !isConnected { } else if latestConnStatus.IsOnline && !isConnected {
w.logger.Debug("Restarting DiscV5: online and is not connected") w.logger.Info("Restarting DiscV5: online and is not connected")
isConnected = true isConnected = true
if !w.node.DiscV5().IsStarted() { if w.node.DiscV5().ErrOnNotRunning() != nil {
err := w.node.DiscV5().Start(ctx) err := w.node.DiscV5().Start(ctx)
if err != nil { if err != nil {
w.logger.Error("Could not start DiscV5", zap.Error(err)) w.logger.Error("Could not start DiscV5", zap.Error(err))
@ -1302,7 +1196,19 @@ func (w *Waku) Start() error {
go w.telemetryBandwidthStats(w.cfg.TelemetryServerURL) go w.telemetryBandwidthStats(w.cfg.TelemetryServerURL)
go w.runPeerExchangeLoop() go w.runPeerExchangeLoop()
go w.runFilterMsgLoop()
if w.settings.LightClient {
// Create FilterManager that will main peer connectivity
// for installed filters
w.filterManager = newFilterManager(w.ctx, w.logger,
func(id string) *common.Filter { return w.GetFilter(id) },
w.settings,
func(env *protocol.Envelope) error { return w.OnNewEnvelopes(env, common.RelayedMessageType) },
w.node)
w.wg.Add(1)
go w.filterManager.runFilterLoop(&w.wg)
}
err = w.setupRelaySubscriptions() err = w.setupRelaySubscriptions()
if err != nil { if err != nil {
@ -1373,18 +1279,16 @@ func (w *Waku) Stop() error {
return nil return nil
} }
func (w *Waku) OnNewEnvelopes(envelope *protocol.Envelope, msgType common.MessageType) ([]common.EnvelopeError, error) { func (w *Waku) OnNewEnvelopes(envelope *protocol.Envelope, msgType common.MessageType) error {
if envelope == nil { if envelope == nil {
return nil, nil return nil
} }
recvMessage := common.NewReceivedMessage(envelope, msgType) recvMessage := common.NewReceivedMessage(envelope, msgType)
if recvMessage == nil { if recvMessage == nil {
return nil, nil return nil
} }
envelopeErrors := make([]common.EnvelopeError, 0)
logger := w.logger.With(zap.String("hash", recvMessage.Hash().Hex())) logger := w.logger.With(zap.String("hash", recvMessage.Hash().Hex()))
logger.Debug("received new envelope") logger.Debug("received new envelope")
@ -1399,10 +1303,10 @@ func (w *Waku) OnNewEnvelopes(envelope *protocol.Envelope, msgType common.Messag
common.EnvelopesValidatedCounter.Inc() common.EnvelopesValidatedCounter.Inc()
if trouble { if trouble {
return envelopeErrors, errors.New("received invalid envelope") return errors.New("received invalid envelope")
} }
return envelopeErrors, nil return nil
} }
// addEnvelope adds an envelope to the envelope map, used for sending // addEnvelope adds an envelope to the envelope map, used for sending
@ -1649,7 +1553,7 @@ func (w *Waku) restartDiscV5() error {
return errors.New("failed to fetch bootnodes") return errors.New("failed to fetch bootnodes")
} }
if !w.node.DiscV5().IsStarted() { if w.node.DiscV5().ErrOnNotRunning() != nil {
w.logger.Info("is not started restarting") w.logger.Info("is not started restarting")
err := w.node.DiscV5().Start(ctx) err := w.node.DiscV5().Start(ctx)
if err != nil { if err != nil {
@ -1681,7 +1585,7 @@ func (w *Waku) AddStorePeer(address string) (peer.ID, error) {
return "", err return "", err
} }
peerID, err := w.node.AddPeer(addr, wps.Static, store.StoreID_v20beta4) peerID, err := w.node.AddPeer(addr, wps.Static, []string{}, store.StoreID_v20beta4)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1698,7 +1602,7 @@ func (w *Waku) AddRelayPeer(address string) (peer.ID, error) {
return "", err return "", err
} }
peerID, err := w.node.AddPeer(addr, wps.Static, relay.WakuRelayID_v200) peerID, err := w.node.AddPeer(addr, wps.Static, []string{}, relay.WakuRelayID_v200)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1798,65 +1702,3 @@ func formatConnStatus(wakuNode *node.WakuNode, c node.ConnStatus) types.ConnStat
Peers: FormatPeerStats(wakuNode, c.Peers), Peers: FormatPeerStats(wakuNode, c.Peers),
} }
} }
// Find suitable peer(s). For this we use a peerDisconnectMap, it works so that
// peers that have been recently disconnected from have lower priority
func (w *Waku) findFilterPeers() []peer.ID {
allPeers := w.node.Host().Peerstore().Peers()
var peers peer.IDSlice
for _, peer := range allPeers {
protocols, err := w.node.Host().Peerstore().SupportsProtocols(peer, filter.FilterSubscribeID_v20beta1, relay.WakuRelayID_v200)
if err != nil {
continue
}
if len(protocols) == 2 {
peers = append(peers, peer)
}
}
if len(peers) > 0 {
sort.Slice(peers, func(i, j int) bool {
// If element not found in map, [] operator will return 0
return w.filterPeerDisconnectMap[peers[i]] < w.filterPeerDisconnectMap[peers[j]]
})
}
var peerLen = len(peers)
if w.settings.MinPeersForFilter < peerLen {
peerLen = w.settings.MinPeersForFilter
}
peers = peers[0:peerLen]
return peers
}
func (w *Waku) subscribeToFilter(f *common.Filter) error {
peers := w.findFilterPeers()
if len(peers) > 0 {
contentFilter := w.buildContentFilter(f.PubsubTopic, f.Topics)
for i := 0; i < len(peers) && i < w.settings.MinPeersForFilter; i++ {
subDetails, err := w.node.FilterLightnode().Subscribe(w.ctx, contentFilter, filter.WithPeer(peers[i]))
if err != nil {
w.logger.Warn("could not add wakuv2 filter for peer", zap.Stringer("peer", peers[i]))
continue
}
subMap := w.filterSubscriptions[f]
if subMap == nil {
subMap = make(map[string]*filter.SubscriptionDetails)
w.filterSubscriptions[f] = subMap
}
subMap[subDetails.ID] = subDetails
go w.runFilterSubscriptionLoop(subDetails)
w.logger.Info("wakuv2 filter subscription success", zap.Stringer("peer", peers[i]), zap.String("pubsubTopic", contentFilter.Topic), zap.Strings("contentTopics", contentFilter.ContentTopics))
}
} else {
return errors.New("could not select a suitable peer for filter")
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More