1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-05-17 23:16:11 +08:00

feat(gateway): expose /routing/v1 server (opt-in) (#9877)

This commit is contained in:
Henrique Dias
2023-08-25 17:30:04 +02:00
committed by GitHub
parent 86bde2894f
commit 4606586207
18 changed files with 505 additions and 56 deletions

View File

@ -832,6 +832,12 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
fmt.Printf("Gateway server listening on %s\n", listener.Multiaddr())
}
if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
for _, listener := range listeners {
fmt.Printf("Routing V1 API exposed at http://%s/routing/v1\n", listener.Addr())
}
}
cmdctx := *cctx
cmdctx.Gateway = true
@ -848,6 +854,10 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
opts = append(opts, corehttp.P2PProxyOption())
}
if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
opts = append(opts, corehttp.RoutingOption())
}
if len(cfg.Gateway.RootRedirect) > 0 {
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
}

View File

@ -3,6 +3,7 @@ package config
const (
DefaultInlineDNSLink = false
DefaultDeserializedResponses = true
DefaultExposeRoutingAPI = false
)
type GatewaySpec struct {
@ -72,4 +73,8 @@ type Gateway struct {
// PublicGateways configures behavior of known public gateways.
// Each key is a fully qualified domain name (FQDN).
PublicGateways map[string]*GatewaySpec
// ExposeRoutingAPI configures the gateway port to expose
// routing system as HTTP API at /routing/v1 (https://specs.ipfs.tech/routing/http-routing-v1/).
ExposeRoutingAPI Flag
}

129
core/corehttp/routing.go Normal file
View File

@ -0,0 +1,129 @@
package corehttp
import (
"context"
"net"
"net/http"
"time"
"github.com/ipfs/boxo/ipns"
"github.com/ipfs/boxo/routing/http/server"
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
cid "github.com/ipfs/go-cid"
core "github.com/ipfs/kubo/core"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
)
func RoutingOption() ServeOption {
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
handler := server.Handler(&contentRouter{n})
mux.Handle("/routing/v1/", handler)
return mux, nil
}
}
type contentRouter struct {
n *core.IpfsNode
}
func (r *contentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) {
ctx, cancel := context.WithCancel(ctx)
ch := r.n.Routing.FindProvidersAsync(ctx, key, limit)
return iter.ToResultIter[types.Record](&peerChanIter{
ch: ch,
cancel: cancel,
}), nil
}
// nolint deprecated
func (r *contentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
return 0, routing.ErrNotSupported
}
func (r *contentRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[types.Record], error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
addr, err := r.n.Routing.FindPeer(ctx, pid)
if err != nil {
return nil, err
}
rec := &types.PeerRecord{
Schema: types.SchemaPeer,
ID: &addr.ID,
}
for _, addr := range addr.Addrs {
rec.Addrs = append(rec.Addrs, types.Multiaddr{Multiaddr: addr})
}
return iter.ToResultIter[types.Record](iter.FromSlice[types.Record]([]types.Record{rec})), nil
}
func (r *contentRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
raw, err := r.n.Routing.GetValue(ctx, string(name.RoutingKey()))
if err != nil {
return nil, err
}
return ipns.UnmarshalRecord(raw)
}
func (r *contentRouter) PutIPNS(ctx context.Context, name ipns.Name, record *ipns.Record) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
raw, err := ipns.MarshalRecord(record)
if err != nil {
return err
}
// The caller guarantees that name matches the record. This is double checked
// by the internals of PutValue.
return r.n.Routing.PutValue(ctx, string(name.RoutingKey()), raw)
}
type peerChanIter struct {
ch <-chan peer.AddrInfo
cancel context.CancelFunc
next *peer.AddrInfo
}
func (it *peerChanIter) Next() bool {
addr, ok := <-it.ch
if ok {
it.next = &addr
return true
} else {
it.next = nil
return false
}
}
func (it *peerChanIter) Val() types.Record {
if it.next == nil {
return nil
}
rec := &types.PeerRecord{
Schema: types.SchemaPeer,
ID: &it.next.ID,
}
for _, addr := range it.next.Addrs {
rec.Addrs = append(rec.Addrs, types.Multiaddr{Multiaddr: addr})
}
return rec
}
func (it *peerChanIter) Close() error {
it.cancel()
return nil
}

View File

