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=