Skip to content

Commit b5d7369

Browse files
hsanjuanlidel
andauthored
feat: opt-in http retrieval client (#10772)
* Feat: http retrieval as experimental feature This introduces the http-retrieval capability as an experimental feature. It can be enabled in the configuration `Experimental.HTTPRetrieval.Enabled = true`. Documentation and changelog to be added later. * refactor: HTTPRetrieval.Enabled as Flag * docs(config): HTTPRetrieval section * refactor: reusable MockHTTPContentRouter * feat: HTTPRetrieval.TLSInsecureSkipVerify allows self-signed certificates in tests * feat(config): HTTPRetrieval.MaxBlockSize * test: end-to-end HTTPRetrieval.Enabled this spawns two http services on localhost: 1. HTTP router that returns HTTP provider when /routing/v1/providers/cid i queried 2. HTTP provider that returns a block when /ipfs/cid is queried 3. Configures Kubo to use (1) instead of cid.contact this seems to work (running test with DEBUG=true shows (1) was queried for the test CID and returned multiaddr of (2), but Kubo never requested test CID block from (2) – needs investigation * fix: enable /routing/v1/peers for non-cid.contact we artificially limited every delegated routing endpoint because of cid.contact being limited to one endpoint * feat: Routing.DelegatedRouters make it easy to override the hardcoded implicit HTTP routeur URL without having to set the entire custom Router.Routers and Router.Methods (http_retrieval_client_test.go still needs to be fixed in future commit) * test: flag remaining work * docs: review feedback * refactor: providerQueryMgr with bitswapNetworks this fixes two regressions: (1) introduced in #10717 where we only used bitswapLib2p query manager (this is why E2E did not act on http provider) (2) introduced in #10765 where it was not possible to set binary peerID in IgnoreProviders (we changed to []string) * refactor: Bitswap.Libp2pEnabled replaces Bitswap.Enabled with Bitswap.Libp2pEnabled adds tests that confirm it is possible to disable libp2p bitswap fully and only keep http in client mode also, removes the need for passing empty blockstore in client-only mode * docs: changelog --------- Co-authored-by: Marcin Rataj <lidel@lidel.org>
1 parent 7059620 commit b5d7369

File tree

17 files changed

+751
-203
lines changed

17 files changed

+751
-203
lines changed

cmd/ipfs/kubo/daemon.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
458458
cfg.Identity.PeerID,
459459
cfg.Addresses,
460460
cfg.Identity.PrivKey,
461+
cfg.HTTPRetrieval.Enabled.WithDefault(config.DefaultHTTPRetrievalEnabled),
461462
)
462463
default:
463464
return fmt.Errorf("unrecognized routing option: %s", routingOption)

config/bitswap.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package config
22

33
// Bitswap holds Bitswap configuration options
44
type Bitswap struct {
5-
// Enabled controls both client and server (enabled by default)
6-
Enabled Flag `json:",omitempty"`
7-
// ServerEnabled controls if the node responds to WANTs (depends on Enabled, enabled by default)
5+
// Libp2pEnabled controls if the node initializes bitswap over libp2p (enabled by default)
6+
// (This can be disabled if HTTPRetrieval.Enabled is set to true)
7+
Libp2pEnabled Flag `json:",omitempty"`
8+
// ServerEnabled controls if the node responds to WANTs (depends on Libp2pEnabled, enabled by default)
89
ServerEnabled Flag `json:",omitempty"`
910
}
1011

1112
const (
12-
DefaultBitswapEnabled = true
13+
DefaultBitswapLibp2pEnabled = true
1314
DefaultBitswapServerEnabled = true
1415
)

config/config.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ type Config struct {
3333
DNS DNS
3434
Migration Migration
3535

36-
Provider Provider
37-
Reprovider Reprovider
38-
Experimental Experiments
39-
Plugins Plugins
40-
Pinning Pinning
41-
Import Import
42-
Version Version
36+
Provider Provider
37+
Reprovider Reprovider
38+
HTTPRetrieval HTTPRetrieval
39+
Experimental Experiments
40+
Plugins Plugins
41+
Pinning Pinning
42+
Import Import
43+
Version Version
4344

4445
Internal Internal // experimental/unstable options
4546

config/http_retrieval.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package config
2+
3+
// HTTPRetrieval is the configuration object for HTTP Retrieval settings.
4+
// Implicit defaults can be found in core/node/bitswap.go
5+
type HTTPRetrieval struct {
6+
Enabled Flag `json:",omitempty"`
7+
Allowlist []string `json:",omitempty"`
8+
Denylist []string `json:",omitempty"`
9+
NumWorkers *OptionalInteger `json:",omitempty"`
10+
MaxBlockSize *OptionalString `json:",omitempty"`
11+
TLSInsecureSkipVerify Flag `json:",omitempty"`
12+
}
13+
14+
const (
15+
DefaultHTTPRetrievalEnabled = false // opt-in for now, until we figure out https://github.com/ipfs/specs/issues/496
16+
DefaultHTTPRetrievalNumWorkers = 16
17+
DefaultHTTPRetrievalTLSInsecureSkipVerify = false // only for testing with self-signed HTTPS certs
18+
DefaultHTTPRetrievalMaxBlockSize = "2MiB" // matching bitswap: https://specs.ipfs.tech/bitswap-protocol/#block-sizes
19+
)

config/init.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,6 @@ func InitWithIdentity(identity Identity) (*Config, error) {
4848
},
4949
},
5050

51-
Routing: Routing{
52-
Type: nil,
53-
Methods: nil,
54-
Routers: nil,
55-
IgnoreProviders: []peer.ID{},
56-
},
57-
5851
// setup the node mount points.
5952
Mounts: Mounts{
6053
IPFS: "/ipfs",

config/routing.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,30 @@ import (
66
"os"
77
"runtime"
88
"strings"
9-
10-
peer "github.com/libp2p/go-libp2p/core/peer"
119
)
1210

1311
const (
1412
DefaultAcceleratedDHTClient = false
1513
DefaultLoopbackAddressesOnLanDHT = false
14+
CidContactRoutingURL = "https://cid.contact"
15+
PublicGoodDelegatedRoutingURL = "https://delegated-ipfs.dev" // cid.contact + amino dht (incl. IPNS PUTs)
16+
EnvHTTPRouters = "IPFS_HTTP_ROUTERS"
17+
EnvHTTPRoutersFilterProtocols = "IPFS_HTTP_ROUTERS_FILTER_PROTOCOLS"
1618
)
1719

1820
var (
1921
// Default HTTP routers used in parallel to DHT when Routing.Type = "auto"
20-
DefaultHTTPRouters = getEnvOrDefault("IPFS_HTTP_ROUTERS", []string{
21-
"https://cid.contact", // https://github.com/ipfs/kubo/issues/9422#issuecomment-1338142084
22+
DefaultHTTPRouters = getEnvOrDefault(EnvHTTPRouters, []string{
23+
CidContactRoutingURL, // https://github.com/ipfs/kubo/issues/9422#issuecomment-1338142084
2224
})
2325

2426
// Default filter-protocols to pass along with delegated routing requests (as defined in IPIP-484)
2527
// and also filter out locally
26-
DefaultHTTPRoutersFilterProtocols = getEnvOrDefault("IPFS_HTTP_ROUTERS_FILTER_PROTOCOLS", []string{
28+
DefaultHTTPRoutersFilterProtocols = getEnvOrDefault(EnvHTTPRoutersFilterProtocols, []string{
2729
"unknown", // allow results without protocol list, we can do libp2p identify to test them
2830
"transport-bitswap",
29-
// TODO: add 'transport-ipfs-gateway-http' once https://github.com/ipfs/rainbow/issues/125 is addressed
31+
// http is added dynamically in routing/delegated.go.
32+
// 'transport-ipfs-gateway-http'
3033
})
3134
)
3235

@@ -43,11 +46,13 @@ type Routing struct {
4346

4447
LoopbackAddressesOnLanDHT Flag `json:",omitempty"`
4548

46-
IgnoreProviders []peer.ID
49+
IgnoreProviders []string `json:",omitempty"`
50+
51+
DelegatedRouters []string `json:",omitempty"`
4752

48-
Routers Routers
53+
Routers Routers `json:",omitempty"`
4954

50-
Methods Methods
55+
Methods Methods `json:",omitempty"`
5156
}
5257

5358
type Router struct {

core/node/bitswap.go

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,28 @@ package node
22

33
import (
44
"context"
5+
"errors"
56
"io"
67
"time"
78

9+
"github.com/dustin/go-humanize"
810
"github.com/ipfs/boxo/bitswap"
911
"github.com/ipfs/boxo/bitswap/client"
12+
"github.com/ipfs/boxo/bitswap/network"
1013
bsnet "github.com/ipfs/boxo/bitswap/network/bsnet"
14+
"github.com/ipfs/boxo/bitswap/network/httpnet"
1115
blockstore "github.com/ipfs/boxo/blockstore"
1216
exchange "github.com/ipfs/boxo/exchange"
1317
"github.com/ipfs/boxo/exchange/providing"
1418
provider "github.com/ipfs/boxo/provider"
1519
rpqm "github.com/ipfs/boxo/routing/providerquerymanager"
1620
"github.com/ipfs/go-cid"
17-
"github.com/ipfs/go-datastore"
1821
ipld "github.com/ipfs/go-ipld-format"
22+
version "github.com/ipfs/kubo"
1923
"github.com/ipfs/kubo/config"
2024
irouting "github.com/ipfs/kubo/routing"
2125
"github.com/libp2p/go-libp2p/core/host"
22-
"github.com/libp2p/go-libp2p/core/routing"
26+
peer "github.com/libp2p/go-libp2p/core/peer"
2327
"go.uber.org/fx"
2428

2529
blocks "github.com/ipfs/go-block-format"
@@ -79,38 +83,63 @@ type bitswapIn struct {
7983
// Bitswap creates the BitSwap server/client instance.
8084
// If Bitswap.ServerEnabled is false, the node will act only as a client
8185
// using an empty blockstore to prevent serving blocks to other peers.
82-
func Bitswap(provide bool) interface{} {
86+
func Bitswap(serverEnabled bool) interface{} {
8387
return func(in bitswapIn, lc fx.Lifecycle) (*bitswap.Bitswap, error) {
84-
bitswapNetwork := bsnet.NewFromIpfsHost(in.Host)
85-
var blockstoree blockstore.Blockstore = in.Bs
86-
var provider routing.ContentDiscovery
88+
var bitswapNetworks, bitswapLibp2p network.BitSwapNetwork
89+
var bitswapBlockstore blockstore.Blockstore = in.Bs
8790

88-
if provide {
91+
libp2pEnabled := in.Cfg.Bitswap.Libp2pEnabled.WithDefault(config.DefaultBitswapLibp2pEnabled)
92+
if libp2pEnabled {
93+
bitswapLibp2p = bsnet.NewFromIpfsHost(in.Host)
94+
}
8995

90-
var maxProviders int = DefaultMaxProviders
91-
if in.Cfg.Internal.Bitswap != nil {
92-
maxProviders = int(in.Cfg.Internal.Bitswap.ProviderSearchMaxResults.WithDefault(DefaultMaxProviders))
96+
if httpCfg := in.Cfg.HTTPRetrieval; httpCfg.Enabled.WithDefault(config.DefaultHTTPRetrievalEnabled) {
97+
maxBlockSize, err := humanize.ParseBytes(httpCfg.MaxBlockSize.WithDefault(config.DefaultHTTPRetrievalMaxBlockSize))
98+
if err != nil {
99+
return nil, err
93100
}
94-
95-
pqm, err := rpqm.New(bitswapNetwork,
96-
in.Rt,
97-
rpqm.WithMaxProviders(maxProviders),
98-
rpqm.WithIgnoreProviders(in.Cfg.Routing.IgnoreProviders...),
101+
bitswapHTTP := httpnet.New(in.Host,
102+
httpnet.WithHTTPWorkers(int(httpCfg.NumWorkers.WithDefault(config.DefaultHTTPRetrievalNumWorkers))),
103+
httpnet.WithAllowlist(httpCfg.Allowlist),
104+
httpnet.WithDenylist(httpCfg.Denylist),
105+
httpnet.WithInsecureSkipVerify(httpCfg.TLSInsecureSkipVerify.WithDefault(config.DefaultHTTPRetrievalTLSInsecureSkipVerify)),
106+
httpnet.WithMaxBlockSize(int64(maxBlockSize)),
107+
httpnet.WithUserAgent(version.GetUserAgentVersion()),
99108
)
109+
bitswapNetworks = network.New(in.Host.Peerstore(), bitswapLibp2p, bitswapHTTP)
110+
} else if libp2pEnabled {
111+
bitswapNetworks = bitswapLibp2p
112+
} else {
113+
return nil, errors.New("invalid configuration: Bitswap.Libp2pEnabled and HTTPRetrieval.Enabled are both disabled, unable to initialize Bitswap")
114+
}
115+
116+
// Kubo uses own, customized ProviderQueryManager
117+
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithClientOption(client.WithDefaultProviderQueryManager(false)))
118+
var maxProviders int = DefaultMaxProviders
119+
if in.Cfg.Internal.Bitswap != nil {
120+
maxProviders = int(in.Cfg.Internal.Bitswap.ProviderSearchMaxResults.WithDefault(DefaultMaxProviders))
121+
}
122+
ignoredPeerIDs := make([]peer.ID, 0, len(in.Cfg.Routing.IgnoreProviders))
123+
for _, str := range in.Cfg.Routing.IgnoreProviders {
124+
pid, err := peer.Decode(str)
100125
if err != nil {
101126
return nil, err
102127
}
103-
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithClientOption(client.WithDefaultProviderQueryManager(false)))
104-
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(true))
105-
provider = pqm
106-
} else {
107-
provider = nil
108-
// When server is disabled, use an empty blockstore to prevent serving blocks
109-
blockstoree = blockstore.NewBlockstore(datastore.NewMapDatastore())
110-
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(false))
128+
ignoredPeerIDs = append(ignoredPeerIDs, pid)
129+
}
130+
providerQueryMgr, err := rpqm.New(bitswapNetworks,
131+
in.Rt,
132+
rpqm.WithMaxProviders(maxProviders),
133+
rpqm.WithIgnoreProviders(ignoredPeerIDs...),
134+
)
135+
if err != nil {
136+
return nil, err
111137
}
112138

113-
bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, blockstoree, in.BitswapOpts...)
139+
// Explicitly enable/disable server to ensure desired provide mode
140+
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(serverEnabled))
141+
142+
bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetworks, providerQueryMgr, bitswapBlockstore, in.BitswapOpts...)
114143

115144
lc.Append(fx.Hook{
116145
OnStop: func(ctx context.Context) error {

core/node/groups.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,17 +335,18 @@ func Online(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part
335335
recordLifetime = d
336336
}
337337

338-
isBitswapEnabled := cfg.Bitswap.Enabled.WithDefault(config.DefaultBitswapEnabled)
338+
isBitswapLibp2pEnabled := cfg.Bitswap.Libp2pEnabled.WithDefault(config.DefaultBitswapLibp2pEnabled)
339339
isBitswapServerEnabled := cfg.Bitswap.ServerEnabled.WithDefault(config.DefaultBitswapServerEnabled)
340-
// Don't provide from bitswap when the strategic provider service is active
341-
shouldBitswapProvide := isBitswapEnabled && isBitswapServerEnabled && !cfg.Experimental.StrategicProviding
340+
341+
// Don't provide from bitswap when the legacy noop experiment "strategic provider service" is active
342+
isBitswapServerEnabled = isBitswapServerEnabled && !cfg.Experimental.StrategicProviding
342343

343344
return fx.Options(
344345
fx.Provide(BitswapOptions(cfg)),
345-
fx.Provide(Bitswap(shouldBitswapProvide)),
346-
fx.Provide(OnlineExchange(isBitswapEnabled)),
346+
fx.Provide(Bitswap(isBitswapServerEnabled)),
347+
fx.Provide(OnlineExchange(isBitswapLibp2pEnabled)),
347348
// Replace our Exchange with a Providing exchange!
348-
fx.Decorate(ProvidingExchange(shouldBitswapProvide)),
349+
fx.Decorate(ProvidingExchange(isBitswapServerEnabled)),
349350
fx.Provide(DNSResolver),
350351
fx.Provide(Namesys(ipnsCacheSize, cfg.Ipns.MaxCacheTTL.WithDefault(config.DefaultIpnsMaxCacheTTL))),
351352
fx.Provide(Peering),

core/node/libp2p/routingopt.go

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package libp2p
22

33
import (
44
"context"
5+
"os"
56
"time"
67

78
"github.com/ipfs/go-datastore"
@@ -29,21 +30,44 @@ type RoutingOptionArgs struct {
2930

3031
type RoutingOption func(args RoutingOptionArgs) (routing.Routing, error)
3132

33+
var noopRouter = routinghelpers.Null{}
34+
3235
func constructDefaultHTTPRouters(cfg *config.Config) ([]*routinghelpers.ParallelRouter, error) {
3336
var routers []*routinghelpers.ParallelRouter
37+
httpRetrievalEnabled := cfg.HTTPRetrieval.Enabled.WithDefault(config.DefaultHTTPRetrievalEnabled)
38+
39+
// Use config.DefaultHTTPRouters if custom override was sent via config.EnvHTTPRouters
40+
// or if user did not set any preference in cfg.Routing.DelegatedRouters
41+
var httpRouterEndpoints []string
42+
if os.Getenv(config.EnvHTTPRouters) != "" || len(cfg.Routing.DelegatedRouters) == 0 {
43+
httpRouterEndpoints = config.DefaultHTTPRouters
44+
} else {
45+
httpRouterEndpoints = cfg.Routing.DelegatedRouters
46+
}
47+
3448
// Append HTTP routers for additional speed
35-
for _, endpoint := range config.DefaultHTTPRouters {
36-
httpRouter, err := irouting.ConstructHTTPRouter(endpoint, cfg.Identity.PeerID, httpAddrsFromConfig(cfg.Addresses), cfg.Identity.PrivKey)
49+
for _, endpoint := range httpRouterEndpoints {
50+
httpRouter, err := irouting.ConstructHTTPRouter(endpoint, cfg.Identity.PeerID, httpAddrsFromConfig(cfg.Addresses), cfg.Identity.PrivKey, httpRetrievalEnabled)
3751
if err != nil {
3852
return nil, err
3953
}
40-
54+
// Mapping router to /routing/v1/* endpoints
55+
// https://specs.ipfs.tech/routing/http-routing-v1/
4156
r := &irouting.Composer{
42-
GetValueRouter: routinghelpers.Null{},
43-
PutValueRouter: routinghelpers.Null{},
44-
ProvideRouter: routinghelpers.Null{}, // modify this when indexers supports provide
45-
FindPeersRouter: routinghelpers.Null{},
46-
FindProvidersRouter: httpRouter,
57+
GetValueRouter: httpRouter, // GET /routing/v1/ipns
58+
PutValueRouter: httpRouter, // PUT /routing/v1/ipns
59+
ProvideRouter: noopRouter, // we don't have spec for sending provides to /routing/v1 (revisit once https://github.com/ipfs/specs/pull/378 or similar is ratified)
60+
FindPeersRouter: httpRouter, // /routing/v1/peers
61+
FindProvidersRouter: httpRouter, // /routing/v1/providers
62+
}
63+
64+
if endpoint == config.CidContactRoutingURL {
65+
// Special-case: cid.contact only supports /routing/v1/providers/cid
66+
// we disable other endpoints to avoid sending requests that always fail
67+
r.GetValueRouter = noopRouter
68+
r.PutValueRouter = noopRouter
69+
r.ProvideRouter = noopRouter
70+
r.FindPeersRouter = noopRouter
4771
}
4872

4973
routers = append(routers, &routinghelpers.ParallelRouter{
@@ -119,7 +143,7 @@ func constructDHTRouting(mode dht.ModeOpt) RoutingOption {
119143
}
120144

121145
// ConstructDelegatedRouting is used when Routing.Type = "custom"
122-
func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, peerID string, addrs config.Addresses, privKey string) RoutingOption {
146+
func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, peerID string, addrs config.Addresses, privKey string, httpRetrieval bool) RoutingOption {
123147
return func(args RoutingOptionArgs) (routing.Routing, error) {
124148
return irouting.Parse(routers, methods,
125149
&irouting.ExtraDHTParams{
@@ -130,9 +154,10 @@ func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, p
130154
Context: args.Ctx,
131155
},
132156
&irouting.ExtraHTTPParams{
133-
PeerID: peerID,
134-
Addrs: httpAddrsFromConfig(addrs),
135-
PrivKeyB64: privKey,
157+
PeerID: peerID,
158+
Addrs: httpAddrsFromConfig(addrs),
159+
PrivKeyB64: privKey,
160+
HTTPRetrieval: httpRetrieval,
136161
},
137162
)
138163
}

0 commit comments

Comments
 (0)