diff --git a/client/rpc/api.go b/client/rpc/api.go index 48a80388fd8c..827b427c9fff 100644 --- a/client/rpc/api.go +++ b/client/rpc/api.go @@ -227,6 +227,8 @@ func (api *HttpApi) Object() iface.ObjectAPI { return (*ObjectAPI)(api) } +// nolint deprecated +// Deprecated: use [HttpApi.Routing] instead. func (api *HttpApi) Dht() iface.DhtAPI { return (*DhtAPI)(api) } diff --git a/client/rpc/dht.go b/client/rpc/dht.go index 1b2c863980d9..cfc886a49e6d 100644 --- a/client/rpc/dht.go +++ b/client/rpc/dht.go @@ -2,110 +2,30 @@ package rpc import ( "context" - "encoding/json" "github.com/ipfs/boxo/path" caopts "github.com/ipfs/kubo/core/coreiface/options" "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/routing" ) type DhtAPI HttpApi +// nolint deprecated +// Deprecated: use [RoutingAPI.FindPeer] instead. func (api *DhtAPI) FindPeer(ctx context.Context, p peer.ID) (peer.AddrInfo, error) { - var out struct { - Type routing.QueryEventType - Responses []peer.AddrInfo - } - resp, err := api.core().Request("dht/findpeer", p.String()).Send(ctx) - if err != nil { - return peer.AddrInfo{}, err - } - if resp.Error != nil { - return peer.AddrInfo{}, resp.Error - } - defer resp.Close() - dec := json.NewDecoder(resp.Output) - for { - if err := dec.Decode(&out); err != nil { - return peer.AddrInfo{}, err - } - if out.Type == routing.FinalPeer { - return out.Responses[0], nil - } - } + return api.core().Routing().FindPeer(ctx, p) } +// nolint deprecated +// Deprecated: use [RoutingAPI.FindProviders] instead. func (api *DhtAPI) FindProviders(ctx context.Context, p path.Path, opts ...caopts.DhtFindProvidersOption) (<-chan peer.AddrInfo, error) { - options, err := caopts.DhtFindProvidersOptions(opts...) - if err != nil { - return nil, err - } - - rp, _, err := api.core().ResolvePath(ctx, p) - if err != nil { - return nil, err - } - - resp, err := api.core().Request("dht/findprovs", rp.RootCid().String()). - Option("num-providers", options.NumProviders). - Send(ctx) - if err != nil { - return nil, err - } - if resp.Error != nil { - return nil, resp.Error - } - res := make(chan peer.AddrInfo) - - go func() { - defer resp.Close() - defer close(res) - dec := json.NewDecoder(resp.Output) - - for { - var out struct { - Extra string - Type routing.QueryEventType - Responses []peer.AddrInfo - } - - if err := dec.Decode(&out); err != nil { - return // todo: handle this somehow - } - if out.Type == routing.QueryError { - return // usually a 'not found' error - // todo: handle other errors - } - if out.Type == routing.Provider { - for _, pi := range out.Responses { - select { - case res <- pi: - case <-ctx.Done(): - return - } - } - } - } - }() - - return res, nil + return api.core().Routing().FindProviders(ctx, p, opts...) } +// nolint deprecated +// Deprecated: use [RoutingAPI.Provide] instead. func (api *DhtAPI) Provide(ctx context.Context, p path.Path, opts ...caopts.DhtProvideOption) error { - options, err := caopts.DhtProvideOptions(opts...) - if err != nil { - return err - } - - rp, _, err := api.core().ResolvePath(ctx, p) - if err != nil { - return err - } - - return api.core().Request("dht/provide", rp.RootCid().String()). - Option("recursive", options.Recursive). - Exec(ctx, nil) + return api.core().Routing().Provide(ctx, p, opts...) } func (api *DhtAPI) core() *HttpApi { diff --git a/client/rpc/routing.go b/client/rpc/routing.go index 2ecf25f8b45e..693f155c6b0c 100644 --- a/client/rpc/routing.go +++ b/client/rpc/routing.go @@ -6,7 +6,9 @@ import ( "encoding/base64" "encoding/json" + "github.com/ipfs/boxo/path" "github.com/ipfs/kubo/core/coreiface/options" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/routing" ) @@ -58,6 +60,102 @@ func (api *RoutingAPI) Put(ctx context.Context, key string, value []byte, opts . return nil } +func (api *RoutingAPI) FindPeer(ctx context.Context, p peer.ID) (peer.AddrInfo, error) { + var out struct { + Type routing.QueryEventType + Responses []peer.AddrInfo + } + resp, err := api.core().Request("routing/findpeer", p.String()).Send(ctx) + if err != nil { + return peer.AddrInfo{}, err + } + if resp.Error != nil { + return peer.AddrInfo{}, resp.Error + } + defer resp.Close() + dec := json.NewDecoder(resp.Output) + for { + if err := dec.Decode(&out); err != nil { + return peer.AddrInfo{}, err + } + if out.Type == routing.FinalPeer { + return out.Responses[0], nil + } + } +} + +func (api *RoutingAPI) FindProviders(ctx context.Context, p path.Path, opts ...options.RoutingFindProvidersOption) (<-chan peer.AddrInfo, error) { + options, err := options.RoutingFindProvidersOptions(opts...) + if err != nil { + return nil, err + } + + rp, _, err := api.core().ResolvePath(ctx, p) + if err != nil { + return nil, err + } + + resp, err := api.core().Request("routing/findprovs", rp.RootCid().String()). + Option("num-providers", options.NumProviders). + Send(ctx) + if err != nil { + return nil, err + } + if resp.Error != nil { + return nil, resp.Error + } + res := make(chan peer.AddrInfo) + + go func() { + defer resp.Close() + defer close(res) + dec := json.NewDecoder(resp.Output) + + for { + var out struct { + Extra string + Type routing.QueryEventType + Responses []peer.AddrInfo + } + + if err := dec.Decode(&out); err != nil { + return // todo: handle this somehow + } + if out.Type == routing.QueryError { + return // usually a 'not found' error + // todo: handle other errors + } + if out.Type == routing.Provider { + for _, pi := range out.Responses { + select { + case res <- pi: + case <-ctx.Done(): + return + } + } + } + } + }() + + return res, nil +} + +func (api *RoutingAPI) Provide(ctx context.Context, p path.Path, opts ...options.RoutingProvideOption) error { + options, err := options.RoutingProvideOptions(opts...) + if err != nil { + return err + } + + rp, _, err := api.core().ResolvePath(ctx, p) + if err != nil { + return err + } + + return api.core().Request("routing/provide", rp.RootCid().String()). + Option("recursive", options.Recursive). + Exec(ctx, nil) +} + func (api *RoutingAPI) core() *HttpApi { return (*HttpApi)(api) } diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index 0723ab659842..6c6aa4907e8e 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -130,7 +130,8 @@ func (api *CoreAPI) Pin() coreiface.PinAPI { return (*PinAPI)(api) } -// Dht returns the DhtAPI interface implementation backed by the go-ipfs node +// nolint deprecated +// Deprecated: use [CoreAPI.Routing] instead. func (api *CoreAPI) Dht() coreiface.DhtAPI { return (*DhtAPI)(api) } diff --git a/core/coreapi/dht.go b/core/coreapi/dht.go index 7b5d4eb84619..f9155de008ef 100644 --- a/core/coreapi/dht.go +++ b/core/coreapi/dht.go @@ -2,151 +2,31 @@ package coreapi import ( "context" - "fmt" - blockservice "github.com/ipfs/boxo/blockservice" - blockstore "github.com/ipfs/boxo/blockstore" - offline "github.com/ipfs/boxo/exchange/offline" - dag "github.com/ipfs/boxo/ipld/merkledag" "github.com/ipfs/boxo/path" - cid "github.com/ipfs/go-cid" - cidutil "github.com/ipfs/go-cidutil" coreiface "github.com/ipfs/kubo/core/coreiface" caopts "github.com/ipfs/kubo/core/coreiface/options" - "github.com/ipfs/kubo/tracing" peer "github.com/libp2p/go-libp2p/core/peer" - routing "github.com/libp2p/go-libp2p/core/routing" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" ) type DhtAPI CoreAPI +// nolint deprecated +// Deprecated: use [RoutingAPI.FindPeer] instead. func (api *DhtAPI) FindPeer(ctx context.Context, p peer.ID) (peer.AddrInfo, error) { - ctx, span := tracing.Span(ctx, "CoreAPI.DhtAPI", "FindPeer", trace.WithAttributes(attribute.String("peer", p.String()))) - defer span.End() - err := api.checkOnline(false) - if err != nil { - return peer.AddrInfo{}, err - } - - pi, err := api.routing.FindPeer(ctx, peer.ID(p)) - if err != nil { - return peer.AddrInfo{}, err - } - - return pi, nil + return api.core().Routing().FindPeer(ctx, p) } +// nolint deprecated +// Deprecated: use [RoutingAPI.FindProviders] instead. func (api *DhtAPI) FindProviders(ctx context.Context, p path.Path, opts ...caopts.DhtFindProvidersOption) (<-chan peer.AddrInfo, error) { - ctx, span := tracing.Span(ctx, "CoreAPI.DhtAPI", "FindProviders", trace.WithAttributes(attribute.String("path", p.String()))) - defer span.End() - - settings, err := caopts.DhtFindProvidersOptions(opts...) - if err != nil { - return nil, err - } - span.SetAttributes(attribute.Int("numproviders", settings.NumProviders)) - - err = api.checkOnline(false) - if err != nil { - return nil, err - } - - rp, _, err := api.core().ResolvePath(ctx, p) - if err != nil { - return nil, err - } - - numProviders := settings.NumProviders - if numProviders < 1 { - return nil, fmt.Errorf("number of providers must be greater than 0") - } - - pchan := api.routing.FindProvidersAsync(ctx, rp.RootCid(), numProviders) - return pchan, nil + return api.core().Routing().FindProviders(ctx, p, opts...) } -func (api *DhtAPI) Provide(ctx context.Context, path path.Path, opts ...caopts.DhtProvideOption) error { - ctx, span := tracing.Span(ctx, "CoreAPI.DhtAPI", "Provide", trace.WithAttributes(attribute.String("path", path.String()))) - defer span.End() - - settings, err := caopts.DhtProvideOptions(opts...) - if err != nil { - return err - } - span.SetAttributes(attribute.Bool("recursive", settings.Recursive)) - - err = api.checkOnline(false) - if err != nil { - return err - } - - rp, _, err := api.core().ResolvePath(ctx, path) - if err != nil { - return err - } - - c := rp.RootCid() - - has, err := api.blockstore.Has(ctx, c) - if err != nil { - return err - } - - if !has { - return fmt.Errorf("block %s not found locally, cannot provide", c) - } - - if settings.Recursive { - err = provideKeysRec(ctx, api.routing, api.blockstore, []cid.Cid{c}) - } else { - err = provideKeys(ctx, api.routing, []cid.Cid{c}) - } - if err != nil { - return err - } - - return nil -} - -func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error { - for _, c := range cids { - err := r.Provide(ctx, c, true) - if err != nil { - return err - } - } - return nil -} - -func provideKeysRec(ctx context.Context, r routing.Routing, bs blockstore.Blockstore, cids []cid.Cid) error { - provided := cidutil.NewStreamingSet() - - errCh := make(chan error) - go func() { - dserv := dag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) - for _, c := range cids { - err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, provided.Visitor(ctx)) - if err != nil { - errCh <- err - } - } - }() - - for { - select { - case k := <-provided.New: - err := r.Provide(ctx, k, true) - if err != nil { - return err - } - case err := <-errCh: - return err - case <-ctx.Done(): - return ctx.Err() - } - } +// nolint deprecated +// Deprecated: use [RoutingAPI.Provide] instead. +func (api *DhtAPI) Provide(ctx context.Context, p path.Path, opts ...caopts.DhtProvideOption) error { + return api.core().Routing().Provide(ctx, p, opts...) } func (api *DhtAPI) core() coreiface.CoreAPI { diff --git a/core/coreapi/routing.go b/core/coreapi/routing.go index d784a738d2ac..fe273158e1eb 100644 --- a/core/coreapi/routing.go +++ b/core/coreapi/routing.go @@ -3,17 +3,29 @@ package coreapi import ( "context" "errors" + "fmt" "strings" + blockservice "github.com/ipfs/boxo/blockservice" + blockstore "github.com/ipfs/boxo/blockstore" + offline "github.com/ipfs/boxo/exchange/offline" + dag "github.com/ipfs/boxo/ipld/merkledag" + "github.com/ipfs/boxo/path" + cid "github.com/ipfs/go-cid" + cidutil "github.com/ipfs/go-cidutil" coreiface "github.com/ipfs/kubo/core/coreiface" caopts "github.com/ipfs/kubo/core/coreiface/options" + "github.com/ipfs/kubo/tracing" peer "github.com/libp2p/go-libp2p/core/peer" + routing "github.com/libp2p/go-libp2p/core/routing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) type RoutingAPI CoreAPI -func (r *RoutingAPI) Get(ctx context.Context, key string) ([]byte, error) { - if !r.nd.IsOnline { +func (api *RoutingAPI) Get(ctx context.Context, key string) ([]byte, error) { + if !api.nd.IsOnline { return nil, coreiface.ErrOffline } @@ -22,16 +34,16 @@ func (r *RoutingAPI) Get(ctx context.Context, key string) ([]byte, error) { return nil, err } - return r.routing.GetValue(ctx, dhtKey) + return api.routing.GetValue(ctx, dhtKey) } -func (r *RoutingAPI) Put(ctx context.Context, key string, value []byte, opts ...caopts.RoutingPutOption) error { +func (api *RoutingAPI) Put(ctx context.Context, key string, value []byte, opts ...caopts.RoutingPutOption) error { options, err := caopts.RoutingPutOptions(opts...) if err != nil { return err } - err = r.checkOnline(options.AllowOffline) + err = api.checkOnline(options.AllowOffline) if err != nil { return err } @@ -41,7 +53,7 @@ func (r *RoutingAPI) Put(ctx context.Context, key string, value []byte, opts ... return err } - return r.routing.PutValue(ctx, dhtKey, value) + return api.routing.PutValue(ctx, dhtKey, value) } func normalizeKey(s string) (string, error) { @@ -58,3 +70,134 @@ func normalizeKey(s string) (string, error) { } return strings.Join(append(parts[:2], string(k)), "/"), nil } + +func (api *RoutingAPI) FindPeer(ctx context.Context, p peer.ID) (peer.AddrInfo, error) { + ctx, span := tracing.Span(ctx, "CoreAPI.DhtAPI", "FindPeer", trace.WithAttributes(attribute.String("peer", p.String()))) + defer span.End() + err := api.checkOnline(false) + if err != nil { + return peer.AddrInfo{}, err + } + + pi, err := api.routing.FindPeer(ctx, peer.ID(p)) + if err != nil { + return peer.AddrInfo{}, err + } + + return pi, nil +} + +func (api *RoutingAPI) FindProviders(ctx context.Context, p path.Path, opts ...caopts.RoutingFindProvidersOption) (<-chan peer.AddrInfo, error) { + ctx, span := tracing.Span(ctx, "CoreAPI.DhtAPI", "FindProviders", trace.WithAttributes(attribute.String("path", p.String()))) + defer span.End() + + settings, err := caopts.RoutingFindProvidersOptions(opts...) + if err != nil { + return nil, err + } + span.SetAttributes(attribute.Int("numproviders", settings.NumProviders)) + + err = api.checkOnline(false) + if err != nil { + return nil, err + } + + rp, _, err := api.core().ResolvePath(ctx, p) + if err != nil { + return nil, err + } + + numProviders := settings.NumProviders + if numProviders < 1 { + return nil, fmt.Errorf("number of providers must be greater than 0") + } + + pchan := api.routing.FindProvidersAsync(ctx, rp.RootCid(), numProviders) + return pchan, nil +} + +func (api *RoutingAPI) Provide(ctx context.Context, path path.Path, opts ...caopts.RoutingProvideOption) error { + ctx, span := tracing.Span(ctx, "CoreAPI.DhtAPI", "Provide", trace.WithAttributes(attribute.String("path", path.String()))) + defer span.End() + + settings, err := caopts.RoutingProvideOptions(opts...) + if err != nil { + return err + } + span.SetAttributes(attribute.Bool("recursive", settings.Recursive)) + + err = api.checkOnline(false) + if err != nil { + return err + } + + rp, _, err := api.core().ResolvePath(ctx, path) + if err != nil { + return err + } + + c := rp.RootCid() + + has, err := api.blockstore.Has(ctx, c) + if err != nil { + return err + } + + if !has { + return fmt.Errorf("block %s not found locally, cannot provide", c) + } + + if settings.Recursive { + err = provideKeysRec(ctx, api.routing, api.blockstore, []cid.Cid{c}) + } else { + err = provideKeys(ctx, api.routing, []cid.Cid{c}) + } + if err != nil { + return err + } + + return nil +} + +func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error { + for _, c := range cids { + err := r.Provide(ctx, c, true) + if err != nil { + return err + } + } + return nil +} + +func provideKeysRec(ctx context.Context, r routing.Routing, bs blockstore.Blockstore, cids []cid.Cid) error { + provided := cidutil.NewStreamingSet() + + errCh := make(chan error) + go func() { + dserv := dag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))) + for _, c := range cids { + err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, provided.Visitor(ctx)) + if err != nil { + errCh <- err + } + } + }() + + for { + select { + case k := <-provided.New: + err := r.Provide(ctx, k, true) + if err != nil { + return err + } + case err := <-errCh: + return err + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (api *RoutingAPI) core() coreiface.CoreAPI { + return (*CoreAPI)(api) +} diff --git a/core/coreiface/coreapi.go b/core/coreiface/coreapi.go index 4fd6851af330..bcd94f3816cb 100644 --- a/core/coreiface/coreapi.go +++ b/core/coreiface/coreapi.go @@ -34,7 +34,8 @@ type CoreAPI interface { // Object returns an implementation of Object API Object() ObjectAPI - // Dht returns an implementation of Dht API + // nolint deprecated + // Deprecated: use [Routing] instead. Dht() DhtAPI // Swarm returns an implementation of Swarm API diff --git a/core/coreiface/dht.go b/core/coreiface/dht.go index a916dbf3d375..001f5856a1b2 100644 --- a/core/coreiface/dht.go +++ b/core/coreiface/dht.go @@ -4,24 +4,22 @@ import ( "context" "github.com/ipfs/boxo/path" - "github.com/ipfs/kubo/core/coreiface/options" - "github.com/libp2p/go-libp2p/core/peer" ) -// DhtAPI specifies the interface to the DHT -// Note: This API will likely get deprecated in near future, see -// https://github.com/ipfs/interface-ipfs-core/issues/249 for more context. +// nolint deprecated +// Deprecated: use [RoutingAPI] instead. type DhtAPI interface { - // FindPeer queries the DHT for all of the multiaddresses associated with a - // Peer ID + // nolint deprecated + // Deprecated: use [RoutingAPI.FindPeer] instead. FindPeer(context.Context, peer.ID) (peer.AddrInfo, error) - // FindProviders finds peers in the DHT who can provide a specific value - // given a key. + // nolint deprecated + // Deprecated: use [RoutingAPI.FindProviders] instead. FindProviders(context.Context, path.Path, ...options.DhtFindProvidersOption) (<-chan peer.AddrInfo, error) - // Provide announces to the network that you are providing given values + // nolint deprecated + // Deprecated: use [RoutingAPI.Provide] instead. Provide(context.Context, path.Path, ...options.DhtProvideOption) error } diff --git a/core/coreiface/options/dht.go b/core/coreiface/options/dht.go index b43bf3e7a755..4a6f7f86e765 100644 --- a/core/coreiface/options/dht.go +++ b/core/coreiface/options/dht.go @@ -1,64 +1,29 @@ package options -type DhtProvideSettings struct { - Recursive bool -} +// nolint deprecated +// Deprecated: use [RoutingProvideSettings] instead. +type DhtProvideSettings = RoutingProvideSettings -type DhtFindProvidersSettings struct { - NumProviders int -} +// nolint deprecated +// Deprecated: use [RoutingFindProvidersSettings] instead. +type DhtFindProvidersSettings = RoutingFindProvidersSettings -type ( - DhtProvideOption func(*DhtProvideSettings) error - DhtFindProvidersOption func(*DhtFindProvidersSettings) error -) +// nolint deprecated +// Deprecated: use [RoutingProvideOption] instead. +type DhtProvideOption = RoutingProvideOption -func DhtProvideOptions(opts ...DhtProvideOption) (*DhtProvideSettings, error) { - options := &DhtProvideSettings{ - Recursive: false, - } +// nolint deprecated +// Deprecated: use [RoutingFindProvidersOption] instead. +type DhtFindProvidersOption = RoutingFindProvidersOption - for _, opt := range opts { - err := opt(options) - if err != nil { - return nil, err - } - } - return options, nil -} +// nolint deprecated +// Deprecated: use [RoutingProvideOptions] instead. +var DhtProvideOptions = RoutingProvideOptions -func DhtFindProvidersOptions(opts ...DhtFindProvidersOption) (*DhtFindProvidersSettings, error) { - options := &DhtFindProvidersSettings{ - NumProviders: 20, - } +// nolint deprecated +// Deprecated: use [RoutingFindProvidersOptions] instead. +var DhtFindProvidersOptions = RoutingFindProvidersOptions - for _, opt := range opts { - err := opt(options) - if err != nil { - return nil, err - } - } - return options, nil -} - -type dhtOpts struct{} - -var Dht dhtOpts - -// Recursive is an option for Dht.Provide which specifies whether to provide -// the given path recursively -func (dhtOpts) Recursive(recursive bool) DhtProvideOption { - return func(settings *DhtProvideSettings) error { - settings.Recursive = recursive - return nil - } -} - -// NumProviders is an option for Dht.FindProviders which specifies the -// number of peers to look for. Default is 20 -func (dhtOpts) NumProviders(numProviders int) DhtFindProvidersOption { - return func(settings *DhtFindProvidersSettings) error { - settings.NumProviders = numProviders - return nil - } -} +// nolint deprecated +// Deprecated: use [Routing] instead. +var Dht = Routing diff --git a/core/coreiface/options/routing.go b/core/coreiface/options/routing.go index d66d44a0dbd5..8da7e7a1db22 100644 --- a/core/coreiface/options/routing.go +++ b/core/coreiface/options/routing.go @@ -21,13 +21,76 @@ func RoutingPutOptions(opts ...RoutingPutOption) (*RoutingPutSettings, error) { return options, nil } -type putOpts struct{} +// nolint deprecated +// Deprecated: use [Routing] instead. +var Put = Routing -var Put putOpts +type RoutingProvideSettings struct { + Recursive bool +} + +type RoutingFindProvidersSettings struct { + NumProviders int +} + +type ( + RoutingProvideOption func(*DhtProvideSettings) error + RoutingFindProvidersOption func(*DhtFindProvidersSettings) error +) + +func RoutingProvideOptions(opts ...RoutingProvideOption) (*RoutingProvideSettings, error) { + options := &RoutingProvideSettings{ + Recursive: false, + } + + for _, opt := range opts { + err := opt(options) + if err != nil { + return nil, err + } + } + return options, nil +} + +func RoutingFindProvidersOptions(opts ...RoutingFindProvidersOption) (*RoutingFindProvidersSettings, error) { + options := &RoutingFindProvidersSettings{ + NumProviders: 20, + } + + for _, opt := range opts { + err := opt(options) + if err != nil { + return nil, err + } + } + return options, nil +} + +type routingOpts struct{} + +var Routing routingOpts + +// Recursive is an option for [Routing.Provide] which specifies whether to provide +// the given path recursively. +func (routingOpts) Recursive(recursive bool) RoutingProvideOption { + return func(settings *DhtProvideSettings) error { + settings.Recursive = recursive + return nil + } +} + +// NumProviders is an option for [Routing.FindProviders] which specifies the +// number of peers to look for. Default is 20. +func (routingOpts) NumProviders(numProviders int) RoutingFindProvidersOption { + return func(settings *DhtFindProvidersSettings) error { + settings.NumProviders = numProviders + return nil + } +} -// AllowOffline is an option for Routing.Put which specifies whether to allow +// AllowOffline is an option for [Routing.Put] which specifies whether to allow // publishing when the node is offline. Default value is false -func (putOpts) AllowOffline(allow bool) RoutingPutOption { +func (routingOpts) AllowOffline(allow bool) RoutingPutOption { return func(settings *RoutingPutSettings) error { settings.AllowOffline = allow return nil diff --git a/core/coreiface/routing.go b/core/coreiface/routing.go index c64e7baef9ca..a17dfcad920c 100644 --- a/core/coreiface/routing.go +++ b/core/coreiface/routing.go @@ -3,7 +3,9 @@ package iface import ( "context" + "github.com/ipfs/boxo/path" "github.com/ipfs/kubo/core/coreiface/options" + "github.com/libp2p/go-libp2p/core/peer" ) // RoutingAPI specifies the interface to the routing layer. @@ -13,4 +15,15 @@ type RoutingAPI interface { // Put sets a value for a given key Put(ctx context.Context, key string, value []byte, opts ...options.RoutingPutOption) error + + // FindPeer queries the routing system for all the multiaddresses associated + // with the given [peer.ID]. + FindPeer(context.Context, peer.ID) (peer.AddrInfo, error) + + // FindProviders finds the peers in the routing system who can provide a specific + // value given a key. + FindProviders(context.Context, path.Path, ...options.RoutingFindProvidersOption) (<-chan peer.AddrInfo, error) + + // Provide announces to the network that you are providing given values + Provide(context.Context, path.Path, ...options.RoutingProvideOption) error } diff --git a/core/coreiface/tests/api.go b/core/coreiface/tests/api.go index c1fcb672df10..86ab60ae910a 100644 --- a/core/coreiface/tests/api.go +++ b/core/coreiface/tests/api.go @@ -75,7 +75,6 @@ func TestApi(p Provider) func(t *testing.T) { return func(t *testing.T) { t.Run("Block", tp.TestBlock) t.Run("Dag", tp.TestDag) - t.Run("Dht", tp.TestDht) t.Run("Key", tp.TestKey) t.Run("Name", tp.TestName) t.Run("Object", tp.TestObject) diff --git a/core/coreiface/tests/dht.go b/core/coreiface/tests/dht.go deleted file mode 100644 index 6a908c5d3317..000000000000 --- a/core/coreiface/tests/dht.go +++ /dev/null @@ -1,166 +0,0 @@ -package tests - -import ( - "context" - "io" - "testing" - "time" - - iface "github.com/ipfs/kubo/core/coreiface" - "github.com/ipfs/kubo/core/coreiface/options" -) - -func (tp *TestSuite) TestDht(t *testing.T) { - tp.hasApi(t, func(api iface.CoreAPI) error { - if api.Dht() == nil { - return errAPINotImplemented - } - return nil - }) - - t.Run("TestDhtFindPeer", tp.TestDhtFindPeer) - t.Run("TestDhtFindProviders", tp.TestDhtFindProviders) - t.Run("TestDhtProvide", tp.TestDhtProvide) -} - -func (tp *TestSuite) TestDhtFindPeer(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - apis, err := tp.MakeAPISwarm(t, ctx, 5) - if err != nil { - t.Fatal(err) - } - - self0, err := apis[0].Key().Self(ctx) - if err != nil { - t.Fatal(err) - } - - laddrs0, err := apis[0].Swarm().LocalAddrs(ctx) - if err != nil { - t.Fatal(err) - } - if len(laddrs0) != 1 { - t.Fatal("unexpected number of local addrs") - } - - time.Sleep(3 * time.Second) - - pi, err := apis[2].Dht().FindPeer(ctx, self0.ID()) - if err != nil { - t.Fatal(err) - } - - if pi.Addrs[0].String() != laddrs0[0].String() { - t.Errorf("got unexpected address from FindPeer: %s", pi.Addrs[0].String()) - } - - self2, err := apis[2].Key().Self(ctx) - if err != nil { - t.Fatal(err) - } - - pi, err = apis[1].Dht().FindPeer(ctx, self2.ID()) - if err != nil { - t.Fatal(err) - } - - laddrs2, err := apis[2].Swarm().LocalAddrs(ctx) - if err != nil { - t.Fatal(err) - } - if len(laddrs2) != 1 { - t.Fatal("unexpected number of local addrs") - } - - if pi.Addrs[0].String() != laddrs2[0].String() { - t.Errorf("got unexpected address from FindPeer: %s", pi.Addrs[0].String()) - } -} - -func (tp *TestSuite) TestDhtFindProviders(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - apis, err := tp.MakeAPISwarm(t, ctx, 5) - if err != nil { - t.Fatal(err) - } - - p, err := addTestObject(ctx, apis[0]) - if err != nil { - t.Fatal(err) - } - - time.Sleep(3 * time.Second) - - out, err := apis[2].Dht().FindProviders(ctx, p, options.Dht.NumProviders(1)) - if err != nil { - t.Fatal(err) - } - - provider := <-out - - self0, err := apis[0].Key().Self(ctx) - if err != nil { - t.Fatal(err) - } - - if provider.ID.String() != self0.ID().String() { - t.Errorf("got wrong provider: %s != %s", provider.ID.String(), self0.ID().String()) - } -} - -func (tp *TestSuite) TestDhtProvide(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - apis, err := tp.MakeAPISwarm(t, ctx, 5) - if err != nil { - t.Fatal(err) - } - - off0, err := apis[0].WithOptions(options.Api.Offline(true)) - if err != nil { - t.Fatal(err) - } - - s, err := off0.Block().Put(ctx, &io.LimitedReader{R: rnd, N: 4092}) - if err != nil { - t.Fatal(err) - } - - p := s.Path() - - time.Sleep(3 * time.Second) - - out, err := apis[2].Dht().FindProviders(ctx, p, options.Dht.NumProviders(1)) - if err != nil { - t.Fatal(err) - } - - _, ok := <-out - - if ok { - t.Fatal("did not expect to find any providers") - } - - self0, err := apis[0].Key().Self(ctx) - if err != nil { - t.Fatal(err) - } - - err = apis[0].Dht().Provide(ctx, p) - if err != nil { - t.Fatal(err) - } - - out, err = apis[2].Dht().FindProviders(ctx, p, options.Dht.NumProviders(1)) - if err != nil { - t.Fatal(err) - } - - provider := <-out - - if provider.ID.String() != self0.ID().String() { - t.Errorf("got wrong provider: %s != %s", provider.ID.String(), self0.ID().String()) - } -} diff --git a/core/coreiface/tests/routing.go b/core/coreiface/tests/routing.go index 3f1f95d75c71..753d49550e73 100644 --- a/core/coreiface/tests/routing.go +++ b/core/coreiface/tests/routing.go @@ -2,6 +2,7 @@ package tests import ( "context" + "io" "testing" "time" @@ -23,6 +24,9 @@ func (tp *TestSuite) TestRouting(t *testing.T) { t.Run("TestRoutingGet", tp.TestRoutingGet) t.Run("TestRoutingPut", tp.TestRoutingPut) t.Run("TestRoutingPutOffline", tp.TestRoutingPutOffline) + t.Run("TestRoutingFindPeer", tp.TestRoutingFindPeer) + t.Run("TestRoutingFindProviders", tp.TestRoutingFindProviders) + t.Run("TestRoutingProvide", tp.TestRoutingProvide) } func (tp *TestSuite) testRoutingPublishKey(t *testing.T, ctx context.Context, api iface.CoreAPI, opts ...options.NamePublishOption) (path.Path, ipns.Name) { @@ -95,6 +99,148 @@ func (tp *TestSuite) TestRoutingPutOffline(t *testing.T) { err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data) require.Error(t, err, "this operation should fail because we are offline") - err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data, options.Put.AllowOffline(true)) + err = api.Routing().Put(ctx, ipns.NamespacePrefix+name.String(), data, options.Routing.AllowOffline(true)) require.NoError(t, err) } + +func (tp *TestSuite) TestRoutingFindPeer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + apis, err := tp.MakeAPISwarm(t, ctx, 5) + if err != nil { + t.Fatal(err) + } + + self0, err := apis[0].Key().Self(ctx) + if err != nil { + t.Fatal(err) + } + + laddrs0, err := apis[0].Swarm().LocalAddrs(ctx) + if err != nil { + t.Fatal(err) + } + if len(laddrs0) != 1 { + t.Fatal("unexpected number of local addrs") + } + + time.Sleep(3 * time.Second) + + pi, err := apis[2].Routing().FindPeer(ctx, self0.ID()) + if err != nil { + t.Fatal(err) + } + + if pi.Addrs[0].String() != laddrs0[0].String() { + t.Errorf("got unexpected address from FindPeer: %s", pi.Addrs[0].String()) + } + + self2, err := apis[2].Key().Self(ctx) + if err != nil { + t.Fatal(err) + } + + pi, err = apis[1].Routing().FindPeer(ctx, self2.ID()) + if err != nil { + t.Fatal(err) + } + + laddrs2, err := apis[2].Swarm().LocalAddrs(ctx) + if err != nil { + t.Fatal(err) + } + if len(laddrs2) != 1 { + t.Fatal("unexpected number of local addrs") + } + + if pi.Addrs[0].String() != laddrs2[0].String() { + t.Errorf("got unexpected address from FindPeer: %s", pi.Addrs[0].String()) + } +} + +func (tp *TestSuite) TestRoutingFindProviders(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + apis, err := tp.MakeAPISwarm(t, ctx, 5) + if err != nil { + t.Fatal(err) + } + + p, err := addTestObject(ctx, apis[0]) + if err != nil { + t.Fatal(err) + } + + time.Sleep(3 * time.Second) + + out, err := apis[2].Routing().FindProviders(ctx, p, options.Routing.NumProviders(1)) + if err != nil { + t.Fatal(err) + } + + provider := <-out + + self0, err := apis[0].Key().Self(ctx) + if err != nil { + t.Fatal(err) + } + + if provider.ID.String() != self0.ID().String() { + t.Errorf("got wrong provider: %s != %s", provider.ID.String(), self0.ID().String()) + } +} + +func (tp *TestSuite) TestRoutingProvide(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + apis, err := tp.MakeAPISwarm(t, ctx, 5) + if err != nil { + t.Fatal(err) + } + + off0, err := apis[0].WithOptions(options.Api.Offline(true)) + if err != nil { + t.Fatal(err) + } + + s, err := off0.Block().Put(ctx, &io.LimitedReader{R: rnd, N: 4092}) + if err != nil { + t.Fatal(err) + } + + p := s.Path() + + time.Sleep(3 * time.Second) + + out, err := apis[2].Routing().FindProviders(ctx, p, options.Routing.NumProviders(1)) + if err != nil { + t.Fatal(err) + } + + _, ok := <-out + + if ok { + t.Fatal("did not expect to find any providers") + } + + self0, err := apis[0].Key().Self(ctx) + if err != nil { + t.Fatal(err) + } + + err = apis[0].Routing().Provide(ctx, p) + if err != nil { + t.Fatal(err) + } + + out, err = apis[2].Routing().FindProviders(ctx, p, options.Routing.NumProviders(1)) + if err != nil { + t.Fatal(err) + } + + provider := <-out + + if provider.ID.String() != self0.ID().String() { + t.Errorf("got wrong provider: %s != %s", provider.ID.String(), self0.ID().String()) + } +} diff --git a/docs/changelogs/v0.27.md b/docs/changelogs/v0.27.md index 26a607f8abbc..6455822e1c10 100644 --- a/docs/changelogs/v0.27.md +++ b/docs/changelogs/v0.27.md @@ -7,6 +7,8 @@ - [Overview](#overview) - [๐Ÿ”ฆ Highlights](#-highlights) - [Gateway: support for `/api/v0` is deprecated](#gateway-support-for-apiv0-is-deprecated) + - [IPNS resolver cache's TTL can now be configured](#ipns-resolver-caches-ttl-can-now-be-configured) + - [RPC client: deprecated DHT API, added Routing API](#rpc-client-deprecated-dht-api-added-routing-api) - [๐Ÿ“ Changelog](#-changelog) - [๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors](#-contributors) @@ -24,6 +26,12 @@ If you have a legacy software that relies on this behavior, and want to expose p You can now configure the upper-bound of a cached IPNS entry's Time-To-Live via [`Ipns.MaxCacheTTL`](https://github.com/ipfs/kubo/blob/master/docs/config.md#ipnsmaxcachettl). +#### RPC client: deprecated DHT API, added Routing API + +The RPC client now includes a Routing API to match the available commands in `/api/v0/routing`. In addition, the DHT API has been marked as deprecated. + +In the next version, all DHT deprecated methods will be removed from the Go RPC client. + ### ๐Ÿ“ Changelog ### ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors