Skip to content

Commit 47c961d

Browse files
feat: update lightpush API for autosharding (#774)
* feat: update lightpush API to make pubSubTopic optional as per autosharding * Extract contentFilter and subscriptions out of filter to reuse in relay (#779) * chore: extract contentFilter outside filter package * chore: move subscription outside of filter so that it can be modified and reused for relay * Feat: filter select peer for sharding (#783) * update selectPeer to support pubsubTopic based selection
1 parent dfd104d commit 47c961d

23 files changed

Lines changed: 417 additions & 205 deletions

examples/chat2/chat.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ func NewChat(ctx context.Context, node *node.WakuNode, connNotifier <-chan node.
6363
}
6464

6565
if options.Filter.Enable {
66-
cf := filter.ContentFilter{
66+
cf := protocol.ContentFilter{
6767
PubsubTopic: relay.DefaultWakuTopic,
68-
ContentTopics: filter.NewContentTopicSet(options.ContentTopic),
68+
ContentTopics: protocol.NewContentTopicSet(options.ContentTopic),
6969
}
7070
var filterOpt filter.FilterSubscribeOption
7171
peerID, err := options.Filter.NodePeerID()
@@ -269,7 +269,7 @@ func (c *Chat) SendMessage(line string) {
269269
err := c.publish(tCtx, line)
270270
if err != nil {
271271
if err.Error() == "validation failed" {
272-
err = errors.New("message rate violation!")
272+
err = errors.New("message rate violation")
273273
}
274274
c.ui.ErrorMessage(err)
275275
}
@@ -524,7 +524,7 @@ func (c *Chat) discoverNodes(connectionWg *sync.WaitGroup) {
524524

525525
ctx, cancel := context.WithTimeout(ctx, time.Duration(10)*time.Second)
526526
defer cancel()
527-
err = c.node.DialPeerWithInfo(ctx, n)
527+
err = c.node.DialPeerWithInfo(ctx, info)
528528
if err != nil {
529529

530530
c.ui.ErrorMessage(fmt.Errorf("co!!uld not connect to %s: %w", info.ID.Pretty(), err))

examples/filter2/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ func main() {
9797
}
9898

9999
// Send FilterRequest from light node to full node
100-
cf := filter.ContentFilter{
101-
ContentTopics: filter.NewContentTopicSet(contentTopic),
100+
cf := protocol.ContentFilter{
101+
ContentTopics: protocol.NewContentTopicSet(contentTopic),
102102
}
103103

104104
theFilter, err := lightNode.FilterLightnode().Subscribe(ctx, cf)

library/c/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ Publish a message using Waku Lightpush.
10831083

10841084
1. `char* messageJson`: JSON string containing the [Waku Message](https://rfc.vac.dev/spec/14/) as [`JsonMessage`](#jsonmessage-type).
10851085
2. `char* pubsubTopic`: pubsub topic on which to publish the message.
1086-
If `NULL`, it uses the default pubsub topic.
1086+
If `NULL`, it derives the pubsub topic from content-topic based on autosharding.
10871087
3. `char* peerID`: Peer ID supporting the lightpush protocol.
10881088
The peer must be already known.
10891089
It must have been added before with [`waku_add_peer`](#extern-char-waku_add_peerchar-address-char-protocolid)

library/c/api_lightpush.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package main
66
import "C"
77
import "github.com/waku-org/go-waku/library"
88

9-
// Publish a message using waku lightpush. Use NULL for topic to use the default pubsub topic..
9+
// Publish a message using waku lightpush. Use NULL for topic to derive the pubsub topic from the contentTopic.
1010
// peerID should contain the ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node
1111
// If ms is greater than 0, the broadcast of the message must happen before the timeout
1212
// (in milliseconds) is reached, or an error will be returned

library/filter.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,32 @@ import (
77
"time"
88

99
"github.com/libp2p/go-libp2p/core/peer"
10+
"github.com/waku-org/go-waku/waku/v2/protocol"
1011
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
12+
"github.com/waku-org/go-waku/waku/v2/protocol/subscription"
1113
)
1214

1315
type filterArgument struct {
1416
PubsubTopic string `json:"pubsubTopic,omitempty"`
1517
ContentTopics []string `json:"contentTopics,omitempty"`
1618
}
1719

18-
func toContentFilter(filterJSON string) (filter.ContentFilter, error) {
20+
func toContentFilter(filterJSON string) (protocol.ContentFilter, error) {
1921
var f filterArgument
2022
err := json.Unmarshal([]byte(filterJSON), &f)
2123
if err != nil {
22-
return filter.ContentFilter{}, err
24+
return protocol.ContentFilter{}, err
2325
}
2426

25-
return filter.ContentFilter{
27+
return protocol.ContentFilter{
2628
PubsubTopic: f.PubsubTopic,
27-
ContentTopics: filter.NewContentTopicSet(f.ContentTopics...),
29+
ContentTopics: protocol.NewContentTopicSet(f.ContentTopics...),
2830
}, nil
2931
}
3032

3133
type subscribeResult struct {
32-
Subscriptions []*filter.SubscriptionDetails `json:"subscriptions"`
33-
Error string `json:"error,omitempty"`
34+
Subscriptions []*subscription.SubscriptionDetails `json:"subscriptions"`
35+
Error string `json:"error,omitempty"`
3436
}
3537

3638
// FilterSubscribe is used to create a subscription to a filter node to receive messages
@@ -71,7 +73,7 @@ func FilterSubscribe(filterJSON string, peerID string, ms int) (string, error) {
7173
}
7274

7375
for _, subscriptionDetails := range subscriptions {
74-
go func(subscriptionDetails *filter.SubscriptionDetails) {
76+
go func(subscriptionDetails *subscription.SubscriptionDetails) {
7577
for envelope := range subscriptionDetails.C {
7678
send("message", toSubscriptionMessage(envelope))
7779
}

library/lightpush.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,20 @@ func lightpushPublish(msg *pb.WakuMessage, pubsubTopic string, peerID string, ms
3737
lpOptions = append(lpOptions, lightpush.WithAutomaticPeerSelection())
3838
}
3939

40-
hash, err := wakuState.node.Lightpush().PublishToTopic(ctx, msg, pubsubTopic, lpOptions...)
40+
if pubsubTopic != "" {
41+
lpOptions = append(lpOptions, lightpush.WithPubSubTopic(pubsubTopic))
42+
}
43+
44+
hash, err := wakuState.node.Lightpush().PublishToTopic(ctx, msg, lpOptions...)
4145
return hexutil.Encode(hash), err
4246
}
4347

4448
// LightpushPublish is used to publish a WakuMessage in a pubsub topic using Lightpush protocol
45-
func LightpushPublish(messageJSON string, topic string, peerID string, ms int) (string, error) {
49+
func LightpushPublish(messageJSON string, pubsubTopic string, peerID string, ms int) (string, error) {
4650
msg, err := wakuMessage(messageJSON)
4751
if err != nil {
4852
return "", err
4953
}
5054

51-
return lightpushPublish(msg, getTopic(topic), peerID, ms)
55+
return lightpushPublish(msg, getTopic(pubsubTopic), peerID, ms)
5256
}

waku/v2/node/wakunode2.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,11 +847,21 @@ func (w *WakuNode) Peers() ([]*Peer, error) {
847847
return peers, nil
848848
}
849849

850-
func (w *WakuNode) PeersByShard(cluster uint16, shard uint16) peer.IDSlice {
850+
// PeersByShard filters peers based on shard information following static sharding
851+
func (w *WakuNode) PeersByStaticShard(cluster uint16, shard uint16) peer.IDSlice {
851852
pTopic := wakuprotocol.NewStaticShardingPubsubTopic(cluster, shard).String()
852853
return w.peerstore.(wps.WakuPeerstore).PeersByPubSubTopic(pTopic)
853854
}
854855

856+
// PeersByContentTopics filters peers based on contentTopic
857+
func (w *WakuNode) PeersByContentTopic(contentTopic string) peer.IDSlice {
858+
pTopic, err := wakuprotocol.GetPubSubTopicFromContentTopic(contentTopic)
859+
if err != nil {
860+
return nil
861+
}
862+
return w.peerstore.(wps.WakuPeerstore).PeersByPubSubTopic(pTopic)
863+
}
864+
855865
func (w *WakuNode) findRelayNodes(ctx context.Context) {
856866
defer w.wg.Done()
857867

waku/v2/peermanager/peer_manager.go

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func (pm *PeerManager) connectivityLoop(ctx context.Context) {
127127
}
128128

129129
// GroupPeersByDirection returns all the connected peers in peer store grouped by Inbound or outBound direction
130-
func (pm *PeerManager) GroupPeersByDirection(specificPeers []peer.ID) (inPeers peer.IDSlice, outPeers peer.IDSlice, err error) {
130+
func (pm *PeerManager) GroupPeersByDirection(specificPeers ...peer.ID) (inPeers peer.IDSlice, outPeers peer.IDSlice, err error) {
131131
if len(specificPeers) == 0 {
132132
specificPeers = pm.host.Network().Peers()
133133
}
@@ -150,9 +150,9 @@ func (pm *PeerManager) GroupPeersByDirection(specificPeers []peer.ID) (inPeers p
150150

151151
// getRelayPeers - Returns list of in and out peers supporting WakuRelayProtocol within specifiedPeers.
152152
// If specifiedPeers is empty, it checks within all peers in peerStore.
153-
func (pm *PeerManager) getRelayPeers(specificPeers []peer.ID) (inRelayPeers peer.IDSlice, outRelayPeers peer.IDSlice) {
153+
func (pm *PeerManager) getRelayPeers(specificPeers ...peer.ID) (inRelayPeers peer.IDSlice, outRelayPeers peer.IDSlice) {
154154
//Group peers by their connected direction inbound or outbound.
155-
inPeers, outPeers, err := pm.GroupPeersByDirection(specificPeers)
155+
inPeers, outPeers, err := pm.GroupPeersByDirection(specificPeers...)
156156
if err != nil {
157157
return
158158
}
@@ -206,7 +206,7 @@ func (pm *PeerManager) connectToRelayPeers() {
206206
//Check for out peer connections and connect to more peers.
207207
pm.ensureMinRelayConnsPerTopic()
208208

209-
inRelayPeers, outRelayPeers := pm.getRelayPeers(nil)
209+
inRelayPeers, outRelayPeers := pm.getRelayPeers()
210210
pm.logger.Info("number of relay peers connected",
211211
zap.Int("in", inRelayPeers.Len()),
212212
zap.Int("out", outRelayPeers.Len()))
@@ -417,29 +417,69 @@ func (pm *PeerManager) addPeerToServiceSlot(proto protocol.ID, peerID peer.ID) {
417417
pm.serviceSlots.getPeers(proto).add(peerID)
418418
}
419419

420+
// SelectPeerByContentTopic is used to return a random peer that supports a given protocol for given contentTopic.
421+
// If a list of specific peers is passed, the peer will be chosen from that list assuming
422+
// it supports the chosen protocol and contentTopic, otherwise it will chose a peer from the service slot.
423+
// If a peer cannot be found in the service slot, a peer will be selected from node peerstore
424+
func (pm *PeerManager) SelectPeerByContentTopic(proto protocol.ID, contentTopic string, specificPeers ...peer.ID) (peer.ID, error) {
425+
pubsubTopic, err := waku_proto.GetPubSubTopicFromContentTopic(contentTopic)
426+
if err != nil {
427+
return "", err
428+
}
429+
return pm.SelectPeer(proto, pubsubTopic, specificPeers...)
430+
}
431+
420432
// SelectPeer is used to return a random peer that supports a given protocol.
421433
// If a list of specific peers is passed, the peer will be chosen from that list assuming
422434
// it supports the chosen protocol, otherwise it will chose a peer from the service slot.
423435
// If a peer cannot be found in the service slot, a peer will be selected from node peerstore
424-
func (pm *PeerManager) SelectPeer(proto protocol.ID, specificPeers []peer.ID) (peer.ID, error) {
436+
// if pubSubTopic is specified, peer is selected from list that support the pubSubTopic
437+
func (pm *PeerManager) SelectPeer(proto protocol.ID, pubSubTopic string, specificPeers ...peer.ID) (peer.ID, error) {
425438
// @TODO We need to be more strategic about which peers we dial. Right now we just set one on the service.
426439
// Ideally depending on the query and our set of peers we take a subset of ideal peers.
427440
// This will require us to check for various factors such as:
428441
// - which topics they track
429442
// - latency?
430443

431-
//Try to fetch from serviceSlot
432-
if slot := pm.serviceSlots.getPeers(proto); slot != nil {
433-
if peerID, err := slot.getRandom(); err == nil {
434-
return peerID, nil
435-
}
444+
if peerID := pm.selectServicePeer(proto, pubSubTopic, specificPeers...); peerID != nil {
445+
return *peerID, nil
436446
}
437447

438448
// if not found in serviceSlots or proto == WakuRelayIDv200
439449
filteredPeers, err := utils.FilterPeersByProto(pm.host, specificPeers, proto)
440450
if err != nil {
441451
return "", err
442452
}
443-
453+
if pubSubTopic != "" {
454+
filteredPeers = pm.host.Peerstore().(wps.WakuPeerstore).PeersByPubSubTopic(pubSubTopic, filteredPeers...)
455+
}
444456
return utils.SelectRandomPeer(filteredPeers, pm.logger)
445457
}
458+
459+
func (pm *PeerManager) selectServicePeer(proto protocol.ID, pubSubTopic string, specificPeers ...peer.ID) (peerIDPtr *peer.ID) {
460+
peerIDPtr = nil
461+
462+
//Try to fetch from serviceSlot
463+
if slot := pm.serviceSlots.getPeers(proto); slot != nil {
464+
if pubSubTopic == "" {
465+
if peerID, err := slot.getRandom(); err == nil {
466+
peerIDPtr = &peerID
467+
} else {
468+
pm.logger.Debug("could not retrieve random peer from slot", zap.Error(err))
469+
}
470+
} else { //PubsubTopic based selection
471+
keys := make([]peer.ID, 0, len(slot.m))
472+
for i := range slot.m {
473+
keys = append(keys, i)
474+
}
475+
selectedPeers := pm.host.Peerstore().(wps.WakuPeerstore).PeersByPubSubTopic(pubSubTopic, keys...)
476+
peerID, err := utils.SelectRandomPeer(selectedPeers, pm.logger)
477+
if err == nil {
478+
peerIDPtr = &peerID
479+
} else {
480+
pm.logger.Debug("could not select random peer", zap.Error(err))
481+
}
482+
}
483+
}
484+
return
485+
}

waku/v2/peermanager/peer_manager_test.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/stretchr/testify/require"
1414
"github.com/waku-org/go-waku/tests"
1515
wps "github.com/waku-org/go-waku/waku/v2/peerstore"
16+
wakuproto "github.com/waku-org/go-waku/waku/v2/protocol"
1617
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
1718
"github.com/waku-org/go-waku/waku/v2/utils"
1819
)
@@ -65,7 +66,7 @@ func TestServiceSlots(t *testing.T) {
6566
///////////////
6667

6768
// select peer from pm, currently only h2 is set in pm
68-
peerID, err := pm.SelectPeer(protocol, nil)
69+
peerID, err := pm.SelectPeer(protocol, "")
6970
require.NoError(t, err)
7071
require.Equal(t, peerID, h2.ID())
7172

@@ -74,7 +75,7 @@ func TestServiceSlots(t *testing.T) {
7475
require.NoError(t, err)
7576

7677
// check that returned peer is h2 or h3 peer
77-
peerID, err = pm.SelectPeer(protocol, nil)
78+
peerID, err = pm.SelectPeer(protocol, "")
7879
require.NoError(t, err)
7980
if peerID == h2.ID() || peerID == h3.ID() {
8081
//Test success
@@ -90,18 +91,55 @@ func TestServiceSlots(t *testing.T) {
9091
require.NoError(t, err)
9192
defer h4.Close()
9293

93-
_, err = pm.SelectPeer(protocol1, nil)
94+
_, err = pm.SelectPeer(protocol1, "")
9495
require.Error(t, err, utils.ErrNoPeersAvailable)
9596

9697
// add h4 peer for protocol1
9798
_, err = pm.AddPeer(getAddr(h4), wps.Static, []string{""}, libp2pProtocol.ID(protocol1))
9899
require.NoError(t, err)
99100

100101
//Test peer selection for protocol1
101-
peerID, err = pm.SelectPeer(protocol1, nil)
102+
peerID, err = pm.SelectPeer(protocol1, "")
102103
require.NoError(t, err)
103104
require.Equal(t, peerID, h4.ID())
104105

106+
_, err = pm.SelectPeerByContentTopic(protocol1, "")
107+
require.Error(t, wakuproto.ErrInvalidFormat, err)
108+
109+
}
110+
111+
func TestPeerSelection(t *testing.T) {
112+
ctx, pm, deferFn := initTest(t)
113+
defer deferFn()
114+
115+
h2, err := tests.MakeHost(ctx, 0, rand.Reader)
116+
require.NoError(t, err)
117+
defer h2.Close()
118+
119+
h3, err := tests.MakeHost(ctx, 0, rand.Reader)
120+
require.NoError(t, err)
121+
defer h3.Close()
122+
123+
protocol := libp2pProtocol.ID("test/protocol")
124+
_, err = pm.AddPeer(getAddr(h2), wps.Static, []string{"/waku/rs/2/1", "/waku/rs/2/2"}, libp2pProtocol.ID(protocol))
125+
require.NoError(t, err)
126+
127+
_, err = pm.AddPeer(getAddr(h3), wps.Static, []string{"/waku/rs/2/1"}, libp2pProtocol.ID(protocol))
128+
require.NoError(t, err)
129+
130+
_, err = pm.SelectPeer(protocol, "")
131+
require.NoError(t, err)
132+
133+
peerID, err := pm.SelectPeer(protocol, "/waku/rs/2/2")
134+
require.NoError(t, err)
135+
require.Equal(t, h2.ID(), peerID)
136+
137+
_, err = pm.SelectPeer(protocol, "/waku/rs/2/3")
138+
require.Error(t, utils.ErrNoPeersAvailable, err)
139+
140+
_, err = pm.SelectPeer(protocol, "/waku/rs/2/1")
141+
require.NoError(t, err)
142+
105143
}
106144

107145
func TestDefaultProtocol(t *testing.T) {
@@ -111,7 +149,7 @@ func TestDefaultProtocol(t *testing.T) {
111149
// check peer for default protocol
112150
///////////////
113151
//Test empty peer selection for relay protocol
114-
_, err := pm.SelectPeer(relay.WakuRelayID_v200, nil)
152+
_, err := pm.SelectPeer(relay.WakuRelayID_v200, "")
115153
require.Error(t, err, utils.ErrNoPeersAvailable)
116154

117155
///////////////
@@ -126,7 +164,7 @@ func TestDefaultProtocol(t *testing.T) {
126164
require.NoError(t, err)
127165

128166
// since we are not passing peerList, selectPeer fn using filterByProto checks in PeerStore for peers with same protocol.
129-
peerID, err := pm.SelectPeer(relay.WakuRelayID_v200, nil)
167+
peerID, err := pm.SelectPeer(relay.WakuRelayID_v200, "")
130168
require.NoError(t, err)
131169
require.Equal(t, peerID, h5.ID())
132170
}
@@ -146,12 +184,12 @@ func TestAdditionAndRemovalOfPeer(t *testing.T) {
146184
_, err = pm.AddPeer(getAddr(h6), wps.Static, []string{""}, protocol2)
147185
require.NoError(t, err)
148186

149-
peerID, err := pm.SelectPeer(protocol2, nil)
187+
peerID, err := pm.SelectPeer(protocol2, "")
150188
require.NoError(t, err)
151189
require.Equal(t, peerID, h6.ID())
152190

153191
pm.RemovePeer(peerID)
154-
_, err = pm.SelectPeer(protocol2, nil)
192+
_, err = pm.SelectPeer(protocol2, "")
155193
require.Error(t, err, utils.ErrNoPeersAvailable)
156194
}
157195

0 commit comments

Comments
 (0)