mirror of
https://github.com/ipfs/kubo.git
synced 2025-05-17 06:57:40 +08:00
feat(gateway): expose /routing/v1 server (opt-in) (#9877)
This commit is contained in:
@ -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))
|
||||
}
|
||||
|
@ -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
129
core/corehttp/routing.go
Normal 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
|
||||
}
|
@ -139,7 +139,8 @@ func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, p
|
||||
PeerID: peerID,
|
||||
Addrs: httpAddrsFromConfig(addrs),
|
||||
PrivKeyB64: privKey,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
@ -224,6 +224,8 @@ func httpRoutingFromConfig(conf config.Router, extraHTTP *ExtraHTTPParams) (rout
|
||||
|
||||
return &httpRoutingWrapper{
|
||||
ContentRouting: cr,
|
||||
PeerRouting: cr,
|
||||
ValueStore: cr,
|
||||
ProvideManyRouter: cr,
|
||||
}, nil
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
findProvidersCalls int
|
||||
provideCalls int
|
||||
m sync.Mutex
|
||||
provideBitswapCalls int
|
||||
findProvidersCalls 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
|
||||
}
|
||||
|
||||
|
@ -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{{
|
||||
"Protocol": "transport-bitswap",
|
||||
"Schema": "bitswap",
|
||||
"ID": provs[0],
|
||||
"Addrs": []string{"/ip4/0.0.0.0/tcp/4001", "/ip4/0.0.0.0/tcp/4002"},
|
||||
}},
|
||||
"Providers": []JSONObj{
|
||||
{
|
||||
"Schema": "bitswap", // Legacy bitswap schema.
|
||||
"Protocol": "transport-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],
|
||||
"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"},
|
||||
}), 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) {
|
147
test/cli/delegated_routing_v1_http_proxy_test.go
Normal file
147
test/cli/delegated_routing_v1_http_proxy_test.go
Normal 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())
|
||||
})
|
||||
}
|
145
test/cli/delegated_routing_v1_http_server_test.go
Normal file
145
test/cli/delegated_routing_v1_http_server_test.go
Normal 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())
|
||||
})
|
||||
}
|
@ -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
|
||||
|
@ -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=
|
||||
|
Reference in New Issue
Block a user