chore: bump go-waku

This commit is contained in:
Richard Ramos 2023-05-08 17:43:27 -04:00 committed by RichΛrd
parent 98d3b4198b
commit acad6e4958
69 changed files with 3324 additions and 1485 deletions

View File

@ -1 +1 @@
0.150.3
0.151.0

5
go.mod
View File

@ -79,7 +79,7 @@ require (
github.com/ladydascalie/currency v1.6.0
github.com/meirf/gopart v0.0.0-20180520194036-37e9492a85a8
github.com/schollz/peerdiscovery v1.7.0
github.com/waku-org/go-waku v0.5.3-0.20230404182041-41691a44e579
github.com/waku-org/go-waku v0.5.3-0.20230509204224-d9a12bf079a8
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
go.uber.org/multierr v1.8.0
@ -241,10 +241,11 @@ require (
github.com/tyler-smith/go-bip39 v1.1.0 // 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-zerokit-rln v0.1.11 // indirect
github.com/waku-org/go-zerokit-rln v0.1.12 // indirect
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230331231302-258cacb91327 // indirect
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230331223149-f90e66aebb0d // indirect
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230331181847-cba74520bae9 // indirect
github.com/wk8/go-ordered-map v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect

10
go.sum
View File

@ -2102,10 +2102,10 @@ github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 h1:xwY0kW5XZFimdqfZb9cZwT1S3VJP9j3AE6bdNd9boXM=
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98/go.mod h1:eBHgM6T4EG0RZzxpxKy+rGz/6Dw2Nd8DWxS0lm9ESDw=
github.com/waku-org/go-waku v0.5.3-0.20230404182041-41691a44e579 h1:M8Q35R35VZvw05OAzE8x5Gbu7bsDcTX8yBAyJvbTY40=
github.com/waku-org/go-waku v0.5.3-0.20230404182041-41691a44e579/go.mod h1:42KC3R7HOi16QwvREB+8LLV2EzhTRgZ4qE+2jxZAEy8=
github.com/waku-org/go-zerokit-rln v0.1.11 h1:e4veZm80uYkW1r43a5f47YBUUhRELpLx3Bge9EyfuI8=
github.com/waku-org/go-zerokit-rln v0.1.11/go.mod h1:MUW+wB6Yj7UBMdZrhko7oHfUZeY2wchggXYjpUiMoac=
github.com/waku-org/go-waku v0.5.3-0.20230509204224-d9a12bf079a8 h1:1agRxCtCBoCaMB/72L87bZgyvCAEMUoBL6l0MImpV2Y=
github.com/waku-org/go-waku v0.5.3-0.20230509204224-d9a12bf079a8/go.mod h1:6AXlCiXueZC7XbvG1LUi0uEOMS2n/30h2kjXzW8zfYY=
github.com/waku-org/go-zerokit-rln v0.1.12 h1:66+tU6sTlmUpuUlEv7kCFOGZ37MwZYFJBXHcm8QquwU=
github.com/waku-org/go-zerokit-rln v0.1.12/go.mod h1:MUW+wB6Yj7UBMdZrhko7oHfUZeY2wchggXYjpUiMoac=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230331231302-258cacb91327 h1:Q5XQqo+PEmvrybT8D7BEsKCwIYDi80s+00Q49cfm9Gs=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230331231302-258cacb91327/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230331223149-f90e66aebb0d h1:Kcg85Y2xGU6hqZ/kMfkLQF2jAog8vt+tw1/VNidzNtE=
@ -2125,6 +2125,8 @@ github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/willf/bloom v0.0.0-20170505221640-54e3b963ee16/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8=
github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk=
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=

View File

@ -9,16 +9,19 @@ import (
"sync"
"time"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol"
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
"github.com/waku-org/go-waku/waku/v2/timesource"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.opencensus.io/stats"
"go.uber.org/zap"
)
type MessageProvider interface {
GetAll() ([]StoredMessage, error)
Validate(env *protocol.Envelope) error
Put(env *protocol.Envelope) error
Query(query *pb.HistoryQuery) ([]StoredMessage, error)
MostRecentTimestamp() (int64, error)
@ -27,10 +30,15 @@ type MessageProvider interface {
}
var ErrInvalidCursor = errors.New("invalid cursor")
var ErrFutureMessage = errors.New("message timestamp in the future")
var ErrMessageTooOld = errors.New("message too old")
// WALMode for sqlite.
const WALMode = "wal"
// MaxTimeVariance is the maximum duration in the future allowed for a message timestamp
const MaxTimeVariance = time.Duration(20) * time.Second
// DBStore is a MessageProvider that has a *sql.DB connection
type DBStore struct {
MessageProvider
@ -152,18 +160,39 @@ func (d *DBStore) Start(ctx context.Context, timesource timesource.Timesource) e
d.cancel = cancel
d.timesource = timesource
err := d.cleanOlderRecords()
err := d.cleanOlderRecords(ctx)
if err != nil {
return err
}
d.wg.Add(1)
d.wg.Add(2)
go d.checkForOlderRecords(ctx, 60*time.Second)
go d.updateMetrics(ctx)
return nil
}
func (d *DBStore) cleanOlderRecords() error {
func (store *DBStore) updateMetrics(ctx context.Context) {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
defer store.wg.Done()
for {
select {
case <-ticker.C:
msgCount, err := store.Count()
if err != nil {
store.log.Error("updating store metrics", zap.Error(err))
} else {
metrics.RecordArchiveMessage(ctx, "stored", msgCount)
}
case <-ctx.Done():
return
}
}
}
func (d *DBStore) cleanOlderRecords(ctx context.Context) error {
d.log.Info("Cleaning older records...")
// Delete older messages
@ -172,6 +201,7 @@ func (d *DBStore) cleanOlderRecords() error {
sqlStmt := `DELETE FROM message WHERE receiverTimestamp < $1`
_, err := d.db.Exec(sqlStmt, utils.GetUnixEpochFrom(d.timesource.Now().Add(-d.maxDuration)))
if err != nil {
metrics.RecordArchiveError(ctx, "retpolicy_failure")
return err
}
elapsed := time.Since(start)
@ -184,6 +214,7 @@ func (d *DBStore) cleanOlderRecords() error {
sqlStmt := `DELETE FROM message WHERE id IN (SELECT id FROM message ORDER BY receiverTimestamp DESC LIMIT -1 OFFSET $1)`
_, err := d.db.Exec(sqlStmt, d.maxMessages)
if err != nil {
metrics.RecordArchiveError(ctx, "retpolicy_failure")
return err
}
elapsed := time.Since(start)
@ -206,7 +237,7 @@ func (d *DBStore) checkForOlderRecords(ctx context.Context, t time.Duration) {
case <-ctx.Done():
return
case <-ticker.C:
err := d.cleanOlderRecords()
err := d.cleanOlderRecords(ctx)
if err != nil {
d.log.Error("cleaning older records", zap.Error(err))
}
@ -225,19 +256,41 @@ func (d *DBStore) Stop() {
d.db.Close()
}
func (d *DBStore) Validate(env *protocol.Envelope) error {
n := time.Unix(0, env.Index().ReceiverTime)
upperBound := n.Add(MaxTimeVariance)
lowerBound := n.Add(-MaxTimeVariance)
// Ensure that messages don't "jump" to the front of the queue with future timestamps
if env.Message().Timestamp > upperBound.UnixNano() {
return ErrFutureMessage
}
if env.Message().Timestamp < lowerBound.UnixNano() {
return ErrMessageTooOld
}
return nil
}
// Put inserts a WakuMessage into the DB
func (d *DBStore) Put(env *protocol.Envelope) error {
stmt, err := d.db.Prepare("INSERT INTO message (id, receiverTimestamp, senderTimestamp, contentTopic, pubsubTopic, payload, version) VALUES ($1, $2, $3, $4, $5, $6, $7)")
if err != nil {
metrics.RecordArchiveError(context.TODO(), "insert_failure")
return err
}
cursor := env.Index()
dbKey := NewDBKey(uint64(cursor.SenderTime), uint64(cursor.ReceiverTime), env.PubsubTopic(), env.Index().Digest)
start := time.Now()
_, err = stmt.Exec(dbKey.Bytes(), cursor.ReceiverTime, env.Message().Timestamp, env.Message().ContentTopic, env.PubsubTopic(), env.Message().Payload, env.Message().Version)
if err != nil {
return err
}
ellapsed := time.Since(start)
stats.Record(context.Background(), metrics.ArchiveInsertDurationSeconds.M(int64(ellapsed.Seconds())))
err = stmt.Close()
if err != nil {
@ -352,10 +405,13 @@ func (d *DBStore) Query(query *pb.HistoryQuery) (*pb.Index, []StoredMessage, err
pageSize := query.PagingInfo.PageSize + 1
parameters = append(parameters, pageSize)
measurementStart := time.Now()
rows, err := stmt.Query(parameters...)
if err != nil {
return nil, nil, err
}
ellapsed := time.Since(measurementStart)
stats.Record(context.Background(), metrics.ArchiveQueryDurationSeconds.M(int64(ellapsed.Seconds())))
var result []StoredMessage
for rows.Next() {

View File

@ -1,175 +0,0 @@
package v2
import (
"github.com/waku-org/go-waku/waku/v2/protocol"
)
// Adapted from https://github.com/dustin/go-broadcast/commit/f664265f5a662fb4d1df7f3533b1e8d0e0277120
// by Dustin Sallings (c) 2013, which was released under MIT license
type doneCh chan struct{}
type chOperation struct {
ch chan<- *protocol.Envelope
topic *string
done doneCh
}
type broadcastOutputs map[chan<- *protocol.Envelope]struct{}
type broadcaster struct {
input chan *protocol.Envelope
reg chan chOperation
unreg chan chOperation
outputs broadcastOutputs
outputsPerTopic map[string]broadcastOutputs
}
// The Broadcaster interface describes the main entry points to
// broadcasters.
type Broadcaster interface {
// Register a new channel to receive broadcasts from a pubsubtopic
Register(topic *string, newch chan<- *protocol.Envelope)
// Register a new channel to receive broadcasts from a pubsub topic and return a channel to wait until this operation is complete
WaitRegister(topic *string, newch chan<- *protocol.Envelope) doneCh
// Unregister a channel so that it no longer receives broadcasts from a pubsub topic
Unregister(topic *string, newch chan<- *protocol.Envelope)
// Unregister a subscriptor channel and return a channel to wait until this operation is done
WaitUnregister(topic *string, newch chan<- *protocol.Envelope) doneCh
// Shut this broadcaster down.
Close()
// Submit a new object to all subscribers
Submit(*protocol.Envelope)
}
func (b *broadcaster) broadcast(m *protocol.Envelope) {
for ch := range b.outputs {
ch <- m
}
outputs, ok := b.outputsPerTopic[m.PubsubTopic()]
if !ok {
return
}
for ch := range outputs {
ch <- m
}
}
func (b *broadcaster) run() {
for {
select {
case m := <-b.input:
b.broadcast(m)
case broadcastee, ok := <-b.reg:
if ok {
if broadcastee.topic != nil {
topicOutputs, ok := b.outputsPerTopic[*broadcastee.topic]
if !ok {
b.outputsPerTopic[*broadcastee.topic] = make(broadcastOutputs)
topicOutputs = b.outputsPerTopic[*broadcastee.topic]
}
topicOutputs[broadcastee.ch] = struct{}{}
b.outputsPerTopic[*broadcastee.topic] = topicOutputs
} else {
b.outputs[broadcastee.ch] = struct{}{}
}
if broadcastee.done != nil {
broadcastee.done <- struct{}{}
}
} else {
if broadcastee.done != nil {
broadcastee.done <- struct{}{}
}
return
}
case broadcastee := <-b.unreg:
if broadcastee.topic != nil {
topicOutputs, ok := b.outputsPerTopic[*broadcastee.topic]
if !ok {
continue
}
delete(topicOutputs, broadcastee.ch)
b.outputsPerTopic[*broadcastee.topic] = topicOutputs
} else {
delete(b.outputs, broadcastee.ch)
}
if broadcastee.done != nil {
broadcastee.done <- struct{}{}
}
}
}
}
// NewBroadcaster creates a Broadcaster with an specified length
// It's used to register subscriptors that will need to receive
// an Envelope containing a WakuMessage
func NewBroadcaster(buflen int) Broadcaster {
b := &broadcaster{
input: make(chan *protocol.Envelope, buflen),
reg: make(chan chOperation),
unreg: make(chan chOperation),
outputs: make(broadcastOutputs),
outputsPerTopic: make(map[string]broadcastOutputs),
}
go b.run()
return b
}
// Register a subscriptor channel and return a channel to wait until this operation is done
func (b *broadcaster) WaitRegister(topic *string, newch chan<- *protocol.Envelope) doneCh {
d := make(doneCh)
b.reg <- chOperation{
ch: newch,
topic: topic,
done: d,
}
return d
}
// Register a subscriptor channel
func (b *broadcaster) Register(topic *string, newch chan<- *protocol.Envelope) {
b.reg <- chOperation{
ch: newch,
topic: topic,
done: nil,
}
}
// Unregister a subscriptor channel and return a channel to wait until this operation is done
func (b *broadcaster) WaitUnregister(topic *string, newch chan<- *protocol.Envelope) doneCh {
d := make(doneCh)
b.unreg <- chOperation{
ch: newch,
topic: topic,
done: d,
}
return d
}
// Unregister a subscriptor channel
func (b *broadcaster) Unregister(topic *string, newch chan<- *protocol.Envelope) {
b.unreg <- chOperation{
ch: newch,
topic: topic,
done: nil,
}
}
// Closes the broadcaster. Used to stop receiving new subscribers
func (b *broadcaster) Close() {
close(b.reg)
}
// Submits an Envelope to be broadcasted among all registered subscriber channels
func (b *broadcaster) Submit(m *protocol.Envelope) {
if b != nil {
b.input <- m
}
}

View File

@ -46,7 +46,7 @@ type PeerConnectionStrategy struct {
// dialTimeout is how long we attempt to connect to a peer before giving up
// minPeers is the minimum number of peers that the node should have
// backoff describes the strategy used to decide how long to backoff after previously attempting to connect to a peer
func NewPeerConnectionStrategy(h host.Host, cacheSize int, minPeers int, dialTimeout time.Duration, backoff backoff.BackoffFactory, logger *zap.Logger) (*PeerConnectionStrategy, error) {
func NewPeerConnectionStrategy(cacheSize int, minPeers int, dialTimeout time.Duration, backoff backoff.BackoffFactory, logger *zap.Logger) (*PeerConnectionStrategy, error) {
cache, err := lru.New2Q(cacheSize)
if err != nil {
return nil, err
@ -54,7 +54,6 @@ func NewPeerConnectionStrategy(h host.Host, cacheSize int, minPeers int, dialTim
return &PeerConnectionStrategy{
cache: cache,
host: h,
wg: sync.WaitGroup{},
minPeers: minPeers,
dialTimeout: dialTimeout,
@ -73,6 +72,11 @@ func (c *PeerConnectionStrategy) PeerChannel() chan<- peer.AddrInfo {
return c.peerCh
}
// Sets the host to be able to mount or consume a protocol
func (c *PeerConnectionStrategy) SetHost(h host.Host) {
c.host = h
}
// Start attempts to connect to the peers passed in by peerCh. Will not connect to peers if they are within the backoff period.
func (c *PeerConnectionStrategy) Start(ctx context.Context) error {
if c.cancel != nil {

View File

@ -7,6 +7,7 @@ import (
"fmt"
"net"
"sync"
"sync/atomic"
"time"
"github.com/libp2p/go-libp2p/core/host"
@ -14,6 +15,8 @@ import (
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-discover/discover"
"github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol/enr"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap"
@ -24,8 +27,6 @@ import (
var ErrNoDiscV5Listener = errors.New("no discv5 listener")
type DiscoveryV5 struct {
sync.RWMutex
params *discV5Parameters
host host.Host
config discover.Config
@ -37,7 +38,7 @@ type DiscoveryV5 struct {
log *zap.Logger
started bool
started atomic.Bool
cancel context.CancelFunc
wg *sync.WaitGroup
}
@ -87,7 +88,7 @@ type PeerConnector interface {
PeerChannel() chan<- peer.AddrInfo
}
func NewDiscoveryV5(host host.Host, priv *ecdsa.PrivateKey, localnode *enode.LocalNode, peerConnector PeerConnector, log *zap.Logger, opts ...DiscoveryV5Option) (*DiscoveryV5, error) {
func NewDiscoveryV5(priv *ecdsa.PrivateKey, localnode *enode.LocalNode, peerConnector PeerConnector, log *zap.Logger, opts ...DiscoveryV5Option) (*DiscoveryV5, error) {
params := new(discV5Parameters)
optList := DefaultOptions()
optList = append(optList, opts...)
@ -103,7 +104,6 @@ func NewDiscoveryV5(host host.Host, priv *ecdsa.PrivateKey, localnode *enode.Loc
}
return &DiscoveryV5{
host: host,
peerConnector: peerConnector,
params: params,
NAT: NAT,
@ -135,6 +135,7 @@ func (d *DiscoveryV5) listen(ctx context.Context) error {
}
d.udpAddr = conn.LocalAddr().(*net.UDPAddr)
if d.NAT != nil && !d.udpAddr.IP.IsLoopback() {
d.wg.Add(1)
go func() {
@ -161,15 +162,21 @@ func (d *DiscoveryV5) listen(ctx context.Context) error {
return nil
}
// Sets the host to be able to mount or consume a protocol
func (d *DiscoveryV5) SetHost(h host.Host) {
d.host = h
}
// only works if the discovery v5 hasn't been started yet.
func (d *DiscoveryV5) Start(ctx context.Context) error {
d.Lock()
defer d.Unlock()
// compare and swap sets the discovery v5 to `started` state
// and prevents multiple calls to the start method by being atomic.
if !d.started.CompareAndSwap(false, true) {
return nil
}
d.wg.Wait() // Waiting for any go routines to stop
ctx, cancel := context.WithCancel(ctx)
d.cancel = cancel
d.started = true
err := d.listen(ctx)
if err != nil {
@ -177,7 +184,10 @@ func (d *DiscoveryV5) Start(ctx context.Context) error {
}
d.wg.Add(1)
go d.runDiscoveryV5Loop(ctx)
go func() {
defer d.wg.Done()
d.runDiscoveryV5Loop(ctx)
}()
return nil
}
@ -190,16 +200,13 @@ func (d *DiscoveryV5) SetBootnodes(nodes []*enode.Node) error {
return d.listener.SetFallbackNodes(nodes)
}
// only works if the discovery v5 is in running state
// so we can assume that cancel method is set
func (d *DiscoveryV5) Stop() {
d.Lock()
defer d.Unlock()
if d.cancel == nil {
if !d.started.CompareAndSwap(true, false) { // if Discoveryv5 is running, set started to false
return
}
d.cancel()
d.started = false
if d.listener != nil {
d.listener.Close()
@ -238,9 +245,10 @@ func evaluateNode(node *enode.Node) bool {
return false
}*/
_, err := utils.EnodeToPeerInfo(node)
_, err := enr.EnodeToPeerInfo(node)
if err != nil {
metrics.RecordDiscV5Error(context.Background(), "peer_info_failure")
utils.Logger().Named("discv5").Error("obtaining peer info from enode", logging.ENode("enr", node), zap.Error(err))
return false
}
@ -248,6 +256,9 @@ func evaluateNode(node *enode.Node) bool {
return true
}
// get random nodes from DHT via discv5 listender
// used for caching enr address in peerExchange
// used for connecting to peers in discovery_connector
func (d *DiscoveryV5) Iterator() (enode.Iterator, error) {
if d.listener == nil {
return nil, ErrNoDiscV5Listener
@ -257,55 +268,38 @@ func (d *DiscoveryV5) Iterator() (enode.Iterator, error) {
return enode.Filter(iterator, evaluateNode), nil
}
// iterate over all fecthed peer addresses and send them to peerConnector
func (d *DiscoveryV5) iterate(ctx context.Context) error {
iterator, err := d.Iterator()
if err != nil {
metrics.RecordDiscV5Error(context.Background(), "iterator_failure")
return fmt.Errorf("obtaining iterator: %w", err)
}
closeCh := make(chan struct{}, 1)
defer close(closeCh)
defer iterator.Close()
// Closing iterator when context is cancelled or function is returning
d.wg.Add(1)
go func() {
defer d.wg.Done()
select {
case <-ctx.Done():
iterator.Close()
case <-closeCh:
iterator.Close()
}
}()
for {
if ctx.Err() != nil {
break
}
exists := iterator.Next()
if !exists {
break
}
_, addresses, err := utils.Multiaddress(iterator.Node())
for iterator.Next() { // while next exists, run for loop
_, addresses, err := enr.Multiaddress(iterator.Node())
if err != nil {
metrics.RecordDiscV5Error(context.Background(), "peer_info_failure")
d.log.Error("extracting multiaddrs from enr", zap.Error(err))
continue
}
peerAddrs, err := peer.AddrInfosFromP2pAddrs(addresses...)
if err != nil {
metrics.RecordDiscV5Error(context.Background(), "peer_info_failure")
d.log.Error("converting multiaddrs to addrinfos", zap.Error(err))
continue
}
if len(peerAddrs) != 0 {
select {
case <-ctx.Done():
return nil
case d.peerConnector.PeerChannel() <- peerAddrs[0]:
}
d.peerConnector.PeerChannel() <- peerAddrs[0]
}
select {
case <-ctx.Done():
return nil
default:
}
}
@ -313,23 +307,20 @@ func (d *DiscoveryV5) iterate(ctx context.Context) error {
}
func (d *DiscoveryV5) runDiscoveryV5Loop(ctx context.Context) {
defer d.wg.Done()
ch := make(chan struct{}, 1)
ch <- struct{}{} // Initial execution
restartLoop:
for {
err := d.iterate(ctx)
if err != nil {
d.log.Debug("iterating discv5", zap.Error(err))
}
t := time.NewTimer(5 * time.Second)
select {
case <-ch:
err := d.iterate(ctx)
if err != nil {
d.log.Debug("iterating discv5", zap.Error(err))
time.Sleep(2 * time.Second)
}
ch <- struct{}{}
case <-t.C:
t.Stop()
case <-ctx.Done():
close(ch)
t.Stop()
break restartLoop
}
}
@ -337,8 +328,5 @@ restartLoop:
}
func (d *DiscoveryV5) IsStarted() bool {
d.RLock()
defer d.RUnlock()
return d.started
return d.started.Load()
}

View File

@ -7,7 +7,8 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/utils"
"github.com/waku-org/go-waku/waku/v2/metrics"
wenr "github.com/waku-org/go-waku/waku/v2/protocol/enr"
ma "github.com/multiformats/go-multiaddr"
)
@ -46,12 +47,14 @@ func RetrieveNodes(ctx context.Context, url string, opts ...DnsDiscoveryOption)
tree, err := client.SyncTree(url)
if err != nil {
metrics.RecordDnsDiscoveryError(ctx, "tree_sync_failure")
return nil, err
}
for _, node := range tree.Nodes() {
peerID, m, err := utils.Multiaddress(node)
peerID, m, err := wenr.Multiaddress(node)
if err != nil {
metrics.RecordDnsDiscoveryError(ctx, "peer_info_failure")
return nil, err
}

View File

@ -3,6 +3,7 @@ package metrics
import (
"context"
"fmt"
"time"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.opencensus.io/stats"
@ -12,16 +13,42 @@ import (
)
var (
WakuVersion = stats.Int64("waku_version", "", stats.UnitDimensionless)
Messages = stats.Int64("node_messages", "Number of messages received", stats.UnitDimensionless)
Peers = stats.Int64("peers", "Number of connected peers", stats.UnitDimensionless)
Dials = stats.Int64("dials", "Number of peer dials", stats.UnitDimensionless)
StoreMessages = stats.Int64("store_messages", "Number of historical messages", stats.UnitDimensionless)
FilterSubscriptions = stats.Int64("filter_subscriptions", "Number of filter subscriptions", stats.UnitDimensionless)
StoreErrors = stats.Int64("errors", "Number of errors in store protocol", stats.UnitDimensionless)
StoreQueries = stats.Int64("store_queries", "Number of store queries", stats.UnitDimensionless)
LightpushErrors = stats.Int64("errors", "Number of errors in lightpush protocol", stats.UnitDimensionless)
PeerExchangeError = stats.Int64("errors", "Number of errors in peer exchange protocol", stats.UnitDimensionless)
WakuVersion = stats.Int64("waku_version", "", stats.UnitDimensionless)
Messages = stats.Int64("node_messages", "Number of messages received", stats.UnitDimensionless)
MessageSize = stats.Int64("waku_histogram_message_size", "message size histogram in kB", stats.UnitDimensionless)
Peers = stats.Int64("peers", "Number of connected peers", stats.UnitDimensionless)
Dials = stats.Int64("dials", "Number of peer dials", stats.UnitDimensionless)
LegacyFilterMessages = stats.Int64("legacy_filter_messages", "Number of legacy filter messages", stats.UnitDimensionless)
LegacyFilterSubscribers = stats.Int64("legacy_filter_subscribers", "Number of legacy filter subscribers", stats.UnitDimensionless)
LegacyFilterSubscriptions = stats.Int64("legacy_filter_subscriptions", "Number of legacy filter subscriptions", stats.UnitDimensionless)
LegacyFilterErrors = stats.Int64("legacy_filter_errors", "Number of errors in legacy filter protocol", stats.UnitDimensionless)
FilterMessages = stats.Int64("filter_messages", "Number of filter messages", stats.UnitDimensionless)
FilterRequests = stats.Int64("filter_requests", "Number of filter requests", stats.UnitDimensionless)
FilterSubscriptions = stats.Int64("filter_subscriptions", "Number of filter subscriptions", stats.UnitDimensionless)
FilterErrors = stats.Int64("filter_errors", "Number of errors in filter protocol", stats.UnitDimensionless)
FilterRequestDurationSeconds = stats.Int64("filter_request_duration_seconds", "Duration of Filter Subscribe Requests", stats.UnitSeconds)
FilterHandleMessageDurationSeconds = stats.Int64("filter_handle_msessageduration_seconds", "Duration to Push Message to Filter Subscribers", stats.UnitSeconds)
StoreErrors = stats.Int64("errors", "Number of errors in store protocol", stats.UnitDimensionless)
StoreQueries = stats.Int64("store_queries", "Number of store queries", stats.UnitDimensionless)
ArchiveMessages = stats.Int64("waku_archive_messages", "Number of historical messages", stats.UnitDimensionless)
ArchiveErrors = stats.Int64("waku_archive_errors", "Number of errors in archive protocol", stats.UnitDimensionless)
ArchiveInsertDurationSeconds = stats.Int64("waku_archive_insert_duration_seconds", "Message insertion duration", stats.UnitSeconds)
ArchiveQueryDurationSeconds = stats.Int64("waku_archive_query_duration_seconds", "History query duration", stats.UnitSeconds)
LightpushMessages = stats.Int64("lightpush_messages", "Number of messages sent via lightpush protocol", stats.UnitDimensionless)
LightpushErrors = stats.Int64("errors", "Number of errors in lightpush protocol", stats.UnitDimensionless)
PeerExchangeError = stats.Int64("errors", "Number of errors in peer exchange protocol", stats.UnitDimensionless)
DnsDiscoveryNodes = stats.Int64("dnsdisc_nodes", "Number of discovered nodes in dns discovert", stats.UnitDimensionless)
DnsDiscoveryErrors = stats.Int64("dnsdisc_errors", "Number of errors in dns discovery", stats.UnitDimensionless)
DiscV5Errors = stats.Int64("discv5_errors", "Number of errors in discv5", stats.UnitDimensionless)
)
var (
@ -32,63 +59,179 @@ var (
var (
PeersView = &view.View{
Name: "gowaku_connected_peers",
Name: "waku_connected_peers",
Measure: Peers,
Description: "Number of connected peers",
Aggregation: view.Sum(),
}
DialsView = &view.View{
Name: "gowaku_peers_dials",
Name: "waku_peers_dials",
Measure: Dials,
Description: "Number of peer dials",
Aggregation: view.Count(),
}
MessageView = &view.View{
Name: "gowaku_node_messages",
Name: "waku_node_messages",
Measure: Messages,
Description: "The number of the messages received",
Aggregation: view.Count(),
}
MessageSizeView = &view.View{
Name: "waku_histogram_message_size",
Measure: MessageSize,
Description: "message size histogram in kB",
Aggregation: view.Distribution(0.0, 5.0, 15.0, 50.0, 100.0, 300.0, 700.0, 1000.0),
}
StoreQueriesView = &view.View{
Name: "gowaku_store_queries",
Name: "waku_store_queries",
Measure: StoreQueries,
Description: "The number of the store queries received",
Aggregation: view.Count(),
}
StoreMessagesView = &view.View{
Name: "gowaku_store_messages",
Measure: StoreMessages,
Description: "The distribution of the store protocol messages",
Aggregation: view.LastValue(),
TagKeys: []tag.Key{KeyType},
}
FilterSubscriptionsView = &view.View{
Name: "gowaku_filter_subscriptions",
Measure: FilterSubscriptions,
Description: "The number of content filter subscriptions",
Aggregation: view.LastValue(),
}
StoreErrorTypesView = &view.View{
Name: "gowaku_store_errors",
Name: "waku_store_errors",
Measure: StoreErrors,
Description: "The distribution of the store protocol errors",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
ArchiveMessagesView = &view.View{
Name: "waku_archive_messages",
Measure: ArchiveMessages,
Description: "The distribution of the archive protocol messages",
Aggregation: view.LastValue(),
TagKeys: []tag.Key{KeyType},
}
ArchiveErrorTypesView = &view.View{
Name: "waku_archive_errors",
Measure: StoreErrors,
Description: "Number of errors in archive protocol",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
ArchiveInsertDurationView = &view.View{
Name: "waku_archive_insert_duration_seconds",
Measure: ArchiveInsertDurationSeconds,
Description: "Message insertion duration",
Aggregation: view.Count(),
}
ArchiveQueryDurationView = &view.View{
Name: "waku_archive_query_duration_seconds",
Measure: ArchiveQueryDurationSeconds,
Description: "History query duration",
Aggregation: view.Count(),
}
LegacyFilterSubscriptionsView = &view.View{
Name: "waku_legacy_filter_subscriptions",
Measure: LegacyFilterSubscriptions,
Description: "The number of legacy filter subscriptions",
Aggregation: view.Count(),
}
LegacyFilterSubscribersView = &view.View{
Name: "waku_legacy_filter_subscribers",
Measure: LegacyFilterSubscribers,
Description: "The number of legacy filter subscribers",
Aggregation: view.LastValue(),
}
LegacyFilterMessagesView = &view.View{
Name: "waku_legacy_filter_messages",
Measure: LegacyFilterMessages,
Description: "The distribution of the legacy filter protocol messages received",
Aggregation: view.Count(),
TagKeys: []tag.Key{KeyType},
}
LegacyFilterErrorTypesView = &view.View{
Name: "waku_legacy_filter_errors",
Measure: LegacyFilterErrors,
Description: "The distribution of the legacy filter protocol errors",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
FilterSubscriptionsView = &view.View{
Name: "waku_filter_subscriptions",
Measure: FilterSubscriptions,
Description: "The number of filter subscriptions",
Aggregation: view.Count(),
}
FilterRequestsView = &view.View{
Name: "waku_filter_requests",
Measure: FilterRequests,
Description: "The number of filter requests",
Aggregation: view.Count(),
}
FilterMessagesView = &view.View{
Name: "waku_filter_messages",
Measure: FilterMessages,
Description: "The distribution of the filter protocol messages received",
Aggregation: view.Count(),
TagKeys: []tag.Key{KeyType},
}
FilterErrorTypesView = &view.View{
Name: "waku_filter_errors",
Measure: FilterErrors,
Description: "The distribution of the filter protocol errors",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
FilterRequestDurationView = &view.View{
Name: "waku_filter_request_duration_seconds",
Measure: FilterRequestDurationSeconds,
Description: "Duration of Filter Subscribe Requests",
Aggregation: view.Count(),
}
FilterHandleMessageDurationView = &view.View{
Name: "waku_filter_handle_msessageduration_seconds",
Measure: FilterHandleMessageDurationSeconds,
Description: "Duration to Push Message to Filter Subscribers",
Aggregation: view.Count(),
}
LightpushMessagesView = &view.View{
Name: "waku_lightpush_messages",
Measure: LightpushMessages,
Description: "The distribution of the lightpush protocol messages",
Aggregation: view.LastValue(),
TagKeys: []tag.Key{KeyType},
}
LightpushErrorTypesView = &view.View{
Name: "gowaku_lightpush_errors",
Name: "waku_lightpush_errors",
Measure: LightpushErrors,
Description: "The distribution of the lightpush protocol errors",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
VersionView = &view.View{
Name: "gowaku_version",
Name: "waku_version",
Measure: WakuVersion,
Description: "The gowaku version",
Aggregation: view.LastValue(),
TagKeys: []tag.Key{GitVersion},
}
DnsDiscoveryNodesView = &view.View{
Name: "waku_dnsdisc_discovered",
Measure: DnsDiscoveryNodes,
Description: "The number of nodes discovered via DNS discovery",
Aggregation: view.Count(),
}
DnsDiscoveryErrorTypesView = &view.View{
Name: "waku_dnsdisc_errors",
Measure: DnsDiscoveryErrors,
Description: "The distribution of the dns discovery protocol errors",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
DiscV5ErrorTypesView = &view.View{
Name: "waku_discv5_errors",
Measure: DiscV5Errors,
Description: "The distribution of the discv5 protocol errors",
Aggregation: view.Count(),
TagKeys: []tag.Key{ErrorType},
}
)
func recordWithTags(ctx context.Context, tagKey tag.Key, tagType string, ms stats.Measurement) {
@ -97,16 +240,61 @@ func recordWithTags(ctx context.Context, tagKey tag.Key, tagType string, ms stat
}
}
func RecordLightpushMessage(ctx context.Context, tagType string) {
if err := stats.RecordWithTags(ctx, []tag.Mutator{tag.Insert(KeyType, tagType)}, LightpushMessages.M(1)); err != nil {
utils.Logger().Error("failed to record with tags", zap.Error(err))
}
}
func RecordLightpushError(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, LightpushErrors.M(1))
}
func RecordLegacyFilterError(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, LegacyFilterErrors.M(1))
}
func RecordArchiveError(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, ArchiveErrors.M(1))
}
func RecordFilterError(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, FilterErrors.M(1))
}
func RecordFilterRequest(ctx context.Context, tagType string, duration time.Duration) {
if err := stats.RecordWithTags(ctx, []tag.Mutator{tag.Insert(KeyType, tagType)}, FilterRequests.M(1)); err != nil {
utils.Logger().Error("failed to record with tags", zap.Error(err))
}
FilterRequestDurationSeconds.M(int64(duration.Seconds()))
}
func RecordFilterMessage(ctx context.Context, tagType string, len int) {
if err := stats.RecordWithTags(ctx, []tag.Mutator{tag.Insert(KeyType, tagType)}, FilterMessages.M(int64(len))); err != nil {
utils.Logger().Error("failed to record with tags", zap.Error(err))
}
}
func RecordLegacyFilterMessage(ctx context.Context, tagType string, len int) {
if err := stats.RecordWithTags(ctx, []tag.Mutator{tag.Insert(KeyType, tagType)}, LegacyFilterMessages.M(int64(len))); err != nil {
utils.Logger().Error("failed to record with tags", zap.Error(err))
}
}
func RecordPeerExchangeError(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, PeerExchangeError.M(1))
}
func RecordMessage(ctx context.Context, tagType string, len int) {
if err := stats.RecordWithTags(ctx, []tag.Mutator{tag.Insert(KeyType, tagType)}, StoreMessages.M(int64(len))); err != nil {
func RecordDnsDiscoveryError(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, DnsDiscoveryErrors.M(1))
}
func RecordDiscV5Error(ctx context.Context, tagType string) {
recordWithTags(ctx, ErrorType, tagType, DiscV5Errors.M(1))
}
func RecordArchiveMessage(ctx context.Context, tagType string, len int) {
if err := stats.RecordWithTags(ctx, []tag.Mutator{tag.Insert(KeyType, tagType)}, ArchiveMessages.M(int64(len))); err != nil {
utils.Logger().Error("failed to record with tags", zap.Error(err))
}
}

View File

@ -10,7 +10,7 @@ import (
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/v2/metrics"
"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/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
@ -131,7 +131,7 @@ func (w *WakuNode) Status() (isOnline bool, hasHistory bool) {
if !hasStore && protocol == store.StoreID_v20beta4 {
hasStore = true
}
if !hasFilter && protocol == filter.FilterID_v20beta1 {
if !hasFilter && protocol == legacy_filter.FilterID_v20beta1 {
hasFilter = true
}
}

View File

@ -14,7 +14,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
ma "github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/waku/v2/utils"
wenr "github.com/waku-org/go-waku/waku/v2/protocol/enr"
"go.uber.org/zap"
)
@ -30,7 +30,7 @@ func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []ma.Multiaddr)
defer func() {
if e := recover(); e != nil {
// Deleting the multiaddr entry, as we could not write it succesfully
localnode.Delete(enr.WithEntry(utils.MultiaddrENRField, struct{}{}))
localnode.Delete(enr.WithEntry(wenr.MultiaddrENRField, struct{}{}))
err = errors.New("could not write enr record")
}
}()
@ -46,7 +46,7 @@ func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []ma.Multiaddr)
}
if len(fieldRaw) != 0 && len(fieldRaw) <= 100 { // Max length for multiaddr field before triggering the 300 bytes limit
localnode.Set(enr.WithEntry(utils.MultiaddrENRField, fieldRaw))
localnode.Set(enr.WithEntry(wenr.MultiaddrENRField, fieldRaw))
}
// This is to trigger the signing record err due to exceeding 300bytes limit
@ -55,9 +55,9 @@ func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []ma.Multiaddr)
return nil
}
func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags utils.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, log *zap.Logger) error {
localnode.SetFallbackUDP(int(udpPort))
localnode.Set(enr.WithEntry(utils.WakuENRField, wakuFlags))
localnode.Set(enr.WithEntry(wenr.WakuENRField, wakuFlags))
localnode.SetFallbackIP(net.IP{127, 0, 0, 1})
if udpPort > math.MaxUint16 {

View File

@ -1,113 +0,0 @@
//go:build gowaku_rln
// +build gowaku_rln
package node
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
const RLN_CREDENTIALS_FILENAME = "rlnCredentials.txt"
func WriteRLNMembershipCredentialsToFile(keyPair *rln.MembershipKeyPair, idx rln.MembershipIndex, contractAddress common.Address, path string, passwd []byte) error {
if path == "" {
return nil // we dont want to use a credentials file
}
if keyPair == nil {
return nil // no credentials to store
}
credentialsJSON, err := json.Marshal(MembershipCredentials{
Keypair: keyPair,
Index: idx,
Contract: contractAddress,
})
if err != nil {
return err
}
encryptedCredentials, err := keystore.EncryptDataV3(credentialsJSON, passwd, keystore.StandardScryptN, keystore.StandardScryptP)
if err != nil {
return err
}
output, err := json.Marshal(encryptedCredentials)
if err != nil {
return err
}
path = filepath.Join(path, RLN_CREDENTIALS_FILENAME)
return ioutil.WriteFile(path, output, 0600)
}
func loadMembershipCredentialsFromFile(credentialsFilePath string, passwd string) (MembershipCredentials, error) {
src, err := ioutil.ReadFile(credentialsFilePath)
if err != nil {
return MembershipCredentials{}, err
}
var encryptedK keystore.CryptoJSON
err = json.Unmarshal(src, &encryptedK)
if err != nil {
return MembershipCredentials{}, err
}
credentialsBytes, err := keystore.DecryptDataV3(encryptedK, passwd)
if err != nil {
return MembershipCredentials{}, err
}
var credentials MembershipCredentials
err = json.Unmarshal(credentialsBytes, &credentials)
return credentials, err
}
func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, password string, membershipContract common.Address, membershipIndex uint) (credentials MembershipCredentials, err error) {
if credentialsPath == "" { // Not using a file
return MembershipCredentials{
Contract: membershipContract,
}, nil
}
credentialsFilePath := filepath.Join(credentialsPath, RLN_CREDENTIALS_FILENAME)
if _, err = os.Stat(credentialsFilePath); err == nil {
if credentials, err := loadMembershipCredentialsFromFile(credentialsFilePath, password); err != nil {
return MembershipCredentials{}, fmt.Errorf("could not read membership credentials file: %w", err)
} else {
logger.Info("loaded rln credentials", zap.String("filepath", credentialsFilePath))
if (bytes.Equal(credentials.Contract.Bytes(), common.Address{}.Bytes())) {
credentials.Contract = membershipContract
}
if (bytes.Equal(membershipContract.Bytes(), common.Address{}.Bytes())) {
return MembershipCredentials{}, errors.New("no contract address specified")
}
return credentials, nil
}
}
if os.IsNotExist(err) {
return MembershipCredentials{
Keypair: nil,
Index: membershipIndex,
Contract: membershipContract,
}, nil
}
return MembershipCredentials{}, fmt.Errorf("could not read membership credentials file: %w", err)
}

View File

@ -3,18 +3,21 @@ package node
import (
"context"
"github.com/libp2p/go-libp2p/core/host"
"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/relay"
)
type Service interface {
Start(ctx context.Context) error
SetHost(h host.Host)
Start(context.Context) error
Stop()
}
type ReceptorService interface {
Service
MessageChannel() chan *protocol.Envelope
SetHost(h host.Host)
Stop()
Start(context.Context, relay.Subscription) error
}
type PeerConnectorService interface {

View File

@ -13,7 +13,6 @@ import (
"github.com/libp2p/go-libp2p"
"go.uber.org/zap"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
@ -33,8 +32,9 @@ import (
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/discv5"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol/enr"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/filterv2"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange"
@ -55,15 +55,18 @@ type Peer struct {
type storeFactory func(w *WakuNode) store.Store
type MembershipKeyPair = struct {
IDKey [32]byte `json:"idKey"`
IDCommitment [32]byte `json:"idCommitment"`
type byte32 = [32]byte
type IdentityCredential = struct {
IDTrapdoor byte32 `json:"idTrapdoor"`
IDNullifier byte32 `json:"idNullifier"`
IDSecretHash byte32 `json:"idSecretHash"`
IDCommitment byte32 `json:"idCommitment"`
}
type RLNRelay interface {
MembershipKeyPair() *MembershipKeyPair
MembershipIndex() uint
MembershipContractAddress() common.Address
IdentityCredential() (IdentityCredential, error)
MembershipIndex() (uint, error)
AppendRLNProof(msg *pb.WakuMessage, senderEpochTime time.Time) error
Stop()
}
@ -74,23 +77,23 @@ type WakuNode struct {
log *zap.Logger
timesource timesource.Timesource
relay Service
lightPush Service
peerConnector PeerConnectorService
discoveryV5 Service
peerExchange Service
rendezvous Service
filter ReceptorService
filterV2Full ReceptorService
filterV2Light Service
store ReceptorService
rlnRelay RLNRelay
relay Service
lightPush Service
peerConnector PeerConnectorService
discoveryV5 Service
peerExchange Service
rendezvous Service
legacyFilter ReceptorService
filterFullnode ReceptorService
filterLightnode Service
store ReceptorService
rlnRelay RLNRelay
wakuFlag utils.WakuEnrBitfield
wakuFlag enr.WakuEnrBitfield
localNode *enode.LocalNode
bcaster v2.Broadcaster
bcaster relay.Broadcaster
connectionNotif ConnectionNotifier
protocolEventSub event.Subscription
@ -112,7 +115,7 @@ type WakuNode struct {
}
func defaultStoreFactory(w *WakuNode) store.Store {
return store.NewWakuStore(w.host, w.opts.messageProvider, w.timesource, w.log)
return store.NewWakuStore(w.opts.messageProvider, w.timesource, w.log)
}
// New is used to instantiate a WakuNode using a set of WakuNodeOptions
@ -165,19 +168,15 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
params.libP2POpts = append(params.libP2POpts, libp2p.AddrsFactory(params.addressFactory))
}
host, err := libp2p.New(params.libP2POpts...)
if err != nil {
return nil, err
}
var err error
w := new(WakuNode)
w.bcaster = v2.NewBroadcaster(1024)
w.host = host
w.bcaster = relay.NewBroadcaster(1024)
w.opts = params
w.log = params.logger.Named("node2")
w.wg = &sync.WaitGroup{}
w.keepAliveFails = make(map[peer.ID]int)
w.wakuFlag = utils.NewWakuEnrBitfield(w.opts.enableLightPush, w.opts.enableFilter, w.opts.enableStore, w.opts.enableRelay)
w.wakuFlag = enr.NewWakuEnrBitfield(w.opts.enableLightPush, w.opts.enableLegacyFilter, w.opts.enableStore, w.opts.enableRelay)
if params.enableNTP {
w.timesource = timesource.NewNTPTimesource(w.opts.ntpURLs, w.log)
@ -195,7 +194,7 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
rngSrc := rand.NewSource(rand.Int63())
minBackoff, maxBackoff := time.Second*30, time.Hour
bkf := backoff.NewExponentialBackoff(minBackoff, maxBackoff, backoff.FullJitter, time.Second, 5.0, 0, rand.New(rngSrc))
w.peerConnector, err = v2.NewPeerConnectionStrategy(host, cacheSize, w.opts.discoveryMinPeers, network.DialPeerTimeout, bkf, w.log)
w.peerConnector, err = v2.NewPeerConnectionStrategy(cacheSize, w.opts.discoveryMinPeers, network.DialPeerTimeout, bkf, w.log)
if err != nil {
w.log.Error("creating peer connection strategy", zap.Error(err))
}
@ -207,7 +206,7 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
}
}
w.peerExchange, err = peer_exchange.NewWakuPeerExchange(w.host, w.DiscV5(), w.peerConnector, w.log)
w.peerExchange, err = peer_exchange.NewWakuPeerExchange(w.DiscV5(), w.peerConnector, w.log)
if err != nil {
return nil, err
}
@ -221,12 +220,12 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
rendezvousPoints = append(rendezvousPoints, peerID)
}
w.rendezvous = rendezvous.NewRendezvous(w.host, w.opts.enableRendezvousServer, w.opts.rendezvousDB, w.opts.enableRendezvous, rendezvousPoints, w.peerConnector, w.log)
w.relay = relay.NewWakuRelay(w.host, w.bcaster, w.opts.minRelayPeersToPublish, w.timesource, w.log, w.opts.wOpts...)
w.filter = filter.NewWakuFilter(w.host, w.bcaster, w.opts.isFilterFullNode, w.timesource, w.log, w.opts.filterOpts...)
w.filterV2Full = filterv2.NewWakuFilterFullnode(w.host, w.bcaster, w.timesource, w.log, w.opts.filterV2Opts...)
w.filterV2Light = filterv2.NewWakuFilterLightnode(w.host, w.bcaster, w.timesource, w.log)
w.lightPush = lightpush.NewWakuLightPush(w.host, w.Relay(), w.log)
w.rendezvous = rendezvous.NewRendezvous(w.opts.enableRendezvousServer, w.opts.rendezvousDB, w.opts.enableRendezvous, rendezvousPoints, w.peerConnector, w.log)
w.relay = relay.NewWakuRelay(w.bcaster, w.opts.minRelayPeersToPublish, w.timesource, w.log, w.opts.wOpts...)
w.legacyFilter = legacy_filter.NewWakuFilter(w.bcaster, w.opts.isLegacyFilterFullnode, w.timesource, w.log, w.opts.legacyFilterOpts...)
w.filterFullnode = filter.NewWakuFilterFullnode(w.timesource, w.log, w.opts.filterOpts...)
w.filterLightnode = filter.NewWakuFilterLightnode(w.bcaster, w.timesource, w.log)
w.lightPush = lightpush.NewWakuLightPush(w.Relay(), w.log)
if params.storeFactory != nil {
w.storeFactory = params.storeFactory
@ -234,18 +233,6 @@ func New(opts ...WakuNodeOption) (*WakuNode, error) {
w.storeFactory = defaultStoreFactory
}
if w.protocolEventSub, err = host.EventBus().Subscribe(new(event.EvtPeerProtocolsUpdated)); err != nil {
return nil, err
}
if w.identificationEventSub, err = host.EventBus().Subscribe(new(event.EvtPeerIdentificationCompleted)); err != nil {
return nil, err
}
if w.addressChangesSub, err = host.EventBus().Subscribe(new(event.EvtLocalAddressesUpdated)); err != nil {
return nil, err
}
if params.connStatusC != nil {
w.connStatusChan = params.connStatusC
}
@ -294,6 +281,25 @@ func (w *WakuNode) Start(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
w.cancel = cancel
host, err := libp2p.New(w.opts.libP2POpts...)
if err != nil {
return err
}
w.host = host
if w.protocolEventSub, err = host.EventBus().Subscribe(new(event.EvtPeerProtocolsUpdated)); err != nil {
return err
}
if w.identificationEventSub, err = host.EventBus().Subscribe(new(event.EvtPeerIdentificationCompleted)); err != nil {
return err
}
if w.addressChangesSub, err = host.EventBus().Subscribe(new(event.EvtLocalAddressesUpdated)); err != nil {
return err
}
w.connectionNotif = NewConnectionNotifier(ctx, w.host, w.log)
w.host.Network().Notify(w.connectionNotif)
@ -304,12 +310,18 @@ func (w *WakuNode) Start(ctx context.Context) error {
go w.watchMultiaddressChanges(ctx)
go w.watchENRChanges(ctx)
err = w.bcaster.Start(ctx)
if err != nil {
return err
}
if w.opts.keepAliveInterval > time.Duration(0) {
w.wg.Add(1)
go w.startKeepAlive(ctx, w.opts.keepAliveInterval)
}
err := w.peerConnector.Start(ctx)
w.peerConnector.SetHost(host)
err = w.peerConnector.Start(ctx)
if err != nil {
return err
}
@ -321,6 +333,7 @@ func (w *WakuNode) Start(ctx context.Context) error {
}
}
w.relay.SetHost(host)
if w.opts.enableRelay {
err := w.relay.Start(ctx)
if err != nil {
@ -332,50 +345,52 @@ func (w *WakuNode) Start(ctx context.Context) error {
if err != nil {
return err
}
w.Broadcaster().Unregister(&relay.DefaultWakuTopic, sub.C)
sub.Unsubscribe()
}
}
w.store = w.storeFactory(w)
w.store.SetHost(host)
if w.opts.enableStore {
err := w.startStore(ctx)
sub := w.bcaster.RegisterForAll()
err := w.startStore(ctx, sub)
if err != nil {
return err
}
w.log.Info("Subscribing store to broadcaster")
w.bcaster.Register(nil, w.store.MessageChannel())
}
w.lightPush.SetHost(host)
if w.opts.enableLightPush {
if err := w.lightPush.Start(ctx); err != nil {
return err
}
}
if w.opts.enableFilter {
err := w.filter.Start(ctx)
w.legacyFilter.SetHost(host)
if w.opts.enableLegacyFilter {
sub := w.bcaster.RegisterForAll()
err := w.legacyFilter.Start(ctx, sub)
if err != nil {
return err
}
w.log.Info("Subscribing filter to broadcaster")
w.bcaster.Register(nil, w.filter.MessageChannel())
}
if w.opts.enableFilterV2FullNode {
err := w.filterV2Full.Start(ctx)
w.filterFullnode.SetHost(host)
if w.opts.enableFilterFullNode {
sub := w.bcaster.RegisterForAll()
err := w.filterFullnode.Start(ctx, sub)
if err != nil {
return err
}
w.log.Info("Subscribing filterV2 to broadcaster")
w.bcaster.Register(nil, w.filterV2Full.MessageChannel())
}
if w.opts.enableFilterV2LightNode {
err := w.filterV2Light.Start(ctx)
w.filterLightnode.SetHost(host)
if w.opts.enableFilterLightNode {
err := w.filterLightnode.Start(ctx)
if err != nil {
return err
}
@ -386,6 +401,7 @@ func (w *WakuNode) Start(ctx context.Context) error {
return err
}
w.peerExchange.SetHost(host)
if w.opts.enablePeerExchange {
err := w.peerExchange.Start(ctx)
if err != nil {
@ -393,6 +409,7 @@ func (w *WakuNode) Start(ctx context.Context) error {
}
}
w.rendezvous.SetHost(host)
if w.opts.enableRendezvousServer || w.opts.enableRendezvous {
err := w.rendezvous.Start(ctx)
if err != nil {
@ -416,9 +433,7 @@ func (w *WakuNode) Stop() {
return
}
w.cancel()
w.bcaster.Close()
w.bcaster.Stop()
defer w.connectionNotif.Close()
defer w.protocolEventSub.Close()
@ -432,8 +447,8 @@ func (w *WakuNode) Stop() {
w.relay.Stop()
w.lightPush.Stop()
w.store.Stop()
w.filter.Stop()
w.filterV2Full.Stop()
w.legacyFilter.Stop()
w.filterFullnode.Stop()
w.peerExchange.Stop()
if w.opts.enableDiscV5 {
@ -448,9 +463,13 @@ func (w *WakuNode) Stop() {
w.host.Close()
w.cancel()
w.wg.Wait()
close(w.enrChangeCh)
w.cancel = nil
}
// Host returns the libp2p Host used by the WakuNode
@ -521,17 +540,25 @@ func (w *WakuNode) Store() store.Store {
return w.store.(store.Store)
}
// Filter is used to access any operation related to Waku Filter protocol
func (w *WakuNode) Filter() *filter.WakuFilter {
if result, ok := w.filter.(*filter.WakuFilter); ok {
// LegacyFilter is used to access any operation related to Waku LegacyFilter protocol
func (w *WakuNode) LegacyFilter() *legacy_filter.WakuFilter {
if result, ok := w.legacyFilter.(*legacy_filter.WakuFilter); ok {
return result
}
return nil
}
// FilterV2 is used to access any operation related to Waku Filter protocol
func (w *WakuNode) FilterV2() *filterv2.WakuFilterLightnode {
if result, ok := w.filterV2Light.(*filterv2.WakuFilterLightnode); ok {
// FilterLightnode is used to access any operation related to Waku Filter protocol Full node feature
func (w *WakuNode) FilterFullnode() *filter.WakuFilterFullNode {
if result, ok := w.filterFullnode.(*filter.WakuFilterFullNode); ok {
return result
}
return nil
}
// FilterFullnode is used to access any operation related to Waku Filter protocol Light node feature
func (w *WakuNode) FilterLightnode() *filter.WakuFilterLightnode {
if result, ok := w.filterLightnode.(*filter.WakuFilterLightnode); ok {
return result
}
return nil
@ -563,7 +590,7 @@ func (w *WakuNode) PeerExchange() *peer_exchange.WakuPeerExchange {
// Broadcaster is used to access the message broadcaster that is used to push
// messages to different protocols
func (w *WakuNode) Broadcaster() v2.Broadcaster {
func (w *WakuNode) Broadcaster() relay.Broadcaster {
return w.bcaster
}
@ -607,13 +634,13 @@ func (w *WakuNode) mountDiscV5() error {
}
var err error
w.discoveryV5, err = discv5.NewDiscoveryV5(w.Host(), w.opts.privKey, w.localNode, w.peerConnector, w.log, discV5Options...)
w.discoveryV5, err = discv5.NewDiscoveryV5(w.opts.privKey, w.localNode, w.peerConnector, w.log, discV5Options...)
return err
}
func (w *WakuNode) startStore(ctx context.Context) error {
err := w.store.Start(ctx)
func (w *WakuNode) startStore(ctx context.Context, sub relay.Subscription) error {
err := w.store.Start(ctx, sub)
if err != nil {
w.log.Error("starting store", zap.Error(err))
return err

View File

@ -6,9 +6,9 @@ package node
import (
"bytes"
"context"
"encoding/hex"
"errors"
"github.com/waku-org/go-waku/waku/v2/protocol/rln"
"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"
r "github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
@ -39,33 +39,53 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error {
return errors.New("relay protocol does not support the configured pubsub topic")
}
var err error
var groupManager rln.GroupManager
if !w.opts.rlnRelayDynamic {
w.log.Info("setting up waku-rln-relay in off-chain mode")
// set up rln relay inputs
groupKeys, idCredential, err := static.Setup(w.opts.rlnRelayMemIndex)
if err != nil {
return err
}
// rlnrelay in off-chain mode with a static group of user
groupManager, err := static.NewStaticGroupManager(groupKeys, idCredential, w.opts.rlnRelayMemIndex, w.log)
groupManager, err = static.NewStaticGroupManager(groupKeys, idCredential, w.opts.rlnRelayMemIndex, w.log)
if err != nil {
return err
}
} else {
w.log.Info("setting up waku-rln-relay in on-chain mode")
rlnRelay, err := rln.New(w.Relay(), groupManager, w.opts.rlnRelayPubsubTopic, w.opts.rlnRelayContentTopic, w.opts.rlnSpamHandler, w.timesource, w.log)
groupManager, err = dynamic.NewDynamicGroupManager(
w.opts.rlnETHClientAddress,
w.opts.rlnETHPrivateKey,
w.opts.rlnMembershipContractAddress,
w.opts.keystorePath,
w.opts.keystorePassword,
true,
w.opts.rlnRegistrationHandler,
w.log,
)
if err != nil {
return err
}
}
err = rlnRelay.Start(ctx)
if err != nil {
return err
}
rlnRelay, err := rln.New(w.Relay(), groupManager, w.opts.rlnRelayPubsubTopic, w.opts.rlnRelayContentTopic, w.opts.rlnSpamHandler, w.timesource, w.log)
if err != nil {
return err
}
w.rlnRelay = rlnRelay
err = rlnRelay.Start(ctx)
if err != nil {
return err
}
w.rlnRelay = rlnRelay
if !w.opts.rlnRelayDynamic {
// check the correct construction of the tree by comparing the calculated root against the expected root
// no error should happen as it is already captured in the unit tests
root, err := rlnRelay.RLN.GetMerkleRoot()
@ -81,26 +101,6 @@ func (w *WakuNode) mountRlnRelay(ctx context.Context) error {
if !bytes.Equal(expectedRoot[:], root[:]) {
return errors.New("root mismatch: something went wrong not in Merkle tree construction")
}
w.log.Debug("the calculated root", zap.String("root", hex.EncodeToString(root[:])))
} else {
w.log.Info("setting up waku-rln-relay in on-chain mode")
/*// check if the peer has provided its rln credentials
var memKeyPair *r.IdentityCredential
if w.opts.rlnRelayIDCommitment != nil && w.opts.rlnRelayIDKey != nil {
memKeyPair = &r.IdentityCredential{
IDCommitment: *w.opts.rlnRelayIDCommitment,
IDSecretHash: *w.opts.rlnRelayIDKey,
}
}
// mount the rln relay protocol in the on-chain/dynamic mode
var err error
w.rlnRelay, err = rln.RlnRelayDynamic(ctx, w.Relay(), w.opts.rlnETHClientAddress, w.opts.rlnETHPrivateKey, w.opts.rlnMembershipContractAddress, memKeyPair, w.opts.rlnRelayMemIndex, w.opts.rlnRelayPubsubTopic, w.opts.rlnRelayContentTopic, w.opts.rlnSpamHandler, w.opts.rlnRegistrationHandler, w.timesource, w.log)
if err != nil {
return err
}*/
}
w.log.Info("mounted waku RLN relay", zap.String("pubsubTopic", w.opts.rlnRelayPubsubTopic), zap.String("contentTopic", w.opts.rlnRelayContentTopic))

View File

@ -22,11 +22,12 @@ import (
"github.com/libp2p/go-libp2p/p2p/net/connmgr"
quic "github.com/libp2p/go-libp2p/p2p/transport/quic"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
"github.com/multiformats/go-multiaddr"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/filterv2"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
"github.com/waku-org/go-waku/waku/v2/rendezvous"
@ -63,15 +64,15 @@ type WakuNodeParameters struct {
logger *zap.Logger
logLevel logging.LogLevel
noDefaultWakuTopic bool
enableRelay bool
enableFilter bool
isFilterFullNode bool
enableFilterV2LightNode bool
enableFilterV2FullNode bool
filterOpts []filter.Option
filterV2Opts []filterv2.Option
wOpts []pubsub.Option
noDefaultWakuTopic bool
enableRelay bool
enableLegacyFilter bool
isLegacyFilterFullnode bool
enableFilterLightNode bool
enableFilterFullNode bool
legacyFilterOpts []legacy_filter.Option
filterOpts []filter.Option
wOpts []pubsub.Option
minRelayPeersToPublish int
@ -100,10 +101,10 @@ type WakuNodeParameters struct {
rlnRelayContentTopic string
rlnRelayDynamic bool
rlnSpamHandler func(message *pb.WakuMessage) error
rlnRelayIDKey *[32]byte
rlnRelayIDCommitment *[32]byte
rlnETHPrivateKey *ecdsa.PrivateKey
rlnETHClientAddress string
keystorePath string
keystorePassword string
rlnMembershipContractAddress common.Address
rlnRegistrationHandler func(tx *types.Transaction)
@ -219,6 +220,31 @@ func WithAdvertiseAddresses(advertiseAddrs ...ma.Multiaddr) WakuNodeOption {
}
}
// WithExternalIP is a WakuNodeOption that allows overriding the advertised external IP used in the waku node with custom value
func WithExternalIP(ip net.IP) WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.addressFactory = func(inputAddr []multiaddr.Multiaddr) (addresses []multiaddr.Multiaddr) {
component := "/ip4/"
if ip.To4() == nil && ip.To16() != nil {
component = "/ip6/"
}
hostAddrMA, err := multiaddr.NewMultiaddr(component + ip.String())
if err != nil {
panic("Could not build external IP")
}
for _, addr := range inputAddr {
_, rest := multiaddr.SplitFirst(addr)
addresses = append(addresses, hostAddrMA.Encapsulate(rest))
}
return addresses
}
return nil
}
}
// WithMultiaddress is a WakuNodeOption that configures libp2p to listen on a list of multiaddresses
func WithMultiaddress(addresses []multiaddr.Multiaddr) WakuNodeOption {
return func(params *WakuNodeParameters) error {
@ -318,31 +344,31 @@ func WithPeerExchange() WakuNodeOption {
}
}
// WithWakuFilter enables the Waku Filter protocol. This WakuNodeOption
// WithLegacyWakuFilter enables the legacy Waku Filter protocol. This WakuNodeOption
// accepts a list of WakuFilter gossipsub options to setup the protocol
func WithWakuFilter(fullNode bool, filterOpts ...filter.Option) WakuNodeOption {
func WithLegacyWakuFilter(fullnode bool, filterOpts ...legacy_filter.Option) WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.enableFilter = true
params.isFilterFullNode = fullNode
params.filterOpts = filterOpts
params.enableLegacyFilter = true
params.isLegacyFilterFullnode = fullnode
params.legacyFilterOpts = filterOpts
return nil
}
}
// WithWakuFilterV2 enables the Waku Filter V2 protocol for lightnode functionality
func WithWakuFilterV2LightNode() WakuNodeOption {
// WithWakuFilter enables the Waku Filter V2 protocol for lightnode functionality
func WithWakuFilterLightNode() WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.enableFilterV2LightNode = true
params.enableFilterLightNode = true
return nil
}
}
// WithWakuFilterV2FullNode enables the Waku Filter V2 protocol full node functionality.
// WithWakuFilterFullNode enables the Waku Filter V2 protocol full node functionality.
// This WakuNodeOption accepts a list of WakuFilter options to setup the protocol
func WithWakuFilterV2FullNode(filterOpts ...filterv2.Option) WakuNodeOption {
func WithWakuFilterFullNode(filterOpts ...filter.Option) WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.enableFilterV2FullNode = true
params.filterV2Opts = filterOpts
params.enableFilterFullNode = true
params.filterOpts = filterOpts
return nil
}
}
@ -473,6 +499,7 @@ var DefaultLibP2POptions = []libp2p.Option{
libp2p.ChainOptions(
libp2p.Transport(tcp.NewTCPTransport),
libp2p.Transport(quic.NewTransport),
libp2p.Transport(libp2pwebtransport.New),
),
libp2p.UserAgent(userAgent),
libp2p.ChainOptions(

View File

@ -25,29 +25,20 @@ func WithStaticRLNRelay(pubsubTopic string, contentTopic string, memberIndex r.M
}
}
type MembershipCredentials struct {
Contract common.Address `json:"contract"`
Keypair *r.MembershipKeyPair `json:"membershipKeyPair"`
Index r.MembershipIndex `json:"rlnIndex"`
}
// WithStaticRLNRelay 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(pubsubTopic string, contentTopic string, membershipCredentials MembershipCredentials, spamHandler rln.SpamHandler, ethClientAddress string, ethPrivateKey *ecdsa.PrivateKey, registrationHandler rln.RegistrationHandler) WakuNodeOption {
func WithDynamicRLNRelay(pubsubTopic string, contentTopic string, keystorePath string, keystorePassword string, membershipContract common.Address, spamHandler rln.SpamHandler, ethClientAddress string, ethPrivateKey *ecdsa.PrivateKey, registrationHandler rln.RegistrationHandler) WakuNodeOption {
return func(params *WakuNodeParameters) error {
params.enableRLN = true
params.rlnRelayDynamic = true
params.rlnRelayMemIndex = membershipCredentials.Index
if membershipCredentials.Keypair != nil {
params.rlnRelayIDKey = &membershipCredentials.Keypair.IDKey
params.rlnRelayIDCommitment = &membershipCredentials.Keypair.IDCommitment
}
params.keystorePassword = keystorePassword
params.keystorePath = keystorePath
params.rlnRelayPubsubTopic = pubsubTopic
params.rlnRelayContentTopic = contentTopic
params.rlnSpamHandler = spamHandler
params.rlnETHClientAddress = ethClientAddress
params.rlnETHPrivateKey = ethPrivateKey
params.rlnMembershipContractAddress = membershipCredentials.Contract
params.rlnMembershipContractAddress = membershipContract
params.rlnRegistrationHandler = registrationHandler
return nil
}

View File

@ -1,4 +1,4 @@
package utils
package enr
import (
"encoding/binary"
@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/waku/v2/utils"
)
// WakuENRField is the name of the ENR field that contains information about which protocols are supported by the node
@ -18,6 +19,10 @@ const WakuENRField = "waku2"
// already available ENR fields (i.e. in the case of websocket connections)
const MultiaddrENRField = "multiaddrs"
const ShardingIndicesListEnrField = "rs"
const ShardingBitVectorEnrField = "rsv"
// WakuEnrBitfield is a8-bit flag field to indicate Waku capabilities. Only the 4 LSBs are currently defined according to RFC31 (https://rfc.vac.dev/spec/31/).
type WakuEnrBitfield = uint8
@ -46,7 +51,7 @@ func NewWakuEnrBitfield(lightpush, filter, store, relay bool) WakuEnrBitfield {
// EnodeToMultiaddress converts an enode into a multiaddress
func enodeToMultiAddr(node *enode.Node) (multiaddr.Multiaddr, error) {
pubKey := EcdsaPubKeyToSecp256k1PublicKey(node.Pubkey())
pubKey := utils.EcdsaPubKeyToSecp256k1PublicKey(node.Pubkey())
peerID, err := peer.IDFromPublicKey(pubKey)
if err != nil {
return nil, err
@ -57,7 +62,7 @@ func enodeToMultiAddr(node *enode.Node) (multiaddr.Multiaddr, error) {
// Multiaddress is used to extract all the multiaddresses that are part of a ENR record
func Multiaddress(node *enode.Node) (peer.ID, []multiaddr.Multiaddr, error) {
pubKey := EcdsaPubKeyToSecp256k1PublicKey(node.Pubkey())
pubKey := utils.EcdsaPubKeyToSecp256k1PublicKey(node.Pubkey())
peerID, err := peer.IDFromPublicKey(pubKey)
if err != nil {
return "", nil, err

View File

@ -0,0 +1,104 @@
package enr
import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/waku-org/go-waku/waku/v2/protocol"
)
func SetWakuRelayShardingIndicesList(localnode *enode.LocalNode, rs protocol.RelayShards) error {
value, err := rs.IndicesList()
if err != nil {
return err
}
localnode.Set(enr.WithEntry(ShardingIndicesListEnrField, value))
return nil
}
func SetWakuRelayShardingBitVector(localnode *enode.LocalNode, rs protocol.RelayShards) error {
localnode.Set(enr.WithEntry(ShardingBitVectorEnrField, rs.BitVector()))
return nil
}
func SetWakuRelaySharding(localnode *enode.LocalNode, rs protocol.RelayShards) error {
if len(rs.Indices) >= 64 {
return SetWakuRelayShardingBitVector(localnode, rs)
} else {
return SetWakuRelayShardingIndicesList(localnode, rs)
}
}
// ENR record accessors
func RelayShardingIndicesList(localnode *enode.LocalNode) (*protocol.RelayShards, error) {
var field []byte
if err := localnode.Node().Record().Load(enr.WithEntry(ShardingIndicesListEnrField, field)); err != nil {
return nil, nil
}
res, err := protocol.FromIndicesList(field)
if err != nil {
return nil, err
}
return &res, nil
}
func RelayShardingBitVector(localnode *enode.LocalNode) (*protocol.RelayShards, error) {
var field []byte
if err := localnode.Node().Record().Load(enr.WithEntry(ShardingBitVectorEnrField, field)); err != nil {
return nil, nil
}
res, err := protocol.FromBitVector(field)
if err != nil {
return nil, err
}
return &res, nil
}
func RelaySharding(localnode *enode.LocalNode) (*protocol.RelayShards, error) {
res, err := RelayShardingIndicesList(localnode)
if err != nil {
return nil, err
}
if res != nil {
return res, nil
}
return RelayShardingBitVector(localnode)
}
// Utils
func ContainsShard(localnode *enode.LocalNode, cluster uint16, index uint16) bool {
if index > protocol.MaxShardIndex {
return false
}
rs, err := RelaySharding(localnode)
if err != nil {
return false
}
return rs.Contains(cluster, index)
}
func ContainsShardWithNsTopic(localnode *enode.LocalNode, topic protocol.NamespacedPubsubTopic) bool {
if topic.Kind() != protocol.StaticSharding {
return false
}
shardTopic := topic.(protocol.StaticShardingPubsubTopic)
return ContainsShard(localnode, shardTopic.Cluster(), shardTopic.Shard())
}
func ContainsShardTopic(localnode *enode.LocalNode, topic string) bool {
shardTopic, err := protocol.ToShardedPubsubTopic(topic)
if err != nil {
return false
}
return ContainsShardWithNsTopic(localnode, shardTopic)
}

View File

@ -1,4 +1,4 @@
package filterv2
package filter
import (
"context"
@ -15,11 +15,11 @@ import (
libp2pProtocol "github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-msgio/pbio"
"github.com/waku-org/go-waku/logging"
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filterv2/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/filter/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/timesource"
"go.opencensus.io/tag"
"go.uber.org/zap"
@ -37,7 +37,7 @@ type WakuFilterLightnode struct {
cancel context.CancelFunc
ctx context.Context
h host.Host
broadcaster v2.Broadcaster
broadcaster relay.Broadcaster
timesource timesource.Timesource
wg *sync.WaitGroup
log *zap.Logger
@ -55,17 +55,21 @@ type WakuFilterPushResult struct {
}
// NewWakuRelay returns a new instance of Waku Filter struct setup according to the chosen parameter and options
func NewWakuFilterLightnode(host host.Host, broadcaster v2.Broadcaster, timesource timesource.Timesource, log *zap.Logger) *WakuFilterLightnode {
func NewWakuFilterLightnode(broadcaster relay.Broadcaster, timesource timesource.Timesource, log *zap.Logger) *WakuFilterLightnode {
wf := new(WakuFilterLightnode)
wf.log = log.Named("filterv2-lightnode")
wf.broadcaster = broadcaster
wf.timesource = timesource
wf.wg = &sync.WaitGroup{}
wf.h = host
return wf
}
// Sets the host to be able to mount or consume a protocol
func (wf *WakuFilterLightnode) SetHost(h host.Host) {
wf.h = h
}
func (wf *WakuFilterLightnode) Start(ctx context.Context) error {
wf.wg.Wait() // Wait for any goroutines to stop
@ -78,11 +82,11 @@ func (wf *WakuFilterLightnode) Start(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
wf.cancel = cancel
wf.ctx = ctx
wf.subscriptions = NewSubscriptionMap()
wf.subscriptions = NewSubscriptionMap(wf.log)
wf.h.SetStreamHandlerMatch(FilterPushID_v20beta1, protocol.PrefixTextMatch(string(FilterPushID_v20beta1)), wf.onRequest(ctx))
wf.log.Info("filter protocol (light) started")
wf.log.Info("filter-push protocol started")
return nil
}
@ -109,15 +113,30 @@ func (wf *WakuFilterLightnode) onRequest(ctx context.Context) func(s network.Str
defer s.Close()
logger := wf.log.With(logging.HostID("peer", s.Conn().RemotePeer()))
if !wf.subscriptions.IsSubscribedTo(s.Conn().RemotePeer()) {
logger.Warn("received message push from unknown peer", logging.HostID("peerID", s.Conn().RemotePeer()))
metrics.RecordFilterError(ctx, "unknown_peer_messagepush")
return
}
reader := pbio.NewDelimitedReader(s, math.MaxInt32)
messagePush := &pb.MessagePushV2{}
err := reader.ReadMsg(messagePush)
if err != nil {
logger.Error("reading message push", zap.Error(err))
metrics.RecordFilterError(ctx, "decode_rpc_failure")
return
}
if !wf.subscriptions.Has(s.Conn().RemotePeer(), messagePush.PubsubTopic, messagePush.WakuMessage.ContentTopic) {
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))
metrics.RecordFilterError(ctx, "invalid_subscription_message")
return
}
metrics.RecordFilterMessage(ctx, "PushMessage", 1)
wf.notify(s.Conn().RemotePeer(), messagePush.PubsubTopic, messagePush.WakuMessage)
logger.Info("received message push")
@ -137,12 +156,14 @@ func (wf *WakuFilterLightnode) notify(remotePeerID peer.ID, pubsubTopic string,
func (wf *WakuFilterLightnode) request(ctx context.Context, params *FilterSubscribeParameters, reqType pb.FilterSubscribeRequest_FilterSubscribeType, contentFilter ContentFilter) error {
err := wf.h.Connect(ctx, wf.h.Peerstore().PeerInfo(params.selectedPeer))
if err != nil {
metrics.RecordFilterError(ctx, "dial_failure")
return err
}
var conn network.Stream
conn, err = wf.h.NewStream(ctx, params.selectedPeer, FilterSubscribeID_v20beta1)
if err != nil {
metrics.RecordFilterError(ctx, "dial_failure")
return err
}
defer conn.Close()
@ -160,6 +181,7 @@ func (wf *WakuFilterLightnode) request(ctx context.Context, params *FilterSubscr
wf.log.Debug("sending FilterSubscribeRequest", zap.Stringer("request", request))
err = writer.WriteMsg(request)
if err != nil {
metrics.RecordFilterError(ctx, "write_request_failure")
wf.log.Error("sending FilterSubscribeRequest", zap.Error(err))
return err
}
@ -168,10 +190,19 @@ func (wf *WakuFilterLightnode) request(ctx context.Context, params *FilterSubscr
err = reader.ReadMsg(filterSubscribeResponse)
if err != nil {
wf.log.Error("receiving FilterSubscribeResponse", zap.Error(err))
metrics.RecordFilterError(ctx, "decode_rpc_failure")
return err
}
if filterSubscribeResponse.RequestId != request.RequestId {
wf.log.Error("requestId mismatch", zap.String("expected", request.RequestId), zap.String("received", filterSubscribeResponse.RequestId))
metrics.RecordFilterError(ctx, "request_id_mismatch")
err := NewFilterError(300, "request_id_mismatch")
return &err
}
if filterSubscribeResponse.StatusCode != http.StatusOK {
metrics.RecordFilterError(ctx, "error_response")
err := NewFilterError(int(filterSubscribeResponse.StatusCode), filterSubscribeResponse.StatusDesc)
return &err
}
@ -204,6 +235,7 @@ func (wf *WakuFilterLightnode) Subscribe(ctx context.Context, contentFilter Cont
}
if params.selectedPeer == "" {
metrics.RecordFilterError(ctx, "peer_not_found_failure")
return nil, ErrNoPeersAvailable
}
@ -217,7 +249,7 @@ func (wf *WakuFilterLightnode) Subscribe(ctx context.Context, contentFilter Cont
// 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) {
if !wf.subscriptions.Has(peerID, contentFilter.Topic, contentFilter.ContentTopics) {
if !wf.subscriptions.Has(peerID, contentFilter.Topic, contentFilter.ContentTopics...) {
return nil, errors.New("subscription does not exist")
}
@ -247,7 +279,24 @@ func (wf *WakuFilterLightnode) Ping(ctx context.Context, peerID peer.ID) error {
}
func (wf *WakuFilterLightnode) IsSubscriptionAlive(ctx context.Context, subscription *SubscriptionDetails) error {
return wf.Ping(ctx, subscription.peerID)
return wf.Ping(ctx, subscription.PeerID)
}
func (wf *WakuFilterLightnode) Subscriptions() []*SubscriptionDetails {
wf.subscriptions.RLock()
defer wf.subscriptions.RUnlock()
var output []*SubscriptionDetails
for _, peerSubscription := range wf.subscriptions.items {
for _, subscriptionPerTopic := range peerSubscription.subscriptionsPerTopic {
for _, subscriptionDetail := range subscriptionPerTopic {
output = append(output, subscriptionDetail)
}
}
}
return output
}
// Unsubscribe is used to stop receiving messages from a peer that match a content filter
@ -305,13 +354,13 @@ func (wf *WakuFilterLightnode) Unsubscribe(ctx context.Context, contentFilter Co
// Unsubscribe is used to stop receiving messages from a peer that match a content filter
func (wf *WakuFilterLightnode) UnsubscribeWithSubscription(ctx context.Context, sub *SubscriptionDetails, opts ...FilterUnsubscribeOption) (<-chan WakuFilterPushResult, error) {
var contentTopics []string
for k := range sub.contentTopics {
for k := range sub.ContentTopics {
contentTopics = append(contentTopics, k)
}
opts = append(opts, Peer(sub.peerID))
opts = append(opts, Peer(sub.PeerID))
return wf.Unsubscribe(ctx, ContentFilter{Topic: sub.pubsubTopic, ContentTopics: contentTopics}, opts...)
return wf.Unsubscribe(ctx, ContentFilter{Topic: sub.PubsubTopic, ContentTopics: contentTopics}, opts...)
}
// UnsubscribeAll is used to stop receiving messages from peer(s). It does not close subscriptions

View File

@ -1,4 +1,4 @@
package filterv2
package filter
import (
"fmt"

View File

@ -1,4 +1,4 @@
package filterv2
package filter
import (
"context"

View File

@ -1,3 +1,3 @@
package pb
//go:generate protoc -I./../../pb/. -I. --go_opt=paths=source_relative --go_opt=Mwaku_filter.proto=github.com/waku-org/go-waku/waku/v2/protocol/filter/pb --go_opt=Mwaku_message.proto=github.com/waku-org/go-waku/waku/v2/protocol/pb --go_out=. ./waku_filter.proto
//go:generate protoc -I./../../pb/. -I. --go_opt=paths=source_relative --go_opt=Mwaku_filter_v2.proto=github.com/waku-org/go-waku/waku/v2/protocol/filter/pb --go_opt=Mwaku_message.proto=github.com/waku-org/go-waku/waku/v2/protocol/pb --go_out=. ./waku_filter_v2.proto

View File

@ -1,4 +1,4 @@
package filterv2
package filter
import (
"context"
@ -7,6 +7,7 @@ import (
"math"
"net/http"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
@ -14,11 +15,12 @@ import (
libp2pProtocol "github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-msgio/pbio"
"github.com/waku-org/go-waku/logging"
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filterv2/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/filter/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/timesource"
"go.opencensus.io/stats"
"go.opencensus.io/tag"
"go.uber.org/zap"
)
@ -30,10 +32,10 @@ const FilterSubscribeID_v20beta1 = libp2pProtocol.ID("/vac/waku/filter-subscribe
const peerHasNoSubscription = "peer has no subscriptions"
type (
WakuFilterFull struct {
WakuFilterFullNode struct {
cancel context.CancelFunc
h host.Host
msgC chan *protocol.Envelope
msgSub relay.Subscription
wg *sync.WaitGroup
log *zap.Logger
@ -44,8 +46,8 @@ type (
)
// NewWakuFilterFullnode returns a new instance of Waku Filter struct setup according to the chosen parameter and options
func NewWakuFilterFullnode(host host.Host, broadcaster v2.Broadcaster, timesource timesource.Timesource, log *zap.Logger, opts ...Option) *WakuFilterFull {
wf := new(WakuFilterFull)
func NewWakuFilterFullnode(timesource timesource.Timesource, log *zap.Logger, opts ...Option) *WakuFilterFullNode {
wf := new(WakuFilterFullNode)
wf.log = log.Named("filterv2-fullnode")
params := new(FilterParameters)
@ -56,14 +58,18 @@ func NewWakuFilterFullnode(host host.Host, broadcaster v2.Broadcaster, timesourc
}
wf.wg = &sync.WaitGroup{}
wf.h = host
wf.subscriptions = NewSubscribersMap(params.Timeout)
wf.maxSubscriptions = params.MaxSubscribers
return wf
}
func (wf *WakuFilterFull) Start(ctx context.Context) error {
// Sets the host to be able to mount or consume a protocol
func (wf *WakuFilterFullNode) SetHost(h host.Host) {
wf.h = h
}
func (wf *WakuFilterFullNode) Start(ctx context.Context, sub relay.Subscription) error {
wf.wg.Wait() // Wait for any goroutines to stop
ctx, err := tag.New(ctx, tag.Insert(metrics.KeyType, "filter"))
@ -77,17 +83,16 @@ func (wf *WakuFilterFull) Start(ctx context.Context) error {
wf.h.SetStreamHandlerMatch(FilterSubscribeID_v20beta1, protocol.PrefixTextMatch(string(FilterSubscribeID_v20beta1)), wf.onRequest(ctx))
wf.cancel = cancel
wf.msgC = make(chan *protocol.Envelope, 1024)
wf.msgSub = sub
wf.wg.Add(1)
go wf.filterListener(ctx)
wf.log.Info("filter protocol (full) started")
wf.log.Info("filter-subscriber protocol started")
return nil
}
func (wf *WakuFilterFull) onRequest(ctx context.Context) func(s network.Stream) {
func (wf *WakuFilterFullNode) onRequest(ctx context.Context) func(s network.Stream) {
return func(s network.Stream) {
defer s.Close()
logger := wf.log.With(logging.HostID("peer", s.Conn().RemotePeer()))
@ -97,28 +102,33 @@ func (wf *WakuFilterFull) onRequest(ctx context.Context) func(s network.Stream)
subscribeRequest := &pb.FilterSubscribeRequest{}
err := reader.ReadMsg(subscribeRequest)
if err != nil {
metrics.RecordFilterError(ctx, "decode_rpc_failure")
logger.Error("reading request", zap.Error(err))
return
}
logger = logger.With(zap.String("requestID", subscribeRequest.RequestId))
start := time.Now()
switch subscribeRequest.FilterSubscribeType {
case pb.FilterSubscribeRequest_SUBSCRIBE:
wf.subscribe(s, logger, subscribeRequest)
wf.subscribe(ctx, s, logger, subscribeRequest)
case pb.FilterSubscribeRequest_SUBSCRIBER_PING:
wf.ping(s, logger, subscribeRequest)
wf.ping(ctx, s, logger, subscribeRequest)
case pb.FilterSubscribeRequest_UNSUBSCRIBE:
wf.unsubscribe(s, logger, subscribeRequest)
wf.unsubscribe(ctx, s, logger, subscribeRequest)
case pb.FilterSubscribeRequest_UNSUBSCRIBE_ALL:
wf.unsubscribeAll(s, logger, subscribeRequest)
wf.unsubscribeAll(ctx, s, logger, subscribeRequest)
}
metrics.RecordFilterRequest(ctx, subscribeRequest.FilterSubscribeType.String(), time.Since(start))
logger.Info("received request")
}
}
func reply(s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest, statusCode int, description ...string) {
func reply(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest, statusCode int, description ...string) {
response := &pb.FilterSubscribeResponse{
RequestId: request.RequestId,
StatusCode: uint32(statusCode),
@ -133,37 +143,38 @@ func reply(s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequ
writer := pbio.NewDelimitedWriter(s)
err := writer.WriteMsg(response)
if err != nil {
metrics.RecordFilterError(ctx, "write_response_failure")
logger.Error("sending response", zap.Error(err))
}
}
func (wf *WakuFilterFull) ping(s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
func (wf *WakuFilterFullNode) ping(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
exists := wf.subscriptions.Has(s.Conn().RemotePeer())
if exists {
reply(s, logger, request, http.StatusOK)
reply(ctx, s, logger, request, http.StatusOK)
} else {
reply(s, logger, request, http.StatusNotFound, peerHasNoSubscription)
reply(ctx, s, logger, request, http.StatusNotFound, peerHasNoSubscription)
}
}
func (wf *WakuFilterFull) subscribe(s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
func (wf *WakuFilterFullNode) subscribe(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
if request.PubsubTopic == "" {
reply(s, logger, request, http.StatusBadRequest, "pubsubtopic can't be empty")
reply(ctx, s, logger, request, http.StatusBadRequest, "pubsubtopic can't be empty")
return
}
if len(request.ContentTopics) == 0 {
reply(s, logger, request, http.StatusBadRequest, "at least one contenttopic should be specified")
reply(ctx, s, logger, request, http.StatusBadRequest, "at least one contenttopic should be specified")
return
}
if len(request.ContentTopics) > MaxContentTopicsPerRequest {
reply(s, logger, request, http.StatusBadRequest, fmt.Sprintf("exceeds maximum content topics: %d", MaxContentTopicsPerRequest))
reply(ctx, s, logger, request, http.StatusBadRequest, fmt.Sprintf("exceeds maximum content topics: %d", MaxContentTopicsPerRequest))
}
if wf.subscriptions.Count() >= wf.maxSubscriptions {
reply(s, logger, request, http.StatusServiceUnavailable, "node has reached maximum number of subscriptions")
reply(ctx, s, logger, request, http.StatusServiceUnavailable, "node has reached maximum number of subscriptions")
return
}
@ -176,49 +187,53 @@ func (wf *WakuFilterFull) subscribe(s network.Stream, logger *zap.Logger, reques
}
if ctTotal+len(request.ContentTopics) > MaxCriteriaPerSubscription {
reply(s, logger, request, http.StatusServiceUnavailable, "peer has reached maximum number of filter criteria")
reply(ctx, s, logger, request, http.StatusServiceUnavailable, "peer has reached maximum number of filter criteria")
return
}
}
wf.subscriptions.Set(peerID, request.PubsubTopic, request.ContentTopics)
reply(s, logger, request, http.StatusOK)
stats.Record(ctx, metrics.FilterSubscriptions.M(int64(wf.subscriptions.Count())))
reply(ctx, s, logger, request, http.StatusOK)
}
func (wf *WakuFilterFull) unsubscribe(s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
func (wf *WakuFilterFullNode) unsubscribe(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
if request.PubsubTopic == "" {
reply(s, logger, request, http.StatusBadRequest, "pubsubtopic can't be empty")
reply(ctx, s, logger, request, http.StatusBadRequest, "pubsubtopic can't be empty")
return
}
if len(request.ContentTopics) == 0 {
reply(s, logger, request, http.StatusBadRequest, "at least one contenttopic should be specified")
reply(ctx, s, logger, request, http.StatusBadRequest, "at least one contenttopic should be specified")
return
}
if len(request.ContentTopics) > MaxContentTopicsPerRequest {
reply(s, logger, request, http.StatusBadRequest, fmt.Sprintf("exceeds maximum content topics: %d", MaxContentTopicsPerRequest))
reply(ctx, s, logger, request, http.StatusBadRequest, fmt.Sprintf("exceeds maximum content topics: %d", MaxContentTopicsPerRequest))
}
err := wf.subscriptions.Delete(s.Conn().RemotePeer(), request.PubsubTopic, request.ContentTopics)
if err != nil {
reply(s, logger, request, http.StatusNotFound, peerHasNoSubscription)
reply(ctx, s, logger, request, http.StatusNotFound, peerHasNoSubscription)
} else {
reply(s, logger, request, http.StatusOK)
stats.Record(ctx, metrics.FilterSubscriptions.M(int64(wf.subscriptions.Count())))
reply(ctx, s, logger, request, http.StatusOK)
}
}
func (wf *WakuFilterFull) unsubscribeAll(s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
func (wf *WakuFilterFullNode) unsubscribeAll(ctx context.Context, s network.Stream, logger *zap.Logger, request *pb.FilterSubscribeRequest) {
err := wf.subscriptions.DeleteAll(s.Conn().RemotePeer())
if err != nil {
reply(s, logger, request, http.StatusNotFound, peerHasNoSubscription)
reply(ctx, s, logger, request, http.StatusNotFound, peerHasNoSubscription)
} else {
reply(s, logger, request, http.StatusOK)
stats.Record(ctx, metrics.FilterSubscriptions.M(int64(wf.subscriptions.Count())))
reply(ctx, s, logger, request, http.StatusOK)
}
}
func (wf *WakuFilterFull) filterListener(ctx context.Context) {
func (wf *WakuFilterFullNode) filterListener(ctx context.Context) {
defer wf.wg.Done()
// This function is invoked for each message received
@ -238,24 +253,28 @@ func (wf *WakuFilterFull) filterListener(ctx context.Context) {
wf.wg.Add(1)
go func(subscriber peer.ID) {
defer wf.wg.Done()
start := time.Now()
err := wf.pushMessage(ctx, subscriber, envelope)
if err != nil {
logger.Error("pushing message", zap.Error(err))
return
}
ellapsed := time.Since(start)
metrics.FilterHandleMessageDurationSeconds.M(int64(ellapsed.Seconds()))
}(subscriber)
}
return nil
}
for m := range wf.msgC {
for m := range wf.msgSub.Ch {
if err := handle(m); err != nil {
wf.log.Error("handling message", zap.Error(err))
}
}
}
func (wf *WakuFilterFull) pushMessage(ctx context.Context, peerID peer.ID, env *protocol.Envelope) error {
func (wf *WakuFilterFullNode) pushMessage(ctx context.Context, peerID peer.ID, env *protocol.Envelope) error {
logger := wf.log.With(logging.HostID("peer", peerID))
messagePush := &pb.MessagePushV2{
@ -270,6 +289,11 @@ func (wf *WakuFilterFull) pushMessage(ctx context.Context, peerID peer.ID, env *
err := wf.h.Connect(ctx, wf.h.Peerstore().PeerInfo(peerID))
if err != nil {
wf.subscriptions.FlagAsFailure(peerID)
if errors.Is(context.DeadlineExceeded, err) {
metrics.RecordFilterError(ctx, "push_timeout_failure")
} else {
metrics.RecordFilterError(ctx, "dial_failure")
}
logger.Error("connecting to peer", zap.Error(err))
return err
}
@ -277,9 +301,12 @@ func (wf *WakuFilterFull) pushMessage(ctx context.Context, peerID peer.ID, env *
conn, err := wf.h.NewStream(ctx, peerID, FilterPushID_v20beta1)
if err != nil {
wf.subscriptions.FlagAsFailure(peerID)
if errors.Is(context.DeadlineExceeded, err) {
metrics.RecordFilterError(ctx, "push_timeout_failure")
} else {
metrics.RecordFilterError(ctx, "dial_failure")
}
logger.Error("opening peer stream", zap.Error(err))
//waku_filter_errors.inc(labelValues = [dialFailure])
return err
}
@ -287,6 +314,11 @@ func (wf *WakuFilterFull) pushMessage(ctx context.Context, peerID peer.ID, env *
writer := pbio.NewDelimitedWriter(conn)
err = writer.WriteMsg(messagePush)
if err != nil {
if errors.Is(context.DeadlineExceeded, err) {
metrics.RecordFilterError(ctx, "push_timeout_failure")
} else {
metrics.RecordFilterError(ctx, "response_write_failure")
}
logger.Error("pushing messages to peer", zap.Error(err))
wf.subscriptions.FlagAsFailure(peerID)
return nil
@ -297,7 +329,7 @@ func (wf *WakuFilterFull) pushMessage(ctx context.Context, peerID peer.ID, env *
}
// Stop unmounts the filter protocol
func (wf *WakuFilterFull) Stop() {
func (wf *WakuFilterFullNode) Stop() {
if wf.cancel == nil {
return
}
@ -306,11 +338,7 @@ func (wf *WakuFilterFull) Stop() {
wf.cancel()
close(wf.msgC)
wf.msgSub.Unsubscribe()
wf.wg.Wait()
}
func (wf *WakuFilterFull) MessageChannel() chan *protocol.Envelope {
return wf.msgC
}

View File

@ -1,4 +1,4 @@
package filterv2
package filter
import (
"sync"
@ -6,19 +6,20 @@ import (
"github.com/google/uuid"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/protocol"
"go.uber.org/zap"
)
type SubscriptionDetails struct {
sync.RWMutex
id string
ID string
mapRef *SubscriptionsMap
closed bool
Closed bool
once sync.Once
peerID peer.ID
pubsubTopic string
contentTopics map[string]struct{}
PeerID peer.ID
PubsubTopic string
ContentTopics map[string]struct{}
C chan *protocol.Envelope
}
@ -31,12 +32,14 @@ type PeerSubscription struct {
type SubscriptionsMap struct {
sync.RWMutex
items map[peer.ID]*PeerSubscription
logger *zap.Logger
items map[peer.ID]*PeerSubscription
}
func NewSubscriptionMap() *SubscriptionsMap {
func NewSubscriptionMap(logger *zap.Logger) *SubscriptionsMap {
return &SubscriptionsMap{
items: make(map[peer.ID]*PeerSubscription),
logger: logger.Named("subscription-map"),
items: make(map[peer.ID]*PeerSubscription),
}
}
@ -59,24 +62,35 @@ func (sub *SubscriptionsMap) NewSubscription(peerID peer.ID, topic string, conte
}
details := &SubscriptionDetails{
id: uuid.NewString(),
ID: uuid.NewString(),
mapRef: sub,
peerID: peerID,
pubsubTopic: topic,
C: make(chan *protocol.Envelope),
contentTopics: make(map[string]struct{}),
PeerID: peerID,
PubsubTopic: topic,
C: make(chan *protocol.Envelope, 1024),
ContentTopics: make(map[string]struct{}),
}
for _, ct := range contentTopics {
details.contentTopics[ct] = struct{}{}
details.ContentTopics[ct] = struct{}{}
}
sub.items[peerID].subscriptionsPerTopic[topic][details.id] = details
sub.items[peerID].subscriptionsPerTopic[topic][details.ID] = details
return details
}
func (sub *SubscriptionsMap) Has(peerID peer.ID, topic string, contentTopics []string) bool {
func (sub *SubscriptionsMap) IsSubscribedTo(peerID peer.ID) bool {
sub.RLock()
defer sub.RUnlock()
_, ok := sub.items[peerID]
return ok
}
func (sub *SubscriptionsMap) Has(peerID peer.ID, topic string, contentTopics ...string) bool {
sub.RLock()
defer sub.RUnlock()
// Check if peer exits
peerSubscription, ok := sub.items[peerID]
if !ok {
@ -93,7 +107,7 @@ func (sub *SubscriptionsMap) Has(peerID peer.ID, topic string, contentTopics []s
for _, ct := range contentTopics {
found := false
for _, subscription := range subscriptions {
_, exists := subscription.contentTopics[ct]
_, exists := subscription.ContentTopics[ct]
if exists {
found = true
break
@ -111,12 +125,12 @@ func (sub *SubscriptionsMap) Delete(subscription *SubscriptionDetails) error {
sub.Lock()
defer sub.Unlock()
peerSubscription, ok := sub.items[subscription.peerID]
peerSubscription, ok := sub.items[subscription.PeerID]
if !ok {
return ErrNotFound
}
delete(peerSubscription.subscriptionsPerTopic[subscription.pubsubTopic], subscription.id)
delete(peerSubscription.subscriptionsPerTopic[subscription.PubsubTopic], subscription.ID)
return nil
}
@ -126,7 +140,7 @@ func (s *SubscriptionDetails) Add(contentTopics ...string) {
defer s.Unlock()
for _, ct := range contentTopics {
s.contentTopics[ct] = struct{}{}
s.ContentTopics[ct] = struct{}{}
}
}
@ -135,7 +149,7 @@ func (s *SubscriptionDetails) Remove(contentTopics ...string) {
defer s.Unlock()
for _, ct := range contentTopics {
delete(s.contentTopics, ct)
delete(s.ContentTopics, ct)
}
}
@ -144,7 +158,7 @@ func (s *SubscriptionDetails) closeC() {
s.Lock()
defer s.Unlock()
s.closed = true
s.Closed = true
close(s.C)
})
}
@ -159,17 +173,17 @@ func (s *SubscriptionDetails) Clone() *SubscriptionDetails {
defer s.RUnlock()
result := &SubscriptionDetails{
id: uuid.NewString(),
ID: uuid.NewString(),
mapRef: s.mapRef,
closed: false,
peerID: s.peerID,
pubsubTopic: s.pubsubTopic,
contentTopics: make(map[string]struct{}),
Closed: false,
PeerID: s.PeerID,
PubsubTopic: s.PubsubTopic,
ContentTopics: make(map[string]struct{}),
C: make(chan *protocol.Envelope),
}
for k := range s.contentTopics {
result.contentTopics[k] = struct{}{}
for k := range s.ContentTopics {
result.ContentTopics[k] = struct{}{}
}
return result
@ -199,24 +213,27 @@ func (sub *SubscriptionsMap) Notify(peerID peer.ID, envelope *protocol.Envelope)
subscriptions, ok := sub.items[peerID].subscriptionsPerTopic[envelope.PubsubTopic()]
if ok {
iterateSubscriptionSet(subscriptions, envelope)
iterateSubscriptionSet(sub.logger, subscriptions, envelope)
}
}
func iterateSubscriptionSet(subscriptions SubscriptionSet, envelope *protocol.Envelope) {
func iterateSubscriptionSet(logger *zap.Logger, subscriptions SubscriptionSet, envelope *protocol.Envelope) {
for _, subscription := range subscriptions {
func(subscription *SubscriptionDetails) {
subscription.RLock()
defer subscription.RUnlock()
_, ok := subscription.contentTopics[envelope.Message().ContentTopic]
if !ok && len(subscription.contentTopics) != 0 { // TODO: confirm if no content topics are allowed
_, ok := subscription.ContentTopics[envelope.Message().ContentTopic]
if !ok && len(subscription.ContentTopics) != 0 {
return
}
if !subscription.closed {
// TODO: consider pushing or dropping if subscription is not available
subscription.C <- envelope
if !subscription.Closed {
select {
case subscription.C <- envelope:
default:
logger.Warn("can't deliver message to subscription. subscriber too slow")
}
}
}(subscription)
}

View File

@ -1,3 +0,0 @@
package pb
//go:generate protoc -I./../../pb/. -I. --go_opt=paths=source_relative --go_opt=Mwaku_filter_v2.proto=github.com/waku-org/go-waku/waku/v2/protocol/filterv2/pb --go_opt=Mwaku_message.proto=github.com/waku-org/go-waku/waku/v2/protocol/pb --go_out=. ./waku_filter_v2.proto

View File

@ -1,11 +1,11 @@
package filter
package legacy_filter
import (
"sync"
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/timesource"
)
@ -13,7 +13,7 @@ type FilterMap struct {
sync.RWMutex
timesource timesource.Timesource
items map[string]Filter
broadcaster v2.Broadcaster
broadcaster relay.Broadcaster
}
type FilterMapItem struct {
@ -21,7 +21,7 @@ type FilterMapItem struct {
Value Filter
}
func NewFilterMap(broadcaster v2.Broadcaster, timesource timesource.Timesource) *FilterMap {
func NewFilterMap(broadcaster relay.Broadcaster, timesource timesource.Timesource) *FilterMap {
return &FilterMap{
timesource: timesource,
items: make(map[string]Filter),
@ -49,6 +49,11 @@ func (fm *FilterMap) Delete(key string) {
fm.Lock()
defer fm.Unlock()
_, ok := fm.items[key]
if !ok {
return
}
close(fm.items[key].Chan)
delete(fm.items, key)
}

View File

@ -1,11 +1,11 @@
package filter
package legacy_filter
import (
"sync"
"time"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/waku/v2/protocol/filter/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter/pb"
)
type Subscriber struct {

View File

@ -0,0 +1,3 @@
package pb
//go:generate protoc -I./../../pb/. -I. --go_opt=paths=source_relative --go_opt=Mwaku_filter.proto=github.com/waku-org/go-waku/waku/v2/protocol/filter/pb --go_opt=Mwaku_message.proto=github.com/waku-org/go-waku/waku/v2/protocol/pb --go_out=. ./waku_filter.proto

View File

@ -1,4 +1,4 @@
package filter
package legacy_filter
import (
"context"
@ -13,10 +13,9 @@ import (
libp2pProtocol "github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-msgio/pbio"
"github.com/waku-org/go-waku/logging"
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter/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/timesource"
@ -53,7 +52,7 @@ type (
cancel context.CancelFunc
h host.Host
isFullNode bool
msgC chan *protocol.Envelope
msgSub relay.Subscription
wg *sync.WaitGroup
log *zap.Logger
@ -66,7 +65,7 @@ type (
const FilterID_v20beta1 = libp2pProtocol.ID("/vac/waku/filter/2.0.0-beta1")
// NewWakuRelay returns a new instance of Waku Filter struct setup according to the chosen parameter and options
func NewWakuFilter(host host.Host, broadcaster v2.Broadcaster, isFullNode bool, timesource timesource.Timesource, log *zap.Logger, opts ...Option) *WakuFilter {
func NewWakuFilter(broadcaster relay.Broadcaster, isFullNode bool, timesource timesource.Timesource, log *zap.Logger, opts ...Option) *WakuFilter {
wf := new(WakuFilter)
wf.log = log.Named("filter").With(zap.Bool("fullNode", isFullNode))
@ -78,7 +77,6 @@ func NewWakuFilter(host host.Host, broadcaster v2.Broadcaster, isFullNode bool,
}
wf.wg = &sync.WaitGroup{}
wf.h = host
wf.isFullNode = isFullNode
wf.filters = NewFilterMap(broadcaster, timesource)
wf.subscribers = NewSubscribers(params.Timeout)
@ -86,7 +84,12 @@ func NewWakuFilter(host host.Host, broadcaster v2.Broadcaster, isFullNode bool,
return wf
}
func (wf *WakuFilter) Start(ctx context.Context) error {
// Sets the host to be able to mount or consume a protocol
func (wf *WakuFilter) SetHost(h host.Host) {
wf.h = h
}
func (wf *WakuFilter) Start(ctx context.Context, sub relay.Subscription) error {
wf.wg.Wait() // Wait for any goroutines to stop
ctx, err := tag.New(ctx, tag.Insert(metrics.KeyType, "filter"))
@ -100,7 +103,7 @@ func (wf *WakuFilter) Start(ctx context.Context) error {
wf.h.SetStreamHandlerMatch(FilterID_v20beta1, protocol.PrefixTextMatch(string(FilterID_v20beta1)), wf.onRequest(ctx))
wf.cancel = cancel
wf.msgC = make(chan *protocol.Envelope, 1024)
wf.msgSub = sub
wf.wg.Add(1)
go wf.filterListener(ctx)
@ -121,6 +124,7 @@ func (wf *WakuFilter) onRequest(ctx context.Context) func(s network.Stream) {
err := reader.ReadMsg(filterRPCRequest)
if err != nil {
metrics.RecordLegacyFilterError(ctx, "decode_rpc_failure")
logger.Error("reading request", zap.Error(err))
return
}
@ -135,7 +139,7 @@ func (wf *WakuFilter) onRequest(ctx context.Context) func(s network.Stream) {
}
logger.Info("received a message push", zap.Int("messages", len(filterRPCRequest.Push.Messages)))
stats.Record(ctx, metrics.Messages.M(int64(len(filterRPCRequest.Push.Messages))))
metrics.RecordLegacyFilterMessage(ctx, "FilterRequest", len(filterRPCRequest.Push.Messages))
} else if filterRPCRequest.Request != nil && wf.isFullNode {
// We're on a full node.
// This is a filter request coming from a light node.
@ -148,13 +152,13 @@ func (wf *WakuFilter) onRequest(ctx context.Context) func(s network.Stream) {
len := wf.subscribers.Append(subscriber)
logger.Info("adding subscriber")
stats.Record(ctx, metrics.FilterSubscriptions.M(int64(len)))
stats.Record(ctx, metrics.LegacyFilterSubscribers.M(int64(len)))
} else {
peerId := s.Conn().RemotePeer()
wf.subscribers.RemoveContentFilters(peerId, filterRPCRequest.RequestId, filterRPCRequest.Request.ContentFilters)
logger.Info("removing subscriber")
stats.Record(ctx, metrics.FilterSubscriptions.M(int64(wf.subscribers.Length())))
stats.Record(ctx, metrics.LegacyFilterSubscribers.M(int64(wf.subscribers.Length())))
}
} else {
logger.Error("can't serve request")
@ -172,15 +176,15 @@ func (wf *WakuFilter) pushMessage(ctx context.Context, subscriber Subscriber, ms
if err != nil {
wf.subscribers.FlagAsFailure(subscriber.peer)
logger.Error("connecting to peer", zap.Error(err))
metrics.RecordLegacyFilterError(ctx, "dial_failure")
return err
}
conn, err := wf.h.NewStream(ctx, subscriber.peer, FilterID_v20beta1)
if err != nil {
wf.subscribers.FlagAsFailure(subscriber.peer)
logger.Error("opening peer stream", zap.Error(err))
//waku_filter_errors.inc(labelValues = [dialFailure])
metrics.RecordLegacyFilterError(ctx, "dial_failure")
return err
}
@ -190,6 +194,7 @@ func (wf *WakuFilter) pushMessage(ctx context.Context, subscriber Subscriber, ms
if err != nil {
logger.Error("pushing messages to peer", zap.Error(err))
wf.subscribers.FlagAsFailure(subscriber.peer)
metrics.RecordLegacyFilterError(ctx, "push_write_error")
return nil
}
@ -233,7 +238,7 @@ func (wf *WakuFilter) filterListener(ctx context.Context) {
return g.Wait()
}
for m := range wf.msgC {
for m := range wf.msgSub.Ch {
if err := handle(m); err != nil {
wf.log.Error("handling message", zap.Error(err))
}
@ -255,6 +260,7 @@ func (wf *WakuFilter) requestSubscription(ctx context.Context, filter ContentFil
}
if params.selectedPeer == "" {
metrics.RecordLegacyFilterError(ctx, "peer_not_found_failure")
return nil, ErrNoPeersAvailable
}
@ -266,6 +272,7 @@ func (wf *WakuFilter) requestSubscription(ctx context.Context, filter ContentFil
// We connect first so dns4 addresses are resolved (NewStream does not do it)
err = wf.h.Connect(ctx, wf.h.Peerstore().PeerInfo(params.selectedPeer))
if err != nil {
metrics.RecordLegacyFilterError(ctx, "dial_failure")
return
}
@ -278,6 +285,7 @@ func (wf *WakuFilter) requestSubscription(ctx context.Context, filter ContentFil
var conn network.Stream
conn, err = wf.h.NewStream(ctx, params.selectedPeer, FilterID_v20beta1)
if err != nil {
metrics.RecordLegacyFilterError(ctx, "dial_failure")
return
}
@ -291,6 +299,7 @@ func (wf *WakuFilter) requestSubscription(ctx context.Context, filter ContentFil
wf.log.Debug("sending filterRPC", zap.Stringer("rpc", filterRPC))
err = writer.WriteMsg(filterRPC)
if err != nil {
metrics.RecordLegacyFilterError(ctx, "request_write_error")
wf.log.Error("sending filterRPC", zap.Error(err))
return
}
@ -307,11 +316,13 @@ func (wf *WakuFilter) Unsubscribe(ctx context.Context, contentFilter ContentFilt
// We connect first so dns4 addresses are resolved (NewStream does not do it)
err := wf.h.Connect(ctx, wf.h.Peerstore().PeerInfo(peer))
if err != nil {
metrics.RecordLegacyFilterError(ctx, "dial_failure")
return err
}
conn, err := wf.h.NewStream(ctx, peer, FilterID_v20beta1)
if err != nil {
metrics.RecordLegacyFilterError(ctx, "dial_failure")
return err
}
@ -335,6 +346,7 @@ func (wf *WakuFilter) Unsubscribe(ctx context.Context, contentFilter ContentFilt
filterRPC := &pb.FilterRPC{RequestId: hex.EncodeToString(id), Request: request}
err = writer.WriteMsg(filterRPC)
if err != nil {
metrics.RecordLegacyFilterError(ctx, "request_write_error")
return err
}
@ -349,7 +361,7 @@ func (wf *WakuFilter) Stop() {
wf.cancel()
close(wf.msgC)
wf.msgSub.Unsubscribe()
wf.h.RemoveStreamHandler(FilterID_v20beta1)
wf.filters.RemoveAll()
@ -381,7 +393,6 @@ func (wf *WakuFilter) Subscribe(ctx context.Context, f ContentFilter, opts ...Fi
ContentFilters: f.ContentTopics,
Chan: make(chan *protocol.Envelope, 1024), // To avoid blocking
}
wf.filters.Set(filterID, theFilter)
return
@ -427,7 +438,7 @@ func (wf *WakuFilter) UnsubscribeFilterByID(ctx context.Context, filterID string
// the contentTopics are removed the subscription is dropped completely
func (wf *WakuFilter) UnsubscribeFilter(ctx context.Context, cf ContentFilter) error {
// Remove local filter
var idsToRemove []string
idsToRemove := make(map[string]struct{})
for filterMapItem := range wf.filters.Items() {
f := filterMapItem.Value
id := filterMapItem.Key
@ -456,18 +467,14 @@ func (wf *WakuFilter) UnsubscribeFilter(ctx context.Context, cf ContentFilter) e
}
if len(f.ContentFilters) == 0 {
idsToRemove = append(idsToRemove, id)
idsToRemove[id] = struct{}{}
}
}
}
for _, rId := range idsToRemove {
for rId := range idsToRemove {
wf.filters.Delete(rId)
}
return nil
}
func (wf *WakuFilter) MessageChannel() chan *protocol.Envelope {
return wf.msgC
}

View File

@ -36,15 +36,19 @@ type WakuLightPush struct {
}
// NewWakuRelay returns a new instance of Waku Lightpush struct
func NewWakuLightPush(h host.Host, relay *relay.WakuRelay, log *zap.Logger) *WakuLightPush {
func NewWakuLightPush(relay *relay.WakuRelay, log *zap.Logger) *WakuLightPush {
wakuLP := new(WakuLightPush)
wakuLP.relay = relay
wakuLP.h = h
wakuLP.log = log.Named("lightpush")
return wakuLP
}
// Sets the host to be able to mount or consume a protocol
func (wakuLP *WakuLightPush) SetHost(h host.Host) {
wakuLP.h = h
}
// Start inits the lighpush protocol
func (wakuLP *WakuLightPush) Start(ctx context.Context) error {
if wakuLP.relayIsNotAvailable() {
@ -77,7 +81,7 @@ func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Strea
err := reader.ReadMsg(requestPushRPC)
if err != nil {
logger.Error("reading request", zap.Error(err))
metrics.RecordLightpushError(ctx, "decodeRpcFailure")
metrics.RecordLightpushError(ctx, "decode_rpc_failure")
return
}
@ -89,6 +93,8 @@ func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Strea
pubSubTopic := requestPushRPC.Query.PubsubTopic
message := requestPushRPC.Query.Message
metrics.RecordLightpushMessage(ctx, "PushRequest")
// TODO: Assumes success, should probably be extended to check for network, peers, etc
// It might make sense to use WithReadiness option here?
@ -96,6 +102,7 @@ func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Strea
if err != nil {
logger.Error("publishing message", zap.Error(err))
metrics.RecordLightpushError(ctx, "message_push_failure")
response.Info = "Could not publish message"
} else {
response.IsSuccess = true
@ -108,11 +115,14 @@ func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Strea
err = writer.WriteMsg(responsePushRPC)
if err != nil {
metrics.RecordLightpushError(ctx, "response_write_failure")
logger.Error("writing response", zap.Error(err))
_ = s.Reset()
} else {
logger.Info("response sent")
}
} else {
metrics.RecordLightpushError(ctx, "empty_request_body_failure")
}
if requestPushRPC.Response != nil {
@ -121,6 +131,8 @@ func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(s network.Strea
} else {
logger.Info("request failure", zap.String("info=", requestPushRPC.Response.Info))
}
} else {
metrics.RecordLightpushError(ctx, "empty_response_body_failure")
}
}
}
@ -136,7 +148,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
}
if params.selectedPeer == "" {
metrics.RecordLightpushError(ctx, "dialError")
metrics.RecordLightpushError(ctx, "peer_not_found_failure")
return nil, ErrNoPeersAvailable
}
@ -148,6 +160,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
// We connect first so dns4 addresses are resolved (NewStream does not do it)
err := wakuLP.h.Connect(ctx, wakuLP.h.Peerstore().PeerInfo(params.selectedPeer))
if err != nil {
metrics.RecordLightpushError(ctx, "dial_failure")
logger.Error("connecting peer", zap.Error(err))
return nil, err
}
@ -155,7 +168,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
connOpt, err := wakuLP.h.NewStream(ctx, params.selectedPeer, LightPushID_v20beta1)
if err != nil {
logger.Error("creating stream to peer", zap.Error(err))
metrics.RecordLightpushError(ctx, "dialError")
metrics.RecordLightpushError(ctx, "dial_failure")
return nil, err
}
@ -163,7 +176,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
defer func() {
err := connOpt.Reset()
if err != nil {
metrics.RecordLightpushError(ctx, "dialError")
metrics.RecordLightpushError(ctx, "dial_failure")
logger.Error("resetting connection", zap.Error(err))
}
}()
@ -175,6 +188,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
err = writer.WriteMsg(pushRequestRPC)
if err != nil {
metrics.RecordLightpushError(ctx, "request_write_failure")
logger.Error("writing request", zap.Error(err))
return nil, err
}
@ -183,7 +197,7 @@ func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, o
err = reader.ReadMsg(pushResponseRPC)
if err != nil {
logger.Error("reading response", zap.Error(err))
metrics.RecordLightpushError(ctx, "decodeRPCFailure")
metrics.RecordLightpushError(ctx, "decode_rpc_failure")
return nil, err
}

View File

@ -11,8 +11,8 @@ import (
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-msgio/pbio"
"github.com/waku-org/go-waku/waku/v2/metrics"
wenr "github.com/waku-org/go-waku/waku/v2/protocol/enr"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange/pb"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap"
)
@ -85,7 +85,7 @@ func (wakuPX *WakuPeerExchange) handleResponse(ctx context.Context, response *pb
return err
}
peerInfo, err := utils.EnodeToPeerInfo(enodeRecord)
peerInfo, err := wenr.EnodeToPeerInfo(enodeRecord)
if err != nil {
return err
}

View File

@ -0,0 +1,71 @@
package peer_exchange
import (
"bufio"
"bytes"
"math/rand"
"sync"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/hashicorp/golang-lru/simplelru"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange/pb"
)
// simpleLRU internal uses container/list, which is ring buffer(double linked list)
type enrCache struct {
// using lru, saves us from periodically cleaning the cache to maintain a certain size
data *simplelru.LRU
rng *rand.Rand
mu sync.RWMutex
}
// err on negative size
func newEnrCache(size int) (*enrCache, error) {
inner, err := simplelru.NewLRU(size, nil)
return &enrCache{
data: inner,
rng: rand.New(rand.NewSource(rand.Int63())),
}, err
}
// updating cache
func (c *enrCache) updateCache(node *enode.Node) {
c.mu.Lock()
defer c.mu.Unlock()
c.data.Add(node.ID(), node)
}
// get `numPeers` records of enr
func (c *enrCache) getENRs(neededPeers int) ([]*pb.PeerInfo, error) {
c.mu.RLock()
defer c.mu.RUnlock()
//
availablePeers := c.data.Len()
if availablePeers == 0 {
return nil, nil
}
if availablePeers < neededPeers {
neededPeers = availablePeers
}
perm := c.rng.Perm(availablePeers)[0:neededPeers]
keys := c.data.Keys()
result := []*pb.PeerInfo{}
for _, ind := range perm {
node, ok := c.data.Get(keys[ind])
if !ok {
continue
}
var b bytes.Buffer
writer := bufio.NewWriter(&b)
err := node.(*enode.Node).Record().EncodeRLP(writer)
if err != nil {
return nil, err
}
writer.Flush()
result = append(result, &pb.PeerInfo{
ENR: b.Bytes(),
})
}
return result, nil
}

View File

@ -1,17 +1,13 @@
package peer_exchange
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"math"
"math/rand"
"sync"
"time"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
@ -21,26 +17,20 @@ import (
"github.com/waku-org/go-waku/waku/v2/discv5"
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/enr"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange/pb"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap"
)
// PeerExchangeID_v20alpha1 is the current Waku Peer Exchange protocol identifier
const PeerExchangeID_v20alpha1 = libp2pProtocol.ID("/vac/waku/peer-exchange/2.0.0-alpha1")
const MaxCacheSize = 1000
const CacheCleanWindow = 200
var (
ErrNoPeersAvailable = errors.New("no suitable remote peers")
ErrInvalidId = errors.New("invalid request id")
)
type peerRecord struct {
node *enode.Node
idx int
}
type WakuPeerExchange struct {
h host.Host
disc *discv5.DiscoveryV5
@ -51,10 +41,7 @@ type WakuPeerExchange struct {
wg sync.WaitGroup
peerConnector PeerConnector
peerCh chan peer.AddrInfo
enrCache map[enode.ID]peerRecord // todo: next step: ring buffer; future: implement cache satisfying https://rfc.vac.dev/spec/34/
enrCacheMutex sync.RWMutex
rng *rand.Rand
enrCache *enrCache
}
type PeerConnector interface {
@ -62,17 +49,24 @@ type PeerConnector interface {
}
// NewWakuPeerExchange returns a new instance of WakuPeerExchange struct
func NewWakuPeerExchange(h host.Host, disc *discv5.DiscoveryV5, peerConnector PeerConnector, log *zap.Logger) (*WakuPeerExchange, error) {
func NewWakuPeerExchange(disc *discv5.DiscoveryV5, peerConnector PeerConnector, log *zap.Logger) (*WakuPeerExchange, error) {
newEnrCache, err := newEnrCache(MaxCacheSize)
if err != nil {
return nil, err
}
wakuPX := new(WakuPeerExchange)
wakuPX.h = h
wakuPX.disc = disc
wakuPX.log = log.Named("wakupx")
wakuPX.enrCache = make(map[enode.ID]peerRecord)
wakuPX.rng = rand.New(rand.NewSource(rand.Int63()))
wakuPX.enrCache = newEnrCache
wakuPX.peerConnector = peerConnector
return wakuPX, nil
}
// Sets the host to be able to mount or consume a protocol
func (wakuPX *WakuPeerExchange) SetHost(h host.Host) {
wakuPX.h = h
}
// Start inits the peer exchange protocol
func (wakuPX *WakuPeerExchange) Start(ctx context.Context) error {
if wakuPX.cancel != nil {
@ -83,7 +77,6 @@ func (wakuPX *WakuPeerExchange) Start(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
wakuPX.cancel = cancel
wakuPX.peerCh = make(chan peer.AddrInfo)
wakuPX.h.SetStreamHandlerMatch(PeerExchangeID_v20alpha1, protocol.PrefixTextMatch(string(PeerExchangeID_v20alpha1)), wakuPX.onRequest(ctx))
wakuPX.log.Info("Peer exchange protocol started")
@ -109,7 +102,7 @@ func (wakuPX *WakuPeerExchange) onRequest(ctx context.Context) func(s network.St
if requestRPC.Query != nil {
logger.Info("request received")
records, err := wakuPX.getENRsFromCache(requestRPC.Query.NumPeers)
records, err := wakuPX.enrCache.getENRs(int(requestRPC.Query.NumPeers))
if err != nil {
logger.Error("obtaining enrs from cache", zap.Error(err))
metrics.RecordPeerExchangeError(ctx, "pxFailure")
@ -138,102 +131,19 @@ func (wakuPX *WakuPeerExchange) Stop() {
}
wakuPX.h.RemoveStreamHandler(PeerExchangeID_v20alpha1)
wakuPX.cancel()
close(wakuPX.peerCh)
wakuPX.wg.Wait()
}
func (wakuPX *WakuPeerExchange) getENRsFromCache(numPeers uint64) ([]*pb.PeerInfo, error) {
wakuPX.enrCacheMutex.Lock()
defer wakuPX.enrCacheMutex.Unlock()
if len(wakuPX.enrCache) == 0 {
return nil, nil
}
numItems := int(numPeers)
if len(wakuPX.enrCache) < int(numPeers) {
numItems = len(wakuPX.enrCache)
}
perm := wakuPX.rng.Perm(len(wakuPX.enrCache))[0:numItems]
permSet := make(map[int]int)
for i, v := range perm {
permSet[v] = i
}
var result []*pb.PeerInfo
iter := 0
for k := range wakuPX.enrCache {
if _, ok := permSet[iter]; ok {
var b bytes.Buffer
writer := bufio.NewWriter(&b)
enode := wakuPX.enrCache[k]
err := enode.node.Record().EncodeRLP(writer)
if err != nil {
return nil, err
}
writer.Flush()
result = append(result, &pb.PeerInfo{
ENR: b.Bytes(),
})
}
iter++
}
return result, nil
}
func (wakuPX *WakuPeerExchange) cleanCache() {
if len(wakuPX.enrCache) < MaxCacheSize {
return
}
r := make(map[enode.ID]peerRecord)
for k, v := range wakuPX.enrCache {
if v.idx > CacheCleanWindow {
v.idx -= CacheCleanWindow
r[k] = v
}
}
wakuPX.enrCache = r
}
func (wakuPX *WakuPeerExchange) iterate(ctx context.Context) error {
iterator, err := wakuPX.disc.Iterator()
if err != nil {
return fmt.Errorf("obtaining iterator: %w", err)
}
// Closing iterator
defer iterator.Close()
closeCh := make(chan struct{}, 1)
defer close(closeCh)
// Closing iterator when context is cancelled or function is returning
wakuPX.wg.Add(1)
go func() {
defer wakuPX.wg.Done()
select {
case <-ctx.Done():
iterator.Close()
case <-closeCh:
iterator.Close()
}
}()
for {
if ctx.Err() != nil {
break
}
exists := iterator.Next()
if !exists {
break
}
_, addresses, err := utils.Multiaddress(iterator.Node())
for iterator.Next() {
_, addresses, err := enr.Multiaddress(iterator.Node())
if err != nil {
wakuPX.log.Error("extracting multiaddrs from enr", zap.Error(err))
continue
@ -244,15 +154,14 @@ func (wakuPX *WakuPeerExchange) iterate(ctx context.Context) error {
}
wakuPX.log.Debug("Discovered px peers via discv5")
wakuPX.enrCache.updateCache(iterator.Node())
wakuPX.enrCacheMutex.Lock()
wakuPX.enrCache[iterator.Node().ID()] = peerRecord{
idx: len(wakuPX.enrCache),
node: iterator.Node(),
select {
case <-ctx.Done():
return nil
default:
}
wakuPX.enrCacheMutex.Unlock()
}
return nil
}
@ -265,27 +174,16 @@ func (wakuPX *WakuPeerExchange) runPeerExchangeDiscv5Loop(ctx context.Context) {
return
}
ch := make(chan struct{}, 1)
ch <- struct{}{} // Initial execution
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
restartLoop:
for {
err := wakuPX.iterate(ctx)
if err != nil {
wakuPX.log.Debug("iterating peer exchange", zap.Error(err))
time.Sleep(2 * time.Second)
}
select {
case <-ch:
err := wakuPX.iterate(ctx)
if err != nil {
wakuPX.log.Debug("iterating peer exchange", zap.Error(err))
time.Sleep(2 * time.Second)
}
ch <- struct{}{}
case <-ticker.C:
wakuPX.cleanCache()
case <-ctx.Done():
close(ch)
break restartLoop
return
default:
}
}
}

View File

@ -0,0 +1,177 @@
package relay
import (
"context"
"errors"
"sync"
"sync/atomic"
"github.com/waku-org/go-waku/waku/v2/protocol"
)
type chStore struct {
mu sync.RWMutex
topicToChans map[string]map[int]chan *protocol.Envelope
id int
}
func newChStore() chStore {
return chStore{
topicToChans: make(map[string]map[int]chan *protocol.Envelope),
}
}
func (s *chStore) getNewCh(topic string, chLen int) Subscription {
ch := make(chan *protocol.Envelope, chLen)
s.mu.Lock()
defer s.mu.Unlock()
s.id++
//
if s.topicToChans[topic] == nil {
s.topicToChans[topic] = make(map[int]chan *protocol.Envelope)
}
id := s.id
s.topicToChans[topic][id] = ch
return Subscription{
// read only channel,will not block forever, returns once closed.
Ch: ch,
// Unsubscribe function is safe, can be called multiple times
// and even after broadcaster has stopped running.
Unsubscribe: func() {
s.mu.Lock()
defer s.mu.Unlock()
if s.topicToChans[topic] == nil {
return
}
if ch := s.topicToChans[topic][id]; ch != nil {
close(ch)
delete(s.topicToChans[topic], id)
}
},
}
}
func (s *chStore) broadcast(ctx context.Context, m *protocol.Envelope) {
s.mu.RLock()
defer s.mu.RUnlock()
for _, ch := range s.topicToChans[m.PubsubTopic()] {
select {
// using ctx.Done for returning on cancellation is needed
// reason:
// if for a channel there is no one listening to it
// the broadcast will acquire lock and wait until there is a receiver on that channel.
// this will also block the chStore close function as it uses same mutex
case <-ctx.Done():
return
case ch <- m:
}
}
// send to all registered subscribers
for _, ch := range s.topicToChans[""] {
select {
case <-ctx.Done():
return
case ch <- m:
}
}
}
func (b *chStore) close() {
b.mu.Lock()
defer b.mu.Unlock()
for _, chans := range b.topicToChans {
for _, ch := range chans {
close(ch)
}
}
b.topicToChans = nil
}
type Broadcaster interface {
Start(ctx context.Context) error
Stop()
Register(topic string, chLen ...int) Subscription
RegisterForAll(chLen ...int) Subscription
Submit(*protocol.Envelope)
}
// ////
// thread safe
// panic safe, input can't be submitted to `input` channel after stop
// lock safe, only read channels are returned and later closed, calling code has guarantee Register channel will not block forever.
// no opened channel leaked, all created only read channels are closed when stop
// even if there is noone listening to returned channels, guarantees to be lockfree.
type broadcaster struct {
bufLen int
cancel context.CancelFunc
input chan *protocol.Envelope
//
chStore chStore
running atomic.Bool
}
func NewBroadcaster(bufLen int) *broadcaster {
return &broadcaster{
bufLen: bufLen,
}
}
func (b *broadcaster) Start(ctx context.Context) error {
if !b.running.CompareAndSwap(false, true) { // if not running then start
return errors.New("already started")
}
ctx, cancel := context.WithCancel(ctx)
b.cancel = cancel
b.chStore = newChStore()
b.input = make(chan *protocol.Envelope, b.bufLen)
go b.run(ctx)
return nil
}
func (b *broadcaster) run(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case msg, ok := <-b.input:
if ok {
b.chStore.broadcast(ctx, msg)
}
}
}
}
func (b *broadcaster) Stop() {
if !b.running.CompareAndSwap(true, false) { // if running then stop
return
}
// cancel must be before chStore.close(), so that broadcast releases lock before chStore.close() acquires it.
b.cancel() // exit the run loop,
b.chStore.close() // close all channels that we send to
close(b.input) // close input channel
}
// returned subscription is all speicfied topic
func (b *broadcaster) Register(topic string, chLen ...int) Subscription {
return b.chStore.getNewCh(topic, getChLen(chLen))
}
// return subscription is for all topic
func (b *broadcaster) RegisterForAll(chLen ...int) Subscription {
return b.chStore.getNewCh("", getChLen(chLen))
}
func getChLen(chLen []int) int {
l := 0
if len(chLen) > 0 {
l = chLen[0]
}
return l
}
// only accepts value when running.
func (b *broadcaster) Submit(m *protocol.Envelope) {
if b.running.Load() {
b.input <- m
}
}

View File

@ -1,34 +1,29 @@
package relay
import (
"sync"
import "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol"
)
// Subscription handles the subscrition to a particular pubsub topic
type Subscription struct {
sync.RWMutex
// C is channel used for receiving envelopes
C chan *protocol.Envelope
closed bool
once sync.Once
quit chan struct{}
Unsubscribe func()
Ch <-chan *protocol.Envelope
}
// Unsubscribe will close a subscription from a pubsub topic. Will close the message channel
func (subs *Subscription) Unsubscribe() {
subs.once.Do(func() {
close(subs.quit)
})
func NoopSubscription() Subscription {
ch := make(chan *protocol.Envelope)
close(ch)
return Subscription{
Unsubscribe: func() {},
Ch: ch,
}
}
// IsClosed determine whether a Subscription is still open for receiving messages
func (subs *Subscription) IsClosed() bool {
subs.RLock()
defer subs.RUnlock()
return subs.closed
func ArraySubscription(msgs []*protocol.Envelope) Subscription {
ch := make(chan *protocol.Envelope, len(msgs))
for _, msg := range msgs {
ch <- msg
}
close(ch)
return Subscription{
Unsubscribe: func() {},
Ch: ch,
}
}

View File

@ -0,0 +1,100 @@
package relay
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"encoding/binary"
"encoding/hex"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer"
"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/timesource"
"go.uber.org/zap"
proto "google.golang.org/protobuf/proto"
)
// Application level message hash
func MsgHash(pubSubTopic string, msg *pb.WakuMessage) []byte {
timestampBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(timestampBytes, uint64(msg.Timestamp))
var ephemeralByte byte
if msg.Ephemeral {
ephemeralByte = 1
}
return hash.SHA256(
[]byte(pubSubTopic),
msg.Payload,
[]byte(msg.ContentTopic),
timestampBytes,
[]byte{ephemeralByte},
)
}
const MessageWindowDuration = time.Minute * 5
func withinTimeWindow(t timesource.Timesource, msg *pb.WakuMessage) bool {
if msg.Timestamp == 0 {
return false
}
now := t.Now()
msgTime := time.Unix(0, msg.Timestamp)
return now.Sub(msgTime).Abs() <= MessageWindowDuration
}
type validatorFn = func(ctx context.Context, peerID peer.ID, message *pubsub.Message) bool
func validatorFnBuilder(t timesource.Timesource, topic string, publicKey *ecdsa.PublicKey) validatorFn {
pubkBytes := crypto.FromECDSAPub(publicKey)
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
}
if !withinTimeWindow(t, msg) {
return false
}
msgHash := MsgHash(topic, msg)
signature := msg.Meta
return secp256k1.VerifySignature(pubkBytes, msgHash, signature)
}
}
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))))
err := w.pubsub.RegisterTopicValidator(topic, validatorFnBuilder(w.timesource, topic, publicKey))
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
}
func SignMessage(privKey *ecdsa.PrivateKey, topic string, msg *pb.WakuMessage) error {
msgHash := MsgHash(topic, msg)
sign, err := secp256k1.Sign(msgHash, crypto.FromECDSA(privKey))
if err != nil {
return err
}
msg.Meta = sign[0:64] // Drop the V in R||S||V
return nil
}

View File

@ -2,13 +2,11 @@ package relay
import (
"context"
"encoding/hex"
"errors"
"fmt"
"sync"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"go.opencensus.io/stats"
"go.opencensus.io/tag"
@ -18,7 +16,6 @@ import (
pubsub "github.com/libp2p/go-libp2p-pubsub"
pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb"
"github.com/waku-org/go-waku/logging"
v2 "github.com/waku-org/go-waku/waku/v2"
"github.com/waku-org/go-waku/waku/v2/hash"
"github.com/waku-org/go-waku/waku/v2/metrics"
waku_proto "github.com/waku-org/go-waku/waku/v2/protocol"
@ -38,7 +35,7 @@ type WakuRelay struct {
log *zap.Logger
bcaster v2.Broadcaster
bcaster Broadcaster
minPeersToPublish int
@ -47,10 +44,6 @@ type WakuRelay struct {
wakuRelayTopics map[string]*pubsub.Topic
relaySubs map[string]*pubsub.Subscription
// TODO: convert to concurrent maps
subscriptions map[string][]*Subscription
subscriptionsMutex sync.Mutex
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
@ -61,13 +54,11 @@ func msgIdFn(pmsg *pubsub_pb.Message) string {
}
// NewWakuRelay returns a new instance of a WakuRelay struct
func NewWakuRelay(h host.Host, bcaster v2.Broadcaster, minPeersToPublish int, timesource timesource.Timesource, log *zap.Logger, opts ...pubsub.Option) *WakuRelay {
func NewWakuRelay(bcaster Broadcaster, minPeersToPublish int, timesource timesource.Timesource, log *zap.Logger, opts ...pubsub.Option) *WakuRelay {
w := new(WakuRelay)
w.host = h
w.timesource = timesource
w.wakuRelayTopics = make(map[string]*pubsub.Topic)
w.relaySubs = make(map[string]*pubsub.Subscription)
w.subscriptions = make(map[string][]*Subscription)
w.bcaster = bcaster
w.minPeersToPublish = minPeersToPublish
w.wg = sync.WaitGroup{}
@ -96,6 +87,11 @@ func NewWakuRelay(h host.Host, bcaster v2.Broadcaster, minPeersToPublish int, ti
return w
}
// Sets the host to be able to mount or consume a protocol
func (w *WakuRelay) SetHost(h host.Host) {
w.host = h
}
func (w *WakuRelay) Start(ctx context.Context) error {
w.wg.Wait()
ctx, cancel := context.WithCancel(ctx)
@ -129,6 +125,13 @@ func (w *WakuRelay) Topics() []string {
return result
}
func (w *WakuRelay) IsSubscribed(topic string) bool {
defer w.topicsMutex.Unlock()
w.topicsMutex.Lock()
_, ok := w.relaySubs[topic]
return ok
}
// SetPubSub is used to set an implementation of the pubsub system
func (w *WakuRelay) SetPubSub(pubSub *pubsub.PubSub) {
w.pubsub = pubSub
@ -150,17 +153,15 @@ func (w *WakuRelay) upsertTopic(topic string) (*pubsub.Topic, error) {
return pubSubTopic, nil
}
/*
func (w *WakuRelay) validatorFactory(pubsubTopic 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
}
return true
return err == nil
}
}
*/
func (w *WakuRelay) subscribe(topic string) (subs *pubsub.Subscription, err error) {
sub, ok := w.relaySubs[topic]
@ -170,17 +171,24 @@ func (w *WakuRelay) subscribe(topic string) (subs *pubsub.Subscription, err erro
return nil, err
}
err = w.pubsub.RegisterTopicValidator(topic, w.validatorFactory(topic))
if err != nil {
return nil, err
}
/*
// TODO: Add a function to validate the WakuMessage integrity
// Rejects messages that are not WakuMessage
err = w.pubsub.RegisterTopicValidator(topic, w.validatorFactory(topic))
if err != nil {
return nil, err
}
*/
sub, err = pubSubTopic.Subscribe()
if err != nil {
return nil, err
}
w.relaySubs[topic] = sub
if w.bcaster != nil {
w.wg.Add(1)
go w.subscribeToTopic(topic, sub)
}
w.log.Info("subscribing to topic", zap.String("topic", sub.Topic()))
}
@ -220,7 +228,7 @@ func (w *WakuRelay) PublishToTopic(ctx context.Context, message *pb.WakuMessage,
hash := message.Hash(topic)
w.log.Debug("waku.relay published", zap.String("hash", hex.EncodeToString(hash)))
w.log.Debug("waku.relay published", zap.String("pubsubTopic", topic), logging.HexString("hash", hash), zap.Int64("publishTime", w.timesource.Now().UnixNano()), zap.Int("payloadSizeBytes", len(message.Payload)))
return hash, nil
}
@ -240,16 +248,6 @@ func (w *WakuRelay) Stop() {
w.cancel()
w.wg.Wait()
w.subscriptionsMutex.Lock()
defer w.subscriptionsMutex.Unlock()
for _, topic := range w.Topics() {
for _, sub := range w.subscriptions[topic] {
sub.Unsubscribe()
}
}
w.subscriptions = nil
}
// EnoughPeersToPublish returns whether there are enough peers connected in the default waku pubsub topic
@ -264,30 +262,21 @@ func (w *WakuRelay) EnoughPeersToPublishToTopic(topic string) bool {
// SubscribeToTopic returns a Subscription to receive messages from a pubsub topic
func (w *WakuRelay) SubscribeToTopic(ctx context.Context, topic string) (*Subscription, error) {
sub, err := w.subscribe(topic)
_, err := w.subscribe(topic)
if err != nil {
return nil, err
}
// Create client subscription
subscription := new(Subscription)
subscription.closed = false
subscription.C = make(chan *waku_proto.Envelope, 1024) // To avoid blocking
subscription.quit = make(chan struct{})
w.subscriptionsMutex.Lock()
defer w.subscriptionsMutex.Unlock()
w.subscriptions[topic] = append(w.subscriptions[topic], subscription)
subscription := NoopSubscription()
if w.bcaster != nil {
w.bcaster.Register(&topic, subscription.C)
subscription = w.bcaster.Register(topic, 1024)
}
w.wg.Add(1)
go w.subscribeToTopic(ctx, topic, subscription, sub)
return subscription, nil
go func() {
<-ctx.Done()
subscription.Unsubscribe()
}()
return &subscription, nil
}
// SubscribeToTopic returns a Subscription to receive messages from the default waku pubsub topic
@ -303,10 +292,6 @@ func (w *WakuRelay) Unsubscribe(ctx context.Context, topic string) error {
}
w.log.Info("unsubscribing from topic", zap.String("topic", sub.Topic()))
for _, sub := range w.subscriptions[topic] {
sub.Unsubscribe()
}
w.relaySubs[topic].Cancel()
delete(w.relaySubs, topic)
@ -321,34 +306,24 @@ func (w *WakuRelay) Unsubscribe(ctx context.Context, topic string) error {
func (w *WakuRelay) nextMessage(ctx context.Context, sub *pubsub.Subscription) <-chan *pubsub.Message {
msgChannel := make(chan *pubsub.Message, 1024)
go func(msgChannel chan *pubsub.Message) {
defer func() {
if r := recover(); r != nil {
w.log.Debug("recovered msgChannel")
}
}()
go func() {
defer close(msgChannel)
for {
msg, err := sub.Next(ctx)
if err != nil {
if !errors.Is(err, context.Canceled) {
w.log.Error("getting message from subscription", zap.Error(err))
}
sub.Cancel()
close(msgChannel)
for _, subscription := range w.subscriptions[sub.Topic()] {
subscription.Unsubscribe()
}
return
}
msgChannel <- msg
}
}(msgChannel)
}()
return msgChannel
}
func (w *WakuRelay) subscribeToTopic(userCtx context.Context, pubsubTopic string, subscription *Subscription, sub *pubsub.Subscription) {
func (w *WakuRelay) subscribeToTopic(pubsubTopic string, sub *pubsub.Subscription) {
defer w.wg.Done()
ctx, err := tag.New(w.ctx, tag.Insert(metrics.KeyType, "relay"))
@ -360,39 +335,25 @@ func (w *WakuRelay) subscribeToTopic(userCtx context.Context, pubsubTopic string
subChannel := w.nextMessage(w.ctx, sub)
for {
select {
case <-userCtx.Done():
return
case <-ctx.Done():
return
case <-subscription.quit:
func(topic string) {
subscription.Lock()
defer subscription.Unlock()
if subscription.closed {
return
}
subscription.closed = true
if w.bcaster != nil {
<-w.bcaster.WaitUnregister(&topic, subscription.C) // Remove from broadcast list
}
close(subscription.C)
}(pubsubTopic)
// TODO: if there are no more relay subscriptions, close the pubsub subscription
case msg := <-subChannel:
if msg == nil {
case msg, ok := <-subChannel:
if !ok {
return
}
stats.Record(ctx, metrics.Messages.M(1))
wakuMessage := &pb.WakuMessage{}
if err := proto.Unmarshal(msg.Data, wakuMessage); err != nil {
w.log.Error("decoding message", zap.Error(err))
return
}
payloadSizeInBytes := len(wakuMessage.Payload)
payloadSizeInKb := payloadSizeInBytes / 1000
stats.Record(ctx, metrics.Messages.M(1), metrics.MessageSize.M(int64(payloadSizeInKb)))
envelope := waku_proto.NewEnvelope(wakuMessage, w.timesource.Now().UnixNano(), pubsubTopic)
w.log.Debug("waku.relay received", logging.HexString("hash", envelope.Hash()))
w.log.Debug("waku.relay received", zap.String("pubsubTopic", pubsubTopic), logging.HexString("hash", envelope.Hash()), zap.Int64("receivedTime", envelope.Index().ReceiverTime), zap.Int("payloadSizeBytes", payloadSizeInBytes))
if w.bcaster != nil {
w.bcaster.Submit(envelope)

View File

@ -24,12 +24,6 @@ const MAX_EPOCH_GAP = int64(MAX_CLOCK_GAP_SECONDS / rln.EPOCH_UNIT_SECONDS)
// Acceptable roots for merkle root validation of incoming messages
const AcceptableRootWindowSize = 5
type AppInfo struct {
Application string
AppIdentifier string
Version string
}
type RegistrationHandler = func(tx *types.Transaction)
type SpamHandler = func(message *pb.WakuMessage) error

View File

@ -0,0 +1,335 @@
package dynamic
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"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/group_manager"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
"github.com/waku-org/go-zerokit-rln/rln"
om "github.com/wk8/go-ordered-map"
"go.uber.org/zap"
)
var RLNAppInfo = keystore.AppInfo{
Application: "go-waku-rln-relay",
AppIdentifier: "01234567890abcdef",
Version: "0.1",
}
type DynamicGroupManager struct {
rln *rln.RLN
log *zap.Logger
cancel context.CancelFunc
wg sync.WaitGroup
identityCredential *rln.IdentityCredential
membershipIndex *rln.MembershipIndex
membershipContractAddress common.Address
ethClientAddress string
ethClient *ethclient.Client
// ethAccountPrivateKey is required for signing transactions
// TODO may need to erase this ethAccountPrivateKey when is not used
// TODO may need to make ethAccountPrivateKey mandatory
ethAccountPrivateKey *ecdsa.PrivateKey
eventHandler RegistrationEventHandler
registrationHandler RegistrationHandler
chainId *big.Int
rlnContract *contracts.RLN
membershipFee *big.Int
saveKeystore bool
keystorePath string
keystorePassword string
rootTracker *group_manager.MerkleRootTracker
}
func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) error {
toRemoveTable := om.New()
toInsertTable := om.New()
for _, event := range events {
if event.Raw.Removed {
var indexes []uint64
i_idx, ok := toRemoveTable.Get(event.Raw.BlockNumber)
if ok {
indexes = i_idx.([]uint64)
}
indexes = append(indexes, event.Index.Uint64())
toRemoveTable.Set(event.Raw.BlockNumber, indexes)
} else {
var eventsPerBlock []*contracts.RLNMemberRegistered
i_evt, ok := toInsertTable.Get(event.Raw.BlockNumber)
if ok {
eventsPerBlock = i_evt.([]*contracts.RLNMemberRegistered)
}
eventsPerBlock = append(eventsPerBlock, event)
toInsertTable.Set(event.Raw.BlockNumber, eventsPerBlock)
}
}
err := gm.RemoveMembers(toRemoveTable)
if err != nil {
return err
}
err = gm.InsertMembers(toInsertTable)
if err != nil {
return err
}
return nil
}
type RegistrationHandler = func(tx *types.Transaction)
func NewDynamicGroupManager(
ethClientAddr string,
ethAccountPrivateKey *ecdsa.PrivateKey,
memContractAddr common.Address,
keystorePath string,
keystorePassword string,
saveKeystore bool,
registrationHandler RegistrationHandler,
log *zap.Logger,
) (*DynamicGroupManager, error) {
log = log.Named("rln-dynamic")
path := keystorePath
if path == "" {
log.Warn("keystore: no credentials path set, using default path", zap.String("path", keystore.RLN_CREDENTIALS_FILENAME))
path = keystore.RLN_CREDENTIALS_FILENAME
}
password := keystorePassword
if password == "" {
log.Warn("keystore: no credentials password set, using default password", zap.String("password", keystore.RLN_CREDENTIALS_PASSWORD))
password = keystore.RLN_CREDENTIALS_PASSWORD
}
return &DynamicGroupManager{
membershipContractAddress: memContractAddr,
ethClientAddress: ethClientAddr,
ethAccountPrivateKey: ethAccountPrivateKey,
registrationHandler: registrationHandler,
eventHandler: handler,
saveKeystore: saveKeystore,
keystorePath: path,
keystorePassword: password,
log: log,
}, nil
}
func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) {
auth, err := bind.NewKeyedTransactorWithChainID(gm.ethAccountPrivateKey, gm.chainId)
if err != nil {
return nil, err
}
auth.Context = ctx
return gm.rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx})
}
func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error {
if gm.cancel != nil {
return errors.New("already started")
}
ctx, cancel := context.WithCancel(ctx)
gm.cancel = cancel
gm.log.Info("mounting rln-relay in on-chain/dynamic mode")
backend, err := ethclient.Dial(gm.ethClientAddress)
if err != nil {
return err
}
gm.ethClient = backend
gm.rln = rlnInstance
gm.rootTracker = rootTracker
gm.chainId, err = backend.ChainID(ctx)
if err != nil {
return err
}
gm.rlnContract, err = contracts.NewRLN(gm.membershipContractAddress, backend)
if err != nil {
return err
}
// check if the contract exists by calling a static function
gm.membershipFee, err = gm.getMembershipFee(ctx)
if err != nil {
return err
}
if gm.identityCredential == nil && gm.keystorePassword != "" && gm.keystorePath != "" {
credentials, err := keystore.GetMembershipCredentials(gm.log,
gm.keystorePath,
gm.keystorePassword,
RLNAppInfo,
nil,
[]keystore.MembershipContract{{
ChainId: gm.chainId.String(),
Address: gm.membershipContractAddress.Hex(),
}})
if err != nil {
return err
}
// TODO: accept an index from the config
if len(credentials) != 0 {
gm.identityCredential = &credentials[0].IdentityCredential
gm.membershipIndex = &credentials[0].MembershipGroups[0].TreeIndex
}
}
if gm.identityCredential == nil && gm.ethAccountPrivateKey == nil {
return errors.New("either a credentials path or a private key must be specified")
}
// prepare rln membership key pair
if gm.identityCredential == nil && gm.ethAccountPrivateKey != nil {
gm.log.Info("no rln-relay key is provided, generating one")
identityCredential, err := rlnInstance.MembershipKeyGen()
if err != nil {
return err
}
gm.identityCredential = identityCredential
// register the rln-relay peer to the membership contract
gm.membershipIndex, err = gm.Register(ctx)
if err != nil {
return err
}
err = gm.persistCredentials()
if err != nil {
return err
}
gm.log.Info("registered peer into the membership contract")
}
if gm.identityCredential == nil || gm.membershipIndex == nil {
return errors.New("no credentials available")
}
if err = gm.HandleGroupUpdates(ctx, gm.eventHandler); err != nil {
return err
}
return nil
}
func (gm *DynamicGroupManager) persistCredentials() error {
if !gm.saveKeystore {
return nil
}
if gm.identityCredential == nil || gm.membershipIndex == nil {
return errors.New("no credentials to persist")
}
keystoreCred := keystore.MembershipCredentials{
IdentityCredential: *gm.identityCredential,
MembershipGroups: []keystore.MembershipGroup{{
TreeIndex: *gm.membershipIndex,
MembershipContract: keystore.MembershipContract{
ChainId: gm.chainId.String(),
Address: gm.membershipContractAddress.String(),
},
}},
}
err := keystore.AddMembershipCredentials(gm.keystorePath, []keystore.MembershipCredentials{keystoreCred}, gm.keystorePassword, RLNAppInfo, keystore.DefaultSeparator)
if err != nil {
return fmt.Errorf("failed to persist credentials: %w", err)
}
return nil
}
func (gm *DynamicGroupManager) InsertMembers(toInsert *om.OrderedMap) error {
for pair := toInsert.Oldest(); pair != nil; pair = pair.Next() {
events := pair.Value.([]*contracts.RLNMemberRegistered) // TODO: should these be sortered by index? we assume all members arrive in order
for _, evt := range events {
pubkey := rln.Bytes32(evt.Pubkey.Bytes())
// TODO: should we track indexes to identify missing?
err := gm.rln.InsertMember(pubkey)
if err != nil {
gm.log.Error("inserting member into merkletree", zap.Error(err))
return err
}
}
_, err := gm.rootTracker.UpdateLatestRoot(pair.Key.(uint64))
if err != nil {
return err
}
}
return nil
}
func (gm *DynamicGroupManager) RemoveMembers(toRemove *om.OrderedMap) error {
for pair := toRemove.Newest(); pair != nil; pair = pair.Prev() {
memberIndexes := pair.Value.([]uint64)
for _, index := range memberIndexes {
err := gm.rln.DeleteMember(uint(index))
if err != nil {
gm.log.Error("deleting member", zap.Error(err))
return err
}
}
gm.rootTracker.Backfill(pair.Key.(uint64))
}
return nil
}
func (gm *DynamicGroupManager) IdentityCredentials() (rln.IdentityCredential, error) {
if gm.identityCredential == nil {
return rln.IdentityCredential{}, errors.New("identity credential has not been setup")
}
return *gm.identityCredential, nil
}
func (gm *DynamicGroupManager) SetCredentials(identityCredential *rln.IdentityCredential, index *rln.MembershipIndex) {
gm.identityCredential = identityCredential
gm.membershipIndex = index
}
func (gm *DynamicGroupManager) MembershipIndex() (rln.MembershipIndex, error) {
if gm.membershipIndex == nil {
return 0, errors.New("membership index has not been setup")
}
return *gm.membershipIndex, nil
}
func (gm *DynamicGroupManager) Stop() {
if gm.cancel == nil {
return
}
gm.cancel()
gm.wg.Wait()
}

View File

@ -0,0 +1,259 @@
package dynamic
import (
"context"
"crypto/ecdsa"
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
r "github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
func ToBigInt(i []byte) *big.Int {
result := new(big.Int)
result.SetBytes(i[:])
return result
}
func register(ctx context.Context, backend *ethclient.Client, membershipFee *big.Int, idComm r.IDCommitment, ethAccountPrivateKey *ecdsa.PrivateKey, rlnContract *contracts.RLN, chainID *big.Int, registrationHandler RegistrationHandler, log *zap.Logger) (*r.MembershipIndex, error) {
auth, err := bind.NewKeyedTransactorWithChainID(ethAccountPrivateKey, chainID)
if err != nil {
return nil, err
}
auth.Value = membershipFee
auth.Context = ctx
log.Debug("registering an id commitment", zap.Binary("idComm", idComm[:]))
// registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress
tx, err := rlnContract.Register(auth, ToBigInt(idComm[:]))
if err != nil {
return nil, err
}
log.Info("transaction broadcasted", zap.String("transactionHash", tx.Hash().Hex()))
if registrationHandler != nil {
registrationHandler(tx)
}
txReceipt, err := bind.WaitMined(ctx, backend, tx)
if err != nil {
return nil, err
}
if txReceipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("transaction reverted")
}
// the receipt topic holds the hash of signature of the raised events
evt, err := rlnContract.ParseMemberRegistered(*txReceipt.Logs[0])
if err != nil {
return nil, err
}
var eventIdComm r.IDCommitment = r.Bytes32(evt.Pubkey.Bytes())
log.Debug("the identity commitment key extracted from tx log", zap.Binary("eventIdComm", eventIdComm[:]))
if eventIdComm != idComm {
return nil, errors.New("invalid id commitment key")
}
result := new(r.MembershipIndex)
*result = r.MembershipIndex(uint(evt.Index.Int64()))
// debug "the index of registered identity commitment key", eventIndex=eventIndex
log.Debug("the index of registered identity commitment key", zap.Uint("eventIndex", uint(*result)))
return result, nil
}
// Register registers the public key of the rlnPeer which is rlnPeer.membershipKeyPair.publicKey
// into the membership contract whose address is in rlnPeer.membershipContractAddress
func (gm *DynamicGroupManager) Register(ctx context.Context) (*r.MembershipIndex, error) {
return register(ctx,
gm.ethClient,
gm.membershipFee,
gm.identityCredential.IDCommitment,
gm.ethAccountPrivateKey,
gm.rlnContract,
gm.chainId,
gm.registrationHandler,
gm.log)
}
// 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 {
err := gm.loadOldEvents(ctx, gm.rlnContract, handler)
if err != nil {
return err
}
errCh := make(chan error)
gm.wg.Add(1)
go gm.watchNewEvents(ctx, gm.rlnContract, handler, gm.log, errCh)
return <-errCh
}
func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, rlnContract *contracts.RLN, handler RegistrationEventHandler) error {
events, err := gm.getEvents(ctx, 0, nil)
if err != nil {
return err
}
return handler(gm, events)
}
func (gm *DynamicGroupManager) watchNewEvents(ctx context.Context, rlnContract *contracts.RLN, 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.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(5000000) // TODO: tune this
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.ethClient.BlockByNumber(ctx, nil)
if err != nil {
return nil, err
}
blockNumber := block.Number().Uint64()
toBlock = &blockNumber
}
batchSize := maxBatchSize
additiveFactor := uint64(float64(batchSize) * additiveFactorMultiplier)
currentBlockNum := from
for currentBlockNum < *toBlock {
start := currentBlockNum
end := currentBlockNum + batchSize
if end > *toBlock {
end = *toBlock
}
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
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.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,34 +1,133 @@
package group_manager
import "github.com/waku-org/go-zerokit-rln/rln"
import (
"sync"
type MerkleRootTracker struct {
rln *rln.RLN
acceptableRootWindowSize int
validMerkleRoots []rln.MerkleNode
"github.com/waku-org/go-zerokit-rln/rln"
)
type RootsPerBlock struct {
root rln.MerkleNode
blockNumber uint64
}
func NewMerkleRootTracker(acceptableRootWindowSize int, rlnInstance *rln.RLN) *MerkleRootTracker {
return &MerkleRootTracker{
type MerkleRootTracker struct {
sync.RWMutex
rln *rln.RLN
acceptableRootWindowSize int
validMerkleRoots []RootsPerBlock
merkleRootBuffer []RootsPerBlock
}
const maxBufferSize = 20
func NewMerkleRootTracker(acceptableRootWindowSize int, rlnInstance *rln.RLN) (*MerkleRootTracker, error) {
result := &MerkleRootTracker{
acceptableRootWindowSize: acceptableRootWindowSize,
rln: rlnInstance,
}
}
func (m *MerkleRootTracker) Sync() error {
root, err := m.rln.GetMerkleRoot()
_, err := result.UpdateLatestRoot(0)
if err != nil {
return err
return nil, err
}
m.validMerkleRoots = append(m.validMerkleRoots, root)
return result, nil
}
func (m *MerkleRootTracker) Backfill(fromBlockNumber uint64) {
m.Lock()
defer m.Unlock()
numBlocks := 0
for i := len(m.validMerkleRoots) - 1; i >= 0; i-- {
if m.validMerkleRoots[i].blockNumber >= fromBlockNumber {
numBlocks++
}
}
if numBlocks == 0 {
return
}
// Remove last roots
rootsToPop := numBlocks
if len(m.validMerkleRoots) < rootsToPop {
rootsToPop = len(m.validMerkleRoots)
}
m.validMerkleRoots = m.validMerkleRoots[0 : len(m.validMerkleRoots)-rootsToPop]
if len(m.merkleRootBuffer) == 0 {
return
}
// Backfill the tree's acceptable roots
rootsToRestore := numBlocks
bufferLen := len(m.merkleRootBuffer)
if bufferLen < rootsToRestore {
rootsToRestore = bufferLen
}
for i := 0; i < rootsToRestore; i++ {
x, newRootBuffer := m.merkleRootBuffer[len(m.merkleRootBuffer)-1], m.merkleRootBuffer[:len(m.merkleRootBuffer)-1] // Pop
m.validMerkleRoots = append([]RootsPerBlock{x}, m.validMerkleRoots...)
m.merkleRootBuffer = newRootBuffer
}
}
func (m *MerkleRootTracker) UpdateLatestRoot(blockNumber uint64) (rln.MerkleNode, error) {
m.Lock()
defer m.Unlock()
root, err := m.rln.GetMerkleRoot()
if err != nil {
return [32]byte{}, err
}
m.pushRoot(blockNumber, root)
return root, nil
}
func (m *MerkleRootTracker) pushRoot(blockNumber uint64, root [32]byte) {
m.validMerkleRoots = append(m.validMerkleRoots, RootsPerBlock{
root: root,
blockNumber: blockNumber,
})
// Maintain valid merkle root window
if len(m.validMerkleRoots) > m.acceptableRootWindowSize {
m.merkleRootBuffer = append(m.merkleRootBuffer, m.validMerkleRoots[0])
m.validMerkleRoots = m.validMerkleRoots[1:]
}
return nil
// Maintain merkle root buffer
if len(m.merkleRootBuffer) > maxBufferSize {
m.merkleRootBuffer = m.merkleRootBuffer[1:]
}
}
func (m *MerkleRootTracker) Roots() []rln.MerkleNode {
return m.validMerkleRoots
m.RLock()
defer m.RUnlock()
result := make([]rln.MerkleNode, len(m.validMerkleRoots))
for i := range m.validMerkleRoots {
result[i] = m.validMerkleRoots[i].root
}
return result
}
func (m *MerkleRootTracker) Buffer() []rln.MerkleNode {
m.RLock()
defer m.RUnlock()
result := make([]rln.MerkleNode, len(m.merkleRootBuffer))
for i := range m.merkleRootBuffer {
result[i] = m.merkleRootBuffer[i].root
}
return result
}

View File

@ -45,18 +45,9 @@ func (gm *StaticGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, r
gm.rln = rlnInstance
gm.rootTracker = rootTracker
err := rootTracker.Sync()
if err != nil {
return err
}
// add members to the Merkle tree
for _, member := range gm.group {
if err := rlnInstance.InsertMember(member); err != nil {
return err
}
err = rootTracker.Sync()
for i, member := range gm.group {
err := gm.insertMember(member, uint64(i+1))
if err != nil {
return err
}
@ -67,8 +58,9 @@ func (gm *StaticGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, r
return nil
}
func (gm *StaticGroupManager) InsertMember(pubkey rln.IDCommitment) error {
gm.log.Debug("a new key is added", zap.Binary("pubkey", pubkey[:]))
func (gm *StaticGroupManager) insertMember(pubkey rln.IDCommitment, index uint64) error {
gm.log.Debug("a new key is added", zap.Binary("pubkey", pubkey[:]), zap.Uint64("index", index))
// assuming all the members arrive in order
err := gm.rln.InsertMember(pubkey)
if err != nil {
@ -76,7 +68,7 @@ func (gm *StaticGroupManager) InsertMember(pubkey rln.IDCommitment) error {
return err
}
err = gm.rootTracker.Sync()
_, err = gm.rootTracker.UpdateLatestRoot(index)
if err != nil {
return err
}

View File

@ -0,0 +1,346 @@
package keystore
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
const RLN_CREDENTIALS_FILENAME = "rlnCredentials.json"
const RLN_CREDENTIALS_PASSWORD = "password"
type MembershipContract struct {
ChainId string `json:"chainId"`
Address string `json:"address"`
}
type MembershipGroup struct {
MembershipContract MembershipContract `json:"membershipContract"`
TreeIndex rln.MembershipIndex `json:"treeIndex"`
}
type MembershipCredentials struct {
IdentityCredential rln.IdentityCredential `json:"identityCredential"`
MembershipGroups []MembershipGroup `json:"membershipGroups"`
}
type AppInfo struct {
Application string `json:"application"`
AppIdentifier string `json:"appIdentifier"`
Version string `json:"version"`
}
type AppKeystore struct {
Application string `json:"application"`
AppIdentifier string `json:"appIdentifier"`
Credentials []keystore.CryptoJSON `json:"credentials"`
Version string `json:"version"`
}
const DefaultSeparator = "\n"
func (m MembershipCredentials) Equals(other MembershipCredentials) bool {
if !rln.IdentityCredentialEquals(m.IdentityCredential, other.IdentityCredential) {
return false
}
for _, x := range m.MembershipGroups {
found := false
for _, y := range other.MembershipGroups {
if x.Equals(y) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (m MembershipGroup) Equals(other MembershipGroup) bool {
return m.MembershipContract.Equals(other.MembershipContract) && m.TreeIndex == other.TreeIndex
}
func (m MembershipContract) Equals(other MembershipContract) bool {
return m.Address == other.Address && m.ChainId == other.ChainId
}
func CreateAppKeystore(path string, appInfo AppInfo, separator string) error {
if separator == "" {
separator = DefaultSeparator
}
keystore := AppKeystore{
Application: appInfo.Application,
AppIdentifier: appInfo.AppIdentifier,
Version: appInfo.Version,
}
b, err := json.Marshal(keystore)
if err != nil {
return err
}
b = append(b, []byte(separator)...)
buffer := new(bytes.Buffer)
err = json.Compact(buffer, b)
if err != nil {
return err
}
return ioutil.WriteFile(path, buffer.Bytes(), 0600)
}
func LoadAppKeystore(path string, appInfo AppInfo, separator string) (AppKeystore, error) {
if separator == "" {
separator = DefaultSeparator
}
_, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
// If no keystore exists at path we create a new empty one with passed keystore parameters
err = CreateAppKeystore(path, appInfo, separator)
if err != nil {
return AppKeystore{}, err
}
} else {
return AppKeystore{}, err
}
}
src, err := os.ReadFile(path)
if err != nil {
return AppKeystore{}, err
}
for _, keystoreBytes := range bytes.Split(src, []byte(separator)) {
if len(keystoreBytes) == 0 {
continue
}
keystore := AppKeystore{}
err := json.Unmarshal(keystoreBytes, &keystore)
if err != nil {
continue
}
if keystore.AppIdentifier == appInfo.AppIdentifier && keystore.Application == appInfo.Application && keystore.Version == appInfo.Version {
return keystore, nil
}
}
return AppKeystore{}, errors.New("no keystore found")
}
func filterCredential(credential MembershipCredentials, filterIdentityCredentials []MembershipCredentials, filterMembershipContracts []MembershipContract) *MembershipCredentials {
if len(filterIdentityCredentials) != 0 {
found := false
for _, filterCreds := range filterIdentityCredentials {
if filterCreds.Equals(credential) {
found = true
}
}
if !found {
return nil
}
}
if len(filterMembershipContracts) != 0 {
var membershipGroupsIntersection []MembershipGroup
for _, filterContract := range filterMembershipContracts {
for _, credentialGroups := range credential.MembershipGroups {
if filterContract.Equals(credentialGroups.MembershipContract) {
membershipGroupsIntersection = append(membershipGroupsIntersection, credentialGroups)
}
}
}
if len(membershipGroupsIntersection) != 0 {
// If we have a match on some groups, we return the credential with filtered groups
return &MembershipCredentials{
IdentityCredential: credential.IdentityCredential,
MembershipGroups: membershipGroupsIntersection,
}
} else {
return nil
}
}
// We hit this return only if
// - filterIdentityCredentials.len() == 0 and filterMembershipContracts.len() == 0 (no filter)
// - filterIdentityCredentials.len() != 0 and filterMembershipContracts.len() == 0 (filter only on identity credential)
// Indeed, filterMembershipContracts.len() != 0 will have its exclusive return based on all values of membershipGroupsIntersection.len()
return &credential
}
func GetMembershipCredentials(logger *zap.Logger, credentialsPath string, password string, appInfo AppInfo, filterIdentityCredentials []MembershipCredentials, filterMembershipContracts []MembershipContract) ([]MembershipCredentials, error) {
k, err := LoadAppKeystore(credentialsPath, appInfo, DefaultSeparator)
if err != nil {
return nil, err
}
var result []MembershipCredentials
for _, credential := range k.Credentials {
credentialsBytes, err := keystore.DecryptDataV3(credential, password)
if err != nil {
return nil, err
}
var credentials MembershipCredentials
err = json.Unmarshal(credentialsBytes, &credentials)
if err != nil {
return nil, err
}
filteredCredential := filterCredential(credentials, filterIdentityCredentials, filterMembershipContracts)
if filteredCredential != nil {
result = append(result, *filteredCredential)
}
}
return result, nil
}
// Adds a sequence of membership credential to the keystore matching the application, appIdentifier and version filters.
func AddMembershipCredentials(path string, credentials []MembershipCredentials, password string, appInfo AppInfo, separator string) error {
k, err := LoadAppKeystore(path, appInfo, DefaultSeparator)
if err != nil {
return err
}
var credentialsToAdd []MembershipCredentials
for _, newCredential := range credentials {
// A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential
found := -1
for i, existingCredentials := range k.Credentials {
credentialsBytes, err := keystore.DecryptDataV3(existingCredentials, password)
if err != nil {
continue
}
var credentials MembershipCredentials
err = json.Unmarshal(credentialsBytes, &credentials)
if err != nil {
continue
}
if rln.IdentityCredentialEquals(credentials.IdentityCredential, newCredential.IdentityCredential) {
// idCredential is present in keystore. We add the input credential membership group to the one contained in the decrypted keystore credential (we deduplicate groups using sets)
allMemberships := append(credentials.MembershipGroups, newCredential.MembershipGroups...)
// we define the updated credential with the updated membership sets
updatedCredential := MembershipCredentials{
IdentityCredential: newCredential.IdentityCredential,
MembershipGroups: allMemberships,
}
// we re-encrypt creating a new keyfile
b, err := json.Marshal(updatedCredential)
if err != nil {
return err
}
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
if err != nil {
return err
}
// we update the original credential field in keystoreCredentials
k.Credentials[i] = encryptedCredentials
found = i
// We stop decrypting other credentials in the keystore
break
}
}
if found == -1 {
credentialsToAdd = append(credentialsToAdd, newCredential)
}
}
for _, c := range credentialsToAdd {
b, err := json.Marshal(c)
if err != nil {
return err
}
encryptedCredentials, err := keystore.EncryptDataV3(b, []byte(password), keystore.StandardScryptN, keystore.StandardScryptP)
if err != nil {
return err
}
k.Credentials = append(k.Credentials, encryptedCredentials)
}
return save(k, path, separator)
}
// Safely saves a Keystore's JsonNode to disk.
// If exists, the destination file is renamed with extension .bkp; the file is written at its destination and the .bkp file is removed if write is successful, otherwise is restored
func save(keystore AppKeystore, path string, separator string) error {
// We first backup the current keystore
_, err := os.Stat(path)
if err == nil {
err := os.Rename(path, path+".bkp")
if err != nil {
return err
}
}
if separator == "" {
separator = DefaultSeparator
}
b, err := json.Marshal(keystore)
if err != nil {
return err
}
b = append(b, []byte(separator)...)
buffer := new(bytes.Buffer)
err = json.Compact(buffer, b)
if err != nil {
restoreErr := os.Rename(path, path+".bkp")
if restoreErr != nil {
return fmt.Errorf("could not restore backup file: %w", restoreErr)
}
return err
}
err = ioutil.WriteFile(path, buffer.Bytes(), 0600)
if err != nil {
restoreErr := os.Rename(path, path+".bkp")
if restoreErr != nil {
return fmt.Errorf("could not restore backup file: %w", restoreErr)
}
return err
}
// The write went fine, so we can remove the backup keystore
_, err = os.Stat(path + ".bkp")
if err == nil {
err := os.Remove(path + ".bkp")
if err != nil {
return err
}
}
return nil
}

View File

@ -1,175 +0,0 @@
package rln
import (
"context"
"crypto/ecdsa"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/timesource"
r "github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
func RlnRelayStatic(
ctx context.Context,
relay *relay.WakuRelay,
group []r.IDCommitment,
memKeyPair r.IdentityCredential,
memIndex r.MembershipIndex,
pubsubTopic string,
contentTopic string,
spamHandler SpamHandler,
timesource timesource.Timesource,
log *zap.Logger,
) (*WakuRLNRelay, error) {
log = log.Named("rln-static")
log.Info("mounting rln-relay in off-chain/static mode")
// check the peer's index and the inclusion of user's identity commitment in the group
if memKeyPair.IDCommitment != group[int(memIndex)] {
return nil, errors.New("peer's IDCommitment does not match commitment in group")
}
rlnInstance, err := r.NewRLN()
if err != nil {
return nil, err
}
// create the WakuRLNRelay
rlnPeer := &WakuRLNRelay{
ctx: ctx,
membershipKeyPair: &memKeyPair,
membershipIndex: memIndex,
RLN: rlnInstance,
pubsubTopic: pubsubTopic,
contentTopic: contentTopic,
log: log,
timesource: timesource,
nullifierLog: make(map[r.Nullifier][]r.ProofMetadata),
}
root, err := rlnPeer.RLN.GetMerkleRoot()
if err != nil {
return nil, err
}
rlnPeer.validMerkleRoots = append(rlnPeer.validMerkleRoots, root)
// add members to the Merkle tree
for _, member := range group {
if err := rlnPeer.insertMember(member); err != nil {
return nil, err
}
}
// adds a topic validator for the supplied pubsub topic at the relay protocol
// messages published on this pubsub topic will be relayed upon a successful validation, otherwise they will be dropped
// the topic validator checks for the correct non-spamming proof of the message
err = rlnPeer.addValidator(relay, pubsubTopic, contentTopic, spamHandler)
if err != nil {
return nil, err
}
log.Info("rln relay topic validator mounted", zap.String("pubsubTopic", pubsubTopic), zap.String("contentTopic", contentTopic))
return rlnPeer, nil
}
func RlnRelayDynamic(
ctx context.Context,
relay *relay.WakuRelay,
ethClientAddr string,
ethAccountPrivateKey *ecdsa.PrivateKey,
memContractAddr common.Address,
memKeyPair *r.IdentityCredential,
memIndex r.MembershipIndex,
pubsubTopic string,
contentTopic string,
spamHandler SpamHandler,
registrationHandler RegistrationHandler,
timesource timesource.Timesource,
log *zap.Logger,
) (*WakuRLNRelay, error) {
log = log.Named("rln-dynamic")
log.Info("mounting rln-relay in onchain/dynamic mode")
rlnInstance, err := r.NewRLN()
if err != nil {
return nil, err
}
// create the WakuRLNRelay
rlnPeer := &WakuRLNRelay{
ctx: ctx,
membershipIndex: memIndex,
membershipContractAddress: memContractAddr,
ethClientAddress: ethClientAddr,
ethAccountPrivateKey: ethAccountPrivateKey,
RLN: rlnInstance,
pubsubTopic: pubsubTopic,
contentTopic: contentTopic,
log: log,
timesource: timesource,
nullifierLog: make(map[r.Nullifier][]r.ProofMetadata),
registrationHandler: registrationHandler,
lastIndexLoaded: -1,
}
root, err := rlnPeer.RLN.GetMerkleRoot()
if err != nil {
return nil, err
}
rlnPeer.validMerkleRoots = append(rlnPeer.validMerkleRoots, root)
// prepare rln membership key pair
if memKeyPair == nil && ethAccountPrivateKey != nil {
log.Debug("no rln-relay key is provided, generating one")
memKeyPair, err = rlnInstance.MembershipKeyGen()
if err != nil {
return nil, err
}
rlnPeer.membershipKeyPair = memKeyPair
// register the rln-relay peer to the membership contract
membershipIndex, err := rlnPeer.Register(ctx)
if err != nil {
return nil, err
}
rlnPeer.membershipIndex = *membershipIndex
log.Info("registered peer into the membership contract")
} else if memKeyPair != nil {
rlnPeer.membershipKeyPair = memKeyPair
}
handler := func(pubkey r.IDCommitment, index r.MembershipIndex) error {
return rlnPeer.insertMember(pubkey)
}
errChan := make(chan error)
go rlnPeer.HandleGroupUpdates(handler, errChan)
err = <-errChan
if err != nil {
return nil, err
}
// adds a topic validator for the supplied pubsub topic at the relay protocol
// messages published on this pubsub topic will be relayed upon a successful validation, otherwise they will be dropped
// the topic validator checks for the correct non-spamming proof of the message
err = rlnPeer.addValidator(relay, pubsubTopic, contentTopic, spamHandler)
if err != nil {
return nil, err
}
log.Info("rln relay topic validator mounted", zap.String("pubsubTopic", pubsubTopic), zap.String("contentTopic", contentTopic))
return rlnPeer, nil
}

View File

@ -21,12 +21,6 @@ import (
proto "google.golang.org/protobuf/proto"
)
var RLNAppInfo = AppInfo{
Application: "go-waku-rln-relay",
AppIdentifier: "01234567890abcdef",
Version: "0.1",
}
type GroupManager interface {
Start(ctx context.Context, rln *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error
IdentityCredentials() (rln.IdentityCredential, error)
@ -68,11 +62,16 @@ func New(
return nil, err
}
rootTracker, err := group_manager.NewMerkleRootTracker(AcceptableRootWindowSize, rlnInstance)
if err != nil {
return nil, err
}
// create the WakuRLNRelay
rlnPeer := &WakuRLNRelay{
RLN: rlnInstance,
groupManager: groupManager,
rootTracker: group_manager.NewMerkleRootTracker(AcceptableRootWindowSize, rlnInstance),
rootTracker: rootTracker,
pubsubTopic: pubsubTopic,
contentTopic: contentTopic,
relay: relay,
@ -367,3 +366,11 @@ func (rlnRelay *WakuRLNRelay) generateProof(input []byte, epoch rln.Epoch) (*pb.
RlnIdentifier: proof.RLNIdentifier[:],
}, nil
}
func (rlnRelay *WakuRLNRelay) IdentityCredential() (rln.IdentityCredential, error) {
return rlnRelay.groupManager.IdentityCredentials()
}
func (rlnRelay *WakuRLNRelay) MembershipIndex() (uint, error) {
return rlnRelay.groupManager.MembershipIndex()
}

View File

@ -1,227 +0,0 @@
package rln
import (
"context"
"crypto/ecdsa"
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
r "github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
var MEMBERSHIP_FEE = big.NewInt(1000000000000000) // wei - 0.001 eth
func toBigInt(i []byte) *big.Int {
result := new(big.Int)
result.SetBytes(i[:])
return result
}
func register(ctx context.Context, idComm r.IDCommitment, ethAccountPrivateKey *ecdsa.PrivateKey, ethClientAddress string, membershipContractAddress common.Address, registrationHandler RegistrationHandler, log *zap.Logger) (*r.MembershipIndex, error) {
backend, err := ethclient.Dial(ethClientAddress)
if err != nil {
return nil, err
}
defer backend.Close()
chainID, err := backend.ChainID(ctx)
if err != nil {
return nil, err
}
auth, err := bind.NewKeyedTransactorWithChainID(ethAccountPrivateKey, chainID)
if err != nil {
return nil, err
}
auth.Value = MEMBERSHIP_FEE
auth.Context = ctx
rlnContract, err := contracts.NewRLN(membershipContractAddress, backend)
if err != nil {
return nil, err
}
log.Debug("registering an id commitment", zap.Binary("idComm", idComm[:]))
// registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress
tx, err := rlnContract.Register(auth, toBigInt(idComm[:]))
if err != nil {
return nil, err
}
log.Info("transaction broadcasted", zap.String("transactionHash", tx.Hash().Hex()))
if registrationHandler != nil {
registrationHandler(tx)
}
txReceipt, err := bind.WaitMined(ctx, backend, tx)
if err != nil {
return nil, err
}
if txReceipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("transaction reverted")
}
// the receipt topic holds the hash of signature of the raised events
evt, err := rlnContract.ParseMemberRegistered(*txReceipt.Logs[0])
if err != nil {
return nil, err
}
var eventIdComm r.IDCommitment = r.Bytes32(evt.Pubkey.Bytes())
log.Debug("the identity commitment key extracted from tx log", zap.Binary("eventIdComm", eventIdComm[:]))
if eventIdComm != idComm {
return nil, errors.New("invalid id commitment key")
}
result := new(r.MembershipIndex)
*result = r.MembershipIndex(uint(evt.Index.Int64()))
// debug "the index of registered identity commitment key", eventIndex=eventIndex
log.Debug("the index of registered identity commitment key", zap.Uint("eventIndex", uint(*result)))
return result, nil
}
// Register registers the public key of the rlnPeer which is rlnPeer.membershipKeyPair.publicKey
// into the membership contract whose address is in rlnPeer.membershipContractAddress
func (rln *WakuRLNRelay) Register(ctx context.Context) (*r.MembershipIndex, error) {
pk := rln.membershipKeyPair.IDCommitment
return register(ctx, pk, rln.ethAccountPrivateKey, rln.ethClientAddress, rln.membershipContractAddress, rln.registrationHandler, rln.log)
}
// the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface
type RegistrationEventHandler = func(pubkey r.IDCommitment, index r.MembershipIndex) error
func (rln *WakuRLNRelay) processLogs(evt *contracts.RLNMemberRegistered, handler RegistrationEventHandler) error {
if evt == nil {
return nil
}
var pubkey r.IDCommitment = r.Bytes32(evt.Pubkey.Bytes())
index := evt.Index.Int64()
if index <= rln.lastIndexLoaded {
return nil
}
rln.lastIndexLoaded = index
return handler(pubkey, r.MembershipIndex(uint(evt.Index.Int64())))
}
// 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 (rln *WakuRLNRelay) HandleGroupUpdates(handler RegistrationEventHandler, errChan chan<- error) {
defer close(errChan)
backend, err := ethclient.Dial(rln.ethClientAddress)
if err != nil {
errChan <- err
return
}
rln.ethClient = backend
rlnContract, err := contracts.NewRLN(rln.membershipContractAddress, backend)
if err != nil {
errChan <- err
return
}
err = rln.loadOldEvents(rlnContract, handler)
if err != nil {
errChan <- err
return
}
doneCh := make(chan struct{})
errCh := make(chan error)
go rln.watchNewEvents(rlnContract, handler, rln.log, doneCh, errCh)
select {
case <-doneCh:
return
case err := <-errCh:
errChan <- err
return
}
}
func (rln *WakuRLNRelay) loadOldEvents(rlnContract *contracts.RLN, handler RegistrationEventHandler) error {
logIterator, err := rlnContract.FilterMemberRegistered(&bind.FilterOpts{Start: 0, End: nil, Context: rln.ctx})
if err != nil {
return err
}
for {
if !logIterator.Next() {
break
}
if logIterator.Error() != nil {
return logIterator.Error()
}
err = rln.processLogs(logIterator.Event, handler)
if err != nil {
return err
}
}
return nil
}
func (rln *WakuRLNRelay) watchNewEvents(rlnContract *contracts.RLN, handler RegistrationEventHandler, log *zap.Logger, doneCh chan struct{}, errCh chan error) {
// Watch for new events
logSink := make(chan *contracts.RLNMemberRegistered)
subs := event.Resubscribe(2*time.Second, func(ctx context.Context) (event.Subscription, error) {
subs, err := rlnContract.WatchMemberRegistered(&bind.WatchOpts{Context: rln.ctx, Start: nil}, logSink)
if err != nil {
if err == rpc.ErrNotificationsUnsupported {
err = errors.New("notifications not supported. The node must support websockets")
}
errCh <- err
subs.Unsubscribe()
rln.log.Error("subscribing to rln events", zap.Error(err))
}
return subs, err
})
defer subs.Unsubscribe()
close(doneCh)
for {
select {
case evt := <-logSink:
err := rln.processLogs(evt, handler)
if err != nil {
rln.log.Error("processing rln log", zap.Error(err))
}
case <-rln.ctx.Done():
subs.Unsubscribe()
close(logSink)
return
case err := <-subs.Err():
close(logSink)
if err != nil {
rln.log.Error("watching new events", zap.Error(err))
errCh <- err
}
return
}
}
}

View File

@ -0,0 +1,166 @@
package protocol
import (
"encoding/binary"
"errors"
"fmt"
"math"
)
const MaxShardIndex = uint16(1023)
type RelayShards struct {
Cluster uint16
Indices []uint16
}
func NewRelayShards(cluster uint16, indices ...uint16) (RelayShards, error) {
if len(indices) > math.MaxUint8 {
return RelayShards{}, errors.New("too many indices")
}
indiceSet := make(map[uint16]struct{})
for _, index := range indices {
if index > MaxShardIndex {
return RelayShards{}, errors.New("invalid index")
}
indiceSet[index] = struct{}{} // dedup
}
if len(indiceSet) == 0 {
return RelayShards{}, errors.New("invalid index count")
}
indices = []uint16{}
for index := range indiceSet {
indices = append(indices, index)
}
return RelayShards{Cluster: cluster, Indices: indices}, nil
}
func (rs RelayShards) Topics() []NamespacedPubsubTopic {
var result []NamespacedPubsubTopic
for _, i := range rs.Indices {
result = append(result, NewStaticShardingPubsubTopic(rs.Cluster, i))
}
return result
}
func (rs RelayShards) Contains(cluster uint16, index uint16) bool {
if rs.Cluster != cluster {
return false
}
found := false
for _, i := range rs.Indices {
if i == index {
found = true
}
}
return found
}
func (rs RelayShards) ContainsNamespacedTopic(topic NamespacedPubsubTopic) bool {
if topic.Kind() != StaticSharding {
return false
}
shardedTopic := topic.(StaticShardingPubsubTopic)
return rs.Contains(shardedTopic.Cluster(), shardedTopic.Shard())
}
func (rs RelayShards) ContainsTopic(topic string) bool {
nsTopic, err := ToShardedPubsubTopic(topic)
if err != nil {
return false
}
return rs.ContainsNamespacedTopic(nsTopic)
}
func (rs RelayShards) IndicesList() ([]byte, error) {
if len(rs.Indices) > math.MaxUint8 {
return nil, errors.New("indices list too long")
}
var result []byte
result = binary.BigEndian.AppendUint16(result, rs.Cluster)
result = append(result, uint8(len(rs.Indices)))
for _, index := range rs.Indices {
result = binary.BigEndian.AppendUint16(result, index)
}
return result, nil
}
func FromIndicesList(buf []byte) (RelayShards, error) {
if len(buf) < 3 {
return RelayShards{}, fmt.Errorf("insufficient data: expected at least 3 bytes, got %d bytes", len(buf))
}
cluster := binary.BigEndian.Uint16(buf[0:2])
length := int(buf[2])
if len(buf) != 3+2*length {
return RelayShards{}, fmt.Errorf("invalid data: `length` field is %d but %d bytes were provided", length, len(buf))
}
var indices []uint16
for i := 0; i < length; i++ {
indices = append(indices, binary.BigEndian.Uint16(buf[3+2*i:5+2*i]))
}
return NewRelayShards(cluster, indices...)
}
func setBit(n byte, pos uint) byte {
n |= (1 << pos)
return n
}
func hasBit(n byte, pos uint) bool {
val := n & (1 << pos)
return (val > 0)
}
func (rs RelayShards) BitVector() []byte {
// The value is comprised of a two-byte shard cluster index in network byte
// order concatenated with a 128-byte wide bit vector. The bit vector
// indicates which shards of the respective shard cluster the node is part
// of. The right-most bit in the bit vector represents shard 0, the left-most
// bit represents shard 1023.
var result []byte
result = binary.BigEndian.AppendUint16(result, rs.Cluster)
vec := make([]byte, 128)
for _, index := range rs.Indices {
n := vec[index/8]
vec[index/8] = byte(setBit(n, uint(index%8)))
}
return append(result, vec...)
}
func FromBitVector(buf []byte) (RelayShards, error) {
if len(buf) != 130 {
return RelayShards{}, errors.New("invalid data: expected 130 bytes")
}
cluster := binary.BigEndian.Uint16(buf[0:2])
var indices []uint16
for i := uint16(0); i < 128; i++ {
for j := uint(0); j < 8; j++ {
if !hasBit(buf[2+i], j) {
continue
}
indices = append(indices, uint16(j)+8*i)
}
}
return RelayShards{Cluster: cluster, Indices: indices}, nil
}

View File

@ -180,12 +180,14 @@ func (store *WakuStore) queryFrom(ctx context.Context, q *pb.HistoryQuery, selec
err := store.h.Connect(ctx, store.h.Peerstore().PeerInfo(selectedPeer))
if err != nil {
logger.Error("connecting to peer", zap.Error(err))
metrics.RecordStoreError(store.ctx, "dial_failure")
return nil, err
}
connOpt, err := store.h.NewStream(ctx, selectedPeer, StoreID_v20beta4)
if err != nil {
logger.Error("creating stream to peer", zap.Error(err))
metrics.RecordStoreError(store.ctx, "dial_failure")
return nil, err
}
@ -202,6 +204,7 @@ func (store *WakuStore) queryFrom(ctx context.Context, q *pb.HistoryQuery, selec
err = writer.WriteMsg(historyRequest)
if err != nil {
logger.Error("writing request", zap.Error(err))
metrics.RecordStoreError(store.ctx, "write_request_failure")
return nil, err
}
@ -209,7 +212,7 @@ func (store *WakuStore) queryFrom(ctx context.Context, q *pb.HistoryQuery, selec
err = reader.ReadMsg(historyResponseRPC)
if err != nil {
logger.Error("reading response", zap.Error(err))
metrics.RecordStoreError(store.ctx, "decodeRPCFailure")
metrics.RecordStoreError(store.ctx, "decode_rpc_failure")
return nil, err
}
@ -220,8 +223,6 @@ func (store *WakuStore) queryFrom(ctx context.Context, q *pb.HistoryQuery, selec
}, nil
}
metrics.RecordMessage(ctx, "retrieved", len(historyResponseRPC.Response.Messages))
return historyResponseRPC.Response, nil
}
@ -275,6 +276,7 @@ func (store *WakuStore) Query(ctx context.Context, query Query, opts ...HistoryR
}
if !params.localQuery && params.selectedPeer == "" {
metrics.RecordStoreError(ctx, "peer_not_found_failure")
return nil, ErrNoPeersAvailable
}

View File

@ -7,7 +7,7 @@ import (
"github.com/libp2p/go-libp2p/core/host"
libp2pProtocol "github.com/libp2p/go-libp2p/core/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/timesource"
"go.uber.org/zap"
)
@ -39,8 +39,6 @@ var (
// ErrFailedQuery is emitted when the query fails to return results
ErrFailedQuery = errors.New("failed to resolve the query")
ErrFutureMessage = errors.New("message timestamp in the future")
)
type WakuSwap interface {
@ -51,7 +49,7 @@ type WakuStore struct {
ctx context.Context
cancel context.CancelFunc
timesource timesource.Timesource
MsgC chan *protocol.Envelope
MsgC relay.Subscription
wg *sync.WaitGroup
log *zap.Logger
@ -63,10 +61,9 @@ type WakuStore struct {
}
// NewWakuStore creates a WakuStore using an specific MessageProvider for storing the messages
func NewWakuStore(host host.Host, p MessageProvider, timesource timesource.Timesource, log *zap.Logger) *WakuStore {
func NewWakuStore(p MessageProvider, timesource timesource.Timesource, log *zap.Logger) *WakuStore {
wakuStore := new(WakuStore)
wakuStore.msgProvider = p
wakuStore.h = host
wakuStore.wg = &sync.WaitGroup{}
wakuStore.log = log.Named("store")
wakuStore.timesource = timesource

View File

@ -7,6 +7,7 @@ import (
"sync"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-msgio/pbio"
@ -17,13 +18,11 @@ import (
"github.com/waku-org/go-waku/waku/v2/metrics"
"github.com/waku-org/go-waku/waku/v2/protocol"
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/store/pb"
"github.com/waku-org/go-waku/waku/v2/timesource"
)
// MaxTimeVariance is the maximum duration in the future allowed for a message timestamp
const MaxTimeVariance = time.Duration(20) * time.Second
func findMessages(query *pb.HistoryQuery, msgProvider MessageProvider) ([]*wpb.WakuMessage, *pb.PagingInfo, error) {
if query.PagingInfo == nil {
query.PagingInfo = &pb.PagingInfo{
@ -77,6 +76,7 @@ func (store *WakuStore) FindMessages(query *pb.HistoryQuery) *pb.HistoryResponse
type MessageProvider interface {
GetAll() ([]persistence.StoredMessage, error)
Query(query *pb.HistoryQuery) (*pb.Index, []persistence.StoredMessage, error)
Validate(env *protocol.Envelope) error
Put(env *protocol.Envelope) error
MostRecentTimestamp() (int64, error)
Start(ctx context.Context, timesource timesource.Timesource) error
@ -85,12 +85,12 @@ type MessageProvider interface {
}
type Store interface {
Start(ctx context.Context) error
SetHost(h host.Host)
Start(context.Context, relay.Subscription) error
Query(ctx context.Context, query Query, opts ...HistoryRequestOption) (*Result, error)
Find(ctx context.Context, query Query, cb criteriaFN, opts ...HistoryRequestOption) (*wpb.WakuMessage, error)
Next(ctx context.Context, r *Result) (*Result, error)
Resume(ctx context.Context, pubsubTopic string, peerList []peer.ID) (int, error)
MessageChannel() chan *protocol.Envelope
Stop()
}
@ -99,8 +99,13 @@ func (store *WakuStore) SetMessageProvider(p MessageProvider) {
store.msgProvider = p
}
// Sets the host to be able to mount or consume a protocol
func (store *WakuStore) SetHost(h host.Host) {
store.h = h
}
// Start initializes the WakuStore by enabling the protocol and fetching records from a message provider
func (store *WakuStore) Start(ctx context.Context) error {
func (store *WakuStore) Start(ctx context.Context, sub relay.Subscription) error {
if store.started {
return nil
}
@ -113,18 +118,17 @@ func (store *WakuStore) Start(ctx context.Context) error {
err := store.msgProvider.Start(ctx, store.timesource) // TODO: store protocol should not start a message provider
if err != nil {
store.log.Error("Error starting message provider", zap.Error(err))
return nil
return err
}
store.started = true
store.ctx, store.cancel = context.WithCancel(ctx)
store.MsgC = make(chan *protocol.Envelope, 1024)
store.MsgC = sub
store.h.SetStreamHandlerMatch(StoreID_v20beta4, protocol.PrefixTextMatch(string(StoreID_v20beta4)), store.onRequest)
store.wg.Add(2)
store.wg.Add(1)
go store.storeIncomingMessages(store.ctx)
go store.updateMetrics(store.ctx)
store.log.Info("Store protocol started")
@ -132,16 +136,17 @@ func (store *WakuStore) Start(ctx context.Context) error {
}
func (store *WakuStore) storeMessage(env *protocol.Envelope) error {
// Ensure that messages don't "jump" to the front of the queue with future timestamps
if env.Index().SenderTime-env.Index().ReceiverTime > int64(MaxTimeVariance) {
return ErrFutureMessage
}
if env.Message().Ephemeral {
return nil
}
err := store.msgProvider.Put(env)
err := store.msgProvider.Validate(env)
if err != nil {
return err
}
err = store.msgProvider.Put(env)
if err != nil {
store.log.Error("storing message", zap.Error(err))
metrics.RecordStoreError(store.ctx, "store_failure")
@ -153,33 +158,13 @@ func (store *WakuStore) storeMessage(env *protocol.Envelope) error {
func (store *WakuStore) storeIncomingMessages(ctx context.Context) {
defer store.wg.Done()
for envelope := range store.MsgC {
for envelope := range store.MsgC.Ch {
go func(env *protocol.Envelope) {
_ = store.storeMessage(env)
}(envelope)
}
}
func (store *WakuStore) updateMetrics(ctx context.Context) {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
defer store.wg.Done()
for {
select {
case <-ticker.C:
msgCount, err := store.msgProvider.Count()
if err != nil {
store.log.Error("updating store metrics", zap.Error(err))
} else {
metrics.RecordMessage(store.ctx, "stored", msgCount)
}
case <-ctx.Done():
return
}
}
}
func (store *WakuStore) onRequest(s network.Stream) {
defer s.Close()
logger := store.log.With(logging.HostID("peer", s.Conn().RemotePeer()))
@ -191,7 +176,7 @@ func (store *WakuStore) onRequest(s network.Stream) {
err := reader.ReadMsg(historyRPCRequest)
if err != nil {
logger.Error("reading request", zap.Error(err))
metrics.RecordStoreError(store.ctx, "decodeRPCFailure")
metrics.RecordStoreError(store.ctx, "decode_rpc_failure")
return
}
@ -200,7 +185,7 @@ func (store *WakuStore) onRequest(s network.Stream) {
logger = logger.With(logging.Filters(query.GetContentFilters()))
} else {
logger.Error("reading request", zap.Error(err))
metrics.RecordStoreError(store.ctx, "emptyRpcQueryFailure")
metrics.RecordStoreError(store.ctx, "empty_rpc_query_failure")
return
}
@ -215,16 +200,13 @@ func (store *WakuStore) onRequest(s network.Stream) {
err = writer.WriteMsg(historyResponseRPC)
if err != nil {
logger.Error("writing response", zap.Error(err), logging.PagingInfo(historyResponseRPC.Response.PagingInfo))
metrics.RecordStoreError(store.ctx, "response_write_failure")
_ = s.Reset()
} else {
logger.Info("response sent")
}
}
func (store *WakuStore) MessageChannel() chan *protocol.Envelope {
return store.MsgC
}
// TODO: queryWithAccounting
// Stop closes the store message channel and removes the protocol stream handler
@ -237,9 +219,7 @@ func (store *WakuStore) Stop() {
store.started = false
if store.MsgC != nil {
close(store.MsgC)
}
store.MsgC.Unsubscribe()
if store.msgProvider != nil {
store.msgProvider.Stop() // TODO: StoreProtocol should not stop a message provider

View File

@ -3,7 +3,6 @@ package protocol
import (
"errors"
"fmt"
"runtime/debug"
"strconv"
"strings"
)
@ -72,19 +71,19 @@ const (
NamedSharding
)
type ShardedPubsubTopic interface {
type NamespacedPubsubTopic interface {
String() string
Kind() NamespacedPubsubTopicKind
Equal(ShardedPubsubTopic) bool
Equal(NamespacedPubsubTopic) bool
}
type NamedShardingPubsubTopic struct {
ShardedPubsubTopic
NamespacedPubsubTopic
kind NamespacedPubsubTopicKind
name string
}
func NewNamedShardingPubsubTopic(name string) ShardedPubsubTopic {
func NewNamedShardingPubsubTopic(name string) NamespacedPubsubTopic {
return NamedShardingPubsubTopic{
kind: NamedSharding,
name: name,
@ -99,7 +98,7 @@ func (n NamedShardingPubsubTopic) Name() string {
return n.name
}
func (s NamedShardingPubsubTopic) Equal(t2 ShardedPubsubTopic) bool {
func (s NamedShardingPubsubTopic) Equal(t2 NamespacedPubsubTopic) bool {
return s.String() == t2.String()
}
@ -124,13 +123,13 @@ func (s *NamedShardingPubsubTopic) Parse(topic string) error {
}
type StaticShardingPubsubTopic struct {
ShardedPubsubTopic
NamespacedPubsubTopic
kind NamespacedPubsubTopicKind
cluster uint16
shard uint16
}
func NewStaticShardingPubsubTopic(cluster uint16, shard uint16) ShardedPubsubTopic {
func NewStaticShardingPubsubTopic(cluster uint16, shard uint16) NamespacedPubsubTopic {
return StaticShardingPubsubTopic{
kind: StaticSharding,
cluster: cluster,
@ -150,7 +149,7 @@ func (n StaticShardingPubsubTopic) Kind() NamespacedPubsubTopicKind {
return n.kind
}
func (s StaticShardingPubsubTopic) Equal(t2 ShardedPubsubTopic) bool {
func (s StaticShardingPubsubTopic) Equal(t2 NamespacedPubsubTopic) bool {
return s.String() == t2.String()
}
@ -196,7 +195,7 @@ func (s *StaticShardingPubsubTopic) Parse(topic string) error {
return nil
}
func ToShardedPubsubTopic(topic string) (ShardedPubsubTopic, error) {
func ToShardedPubsubTopic(topic string) (NamespacedPubsubTopic, error) {
if strings.HasPrefix(topic, StaticShardingPubsubTopicPrefix) {
s := StaticShardingPubsubTopic{}
err := s.Parse(topic)
@ -205,7 +204,6 @@ func ToShardedPubsubTopic(topic string) (ShardedPubsubTopic, error) {
}
return s, nil
} else {
debug.PrintStack()
s := NamedShardingPubsubTopic{}
err := s.Parse(topic)
if err != nil {
@ -215,6 +213,6 @@ func ToShardedPubsubTopic(topic string) (ShardedPubsubTopic, error) {
}
}
func DefaultPubsubTopic() ShardedPubsubTopic {
func DefaultPubsubTopic() NamespacedPubsubTopic {
return NewNamedShardingPubsubTopic("default-waku/proto")
}

View File

@ -43,7 +43,7 @@ type PeerConnector interface {
PeerChannel() chan<- peer.AddrInfo
}
func NewRendezvous(host host.Host, enableServer bool, db *DB, discoverPeers bool, rendezvousPoints []peer.ID, peerConnector PeerConnector, log *zap.Logger) *Rendezvous {
func NewRendezvous(enableServer bool, db *DB, discoverPeers bool, rendezvousPoints []peer.ID, peerConnector PeerConnector, log *zap.Logger) *Rendezvous {
logger := log.Named("rendezvous")
var rendevousPoints []*rendezvousPoint
@ -54,7 +54,6 @@ func NewRendezvous(host host.Host, enableServer bool, db *DB, discoverPeers bool
}
return &Rendezvous{
host: host,
enableServer: enableServer,
db: db,
discoverPeers: discoverPeers,
@ -64,6 +63,11 @@ func NewRendezvous(host host.Host, enableServer bool, db *DB, discoverPeers bool
}
}
// Sets the host to be able to mount or consume a protocol
func (r *Rendezvous) SetHost(h host.Host) {
r.host = h
}
func (r *Rendezvous) Start(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
r.cancel = cancel

View File

@ -37,6 +37,10 @@ type IdentityCredential = struct {
IDCommitment IDCommitment `json:"idCommitment"`
}
func IdentityCredentialEquals(i IdentityCredential, i2 IdentityCredential) bool {
return bytes.Equal(i.IDTrapdoor[:], i2.IDTrapdoor[:]) && bytes.Equal(i.IDNullifier[:], i2.IDNullifier[:]) && bytes.Equal(i.IDSecretHash[:], i2.IDSecretHash[:]) && bytes.Equal(i.IDCommitment[:], i2.IDCommitment[:])
}
type RateLimitProof struct {
// RateLimitProof holds the public inputs to rln circuit as
// defined in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Public-Inputs

1
vendor/github.com/wk8/go-ordered-map/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
/vendor/

85
vendor/github.com/wk8/go-ordered-map/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,85 @@
run:
tests: false
linters:
disable-all: true
enable:
- asciicheck
- bidichk
- bodyclose
- containedctx
- contextcheck
- cyclop
- deadcode
- decorder
- depguard
- dogsled
- dupl
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- exhaustive
- exportloopref
- forbidigo
- funlen
- gci
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godox
- goerr113
- gofmt
- gofumpt
- goheader
- goimports
- gomnd
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- gosimple
- govet
- grouper
- ifshort
- importas
- ineffassign
- ireturn
- lll
- maintidx
- makezero
- misspell
- nakedret
- nestif
- nilerr
- nilnil
- noctx
- nolintlint
- paralleltest
- prealloc
- predeclared
- promlinter
- revive
- rowserrcheck
- sqlclosecheck
- staticcheck
- structcheck
- stylecheck
- tagliatelle
- tenv
- testpackage
- thelper
- tparallel
- typecheck
- unconvert
- unparam
- unused
- varcheck
- varnamelen
- wastedassign
- whitespace
- wrapcheck

201
vendor/github.com/wk8/go-ordered-map/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

13
vendor/github.com/wk8/go-ordered-map/Makefile generated vendored Normal file
View File

@ -0,0 +1,13 @@
.DEFAULT_GOAL := all
.PHONY: all
all: test lint
# the TEST_FLAGS env var can be set to eg run only specific tests
.PHONY: test
test:
go test -v -count=1 -race -cover "$$TEST_FLAGS"
.PHONY: lint
lint:
golangci-lint run

104
vendor/github.com/wk8/go-ordered-map/README.md generated vendored Normal file
View File

@ -0,0 +1,104 @@
[![Build Status](https://circleci.com/gh/wk8/go-ordered-map.svg?style=svg)](https://app.circleci.com/pipelines/github/wk8/go-ordered-map)
# Goland Ordered Maps
Same as regular maps, but also remembers the order in which keys were inserted, akin to [Python's `collections.OrderedDict`s](https://docs.python.org/3.7/library/collections.html#ordereddict-objects).
It offers the following features:
* optimal runtime performance (all operations are constant time)
* optimal memory usage (only one copy of values, no unnecessary memory allocation)
* allows iterating from newest or oldest keys indifferently, without memory copy, allowing to `break` the iteration, and in time linear to the number of keys iterated over rather than the total length of the ordered map
* takes and returns generic `interface{}`s
* idiomatic API, akin to that of [`container/list`](https://golang.org/pkg/container/list)
## Installation
```bash
go get -u github.com/wk8/go-ordered-map
```
Or use your favorite golang vendoring tool!
## Supported go versions
All go versions >= 1.13 are supported. There's no reason for older versions to not also work, but they're not part of the build matrix.
## Documentation
[The full documentation is available on godoc.org](https://godoc.org/github.com/wk8/go-ordered-map).
## Example / usage
```go
package main
import (
"fmt"
"github.com/wk8/go-ordered-map"
)
func main() {
om := orderedmap.New()
om.Set("foo", "bar")
om.Set("bar", "baz")
om.Set("coucou", "toi")
fmt.Println(om.Get("foo")) // => bar, true
fmt.Println(om.Get("i dont exist")) // => <nil>, false
// iterating pairs from oldest to newest:
for pair := om.Oldest(); pair != nil; pair = pair.Next() {
fmt.Printf("%s => %s\n", pair.Key, pair.Value)
} // prints:
// foo => bar
// bar => baz
// coucou => toi
// iterating over the 2 newest pairs:
i := 0
for pair := om.Newest(); pair != nil; pair = pair.Prev() {
fmt.Printf("%s => %s\n", pair.Key, pair.Value)
i++
if i >= 2 {
break
}
} // prints:
// coucou => toi
// bar => baz
}
```
All of `OrderedMap`'s methods accept and return `interface{}`s, so you can use any type of keys that regular `map`s accept, as well pack/unpack arbitrary values, e.g.:
```go
type myStruct struct {
payload string
}
func main() {
om := orderedmap.New()
om.Set(12, &myStruct{"foo"})
om.Set(1, &myStruct{"bar"})
value, present := om.Get(12)
if !present {
panic("should be there!")
}
fmt.Println(value.(*myStruct).payload) // => foo
for pair := om.Oldest(); pair != nil; pair = pair.Next() {
fmt.Printf("%d => %s\n", pair.Key, pair.Value.(*myStruct).payload)
} // prints:
// 12 => foo
// 1 => bar
}
```
## Alternatives
There are several other ordered map golang implementations out there, but I believe that at the time of writing none of them offer the same functionality as this library; more specifically:
* [iancoleman/orderedmap](https://github.com/iancoleman/orderedmap) only accepts `string` keys, its `Delete` operations are linear
* [cevaris/ordered_map](https://github.com/cevaris/ordered_map) uses a channel for iterations, and leaks goroutines if the iteration is interrupted before fully traversing the map
* [mantyr/iterator](https://github.com/mantyr/iterator) also uses a channel for iterations, and its `Delete` operations are linear
* [samdolan/go-ordered-map](https://github.com/samdolan/go-ordered-map) adds unnecessary locking (users should add their own locking instead if they need it), its `Delete` and `Get` operations are linear, iterations trigger a linear memory allocation

193
vendor/github.com/wk8/go-ordered-map/orderedmap.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
// Package orderedmap implements an ordered map, i.e. a map that also keeps track of
// the order in which keys were inserted.
//
// All operations are constant-time.
//
// Github repo: https://github.com/wk8/go-ordered-map
//
package orderedmap
import (
"container/list"
"fmt"
)
type Pair struct {
Key interface{}
Value interface{}
element *list.Element
}
type OrderedMap struct {
pairs map[interface{}]*Pair
list *list.List
}
// New creates a new OrderedMap.
func New() *OrderedMap {
return &OrderedMap{
pairs: make(map[interface{}]*Pair),
list: list.New(),
}
}
// Get looks for the given key, and returns the value associated with it,
// or nil if not found. The boolean it returns says whether the key is present in the map.
func (om *OrderedMap) Get(key interface{}) (interface{}, bool) {
if pair, present := om.pairs[key]; present {
return pair.Value, present
}
return nil, false
}
// Load is an alias for Get, mostly to present an API similar to `sync.Map`'s.
func (om *OrderedMap) Load(key interface{}) (interface{}, bool) {
return om.Get(key)
}
// GetPair looks for the given key, and returns the pair associated with it,
// or nil if not found. The Pair struct can then be used to iterate over the ordered map
// from that point, either forward or backward.
func (om *OrderedMap) GetPair(key interface{}) *Pair {
return om.pairs[key]
}
// Set sets the key-value pair, and returns what `Get` would have returned
// on that key prior to the call to `Set`.
func (om *OrderedMap) Set(key interface{}, value interface{}) (interface{}, bool) {
if pair, present := om.pairs[key]; present {
oldValue := pair.Value
pair.Value = value
return oldValue, true
}
pair := &Pair{
Key: key,
Value: value,
}
pair.element = om.list.PushBack(pair)
om.pairs[key] = pair
return nil, false
}
// Store is an alias for Set, mostly to present an API similar to `sync.Map`'s.
func (om *OrderedMap) Store(key interface{}, value interface{}) (interface{}, bool) {
return om.Set(key, value)
}
// Delete removes the key-value pair, and returns what `Get` would have returned
// on that key prior to the call to `Delete`.
func (om *OrderedMap) Delete(key interface{}) (interface{}, bool) {
if pair, present := om.pairs[key]; present {
om.list.Remove(pair.element)
delete(om.pairs, key)
return pair.Value, true
}
return nil, false
}
// Len returns the length of the ordered map.
func (om *OrderedMap) Len() int {
return len(om.pairs)
}
// Oldest returns a pointer to the oldest pair. It's meant to be used to iterate on the ordered map's
// pairs from the oldest to the newest, e.g.:
// for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) }
func (om *OrderedMap) Oldest() *Pair {
return listElementToPair(om.list.Front())
}
// Newest returns a pointer to the newest pair. It's meant to be used to iterate on the ordered map's
// pairs from the newest to the oldest, e.g.:
// for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) }
func (om *OrderedMap) Newest() *Pair {
return listElementToPair(om.list.Back())
}
// Next returns a pointer to the next pair.
func (p *Pair) Next() *Pair {
return listElementToPair(p.element.Next())
}
// Previous returns a pointer to the previous pair.
func (p *Pair) Prev() *Pair {
return listElementToPair(p.element.Prev())
}
func listElementToPair(element *list.Element) *Pair {
if element == nil {
return nil
}
return element.Value.(*Pair)
}
// KeyNotFoundError may be returned by functions in this package when they're called with keys that are not present
// in the map.
type KeyNotFoundError struct {
MissingKey interface{}
}
var _ error = &KeyNotFoundError{}
func (e *KeyNotFoundError) Error() string {
return fmt.Sprintf("missing key: %v", e.MissingKey)
}
// MoveAfter moves the value associated with key to its new position after the one associated with markKey.
// Returns an error iff key or markKey are not present in the map.
func (om *OrderedMap) MoveAfter(key, markKey interface{}) error {
elements, err := om.getElements(key, markKey)
if err != nil {
return err
}
om.list.MoveAfter(elements[0], elements[1])
return nil
}
// MoveBefore moves the value associated with key to its new position before the one associated with markKey.
// Returns an error iff key or markKey are not present in the map.
func (om *OrderedMap) MoveBefore(key, markKey interface{}) error {
elements, err := om.getElements(key, markKey)
if err != nil {
return err
}
om.list.MoveBefore(elements[0], elements[1])
return nil
}
func (om *OrderedMap) getElements(keys ...interface{}) ([]*list.Element, error) {
elements := make([]*list.Element, len(keys))
for i, k := range keys {
pair, present := om.pairs[k]
if !present {
return nil, &KeyNotFoundError{k}
}
elements[i] = pair.element
}
return elements, nil
}
// MoveToBack moves the value associated with key to the back of the ordered map.
// Returns an error iff key is not present in the map.
func (om *OrderedMap) MoveToBack(key interface{}) error {
pair, present := om.pairs[key]
if !present {
return &KeyNotFoundError{key}
}
om.list.MoveToBack(pair.element)
return nil
}
// MoveToFront moves the value associated with key to the front of the ordered map.
// Returns an error iff key is not present in the map.
func (om *OrderedMap) MoveToFront(key interface{}) error {
pair, present := om.pairs[key]
if !present {
return &KeyNotFoundError{key}
}
om.list.MoveToFront(pair.element)
return nil
}

14
vendor/modules.txt vendored
View File

@ -975,7 +975,7 @@ github.com/vacp2p/mvds/transport
github.com/waku-org/go-discover/discover
github.com/waku-org/go-discover/discover/v4wire
github.com/waku-org/go-discover/discover/v5wire
# github.com/waku-org/go-waku v0.5.3-0.20230404182041-41691a44e579
# github.com/waku-org/go-waku v0.5.3-0.20230509204224-d9a12bf079a8
## explicit; go 1.19
github.com/waku-org/go-waku/logging
github.com/waku-org/go-waku/waku/persistence
@ -988,10 +988,11 @@ github.com/waku-org/go-waku/waku/v2/metrics
github.com/waku-org/go-waku/waku/v2/node
github.com/waku-org/go-waku/waku/v2/payload
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/filter
github.com/waku-org/go-waku/waku/v2/protocol/filter/pb
github.com/waku-org/go-waku/waku/v2/protocol/filterv2
github.com/waku-org/go-waku/waku/v2/protocol/filterv2/pb
github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter
github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter/pb
github.com/waku-org/go-waku/waku/v2/protocol/lightpush
github.com/waku-org/go-waku/waku/v2/protocol/lightpush/pb
github.com/waku-org/go-waku/waku/v2/protocol/pb
@ -1001,13 +1002,15 @@ github.com/waku-org/go-waku/waku/v2/protocol/relay
github.com/waku-org/go-waku/waku/v2/protocol/rln
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/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/keystore
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/rendezvous
github.com/waku-org/go-waku/waku/v2/timesource
github.com/waku-org/go-waku/waku/v2/utils
# github.com/waku-org/go-zerokit-rln v0.1.11
# github.com/waku-org/go-zerokit-rln v0.1.12
## explicit; go 1.18
github.com/waku-org/go-zerokit-rln/rln
github.com/waku-org/go-zerokit-rln/rln/link
@ -1039,6 +1042,9 @@ github.com/wealdtech/go-ens/v3/util
# github.com/wealdtech/go-multicodec v1.4.0
## explicit; go 1.12
github.com/wealdtech/go-multicodec
# github.com/wk8/go-ordered-map v1.0.0
## explicit; go 1.14
github.com/wk8/go-ordered-map
# github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f
## explicit
github.com/xeipuuv/gojsonpointer

View File

@ -21,6 +21,12 @@ import (
var ErrInvalidCursor = errors.New("invalid cursor")
var ErrFutureMessage = errors.New("message timestamp in the future")
var ErrMessageTooOld = errors.New("message too old")
// MaxTimeVariance is the maximum duration in the future allowed for a message timestamp
const MaxTimeVariance = time.Duration(20) * time.Second
// DBStore is a MessageProvider that has a *sql.DB connection
type DBStore struct {
db *sql.DB
@ -87,6 +93,23 @@ func (d *DBStore) Start(ctx context.Context, timesource timesource.Timesource) e
return nil
}
func (d *DBStore) Validate(env *protocol.Envelope) error {
n := time.Unix(0, env.Index().ReceiverTime)
upperBound := n.Add(MaxTimeVariance)
lowerBound := n.Add(-MaxTimeVariance)
// Ensure that messages don't "jump" to the front of the queue with future timestamps
if env.Message().Timestamp > upperBound.UnixNano() {
return ErrFutureMessage
}
if env.Message().Timestamp < lowerBound.UnixNano() {
return ErrMessageTooOld
}
return nil
}
func (d *DBStore) cleanOlderRecords() error {
d.log.Debug("Cleaning older records...")

View File

@ -58,7 +58,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/dnsdisc"
"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/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
@ -286,7 +286,7 @@ func New(nodeKey string, fleet string, cfg *Config, logger *zap.Logger, appDB *s
}
if cfg.LightClient {
opts = append(opts, node.WithWakuFilter(false))
opts = append(opts, node.WithLegacyWakuFilter(false))
} else {
relayOpts := []pubsub.Option{
pubsub.WithMaxMessageSize(int(waku.settings.MaxMsgSize)),
@ -525,8 +525,7 @@ func (w *Waku) GetStats() types.StatsSummary {
func (w *Waku) runPeerExchangeLoop() {
defer w.wg.Done()
if w.settings.PeerExchange && !w.settings.LightClient {
if !w.settings.PeerExchange || !w.settings.LightClient {
// Currently peer exchange is only used for full nodes
// TODO: should it be used for lightpush? or lightpush nodes
// are only going to be selected from a specific set of peers?
@ -620,7 +619,7 @@ func (w *Waku) runRelayMsgLoop() {
case <-w.quit:
sub.Unsubscribe()
return
case env := <-sub.C:
case env := <-sub.Ch:
envelopeErrors, err := w.OnNewEnvelopes(env, common.RelayedMessageType)
if err != nil {
w.logger.Error("onNewEnvelope error", zap.Error(err))
@ -661,13 +660,13 @@ func (w *Waku) subscribeWakuFilterTopic(topics [][]byte) {
}
var err error
contentFilter := filter.ContentFilter{
contentFilter := legacy_filter.ContentFilter{
Topic: relay.DefaultWakuTopic,
ContentTopics: contentTopics,
}
var wakuFilter filter.Filter
_, wakuFilter, err = w.node.Filter().Subscribe(context.Background(), contentFilter)
var wakuFilter legacy_filter.Filter
_, wakuFilter, err = w.node.LegacyFilter().Subscribe(context.Background(), contentFilter)
if err != nil {
w.logger.Warn("could not add wakuv2 filter for topics", zap.Any("topics", topics))
return
@ -982,14 +981,14 @@ func (w *Waku) GetFilter(id string) *common.Filter {
func (w *Waku) Unsubscribe(id string) error {
f := w.filters.Get(id)
if f != nil && w.settings.LightClient {
contentFilter := filter.ContentFilter{
contentFilter := legacy_filter.ContentFilter{
Topic: relay.DefaultWakuTopic,
}
for _, topic := range f.Topics {
contentFilter.ContentTopics = append(contentFilter.ContentTopics, common.BytesToTopic(topic).ContentTopic())
}
if err := w.node.Filter().UnsubscribeFilter(context.Background(), contentFilter); err != nil {
if err := w.node.LegacyFilter().UnsubscribeFilter(context.Background(), contentFilter); err != nil {
return fmt.Errorf("failed to unsubscribe: %w", err)
}
}
@ -1125,13 +1124,6 @@ func (w *Waku) Start() error {
return fmt.Errorf("failed to create a go-waku node: %v", err)
}
idService, err := identify.NewIDService(w.node.Host())
if err != nil {
return err
}
w.identifyService = idService
w.quit = make(chan struct{})
w.filterMsgChannel = make(chan *protocol.Envelope, 1024)
w.connectionChanged = make(chan struct{})
@ -1141,6 +1133,13 @@ func (w *Waku) Start() error {
return fmt.Errorf("failed to start go-waku node: %v", err)
}
idService, err := identify.NewIDService(w.node.Host())
if err != nil {
return err
}
w.identifyService = idService
if err = w.addWakuV2Peers(ctx, w.cfg); err != nil {
return fmt.Errorf("failed to add wakuv2 peers: %v", err)
}
@ -1215,9 +1214,9 @@ func (w *Waku) Start() error {
// Stop implements node.Service, stopping the background data propagation thread
// of the Waku protocol.
func (w *Waku) Stop() error {
close(w.quit)
w.identifyService.Close()
w.node.Stop()
close(w.quit)
close(w.filterMsgChannel)
close(w.connectionChanged)
w.wg.Wait()
@ -1226,7 +1225,7 @@ func (w *Waku) Stop() error {
func (w *Waku) OnNewEnvelopes(envelope *protocol.Envelope, msgType common.MessageType) ([]common.EnvelopeError, error) {
if envelope == nil {
return nil, errors.New("nil envelope error")
return nil, nil
}
recvMessage := common.NewReceivedMessage(envelope, msgType)