Skip to content

Commit 77bd614

Browse files
authored
fix: raise block size limits from 1MiB to 2MiB (#1101)
* fix: raise block size limits from 1MiB to 2MiB align chunker and importer block size limits with the bitswap spec (https://specs.ipfs.tech/bitswap-protocol/#block-sizes) which mandates 2MiB as the maximum block size. the previous 1MiB limit broke `dag import` of 1MiB-chunked non-raw-leaf data where protobuf wrapping pushes blocks slightly over 1MiB. max chunker size is set to 2MiB - 256 bytes to leave room for protobuf framing overhead when chunks are wrapped in non-raw leaves. IPIP-499 profiles use lower chunk sizes (256KiB and 1MiB) and are not affected. * test: add guard tests for block size limits and transport fit - chunker: verify ChunkSizeLimit + ChunkOverheadBudget == BlockSizeLimit - bitswap/message: verify BlockSizeLimit block (CIDv1+raw+SHA2-256) serializes within libp2p network.MessageSizeMax and round-trips - use explicit byte values instead of bit-shift notation
1 parent 50b2cf5 commit 77bd614

File tree

5 files changed

+104
-5
lines changed

5 files changed

+104
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The following emojis are used to highlight certain changes:
2222

2323
### Changed
2424

25+
- 🛠 `chunker`, `ipld/unixfs/importer/helpers`: block size limits raised from 1MiB to 2MiB to match the [bitswap spec](https://specs.ipfs.tech/bitswap-protocol/#block-sizes). Max chunker size is `2MiB - 256 bytes` to leave room for protobuf framing when `--raw-leaves=false`. IPIP-499 profiles use lower chunk sizes (256KiB and 1MiB) and are not affected.
2526
- 🛠 `chunker`: `DefaultBlockSize` changed from `const` to `var` to allow runtime configuration via global profiles. [#1088](https://github.com/ipfs/boxo/pull/1088), [IPIP-499](https://github.com/ipfs/specs/pull/499)
2627
- `gateway`: 🛠 ✨ [IPIP-523](https://github.com/ipfs/specs/pull/523) `?format=` URL query parameter now takes precedence over `Accept` HTTP header, ensuring deterministic HTTP cache behavior and allowing browsers to use `?format=` even when they send `Accept` headers with specific content types. [#1074](https://github.com/ipfs/boxo/pull/1074)
2728
- `gateway`: 🛠 ✨ [IPIP-524](https://github.com/ipfs/specs/pull/524) codec conversions (e.g., dag-pb to dag-json, dag-json to dag-cbor) are no longer performed by default. Requesting a format that differs from the block's codec now returns HTTP 406 Not Acceptable with a hint to fetch raw blocks (`?format=raw`) and convert client-side. Set `Config.AllowCodecConversion` to `true` to restore the old behavior. [#1077](https://github.com/ipfs/boxo/pull/1077)

bitswap/message/message_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ import (
77

88
"github.com/ipfs/boxo/bitswap/client/wantlist"
99
pb "github.com/ipfs/boxo/bitswap/message/pb"
10+
chunker "github.com/ipfs/boxo/chunker"
1011
blocks "github.com/ipfs/go-block-format"
1112
cid "github.com/ipfs/go-cid"
1213
"github.com/ipfs/go-test/random"
14+
"github.com/libp2p/go-libp2p/core/network"
15+
mh "github.com/multiformats/go-multihash"
1316
"google.golang.org/protobuf/proto"
1417
)
1518

@@ -302,3 +305,60 @@ func TestEntrySize(t *testing.T) {
302305
t.Fatal("entry size calculation incorrect", e.Size(), proto.Size(epb))
303306
}
304307
}
308+
309+
// TestBlockSizeLimitFitsInLibp2pMessage verifies that a single block at the
310+
// bitswap spec limit (chunker.BlockSizeLimit, 2MiB) fits within the libp2p
311+
// message size limit (network.MessageSizeMax, 4MiB) when serialized as a
312+
// bitswap message. This guards the invariant that the spec block size can
313+
// always be transferred over bitswap-over-libp2p. The test uses CIDv1 with
314+
// raw codec and SHA2-256 multihash, matching the unixfs-v1-2025 profile.
315+
func TestBlockSizeLimitFitsInLibp2pMessage(t *testing.T) {
316+
t.Parallel()
317+
318+
// create a CIDv1 + raw + SHA2-256 block at exactly BlockSizeLimit
319+
data := make([]byte, chunker.BlockSizeLimit)
320+
prefix := cid.Prefix{
321+
Version: 1,
322+
Codec: cid.Raw,
323+
MhType: mh.SHA2_256,
324+
MhLength: -1,
325+
}
326+
c, err := prefix.Sum(data)
327+
if err != nil {
328+
t.Fatal(err)
329+
}
330+
blk, err := blocks.NewBlockWithCid(data, c)
331+
if err != nil {
332+
t.Fatal(err)
333+
}
334+
335+
// build a bitswap message with the block
336+
msg := New(true)
337+
msg.AddBlock(blk)
338+
339+
// serialize and check size fits in libp2p message limit
340+
buf := new(bytes.Buffer)
341+
if err := msg.ToNetV1(buf); err != nil {
342+
t.Fatal(err)
343+
}
344+
wireSize := buf.Len()
345+
if wireSize > network.MessageSizeMax {
346+
t.Fatalf("serialized message (%d bytes) exceeds network.MessageSizeMax (%d bytes)",
347+
wireSize, network.MessageSizeMax)
348+
}
349+
t.Logf("block=%d wire=%d limit=%d headroom=%d bytes",
350+
chunker.BlockSizeLimit, wireSize, network.MessageSizeMax, network.MessageSizeMax-wireSize)
351+
352+
// round-trip: verify FromNet can read it back (uses MessageSizeMax as reader limit)
353+
m2, _, err := FromNet(bytes.NewReader(buf.Bytes()))
354+
if err != nil {
355+
t.Fatalf("FromNet failed: %v", err)
356+
}
357+
received := m2.Blocks()
358+
if len(received) != 1 {
359+
t.Fatalf("expected 1 block, got %d", len(received))
360+
}
361+
if len(received[0].RawData()) != chunker.BlockSizeLimit {
362+
t.Fatalf("expected block of %d bytes, got %d", chunker.BlockSizeLimit, len(received[0].RawData()))
363+
}
364+
}

chunker/parse.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,22 @@ import (
1515
var DefaultBlockSize int64 = 1024 * 256
1616

1717
const (
18-
// ChunkSizeLimit is the maximum allowed chunk size.
19-
// No leaf block should contain more than 1MiB of payload data (wrapping overhead aside).
20-
// See discussion at https://github.com/ipfs/go-ipfs-chunker/pull/21#discussion_r369124879
21-
ChunkSizeLimit int = 1048576
18+
// BlockSizeLimit is the maximum block size defined by the bitswap spec.
19+
// https://specs.ipfs.tech/bitswap-protocol/#block-sizes
20+
BlockSizeLimit int = 2 * 1024 * 1024 // 2MiB
21+
22+
// ChunkOverheadBudget is reserved for protobuf/UnixFS framing overhead
23+
// when chunks are wrapped in non-raw leaves (--raw-leaves=false).
24+
ChunkOverheadBudget int = 256
25+
26+
// ChunkSizeLimit is the maximum chunk size accepted by the chunker.
27+
// It is set below BlockSizeLimit to leave room for framing overhead
28+
// so that serialized blocks stay within the 2MiB wire limit.
29+
//
30+
// In practice this limit only matters for custom chunker sizes.
31+
// The CID-deterministic profiles defined in IPIP-499 use max 1MiB
32+
// chunks, well within this limit.
33+
ChunkSizeLimit int = BlockSizeLimit - ChunkOverheadBudget
2234
)
2335

2436
var (

chunker/parse_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,28 @@ const (
1010
testTwoThirdsOfChunkLimit = 2 * (float32(ChunkSizeLimit) / float32(3))
1111
)
1212

13+
func TestBlockSizeConstants(t *testing.T) {
14+
t.Parallel()
15+
16+
if ChunkOverheadBudget <= 0 {
17+
t.Fatal("ChunkOverheadBudget must be positive")
18+
}
19+
if ChunkSizeLimit <= 0 {
20+
t.Fatal("ChunkSizeLimit must be positive")
21+
}
22+
if BlockSizeLimit <= 0 {
23+
t.Fatal("BlockSizeLimit must be positive")
24+
}
25+
if ChunkSizeLimit+ChunkOverheadBudget != BlockSizeLimit {
26+
t.Fatalf("ChunkSizeLimit (%d) + ChunkOverheadBudget (%d) != BlockSizeLimit (%d)",
27+
ChunkSizeLimit, ChunkOverheadBudget, BlockSizeLimit)
28+
}
29+
if ChunkSizeLimit >= BlockSizeLimit {
30+
t.Fatalf("ChunkSizeLimit (%d) must be less than BlockSizeLimit (%d)",
31+
ChunkSizeLimit, BlockSizeLimit)
32+
}
33+
}
34+
1335
func TestParseRabin(t *testing.T) {
1436
t.Parallel()
1537

ipld/unixfs/importer/helpers/helpers.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package helpers
22

33
import (
44
"errors"
5+
6+
chunker "github.com/ipfs/boxo/chunker"
57
)
68

79
// BlockSizeLimit specifies the maximum size an imported block can have.
8-
var BlockSizeLimit = 1048576 // 1 MB
10+
// Defaults to chunker.BlockSizeLimit (2MiB), the bitswap spec maximum.
11+
// https://specs.ipfs.tech/bitswap-protocol/#block-sizes
12+
var BlockSizeLimit = chunker.BlockSizeLimit
913

1014
// rough estimates on expected sizes
1115
var (

0 commit comments

Comments
 (0)