mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-23 19:37:46 +08:00
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>
This commit is contained in:
parent
b3973fa016
commit
e8ff2d59de
14
config/bitswap.go
Normal file
14
config/bitswap.go
Normal file
@ -0,0 +1,14 @@
|
||||
package config
|
||||
|
||||
// Bitswap holds Bitswap configuration options
|
||||
type Bitswap struct {
|
||||
// Enabled controls both client and server (enabled by default)
|
||||
Enabled Flag `json:",omitempty"`
|
||||
// ServerEnabled controls if the node responds to WANTs (depends on Enabled, enabled by default)
|
||||
ServerEnabled Flag `json:",omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultBitswapEnabled = true
|
||||
DefaultBitswapServerEnabled = true
|
||||
)
|
||||
@ -42,6 +42,8 @@ type Config struct {
|
||||
Version Version
|
||||
|
||||
Internal Internal // experimental/unstable options
|
||||
|
||||
Bitswap Bitswap `json:",omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@ -2,6 +2,7 @@ package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/boxo/bitswap"
|
||||
@ -12,12 +13,16 @@ import (
|
||||
"github.com/ipfs/boxo/exchange/providing"
|
||||
provider "github.com/ipfs/boxo/provider"
|
||||
rpqm "github.com/ipfs/boxo/routing/providerquerymanager"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-datastore"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
"github.com/ipfs/kubo/config"
|
||||
irouting "github.com/ipfs/kubo/routing"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/routing"
|
||||
"go.uber.org/fx"
|
||||
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/kubo/core/node/helpers"
|
||||
)
|
||||
|
||||
@ -72,18 +77,21 @@ type bitswapIn struct {
|
||||
}
|
||||
|
||||
// Bitswap creates the BitSwap server/client instance.
|
||||
// Additional options to bitswap.New can be provided via the "bitswap-options"
|
||||
// group.
|
||||
// If Bitswap.ServerEnabled is false, the node will act only as a client
|
||||
// using an empty blockstore to prevent serving blocks to other peers.
|
||||
func Bitswap(provide bool) interface{} {
|
||||
return func(in bitswapIn, lc fx.Lifecycle) (*bitswap.Bitswap, error) {
|
||||
bitswapNetwork := bsnet.NewFromIpfsHost(in.Host)
|
||||
|
||||
var blockstoree blockstore.Blockstore = in.Bs
|
||||
var provider routing.ContentDiscovery
|
||||
|
||||
if provide {
|
||||
|
||||
var maxProviders int = DefaultMaxProviders
|
||||
if in.Cfg.Internal.Bitswap != nil {
|
||||
maxProviders = int(in.Cfg.Internal.Bitswap.ProviderSearchMaxResults.WithDefault(DefaultMaxProviders))
|
||||
}
|
||||
|
||||
pqm, err := rpqm.New(bitswapNetwork,
|
||||
in.Rt,
|
||||
rpqm.WithMaxProviders(maxProviders),
|
||||
@ -93,10 +101,16 @@ func Bitswap(provide bool) interface{} {
|
||||
return nil, err
|
||||
}
|
||||
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithClientOption(client.WithDefaultProviderQueryManager(false)))
|
||||
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(true))
|
||||
provider = pqm
|
||||
|
||||
} else {
|
||||
provider = nil
|
||||
// When server is disabled, use an empty blockstore to prevent serving blocks
|
||||
blockstoree = blockstore.NewBlockstore(datastore.NewMapDatastore())
|
||||
in.BitswapOpts = append(in.BitswapOpts, bitswap.WithServerEnabled(false))
|
||||
}
|
||||
bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, in.Bs, in.BitswapOpts...)
|
||||
|
||||
bs := bitswap.New(helpers.LifecycleCtx(in.Mctx, lc), bitswapNetwork, provider, blockstoree, in.BitswapOpts...)
|
||||
|
||||
lc.Append(fx.Hook{
|
||||
OnStop: func(ctx context.Context) error {
|
||||
@ -108,8 +122,12 @@ func Bitswap(provide bool) interface{} {
|
||||
}
|
||||
|
||||
// OnlineExchange creates new LibP2P backed block exchange.
|
||||
func OnlineExchange() interface{} {
|
||||
// Returns a no-op exchange if Bitswap is disabled.
|
||||
func OnlineExchange(isBitswapActive bool) interface{} {
|
||||
return func(in *bitswap.Bitswap, lc fx.Lifecycle) exchange.Interface {
|
||||
if !isBitswapActive {
|
||||
return &noopExchange{closer: in}
|
||||
}
|
||||
lc.Append(fx.Hook{
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return in.Close()
|
||||
@ -144,3 +162,25 @@ func ProvidingExchange(provide bool) interface{} {
|
||||
return exch
|
||||
}
|
||||
}
|
||||
|
||||
type noopExchange struct {
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func (e *noopExchange) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) {
|
||||
return nil, ipld.ErrNotFound{Cid: c}
|
||||
}
|
||||
|
||||
func (e *noopExchange) GetBlocks(ctx context.Context, cids []cid.Cid) (<-chan blocks.Block, error) {
|
||||
ch := make(chan blocks.Block)
|
||||
close(ch)
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (e *noopExchange) NotifyNewBlocks(ctx context.Context, blocks ...blocks.Block) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *noopExchange) Close() error {
|
||||
return e.closer.Close()
|
||||
}
|
||||
|
||||
@ -335,13 +335,15 @@ func Online(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.Part
|
||||
recordLifetime = d
|
||||
}
|
||||
|
||||
/* don't provide from bitswap when the strategic provider service is active */
|
||||
shouldBitswapProvide := !cfg.Experimental.StrategicProviding
|
||||
isBitswapEnabled := cfg.Bitswap.Enabled.WithDefault(config.DefaultBitswapEnabled)
|
||||
isBitswapServerEnabled := cfg.Bitswap.ServerEnabled.WithDefault(config.DefaultBitswapServerEnabled)
|
||||
// Don't provide from bitswap when the strategic provider service is active
|
||||
shouldBitswapProvide := isBitswapEnabled && isBitswapServerEnabled && !cfg.Experimental.StrategicProviding
|
||||
|
||||
return fx.Options(
|
||||
fx.Provide(BitswapOptions(cfg)),
|
||||
fx.Provide(Bitswap(shouldBitswapProvide)),
|
||||
fx.Provide(OnlineExchange()),
|
||||
fx.Provide(OnlineExchange(isBitswapEnabled)),
|
||||
// Replace our Exchange with a Providing exchange!
|
||||
fx.Decorate(ProvidingExchange(shouldBitswapProvide)),
|
||||
fx.Provide(DNSResolver),
|
||||
|
||||
@ -41,7 +41,8 @@ See [`Reprovider.Strategy`](https://github.com/ipfs/kubo/blob/master/docs/config
|
||||
|
||||
#### Additional new configuration options
|
||||
|
||||
- [`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.
|
||||
- [`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.
|
||||
- [`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.
|
||||
- [`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.
|
||||
|
||||
#### Grid view in WebUI
|
||||
@ -144,5 +145,4 @@ See other caveats and configuration options at [`kubo/docs/datastores.md#pebbled
|
||||
- update `pebble` to [v2.0.3](https://github.com/cockroachdb/pebble/releases/tag/v2.0.3)
|
||||
|
||||
### 📝 Changelog
|
||||
|
||||
### 👨👩👧👦 Contributors
|
||||
|
||||
@ -36,6 +36,9 @@ config file at runtime.
|
||||
- [`AutoTLS.RegistrationToken`](#autotlsregistrationtoken)
|
||||
- [`AutoTLS.RegistrationDelay`](#autotlsregistrationdelay)
|
||||
- [`AutoTLS.CAEndpoint`](#autotlscaendpoint)
|
||||
- [`Bitswap`](#bitswap)
|
||||
- [`Bitswap.Enabled`](#bitswapenabled)
|
||||
- [`Bitswap.ServerEnabled`](#bitswapserverenabled)
|
||||
- [`Bootstrap`](#bootstrap)
|
||||
- [`Datastore`](#datastore)
|
||||
- [`Datastore.StorageMax`](#datastorestoragemax)
|
||||
@ -619,6 +622,33 @@ Default: [certmagic.LetsEncryptProductionCA](https://pkg.go.dev/github.com/caddy
|
||||
|
||||
Type: `optionalString`
|
||||
|
||||
## `Bitswap`
|
||||
|
||||
High level client and server configuration of the [Bitswap Protocol](https://specs.ipfs.tech/bitswap-protocol/).
|
||||
|
||||
For internal configuration see [`Internal.Bitswap`](#internalbitswap).
|
||||
|
||||
### `Bitswap.Enabled`
|
||||
|
||||
Manages both Bitswap client and server functionality. For testing or operating a node without Bitswap requirements.
|
||||
|
||||
> [!WARNING]
|
||||
> 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.
|
||||
|
||||
Default: `true`
|
||||
|
||||
Type: `flag`
|
||||
|
||||
### `Bitswap.ServerEnabled`
|
||||
|
||||
Determines whether Kubo functions as a Bitswap server to host and respond to block requests.
|
||||
|
||||
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.
|
||||
|
||||
Default: `true`
|
||||
|
||||
Type: `flag`
|
||||
|
||||
## `Bootstrap`
|
||||
|
||||
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
|
||||
### `Internal.Bitswap`
|
||||
|
||||
`Internal.Bitswap` contains knobs for tuning bitswap resource utilization.
|
||||
|
||||
> [!TIP]
|
||||
> For high level configuration see [`Bitswap`](#bitswap).
|
||||
|
||||
The knobs (below) document how their value should related to each other.
|
||||
Whether their values should be raised or lowered should be determined
|
||||
based on the metrics `ipfs_bitswap_active_tasks`, `ipfs_bitswap_pending_tasks`,
|
||||
|
||||
138
test/cli/bitswap_config_test.go
Normal file
138
test/cli/bitswap_config_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/kubo/config"
|
||||
"github.com/ipfs/kubo/test/cli/harness"
|
||||
"github.com/ipfs/kubo/test/cli/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBitswapConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create test data that will be shared between nodes
|
||||
testData := testutils.RandomBytes(100)
|
||||
|
||||
t.Run("server enabled (default)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := harness.NewT(t)
|
||||
provider := h.NewNode().Init().StartDaemon()
|
||||
requester := h.NewNode().Init().StartDaemon()
|
||||
|
||||
hash := provider.IPFSAddStr(string(testData))
|
||||
requester.Connect(provider)
|
||||
|
||||
res := requester.IPFS("cat", hash)
|
||||
assert.Equal(t, testData, res.Stdout.Bytes(), "retrieved data should match original")
|
||||
})
|
||||
|
||||
t.Run("server disabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := harness.NewT(t)
|
||||
|
||||
provider := h.NewNode().Init()
|
||||
provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
|
||||
provider = provider.StartDaemon()
|
||||
|
||||
requester := h.NewNode().Init().StartDaemon()
|
||||
|
||||
hash := provider.IPFSAddStr(string(testData))
|
||||
requester.Connect(provider)
|
||||
|
||||
// If the data was available, it would be retrieved immediately.
|
||||
// Therefore, after the timeout, we can assume the data is not available
|
||||
// i.e. the server is disabled
|
||||
timeout := time.After(3 * time.Second)
|
||||
dataChan := make(chan []byte)
|
||||
|
||||
go func() {
|
||||
res := requester.RunIPFS("cat", hash)
|
||||
dataChan <- res.Stdout.Bytes()
|
||||
}()
|
||||
|
||||
select {
|
||||
case data := <-dataChan:
|
||||
assert.NotEqual(t, testData, data, "retrieved data should not match original")
|
||||
case <-timeout:
|
||||
t.Log("Test passed: operation timed out after 3 seconds as expected")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("server disabled and client enabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := harness.NewT(t)
|
||||
|
||||
provider := h.NewNode().Init()
|
||||
provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
|
||||
provider.StartDaemon()
|
||||
|
||||
requester := h.NewNode().Init().StartDaemon()
|
||||
hash := requester.IPFSAddStr(string(testData))
|
||||
|
||||
provider.Connect(requester)
|
||||
|
||||
// Even when the server is disabled, the client should be able to retrieve data
|
||||
res := provider.RunIPFS("cat", hash)
|
||||
assert.Equal(t, testData, res.Stdout.Bytes(), "retrieved data should match original")
|
||||
})
|
||||
|
||||
t.Run("bitswap completely disabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
h := harness.NewT(t)
|
||||
|
||||
requester := h.NewNode().Init()
|
||||
requester.UpdateConfig(func(cfg *config.Config) {
|
||||
cfg.Bitswap.Enabled = config.False
|
||||
cfg.Bitswap.ServerEnabled = config.False
|
||||
})
|
||||
requester.StartDaemon()
|
||||
|
||||
provider := h.NewNode().Init().StartDaemon()
|
||||
hash := provider.IPFSAddStr(string(testData))
|
||||
|
||||
requester.Connect(provider)
|
||||
res := requester.RunIPFS("cat", hash)
|
||||
assert.Equal(t, []byte{}, res.Stdout.Bytes(), "cat should not return any data")
|
||||
assert.Contains(t, res.Stderr.String(), "Error: ipld: could not find")
|
||||
|
||||
// Verify that basic operations still work with bitswap disabled
|
||||
res = requester.IPFS("id")
|
||||
assert.Equal(t, 0, res.ExitCode(), "basic IPFS operations should work")
|
||||
res = requester.IPFS("bitswap", "stat")
|
||||
assert.Equal(t, 0, res.ExitCode(), "bitswap stat should work even with bitswap disabled")
|
||||
res = requester.IPFS("bitswap", "wantlist")
|
||||
assert.Equal(t, 0, res.ExitCode(), "bitswap wantlist should work even with bitswap disabled")
|
||||
|
||||
// Verify local operations still work
|
||||
hashNew := requester.IPFSAddStr("random")
|
||||
res = requester.IPFS("cat", hashNew)
|
||||
assert.Equal(t, []byte("random"), res.Stdout.Bytes(), "cat should return the added data")
|
||||
})
|
||||
|
||||
// TODO: Disabling Bitswap.Enabled should remove /ifps/bitswap* protocols from `ipfs id`
|
||||
// t.Run("bitswap protocols disabled", func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
// harness.EnableDebugLogging()
|
||||
// h := harness.NewT(t)
|
||||
|
||||
// provider := h.NewNode().Init()
|
||||
// provider.SetIPFSConfig("Bitswap.ServerEnabled", false)
|
||||
// provider = provider.StartDaemon()
|
||||
// requester := h.NewNode().Init().StartDaemon()
|
||||
// requester.Connect(provider)
|
||||
// // Parse and check ID output
|
||||
// res := provider.IPFS("id", "-f", "<protocols>")
|
||||
// protocols := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
|
||||
|
||||
// // No bitswap protocols should be present
|
||||
// for _, proto := range protocols {
|
||||
// assert.NotContains(t, proto, bsnet.ProtocolBitswap, "bitswap protocol %s should not be advertised when server is disabled", proto)
|
||||
// assert.NotContains(t, proto, bsnet.ProtocolBitswapNoVers, "bitswap protocol %s should not be advertised when server is disabled", proto)
|
||||
// assert.NotContains(t, proto, bsnet.ProtocolBitswapOneOne, "bitswap protocol %s should not be advertised when server is disabled", proto)
|
||||
// assert.NotContains(t, proto, bsnet.ProtocolBitswapOneZero, "bitswap protocol %s should not be advertised when server is disabled", proto)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user