fix(cmd): useful errors in dag import (#9945)
* fix: useful errors during dag import Most of the time the error is either a bitflip in one of blocks, or a truncation of car stream. This allows user to understand what happened and at which place in the car stream, making debug more humane. * fix: correct message when root pin failed this also correctly exits CLI commands with code 1 (was silent false-positive 0 before)
Marcin Rataj committed
Jun 14, 2023 at 20:55 UTC
0e52389016de1299d7a83b7d4d32289004690074
2 files changed
+22
-5
core/commands/dag/dag.go
+1
-1
@@ -237,7 +237,7 @@ Specification of CAR formats: https://ipld.io/specs/transport/car/
237
}
238
239
if event.Root.PinErrorMsg != "" {
240
- event.Root.PinErrorMsg = fmt.Sprintf("FAILED: %s", event.Root.PinErrorMsg)
240
+ return fmt.Errorf("pinning root %q FAILED: %s", enc.Encode(event.Root.Cid), event.Root.PinErrorMsg)
241
} else {
242
event.Root.PinErrorMsg = "success"
243
}
core/commands/dag/import.go
+21
-4
@@ -2,11 +2,13 @@ package dagcmd
2
3
import (
4
"errors"
5
+ "fmt"
6
"io"
7
8
"github.com/ipfs/boxo/coreiface/options"
9
"github.com/ipfs/boxo/files"
10
gocarv2 "github.com/ipfs/boxo/ipld/car/v2"
11
+ blocks "github.com/ipfs/go-block-format"
12
cid "github.com/ipfs/go-cid"
13
cmds "github.com/ipfs/go-ipfs-cmds"
14
ipld "github.com/ipfs/go-ipld-format"
@@ -58,6 +60,18 @@ func dagImport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment
60
roots := cid.NewSet()
61
var blockCount, blockBytesCount uint64
62
63
+ // remember last valid block and provide a meaningful error message
64
+ // when a truncated/mangled CAR is being imported
65
+ importError := func(previous blocks.Block, current blocks.Block, err error) error {
66
+ if current != nil {
67
+ return fmt.Errorf("import failed at block %q: %w", current.Cid(), err)
68
+ }
69
+ if previous != nil {
70
+ return fmt.Errorf("import failed after block %q: %w", previous.Cid(), err)
71
+ }
72
+ return fmt.Errorf("import failed: %w", err)
73
+ }
74
+
75
it := req.Files.Entries()
76
for it.Next() {
77
file := files.FileFromEntry(it)
@@ -75,6 +89,8 @@ func dagImport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment
89
// this won't/can't help with not running out of handles
90
defer file.Close()
91
92
+ var previous blocks.Block
93
+
94
car, err := gocarv2.NewBlockReader(file)
95
if err != nil {
96
return err
@@ -87,25 +103,26 @@ func dagImport(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment
103
for {
104
block, err := car.Next()
105
if err != nil && err != io.EOF {
90
- return err
106
+ return importError(previous, block, err)
107
} else if block == nil {
108
break
109
}
110
if err := cmdutils.CheckBlockSize(req, uint64(len(block.RawData()))); err != nil {
95
- return err
111
+ return importError(previous, block, err)
112
}
113
114
// the double-decode is suboptimal, but we need it for batching
115
nd, err := blockDecoder.DecodeNode(req.Context, block)
116
if err != nil {
101
- return err
117
+ return importError(previous, block, err)
118
}
119
120
if err := batch.Add(req.Context, nd); err != nil {
105
- return err
121
+ return importError(previous, block, err)
122
}
123
blockCount++
124
blockBytesCount += uint64(len(block.RawData()))
125
+ previous = block
126
}
127
return nil
128
}()