Skip to content

Commit 41a7388

Browse files
authored
Merge pull request #4805 from ipfs/feat/coreapi/pubsub
coreapi: PubSub API
2 parents 0fb2020 + 5618fed commit 41a7388

File tree

9 files changed

+419
-98
lines changed

9 files changed

+419
-98
lines changed

core/commands/pubsub.go

+36-97
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,16 @@ package commands
33
import (
44
"context"
55
"encoding/binary"
6-
"errors"
76
"fmt"
87
"io"
98
"net/http"
109
"sort"
11-
"sync"
12-
"time"
1310

14-
core "github.com/ipfs/go-ipfs/core"
1511
cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
1612
e "github.com/ipfs/go-ipfs/core/commands/e"
13+
options "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
1714

18-
cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
19-
blocks "gx/ipfs/QmRcHuYzAyswytBuMF78rj3LTChYszomRFXNg4685ZN1WM/go-block-format"
20-
pstore "gx/ipfs/QmSJ36wcYQyEViJUWUEhJU81tw1KdakTKqLLHbvYbA9zDv/go-libp2p-peerstore"
2115
cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
22-
floodsub "gx/ipfs/QmUK4h113Hh7bR2gPpsMcbUEbbzc7hspocmPi91Bmi69nH/go-libp2p-floodsub"
2316
cmds "gx/ipfs/QmXTmUCBtDUrzDYVzASogLiNph7EBuYqEgPL7QoHNMzUnz/go-ipfs-cmds"
2417
)
2518

@@ -48,6 +41,13 @@ const (
4841
pubsubDiscoverOptionName = "discover"
4942
)
5043

44+
type pubsubMessage struct {
45+
From []byte `json:"from,omitempty"`
46+
Data []byte `json:"data,omitempty"`
47+
Seqno []byte `json:"seqno,omitempty"`
48+
TopicIDs []string `json:"topicIDs,omitempty"`
49+
}
50+
5151
var PubsubSubCmd = &cmds.Command{
5252
Helptext: cmdkit.HelpText{
5353
Tagline: "Subscribe to messages on a given topic.",
@@ -79,40 +79,19 @@ This command outputs data in the following encodings:
7979
cmdkit.BoolOption(pubsubDiscoverOptionName, "try to discover other peers subscribed to the same topic"),
8080
},
8181
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
82-
n, err := cmdenv.GetNode(env)
82+
api, err := cmdenv.GetApi(env)
8383
if err != nil {
8484
return err
8585
}
8686

87-
// Must be online!
88-
if !n.OnlineMode() {
89-
return cmdkit.Errorf(cmdkit.ErrClient, ErrNotOnline.Error())
90-
}
91-
92-
if n.Floodsub == nil {
93-
return fmt.Errorf("experimental pubsub feature not enabled. Run daemon with --enable-pubsub-experiment to use")
94-
}
95-
9687
topic := req.Arguments[0]
97-
sub, err := n.Floodsub.Subscribe(topic)
88+
discover, _ := req.Options[pubsubDiscoverOptionName].(bool)
89+
90+
sub, err := api.PubSub().Subscribe(req.Context, topic, options.PubSub.Discover(discover))
9891
if err != nil {
9992
return err
10093
}
101-
defer sub.Cancel()
102-
103-
discover, _ := req.Options[pubsubDiscoverOptionName].(bool)
104-
if discover {
105-
go func() {
106-
blk := blocks.NewBlock([]byte("floodsub:" + topic))
107-
err := n.Blocks.AddBlock(blk)
108-
if err != nil {
109-
log.Error("pubsub discovery: ", err)
110-
return
111-
}
112-
113-
connectToPubSubPeers(req.Context, n, blk.Cid())
114-
}()
115-
}
94+
defer sub.Close()
11695

11796
if f, ok := res.(http.Flusher); ok {
11897
f.Flush()
@@ -126,15 +105,17 @@ This command outputs data in the following encodings:
126105
return err
127106
}
128107

129-
err = res.Emit(msg)
130-
if err != nil {
131-
return err
132-
}
108+
res.Emit(&pubsubMessage{
109+
Data: msg.Data(),
110+
From: []byte(msg.From()),
111+
Seqno: msg.Seq(),
112+
TopicIDs: msg.Topics(),
113+
})
133114
}
134115
},
135116
Encoders: cmds.EncoderMap{
136117
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
137-
m, ok := v.(*floodsub.Message)
118+
m, ok := v.(*pubsubMessage)
138119
if !ok {
139120
return fmt.Errorf("unexpected type: %T", v)
140121
}
@@ -143,7 +124,7 @@ This command outputs data in the following encodings:
143124
return err
144125
}),
145126
"ndpayload": cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
146-
m, ok := v.(*floodsub.Message)
127+
m, ok := v.(*pubsubMessage)
147128
if !ok {
148129
return fmt.Errorf("unexpected type: %T", v)
149130
}
@@ -153,7 +134,7 @@ This command outputs data in the following encodings:
153134
return err
154135
}),
155136
"lenpayload": cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
156-
m, ok := v.(*floodsub.Message)
137+
m, ok := v.(*pubsubMessage)
157138
if !ok {
158139
return fmt.Errorf("unexpected type: %T", v)
159140
}
@@ -166,31 +147,7 @@ This command outputs data in the following encodings:
166147
return err
167148
}),
168149
},
169-
Type: floodsub.Message{},
170-
}
171-
172-
func connectToPubSubPeers(ctx context.Context, n *core.IpfsNode, cid cid.Cid) {
173-
ctx, cancel := context.WithCancel(ctx)
174-
defer cancel()
175-
176-
provs := n.Routing.FindProvidersAsync(ctx, cid, 10)
177-
wg := &sync.WaitGroup{}
178-
for p := range provs {
179-
wg.Add(1)
180-
go func(pi pstore.PeerInfo) {
181-
defer wg.Done()
182-
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
183-
defer cancel()
184-
err := n.PeerHost.Connect(ctx, pi)
185-
if err != nil {
186-
log.Info("pubsub discover: ", err)
187-
return
188-
}
189-
log.Info("connected to pubsub peer:", pi.ID)
190-
}(p)
191-
}
192-
193-
wg.Wait()
150+
Type: pubsubMessage{},
194151
}
195152