@ -139,7 +139,8 @@ func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, p
PeerID: peerID,
Addrs: httpAddrsFromConfig(addrs),
PrivKeyB64: privKey,
})
},
)
}
}

View File

@ -9,6 +9,7 @@
- [Mplex deprecation](#mplex-deprecation)
- [Gateway: meaningful CAR responses on Not Found errors](#gateway-meaningful-car-responses-on-not-found-errors)
- [Binary characters in file names: no longer works with old clients and new Kubo servers](#binary-characters-in-file-names-no-longer-works-with-old-clients-and-new-kubo-servers)
- [Self-hosting `/routing/v1` endpoint for delegated routing needs](#self-hosting-routingv1-endpoint-for-delegated-routing-needs)
- [📝 Changelog](#-changelog)
- [👨‍👩‍👧‍👦 Contributors](#-contributors)
@ -59,6 +60,13 @@ the compatibility table:
*Old clients can only send Unicode file paths to the server.
#### Self-hosting `/routing/v1` endpoint for delegated routing needs
The `Routing` system configured in Kubo can be now exposed on the gateway port as a standard
HTTP [Routing V1](https://specs.ipfs.tech/routing/http-routing-v1/) API endpoint. This allows
self-hosting and experimentation with custom delegated routers. This is disabled by default,
but can be enabled by setting [`Gateway.ExposeRoutingAPI`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayexposeroutingapi) to `true` .
### 📝 Changelog
### 👨‍👩‍👧‍👦 Contributors

View File

@ -658,6 +658,16 @@ Default: `true`
Type: `flag`
#### `Gateway.ExposeRoutingAPI`
An optional flag to expose Kubo `Routing` system on the gateway port as a [Routing
V1](https://specs.ipfs.tech/routing/routing-v1/) endpoint. This only affects your
local gateway, at `127.0.0.1`.
Default: `false`
Type: `flag`
### `Gateway.HTTPHeaders`
Headers to set on gateway responses.

View File

@ -7,7 +7,7 @@ go 1.20
replace github.com/ipfs/kubo => ./../../..
require (
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
github.com/libp2p/go-libp2p v0.30.0
github.com/multiformats/go-multiaddr v0.11.0
@ -52,7 +52,6 @@ require (
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e // indirect

View File

@ -270,7 +270,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@ -301,8 +300,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=

2
go.mod
View File

@ -15,7 +15,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
github.com/ipfs/go-block-format v0.1.2
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-cidutil v0.1.0

4
go.sum
View File

@ -335,8 +335,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=

View File

@ -224,6 +224,8 @@ func httpRoutingFromConfig(conf config.Router, extraHTTP *ExtraHTTPParams) (rout
return &httpRoutingWrapper{
ContentRouting: cr,
PeerRouting: cr,
ValueStore: cr,
ProvideManyRouter: cr,
}, nil
}

View File

@ -4,7 +4,6 @@ import (
"context"
routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
)
@ -22,27 +21,11 @@ var (
// http delegated routing.
type httpRoutingWrapper struct {
routing.ContentRouting
routing.PeerRouting
routing.ValueStore
routinghelpers.ProvideManyRouter
}
func (c *httpRoutingWrapper) Bootstrap(ctx context.Context) error {
return nil
}
func (c *httpRoutingWrapper) FindPeer(ctx context.Context, id peer.ID) (peer.AddrInfo, error) {
return peer.AddrInfo{}, routing.ErrNotSupported
}
func (c *httpRoutingWrapper) PutValue(context.Context, string, []byte, ...routing.Option) error {
return routing.ErrNotSupported
}
func (c *httpRoutingWrapper) GetValue(context.Context, string, ...routing.Option) ([]byte, error) {
return nil, routing.ErrNotSupported
}
func (c *httpRoutingWrapper) SearchValue(context.Context, string, ...routing.Option) (<-chan []byte, error) {
out := make(chan []byte)
close(out)
return out, routing.ErrNotSupported
}

View File

@ -16,42 +16,45 @@ import (
"github.com/ipfs/go-cid"
"github.com/ipfs/kubo/test/cli/harness"
"github.com/ipfs/kubo/test/cli/testutils"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
"github.com/stretchr/testify/assert"
)
type fakeHTTPContentRouter struct {
m sync.Mutex
provideBitswapCalls int
findProvidersCalls int
provideCalls int
findPeersCalls int
}
func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.ProviderResponse], error) {
func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) {
r.m.Lock()
defer r.m.Unlock()
r.findProvidersCalls++
return iter.FromSlice([]iter.Result[types.ProviderResponse]{}), nil
return iter.FromSlice([]iter.Result[types.Record]{}), nil
}
// nolint deprecated
func (r *fakeHTTPContentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
r.m.Lock()
defer r.m.Unlock()
r.provideCalls++
r.provideBitswapCalls++
return 0, nil
}
func (r *fakeHTTPContentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) {
func (r *fakeHTTPContentRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[types.Record], error) {
r.m.Lock()
defer r.m.Unlock()
r.provideCalls++
return nil, nil
r.findPeersCalls++
return iter.FromSlice([]iter.Result[types.Record]{}), nil
}
func (r *fakeHTTPContentRouter) FindIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
func (r *fakeHTTPContentRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
return nil, routing.ErrNotSupported
}
func (r *fakeHTTPContentRouter) ProvideIPNSRecord(ctx context.Context, name ipns.Name, rec *ipns.Record) error {
func (r *fakeHTTPContentRouter) PutIPNS(ctx context.Context, name ipns.Name, rec *ipns.Record) error {
return routing.ErrNotSupported
}

View File

@ -88,12 +88,20 @@ func TestHTTPDelegatedRouting(t *testing.T) {
t.Run("adding HTTP delegated routing endpoint to Routing.Routers config works", func(t *testing.T) {
server := fakeServer("application/json", ToJSONStr(JSONObj{
"Providers": []JSONObj{{
"Providers": []JSONObj{
{
"Schema": "bitswap", // Legacy bitswap schema.
"Protocol": "transport-bitswap",
"Schema": "bitswap",
"ID": provs[1],
"Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
},
{
"Schema": "peer",
"Protocols": []string{"transport-bitswap"},
"ID": provs[0],
"Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
}},
},
},
}))
t.Cleanup(server.Close)
@ -117,21 +125,21 @@ func TestHTTPDelegatedRouting(t *testing.T) {
node.StartDaemon()
res = node.IPFS("routing", "findprovs", findProvsCID)
assert.Equal(t, provs[0], res.Stdout.Trimmed())
assert.Equal(t, provs[1]+"\n"+provs[0], res.Stdout.Trimmed())
})
node.StopDaemon()
t.Run("adding HTTP delegated routing endpoint to Routing.Routers config works (streaming)", func(t *testing.T) {
server := fakeServer("application/x-ndjson", ToJSONStr(JSONObj{
"Protocol": "transport-bitswap",
"Schema": "bitswap",
"ID": provs[1],
"Schema": "peer",
"Protocols": []string{"transport-bitswap"},
"ID": provs[0],
"Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
}), ToJSONStr(JSONObj{
"Schema": "bitswap", // Legacy bitswap schema.
"Protocol": "transport-bitswap",
"Schema": "bitswap",
"ID": provs[0],
"ID": provs[1],
"Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
}))
t.Cleanup(server.Close)
@ -148,7 +156,7 @@ func TestHTTPDelegatedRouting(t *testing.T) {
node.StartDaemon()
res = node.IPFS("routing", "findprovs", findProvsCID)
assert.Equal(t, provs[1]+"\n"+provs[0], res.Stdout.Trimmed())
assert.Equal(t, provs[0]+"\n"+provs[1], res.Stdout.Trimmed())
})
t.Run("HTTP client should emit OpenCensus metrics", func(t *testing.T) {

View File

@ -0,0 +1,147 @@
package cli
import (
"testing"
"github.com/ipfs/boxo/ipns"
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/test/cli/harness"
"github.com/ipfs/kubo/test/cli/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRoutingV1Proxy(t *testing.T) {
t.Parallel()
setupNodes := func(t *testing.T) harness.Nodes {
nodes := harness.NewT(t).NewNodes(2).Init()
// Node 0 uses DHT and exposes the Routing API.
nodes[0].UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.ExposeRoutingAPI = config.True
cfg.Discovery.MDNS.Enabled = false
cfg.Routing.Type = config.NewOptionalString("dht")
})
nodes[0].StartDaemon()
// Node 1 uses Node 0 as Routing V1 source, no DHT.
nodes[1].UpdateConfig(func(cfg *config.Config) {
cfg.Discovery.MDNS.Enabled = false
cfg.Routing.Type = config.NewOptionalString("custom")
cfg.Routing.Methods = config.Methods{
config.MethodNameFindPeers: {RouterName: "KuboA"},
config.MethodNameFindProviders: {RouterName: "KuboA"},
config.MethodNameGetIPNS: {RouterName: "KuboA"},
config.MethodNamePutIPNS: {RouterName: "KuboA"},
config.MethodNameProvide: {RouterName: "KuboA"},
}
cfg.Routing.Routers = config.Routers{
"KuboA": config.RouterParser{
Router: config.Router{
Type: config.RouterTypeHTTP,
Parameters: &config.HTTPRouterParams{
Endpoint: nodes[0].GatewayURL(),
},
},
},
}
})
nodes[1].StartDaemon()
// Connect them.
nodes.Connect()
return nodes
}
t.Run("Kubo can find provider for CID via Routing V1", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
cidStr := nodes[0].IPFSAddStr(testutils.RandomStr(1000))
res := nodes[1].IPFS("routing", "findprovs", cidStr)
assert.Equal(t, nodes[0].PeerID().String(), res.Stdout.Trimmed())
})
t.Run("Kubo can find peer via Routing V1", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
// Start lonely node that is not connected to other nodes.
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Discovery.MDNS.Enabled = false
cfg.Routing.Type = config.NewOptionalString("dht")
})
node.StartDaemon()
// Connect Node 0 to Lonely Node.
nodes[0].Connect(node)
// Node 1 must find Lonely Node through Node 0 Routing V1.
res := nodes[1].IPFS("routing", "findpeer", node.PeerID().String())
assert.Equal(t, node.SwarmAddrs()[0].String(), res.Stdout.Trimmed())
})
t.Run("Kubo can retrieve IPNS record via Routing V1", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
nodeName := "/ipns/" + ipns.NameFromPeer(nodes[0].PeerID()).String()
// Can't resolve the name as isn't published yet.
res := nodes[1].RunIPFS("routing", "get", nodeName)
require.Error(t, res.ExitErr)
// Publish record on Node 0.
path := "/ipfs/" + nodes[0].IPFSAddStr(testutils.RandomStr(1000))
nodes[0].IPFS("name", "publish", "--allow-offline", path)
// Get record on Node 1 (no DHT).
res = nodes[1].IPFS("routing", "get", nodeName)
record, err := ipns.UnmarshalRecord(res.Stdout.Bytes())
require.NoError(t, err)
value, err := record.Value()
require.NoError(t, err)
require.Equal(t, path, value.String())
})
t.Run("Kubo can resolve IPNS name via Routing V1", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
nodeName := "/ipns/" + ipns.NameFromPeer(nodes[0].PeerID()).String()
// Can't resolve the name as isn't published yet.
res := nodes[1].RunIPFS("routing", "get", nodeName)
require.Error(t, res.ExitErr)
// Publish name.
path := "/ipfs/" + nodes[0].IPFSAddStr(testutils.RandomStr(1000))
nodes[0].IPFS("name", "publish", "--allow-offline", path)
// Resolve IPNS name
res = nodes[1].IPFS("name", "resolve", nodeName)
require.Equal(t, path, res.Stdout.Trimmed())
})
t.Run("Kubo can provide IPNS record via Routing V1", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
// Publish something on Node 1 (no DHT).
nodeName := "/ipns/" + ipns.NameFromPeer(nodes[1].PeerID()).String()
path := "/ipfs/" + nodes[1].IPFSAddStr(testutils.RandomStr(1000))
nodes[1].IPFS("name", "publish", "--allow-offline", path)
// Retrieve through Node 0.
res := nodes[0].IPFS("routing", "get", nodeName)
record, err := ipns.UnmarshalRecord(res.Stdout.Bytes())
require.NoError(t, err)
value, err := record.Value()
require.NoError(t, err)
require.Equal(t, path, value.String())
})
}

View File

@ -0,0 +1,145 @@
package cli
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/ipfs/boxo/ipns"
"github.com/ipfs/boxo/routing/http/client"
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
"github.com/ipfs/go-cid"
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/test/cli/harness"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/assert"
)
func TestRoutingV1Server(t *testing.T) {
t.Parallel()
setupNodes := func(t *testing.T) harness.Nodes {
nodes := harness.NewT(t).NewNodes(5).Init()
nodes.ForEachPar(func(node *harness.Node) {
node.UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.ExposeRoutingAPI = config.True
cfg.Routing.Type = config.NewOptionalString("dht")
})
})
nodes.StartDaemons().Connect()
return nodes
}
t.Run("Get Providers Responds With Correct Peers", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
text := "hello world " + uuid.New().String()
cidStr := nodes[2].IPFSAddStr(text)
_ = nodes[3].IPFSAddStr(text)
cid, err := cid.Decode(cidStr)
assert.NoError(t, err)
c, err := client.New(nodes[1].GatewayURL())
assert.NoError(t, err)
resultsIter, err := c.FindProviders(context.Background(), cid)
assert.NoError(t, err)
records, err := iter.ReadAllResults(resultsIter)
assert.NoError(t, err)
var peers []peer.ID
for _, record := range records {
assert.Equal(t, types.SchemaPeer, record.GetSchema())
peer, ok := record.(*types.PeerRecord)
assert.True(t, ok)
peers = append(peers, *peer.ID)
}
assert.Contains(t, peers, nodes[2].PeerID())
assert.Contains(t, peers, nodes[3].PeerID())
})
t.Run("Get Peers Responds With Correct Peers", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
c, err := client.New(nodes[1].GatewayURL())
assert.NoError(t, err)
resultsIter, err := c.FindPeers(context.Background(), nodes[2].PeerID())
assert.NoError(t, err)
records, err := iter.ReadAllResults(resultsIter)
assert.NoError(t, err)
assert.Len(t, records, 1)
assert.IsType(t, records[0].GetSchema(), records[0].GetSchema())
assert.IsType(t, records[0], &types.PeerRecord{})
peer := records[0].(*types.PeerRecord)
assert.Equal(t, nodes[2].PeerID().String(), peer.ID.String())
assert.NotEmpty(t, peer.Addrs)
})
t.Run("Get IPNS Record Responds With Correct Record", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
text := "hello ipns test " + uuid.New().String()
cidStr := nodes[0].IPFSAddStr(text)
nodes[0].IPFS("name", "publish", "--allow-offline", cidStr)
// Ask for record from a different peer.
c, err := client.New(nodes[1].GatewayURL())
assert.NoError(t, err)
record, err := c.GetIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()))
assert.NoError(t, err)
value, err := record.Value()
assert.NoError(t, err)
assert.Equal(t, "/ipfs/"+cidStr, value.String())
})
t.Run("Put IPNS Record Succeeds", func(t *testing.T) {
t.Parallel()
nodes := setupNodes(t)
// Publish a record and confirm the /routing/v1/ipns API exposes the IPNS record
text := "hello ipns test " + uuid.New().String()
cidStr := nodes[0].IPFSAddStr(text)
nodes[0].IPFS("name", "publish", "--allow-offline", cidStr)
c, err := client.New(nodes[0].GatewayURL())
assert.NoError(t, err)
record, err := c.GetIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()))
assert.NoError(t, err)
value, err := record.Value()
assert.NoError(t, err)
assert.Equal(t, "/ipfs/"+cidStr, value.String())
// Start lonely node that is not connected to other nodes.
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.ExposeRoutingAPI = config.True
cfg.Routing.Type = config.NewOptionalString("dht")
})
node.StartDaemon()
// Put IPNS record in lonely node. It should be accepted as it is a valid record.
c, err = client.New(node.GatewayURL())
assert.NoError(t, err)
err = c.PutIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()), record)
assert.NoError(t, err)
// Get the record from lonely node and double check.
record, err = c.GetIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()))
assert.NoError(t, err)
value, err = record.Value()
assert.NoError(t, err)
assert.Equal(t, "/ipfs/"+cidStr, value.String())
})
}

View File

@ -7,7 +7,7 @@ replace github.com/ipfs/kubo => ../../
require (
github.com/Kubuxu/gocovmerge v0.0.0-20161216165753-7ecaa51963cd
github.com/golangci/golangci-lint v1.54.1
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-cidutil v0.1.0
github.com/ipfs/go-datastore v0.6.0

View File

@ -396,8 +396,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo=