Skip to content

Commit e5e7cf1

Browse files
committed
eat(config): ability to disable Bitswap fully or just server
Original PR: #10782 This pull request introduces changes to the Bitswap configuration and functionality, including the addition of new configuration options, modifications to the Bitswap server/client behavior, and the inclusion of new tests to verify these changes. * Added a new `BitswapConfig` struct in `config/bitswap.go` to hold Bitswap configuration options, including `Enabled` and `ServerEnabled` flags. * Updated the `Config` struct in `config/config.go` to include the new `BitswapConfig` field. * Modified the `Bitswap` function in `core/node/bitswap.go` to respect the `ServerEnabled` flag, using an empty blockstore when the server is disabled to prevent serving blocks to other peers. * Updated the `OnlineExchange` function to return a no-op exchange when Bitswap is disabled. * Added a `noopExchange` struct to handle cases where Bitswap is disabled, ensuring no blocks are served or retrieved. * Introduced a new test file `test/cli/bitswap_config_test.go` with multiple test cases to verify the behavior of Bitswap configuration, including scenarios where the server is enabled, disabled, or Bitswap is completely disabled. - Closes #10717 - Depends on ipfs/boxo#912
1 parent a599737 commit e5e7cf1

File tree

6 files changed

+199
-11
lines changed

6 files changed

+199
-11
lines changed

config/bitswap.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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
8+
// Enabled, enabled by default).
9+
ServerEnabled Flag `json:",omitempty"`
10+
}

config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ type Config struct {
4242
Version Version
4343

4444
Internal Internal // experimental/unstable options
45+
46+
Bitswap Bitswap `json:",omitempty"`
4547
}
4648

4749
const (

core/node/bitswap.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package node
22

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

78
"github.com/ipfs/boxo/bitswap"
@@ -12,13 +13,15 @@ import (
1213
"github.com/ipfs/boxo/exchange/providing"
1314
provider "github.com/ipfs/boxo/provider"
1415
rpqm "github.com/ipfs/boxo/routing/providerquerymanager"
16+
blocks "github.com/ipfs/go-block-format"
17+
"github.com/ipfs/go-cid"
18+
"github.com/ipfs/go-datastore"
1519
"github.com/ipfs/kubo/config"
20+
"github.com/ipfs/kubo/core/node/helpers"
1621
irouting "github.com/ipfs/kubo/routing"
1722
"github.com/libp2p/go-libp2p/core/host"
1823
"github.com/libp2p/go-libp2p/core/routing"
1924
"go.uber.org/fx"
20-
21-
"github.com/ipfs/kubo/core/node/helpers"
2225
)
2326

2427
// Docs: https://github.com/ipfs/kubo/blob/master/docs/config.md#internalbitswap
@@ -72,13 +75,15 @@ type bitswapIn struct {
7275
}
7376

7477
// Bitswap creates the BitSwap server/client instance.
75-
// Additional options to bitswap.New can be provided via the "bitswap-options"
76-
// group.
78+
// If Bitswap.ServerEnabled is false, the node will act only as a client
79+
// using an empty blockstore to prevent serving blocks to other peers.
7780
func Bitswap(provide bool) interface{} {
7881
return func(in bitswapIn, lc fx.Lifecycle) (*bitswap.Bitswap, error) {
7982
bitswapNetwork := bsnet.NewFromIpfsHost(in.Host)
8083

84+
var blockstoree blockstore.Blockstore = in.Bs
8185
var provider routing.ContentDiscovery
86+
8287
if provide {
8388
var maxProviders int = DefaultMaxProviders
8489
if in.Cfg.Internal.Bitswap != nil {
@@ -93,10 +98,16 @@ func Bitswap(provide bool) interface{} {
9398
return nil, err
9499
}
95100
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithClientOption(client.WithDefaultProviderQueryManager(false)))
101+
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(true))
96102
provider = pqm
97-
103+
} else {
104+
provider = nil
105+
// When server is disabled, use an empty blockstore to prevent serving blocks
106+
blockstoree = blockstore.NewBlockstore(datastore.NewMapDatastore())
107+
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(false))
98108
}
99-
bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, in.Bs, in.BitswapOpts...)
109+
110+
bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, blockstoree, in.BitswapOpts...)
100111

101112
lc.Append(fx.Hook{
102113
OnStop: func(ctx context.Context) error {
@@ -107,9 +118,12 @@ func Bitswap(provide bool) interface{} {
107118
}
108119
}
109120

110-
// OnlineExchange creates new LibP2P backed block exchange.
111-
func OnlineExchange() interface{} {
121+
// Returns a no-op exchange if Bitswap is disabled.
122+
func OnlineExchange(isBitswapActive bool) interface{} {
112123
return func(in *bitswap.Bitswap, lc fx.Lifecycle) exchange.Interface {
124+
if !isBitswapActive {
125+
return &noopExchange{closer: in}
126+
}
113127
lc.Append(fx.Hook{
114128
OnStop: func(ctx context.Context) error {
115129
return in.Close()
@@ -144,3 +158,25 @@ func ProvidingExchange(provide bool) interface{} {
144158
return exch
145159
}
146160
}
161+
162+
type noopExchange struct {
163+
closer io.Closer
164+
}
165+
166+
func (e *noopExchange) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) {
167+
return nil, nil
168+
}
169+
170+
func (e *noopExchange) GetBlocks(ctx context.Context, cids []cid.Cid) (<-chan blocks.Block, error) {
171+
ch := make(chan blocks.Block)
172+
close(ch)
173+
return ch, nil
174+
}
175+
176+
func (e *noopExchange) NotifyNewBlocks(ctx context.Context, blocks ...blocks.Block) error {
177+
return nil
178+
}
179+
180+
func (e *noopExchange) Close() error {
181+
return e.closer.Close()
182+
}

core/node/groups.go

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

338-
/* don't provide from bitswap when the strategic provider service is active */
339-
shouldBitswapProvide := !cfg.Experimental.StrategicProviding
338+
isBitswapEnabled := cfg.Bitswap.Enabled.WithDefault(true) && cfg.Bitswap.ServerEnabled.WithDefault(true)
339+
// Don't provide from bitswap when the strategic provider service is active
340+
shouldBitswapProvide := isBitswapEnabled && !cfg.Experimental.StrategicProviding
340341

341342
return fx.Options(
342343
fx.Provide(BitswapOptions(cfg)),
343344
fx.Provide(Bitswap(shouldBitswapProvide)),
344-
fx.Provide(OnlineExchange()),
345+
fx.Provide(OnlineExchange(cfg.Bitswap.Enabled.WithDefault(true))),
345346
// Replace our Exchange with a Providing exchange!
346347
fx.Decorate(ProvidingExchange(shouldBitswapProvide)),
347348
fx.Provide(DNSResolver),

docs/changelogs/v0.35.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,7 @@ but for reprovides only.
120120

121121
### 📝 Changelog
122122

123+
- Completely disable Bitswap functionality through the `Bitswap.Enabled` flag
124+
- Control server behavior separately with `Bitswap.ServerEnabled`, allowing nodes to operate in client-only mode
125+
123126
### 👨‍👩‍👧‍👦 Contributors

test/cli/bitswap_client_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
100+
// Verify that basic operations still work with bitswap disabled
101+
res = requester.IPFS("id")
102+
assert.Equal(t, 0, res.ExitCode(), "basic IPFS operations should work")
103+
res = requester.IPFS("bitswap", "stat")
104+
assert.Equal(t, 0, res.ExitCode(), "bitswap stat should work even with bitswap disabled")
105+
res = requester.IPFS("bitswap", "wantlist")
106+
assert.Equal(t, 0, res.ExitCode(), "bitswap wantlist should work even with bitswap disabled")
107+
108+
// Verify local operations still work
109+
hashNew := requester.IPFSAddStr("random")
110+
res = requester.IPFS("cat", hashNew)
111+
assert.Equal(t, []byte("random"), res.Stdout.Bytes(), "cat should return the added data")
112+
})
113+
114+
// t.Run("bitswap protocols disabled", func(t *testing.T) {
115+
// t.Parallel()
116+
// harness.EnableDebugLogging()
117+
// h := harness.NewT(t)
118+
119+
// provider := h.NewNode().Init()
120+
// provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
121+
// provider = provider.StartDaemon()
122+
// requester := h.NewNode().Init().StartDaemon()
123+
// requester.Connect(provider)
124+
// // Parse and check ID output
125+
// res := provider.IPFS("id", "-f", "<protocols>")
126+
// protocols := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
127+
128+
// // No bitswap protocols should be present
129+
// for _, proto := range protocols {
130+
// assert.NotContains(t, proto, bsnet.ProtocolBitswap, "bitswap protocol %s should not be advertised when server is disabled", proto)
131+
// assert.NotContains(t, proto, bsnet.ProtocolBitswapNoVers, "bitswap protocol %s should not be advertised when server is disabled", proto)
132+
// assert.NotContains(t, proto, bsnet.ProtocolBitswapOneOne, "bitswap protocol %s should not be advertised when server is disabled", proto)
133+
// assert.NotContains(t, proto, bsnet.ProtocolBitswapOneZero, "bitswap protocol %s should not be advertised when server is disabled", proto)
134+
// }
135+
// })
136+
}

0 commit comments

Comments
 (0)