Skip to content

Commit f54c309

Browse files
authored
feat(bitswap): add option to disable Bitswap server (#911)
* add option to enable/disable server initialization * replace serverEnabled checks with nil checks for server initialization * add tests for BitSwap functionality with server disabled * update NotifyNewBlocks to check for server initialization using nil check * update changelog
1 parent 95aa3f0 commit f54c309

File tree

4 files changed

+178
-24
lines changed

4 files changed

+178
-24
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The following emojis are used to highlight certain changes:
3030
- `fetcher/impl/blockservice`: new option `SkipNotFound` for the IPLD fetcher. It will skip not found nodes when traversing the DAG. This allows offline traversal of DAGs when using, for example, an offline blockservice.
3131
- This enables use case of providing lazy-loaded, partially local DAGs (like `ipfs files` in Kubo's MFS implementation, see [kubo#10386](https://github.com/ipfs/kubo/issues/10386))
3232
- `gateway`: generated HTML with UnixFS directory listings now include a button for copying CIDs of child entities [#899](https://github.com/ipfs/boxo/pull/899)
33+
- `bitswap/server`: Add ability to enable/disable bitswap server using `WithServerEnabled` bitswap option (#911)[https://github.com/ipfs/boxo/pull/911]
3334

3435
### Changed
3536

bitswap/bitswap.go

+50-24
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ type Bitswap struct {
5151
*client.Client
5252
*server.Server
5353

54-
tracer tracer.Tracer
55-
net network.BitSwapNetwork
54+
tracer tracer.Tracer
55+
net network.BitSwapNetwork
56+
serverEnabled bool
5657
}
5758

5859
func New(ctx context.Context, net network.BitSwapNetwork, providerFinder routing.ContentDiscovery, bstore blockstore.Blockstore, options ...Option) *Bitswap {
5960
bs := &Bitswap{
60-
net: net,
61+
net: net,
62+
serverEnabled: true,
6163
}
6264

6365
var serverOptions []server.Option
@@ -83,19 +85,24 @@ func New(ctx context.Context, net network.BitSwapNetwork, providerFinder routing
8385
}
8486

8587
ctx = metrics.CtxSubScope(ctx, "bitswap")
86-
87-
bs.Server = server.New(ctx, net, bstore, serverOptions...)
88-
bs.Client = client.New(ctx, net, providerFinder, bstore, append(clientOptions, client.WithBlockReceivedNotifier(bs.Server))...)
88+
if bs.serverEnabled {
89+
bs.Server = server.New(ctx, net, bstore, serverOptions...)
90+
clientOptions = append(clientOptions, client.WithBlockReceivedNotifier(bs.Server))
91+
}
92+
bs.Client = client.New(ctx, net, providerFinder, bstore, clientOptions...)
8993
net.Start(bs) // use the polyfill receiver to log received errors and trace messages only once
9094

9195
return bs
9296
}
9397

9498
func (bs *Bitswap) NotifyNewBlocks(ctx context.Context, blks ...blocks.Block) error {
95-
return multierr.Combine(
96-
bs.Client.NotifyNewBlocks(ctx, blks...),
97-
bs.Server.NotifyNewBlocks(ctx, blks...),
98-
)
99+
if bs.Server != nil {
100+
return multierr.Combine(
101+
bs.Client.NotifyNewBlocks(ctx, blks...),
102+
bs.Server.NotifyNewBlocks(ctx, blks...),
103+
)
104+
}
105+
return bs.Client.NotifyNewBlocks(ctx, blks...)
99106
}
100107

101108
type Stat struct {
@@ -115,46 +122,63 @@ func (bs *Bitswap) Stat() (*Stat, error) {
115122
if err != nil {
116123
return nil, err
117124
}
118-
ss, err := bs.Server.Stat()
119-
if err != nil {
120-
return nil, err
121-
}
122125

123-
return &Stat{
126+
// Initialize stat with client stats
127+
stat := &Stat{
124128
Wantlist: cs.Wantlist,
125129
BlocksReceived: cs.BlocksReceived,
126130
DataReceived: cs.DataReceived,
127131
DupBlksReceived: cs.DupBlksReceived,
128132
DupDataReceived: cs.DupDataReceived,
129133
MessagesReceived: cs.MessagesReceived,
130-
Peers: ss.Peers,
131-
BlocksSent: ss.BlocksSent,
132-
DataSent: ss.DataSent,
133-
}, nil
134+
// Server stats will be added conditionally
135+
}
136+
137+
// Stats only available if server is enabled
138+
if bs.Server != nil {
139+
ss, err := bs.Server.Stat()
140+
if err != nil {
141+
return stat, fmt.Errorf("failed to get server stats: %w", err)
142+
}
143+
stat.Peers = ss.Peers
144+
stat.BlocksSent = ss.BlocksSent
145+
stat.DataSent = ss.DataSent
146+
}
147+
148+
return stat, nil
134149
}
135150

136151
func (bs *Bitswap) Close() error {
137152
bs.net.Stop()
138153
bs.Client.Close()
139-
bs.Server.Close()
154+
if bs.Server != nil {
155+
bs.Server.Close()
156+
}
140157
return nil
141158
}
142159

143160
func (bs *Bitswap) WantlistForPeer(p peer.ID) []cid.Cid {
144161
if p == bs.net.Self() {
145162
return bs.Client.GetWantlist()
146163
}
147-
return bs.Server.WantlistForPeer(p)
164+
if bs.Server != nil {
165+
return bs.Server.WantlistForPeer(p)
166+
}
167+
return nil
148168
}
149169

150170
func (bs *Bitswap) PeerConnected(p peer.ID) {
151171
bs.Client.PeerConnected(p)
152-
bs.Server.PeerConnected(p)
172+
if bs.Server != nil {
173+
bs.Server.PeerConnected(p)
174+
}
153175
}
154176

155177
func (bs *Bitswap) PeerDisconnected(p peer.ID) {
156178
bs.Client.PeerDisconnected(p)
157-
bs.Server.PeerDisconnected(p)
179+
if bs.Server != nil {
180+
bs.Server.PeerDisconnected(p)
181+
}
158182
}
159183

160184
func (bs *Bitswap) ReceiveError(err error) {
@@ -169,5 +193,7 @@ func (bs *Bitswap) ReceiveMessage(ctx context.Context, p peer.ID, incoming messa
169193
}
170194

171195
bs.Client.ReceiveMessage(ctx, p, incoming)
172-
bs.Server.ReceiveMessage(ctx, p, incoming)
196+
if bs.Server != nil {
197+
bs.Server.ReceiveMessage(ctx, p, incoming)
198+
}
173199
}

bitswap/bitswap_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
tu "github.com/libp2p/go-libp2p-testing/etc"
2626
p2ptestutil "github.com/libp2p/go-libp2p-testing/netutil"
2727
peer "github.com/libp2p/go-libp2p/core/peer"
28+
"github.com/multiformats/go-multiaddr"
2829
)
2930

3031
const blockSize = 4
@@ -832,3 +833,121 @@ func TestWithScoreLedger(t *testing.T) {
832833
t.Fatal("Expected the score ledger to be closed within 5s")
833834
}
834835
}
836+
837+
// TestWithServerDisabled tests that BitSwap can function properly with the server disabled.
838+
// In this mode, it should still be able to request and receive blocks from other peers,
839+
// but should not respond to requests from others.
840+
func TestWithServerDisabled(t *testing.T) {
841+
net := tn.VirtualNetwork(delay.Fixed(kNetworkDelay))
842+
block := blocks.NewBlock([]byte("block"))
843+
router := mockrouting.NewServer()
844+
845+
// Create instance generator with default options
846+
igWithServer := testinstance.NewTestInstanceGenerator(net, router, nil, nil)
847+
defer igWithServer.Close()
848+
849+
// Create instance generator with server disabled
850+
igNoServer := testinstance.NewTestInstanceGenerator(net, router, nil, []bitswap.Option{
851+
bitswap.WithServerEnabled(false),
852+
})
853+
defer igNoServer.Close()
854+
855+
// Create two peers:
856+
// - peerWithServer: has server enabled and has the block
857+
// - peerNoServer: has server disabled
858+
peerWithServer := igWithServer.Next()
859+
peerNoServer := igNoServer.Next()
860+
861+
// We need to connect the peers so they can communicate
862+
connectPeers(t, net, peerWithServer, peerNoServer)
863+
864+
// Add block to peerWithServer
865+
addBlock(t, context.Background(), peerWithServer, block)
866+
867+
// Test 1: peerNoServer should be able to get a block from peerWithServer
868+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
869+
defer cancel()
870+
receivedBlock, err := peerNoServer.Exchange.GetBlock(ctx, block.Cid())
871+
if err != nil {
872+
t.Fatal(err)
873+
}
874+
if !bytes.Equal(block.RawData(), receivedBlock.RawData()) {
875+
t.Fatal("Data doesn't match")
876+
}
877+
878+
// Test 2: Now try the opposite - peerWithServer should NOT be able to get a block from peerNoServer
879+
// because peerNoServer has server disabled and won't respond to requests
880+
newBlock := blocks.NewBlock([]byte("newblock"))
881+
addBlock(t, context.Background(), peerNoServer, newBlock)
882+
883+
ctx2, cancel2 := context.WithTimeout(context.Background(), 500*time.Millisecond)
884+
defer cancel2()
885+
_, err = peerWithServer.Exchange.GetBlock(ctx2, newBlock.Cid())
886+
887+
// We expect this to fail with deadline exceeded because peerNoServer won't respond
888+
if err != context.DeadlineExceeded {
889+
t.Fatalf("Expected DeadlineExceeded error, got: %v", err)
890+
}
891+
892+
// Test 3: Verify that Stat() returns correct values even with server disabled
893+
noServerStat, err := peerNoServer.Exchange.Stat()
894+
if err != nil {
895+
t.Fatal(err)
896+
}
897+
898+
// With server disabled, we should still have valid client stats
899+
if noServerStat.BlocksReceived != 1 {
900+
t.Errorf("Expected BlocksReceived to be 1, got %d", noServerStat.BlocksReceived)
901+
}
902+
if noServerStat.DataReceived != uint64(len(block.RawData())) {
903+
t.Errorf("Expected DataReceived to be %d, got %d", len(block.RawData()), noServerStat.DataReceived)
904+
}
905+
906+
// With server disabled, server stats should be zero
907+
if noServerStat.BlocksSent != 0 {
908+
t.Errorf("Expected BlocksSent to be 0, got %d", noServerStat.BlocksSent)
909+
}
910+
if noServerStat.DataSent != 0 {
911+
t.Errorf("Expected DataSent to be 0, got %d", noServerStat.DataSent)
912+
}
913+
}
914+
915+
// Helper function to connect two peers
916+
func connectPeers(t *testing.T, net tn.Network, a, b testinstance.Instance) {
917+
t.Helper()
918+
err := a.Adapter.Connect(context.Background(), peer.AddrInfo{
919+
ID: b.Identity.ID(),
920+
Addrs: []multiaddr.Multiaddr{b.Identity.Address()},
921+
})
922+
if err != nil {
923+
t.Fatal(err)
924+
}
925+
}
926+
927+
// TestServerDisabledNotifyNewBlocks tests that NotifyNewBlocks works correctly
928+
// when the server is disabled.
929+
func TestServerDisabledNotifyNewBlocks(t *testing.T) {
930+
net := tn.VirtualNetwork(delay.Fixed(kNetworkDelay))
931+
router := mockrouting.NewServer()
932+
933+
// Create instance with server disabled
934+
ig := testinstance.NewTestInstanceGenerator(net, router, nil, []bitswap.Option{
935+
bitswap.WithServerEnabled(false),
936+
})
937+
defer ig.Close()
938+
939+
instance := ig.Next()
940+
block := blocks.NewBlock([]byte("test block"))
941+
942+
// Add block to blockstore
943+
err := instance.Blockstore.Put(context.Background(), block)
944+
if err != nil {
945+
t.Fatal(err)
946+
}
947+
948+
// Notify about new block - this should not cause errors even with server disabled
949+
err = instance.Exchange.NotifyNewBlocks(context.Background(), block)
950+
if err != nil {
951+
t.Fatalf("NotifyNewBlocks failed with server disabled: %s", err)
952+
}
953+
}

bitswap/options.go

+8
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ func WithTracer(tap tracer.Tracer) Option {
103103
}
104104
}
105105

106+
func WithServerEnabled(enabled bool) Option {
107+
return Option{
108+
option(func(bs *Bitswap) {
109+
bs.serverEnabled = enabled
110+
}),
111+
}
112+
}
113+
106114
func WithClientOption(opt client.Option) Option {
107115
return Option{opt}
108116
}

0 commit comments

Comments
 (0)