@cryptotaxi247 / kubo / commits / 92c4dc61a

feat(routing): Delegated Routing (#8997)

* Delegated Routing. Implementation of Reframe specs (https://github.com/ipfs/specs/blob/master/REFRAME.md) using go-delegated-routing library. * Requested changes. * Init using op string * Separate possible ContentRouters for TopicDiscovery. If we don't do this, we have a ciclic dependency creating TieredRouter. Now we can create first all possible content routers, and after that, create Routers. * Set dht default routing type * Add tests and remove uneeded code * Add documentation. * docs: Routing.Routers * Requested changes. Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com> * Add some documentation on new fx functions. * Add changelog entry and integration tests * test: sharness for 'dht' in 'routing' commands Since 'routing' is currently the same as 'dht' (minus query command) we need to test both, that way we won't have unnoticed divergence in the default behavior. * test(sharness): delegated routing via reframe URL * Add more tests for delegated routing. * If any put operation fails, the tiered router will fail. * refactor: Routing.Routers: Parameters.Endpoint As agreed in https://github.com/ipfs/kubo/pull/8997#issuecomment-1175684716 * Try to improve CHANGELOG entry. * chore: update reframe spec link * Update go-delegated-routing dependency * Fix config error test * use new changelog format * Remove port conflict * go mod tidy * ProviderManyWrapper to ProviderMany * Update docs/changelogs/v0.14.md Co-authored-by: Adin Schmahmann <adin.schmahmann@gmail.com> Co-authored-by: Marcin Rataj <lidel@lidel.org> Co-authored-by: Adin Schmahmann <adin.schmahmann@gmail.com>

Antonio Navarro Perez committed Jul 7, 2022 at 23:10 UTC 92c4dc61a89dcba4ca9fdc7664758252667826a6
33 files changed +1821 -644
CHANGELOG.md
+1
@@ -1,5 +1,6 @@
1 # Kubo Changelogs
2
3 +- [v0.14](docs/changelogs/v0.14.md)
4 - [v0.13](docs/changelogs/v0.13.md)
5 - [v0.12](docs/changelogs/v0.12.md)
6 - [v0.11](docs/changelogs/v0.11.md)
cmd/ipfs/daemon.go
+1 -4
@@ -400,10 +400,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
400
401 routingOption, _ := req.Options[routingOptionKwd].(string)
402 if routingOption == routingOptionDefaultKwd {
403 - routingOption = cfg.Routing.Type
404 - if routingOption == "" {
405 - routingOption = routingOptionDHTKwd
406 - }
403 + routingOption = cfg.Routing.Type.WithDefault(routingOptionDHTKwd)
404 }
405 switch routingOption {
406 case routingOptionSupernodeKwd:
config/init.go
+1 -1
@@ -48,7 +48,7 @@ func InitWithIdentity(identity Identity) (*Config, error) {
48 },
49
50 Routing: Routing{
51 - Type: "dht",
51 + Type: NewOptionalString("dht"),
52 },
53
54 // setup the node mount points.
config/profile.go
+1 -1
@@ -174,7 +174,7 @@ functionality - performance of content discovery and data
174 fetching may be degraded.
175 `,
176 Transform: func(c *Config) error {
177 - c.Routing.Type = "dhtclient"
177 + c.Routing.Type = NewOptionalString("dhtclient")
178 c.AutoNAT.ServiceMode = AutoNATServiceDisabled
179 c.Reprovider.Interval = "0"
180
config/routing.go
+35
@@ -5,5 +5,40 @@ type Routing struct {
5 // Type sets default daemon routing mode.
6 //
7 // Can be one of "dht", "dhtclient", "dhtserver", "none", or unset.
8 + Type *OptionalString `json:",omitempty"`
9 +
10 + Routers map[string]Router
11 +}
12 +
13 +type Router struct {
14 +
15 + // Currenly only supported Type is "reframe".
16 + // Reframe type allows to add other resolvers using the Reframe spec:
17 + // https://github.com/ipfs/specs/tree/main/reframe
18 + // In the future we will support "dht" and other Types here.
19 Type string
20 +
21 + Enabled Flag `json:",omitempty"`
22 +
23 + // Parameters are extra configuration that this router might need.
24 + // A common one for reframe router is "Endpoint".
25 + Parameters map[string]string
26 }
27 +
28 +// Type is the routing type.
29 +// Depending of the type we need to instantiate different Routing implementations.
30 +type RouterType string
31 +
32 +const (
33 + RouterTypeReframe RouterType = "reframe"
34 +)
35 +
36 +type RouterParam string
37 +
38 +const (
39 + // RouterParamEndpoint is the URL where the routing implementation will point to get the information.
40 + // Usually used for reframe Routers.
41 + RouterParamEndpoint RouterParam = "Endpoint"
42 +
43 + RouterParamPriority RouterParam = "Priority"
44 +)
config/types.go
+5
@@ -321,6 +321,11 @@ type OptionalString struct {
321 value *string
322 }
323
324 +// NewOptionalString returns an OptionalString from a string
325 +func NewOptionalString(s string) *OptionalString {
326 + return &OptionalString{value: &s}
327 +}
328 +
329 // WithDefault resolves the integer with the given default.
330 func (p *OptionalString) WithDefault(defaultValue string) (value string) {
331 if p == nil || p.value == nil {
core/commands/commands_test.go
+6
@@ -119,6 +119,12 @@ func TestCommands(t *testing.T) {
119 "/dht/provide",
120 "/dht/put",
121 "/dht/query",
122 + "/routing",
123 + "/routing/put",
124 + "/routing/get",
125 + "/routing/findpeer",
126 + "/routing/findprovs",
127 + "/routing/provide",
128 "/diag",
129 "/diag/cmds",
130 "/diag/cmds/clear",
core/commands/dht.go
+50 -579
@@ -2,28 +2,18 @@ package commands
2
3 import (
4 "context"
5 - "encoding/base64"
5 "errors"
6 "fmt"
7 "io"
9 - "time"
8
11 - cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
12 -
13 - cid "github.com/ipfs/go-cid"
9 cmds "github.com/ipfs/go-ipfs-cmds"
15 - ipld "github.com/ipfs/go-ipld-format"
16 - dag "github.com/ipfs/go-merkledag"
17 - path "github.com/ipfs/go-path"
10 + "github.com/ipfs/kubo/core/commands/cmdenv"
11 peer "github.com/libp2p/go-libp2p-core/peer"
12 routing "github.com/libp2p/go-libp2p-core/routing"
13 )
14
15 var ErrNotDHT = errors.New("routing service is not a DHT")
16
24 -// TODO: Factor into `ipfs dht` and `ipfs routing`.
25 -// Everything *except `query` goes into `ipfs routing`.
26 -
17 var DhtCmd = &cmds.Command{
18 Helptext: cmds.HelpText{
19 Tagline: "Issue commands directly through the DHT.",
@@ -40,9 +30,55 @@ var DhtCmd = &cmds.Command{
30 },
31 }
32
43 -const (
44 - dhtVerboseOptionName = "verbose"
45 -)
33 +var findProvidersDhtCmd = &cmds.Command{
34 + Helptext: findProvidersRoutingCmd.Helptext,
35 + Arguments: findProvidersRoutingCmd.Arguments,
36 + Options: findProvidersRoutingCmd.Options,
37 + Run: findProvidersRoutingCmd.Run,
38 + Encoders: findProvidersRoutingCmd.Encoders,
39 + Type: findProvidersRoutingCmd.Type,
40 + Status: cmds.Deprecated,
41 +}
42 +
43 +var findPeerDhtCmd = &cmds.Command{
44 + Helptext: findPeerRoutingCmd.Helptext,
45 + Arguments: findPeerRoutingCmd.Arguments,
46 + Options: findPeerRoutingCmd.Options,
47 + Run: findPeerRoutingCmd.Run,
48 + Encoders: findPeerRoutingCmd.Encoders,
49 + Type: findPeerRoutingCmd.Type,
50 + Status: cmds.Deprecated,
51 +}
52 +
53 +var getValueDhtCmd = &cmds.Command{
54 + Helptext: getValueRoutingCmd.Helptext,
55 + Arguments: getValueRoutingCmd.Arguments,
56 + Options: getValueRoutingCmd.Options,
57 + Run: getValueRoutingCmd.Run,
58 + Encoders: getValueRoutingCmd.Encoders,
59 + Type: getValueRoutingCmd.Type,
60 + Status: cmds.Deprecated,
61 +}
62 +
63 +var putValueDhtCmd = &cmds.Command{
64 + Helptext: putValueRoutingCmd.Helptext,
65 + Arguments: putValueRoutingCmd.Arguments,
66 + Options: putValueRoutingCmd.Options,
67 + Run: putValueRoutingCmd.Run,
68 + Encoders: putValueRoutingCmd.Encoders,
69 + Type: putValueRoutingCmd.Type,
70 + Status: cmds.Deprecated,
71 +}
72 +
73 +var provideRefDhtCmd = &cmds.Command{
74 + Helptext: provideRefRoutingCmd.Helptext,
75 + Arguments: provideRefRoutingCmd.Arguments,
76 + Options: provideRefRoutingCmd.Options,
77 + Run: provideRefRoutingCmd.Run,
78 + Encoders: provideRefRoutingCmd.Encoders,
79 + Type: provideRefRoutingCmd.Type,
80 + Status: cmds.Deprecated,
81 +}
82
83 // kademlia extends the routing interface with a command to get the peers closest to the target
84 type kademlia interface {
@@ -133,568 +169,3 @@ var queryDhtCmd = &cmds.Command{
169 },
170 Type: routing.QueryEvent{},
171 }
136 -
137 -const (
138 - numProvidersOptionName = "num-providers"
139 -)
140 -
141 -var findProvidersDhtCmd = &cmds.Command{
142 - Helptext: cmds.HelpText{
143 - Tagline: "Find peers that can provide a specific value, given a key.",
144 - ShortDescription: "Outputs a list of newline-delimited provider Peer IDs.",
145 - },
146 -
147 - Arguments: []cmds.Argument{
148 - cmds.StringArg("key", true, true, "The key to find providers for."),
149 - },
150 - Options: []cmds.Option{
151 - cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
152 - cmds.IntOption(numProvidersOptionName, "n", "The number of providers to find.").WithDefault(20),
153 - },
154 - Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
155 - n, err := cmdenv.GetNode(env)
156 - if err != nil {
157 - return err
158 - }
159 -
160 - if !n.IsOnline {
161 - return ErrNotOnline
162 - }
163 -
164 - numProviders, _ := req.Options[numProvidersOptionName].(int)
165 - if numProviders < 1 {
166 - return fmt.Errorf("number of providers must be greater than 0")
167 - }
168 -
169 - c, err := cid.Parse(req.Arguments[0])
170 -
171 - if err != nil {
172 - return err
173 - }
174 -
175 - ctx, cancel := context.WithCancel(req.Context)
176 - ctx, events := routing.RegisterForQueryEvents(ctx)
177 -
178 - pchan := n.Routing.FindProvidersAsync(ctx, c, numProviders)
179 -
180 - go func() {
181 - defer cancel()
182 - for p := range pchan {
183 - np := p
184 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
185 - Type: routing.Provider,
186 - Responses: []*peer.AddrInfo{&np},
187 - })
188 - }
189 - }()
190 - for e := range events {
191 - if err := res.Emit(e); err != nil {
192 - return err
193 - }
194 - }
195 -
196 - return nil
197 - },
198 - Encoders: cmds.EncoderMap{
199 - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
200 - pfm := pfuncMap{
201 - routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
202 - if verbose {
203 - fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
204 - }
205 - return nil
206 - },
207 - routing.Provider: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
208 - prov := obj.Responses[0]
209 - if verbose {
210 - fmt.Fprintf(out, "provider: ")
211 - }
212 - fmt.Fprintf(out, "%s\n", prov.ID.Pretty())
213 - if verbose {
214 - for _, a := range prov.Addrs {
215 - fmt.Fprintf(out, "\t%s\n", a)
216 - }
217 - }
218 - return nil
219 - },
220 - }
221 -
222 - verbose, _ := req.Options[dhtVerboseOptionName].(bool)
223 - return printEvent(out, w, verbose, pfm)
224 - }),
225 - },
226 - Type: routing.QueryEvent{},
227 -}
228 -
229 -const (
230 - recursiveOptionName = "recursive"
231 -)
232 -
233 -var provideRefDhtCmd = &cmds.Command{
234 - Helptext: cmds.HelpText{
235 - Tagline: "Announce to the network that you are providing given values.",
236 - },
237 -
238 - Arguments: []cmds.Argument{
239 - cmds.StringArg("key", true, true, "The key[s] to send provide records for.").EnableStdin(),
240 - },
241 - Options: []cmds.Option{
242 - cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
243 - cmds.BoolOption(recursiveOptionName, "r", "Recursively provide entire graph."),
244 - },
245 - Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
246 - nd, err := cmdenv.GetNode(env)
247 - if err != nil {
248 - return err
249 - }
250 -
251 - if !nd.IsOnline {
252 - return ErrNotOnline
253 - }
254 -
255 - if len(nd.PeerHost.Network().Conns()) == 0 {
256 - return errors.New("cannot provide, no connected peers")
257 - }
258 -
259 - // Needed to parse stdin args.
260 - // TODO: Lazy Load
261 - err = req.ParseBodyArgs()
262 - if err != nil {
263 - return err
264 - }
265 -
266 - rec, _ := req.Options[recursiveOptionName].(bool)
267 -
268 - var cids []cid.Cid
269 - for _, arg := range req.Arguments {
270 - c, err := cid.Decode(arg)
271 - if err != nil {
272 - return err
273 - }
274 -
275 - has, err := nd.Blockstore.Has(req.Context, c)
276 - if err != nil {
277 - return err
278 - }
279 -
280 - if !has {
281 - return fmt.Errorf("block %s not found locally, cannot provide", c)
282 - }
283 -
284 - cids = append(cids, c)
285 - }
286 -
287 - ctx, cancel := context.WithCancel(req.Context)
288 - ctx, events := routing.RegisterForQueryEvents(ctx)
289 -
290 - var provideErr error
291 - go func() {
292 - defer cancel()
293 - if rec {
294 - provideErr = provideKeysRec(ctx, nd.Routing, nd.DAG, cids)
295 - } else {
296 - provideErr = provideKeys(ctx, nd.Routing, cids)
297 - }
298 - if provideErr != nil {
299 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
300 - Type: routing.QueryError,
301 - Extra: provideErr.Error(),
302 - })
303 - }
304 - }()
305 -
306 - for e := range events {
307 - if err := res.Emit(e); err != nil {
308 - return err
309 - }
310 - }
311 -
312 - return provideErr
313 - },
314 - Encoders: cmds.EncoderMap{
315 - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
316 - pfm := pfuncMap{
317 - routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
318 - if verbose {
319 - fmt.Fprintf(out, "sending provider record to peer %s\n", obj.ID)
320 - }
321 - return nil
322 - },
323 - }
324 -
325 - verbose, _ := req.Options[dhtVerboseOptionName].(bool)
326 - return printEvent(out, w, verbose, pfm)
327 - }),
328 - },
329 - Type: routing.QueryEvent{},
330 -}
331 -
332 -func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error {
333 - for _, c := range cids {
334 - err := r.Provide(ctx, c, true)
335 - if err != nil {
336 - return err
337 - }
338 - }
339 - return nil
340 -}
341 -
342 -func provideKeysRec(ctx context.Context, r routing.Routing, dserv ipld.DAGService, cids []cid.Cid) error {
343 - provided := cid.NewSet()
344 - for _, c := range cids {
345 - kset := cid.NewSet()
346 -
347 - err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, kset.Visit)
348 - if err != nil {
349 - return err
350 - }
351 -
352 - for _, k := range kset.Keys() {
353 - if provided.Has(k) {
354 - continue
355 - }
356 -
357 - err = r.Provide(ctx, k, true)
358 - if err != nil {
359 - return err
360 - }
361 - provided.Add(k)
362 - }
363 - }
364 -
365 - return nil
366 -}
367 -
368 -var findPeerDhtCmd = &cmds.Command{
369 - Helptext: cmds.HelpText{
370 - Tagline: "Find the multiaddresses associated with a Peer ID.",
371 - ShortDescription: "Outputs a list of newline-delimited multiaddresses.",
372 - },
373 -
374 - Arguments: []cmds.Argument{
375 - cmds.StringArg("peerID", true, true, "The ID of the peer to search for."),
376 - },
377 - Options: []cmds.Option{
378 - cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
379 - },
380 - Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
381 - nd, err := cmdenv.GetNode(env)
382 - if err != nil {
383 - return err
384 - }
385 -
386 - if !nd.IsOnline {
387 - return ErrNotOnline
388 - }
389 -
390 - pid, err := peer.Decode(req.Arguments[0])
391 - if err != nil {
392 - return err
393 - }
394 -
395 - ctx, cancel := context.WithCancel(req.Context)
396 - ctx, events := routing.RegisterForQueryEvents(ctx)
397 -
398 - var findPeerErr error
399 - go func() {
400 - defer cancel()
401 - var pi peer.AddrInfo
402 - pi, findPeerErr = nd.Routing.FindPeer(ctx, pid)
403 - if findPeerErr != nil {
404 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
405 - Type: routing.QueryError,
406 - Extra: findPeerErr.Error(),
407 - })
408 - return
409 - }
410 -
411 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
412 - Type: routing.FinalPeer,
413 - Responses: []*peer.AddrInfo{&pi},
414 - })
415 - }()
416 -
417 - for e := range events {
418 - if err := res.Emit(e); err != nil {
419 - return err
420 - }
421 - }
422 -
423 - return findPeerErr
424 - },
425 - Encoders: cmds.EncoderMap{
426 - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
427 - pfm := pfuncMap{
428 - routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
429 - pi := obj.Responses[0]
430 - for _, a := range pi.Addrs {
431 - fmt.Fprintf(out, "%s\n", a)
432 - }
433 - return nil
434 - },
435 - }
436 -
437 - verbose, _ := req.Options[dhtVerboseOptionName].(bool)
438 - return printEvent(out, w, verbose, pfm)
439 - }),
440 - },
441 - Type: routing.QueryEvent{},
442 -}
443 -
444 -var getValueDhtCmd = &cmds.Command{
445 - Helptext: cmds.HelpText{
446 - Tagline: "Given a key, query the routing system for its best value.",
447 - ShortDescription: `
448 -Outputs the best value for the given key.
449 -
450 -There may be several different values for a given key stored in the routing
451 -system; in this context 'best' means the record that is most desirable. There is
452 -no one metric for 'best': it depends entirely on the key type. For IPNS, 'best'
453 -is the record that is both valid and has the highest sequence number (freshest).
454 -Different key types can specify other 'best' rules.
455 -`,
456 - },
457 -
458 - Arguments: []cmds.Argument{
459 - cmds.StringArg("key", true, true, "The key to find a value for."),
460 - },
461 - Options: []cmds.Option{
462 - cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
463 - },
464 - Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
465 - nd, err := cmdenv.GetNode(env)
466 - if err != nil {
467 - return err
468 - }
469 -
470 - if !nd.IsOnline {
471 - return ErrNotOnline
472 - }
473 -
474 - dhtkey, err := escapeDhtKey(req.Arguments[0])
475 - if err != nil {
476 - return err
477 - }
478 -
479 - ctx, cancel := context.WithCancel(req.Context)
480 - ctx, events := routing.RegisterForQueryEvents(ctx)
481 -
482 - var getErr error
483 - go func() {
484 - defer cancel()
485 - var val []byte
486 - val, getErr = nd.Routing.GetValue(ctx, dhtkey)
487 - if getErr != nil {
488 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
489 - Type: routing.QueryError,
490 - Extra: getErr.Error(),
491 - })
492 - } else {
493 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
494 - Type: routing.Value,
495 - Extra: base64.StdEncoding.EncodeToString(val),
496 - })
497 - }
498 - }()
499 -
500 - for e := range events {
501 - if err := res.Emit(e); err != nil {
502 - return err
503 - }
504 - }
505 -
506 - return getErr
507 - },
508 - Encoders: cmds.EncoderMap{
509 - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
510 - pfm := pfuncMap{
511 - routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
512 - if verbose {
513 - _, err := fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
514 - return err
515 - }
516 - res, err := base64.StdEncoding.DecodeString(obj.Extra)
517 - if err != nil {
518 - return err
519 - }
520 - _, err = out.Write(res)
521 - return err
522 - },
523 - }
524 -
525 - verbose, _ := req.Options[dhtVerboseOptionName].(bool)
526 - return printEvent(out, w, verbose, pfm)
527 - }),
528 - },
529 - Type: routing.QueryEvent{},
530 -}
531 -
532 -var putValueDhtCmd = &cmds.Command{
533 - Helptext: cmds.HelpText{
534 - Tagline: "Write a key/value pair to the routing system.",
535 - ShortDescription: `
536 -Given a key of the form /foo/bar and a valid value for that key, this will write
537 -that value to the routing system with that key.
538 -
539 -Keys have two parts: a keytype (foo) and the key name (bar). IPNS uses the
540 -/ipns keytype, and expects the key name to be a Peer ID. IPNS entries are
541 -specifically formatted (protocol buffer).
542 -
543 -You may only use keytypes that are supported in your ipfs binary: currently
544 -this is only /ipns. Unless you have a relatively deep understanding of the
545 -go-ipfs routing internals, you likely want to be using 'ipfs name publish' instead
546 -of this.
547 -
548 -The value must be a valid value for the given key type. For example, if the key
549 -is /ipns/QmFoo, the value must be IPNS record (protobuf) signed with the key
550 -identified by QmFoo.
551 -`,
552 - },
553 -
554 - Arguments: []cmds.Argument{
555 - cmds.StringArg("key", true, false, "The key to store the value at."),
556 - cmds.FileArg("value-file", true, false, "A path to a file containing the value to store.").EnableStdin(),
557 - },
558 - Options: []cmds.Option{
559 - cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
560 - },
561 - Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
562 - nd, err := cmdenv.GetNode(env)
563 - if err != nil {
564 - return err
565 - }
566 -
567 - if !nd.IsOnline {
568 - return ErrNotOnline
569 - }
570 -
571 - key, err := escapeDhtKey(req.Arguments[0])
572 - if err != nil {
573 - return err
574 - }
575 -
576 - file, err := cmdenv.GetFileArg(req.Files.Entries())
577 - if err != nil {
578 - return err
579 - }
580 - defer file.Close()
581 -
582 - data, err := io.ReadAll(file)
583 - if err != nil {
584 - return err
585 - }
586 -
587 - ctx, cancel := context.WithCancel(req.Context)
588 - ctx, events := routing.RegisterForQueryEvents(ctx)
589 -
590 - var putErr error
591 - go func() {
592 - defer cancel()
593 - putErr = nd.Routing.PutValue(ctx, key, []byte(data))
594 - if putErr != nil {
595 - routing.PublishQueryEvent(ctx, &routing.QueryEvent{
596 - Type: routing.QueryError,
597 - Extra: putErr.Error(),
598 - })
599 - }
600 - }()
601 -
602 - for e := range events {
603 - if err := res.Emit(e); err != nil {
604 - return err
605 - }
606 - }
607 -
608 - return putErr
609 - },
610 - Encoders: cmds.EncoderMap{
611 - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
612 - pfm := pfuncMap{
613 - routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
614 - if verbose {
615 - fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
616 - }
617 - return nil
618 - },
619 - routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
620 - fmt.Fprintf(out, "%s\n", obj.ID.Pretty())
621 - return nil
622 - },
623 - }
624 -
625 - verbose, _ := req.Options[dhtVerboseOptionName].(bool)
626 -
627 - return printEvent(out, w, verbose, pfm)
628 - }),
629 - },
630 - Type: routing.QueryEvent{},
631 -}
632 -
633 -type printFunc func(obj *routing.QueryEvent, out io.Writer, verbose bool) error
634 -type pfuncMap map[routing.QueryEventType]printFunc
635 -
636 -func printEvent(obj *routing.QueryEvent, out io.Writer, verbose bool, override pfuncMap) error {
637 - if verbose {
638 - fmt.Fprintf(out, "%s: ", time.Now().Format("15:04:05.000"))
639 - }
640 -
641 - if override != nil {
642 - if pf, ok := override[obj.Type]; ok {
643 - return pf(obj, out, verbose)
644 - }
645 - }
646 -
647 - switch obj.Type {
648 - case routing.SendingQuery:
649 - if verbose {
650 - fmt.Fprintf(out, "* querying %s\n", obj.ID)
651 - }
652 - case routing.Value:
653 - if verbose {
654 - fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
655 - } else {
656 - fmt.Fprint(out, obj.Extra)
657 - }
658 - case routing.PeerResponse:
659 - if verbose {
660 - fmt.Fprintf(out, "* %s says use ", obj.ID)
661 - for _, p := range obj.Responses {
662 - fmt.Fprintf(out, "%s ", p.ID)
663 - }
664 - fmt.Fprintln(out)
665 - }
666 - case routing.QueryError:
667 - if verbose {
668 - fmt.Fprintf(out, "error: %s\n", obj.Extra)
669 - }
670 - case routing.DialingPeer:
671 - if verbose {
672 - fmt.Fprintf(out, "dialing peer: %s\n", obj.ID)
673 - }
674 - case routing.AddingPeer:
675 - if verbose {
676 - fmt.Fprintf(out, "adding peer to query: %s\n", obj.ID)
677 - }
678 - case routing.FinalPeer:
679 - default:
680 - if verbose {
681 - fmt.Fprintf(out, "unrecognized event type: %d\n", obj.Type)
682 - }
683 - }
684 - return nil
685 -}
686 -
687 -func escapeDhtKey(s string) (string, error) {
688 - parts := path.SplitList(s)
689 - if len(parts) != 3 ||
690 - parts[0] != "" ||
691 - !(parts[1] == "ipns" || parts[1] == "pk") {
692 - return "", errors.New("invalid key")
693 - }
694 -
695 - k, err := peer.Decode(parts[2])
696 - if err != nil {
697 - return "", err
698 - }
699 - return path.Join(append(parts[:2], string(k))), nil
700 -}
core/commands/root.go
+1
@@ -135,6 +135,7 @@ var rootSubcommands = map[string]*cmds.Command{
135 "config": ConfigCmd,
136 "dag": dag.DagCmd,
137 "dht": DhtCmd,
138 + "routing": RoutingCmd,
139 "diag": DiagCmd,
140 "dns": DNSCmd,
141 "id": IDCmd,
core/commands/routing.go new
+604
@@ -0,0 +1,604 @@
1 +package commands
2 +
3 +import (
4 + "context"
5 + "encoding/base64"
6 + "errors"
7 + "fmt"
8 + "io"
9 + "time"
10 +
11 + cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
12 +
13 + cid "github.com/ipfs/go-cid"
14 + cmds "github.com/ipfs/go-ipfs-cmds"
15 + ipld "github.com/ipfs/go-ipld-format"
16 + dag "github.com/ipfs/go-merkledag"
17 + path "github.com/ipfs/go-path"
18 + peer "github.com/libp2p/go-libp2p-core/peer"
19 + routing "github.com/libp2p/go-libp2p-core/routing"
20 +)
21 +
22 +var RoutingCmd = &cmds.Command{
23 + Helptext: cmds.HelpText{
24 + Tagline: "Issue routing commands.",
25 + ShortDescription: ``,
26 + },
27 +
28 + Subcommands: map[string]*cmds.Command{
29 + "findprovs": findProvidersRoutingCmd,
30 + "findpeer": findPeerRoutingCmd,
31 + "get": getValueRoutingCmd,
32 + "put": putValueRoutingCmd,
33 + "provide": provideRefRoutingCmd,
34 + },
35 +}
36 +
37 +const (
38 + dhtVerboseOptionName = "verbose"
39 +)
40 +
41 +const (
42 + numProvidersOptionName = "num-providers"
43 +)
44 +
45 +var findProvidersRoutingCmd = &cmds.Command{
46 + Helptext: cmds.HelpText{
47 + Tagline: "Find peers that can provide a specific value, given a key.",
48 + ShortDescription: "Outputs a list of newline-delimited provider Peer IDs.",
49 + },
50 +
51 + Arguments: []cmds.Argument{
52 + cmds.StringArg("key", true, true, "The key to find providers for."),
53 + },
54 + Options: []cmds.Option{
55 + cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
56 + cmds.IntOption(numProvidersOptionName, "n", "The number of providers to find.").WithDefault(20),
57 + },
58 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
59 + n, err := cmdenv.GetNode(env)
60 + if err != nil {
61 + return err
62 + }
63 +
64 + if !n.IsOnline {
65 + return ErrNotOnline
66 + }
67 +
68 + numProviders, _ := req.Options[numProvidersOptionName].(int)
69 + if numProviders < 1 {
70 + return fmt.Errorf("number of providers must be greater than 0")
71 + }
72 +
73 + c, err := cid.Parse(req.Arguments[0])
74 +
75 + if err != nil {
76 + return err
77 + }
78 +
79 + ctx, cancel := context.WithCancel(req.Context)
80 + ctx, events := routing.RegisterForQueryEvents(ctx)
81 +
82 + pchan := n.Routing.FindProvidersAsync(ctx, c, numProviders)
83 +
84 + go func() {
85 + defer cancel()
86 + for p := range pchan {
87 + np := p
88 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
89 + Type: routing.Provider,
90 + Responses: []*peer.AddrInfo{&np},
91 + })
92 + }
93 + }()
94 + for e := range events {
95 + if err := res.Emit(e); err != nil {
96 + return err
97 + }
98 + }
99 +
100 + return nil
101 + },
102 + Encoders: cmds.EncoderMap{
103 + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
104 + pfm := pfuncMap{
105 + routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
106 + if verbose {
107 + fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
108 + }
109 + return nil
110 + },
111 + routing.Provider: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
112 + prov := obj.Responses[0]
113 + if verbose {
114 + fmt.Fprintf(out, "provider: ")
115 + }
116 + fmt.Fprintf(out, "%s\n", prov.ID.Pretty())
117 + if verbose {
118 + for _, a := range prov.Addrs {
119 + fmt.Fprintf(out, "\t%s\n", a)
120 + }
121 + }
122 + return nil
123 + },
124 + }
125 +
126 + verbose, _ := req.Options[dhtVerboseOptionName].(bool)
127 + return printEvent(out, w, verbose, pfm)
128 + }),
129 + },
130 + Type: routing.QueryEvent{},
131 +}
132 +
133 +const (
134 + recursiveOptionName = "recursive"
135 +)
136 +
137 +var provideRefRoutingCmd = &cmds.Command{
138 + Helptext: cmds.HelpText{
139 + Tagline: "Announce to the network that you are providing given values.",
140 + },
141 +
142 + Arguments: []cmds.Argument{
143 + cmds.StringArg("key", true, true, "The key[s] to send provide records for.").EnableStdin(),
144 + },
145 + Options: []cmds.Option{
146 + cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
147 + cmds.BoolOption(recursiveOptionName, "r", "Recursively provide entire graph."),
148 + },
149 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
150 + nd, err := cmdenv.GetNode(env)
151 + if err != nil {
152 + return err
153 + }
154 +
155 + if !nd.IsOnline {
156 + return ErrNotOnline
157 + }
158 +
159 + if len(nd.PeerHost.Network().Conns()) == 0 {
160 + return errors.New("cannot provide, no connected peers")
161 + }
162 +
163 + // Needed to parse stdin args.
164 + // TODO: Lazy Load
165 + err = req.ParseBodyArgs()
166 + if err != nil {
167 + return err
168 + }
169 +
170 + rec, _ := req.Options[recursiveOptionName].(bool)
171 +
172 + var cids []cid.Cid
173 + for _, arg := range req.Arguments {
174 + c, err := cid.Decode(arg)
175 + if err != nil {
176 + return err
177 + }
178 +
179 + has, err := nd.Blockstore.Has(req.Context, c)
180 + if err != nil {
181 + return err
182 + }
183 +
184 + if !has {
185 + return fmt.Errorf("block %s not found locally, cannot provide", c)
186 + }
187 +
188 + cids = append(cids, c)
189 + }
190 +
191 + ctx, cancel := context.WithCancel(req.Context)
192 + ctx, events := routing.RegisterForQueryEvents(ctx)
193 +
194 + var provideErr error
195 + go func() {
196 + defer cancel()
197 + if rec {
198 + provideErr = provideKeysRec(ctx, nd.Routing, nd.DAG, cids)
199 + } else {
200 + provideErr = provideKeys(ctx, nd.Routing, cids)
201 + }
202 + if provideErr != nil {
203 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
204 + Type: routing.QueryError,
205 + Extra: provideErr.Error(),
206 + })
207 + }
208 + }()
209 +
210 + for e := range events {
211 + if err := res.Emit(e); err != nil {
212 + return err
213 + }
214 + }
215 +
216 + return provideErr
217 + },
218 + Encoders: cmds.EncoderMap{
219 + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
220 + pfm := pfuncMap{
221 + routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
222 + if verbose {
223 + fmt.Fprintf(out, "sending provider record to peer %s\n", obj.ID)
224 + }
225 + return nil
226 + },
227 + }
228 +
229 + verbose, _ := req.Options[dhtVerboseOptionName].(bool)
230 + return printEvent(out, w, verbose, pfm)
231 + }),
232 + },
233 + Type: routing.QueryEvent{},
234 +}
235 +
236 +func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error {
237 + for _, c := range cids {
238 + err := r.Provide(ctx, c, true)
239 + if err != nil {
240 + return err
241 + }
242 + }
243 + return nil
244 +}
245 +
246 +func provideKeysRec(ctx context.Context, r routing.Routing, dserv ipld.DAGService, cids []cid.Cid) error {
247 + provided := cid.NewSet()
248 + for _, c := range cids {
249 + kset := cid.NewSet()
250 +
251 + err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, kset.Visit)
252 + if err != nil {
253 + return err
254 + }
255 +
256 + for _, k := range kset.Keys() {
257 + if provided.Has(k) {
258 + continue
259 + }
260 +
261 + err = r.Provide(ctx, k, true)
262 + if err != nil {
263 + return err
264 + }
265 + provided.Add(k)
266 + }
267 + }
268 +
269 + return nil
270 +}
271 +
272 +var findPeerRoutingCmd = &cmds.Command{
273 + Helptext: cmds.HelpText{
274 + Tagline: "Find the multiaddresses associated with a Peer ID.",
275 + ShortDescription: "Outputs a list of newline-delimited multiaddresses.",
276 + },
277 +
278 + Arguments: []cmds.Argument{
279 + cmds.StringArg("peerID", true, true, "The ID of the peer to search for."),
280 + },
281 + Options: []cmds.Option{
282 + cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
283 + },
284 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
285 + nd, err := cmdenv.GetNode(env)
286 + if err != nil {
287 + return err
288 + }
289 +
290 + if !nd.IsOnline {
291 + return ErrNotOnline
292 + }
293 +
294 + pid, err := peer.Decode(req.Arguments[0])
295 + if err != nil {
296 + return err
297 + }
298 +
299 + ctx, cancel := context.WithCancel(req.Context)
300 + ctx, events := routing.RegisterForQueryEvents(ctx)
301 +
302 + var findPeerErr error
303 + go func() {
304 + defer cancel()
305 + var pi peer.AddrInfo
306 + pi, findPeerErr = nd.Routing.FindPeer(ctx, pid)
307 + if findPeerErr != nil {
308 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
309 + Type: routing.QueryError,
310 + Extra: findPeerErr.Error(),
311 + })
312 + return
313 + }
314 +
315 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
316 + Type: routing.FinalPeer,
317 + Responses: []*peer.AddrInfo{&pi},
318 + })
319 + }()
320 +
321 + for e := range events {
322 + if err := res.Emit(e); err != nil {
323 + return err
324 + }
325 + }
326 +
327 + return findPeerErr
328 + },
329 + Encoders: cmds.EncoderMap{
330 + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
331 + pfm := pfuncMap{
332 + routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
333 + pi := obj.Responses[0]
334 + for _, a := range pi.Addrs {
335 + fmt.Fprintf(out, "%s\n", a)
336 + }
337 + return nil
338 + },
339 + }
340 +
341 + verbose, _ := req.Options[dhtVerboseOptionName].(bool)
342 + return printEvent(out, w, verbose, pfm)
343 + }),
344 + },
345 + Type: routing.QueryEvent{},
346 +}
347 +
348 +var getValueRoutingCmd = &cmds.Command{
349 + Helptext: cmds.HelpText{
350 + Tagline: "Given a key, query the routing system for its best value.",
351 + ShortDescription: `
352 +Outputs the best value for the given key.
353 +
354 +There may be several different values for a given key stored in the routing
355 +system; in this context 'best' means the record that is most desirable. There is
356 +no one metric for 'best': it depends entirely on the key type. For IPNS, 'best'
357 +is the record that is both valid and has the highest sequence number (freshest).
358 +Different key types can specify other 'best' rules.
359 +`,
360 + },
361 +
362 + Arguments: []cmds.Argument{
363 + cmds.StringArg("key", true, true, "The key to find a value for."),
364 + },
365 + Options: []cmds.Option{
366 + cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
367 + },
368 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
369 + nd, err := cmdenv.GetNode(env)
370 + if err != nil {
371 + return err
372 + }
373 +
374 + if !nd.IsOnline {
375 + return ErrNotOnline
376 + }
377 +
378 + dhtkey, err := escapeDhtKey(req.Arguments[0])
379 + if err != nil {
380 + return err
381 + }
382 +
383 + ctx, cancel := context.WithCancel(req.Context)
384 + ctx, events := routing.RegisterForQueryEvents(ctx)
385 +
386 + var getErr error
387 + go func() {
388 + defer cancel()
389 + var val []byte
390 + val, getErr = nd.Routing.GetValue(ctx, dhtkey)
391 + if getErr != nil {
392 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
393 + Type: routing.QueryError,
394 + Extra: getErr.Error(),
395 + })
396 + } else {
397 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
398 + Type: routing.Value,
399 + Extra: base64.StdEncoding.EncodeToString(val),
400 + })
401 + }
402 + }()
403 +
404 + for e := range events {
405 + if err := res.Emit(e); err != nil {
406 + return err
407 + }
408 + }
409 +
410 + return getErr
411 + },
412 + Encoders: cmds.EncoderMap{
413 + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
414 + pfm := pfuncMap{
415 + routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
416 + if verbose {
417 + _, err := fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
418 + return err
419 + }
420 + res, err := base64.StdEncoding.DecodeString(obj.Extra)
421 + if err != nil {
422 + return err
423 + }
424 + _, err = out.Write(res)
425 + return err
426 + },
427 + }
428 +
429 + verbose, _ := req.Options[dhtVerboseOptionName].(bool)
430 + return printEvent(out, w, verbose, pfm)
431 + }),
432 + },
433 + Type: routing.QueryEvent{},
434 +}
435 +
436 +var putValueRoutingCmd = &cmds.Command{
437 + Helptext: cmds.HelpText{
438 + Tagline: "Write a key/value pair to the routing system.",
439 + ShortDescription: `
440 +Given a key of the form /foo/bar and a valid value for that key, this will write
441 +that value to the routing system with that key.
442 +
443 +Keys have two parts: a keytype (foo) and the key name (bar). IPNS uses the
444 +/ipns keytype, and expects the key name to be a Peer ID. IPNS entries are
445 +specifically formatted (protocol buffer).
446 +
447 +You may only use keytypes that are supported in your ipfs binary: currently
448 +this is only /ipns. Unless you have a relatively deep understanding of the
449 +go-ipfs routing internals, you likely want to be using 'ipfs name publish' instead
450 +of this.
451 +
452 +The value must be a valid value for the given key type. For example, if the key
453 +is /ipns/QmFoo, the value must be IPNS record (protobuf) signed with the key
454 +identified by QmFoo.
455 +`,
456 + },
457 +
458 + Arguments: []cmds.Argument{
459 + cmds.StringArg("key", true, false, "The key to store the value at."),
460 + cmds.FileArg("value-file", true, false, "A path to a file containing the value to store.").EnableStdin(),
461 + },
462 + Options: []cmds.Option{
463 + cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
464 + },
465 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
466 + nd, err := cmdenv.GetNode(env)
467 + if err != nil {
468 + return err
469 + }
470 +
471 + if !nd.IsOnline {
472 + return ErrNotOnline
473 + }
474 +
475 + key, err := escapeDhtKey(req.Arguments[0])
476 + if err != nil {
477 + return err
478 + }
479 +
480 + file, err := cmdenv.GetFileArg(req.Files.Entries())
481 + if err != nil {
482 + return err
483 + }
484 + defer file.Close()
485 +
486 + data, err := io.ReadAll(file)
487 + if err != nil {
488 + return err
489 + }
490 +
491 + ctx, cancel := context.WithCancel(req.Context)
492 + ctx, events := routing.RegisterForQueryEvents(ctx)
493 +
494 + var putErr error
495 + go func() {
496 + defer cancel()
497 + putErr = nd.Routing.PutValue(ctx, key, []byte(data))
498 + if putErr != nil {
499 + routing.PublishQueryEvent(ctx, &routing.QueryEvent{
500 + Type: routing.QueryError,
501 + Extra: putErr.Error(),
502 + })
503 + }
504 + }()
505 +
506 + for e := range events {
507 + if err := res.Emit(e); err != nil {
508 + return err
509 + }
510 + }
511 +
512 + return putErr
513 + },
514 + Encoders: cmds.EncoderMap{
515 + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
516 + pfm := pfuncMap{
517 + routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
518 + if verbose {
519 + fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
520 + }
521 + return nil
522 + },
523 + routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
524 + fmt.Fprintf(out, "%s\n", obj.ID.Pretty())
525 + return nil
526 + },
527 + }
528 +
529 + verbose, _ := req.Options[dhtVerboseOptionName].(bool)
530 +
531 + return printEvent(out, w, verbose, pfm)
532 + }),
533 + },
534 + Type: routing.QueryEvent{},
535 +}
536 +
537 +type printFunc func(obj *routing.QueryEvent, out io.Writer, verbose bool) error
538 +type pfuncMap map[routing.QueryEventType]printFunc
539 +
540 +func printEvent(obj *routing.QueryEvent, out io.Writer, verbose bool, override pfuncMap) error {
541 + if verbose {
542 + fmt.Fprintf(out, "%s: ", time.Now().Format("15:04:05.000"))
543 + }
544 +
545 + if override != nil {
546 + if pf, ok := override[obj.Type]; ok {
547 + return pf(obj, out, verbose)
548 + }
549 + }
550 +
551 + switch obj.Type {
552 + case routing.SendingQuery:
553 + if verbose {
554 + fmt.Fprintf(out, "* querying %s\n", obj.ID)
555 + }
556 + case routing.Value:
557 + if verbose {
558 + fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
559 + } else {
560 + fmt.Fprint(out, obj.Extra)
561 + }
562 + case routing.PeerResponse:
563 + if verbose {
564 + fmt.Fprintf(out, "* %s says use ", obj.ID)
565 + for _, p := range obj.Responses {
566 + fmt.Fprintf(out, "%s ", p.ID)
567 + }
568 + fmt.Fprintln(out)
569 + }
570 + case routing.QueryError:
571 + if verbose {
572 + fmt.Fprintf(out, "error: %s\n", obj.Extra)
573 + }
574 + case routing.DialingPeer:
575 + if verbose {
576 + fmt.Fprintf(out, "dialing peer: %s\n", obj.ID)
577 + }
578 + case routing.AddingPeer:
579 + if verbose {
580 + fmt.Fprintf(out, "adding peer to query: %s\n", obj.ID)
581 + }
582 + case routing.FinalPeer:
583 + default:
584 + if verbose {
585 + fmt.Fprintf(out, "unrecognized event type: %d\n", obj.Type)
586 + }
587 + }
588 + return nil
589 +}
590 +
591 +func escapeDhtKey(s string) (string, error) {
592 + parts := path.SplitList(s)
593 + if len(parts) != 3 ||
594 + parts[0] != "" ||
595 + !(parts[1] == "ipns" || parts[1] == "pk") {
596 + return "", errors.New("invalid key")
597 + }
598 +
599 + k, err := peer.Decode(parts[2])
600 + if err != nil {
601 + return "", err
602 + }
603 + return path.Join(append(parts[:2], string(k))), nil
604 +}
core/core.go
+4 -3
@@ -14,14 +14,14 @@ import (
14 "io"
15
16 "github.com/ipfs/go-filestore"
17 - "github.com/ipfs/go-ipfs-pinner"
17 + pin "github.com/ipfs/go-ipfs-pinner"
18
19 bserv "github.com/ipfs/go-blockservice"
20 "github.com/ipfs/go-fetcher"
21 "github.com/ipfs/go-graphsync"
22 bstore "github.com/ipfs/go-ipfs-blockstore"
23 exchange "github.com/ipfs/go-ipfs-exchange-interface"
24 - "github.com/ipfs/go-ipfs-provider"
24 + provider "github.com/ipfs/go-ipfs-provider"
25 ipld "github.com/ipfs/go-ipld-format"
26 logging "github.com/ipfs/go-log"
27 mfs "github.com/ipfs/go-mfs"
@@ -52,6 +52,7 @@ import (
52 "github.com/ipfs/kubo/p2p"
53 "github.com/ipfs/kubo/peering"
54 "github.com/ipfs/kubo/repo"
55 + irouting "github.com/ipfs/kubo/routing"
56 )
57
58 var log = logging.Logger("core")
@@ -90,7 +91,7 @@ type IpfsNode struct {
91 Peering *peering.PeeringService `optional:"true"`
92 Filters *ma.Filters `optional:"true"`
93 Bootstrapper io.Closer `optional:"true"` // the periodic bootstrapper
93 - Routing routing.Routing `optional:"true"` // the routing system. recommend ipfs-dht
94 + Routing irouting.TieredRouter `optional:"true"` // the routing system. recommend ipfs-dht
95 DNSResolver *madns.Resolver // the DNS resolver
96 Exchange exchange.Interface // the block exchange + strategy (bitswap)
97 Namesys namesys.NameSystem // the name system, resolves paths to hashes
core/core_test.go
+246
@@ -1,14 +1,28 @@
1 package core
2
3 import (
4 + "crypto/rand"
5 + "errors"
6 + "fmt"
7 + "net/http/httptest"
8 + "path"
9 "testing"
10 + "time"
11
12 context "context"
13
14 + "github.com/ipfs/go-cid"
15 + "github.com/ipfs/go-delegated-routing/client"
16 + "github.com/ipfs/go-ipns"
17 + "github.com/ipfs/kubo/core/node/libp2p"
18 "github.com/ipfs/kubo/repo"
19 + "github.com/libp2p/go-libp2p-core/crypto"
20 + peer "github.com/libp2p/go-libp2p-core/peer"
21 + "github.com/stretchr/testify/require"
22
23 datastore "github.com/ipfs/go-datastore"
24 syncds "github.com/ipfs/go-datastore/sync"
25 + drs "github.com/ipfs/go-delegated-routing/server"
26 config "github.com/ipfs/kubo/config"
27 )
28
@@ -65,3 +79,235 @@ var testIdentity = config.Identity{
79 PeerID: "QmNgdzLieYi8tgfo2WfTUzNVH5hQK9oAYGVf6dxN12NrHt",
80 PrivKey: "CAASrRIwggkpAgEAAoICAQCwt67GTUQ8nlJhks6CgbLKOx7F5tl1r9zF4m3TUrG3Pe8h64vi+ILDRFd7QJxaJ/n8ux9RUDoxLjzftL4uTdtv5UXl2vaufCc/C0bhCRvDhuWPhVsD75/DZPbwLsepxocwVWTyq7/ZHsCfuWdoh/KNczfy+Gn33gVQbHCnip/uhTVxT7ARTiv8Qa3d7qmmxsR+1zdL/IRO0mic/iojcb3Oc/PRnYBTiAZFbZdUEit/99tnfSjMDg02wRayZaT5ikxa6gBTMZ16Yvienq7RwSELzMQq2jFA4i/TdiGhS9uKywltiN2LrNDBcQJSN02pK12DKoiIy+wuOCRgs2NTQEhU2sXCk091v7giTTOpFX2ij9ghmiRfoSiBFPJA5RGwiH6ansCHtWKY1K8BS5UORM0o3dYk87mTnKbCsdz4bYnGtOWafujYwzueGx8r+IWiys80IPQKDeehnLW6RgoyjszKgL/2XTyP54xMLSW+Qb3BPgDcPaPO0hmop1hW9upStxKsefW2A2d46Ds4HEpJEry7PkS5M4gKL/zCKHuxuXVk14+fZQ1rstMuvKjrekpAC2aVIKMI9VRA3awtnje8HImQMdj+r+bPmv0N8rTTr3eS4J8Yl7k12i95LLfK+fWnmUh22oTNzkRlaiERQrUDyE4XNCtJc0xs1oe1yXGqazCIAQIDAQABAoICAQCk1N/ftahlRmOfAXk//8wNl7FvdJD3le6+YSKBj0uWmN1ZbUSQk64chr12iGCOM2WY180xYjy1LOS44PTXaeW5bEiTSnb3b3SH+HPHaWCNM2EiSogHltYVQjKW+3tfH39vlOdQ9uQ+l9Gh6iTLOqsCRyszpYPqIBwi1NMLY2Ej8PpVU7ftnFWouHZ9YKS7nAEiMoowhTu/7cCIVwZlAy3AySTuKxPMVj9LORqC32PVvBHZaMPJ+X1Xyijqg6aq39WyoztkXg3+Xxx5j5eOrK6vO/Lp6ZUxaQilHDXoJkKEJjgIBDZpluss08UPfOgiWAGkW+L4fgUxY0qDLDAEMhyEBAn6KOKVL1JhGTX6GjhWziI94bddSpHKYOEIDzUy4H8BXnKhtnyQV6ELS65C2hj9D0IMBTj7edCF1poJy0QfdK0cuXgMvxHLeUO5uc2YWfbNosvKxqygB9rToy4b22YvNwsZUXsTY6Jt+p9V2OgXSKfB5VPeRbjTJL6xqvvUJpQytmII/C9JmSDUtCbYceHj6X9jgigLk20VV6nWHqCTj3utXD6NPAjoycVpLKDlnWEgfVELDIk0gobxUqqSm3jTPEKRPJgxkgPxbwxYumtw++1UY2y35w3WRDc2xYPaWKBCQeZy+mL6ByXp9bWlNvxS3Knb6oZp36/ovGnf2pGvdQKCAQEAyKpipz2lIUySDyE0avVWAmQb2tWGKXALPohzj7AwkcfEg2GuwoC6GyVE2sTJD1HRazIjOKn3yQORg2uOPeG7sx7EKHxSxCKDrbPawkvLCq8JYSy9TLvhqKUVVGYPqMBzu2POSLEA81QXas+aYjKOFWA2Zrjq26zV9ey3+6Lc6WULePgRQybU8+RHJc6fdjUCCfUxgOrUO2IQOuTJ+FsDpVnrMUGlokmWn23OjL4qTL9wGDnWGUs2pjSzNbj3qA0d8iqaiMUyHX/D/VS0wpeT1osNBSm8suvSibYBn+7wbIApbwXUxZaxMv2OHGz3empae4ckvNZs7r8wsI9UwFt8mwKCAQEA4XK6gZkv9t+3YCcSPw2ensLvL/xU7i2bkC9tfTGdjnQfzZXIf5KNdVuj/SerOl2S1s45NMs3ysJbADwRb4ahElD/V71nGzV8fpFTitC20ro9fuX4J0+twmBolHqeH9pmeGTjAeL1rvt6vxs4FkeG/yNft7GdXpXTtEGaObn8Mt0tPY+aB3UnKrnCQoQAlPyGHFrVRX0UEcp6wyyNGhJCNKeNOvqCHTFObhbhO+KWpWSN0MkVHnqaIBnIn1Te8FtvP/iTwXGnKc0YXJUG6+LM6LmOguW6tg8ZqiQeYyyR+e9eCFH4csLzkrTl1GxCxwEsoSLIMm7UDcjttW6tYEghkwKCAQEAmeCO5lCPYImnN5Lu71ZTLmI2OgmjaANTnBBnDbi+hgv61gUCToUIMejSdDCTPfwv61P3TmyIZs0luPGxkiKYHTNqmOE9Vspgz8Mr7fLRMNApESuNvloVIY32XVImj/GEzh4rAfM6F15U1sN8T/EUo6+0B/Glp+9R49QzAfRSE2g48/rGwgf1JVHYfVWFUtAzUA+GdqWdOixo5cCsYJbqpNHfWVZN/bUQnBFIYwUwysnC29D+LUdQEQQ4qOm+gFAOtrWU62zMkXJ4iLt8Ify6kbrvsRXgbhQIzzGS7WH9XDarj0eZciuslr15TLMC1Azadf+cXHLR9gMHA13mT9vYIQKCAQA/DjGv8cKCkAvf7s2hqROGYAs6Jp8yhrsN1tYOwAPLRhtnCs+rLrg17M2vDptLlcRuI/vIElamdTmylRpjUQpX7yObzLO73nfVhpwRJVMdGU394iBIDncQ+JoHfUwgqJskbUM40dvZdyjbrqc/Q/4z+hbZb+oN/GXb8sVKBATPzSDMKQ/xqgisYIw+wmDPStnPsHAaIWOtni47zIgilJzD0WEk78/YjmPbUrboYvWziK5JiRRJFA1rkQqV1c0M+OXixIm+/yS8AksgCeaHr0WUieGcJtjT9uE8vyFop5ykhRiNxy9wGaq6i7IEecsrkd6DqxDHWkwhFuO1bSE83q/VAoIBAEA+RX1i/SUi08p71ggUi9WFMqXmzELp1L3hiEjOc2AklHk2rPxsaTh9+G95BvjhP7fRa/Yga+yDtYuyjO99nedStdNNSg03aPXILl9gs3r2dPiQKUEXZJ3FrH6tkils/8BlpOIRfbkszrdZIKTO9GCdLWQ30dQITDACs8zV/1GFGrHFrqnnMe/NpIFHWNZJ0/WZMi8wgWO6Ik8jHEpQtVXRiXLqy7U6hk170pa4GHOzvftfPElOZZjy9qn7KjdAQqy6spIrAE94OEL+fBgbHQZGLpuTlj6w6YGbMtPU8uo7sXKoc6WOCb68JWft3tejGLDa1946HAWqVM9B/UcneNc=",
81 }
82 +
83 +var errNotSupported = errors.New("method not supported")
84 +
85 +func TestDelegatedRoutingSingle(t *testing.T) {
86 + require := require.New(t)
87 +
88 + pId1, priv1, err := GeneratePeerID()
89 + require.NoError(err)
90 +
91 + pId2, _, err := GeneratePeerID()
92 + require.NoError(err)
93 +
94 + theID := path.Join("/ipns", string(pId1))
95 + theErrorID := path.Join("/ipns", string(pId2))
96 +
97 + d := &delegatedRoutingService{
98 + goodPeerID: pId1,
99 + badPeerID: pId2,
100 + pk1: priv1,
101 + }
102 +
103 + url := StartRoutingServer(t, d)
104 + n := GetNode(t, url)
105 +
106 + ctx := context.Background()
107 +
108 + v, err := n.Routing.GetValue(ctx, theID)
109 + require.NoError(err)
110 + require.NotNil(v)
111 + require.Contains(string(v), "RECORD FROM SERVICE 0")
112 +
113 + v, err = n.Routing.GetValue(ctx, theErrorID)
114 + require.Nil(v)
115 + require.Error(err)
116 +
117 + err = n.Routing.PutValue(ctx, theID, v)
118 + require.NoError(err)
119 +
120 + err = n.Routing.PutValue(ctx, theErrorID, v)
121 + require.Error(err)
122 +}
123 +
124 +func TestDelegatedRoutingMulti(t *testing.T) {
125 + require := require.New(t)
126 +
127 + pId1, priv1, err := GeneratePeerID()
128 + require.NoError(err)
129 +
130 + pId2, priv2, err := GeneratePeerID()
131 + require.NoError(err)
132 +
133 + theID1 := path.Join("/ipns", string(pId1))
134 + theID2 := path.Join("/ipns", string(pId2))
135 +
136 + d1 := &delegatedRoutingService{
137 + goodPeerID: pId1,
138 + badPeerID: pId2,
139 + pk1: priv1,
140 + serviceID: 1,
141 + }
142 +
143 + url1 := StartRoutingServer(t, d1)
144 +
145 + d2 := &delegatedRoutingService{
146 + goodPeerID: pId2,
147 + badPeerID: pId1,
148 + pk1: priv2,
149 + serviceID: 2,
150 + }
151 +
152 + url2 := StartRoutingServer(t, d2)
153 +
154 + n := GetNode(t, url1, url2)
155 +
156 + ctx := context.Background()
157 +
158 + v, err := n.Routing.GetValue(ctx, theID1)
159 + require.NoError(err)
160 + require.NotNil(v)
161 + require.Contains(string(v), "RECORD FROM SERVICE 1")
162 +
163 + v, err = n.Routing.GetValue(ctx, theID2)
164 + require.NoError(err)
165 + require.NotNil(v)
166 + require.Contains(string(v), "RECORD FROM SERVICE 2")
167 +
168 + err = n.Routing.PutValue(ctx, theID1, v)
169 + require.Error(err)
170 +
171 + err = n.Routing.PutValue(ctx, theID2, v)
172 + require.Error(err)
173 +}
174 +
175 +func StartRoutingServer(t *testing.T, d drs.DelegatedRoutingService) string {
176 + t.Helper()
177 +
178 + f := drs.DelegatedRoutingAsyncHandler(d)
179 + svr := httptest.NewServer(f)
180 + t.Cleanup(func() {
181 + svr.Close()
182 + })
183 +
184 + return svr.URL
185 +}
186 +
187 +func GetNode(t *testing.T, reframeURLs ...string) *IpfsNode {
188 + t.Helper()
189 +
190 + routers := make(map[string]config.Router)
191 + for i, ru := range reframeURLs {
192 + routers[fmt.Sprintf("reframe-%d", i)] = config.Router{
193 + Type: string(config.RouterTypeReframe),
194 + Parameters: map[string]string{
195 + string(config.RouterParamEndpoint): ru,
196 + },
197 + }
198 + }
199 +
200 + cfg := config.Config{
201 + Identity: testIdentity,
202 + Addresses: config.Addresses{
203 + Swarm: []string{"/ip4/0.0.0.0/tcp/0", "/ip4/0.0.0.0/udp/0/quic"},
204 + API: []string{"/ip4/127.0.0.1/tcp/0"},
205 + },
206 + Routing: config.Routing{
207 + Type: config.NewOptionalString("none"),
208 + Routers: routers,
209 + },
210 + }
211 +
212 + r := &repo.Mock{
213 + C: cfg,
214 + D: syncds.MutexWrap(datastore.NewMapDatastore()),
215 + }
216 +
217 + n, err := NewNode(context.Background(), &BuildCfg{Repo: r, Online: true, Routing: libp2p.NilRouterOption})
218 + require.NoError(t, err)
219 +
220 + return n
221 +}
222 +
223 +func GeneratePeerID() (peer.ID, crypto.PrivKey, error) {
224 + priv, pk, err := crypto.GenerateEd25519Key(rand.Reader)
225 + if err != nil {
226 + return peer.ID(""), nil, err
227 + }
228 +
229 + pid, err := peer.IDFromPublicKey(pk)
230 + return pid, priv, err
231 +}
232 +
233 +type delegatedRoutingService struct {
234 + goodPeerID, badPeerID peer.ID
235 + pk1 crypto.PrivKey
236 + serviceID int
237 +}
238 +
239 +func (drs *delegatedRoutingService) FindProviders(ctx context.Context, key cid.Cid) (<-chan client.FindProvidersAsyncResult, error) {
240 + return nil, errNotSupported
241 +}
242 +
243 +func (drs *delegatedRoutingService) GetIPNS(ctx context.Context, id []byte) (<-chan client.GetIPNSAsyncResult, error) {
244 + ctx, cancel := context.WithCancel(ctx)
245 + ch := make(chan client.GetIPNSAsyncResult)
246 + go func() {
247 + defer close(ch)
248 + defer cancel()
249 +
250 + var out client.GetIPNSAsyncResult
251 + switch peer.ID(id) {
252 + case drs.goodPeerID:
253 + ie, err := ipns.Create(drs.pk1, []byte(fmt.Sprintf("RECORD FROM SERVICE %d", drs.serviceID)), 0, time.Now().Add(10*time.Hour), 100*time.Hour)
254 + if err != nil {
255 + log.Fatal(err)
256 + }
257 + ieb, err := ie.Marshal()
258 + if err != nil {
259 + log.Fatal(err)
260 + }
261 +
262 + out = client.GetIPNSAsyncResult{
263 + Record: ieb,
264 + Err: nil,
265 + }
266 + case drs.badPeerID:
267 + out = client.GetIPNSAsyncResult{
268 + Record: nil,
269 + Err: errors.New("THE ERROR"),
270 + }
271 + default:
272 + return
273 + }
274 +
275 + select {
276 + case <-ctx.Done():
277 + return
278 + case ch <- out:
279 + }
280 + }()
281 +
282 + return ch, nil
283 +
284 +}
285 +
286 +func (drs *delegatedRoutingService) PutIPNS(ctx context.Context, id []byte, record []byte) (<-chan client.PutIPNSAsyncResult, error) {
287 + ctx, cancel := context.WithCancel(ctx)
288 + ch := make(chan client.PutIPNSAsyncResult)
289 + go func() {
290 + defer close(ch)
291 + defer cancel()
292 +
293 + var out client.PutIPNSAsyncResult
294 + switch peer.ID(id) {
295 + case drs.goodPeerID:
296 + out = client.PutIPNSAsyncResult{}
297 + case drs.badPeerID:
298 + out = client.PutIPNSAsyncResult{
299 + Err: fmt.Errorf("THE ERROR %d", drs.serviceID),
300 + }
301 + default:
302 + return
303 + }
304 +
305 + select {
306 + case <-ctx.Done():
307 + return
308 + case ch <- out:
309 + }
310 + }()
311 +
312 + return ch, nil
313 +}
core/node/bitswap.go
+2 -2
@@ -8,8 +8,8 @@ import (
8 blockstore "github.com/ipfs/go-ipfs-blockstore"
9 exchange "github.com/ipfs/go-ipfs-exchange-interface"
10 config "github.com/ipfs/kubo/config"
11 + irouting "github.com/ipfs/kubo/routing"
12 "github.com/libp2p/go-libp2p-core/host"
12 - "github.com/libp2p/go-libp2p-core/routing"
13 "go.uber.org/fx"
14
15 "github.com/ipfs/kubo/core/node/helpers"
@@ -25,7 +25,7 @@ const (
25
26 // OnlineExchange creates new LibP2P backed block exchange (BitSwap)
27 func OnlineExchange(cfg *config.Config, provide bool) interface{} {
28 - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, host host.Host, rt routing.Routing, bs blockstore.GCBlockstore) exchange.Interface {
28 + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, host host.Host, rt irouting.TieredRouter, bs blockstore.GCBlockstore) exchange.Interface {
29 bitswapNetwork := network.NewFromIpfsHost(host, rt)
30
31 var internalBsCfg config.InternalBitswap
core/node/groups.go
+6 -2
@@ -17,7 +17,6 @@ import (
17 "github.com/ipfs/kubo/p2p"
18
19 offline "github.com/ipfs/go-ipfs-exchange-offline"
20 - offroute "github.com/ipfs/go-ipfs-routing/offline"
20 uio "github.com/ipfs/go-unixfs/io"
21
22 "github.com/dustin/go-humanize"
@@ -166,7 +165,10 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config) fx.Option {
165 fx.Provide(libp2p.Security(!bcfg.DisableEncryptedConnections, cfg.Swarm.Transports)),
166
167 fx.Provide(libp2p.Routing),
168 + fx.Provide(libp2p.ContentRouting),
169 +
170 fx.Provide(libp2p.BaseRouting(cfg.Experimental.AcceleratedDHTClient)),
171 + fx.Provide(libp2p.DelegatedRouting(cfg.Routing.Routers)),
172 maybeProvide(libp2p.PubsubRouter, bcfg.getOpt("ipnsps")),
173
174 maybeProvide(libp2p.BandwidthCounter, !cfg.Swarm.DisableBandwidthMetrics),
@@ -313,7 +315,9 @@ func Offline(cfg *config.Config) fx.Option {
315 fx.Provide(offline.Exchange),
316 fx.Provide(DNSResolver),
317 fx.Provide(Namesys(0)),
316 - fx.Provide(offroute.NewOfflineRouter),
318 + fx.Provide(libp2p.Routing),
319 + fx.Provide(libp2p.ContentRouting),
320 + fx.Provide(libp2p.OfflineRouting),
321 OfflineProviders(cfg.Experimental.StrategicProviding, cfg.Experimental.AcceleratedDHTClient, cfg.Reprovider.Strategy, cfg.Reprovider.Interval),
322 )
323 }
core/node/ipns.go
+6 -5
@@ -4,14 +4,15 @@ import (
4 "fmt"
5 "time"
6
7 - "github.com/ipfs/go-ipfs-util"
7 + util "github.com/ipfs/go-ipfs-util"
8 "github.com/ipfs/go-ipns"
9 "github.com/libp2p/go-libp2p-core/crypto"
10 "github.com/libp2p/go-libp2p-core/peerstore"
11 - "github.com/libp2p/go-libp2p-core/routing"
12 - "github.com/libp2p/go-libp2p-record"
11 + record "github.com/libp2p/go-libp2p-record"
12 madns "github.com/multiformats/go-multiaddr-dns"
13
14 + irouting "github.com/ipfs/kubo/routing"
15 +
16 "github.com/ipfs/go-namesys"
17 "github.com/ipfs/go-namesys/republisher"
18 "github.com/ipfs/kubo/repo"
@@ -28,8 +29,8 @@ func RecordValidator(ps peerstore.Peerstore) record.Validator {
29 }
30
31 // Namesys creates new name system
31 -func Namesys(cacheSize int) func(rt routing.Routing, rslv *madns.Resolver, repo repo.Repo) (namesys.NameSystem, error) {
32 - return func(rt routing.Routing, rslv *madns.Resolver, repo repo.Repo) (namesys.NameSystem, error) {
32 +func Namesys(cacheSize int) func(rt irouting.TieredRouter, rslv *madns.Resolver, repo repo.Repo) (namesys.NameSystem, error) {
33 + return func(rt irouting.TieredRouter, rslv *madns.Resolver, repo repo.Repo) (namesys.NameSystem, error) {
34 opts := []namesys.Option{
35 namesys.WithDatastore(repo.Datastore()),
36 namesys.WithDNSResolver(rslv),
core/node/libp2p/routing.go
+91 -19
@@ -8,7 +8,10 @@ import (
8 "time"
9
10 "github.com/ipfs/kubo/core/node/helpers"
11 + irouting "github.com/ipfs/kubo/routing"
12
13 + ds "github.com/ipfs/go-datastore"
14 + offroute "github.com/ipfs/go-ipfs-routing/offline"
15 config "github.com/ipfs/kubo/config"
16 "github.com/ipfs/kubo/repo"
17 "github.com/libp2p/go-libp2p-core/host"
@@ -26,8 +29,6 @@ import (
29 "go.uber.org/fx"
30 )
31
29 -type BaseIpfsRouting routing.Routing
30 -
32 type Router struct {
33 routing.Routing
34
@@ -54,16 +55,17 @@ type processInitialRoutingIn struct {
55 type processInitialRoutingOut struct {
56 fx.Out
57
57 - Router Router `group:"routers"`
58 + Router Router `group:"routers"`
59 + ContentRouter routing.ContentRouting `group:"content-routers"`
60 +
61 DHT *ddht.DHT
62 DHTClient routing.Routing `name:"dhtc"`
60 - BaseRT BaseIpfsRouting
63 }
64
65 type AddrInfoChan chan peer.AddrInfo
66
67 func BaseRouting(experimentalDHTClient bool) interface{} {
66 - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, in processInitialRoutingIn) (out processInitialRoutingOut, err error) {
68 + return func(lc fx.Lifecycle, in processInitialRoutingIn) (out processInitialRoutingOut, err error) {
69 var dr *ddht.DHT
70 if dht, ok := in.Router.(*ddht.DHT); ok {
71 dr = dht
@@ -109,9 +111,9 @@ func BaseRouting(experimentalDHTClient bool) interface{} {
111 Routing: expClient,
112 Priority: 1000,
113 },
112 - DHT: dr,
113 - DHTClient: expClient,
114 - BaseRT: expClient,
114 + DHT: dr,
115 + DHTClient: expClient,
116 + ContentRouter: expClient,
117 }, nil
118 }
119
@@ -120,13 +122,69 @@ func BaseRouting(experimentalDHTClient bool) interface{} {
122 Priority: 1000,
123 Routing: in.Router,
124 },
123 - DHT: dr,
124 - DHTClient: dr,
125 - BaseRT: in.Router,
125 + DHT: dr,
126 + DHTClient: dr,
127 + ContentRouter: in.Router,
128 }, nil
129 }
130 }
131
132 +type delegatedRouterOut struct {
133 + fx.Out
134 +
135 + Routers []Router `group:"routers,flatten"`
136 + ContentRouter []routing.ContentRouting `group:"content-routers,flatten"`
137 +}
138 +
139 +func DelegatedRouting(routers map[string]config.Router) interface{} {
140 + return func() (delegatedRouterOut, error) {
141 + out := delegatedRouterOut{}
142 +
143 + for _, v := range routers {
144 + if !v.Enabled.WithDefault(true) {
145 + continue
146 + }
147 +
148 + r, err := irouting.RoutingFromConfig(v)
149 + if err != nil {
150 + return out, err
151 + }
152 +
153 + out.Routers = append(out.Routers, Router{
154 + Routing: r,
155 + Priority: irouting.GetPriority(v.Parameters),
156 + })
157 +
158 + out.ContentRouter = append(out.ContentRouter, r)
159 + }
160 +
161 + return out, nil
162 + }
163 +}
164 +
165 +type p2pOnlineContentRoutingIn struct {
166 + fx.In
167 +
168 + ContentRouter []routing.ContentRouting `group:"content-routers"`
169 +}
170 +
171 +// ContentRouting will get all routers that can do contentRouting and add them
172 +// all together using a TieredRouter. It will be used for topic discovery.
173 +func ContentRouting(in p2pOnlineContentRoutingIn) routing.ContentRouting {
174 + var routers []routing.Routing
175 + for _, cr := range in.ContentRouter {
176 + routers = append(routers,
177 + &routinghelpers.Compose{
178 + ContentRouting: cr,
179 + },
180 + )
181 + }
182 +
183 + return routinghelpers.Tiered{
184 + Routers: routers,
185 + }
186 +}
187 +
188 type p2pOnlineRoutingIn struct {
189 fx.In
190
@@ -134,7 +192,10 @@ type p2pOnlineRoutingIn struct {
192 Validator record.Validator
193 }
194
137 -func Routing(in p2pOnlineRoutingIn) routing.Routing {
195 +// Routing will get all routers obtained from different methods
196 +// (delegated routers, pub-sub, and so on) and add them all together
197 +// using a TieredRouter.
198 +func Routing(in p2pOnlineRoutingIn) irouting.TieredRouter {
199 routers := in.Routers
200
201 sort.SliceStable(routers, func(i, j int) bool {
@@ -146,19 +207,30 @@ func Routing(in p2pOnlineRoutingIn) routing.Routing {
207 irouters[i] = v.Routing
208 }
209
149 - return routinghelpers.Tiered{
150 - Routers: irouters,
151 - Validator: in.Validator,
210 + return irouting.Tiered{
211 + Tiered: routinghelpers.Tiered{
212 + Routers: irouters,
213 + Validator: in.Validator,
214 + },
215 + }
216 +}
217 +
218 +// OfflineRouting provides a special Router to the routers list when we are creating a offline node.
219 +func OfflineRouting(dstore ds.Datastore, validator record.Validator) p2pRouterOut {
220 + return p2pRouterOut{
221 + Router: Router{
222 + Routing: offroute.NewOfflineRouter(dstore, validator),
223 + Priority: 10000,
224 + },
225 }
226 }
227
228 type p2pPSRoutingIn struct {
229 fx.In
230
158 - BaseIpfsRouting BaseIpfsRouting
159 - Validator record.Validator
160 - Host host.Host
161 - PubSub *pubsub.PubSub `optional:"true"`
231 + Validator record.Validator
232 + Host host.Host
233 + PubSub *pubsub.PubSub `optional:"true"`
234 }
235
236 func PubsubRouter(mctx helpers.MetricsCtx, lc fx.Lifecycle, in p2pPSRoutingIn) (p2pRouterOut, *namesys.PubsubValueStore, error) {
core/node/libp2p/topicdiscovery.go
+2 -3
@@ -9,12 +9,11 @@ import (
9 "github.com/libp2p/go-libp2p/p2p/discovery/backoff"
10 disc "github.com/libp2p/go-libp2p/p2p/discovery/routing"
11
12 - "github.com/ipfs/kubo/core/node/helpers"
13 - "go.uber.org/fx"
12 + "github.com/libp2p/go-libp2p-core/routing"
13 )
14
15 func TopicDiscovery() interface{} {
17 - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, host host.Host, cr BaseIpfsRouting) (service discovery.Discovery, err error) {
16 + return func(host host.Host, cr routing.ContentRouting) (service discovery.Discovery, err error) {
17 baseDisc := disc.NewRoutingDiscovery(cr)
18 minBackoff, maxBackoff := time.Second*60, time.Hour
19 rng := rand.New(rand.NewSource(rand.Int63()))
core/node/provider.go
+8 -15
@@ -6,18 +6,16 @@ import (
6 "time"
7
8 "github.com/ipfs/go-fetcher"
9 - "github.com/ipfs/go-ipfs-pinner"
10 - "github.com/ipfs/go-ipfs-provider"
9 + pin "github.com/ipfs/go-ipfs-pinner"
10 + provider "github.com/ipfs/go-ipfs-provider"
11 "github.com/ipfs/go-ipfs-provider/batched"
12 q "github.com/ipfs/go-ipfs-provider/queue"
13 "github.com/ipfs/go-ipfs-provider/simple"
14 - "github.com/libp2p/go-libp2p-core/routing"
15 - "github.com/multiformats/go-multihash"
14 "go.uber.org/fx"
15
16 "github.com/ipfs/kubo/core/node/helpers"
19 - "github.com/ipfs/kubo/core/node/libp2p"
17 "github.com/ipfs/kubo/repo"
18 + irouting "github.com/ipfs/kubo/routing"
19 )
20
21 const kReprovideFrequency = time.Hour * 12
@@ -30,13 +28,13 @@ func ProviderQueue(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo) (*q
28 }
29
30 // SimpleProvider creates new record provider
33 -func SimpleProvider(mctx helpers.MetricsCtx, lc fx.Lifecycle, queue *q.Queue, rt routing.Routing) provider.Provider {
31 +func SimpleProvider(mctx helpers.MetricsCtx, lc fx.Lifecycle, queue *q.Queue, rt irouting.TieredRouter) provider.Provider {
32 return simple.NewProvider(helpers.LifecycleCtx(mctx, lc), queue, rt)
33 }
34
35 // SimpleReprovider creates new reprovider
36 func SimpleReprovider(reproviderInterval time.Duration) interface{} {
39 - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, rt routing.Routing, keyProvider simple.KeyChanFunc) (provider.Reprovider, error) {
37 + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, rt irouting.TieredRouter, keyProvider simple.KeyChanFunc) (provider.Reprovider, error) {
38 return simple.NewReprovider(helpers.LifecycleCtx(mctx, lc), reproviderInterval, rt, keyProvider), nil
39 }
40 }
@@ -62,16 +60,11 @@ func SimpleProviderSys(isOnline bool) interface{} {
60 }
61 }
62
65 -type provideMany interface {
66 - ProvideMany(ctx context.Context, keys []multihash.Multihash) error
67 - Ready() bool
68 -}
69 -
63 // BatchedProviderSys creates new provider system
64 func BatchedProviderSys(isOnline bool, reprovideInterval string) interface{} {
72 - return func(lc fx.Lifecycle, cr libp2p.BaseIpfsRouting, q *q.Queue, keyProvider simple.KeyChanFunc, repo repo.Repo) (provider.System, error) {
73 - r, ok := (cr).(provideMany)
74 - if !ok {
65 + return func(lc fx.Lifecycle, cr irouting.TieredRouter, q *q.Queue, keyProvider simple.KeyChanFunc, repo repo.Repo) (provider.System, error) {
66 + r := cr.ProvideMany()
67 + if r == nil {
68 return nil, fmt.Errorf("BatchedProviderSys requires a content router that supports provideMany")
69 }
70
docs/changelogs/v0.14.md new
+31
@@ -0,0 +1,31 @@
1 +# Kubo changelog
2 +
3 +## v0.14.0 TBD
4 +
5 +### Overview
6 +
7 +Below is an outline of all that is in this release, so you get a sense of all that's included.
8 +
9 +- [🔦 Highlights](#---highlights)
10 + * [🛣️ Delegated Routing](#---Delegated-Routing)
11 +
12 +### 🔦 Highlights
13 +
14 +#### 🛣️ Delegated Routing
15 +
16 +Content routing is the a term used to describe the problem of finding providers for a given piece of content.
17 +If you have a hash, or CID of some data, how do you find who has it?
18 +In IPFS, until now, only a DHT was used as a decentralized answer to content routing.
19 +Now, content routing can be handled by clients implementing the [Reframe protocol](https://github.com/ipfs/specs/tree/main/reframe#readme).
20 +
21 +Example configuration usage using the [Filecoin Network Indexer](https://docs.cid.contact/filecoin-network-indexer/overview):
22 +
23 +```
24 +ipfs config Routing.Routers.CidContact --json '{
25 + "Type": "reframe",
26 + "Parameters": {
27 + "Endpoint": "https://cid.contact/reframe"
28 + }
29 +}'
30 +
31 +```
docs/config.md
+72 -4
@@ -102,6 +102,10 @@ config file at runtime.
102 - [`Reprovider.Interval`](#reproviderinterval)
103 - [`Reprovider.Strategy`](#reproviderstrategy)
104 - [`Routing`](#routing)
105 + - [`Routing.Routers`](#routingrouters)
106 + - [`Routing.Routers: Type`](#routingrouters-type)
107 + - [`Routing.Routers: Enabled`](#routingrouters-enabled)
108 + - [`Routing.Routers: Parameters`](#routingrouters-parameters)
109 - [`Routing.Type`](#routingtype)
110 - [`Swarm`](#swarm)
111 - [`Swarm.AddrFilters`](#swarmaddrfilters)
@@ -1289,9 +1293,73 @@ Type: `string` (or unset for the default, which is "all")
1293
1294 Contains options for content, peer, and IPNS routing mechanisms.
1295
1292 -### `Routing.Type`
1296 +### `Routing.Routers`
1297 +
1298 +**EXPERIMENTAL: `Routing.Routers` configuration may change in future release**
1299 +
1300 +Map of additional Routers.
1301 +
1302 +Allows for extending the default routing (DHT) with alternative Router
1303 +implementations, such as custom DHTs and delegated routing based
1304 +on the [reframe protocol](https://github.com/ipfs/specs/tree/main/reframe#readme).
1305 +
1306 +The map key is a name of a Router, and the value is its configuration.
1307 +
1308 +Default: `{}`
1309 +
1310 +Type: `object[string->object]`
1311 +
1312 +#### `Routing.Routers: Type`
1313 +
1314 +**EXPERIMENTAL: `Routing.Routers` configuration may change in future release**
1315 +
1316 +It specifies the routing type that will be created.
1317 +
1318 +Currently supported types:
1319 +
1320 +- `reframe` (delegated routing based on the [reframe protocol](https://github.com/ipfs/specs/tree/main/reframe#readme))
1321 +- <del>`dht`</del> (WIP, custom DHT will be added in a future release)
1322 +
1323 +Type: `string`
1324 +
1325 +#### `Routing.Routers: Enabled`
1326 +
1327 +**EXPERIMENTAL: `Routing.Routers` configuration may change in future release**
1328 +
1329 +Optional flag to disable the specified router without removing it from the configuration file.
1330
1294 -Content routing mode. Can be overridden with daemon `--routing` flag.
1331 +Default: `true`
1332 +
1333 +Type: `flag` (`null`/missing will apply the default)
1334 +
1335 +#### `Routing.Routers: Parameters`
1336 +
1337 +**EXPERIMENTAL: `Routing.Routers` configuration may change in future release**
1338 +
1339 +Parameters needed to create the specified router. Supported params per router type:
1340 +
1341 +Reframe:
1342 + - `Endpoint` (mandatory): URL that will be used to connect to a specified router.
1343 + - `Priority` (optional): Priority is used when making a routing request. Small numbers represent more important routers. The default priority is 100000.
1344 +
1345 +**Example:**
1346 +
1347 +To add router provided by _Store the Index_ team at [cid.contact](https://cid.contact):
1348 +
1349 +```console
1350 +$ ipfs config Routing.Routers.CidContact --json '{
1351 + "Type": "reframe",
1352 + "Parameters": {
1353 + "Endpoint": "https://cid.contact/reframe"
1354 + }
1355 +}'
1356 +```
1357 +
1358 +Default: `{}` (use the safe implicit defaults)
1359 +
1360 +Type: `object[string->string]`
1361 +
1362 +### `Routing.Type`
1363
1364 There are two core routing options: "none" and "dht" (default).
1365
@@ -1326,9 +1394,9 @@ unless you're sure your node is reachable from the public network.
1394 }
1395 ```
1396
1329 -Default: dht
1397 +Default: `dht`
1398
1331 -Type: `string` (or unset for the default)
1399 +Type: `optionalString` (`null`/missing means the default)
1400
1401 ## `Swarm`
1402
go.mod
+3 -1
@@ -63,7 +63,7 @@ require (
63 github.com/ipld/go-car v0.4.0
64 github.com/ipld/go-car/v2 v2.4.0
65 github.com/ipld/go-codec-dagpb v1.4.0
66 - github.com/ipld/go-ipld-prime v0.16.0
66 + github.com/ipld/go-ipld-prime v0.17.0
67 github.com/jbenet/go-random v0.0.0-20190219211222-123a90aedc0c
68 github.com/jbenet/go-temp-err-catcher v0.1.0
69 github.com/jbenet/goprocess v0.1.4
@@ -118,6 +118,7 @@ require (
118
119 require (
120 github.com/benbjohnson/clock v1.3.0
121 + github.com/ipfs/go-delegated-routing v0.3.0
122 github.com/ipfs/go-log/v2 v2.5.1
123 )
124
@@ -171,6 +172,7 @@ require (
172 github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect
173 github.com/ipfs/go-ipfs-pq v0.0.2 // indirect
174 github.com/ipfs/go-peertaskqueue v0.7.1 // indirect
175 + github.com/ipld/edelweiss v0.1.4 // indirect
176 github.com/jackpal/go-nat-pmp v1.0.2 // indirect
177 github.com/klauspost/compress v1.15.1 // indirect
178 github.com/klauspost/cpuid/v2 v2.0.12 // indirect
go.sum
+12 -4
@@ -252,8 +252,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
252 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
253 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
254 github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
255 -github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
255 github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
256 +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
257 +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
258 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
259 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
260 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
@@ -355,8 +356,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
356 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
357 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
358 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
358 -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
359 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
360 +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
361 +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
362 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
363 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
364 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -500,6 +502,8 @@ github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w
502 github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk=
503 github.com/ipfs/go-datastore v0.5.1 h1:WkRhLuISI+XPD0uk3OskB0fYFSyqK8Ob5ZYew9Qa1nQ=
504 github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk=
505 +github.com/ipfs/go-delegated-routing v0.3.0 h1:pF5apOJ/xdQkj22mRahW9GmSuCkgMLparKZWKJBO4CE=
506 +github.com/ipfs/go-delegated-routing v0.3.0/go.mod h1:2w79E1/G9YOaxyJJQgqIFSQaa/GdS2zSATEpK8aJUBM=
507 github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
508 github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
509 github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8=
@@ -655,6 +659,8 @@ github.com/ipfs/interface-go-ipfs-core v0.7.0 h1:7tb+2upz8oCcjIyjo1atdMk+P+u7wPm
659 github.com/ipfs/interface-go-ipfs-core v0.7.0/go.mod h1:lF27E/nnSPbylPqKVXGZghal2hzifs3MmjyiEjnc9FY=
660 github.com/ipfs/tar-utils v0.0.2 h1:UNgHB4x/PPzbMkmJi+7EqC9LNMPDztOVSnx1HAqSNg4=
661 github.com/ipfs/tar-utils v0.0.2/go.mod h1:4qlnRWgTVljIMhSG2SqRYn66NT+3wrv/kZt9V+eqxDM=
662 +github.com/ipld/edelweiss v0.1.4 h1:g4+C2Ph+8SV2MCJBG3oRtetvxJYAS2WzlNGgsOY95iM=
663 +github.com/ipld/edelweiss v0.1.4/go.mod h1:JX1MR06BPcTOF+5xCYDLnylYkXS15iUN0/RXVSiUIQs=
664 github.com/ipld/go-car v0.4.0 h1:U6W7F1aKF/OJMHovnOVdst2cpQE5GhmHibQkAixgNcQ=
665 github.com/ipld/go-car v0.4.0/go.mod h1:Uslcn4O9cBKK9wqHm/cLTFacg6RAPv6LZx2mxd2Ypl4=
666 github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI=
@@ -669,8 +675,9 @@ github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvB
675 github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8=
676 github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM=
677 github.com/ipld/go-ipld-prime v0.14.1/go.mod h1:QcE4Y9n/ZZr8Ijg5bGPT0GqYWgZ1704nH0RDcQtgTP0=
672 -github.com/ipld/go-ipld-prime v0.16.0 h1:RS5hhjB/mcpeEPJvfyj0qbOj/QL+/j05heZ0qa97dVo=
678 github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA=
679 +github.com/ipld/go-ipld-prime v0.17.0 h1:+U2peiA3aQsE7mrXjD2nYZaZrCcakoz2Wge8K42Ld8g=
680 +github.com/ipld/go-ipld-prime v0.17.0/go.mod h1:aYcKm5TIvGfY8P3QBKz/2gKcLxzJ1zDaD+o0bOowhgs=
681 github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ=
682 github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY=
683 github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
@@ -1523,8 +1530,9 @@ github.com/wI2L/jsondiff v0.2.0 h1:dE00WemBa1uCjrzQUUTE/17I6m5qAaN0EMFOg2Ynr/k=
1530 github.com/wI2L/jsondiff v0.2.0/go.mod h1:axTcwtBkY4TsKuV+RgoMhHyHKKFRI6nnjRLi8LLYQnA=
1531 github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE=
1532 github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0=
1526 -github.com/warpfork/go-testmark v0.9.0 h1:nc+uaCiv5lFQLYjhuC2LTYeJ7JaC+gdDmsz9r0ISy0Y=
1533 github.com/warpfork/go-testmark v0.9.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0=
1534 +github.com/warpfork/go-testmark v0.10.0 h1:E86YlUMYfwIacEsQGlnTvjk1IgYkyTGjPhF0RnwTCmw=
1535 +github.com/warpfork/go-testmark v0.10.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0=
1536 github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
1537 github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
1538 github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w=
repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher.go
+1 -1
@@ -188,7 +188,7 @@ func initTempNode(ctx context.Context, bootstrap []string, peers []peer.AddrInfo
188 }
189
190 // configure the temporary node
191 - cfg.Routing.Type = "dhtclient"
191 + cfg.Routing.Type = config.NewOptionalString("dhtclient")
192
193 // Disable listening for inbound connections
194 cfg.Addresses.Gateway = []string{}
routing/delegated.go new
+93
@@ -0,0 +1,93 @@
1 +package routing
2 +
3 +import (
4 + "strconv"
5 +
6 + drc "github.com/ipfs/go-delegated-routing/client"
7 + drp "github.com/ipfs/go-delegated-routing/gen/proto"
8 + "github.com/ipfs/kubo/config"
9 + "github.com/libp2p/go-libp2p-core/routing"
10 + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
11 +)
12 +
13 +type TieredRouter interface {
14 + routing.Routing
15 + ProvideMany() ProvideMany
16 +}
17 +
18 +var _ TieredRouter = &Tiered{}
19 +
20 +// Tiered is a routing Tiered implementation providing some extra methods to fill
21 +// some special use cases when initializing the client.
22 +type Tiered struct {
23 + routinghelpers.Tiered
24 +}
25 +
26 +// ProvideMany returns a ProvideMany implementation including all Routers that
27 +// implements ProvideMany
28 +func (ds Tiered) ProvideMany() ProvideMany {
29 + var pms []ProvideMany
30 + for _, r := range ds.Tiered.Routers {
31 + pm, ok := r.(ProvideMany)
32 + if !ok {
33 + continue
34 + }
35 + pms = append(pms, pm)
36 + }
37 +
38 + if len(pms) == 0 {
39 + return nil
40 + }
41 +
42 + return &ProvideManyWrapper{pms: pms}
43 +}
44 +
45 +const defaultPriority = 100000
46 +
47 +// GetPriority extract priority from config params.
48 +// Small numbers represent more important routers.
49 +func GetPriority(params map[string]string) int {
50 + param := params[string(config.RouterParamPriority)]
51 + if param == "" {
52 + return defaultPriority
53 + }
54 +
55 + p, err := strconv.Atoi(param)
56 + if err != nil {
57 + return defaultPriority
58 + }
59 +
60 + return p
61 +}
62 +
63 +// RoutingFromConfig creates a Routing instance from the specified configuration.
64 +func RoutingFromConfig(c config.Router) (routing.Routing, error) {
65 + switch {
66 + case c.Type == string(config.RouterTypeReframe):
67 + return reframeRoutingFromConfig(c)
68 + default:
69 + return nil, &RouterTypeNotFoundError{c.Type}
70 + }
71 +}
72 +
73 +func reframeRoutingFromConfig(conf config.Router) (routing.Routing, error) {
74 + var dr drp.DelegatedRouting_Client
75 +
76 + param := string(config.RouterParamEndpoint)
77 + addr, ok := conf.Parameters[param]
78 + if !ok {
79 + return nil, NewParamNeededErr(param, conf.Type)
80 + }
81 +
82 + dr, err := drp.New_DelegatedRouting_Client(addr)
83 + if err != nil {
84 + return nil, err
85 + }
86 +
87 + c := drc.NewClient(dr)
88 + crc := drc.NewContentRoutingClient(c)
89 + return &reframeRoutingWrapper{
90 + Client: c,
91 + ContentRoutingClient: crc,
92 + }, nil
93 +}
routing/delegated_test.go new
+121
@@ -0,0 +1,121 @@
1 +package routing
2 +
3 +import (
4 + "context"
5 + "testing"
6 +
7 + "github.com/ipfs/go-cid"
8 + "github.com/ipfs/kubo/config"
9 + "github.com/libp2p/go-libp2p-core/peer"
10 + "github.com/libp2p/go-libp2p-core/routing"
11 + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
12 + "github.com/multiformats/go-multihash"
13 + "github.com/stretchr/testify/require"
14 +)
15 +
16 +func TestPriority(t *testing.T) {
17 + require := require.New(t)
18 + params := make(map[string]string)
19 + p := GetPriority(params)
20 +
21 + require.Equal(defaultPriority, p)
22 +
23 + params[string(config.RouterParamPriority)] = "101"
24 +
25 + p = GetPriority(params)
26 +
27 + require.Equal(101, p)
28 +
29 + params[string(config.RouterParamPriority)] = "NAN"
30 +
31 + p = GetPriority(params)
32 +
33 + require.Equal(defaultPriority, p)
34 +}
35 +
36 +func TestRoutingFromConfig(t *testing.T) {
37 + require := require.New(t)
38 +
39 + r, err := RoutingFromConfig(config.Router{
40 + Type: "unknown",
41 + })
42 +
43 + require.Nil(r)
44 + require.EqualError(err, "router type unknown is not supported")
45 +
46 + r, err = RoutingFromConfig(config.Router{
47 + Type: string(config.RouterTypeReframe),
48 + Parameters: make(map[string]string),
49 + })
50 +
51 + require.Nil(r)
52 + require.EqualError(err, "configuration param 'Endpoint' is needed for reframe delegated routing types")
53 +
54 + r, err = RoutingFromConfig(config.Router{
55 + Type: string(config.RouterTypeReframe),
56 + Parameters: map[string]string{
57 + string(config.RouterParamEndpoint): "test",
58 + },
59 + })
60 +
61 + require.NotNil(r)
62 + require.NoError(err)
63 +}
64 +
65 +func TestTieredRouter(t *testing.T) {
66 + require := require.New(t)
67 +
68 + tr := &Tiered{
69 + Tiered: routinghelpers.Tiered{
70 + Routers: []routing.Routing{routinghelpers.Null{}},
71 + },
72 + }
73 +
74 + pm := tr.ProvideMany()
75 + require.Nil(pm)
76 +
77 + tr.Tiered.Routers = append(tr.Tiered.Routers, &dummyRouter{})
78 +
79 + pm = tr.ProvideMany()
80 + require.NotNil(pm)
81 +}
82 +
83 +type dummyRouter struct {
84 +}
85 +
86 +func (dr *dummyRouter) Provide(context.Context, cid.Cid, bool) error {
87 + panic("not implemented")
88 +
89 +}
90 +
91 +func (dr *dummyRouter) FindProvidersAsync(context.Context, cid.Cid, int) <-chan peer.AddrInfo {
92 + panic("not implemented")
93 +}
94 +
95 +func (dr *dummyRouter) FindPeer(context.Context, peer.ID) (peer.AddrInfo, error) {
96 + panic("not implemented")
97 +}
98 +
99 +func (dr *dummyRouter) PutValue(context.Context, string, []byte, ...routing.Option) error {
100 + panic("not implemented")
101 +}
102 +
103 +func (dr *dummyRouter) GetValue(context.Context, string, ...routing.Option) ([]byte, error) {
104 + panic("not implemented")
105 +}
106 +
107 +func (dr *dummyRouter) SearchValue(context.Context, string, ...routing.Option) (<-chan []byte, error) {
108 + panic("not implemented")
109 +}
110 +
111 +func (dr *dummyRouter) Bootstrap(context.Context) error {
112 + panic("not implemented")
113 +}
114 +
115 +func (dr *dummyRouter) ProvideMany(ctx context.Context, keys []multihash.Multihash) error {
116 + panic("not implemented")
117 +}
118 +
119 +func (dr *dummyRouter) Ready() bool {
120 + panic("not implemented")
121 +}
routing/error.go new
+27
@@ -0,0 +1,27 @@
1 +package routing
2 +
3 +import "fmt"
4 +
5 +type ParamNeededError struct {
6 + ParamName string
7 + RouterType string
8 +}
9 +
10 +func NewParamNeededErr(param, routing string) error {
11 + return &ParamNeededError{
12 + ParamName: param,
13 + RouterType: routing,
14 + }
15 +}
16 +
17 +func (e *ParamNeededError) Error() string {
18 + return fmt.Sprintf("configuration param '%v' is needed for %v delegated routing types", e.ParamName, e.RouterType)
19 +}
20 +
21 +type RouterTypeNotFoundError struct {
22 + RouterType string
23 +}
24 +
25 +func (e *RouterTypeNotFoundError) Error() string {
26 + return fmt.Sprintf("router type %v is not supported", e.RouterType)
27 +}
routing/wrapper.go new
+66
@@ -0,0 +1,66 @@
1 +package routing
2 +
3 +import (
4 + "context"
5 +
6 + "github.com/ipfs/go-cid"
7 + drc "github.com/ipfs/go-delegated-routing/client"
8 + "github.com/libp2p/go-libp2p-core/peer"
9 + "github.com/libp2p/go-libp2p-core/routing"
10 + "github.com/multiformats/go-multihash"
11 + "golang.org/x/sync/errgroup"
12 +)
13 +
14 +var _ routing.Routing = &reframeRoutingWrapper{}
15 +
16 +// reframeRoutingWrapper is a wrapper needed to construct the routing.Routing interface from
17 +// delegated-routing library.
18 +type reframeRoutingWrapper struct {
19 + *drc.Client
20 + *drc.ContentRoutingClient
21 +}
22 +
23 +func (c *reframeRoutingWrapper) FindProvidersAsync(ctx context.Context, cid cid.Cid, count int) <-chan peer.AddrInfo {
24 + return c.ContentRoutingClient.FindProvidersAsync(ctx, cid, count)
25 +}
26 +
27 +func (c *reframeRoutingWrapper) Bootstrap(ctx context.Context) error {
28 + return nil
29 +}
30 +
31 +func (c *reframeRoutingWrapper) FindPeer(ctx context.Context, id peer.ID) (peer.AddrInfo, error) {
32 + return peer.AddrInfo{}, routing.ErrNotSupported
33 +}
34 +
35 +type ProvideMany interface {
36 + ProvideMany(ctx context.Context, keys []multihash.Multihash) error
37 + Ready() bool
38 +}
39 +
40 +var _ ProvideMany = &ProvideManyWrapper{}
41 +
42 +type ProvideManyWrapper struct {
43 + pms []ProvideMany
44 +}
45 +
46 +func (pmw *ProvideManyWrapper) ProvideMany(ctx context.Context, keys []multihash.Multihash) error {
47 + var g errgroup.Group
48 + for _, pm := range pmw.pms {
49 + pm := pm
50 + g.Go(func() error {
51 + return pm.ProvideMany(ctx, keys)
52 + })
53 + }
54 +
55 + return g.Wait()
56 +}
57 +
58 +// Ready is ready if all providers are ready
59 +func (pmw *ProvideManyWrapper) Ready() bool {
60 + out := true
61 + for _, pm := range pmw.pms {
62 + out = out && pm.Ready()
63 + }
64 +
65 + return out
66 +}
routing/wrapper_test.go new
+101
@@ -0,0 +1,101 @@
1 +package routing
2 +
3 +import (
4 + "context"
5 + "errors"
6 + "testing"
7 +
8 + "github.com/multiformats/go-multihash"
9 +)
10 +
11 +func TestProvideManyWrapper_ProvideMany(t *testing.T) {
12 + type fields struct {
13 + pms []ProvideMany
14 + }
15 + tests := []struct {
16 + name string
17 + fields fields
18 + wantErr bool
19 + ready bool
20 + }{
21 + {
22 + name: "one provider",
23 + fields: fields{
24 + pms: []ProvideMany{
25 + newDummyProvideMany(true, false),
26 + },
27 + },
28 + wantErr: false,
29 + ready: true,
30 + },
31 + {
32 + name: "two providers, no errors and ready",
33 + fields: fields{
34 + pms: []ProvideMany{
35 + newDummyProvideMany(true, false),
36 + newDummyProvideMany(true, false),
37 + },
38 + },
39 + wantErr: false,
40 + ready: true,
41 + },
42 + {
43 + name: "two providers, no ready, no error",
44 + fields: fields{
45 + pms: []ProvideMany{
46 + newDummyProvideMany(true, false),
47 + newDummyProvideMany(false, false),
48 + },
49 + },
50 + wantErr: false,
51 + ready: false,
52 + },
53 + {
54 + name: "two providers, no ready, and one erroing",
55 + fields: fields{
56 + pms: []ProvideMany{
57 + newDummyProvideMany(true, false),
58 + newDummyProvideMany(false, true),
59 + },
60 + },
61 + wantErr: true,
62 + ready: false,
63 + },
64 + }
65 + for _, tt := range tests {
66 + t.Run(tt.name, func(t *testing.T) {
67 + pmw := &ProvideManyWrapper{
68 + pms: tt.fields.pms,
69 + }
70 + if err := pmw.ProvideMany(context.Background(), nil); (err != nil) != tt.wantErr {
71 + t.Errorf("ProvideManyWrapper.ProvideMany() error = %v, wantErr %v", err, tt.wantErr)
72 + }
73 +
74 + if ready := pmw.Ready(); ready != tt.ready {
75 + t.Errorf("ProvideManyWrapper.Ready() unexpected output = %v, want %v", ready, tt.ready)
76 + }
77 + })
78 + }
79 +}
80 +
81 +func newDummyProvideMany(ready, failProviding bool) *dummyProvideMany {
82 + return &dummyProvideMany{
83 + ready: ready,
84 + failProviding: failProviding,
85 + }
86 +}
87 +
88 +type dummyProvideMany struct {
89 + ready, failProviding bool
90 +}
91 +
92 +func (dpm *dummyProvideMany) ProvideMany(ctx context.Context, keys []multihash.Multihash) error {
93 + if dpm.failProviding {
94 + return errors.New("error providing many")
95 + }
96 +
97 + return nil
98 +}
99 +func (dpm *dummyProvideMany) Ready() bool {
100 + return dpm.ready
101 +}
test/sharness/t0170-legacy-dht.sh renamed
+1
@@ -1,5 +1,6 @@
1 #!/usr/bin/env bash
2
3 +# Legacy / deprecated, see: t0170-routing-dht.sh
4 test_description="Test dht command"
5
6 . lib/test-lib.sh
test/sharness/t0170-routing-dht.sh new
+118
@@ -0,0 +1,118 @@
1 +#!/usr/bin/env bash
2 +
3 +# This file does the same tests as t0170-dht.sh but uses 'routing' commands instead
4 +# (only exception is query, which lives only under dht)
5 +test_description="Test routing command"
6 +
7 +. lib/test-lib.sh
8 +
9 +test_dht() {
10 + NUM_NODES=5
11 +
12 + test_expect_success 'init iptb' '
13 + rm -rf .iptb/ &&
14 + iptb testbed create -type localipfs -count $NUM_NODES -init
15 + '
16 +
17 + startup_cluster $NUM_NODES $@
18 +
19 + test_expect_success 'peer ids' '
20 + PEERID_0=$(iptb attr get 0 id) &&
21 + PEERID_2=$(iptb attr get 2 id)
22 + '
23 +
24 + # ipfs routing findpeer <peerID>
25 + test_expect_success 'findpeer' '
26 + ipfsi 1 routing findpeer $PEERID_0 | sort >actual &&
27 + ipfsi 0 id -f "<addrs>" | cut -d / -f 1-5 | sort >expected &&
28 + test_cmp actual expected
29 + '
30 +
31 + # ipfs routing get <key>
32 + test_expect_success 'get with good keys works' '
33 + HASH="$(echo "hello world" | ipfsi 2 add -q)" &&
34 + ipfsi 2 name publish "/ipfs/$HASH" &&
35 + ipfsi 1 routing get "/ipns/$PEERID_2" >get_result
36 + '
37 +
38 + test_expect_success 'get with good keys contains the right value' '
39 + cat get_result | grep -aq "/ipfs/$HASH"
40 + '
41 +
42 + test_expect_success 'put round trips (#3124)' '
43 + ipfsi 0 routing put "/ipns/$PEERID_2" get_result | sort >putted &&
44 + [ -s putted ] ||
45 + test_fsh cat putted
46 + '
47 +
48 + test_expect_success 'put with bad keys fails (issue #5113)' '
49 + ipfsi 0 routing put "foo" <<<bar >putted
50 + ipfsi 0 routing put "/pk/foo" <<<bar >>putted
51 + ipfsi 0 routing put "/ipns/foo" <<<bar >>putted
52 + [ ! -s putted ] ||
53 + test_fsh cat putted
54 + '
55 +
56 + test_expect_success 'put with bad keys returns error (issue #4611)' '
57 + test_must_fail ipfsi 0 routing put "foo" <<<bar &&
58 + test_must_fail ipfsi 0 routing put "/pk/foo" <<<bar &&
59 + test_must_fail ipfsi 0 routing put "/ipns/foo" <<<bar
60 + '
61 +
62 + test_expect_success 'get with bad keys (issue #4611)' '
63 + test_must_fail ipfsi 0 routing get "foo" &&
64 + test_must_fail ipfsi 0 routing get "/pk/foo"
65 + '
66 +
67 + test_expect_success "add a ref so we can find providers for it" '
68 + echo "some stuff" > afile &&
69 + HASH=$(ipfsi 3 add -q afile)
70 + '
71 +
72 + # ipfs routing findprovs <key>
73 + test_expect_success 'findprovs' '
74 + ipfsi 4 routing findprovs $HASH > provs &&
75 + iptb attr get 3 id > expected &&
76 + test_cmp provs expected
77 + '
78 +
79 +
80 + # ipfs dht query <peerID>
81 + #
82 + # We test all nodes. 4 nodes should see the same peer ID, one node (the
83 + # closest) should see a different one.
84 +
85 + for i in $(test_seq 0 4); do
86 + test_expect_success "dht query from $i" '
87 + ipfsi "$i" dht query "$HASH" | head -1 >closest-$i
88 + '
89 + done
90 +
91 + test_expect_success "collecting results" '
92 + cat closest-* | sort | uniq -c | sed -e "s/ *\([0-9]\+\) .*/\1/g" | sort -g > actual &&
93 + echo 1 > expected &&
94 + echo 4 >> expected
95 + '
96 +
97 + test_expect_success "checking results" '
98 + test_cmp actual expected
99 + '
100 +
101 + test_expect_success 'stop iptb' '
102 + iptb stop
103 + '
104 +
105 + test_expect_success "dht commands fail when offline" '
106 + test_must_fail ipfsi 0 routing findprovs "$HASH" 2>err_findprovs &&
107 + test_must_fail ipfsi 0 routing findpeer "$HASH" 2>err_findpeer &&
108 + test_must_fail ipfsi 0 routing put "/ipns/$PEERID_2" "get_result" 2>err_put &&
109 + test_should_contain "this command must be run in online mode" err_findprovs &&
110 + test_should_contain "this command must be run in online mode" err_findpeer &&
111 + test_should_contain "this command must be run in online mode" err_put
112 + '
113 +}
114 +
115 +test_dht
116 +test_dht --enable-pubsub-experiment --enable-namesys-pubsub
117 +
118 +test_done
test/sharness/t0701-delegated-routing-reframe.sh new
+103
@@ -0,0 +1,103 @@
1 +#!/usr/bin/env bash
2 +
3 +test_description="Test delegated routing via reframe endpoint"
4 +
5 +. lib/test-lib.sh
6 +
7 +if ! test_have_prereq SOCAT; then
8 + skip_all="skipping '$test_description': socat is not available"
9 + test_done
10 +fi
11 +
12 +# simple reframe server mock
13 +# local endpoint responds with deterministic application/vnd.ipfs.rpc+dag-json; version=1
14 +REFRAME_PORT=5098
15 +function start_reframe_mock_endpoint() {
16 + REMOTE_SERVER_LOG="reframe-server.log"
17 + rm -f $REMOTE_SERVER_LOG
18 +
19 + touch response
20 + socat tcp-listen:$REFRAME_PORT,fork,bind=127.0.0.1,reuseaddr 'SYSTEM:cat response'!!CREATE:$REMOTE_SERVER_LOG &
21 + REMOTE_SERVER_PID=$!
22 +
23 + socat /dev/null tcp:127.0.0.1:$REFRAME_PORT,retry=10
24 + return $?
25 +}
26 +function serve_reframe_response() {
27 + local body=$1
28 + local status_code=${2:-"200 OK"}
29 + local length=$((1 + ${#body}))
30 + echo -e "HTTP/1.1 $status_code\nContent-Type: application/vnd.ipfs.rpc+dag-json; version=1\nContent-Length: $length\n\n$body" > response
31 +}
32 +function stop_reframe_mock_endpoint() {
33 + exec 7<&-
34 + kill $REMOTE_SERVER_PID > /dev/null 2>&1
35 + wait $REMOTE_SERVER_PID || true
36 +}
37 +
38 +# daemon running in online mode to ensure Pin.origins/PinStatus.delegates work
39 +test_init_ipfs
40 +
41 +# based on static, synthetic reframe messages:
42 +# t0701-delegated-routing-reframe/FindProvidersRequest
43 +# t0701-delegated-routing-reframe/FindProvidersResponse
44 +FINDPROV_CID="bafybeigvgzoolc3drupxhlevdp2ugqcrbcsqfmcek2zxiw5wctk3xjpjwy"
45 +EXPECTED_PROV="QmQzqxhK82kAmKvARFZSkUVS6fo9sySaiogAnx5EnZ6ZmC"
46 +
47 +test_expect_success "default Routing config has no Routers defined" '
48 + echo null > expected &&
49 + ipfs config show | jq .Routing.Routers > actual &&
50 + test_cmp expected actual
51 +'
52 +
53 +# turn off all implicit routers
54 +ipfs config Routing.Type none || exit 1
55 +test_launch_ipfs_daemon
56 +test_expect_success "disabling default router (dht) works" '
57 + ipfs config Routing.Type > actual &&
58 + echo none > expected &&
59 + test_cmp expected actual
60 +'
61 +test_expect_success "no routers means findprovs returns no results" '
62 + ipfs routing findprovs "$FINDPROV_CID" > actual &&
63 + echo -n > expected &&
64 + test_cmp expected actual
65 +'
66 +
67 +test_kill_ipfs_daemon
68 +
69 +# set Routing config to only use delegated routing via mocked reframe endpoint
70 +ipfs config Routing.Routers.TestDelegatedRouter --json '{
71 + "Type": "reframe",
72 + "Parameters": {
73 + "Endpoint": "http://127.0.0.1:5098/reframe"
74 + }
75 +}' || exit 1
76 +
77 +test_expect_success "adding reframe endpoint to Routing.Routers config works" '
78 + echo "http://127.0.0.1:5098/reframe" > expected &&
79 + ipfs config Routing.Routers.TestDelegatedRouter.Parameters.Endpoint > actual &&
80 + test_cmp expected actual
81 +'
82 +
83 +test_launch_ipfs_daemon
84 +
85 +test_expect_success "start_reframe_mock_endpoint" '
86 + start_reframe_mock_endpoint
87 +'
88 +
89 +test_expect_success "'ipfs routing findprovs' returns result from delegated reframe router" '
90 + serve_reframe_response "$(<../t0701-delegated-routing-reframe/FindProvidersResponse)" &&
91 + echo "$EXPECTED_PROV" > expected &&
92 + ipfs routing findprovs "$FINDPROV_CID" > actual &&
93 + test_cmp expected actual
94 +'
95 +
96 +test_expect_success "stop_reframe_mock_endpoint" '
97 + stop_reframe_mock_endpoint
98 +'
99 +
100 +
101 +test_kill_ipfs_daemon
102 +test_done
103 +# vim: ts=2 sw=2 sts=2 et:
test/sharness/t0701-delegated-routing-reframe/FindProvidersRequest new
+1
@@ -0,0 +1 @@
1 +{"FindProvidersRequest":{"Key":{"/":"bafybeigvgzoolc3drupxhlevdp2ugqcrbcsqfmcek2zxiw5wctk3xjpjwy"}}}
test/sharness/t0701-delegated-routing-reframe/FindProvidersResponse new
+1
@@ -0,0 +1 @@
1 +{"FindProvidersResponse":{"Providers":[{"Node":{"peer":{"ID":{"/":{"bytes":"EiAngCqwSSL46hQ5+DWaJsZ1SPV2RwrqwID/OEuj5Rdgqw"}},"Multiaddresses":[{"/":{"bytes":"NiJwZWVyLmlwZnMtZWxhc3RpYy1wcm92aWRlci1hd3MuY29tBgu43QM"}}]}},"Proto":[{"2304":{}}]}]}}