@cryptotaxi247 / kubo / commits / 460658620

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

Henrique Dias committed Aug 25, 2023 at 17:30 UTC 460658620792eb433331142b1435dad89630b15a
18 files changed +505 -56
cmd/ipfs/daemon.go
+10
@@ -832,6 +832,12 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
832 fmt.Printf("Gateway server listening on %s\n", listener.Multiaddr())
833 }
834
835 + if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
836 + for _, listener := range listeners {
837 + fmt.Printf("Routing V1 API exposed at http://%s/routing/v1\n", listener.Addr())
838 + }
839 + }
840 +
841 cmdctx := *cctx
842 cmdctx.Gateway = true
843
@@ -848,6 +854,10 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
854 opts = append(opts, corehttp.P2PProxyOption())
855 }
856
857 + if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
858 + opts = append(opts, corehttp.RoutingOption())
859 + }
860 +
861 if len(cfg.Gateway.RootRedirect) > 0 {
862 opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
863 }
config/gateway.go
+5
@@ -3,6 +3,7 @@ package config
3 const (
4 DefaultInlineDNSLink = false
5 DefaultDeserializedResponses = true
6 + DefaultExposeRoutingAPI = false
7 )
8
9 type GatewaySpec struct {
@@ -72,4 +73,8 @@ type Gateway struct {
73 // PublicGateways configures behavior of known public gateways.
74 // Each key is a fully qualified domain name (FQDN).
75 PublicGateways map[string]*GatewaySpec
76 +
77 + // ExposeRoutingAPI configures the gateway port to expose
78 + // routing system as HTTP API at /routing/v1 (https://specs.ipfs.tech/routing/http-routing-v1/).
79 + ExposeRoutingAPI Flag
80 }
core/corehttp/routing.go new
+129
@@ -0,0 +1,129 @@
1 +package corehttp
2 +
3 +import (
4 + "context"
5 + "net"
6 + "net/http"
7 + "time"
8 +
9 + "github.com/ipfs/boxo/ipns"
10 + "github.com/ipfs/boxo/routing/http/server"
11 + "github.com/ipfs/boxo/routing/http/types"
12 + "github.com/ipfs/boxo/routing/http/types/iter"
13 + cid "github.com/ipfs/go-cid"
14 + core "github.com/ipfs/kubo/core"
15 + "github.com/libp2p/go-libp2p/core/peer"
16 + "github.com/libp2p/go-libp2p/core/routing"
17 +)
18 +
19 +func RoutingOption() ServeOption {
20 + return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
21 + handler := server.Handler(&contentRouter{n})
22 + mux.Handle("/routing/v1/", handler)
23 + return mux, nil
24 + }
25 +}
26 +
27 +type contentRouter struct {
28 + n *core.IpfsNode
29 +}
30 +
31 +func (r *contentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) {
32 + ctx, cancel := context.WithCancel(ctx)
33 + ch := r.n.Routing.FindProvidersAsync(ctx, key, limit)
34 + return iter.ToResultIter[types.Record](&peerChanIter{
35 + ch: ch,
36 + cancel: cancel,
37 + }), nil
38 +}
39 +
40 +// nolint deprecated
41 +func (r *contentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
42 + return 0, routing.ErrNotSupported
43 +}
44 +
45 +func (r *contentRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[types.Record], error) {
46 + ctx, cancel := context.WithCancel(ctx)
47 + defer cancel()
48 +
49 + addr, err := r.n.Routing.FindPeer(ctx, pid)
50 + if err != nil {
51 + return nil, err
52 + }
53 +
54 + rec := &types.PeerRecord{
55 + Schema: types.SchemaPeer,
56 + ID: &addr.ID,
57 + }
58 +
59 + for _, addr := range addr.Addrs {
60 + rec.Addrs = append(rec.Addrs, types.Multiaddr{Multiaddr: addr})
61 + }
62 +
63 + return iter.ToResultIter[types.Record](iter.FromSlice[types.Record]([]types.Record{rec})), nil
64 +}
65 +
66 +func (r *contentRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
67 + ctx, cancel := context.WithCancel(ctx)
68 + defer cancel()
69 +
70 + raw, err := r.n.Routing.GetValue(ctx, string(name.RoutingKey()))
71 + if err != nil {
72 + return nil, err
73 + }
74 +
75 + return ipns.UnmarshalRecord(raw)
76 +}
77 +
78 +func (r *contentRouter) PutIPNS(ctx context.Context, name ipns.Name, record *ipns.Record) error {
79 + ctx, cancel := context.WithCancel(ctx)
80 + defer cancel()
81 +
82 + raw, err := ipns.MarshalRecord(record)
83 + if err != nil {
84 + return err
85 + }
86 +
87 + // The caller guarantees that name matches the record. This is double checked
88 + // by the internals of PutValue.
89 + return r.n.Routing.PutValue(ctx, string(name.RoutingKey()), raw)
90 +}
91 +
92 +type peerChanIter struct {
93 + ch <-chan peer.AddrInfo
94 + cancel context.CancelFunc
95 + next *peer.AddrInfo
96 +}
97 +
98 +func (it *peerChanIter) Next() bool {
99 + addr, ok := <-it.ch
100 + if ok {
101 + it.next = &addr
102 + return true
103 + } else {
104 + it.next = nil
105 + return false
106 + }
107 +}
108 +
109 +func (it *peerChanIter) Val() types.Record {
110 + if it.next == nil {
111 + return nil
112 + }
113 +
114 + rec := &types.PeerRecord{
115 + Schema: types.SchemaPeer,
116 + ID: &it.next.ID,
117 + }
118 +
119 + for _, addr := range it.next.Addrs {
120 + rec.Addrs = append(rec.Addrs, types.Multiaddr{Multiaddr: addr})
121 + }
122 +
123 + return rec
124 +}
125 +
126 +func (it *peerChanIter) Close() error {
127 + it.cancel()
128 + return nil
129 +}
core/node/libp2p/routingopt.go
+2 -1
@@ -139,7 +139,8 @@ func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, p
139 PeerID: peerID,
140 Addrs: httpAddrsFromConfig(addrs),
141 PrivKeyB64: privKey,
142 - })
142 + },
143 + )
144 }
145 }
146
docs/changelogs/v0.23.md
+8
@@ -9,6 +9,7 @@
9 - [Mplex deprecation](#mplex-deprecation)
10 - [Gateway: meaningful CAR responses on Not Found errors](#gateway-meaningful-car-responses-on-not-found-errors)
11 - [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)
12 + - [Self-hosting `/routing/v1` endpoint for delegated routing needs](#self-hosting-routingv1-endpoint-for-delegated-routing-needs)
13 - [📝 Changelog](#-changelog)
14 - [👨‍👩‍👧‍👦 Contributors](#-contributors)
15
@@ -59,6 +60,13 @@ the compatibility table:
60
61 *Old clients can only send Unicode file paths to the server.
62
63 +#### Self-hosting `/routing/v1` endpoint for delegated routing needs
64 +
65 +The `Routing` system configured in Kubo can be now exposed on the gateway port as a standard
66 +HTTP [Routing V1](https://specs.ipfs.tech/routing/http-routing-v1/) API endpoint. This allows
67 +self-hosting and experimentation with custom delegated routers. This is disabled by default,
68 +but can be enabled by setting [`Gateway.ExposeRoutingAPI`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayexposeroutingapi) to `true` .
69 +
70 ### 📝 Changelog
71
72 ### 👨‍👩‍👧‍👦 Contributors
docs/config.md
+10
@@ -658,6 +658,16 @@ Default: `true`
658
659 Type: `flag`
660
661 +#### `Gateway.ExposeRoutingAPI`
662 +
663 +An optional flag to expose Kubo `Routing` system on the gateway port as a [Routing
664 +V1](https://specs.ipfs.tech/routing/routing-v1/) endpoint. This only affects your
665 +local gateway, at `127.0.0.1`.
666 +
667 +Default: `false`
668 +
669 +Type: `flag`
670 +
671 ### `Gateway.HTTPHeaders`
672
673 Headers to set on gateway responses.
docs/examples/kubo-as-a-library/go.mod
+1 -2
@@ -7,7 +7,7 @@ go 1.20
7 replace github.com/ipfs/kubo => ./../../..
8
9 require (
10 - github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
10 + github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
11 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
12 github.com/libp2p/go-libp2p v0.30.0
13 github.com/multiformats/go-multiaddr v0.11.0
@@ -52,7 +52,6 @@ require (
52 github.com/google/gopacket v1.1.19 // indirect
53 github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
54 github.com/google/uuid v1.3.0 // indirect
55 - github.com/gorilla/mux v1.8.0 // indirect
55 github.com/gorilla/websocket v1.5.0 // indirect
56 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
57 github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e // indirect
docs/examples/kubo-as-a-library/go.sum
+2 -3
@@ -270,7 +270,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
270 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
271 github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
272 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
273 -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
273 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
274 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
275 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:
300 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
301 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
302 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
304 -github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
305 -github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
303 +github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
304 +github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
305 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
306 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
307 github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=
go.mod
+1 -1
@@ -15,7 +15,7 @@ require (
15 github.com/fsnotify/fsnotify v1.6.0
16 github.com/google/uuid v1.3.0
17 github.com/hashicorp/go-multierror v1.1.1
18 - github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
18 + github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
19 github.com/ipfs/go-block-format v0.1.2
20 github.com/ipfs/go-cid v0.4.1
21 github.com/ipfs/go-cidutil v0.1.0
go.sum
+2 -2
@@ -335,8 +335,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
335 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
336 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
337 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
338 -github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
339 -github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
338 +github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
339 +github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
340 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
341 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
342 github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=
routing/delegated.go
+2
@@ -224,6 +224,8 @@ func httpRoutingFromConfig(conf config.Router, extraHTTP *ExtraHTTPParams) (rout
224
225 return &httpRoutingWrapper{
226 ContentRouting: cr,
227 + PeerRouting: cr,
228 + ValueStore: cr,
229 ProvideManyRouter: cr,
230 }, nil
231 }
routing/wrapper.go
+2 -19
@@ -4,7 +4,6 @@ import (
4 "context"
5
6 routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
7 - "github.com/libp2p/go-libp2p/core/peer"
7 "github.com/libp2p/go-libp2p/core/routing"
8 )
9
@@ -22,27 +21,11 @@ var (
21 // http delegated routing.
22 type httpRoutingWrapper struct {
23 routing.ContentRouting
24 + routing.PeerRouting
25 + routing.ValueStore
26 routinghelpers.ProvideManyRouter
27 }
28
29 func (c *httpRoutingWrapper) Bootstrap(ctx context.Context) error {
30 return nil
31 }
31 -
32 -func (c *httpRoutingWrapper) FindPeer(ctx context.Context, id peer.ID) (peer.AddrInfo, error) {
33 - return peer.AddrInfo{}, routing.ErrNotSupported
34 -}
35 -
36 -func (c *httpRoutingWrapper) PutValue(context.Context, string, []byte, ...routing.Option) error {
37 - return routing.ErrNotSupported
38 -}
39 -
40 -func (c *httpRoutingWrapper) GetValue(context.Context, string, ...routing.Option) ([]byte, error) {
41 - return nil, routing.ErrNotSupported
42 -}
43 -
44 -func (c *httpRoutingWrapper) SearchValue(context.Context, string, ...routing.Option) (<-chan []byte, error) {
45 - out := make(chan []byte)
46 - close(out)
47 - return out, routing.ErrNotSupported
48 -}
test/cli/content_routing_http_test.go
+14 -11
@@ -16,42 +16,45 @@ import (
16 "github.com/ipfs/go-cid"
17 "github.com/ipfs/kubo/test/cli/harness"
18 "github.com/ipfs/kubo/test/cli/testutils"
19 + "github.com/libp2p/go-libp2p/core/peer"
20 "github.com/libp2p/go-libp2p/core/routing"
21 "github.com/stretchr/testify/assert"
22 )
23
24 type fakeHTTPContentRouter struct {
24 - m sync.Mutex
25 - findProvidersCalls int
26 - provideCalls int
25 + m sync.Mutex
26 + provideBitswapCalls int
27 + findProvidersCalls int
28 + findPeersCalls int
29 }
30
29 -func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.ProviderResponse], error) {
31 +func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, limit int) (iter.ResultIter[types.Record], error) {
32 r.m.Lock()
33 defer r.m.Unlock()
34 r.findProvidersCalls++
33 - return iter.FromSlice([]iter.Result[types.ProviderResponse]{}), nil
35 + return iter.FromSlice([]iter.Result[types.Record]{}), nil
36 }
37
38 +// nolint deprecated
39 func (r *fakeHTTPContentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
40 r.m.Lock()
41 defer r.m.Unlock()
39 - r.provideCalls++
42 + r.provideBitswapCalls++
43 return 0, nil
44 }
45
43 -func (r *fakeHTTPContentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) {
46 +func (r *fakeHTTPContentRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (iter.ResultIter[types.Record], error) {
47 r.m.Lock()
48 defer r.m.Unlock()
46 - r.provideCalls++
47 - return nil, nil
49 + r.findPeersCalls++
50 + return iter.FromSlice([]iter.Result[types.Record]{}), nil
51 }
52
50 -func (r *fakeHTTPContentRouter) FindIPNSRecord(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
53 +func (r *fakeHTTPContentRouter) GetIPNS(ctx context.Context, name ipns.Name) (*ipns.Record, error) {
54 return nil, routing.ErrNotSupported
55 }
56
54 -func (r *fakeHTTPContentRouter) ProvideIPNSRecord(ctx context.Context, name ipns.Name, rec *ipns.Record) error {
57 +func (r *fakeHTTPContentRouter) PutIPNS(ctx context.Context, name ipns.Name, rec *ipns.Record) error {
58 return routing.ErrNotSupported
59 }
60
test/cli/delegated_routing_v1_http_client_test.go renamed
+22 -14
@@ -88,12 +88,20 @@ func TestHTTPDelegatedRouting(t *testing.T) {
88
89 t.Run("adding HTTP delegated routing endpoint to Routing.Routers config works", func(t *testing.T) {
90 server := fakeServer("application/json", ToJSONStr(JSONObj{
91 - "Providers": []JSONObj{{
92 - "Protocol": "transport-bitswap",
93 - "Schema": "bitswap",
94 - "ID": provs[0],
95 - "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
96 - }},
91 + "Providers": []JSONObj{
92 + {
93 + "Schema": "bitswap", // Legacy bitswap schema.
94 + "Protocol": "transport-bitswap",
95 + "ID": provs[1],
96 + "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
97 + },
98 + {
99 + "Schema": "peer",
100 + "Protocols": []string{"transport-bitswap"},
101 + "ID": provs[0],
102 + "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
103 + },
104 + },
105 }))
106 t.Cleanup(server.Close)
107
@@ -117,21 +125,21 @@ func TestHTTPDelegatedRouting(t *testing.T) {
125
126 node.StartDaemon()
127 res = node.IPFS("routing", "findprovs", findProvsCID)
120 - assert.Equal(t, provs[0], res.Stdout.Trimmed())
128 + assert.Equal(t, provs[1]+"\n"+provs[0], res.Stdout.Trimmed())
129 })
130
131 node.StopDaemon()
132
133 t.Run("adding HTTP delegated routing endpoint to Routing.Routers config works (streaming)", func(t *testing.T) {
134 server := fakeServer("application/x-ndjson", ToJSONStr(JSONObj{
127 - "Protocol": "transport-bitswap",
128 - "Schema": "bitswap",
129 - "ID": provs[1],
130 - "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
135 + "Schema": "peer",
136 + "Protocols": []string{"transport-bitswap"},
137 + "ID": provs[0],
138 + "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
139 }), ToJSONStr(JSONObj{
140 + "Schema": "bitswap", // Legacy bitswap schema.
141 "Protocol": "transport-bitswap",
133 - "Schema": "bitswap",
134 - "ID": provs[0],
142 + "ID": provs[1],
143 "Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
144 }))
145 t.Cleanup(server.Close)
@@ -148,7 +156,7 @@ func TestHTTPDelegatedRouting(t *testing.T) {
156
157 node.StartDaemon()
158 res = node.IPFS("routing", "findprovs", findProvsCID)
151 - assert.Equal(t, provs[1]+"\n"+provs[0], res.Stdout.Trimmed())
159 + assert.Equal(t, provs[0]+"\n"+provs[1], res.Stdout.Trimmed())
160 })
161
162 t.Run("HTTP client should emit OpenCensus metrics", func(t *testing.T) {
test/cli/delegated_routing_v1_http_proxy_test.go new
+147
@@ -0,0 +1,147 @@
1 +package cli
2 +
3 +import (
4 + "testing"
5 +
6 + "github.com/ipfs/boxo/ipns"
7 + "github.com/ipfs/kubo/config"
8 + "github.com/ipfs/kubo/test/cli/harness"
9 + "github.com/ipfs/kubo/test/cli/testutils"
10 + "github.com/stretchr/testify/assert"
11 + "github.com/stretchr/testify/require"
12 +)
13 +
14 +func TestRoutingV1Proxy(t *testing.T) {
15 + t.Parallel()
16 +
17 + setupNodes := func(t *testing.T) harness.Nodes {
18 + nodes := harness.NewT(t).NewNodes(2).Init()
19 +
20 + // Node 0 uses DHT and exposes the Routing API.
21 + nodes[0].UpdateConfig(func(cfg *config.Config) {
22 + cfg.Gateway.ExposeRoutingAPI = config.True
23 + cfg.Discovery.MDNS.Enabled = false
24 + cfg.Routing.Type = config.NewOptionalString("dht")
25 + })
26 + nodes[0].StartDaemon()
27 +
28 + // Node 1 uses Node 0 as Routing V1 source, no DHT.
29 + nodes[1].UpdateConfig(func(cfg *config.Config) {
30 + cfg.Discovery.MDNS.Enabled = false
31 + cfg.Routing.Type = config.NewOptionalString("custom")
32 + cfg.Routing.Methods = config.Methods{
33 + config.MethodNameFindPeers: {RouterName: "KuboA"},
34 + config.MethodNameFindProviders: {RouterName: "KuboA"},
35 + config.MethodNameGetIPNS: {RouterName: "KuboA"},
36 + config.MethodNamePutIPNS: {RouterName: "KuboA"},
37 + config.MethodNameProvide: {RouterName: "KuboA"},
38 + }
39 + cfg.Routing.Routers = config.Routers{
40 + "KuboA": config.RouterParser{
41 + Router: config.Router{
42 + Type: config.RouterTypeHTTP,
43 + Parameters: &config.HTTPRouterParams{
44 + Endpoint: nodes[0].GatewayURL(),
45 + },
46 + },
47 + },
48 + }
49 + })
50 + nodes[1].StartDaemon()
51 +
52 + // Connect them.
53 + nodes.Connect()
54 +
55 + return nodes
56 + }
57 +
58 + t.Run("Kubo can find provider for CID via Routing V1", func(t *testing.T) {
59 + t.Parallel()
60 + nodes := setupNodes(t)
61 +
62 + cidStr := nodes[0].IPFSAddStr(testutils.RandomStr(1000))
63 +
64 + res := nodes[1].IPFS("routing", "findprovs", cidStr)
65 + assert.Equal(t, nodes[0].PeerID().String(), res.Stdout.Trimmed())
66 + })
67 +
68 + t.Run("Kubo can find peer via Routing V1", func(t *testing.T) {
69 + t.Parallel()
70 + nodes := setupNodes(t)
71 +
72 + // Start lonely node that is not connected to other nodes.
73 + node := harness.NewT(t).NewNode().Init()
74 + node.UpdateConfig(func(cfg *config.Config) {
75 + cfg.Discovery.MDNS.Enabled = false
76 + cfg.Routing.Type = config.NewOptionalString("dht")
77 + })
78 + node.StartDaemon()
79 +
80 + // Connect Node 0 to Lonely Node.
81 + nodes[0].Connect(node)
82 +
83 + // Node 1 must find Lonely Node through Node 0 Routing V1.
84 + res := nodes[1].IPFS("routing", "findpeer", node.PeerID().String())
85 + assert.Equal(t, node.SwarmAddrs()[0].String(), res.Stdout.Trimmed())
86 + })
87 +
88 + t.Run("Kubo can retrieve IPNS record via Routing V1", func(t *testing.T) {
89 + t.Parallel()
90 + nodes := setupNodes(t)
91 +
92 + nodeName := "/ipns/" + ipns.NameFromPeer(nodes[0].PeerID()).String()
93 +
94 + // Can't resolve the name as isn't published yet.
95 + res := nodes[1].RunIPFS("routing", "get", nodeName)
96 + require.Error(t, res.ExitErr)
97 +
98 + // Publish record on Node 0.
99 + path := "/ipfs/" + nodes[0].IPFSAddStr(testutils.RandomStr(1000))
100 + nodes[0].IPFS("name", "publish", "--allow-offline", path)
101 +
102 + // Get record on Node 1 (no DHT).
103 + res = nodes[1].IPFS("routing", "get", nodeName)
104 + record, err := ipns.UnmarshalRecord(res.Stdout.Bytes())
105 + require.NoError(t, err)
106 + value, err := record.Value()
107 + require.NoError(t, err)
108 + require.Equal(t, path, value.String())
109 + })
110 +
111 + t.Run("Kubo can resolve IPNS name via Routing V1", func(t *testing.T) {
112 + t.Parallel()
113 + nodes := setupNodes(t)
114 +
115 + nodeName := "/ipns/" + ipns.NameFromPeer(nodes[0].PeerID()).String()
116 +
117 + // Can't resolve the name as isn't published yet.
118 + res := nodes[1].RunIPFS("routing", "get", nodeName)
119 + require.Error(t, res.ExitErr)
120 +
121 + // Publish name.
122 + path := "/ipfs/" + nodes[0].IPFSAddStr(testutils.RandomStr(1000))
123 + nodes[0].IPFS("name", "publish", "--allow-offline", path)
124 +
125 + // Resolve IPNS name
126 + res = nodes[1].IPFS("name", "resolve", nodeName)
127 + require.Equal(t, path, res.Stdout.Trimmed())
128 + })
129 +
130 + t.Run("Kubo can provide IPNS record via Routing V1", func(t *testing.T) {
131 + t.Parallel()
132 + nodes := setupNodes(t)
133 +
134 + // Publish something on Node 1 (no DHT).
135 + nodeName := "/ipns/" + ipns.NameFromPeer(nodes[1].PeerID()).String()
136 + path := "/ipfs/" + nodes[1].IPFSAddStr(testutils.RandomStr(1000))
137 + nodes[1].IPFS("name", "publish", "--allow-offline", path)
138 +
139 + // Retrieve through Node 0.
140 + res := nodes[0].IPFS("routing", "get", nodeName)
141 + record, err := ipns.UnmarshalRecord(res.Stdout.Bytes())
142 + require.NoError(t, err)
143 + value, err := record.Value()
144 + require.NoError(t, err)
145 + require.Equal(t, path, value.String())
146 + })
147 +}
test/cli/delegated_routing_v1_http_server_test.go new
+145
@@ -0,0 +1,145 @@
1 +package cli
2 +
3 +import (
4 + "context"
5 + "testing"
6 +
7 + "github.com/google/uuid"
8 + "github.com/ipfs/boxo/ipns"
9 + "github.com/ipfs/boxo/routing/http/client"
10 + "github.com/ipfs/boxo/routing/http/types"
11 + "github.com/ipfs/boxo/routing/http/types/iter"
12 + "github.com/ipfs/go-cid"
13 + "github.com/ipfs/kubo/config"
14 + "github.com/ipfs/kubo/test/cli/harness"
15 + "github.com/libp2p/go-libp2p/core/peer"
16 + "github.com/stretchr/testify/assert"
17 +)
18 +
19 +func TestRoutingV1Server(t *testing.T) {
20 + t.Parallel()
21 +
22 + setupNodes := func(t *testing.T) harness.Nodes {
23 + nodes := harness.NewT(t).NewNodes(5).Init()
24 + nodes.ForEachPar(func(node *harness.Node) {
25 + node.UpdateConfig(func(cfg *config.Config) {
26 + cfg.Gateway.ExposeRoutingAPI = config.True
27 + cfg.Routing.Type = config.NewOptionalString("dht")
28 + })
29 + })
30 + nodes.StartDaemons().Connect()
31 + return nodes
32 + }
33 +
34 + t.Run("Get Providers Responds With Correct Peers", func(t *testing.T) {
35 + t.Parallel()
36 + nodes := setupNodes(t)
37 +
38 + text := "hello world " + uuid.New().String()
39 + cidStr := nodes[2].IPFSAddStr(text)
40 + _ = nodes[3].IPFSAddStr(text)
41 +
42 + cid, err := cid.Decode(cidStr)
43 + assert.NoError(t, err)
44 +
45 + c, err := client.New(nodes[1].GatewayURL())
46 + assert.NoError(t, err)
47 +
48 + resultsIter, err := c.FindProviders(context.Background(), cid)
49 + assert.NoError(t, err)
50 +
51 + records, err := iter.ReadAllResults(resultsIter)
52 + assert.NoError(t, err)
53 +
54 + var peers []peer.ID
55 + for _, record := range records {
56 + assert.Equal(t, types.SchemaPeer, record.GetSchema())
57 +
58 + peer, ok := record.(*types.PeerRecord)
59 + assert.True(t, ok)
60 + peers = append(peers, *peer.ID)
61 + }
62 +
63 + assert.Contains(t, peers, nodes[2].PeerID())
64 + assert.Contains(t, peers, nodes[3].PeerID())
65 + })
66 +
67 + t.Run("Get Peers Responds With Correct Peers", func(t *testing.T) {
68 + t.Parallel()
69 + nodes := setupNodes(t)
70 +
71 + c, err := client.New(nodes[1].GatewayURL())
72 + assert.NoError(t, err)
73 +
74 + resultsIter, err := c.FindPeers(context.Background(), nodes[2].PeerID())
75 + assert.NoError(t, err)
76 +
77 + records, err := iter.ReadAllResults(resultsIter)
78 + assert.NoError(t, err)
79 + assert.Len(t, records, 1)
80 + assert.IsType(t, records[0].GetSchema(), records[0].GetSchema())
81 + assert.IsType(t, records[0], &types.PeerRecord{})
82 +
83 + peer := records[0].(*types.PeerRecord)
84 + assert.Equal(t, nodes[2].PeerID().String(), peer.ID.String())
85 + assert.NotEmpty(t, peer.Addrs)
86 + })
87 +
88 + t.Run("Get IPNS Record Responds With Correct Record", func(t *testing.T) {
89 + t.Parallel()
90 + nodes := setupNodes(t)
91 +
92 + text := "hello ipns test " + uuid.New().String()
93 + cidStr := nodes[0].IPFSAddStr(text)
94 + nodes[0].IPFS("name", "publish", "--allow-offline", cidStr)
95 +
96 + // Ask for record from a different peer.
97 + c, err := client.New(nodes[1].GatewayURL())
98 + assert.NoError(t, err)
99 +
100 + record, err := c.GetIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()))
101 + assert.NoError(t, err)
102 +
103 + value, err := record.Value()
104 + assert.NoError(t, err)
105 + assert.Equal(t, "/ipfs/"+cidStr, value.String())
106 + })
107 +
108 + t.Run("Put IPNS Record Succeeds", func(t *testing.T) {
109 + t.Parallel()
110 + nodes := setupNodes(t)
111 +
112 + // Publish a record and confirm the /routing/v1/ipns API exposes the IPNS record
113 + text := "hello ipns test " + uuid.New().String()
114 + cidStr := nodes[0].IPFSAddStr(text)
115 + nodes[0].IPFS("name", "publish", "--allow-offline", cidStr)
116 + c, err := client.New(nodes[0].GatewayURL())
117 + assert.NoError(t, err)
118 + record, err := c.GetIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()))
119 + assert.NoError(t, err)
120 + value, err := record.Value()
121 + assert.NoError(t, err)
122 + assert.Equal(t, "/ipfs/"+cidStr, value.String())
123 +
124 + // Start lonely node that is not connected to other nodes.
125 + node := harness.NewT(t).NewNode().Init()
126 + node.UpdateConfig(func(cfg *config.Config) {
127 + cfg.Gateway.ExposeRoutingAPI = config.True
128 + cfg.Routing.Type = config.NewOptionalString("dht")
129 + })
130 + node.StartDaemon()
131 +
132 + // Put IPNS record in lonely node. It should be accepted as it is a valid record.
133 + c, err = client.New(node.GatewayURL())
134 + assert.NoError(t, err)
135 + err = c.PutIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()), record)
136 + assert.NoError(t, err)
137 +
138 + // Get the record from lonely node and double check.
139 + record, err = c.GetIPNS(context.Background(), ipns.NameFromPeer(nodes[0].PeerID()))
140 + assert.NoError(t, err)
141 + value, err = record.Value()
142 + assert.NoError(t, err)
143 + assert.Equal(t, "/ipfs/"+cidStr, value.String())
144 + })
145 +}
test/dependencies/go.mod
+1 -1
@@ -7,7 +7,7 @@ replace github.com/ipfs/kubo => ../../
7 require (
8 github.com/Kubuxu/gocovmerge v0.0.0-20161216165753-7ecaa51963cd
9 github.com/golangci/golangci-lint v1.54.1
10 - github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7
10 + github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd
11 github.com/ipfs/go-cid v0.4.1
12 github.com/ipfs/go-cidutil v0.1.0
13 github.com/ipfs/go-datastore v0.6.0
test/dependencies/go.sum
+2 -2
@@ -396,8 +396,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
396 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
397 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
398 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
399 -github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7 h1:f7n4M8UIf+4BY6Q0kcZ5FbpkxKaIqq/BW3evqI87DNo=
400 -github.com/ipfs/boxo v0.12.1-0.20230822135301-303595bcdba7/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
399 +github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd h1:uAp9W7FRQ7W16FENlURZqBh7/3PnakG0DjHpKPirKVY=
400 +github.com/ipfs/boxo v0.12.1-0.20230825151903-13569468babd/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk=
401 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
402 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
403 github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo=