@cryptotaxi247 / kubo / commits / 43a26ce8c

fix(rpc): CARv2 import over HTTP API (#11253)

* test(cli): add CARv2 import over HTTP API test Regression test for https://github.com/ipfs/kubo/issues/9361. Imports a CARv2 fixture via the daemon (online mode) and verifies the blocks are accessible. Currently fails with "operation not supported" due to the multipart reader not supporting seeking. * fix(cmd): support CARv2 import over HTTP API Strip the io.Seeker interface from the file before passing it to go-car's NewBlockReader. Over the HTTP API the underlying reader is a multipart stream that cannot seek, but boxo's ReaderFile advertises io.Seeker and returns ErrNotSupported at runtime. Hiding the interface lets go-car fall back to forward-only reading. Fixes https://github.com/ipfs/kubo/issues/9361 --------- Co-authored-by: Andrew Gillis <11790789+gammazero@users.noreply.github.com>

Marcin Rataj committed Mar 31, 2026 at 16:30 UTC 43a26ce8c099b4b9e6825e458d888ebbdbfa285c
3 files changed +50 -1
core/commands/dag/import.go
+11 -1
@@ -113,7 +113,17 @@ func dagImport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment
113
114 var previous blocks.Block
115
116 - car, err := gocarv2.NewBlockReader(file)
116 + // Wrap the file to hide the io.Seeker interface.
117 + // Over the HTTP API the underlying reader is a multipart stream
118 + // that cannot seek, but boxo's ReaderFile advertises io.Seeker
119 + // anyway and returns ErrNotSupported at runtime. Hiding the
120 + // interface lets go-car fall back to sequential (forward-only)
121 + // reading, which is all that CARv2 streaming needs.
122 + // See https://github.com/ipfs/kubo/issues/9361
123 + car, err := gocarv2.NewBlockReader(struct {
124 + io.Reader
125 + io.Closer
126 + }{file, file})
127 if err != nil {
128 return err
129 }
test/cli/dag_test.go
+39
@@ -109,6 +109,45 @@ func TestDag(t *testing.T) {
109 })
110 }
111
112 +func TestDagImportCARv2(t *testing.T) {
113 + t.Parallel()
114 + // Regression test for https://github.com/ipfs/kubo/issues/9361
115 + // CARv2 import fails with "operation not supported" when using the HTTP API
116 + // because the multipart reader doesn't support seeking, but the boxo
117 + // ReaderFile falsely advertises io.Seeker compliance.
118 +
119 + carv2Fixture := "./fixtures/TestDagStatCARv2.car"
120 +
121 + t.Run("CARv2 import via HTTP API (online)", func(t *testing.T) {
122 + t.Parallel()
123 + node := harness.NewT(t).NewNode().Init().StartDaemon()
124 + defer node.StopDaemon()
125 +
126 + r, err := os.Open(carv2Fixture)
127 + require.NoError(t, err)
128 + defer r.Close()
129 +
130 + // Use Runner.Run (not MustRun) so the test captures errors
131 + // instead of panicking -- this lets us assert on the result.
132 + res := node.Runner.Run(harness.RunRequest{
133 + Path: node.IPFSBin,
134 + Args: []string{"dag", "import", "--pin-roots=false"},
135 + CmdOpts: []harness.CmdOpt{
136 + harness.RunWithStdin(r),
137 + },
138 + })
139 + require.Equal(t, 0, res.ExitCode(), "CARv2 import should succeed over HTTP API, stderr: %s", res.Stderr.String())
140 +
141 + // Verify the imported blocks are accessible
142 + stat := node.RunIPFS("dag", "stat", "--progress=false", "--enc=json", fixtureCid)
143 + var data Data
144 + err = json.Unmarshal(stat.Stdout.Bytes(), &data)
145 + require.NoError(t, err)
146 + // root + node1 + node2 + shared child = 4 unique blocks
147 + require.Equal(t, 4, data.UniqueBlocks)
148 + })
149 +}
150 +
151 func TestDagImportFastProvide(t *testing.T) {
152 t.Parallel()
153
test/cli/fixtures/TestDagStatCARv2.car
Binary files /dev/null and b/test/cli/fixtures/TestDagStatCARv2.car differ