196153
var PubsubPubCmd = &cmds.Command{
@@ -210,20 +167,11 @@ To use, the daemon must be run with '--enable-pubsub-experiment'.
210167
cmdkit.StringArg("data", true, true, "Payload of message to publish.").EnableStdin(),
211168
},
212169
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
213-
n, err := cmdenv.GetNode(env)
170+
api, err := cmdenv.GetApi(env)
214171
if err != nil {
215172
return err
216173
}
217174

218-
// Must be online!
219-
if !n.OnlineMode() {
220-
return cmdkit.Errorf(cmdkit.ErrClient, ErrNotOnline.Error())
221-
}
222-
223-
if n.Floodsub == nil {
224-
return errors.New("experimental pubsub feature not enabled. Run daemon with --enable-pubsub-experiment to use.")
225-
}
226-
227175
topic := req.Arguments[0]
228176

229177
err = req.ParseBodyArgs()
@@ -232,7 +180,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'.
232180
}
233181

234182
for _, data := range req.Arguments[1:] {
235-
if err := n.Floodsub.Publish(topic, []byte(data)); err != nil {
183+
if err := api.PubSub().Publish(req.Context, topic, []byte(data)); err != nil {
236184
return err
237185
}
238186
}
@@ -254,21 +202,17 @@ To use, the daemon must be run with '--enable-pubsub-experiment'.
254202
`,
255203
},
256204
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
257-
n, err := cmdenv.GetNode(env)
205+
api, err := cmdenv.GetApi(env)
258206
if err != nil {
259207
return err
260208
}
261209

262-
// Must be online!
263-
if !n.OnlineMode() {
264-
return cmdkit.Errorf(cmdkit.ErrClient, ErrNotOnline.Error())
265-
}
266-
267-
if n.Floodsub == nil {
268-
return errors.New("experimental pubsub feature not enabled. Run daemon with --enable-pubsub-experiment to use.")
210+
l, err := api.PubSub().Ls(req.Context)
211+
if err != nil {
212+
return err
269213
}
270214

271-
return cmds.EmitOnce(res, stringList{n.Floodsub.GetTopics()})
215+
return cmds.EmitOnce(res, stringList{l})
272216
},
273217
Type: stringList{},
274218
Encoders: cmds.EncoderMap{
@@ -308,26 +252,21 @@ To use, the daemon must be run with '--enable-pubsub-experiment'.
308252
cmdkit.StringArg("topic", false, false, "topic to list connected peers of"),
309253
},
310254
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
311-
n, err := cmdenv.GetNode(env)
255+
api, err := cmdenv.GetApi(env)
312256
if err != nil {
313257
return err
314258
}
315259

316-
// Must be online!
317-
if !n.OnlineMode() {
318-
return cmdkit.Errorf(cmdkit.ErrClient, ErrNotOnline.Error())
319-
}
320-
321-
if n.Floodsub == nil {
322-
return errors.New("experimental pubsub feature not enabled. Run daemon with --enable-pubsub-experiment to use")
323-
}
324-
325260
var topic string
326261
if len(req.Arguments) == 1 {
327262
topic = req.Arguments[0]
328263
}
329264

330-
peers := n.Floodsub.ListPeers(topic)
265+
peers, err := api.PubSub().Peers(req.Context, options.PubSub.Topic(topic))
266+
if err != nil {
267+
return err
268+
}
269+
331270
list := &stringList{make([]string, 0, len(peers))}
332271

333272
for _, peer := range peers {

core/coreapi/coreapi.go

+9
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ package coreapi
1616
import (
1717
core "github.com/ipfs/go-ipfs/core"
1818
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
19+
20+
logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
1921
)
2022

23+
var log = logging.Logger("core/coreapi")
24+
2125
type CoreAPI struct {
2226
node *core.IpfsNode
2327
}
@@ -72,3 +76,8 @@ func (api *CoreAPI) Dht() coreiface.DhtAPI {
7276
func (api *CoreAPI) Swarm() coreiface.SwarmAPI {
7377
return (*SwarmAPI)(api)
7478
}
79+
80+
// PubSub returns the PubSubAPI interface implementation backed by the go-ipfs node
81+
func (api *CoreAPI) PubSub() coreiface.PubSubAPI {
82+
return (*PubSubAPI)(api)
83+
}

core/coreapi/interface/coreapi.go

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ type CoreAPI interface {
3737
// Swarm returns an implementation of Swarm API
3838
Swarm() SwarmAPI
3939

40+
// PubSub returns an implementation of PubSub API
41+
PubSub() PubSubAPI
42+
4043
// ResolvePath resolves the path using Unixfs resolver
4144
ResolvePath(context.Context, Path) (ResolvedPath, error)
4245

core/coreapi/interface/errors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import "errors"
44

55
var (
66
ErrIsDir = errors.New("object is a directory")
7-
ErrOffline = errors.New("can't resolve, ipfs node is offline")
7+
ErrOffline = errors.New("this action must be run in online mode, try running 'ipfs daemon' first")
88
)
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package options
2+
3+
type PubSubPeersSettings struct {
4+
Topic string
5+
}
6+
7+
type PubSubSubscribeSettings struct {
8+
Discover bool
9+
}
10+
11+
type PubSubPeersOption func(*PubSubPeersSettings) error
12+
type PubSubSubscribeOption func(*PubSubSubscribeSettings) error
13+
14+
func PubSubPeersOptions(opts ...PubSubPeersOption) (*PubSubPeersSettings, error) {
15+
options := &PubSubPeersSettings{
16+
Topic: "",
17+
}
18+
19+
for _, opt := range opts {
20+
err := opt(options)
21+
if err != nil {
22+
return nil, err
23+
}
24+
}
25+
return options, nil
26+
}
27+
28+
func PubSubSubscribeOptions(opts ...PubSubSubscribeOption) (*PubSubSubscribeSettings, error) {
29+
options := &PubSubSubscribeSettings{
30+
Discover: false,
31+
}
32+
33+
for _, opt := range opts {
34+
err := opt(options)
35+
if err != nil {
36+
return nil, err
37+
}
38+
}
39+
return options, nil
40+
}
41+
42+
type pubsubOpts struct{}
43+
44+
var PubSub pubsubOpts
45+
46+
func (pubsubOpts) Topic(topic string) PubSubPeersOption {
47+
return func(settings *PubSubPeersSettings) error {
48+
settings.Topic = topic
49+
return nil
50+
}
51+
}
52+
53+
func (pubsubOpts) Discover(discover bool) PubSubSubscribeOption {
54+
return func(settings *PubSubSubscribeSettings) error {
55+
settings.Discover = discover
56+
return nil
57+
}
58+
}

core/coreapi/interface/pubsub.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package iface
2+
3+
import (
4+
"context"
5+
"io"
6+
7+
options "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
8+
9+
peer "gx/ipfs/QmbNepETomvmXfz1X5pHNFD2QuPqnqi47dTd94QJWSorQ3/go-libp2p-peer"
10+
)
11+
12+
// PubSubSubscription is an active PubSub subscription
13+
type PubSubSubscription interface {
14+
io.Closer
15+
16+
// Next return the next incoming message
17+
Next(context.Context) (PubSubMessage, error)
18+
}
19+
20+
// PubSubMessage is a single PubSub message
21+
type PubSubMessage interface {
22+
// From returns id of a peer from which the message has arrived
23+
From() peer.ID
24+
25+
// Data returns the message body
26+
Data() []byte
27+
28+
// Seq returns message identifier
29+
Seq() []byte
30+
31+
// Topics returns list of topics this message was set to
32+
Topics() []string
33+
}
34+
35+
// PubSubAPI specifies the interface to PubSub
36+
type PubSubAPI interface {
37+
// Ls lists subscribed topics by name
38+
Ls(context.Context) ([]string, error)
39+
40+
// Peers list peers we are currently pubsubbing with
41+
Peers(context.Context, ...options.PubSubPeersOption) ([]peer.ID, error)
42+
43+
// Publish a message to a given pubsub topic
44+
Publish(context.Context, string, []byte) error
45+
46+
// Subscribe to messages on a given topic
47+
Subscribe(context.Context, string, ...options.PubSubSubscribeOption) (PubSubSubscription, error)
48+
}

0 commit comments

Comments
 (0)