@cryptotaxi247 / kubo / commits / e8ff2d59d

feat(config): ability to disable Bitswap fully or just server (#10782)

* feat: add Bitswap configuration and related tests * fix: update Bitswap function to use 'provide' parameter for server enablement * docs: update changelog for Bitswap functionality changes * fix: update Bitswap server enablement logic and improve related tests * fix: rename BitswapConfig to Bitswap and update references * docs: config and changelog * fix: `ipfs cat` panic when `Bitswap.Enabled=false` Fixes panic described in: https://github.com/ipfs/kubo/pull/10782#discussion_r2069116219 --------- Co-authored-by: gystemd <gystemd@gmail.com> Co-authored-by: gammazero <11790789+gammazero@users.noreply.github.com> Co-authored-by: Giulio Piva <giulio.piva@dedicated.world> Co-authored-by: Marcin Rataj <lidel@lidel.org>

IGP committed Apr 30, 2025 at 22:23 UTC e8ff2d59de68b014ffee2c0d741f33612e5af5a3
7 files changed +241 -11
config/bitswap.go new
+14
@@ -0,0 +1,14 @@
1 +package config
2 +
3 +// Bitswap holds Bitswap configuration options
4 +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)
8 + ServerEnabled Flag `json:",omitempty"`
9 +}
10 +
11 +const (
12 + DefaultBitswapEnabled = true
13 + DefaultBitswapServerEnabled = true
14 +)
config/config.go
+2
@@ -42,6 +42,8 @@ type Config struct {
42 Version Version
43
44 Internal Internal // experimental/unstable options
45 +
46 + Bitswap Bitswap `json:",omitempty"`
47 }
48
49 const (
core/node/bitswap.go
+46 -6
@@ -2,6 +2,7 @@ package node
2
3 import (
4 "context"
5 + "io"
6 "time"
7
8 "github.com/ipfs/boxo/bitswap"
@@ -12,12 +13,16 @@ import (
13 "github.com/ipfs/boxo/exchange/providing"
14 provider "github.com/ipfs/boxo/provider"
15 rpqm "github.com/ipfs/boxo/routing/providerquerymanager"
16 + "github.com/ipfs/go-cid"
17 + "github.com/ipfs/go-datastore"
18 + ipld "github.com/ipfs/go-ipld-format"
19 "github.com/ipfs/kubo/config"
20 irouting "github.com/ipfs/kubo/routing"
21 "github.com/libp2p/go-libp2p/core/host"
22 "github.com/libp2p/go-libp2p/core/routing"
23 "go.uber.org/fx"
24
25 + blocks "github.com/ipfs/go-block-format"
26 "github.com/ipfs/kubo/core/node/helpers"
27 )
28
@@ -72,18 +77,21 @@ type bitswapIn struct {
77 }
78
79 // Bitswap creates the BitSwap server/client instance.
75 -// Additional options to bitswap.New can be provided via the "bitswap-options"
76 -// group.
80 +// If Bitswap.ServerEnabled is false, the node will act only as a client
81 +// using an empty blockstore to prevent serving blocks to other peers.
82 func Bitswap(provide bool) interface{} {
83 return func(in bitswapIn, lc fx.Lifecycle) (*bitswap.Bitswap, error) {
84 bitswapNetwork := bsnet.NewFromIpfsHost(in.Host)
80 -
85 + var blockstoree blockstore.Blockstore = in.Bs
86 var provider routing.ContentDiscovery
87 +
88 if provide {
89 +
90 var maxProviders int = DefaultMaxProviders
91 if in.Cfg.Internal.Bitswap != nil {
92 maxProviders = int(in.Cfg.Internal.Bitswap.ProviderSearchMaxResults.WithDefault(DefaultMaxProviders))
93 }
94 +
95 pqm, err := rpqm.New(bitswapNetwork,
96 in.Rt,
97 rpqm.WithMaxProviders(maxProviders),
@@ -93,10 +101,16 @@ func Bitswap(provide bool) interface{} {
101 return nil, err
102 }
103 in.BitswapOpts = append(in.BitswapOpts, bitswap.WithClientOption(client.WithDefaultProviderQueryManager(false)))
104 + in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(true))
105 provider = pqm
97 -
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))
111 }
99 - bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, in.Bs, in.BitswapOpts...)
112 +
113 + bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, blockstoree, in.BitswapOpts...)
114
115 lc.Append(fx.Hook{
116 OnStop: func(ctx context.Context) error {
@@ -108,8 +122,12 @@ func Bitswap(provide bool) interface{} {
122 }
123
124 // OnlineExchange creates new LibP2P backed block exchange.
111 -func OnlineExchange() interface{} {
125 +// Returns a no-op exchange if Bitswap is disabled.
126 +func OnlineExchange(isBitswapActive bool) interface{} {
127 return func(in *bitswap.Bitswap, lc fx.Lifecycle) exchange.Interface {
128 + if !isBitswapActive {
129 + return &noopExchange{closer: in}
130 + }
131 lc.Append(fx.Hook{
132 OnStop: func(ctx context.Context) error {
133 return in.Close()
@@ -144,3 +162,25 @@ func ProvidingExchange(provide bool) interface{} {
162 return exch
163 }
164 }
165 +
166 +type noopExchange struct {
167 + closer io.Closer
168 +}
169 +
170 +func (e *noopExchange) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) {
171 + return nil, ipld.ErrNotFound{Cid: c}
172 +}
173 +
174 +func (e *noopExchange) GetBlocks(ctx context.Context, cids []cid.Cid) (<-chan blocks.Block, error) {
175 + ch := make(chan blocks.Block)
176 + close(ch)
177 + return ch, nil
178 +}
179 +
180 +func (e *noopExchange) NotifyNewBlocks(ctx context.Context, blocks ...blocks.Block) error {
181 + return nil
182 +}
183 +
184 +func (e *noopExchange) Close() error {
185 + return e.closer.Close()
186 +}
core/node/groups.go
+5 -3
@@ -335,13 +335,15 @@ func Online(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part
335 recordLifetime = d
336 }
337
338 - /* don't provide from bitswap when the strategic provider service is active */
339 - shouldBitswapProvide := !cfg.Experimental.StrategicProviding
338 + isBitswapEnabled := cfg.Bitswap.Enabled.WithDefault(config.DefaultBitswapEnabled)
339 + 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
342
343 return fx.Options(
344 fx.Provide(BitswapOptions(cfg)),
345 fx.Provide(Bitswap(shouldBitswapProvide)),
344 - fx.Provide(OnlineExchange()),
346 + fx.Provide(OnlineExchange(isBitswapEnabled)),
347 // Replace our Exchange with a Providing exchange!
348 fx.Decorate(ProvidingExchange(shouldBitswapProvide)),
349 fx.Provide(DNSResolver),
docs/changelogs/v0.35.md
+2 -2
@@ -41,7 +41,8 @@ See [`Reprovider.Strategy`](https://github.com/ipfs/kubo/blob/master/docs/config
41
42 #### Additional new configuration options
43
44 -- [`Internal.Bitswap.ProviderSearchMaxResults`](https://github.com/ipfs/kubo/blob/master/docs/config.md##internalbitswapprovidersearchmaxresults) for adjusting the maximum number of providers bitswap client should aim at before it stops searching for new ones.
44 +- [`Bitswap`](https://github.com/ipfs/kubo/blob/master/docs/config.md#bitswap) section with `Enabled` and `ServerEnabled` flags determine whether Kubo initializes Bitswap, enabling just the client or both the client and server.
45 +- [`Internal.Bitswap.ProviderSearchMaxResults`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internalbitswapprovidersearchmaxresults) for adjusting the maximum number of providers bitswap client should aim at before it stops searching for new ones.
46 - [`Routing.IgnoreProviders`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingignoreproviders) allows ignoring specific peer IDs when returned by the content routing system as providers of content.
47
48 #### Grid view in WebUI
@@ -144,5 +145,4 @@ See other caveats and configuration options at [`kubo/docs/datastores.md#pebbled
145 - update `pebble` to [v2.0.3](https://github.com/cockroachdb/pebble/releases/tag/v2.0.3)
146
147 ### 📝 Changelog
147 -
148 ### 👨‍👩‍👧‍👦 Contributors
docs/config.md
+34
@@ -36,6 +36,9 @@ config file at runtime.
36 - [`AutoTLS.RegistrationToken`](#autotlsregistrationtoken)
37 - [`AutoTLS.RegistrationDelay`](#autotlsregistrationdelay)
38 - [`AutoTLS.CAEndpoint`](#autotlscaendpoint)
39 + - [`Bitswap`](#bitswap)
40 + - [`Bitswap.Enabled`](#bitswapenabled)
41 + - [`Bitswap.ServerEnabled`](#bitswapserverenabled)
42 - [`Bootstrap`](#bootstrap)
43 - [`Datastore`](#datastore)
44 - [`Datastore.StorageMax`](#datastorestoragemax)
@@ -619,6 +622,33 @@ Default: [certmagic.LetsEncryptProductionCA](https://pkg.go.dev/github.com/caddy
622
623 Type: `optionalString`
624
625 +## `Bitswap`
626 +
627 +High level client and server configuration of the [Bitswap Protocol](https://specs.ipfs.tech/bitswap-protocol/).
628 +
629 +For internal configuration see [`Internal.Bitswap`](#internalbitswap).
630 +
631 +### `Bitswap.Enabled`
632 +
633 +Manages both Bitswap client and server functionality. For testing or operating a node without Bitswap requirements.
634 +
635 +> [!WARNING]
636 +> Bitswap is a core component of Kubo, and disabling it completely may cause unpredictable outcomes. Treat this as experimental and use it solely for testing purposes.
637 +
638 +Default: `true`
639 +
640 +Type: `flag`
641 +
642 +### `Bitswap.ServerEnabled`
643 +
644 +Determines whether Kubo functions as a Bitswap server to host and respond to block requests.
645 +
646 +Disabling the server retains client and protocol support in libp2p identify responses but causes Kubo to reply with "don't have" to all block requests.
647 +
648 +Default: `true`
649 +
650 +Type: `flag`
651 +
652 ## `Bootstrap`
653
654 Bootstrap is an array of [multiaddrs][multiaddr] of trusted nodes that your node connects to, to fetch other nodes of the network on startup.
@@ -1151,6 +1181,10 @@ This section includes internal knobs for various subsystems to allow advanced us
1181 ### `Internal.Bitswap`
1182
1183 `Internal.Bitswap` contains knobs for tuning bitswap resource utilization.
1184 +
1185 +> [!TIP]
1186 +> For high level configuration see [`Bitswap`](#bitswap).
1187 +
1188 The knobs (below) document how their value should related to each other.
1189 Whether their values should be raised or lowered should be determined
1190 based on the metrics `ipfs_bitswap_active_tasks`, `ipfs_bitswap_pending_tasks`,
test/cli/bitswap_config_test.go new
+138
@@ -0,0 +1,138 @@
1 +package cli
2 +
3 +import (
4 + "testing"
5 + "time"
6 +
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 +)
12 +
13 +func TestBitswapConfig(t *testing.T) {
14 + t.Parallel()
15 +
16 + // Create test data that will be shared between nodes
17 + testData := testutils.RandomBytes(100)
18 +
19 + t.Run("server enabled (default)", func(t *testing.T) {
20 + t.Parallel()
21 + h := harness.NewT(t)
22 + provider := h.NewNode().Init().StartDaemon()
23 + requester := h.NewNode().Init().StartDaemon()
24 +
25 + hash := provider.IPFSAddStr(string(testData))
26 + requester.Connect(provider)
27 +
28 + res := requester.IPFS("cat", hash)
29 + assert.Equal(t, testData, res.Stdout.Bytes(), "retrieved data should match original")
30 + })
31 +
32 + t.Run("server disabled", func(t *testing.T) {
33 + t.Parallel()
34 + h := harness.NewT(t)
35 +
36 + provider := h.NewNode().Init()
37 + provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
38 + provider = provider.StartDaemon()
39 +
40 + requester := h.NewNode().Init().StartDaemon()
41 +
42 + hash := provider.IPFSAddStr(string(testData))
43 + requester.Connect(provider)
44 +
45 + // If the data was available, it would be retrieved immediately.
46 + // Therefore, after the timeout, we can assume the data is not available
47 + // i.e. the server is disabled
48 + timeout := time.After(3 * time.Second)
49 + dataChan := make(chan []byte)
50 +
51 + go func() {
52 + res := requester.RunIPFS("cat", hash)
53 + dataChan <- res.Stdout.Bytes()
54 + }()
55 +
56 + select {
57 + case data := <-dataChan:
58 + assert.NotEqual(t, testData, data, "retrieved data should not match original")
59 + case <-timeout:
60 + t.Log("Test passed: operation timed out after 3 seconds as expected")
61 + }
62 + })
63 +
64 + t.Run("server disabled and client enabled", func(t *testing.T) {
65 + t.Parallel()
66 + h := harness.NewT(t)
67 +
68 + provider := h.NewNode().Init()
69 + provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
70 + provider.StartDaemon()
71 +
72 + requester := h.NewNode().Init().StartDaemon()
73 + hash := requester.IPFSAddStr(string(testData))
74 +
75 + provider.Connect(requester)
76 +
77 + // Even when the server is disabled, the client should be able to retrieve data
78 + res := provider.RunIPFS("cat", hash)
79 + assert.Equal(t, testData, res.Stdout.Bytes(), "retrieved data should match original")
80 + })
81 +
82 + t.Run("bitswap completely disabled", func(t *testing.T) {
83 + t.Parallel()
84 + h := harness.NewT(t)
85 +
86 + requester := h.NewNode().Init()
87 + requester.UpdateConfig(func(cfg *config.Config) {
88 + cfg.Bitswap.Enabled = config.False
89 + cfg.Bitswap.ServerEnabled = config.False
90 + })
91 + requester.StartDaemon()
92 +
93 + provider := h.NewNode().Init().StartDaemon()
94 + hash := provider.IPFSAddStr(string(testData))
95 +
96 + requester.Connect(provider)
97 + res := requester.RunIPFS("cat", hash)
98 + assert.Equal(t, []byte{}, res.Stdout.Bytes(), "cat should not return any data")
99 + assert.Contains(t, res.Stderr.String(), "Error: ipld: could not find")
100 +
101 + // Verify that basic operations still work with bitswap disabled
102 + res = requester.IPFS("id")
103 + assert.Equal(t, 0, res.ExitCode(), "basic IPFS operations should work")
104 + res = requester.IPFS("bitswap", "stat")
105 + assert.Equal(t, 0, res.ExitCode(), "bitswap stat should work even with bitswap disabled")
106 + res = requester.IPFS("bitswap", "wantlist")
107 + assert.Equal(t, 0, res.ExitCode(), "bitswap wantlist should work even with bitswap disabled")
108 +
109 + // Verify local operations still work
110 + hashNew := requester.IPFSAddStr("random")
111 + res = requester.IPFS("cat", hashNew)
112 + assert.Equal(t, []byte("random"), res.Stdout.Bytes(), "cat should return the added data")
113 + })
114 +
115 + // TODO: Disabling Bitswap.Enabled should remove /ifps/bitswap* protocols from `ipfs id`
116 + // t.Run("bitswap protocols disabled", func(t *testing.T) {
117 + // t.Parallel()
118 + // harness.EnableDebugLogging()
119 + // h := harness.NewT(t)
120 +
121 + // provider := h.NewNode().Init()
122 + // provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
123 + // provider = provider.StartDaemon()
124 + // requester := h.NewNode().Init().StartDaemon()
125 + // requester.Connect(provider)
126 + // // Parse and check ID output
127 + // res := provider.IPFS("id", "-f", "<protocols>")
128 + // protocols := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
129 +
130 + // // No bitswap protocols should be present
131 + // for _, proto := range protocols {
132 + // assert.NotContains(t, proto, bsnet.ProtocolBitswap, "bitswap protocol %s should not be advertised when server is disabled", proto)
133 + // assert.NotContains(t, proto, bsnet.ProtocolBitswapNoVers, "bitswap protocol %s should not be advertised when server is disabled", proto)
134 + // assert.NotContains(t, proto, bsnet.ProtocolBitswapOneOne, "bitswap protocol %s should not be advertised when server is disabled", proto)
135 + // assert.NotContains(t, proto, bsnet.ProtocolBitswapOneZero, "bitswap protocol %s should not be advertised when server is disabled", proto)
136 + // }
137 + // })
138 +}