1
-// Package balanced provides methods to build balanced DAGs.
2
-// In a balanced DAG, nodes are added to a single root
3
-// until the maximum number of links is reached (with leaves
4
-// being at depth 0). Then, a new root is created, and points to the
5
-// old root, and incorporates a new child, which proceeds to be
6
-// filled up (link) to more leaves. In all cases, the Data (chunks)
7
-// is stored only at the leaves, with the rest of nodes only
8
-// storing links to their children.
9
-//
10
-// In a balanced DAG, nodes fill their link capacity before
11
-// creating new ones, thus depth only increases when the
12
-// current tree is completely full.
13
-//
14
-// Balanced DAGs are generalistic DAGs in which all leaves
15
-// are at the same distance from the root.
1
+// Package balanced provides methods to build balanced DAGs, which are generalistic
2
+// DAGs in which all leaves (nodes representing chunks of data) are at the same
3
+// distance from the root. Nodes can have only a maximum number of children; to be
4
+// able to store more leaf data nodes balanced DAGs are extended by increasing its
5
+// depth (and having more intermediary nodes).
6
+//
7
+// Internal nodes are always represented by UnixFS nodes (of type `File`) encoded
8
+// inside DAG nodes (see the `go-ipfs/unixfs` package for details of UnixFS). In
9
+// contrast, leaf nodes with data have multiple possible representations: UnixFS
10
+// nodes as above, raw nodes with just the file data (no format) and Filestore
11
+// nodes (that directly link to the file on disk using a format stored on a raw
12
+// node, see the `go-ipfs/filestore` package for details of Filestore.)
13
+//
14
+// In the case the entire file fits into just one node it will be formatted as a
15
+// (single) leaf node (without parent) with the possible representations already
16
+// mentioned. This is the only scenario where the root can be of a type different
17
+// that the UnixFS node.
18
+//
19
+// +-------------+
20
+// | Root 4 |
21
+// +-------------+
22
+// |
23
+// +--------------------------+----------------------------+
24
+// | |
25
+// +-------------+ +-------------+
26
+// | Node 2 | | Node 5 |
27
+// +-------------+ +-------------+
28
+// | |
29
+// +-------------+-------------+ +-------------+
30
+// | | |
31
+// +-------------+ +-------------+ +-------------+
32
+// | Node 1 | | Node 3 | | Node 6 |
33
+// +-------------+ +-------------+ +-------------+
34
+// | | |
35
+// +------+------+ +------+------+ +------+
36
+// | | | | |
37
+// +=========+ +=========+ +=========+ +=========+ +=========+
38
+// | Chunk 1 | | Chunk 2 | | Chunk 3 | | Chunk 4 | | Chunk 5 |
39
+// +=========+ +=========+ +=========+ +=========+ +=========+
40
+//
41
package balanced
42
43
import (
44
"errors"
45
46
h "github.com/ipfs/go-ipfs/importer/helpers"
47
+ ft "github.com/ipfs/go-ipfs/unixfs"
48
49
ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format"
50
)
51
26
-// Layout builds a balanced DAG. Data is stored at the leaves
27
-// and depth only increases when the tree is full, that is, when
28
-// the root node has reached the maximum number of links.
52
+// Layout builds a balanced DAG layout. In a balanced DAG of depth 1, leaf nodes
53
+// with data are added to a single `root` until the maximum number of links is
54
+// reached. Then, to continue adding more data leaf nodes, a `newRoot` is created
55
+// pointing to the old `root` (which will now become and intermediary node),
56
+// increasing the depth of the DAG to 2. This will increase the maximum number of
57
+// data leaf nodes the DAG can have (`Maxlinks() ^ depth`). The `fillNodeRec`
58
+// function will add more intermediary child nodes to `newRoot` (which already has
59
+// `root` as child) that in turn will have leaf nodes with data added to them.
60
+// After that process is completed (the maximum number of links is reached),
61
+// `fillNodeRec` will return and the loop will be repeated: the `newRoot` created
62
+// will become the old `root` and a new root will be created again to increase the
63
+// depth of the DAG. The process is repeated until there is no more data to add
64
+// (i.e. the DagBuilderHelper’s Done() function returns true).
65
+//
66
+// The nodes are filled recursively, so the DAG is built from the bottom up. Leaf
67
+// nodes are created first using the chunked file data and its size. The size is
68
+// then bubbled up to the parent (internal) node, which aggregates all the sizes of
69
+// its children and bubbles that combined size up to its parent, and so on up to
70
+// the root. This way, a balanced DAG acts like a B-tree when seeking to a byte
71
+// offset in the file the graph represents: each internal node uses the file size
72
+// of its children as an index when seeking.
73
+//
74
+// `Layout` creates a root and hands it off to be filled:
75
+//
76
+// +-------------+
77
+// | Root 1 |
78
+// +-------------+
79
+// |
80
+// ( fillNodeRec fills in the )
81
+// ( chunks on the root. )
82
+// |
83
+// +------+------+
84
+// | |
85
+// + - - - - + + - - - - +
86
+// | Chunk 1 | | Chunk 2 |
87
+// + - - - - + + - - - - +
88
+//
89
+// ↓
90
+// When the root is full but there's more data...
91
+// ↓
92
+//
93
+// +-------------+
94
+// | Root 1 |
95
+// +-------------+
96
+// |
97
+// +------+------+
98
+// | |
99
+// +=========+ +=========+ + - - - - +
100
+// | Chunk 1 | | Chunk 2 | | Chunk 3 |
101
+// +=========+ +=========+ + - - - - +
102
+//
103
+// ↓
104
+// ...Layout's job is to create a new root.
105
+// ↓
106
+//
107
+// +-------------+
108
+// | Root 2 |
109
+// +-------------+
110
+// |
111
+// +-------------+ - - - - - - - - +
112
+// | |
113
+// +-------------+ ( fillNodeRec creates the )
114
+// | Node 1 | ( branch that connects )
115
+// +-------------+ ( "Root 2" to "Chunk 3." )
116
+// | |
117
+// +------+------+ + - - - - -+
118
+// | | |
119
+// +=========+ +=========+ + - - - - +
120
+// | Chunk 1 | | Chunk 2 | | Chunk 3 |
121
+// +=========+ +=========+ + - - - - +
122
+//
123
func Layout(db *h.DagBuilderHelper) (ipld.Node, error) {
30
- var offset uint64
31
- var root *h.UnixfsNode
32
- for level := 0; !db.Done(); level++ {
33
-
34
- nroot := db.NewUnixfsNode()
35
- db.SetPosInfo(nroot, 0)
36
-
37
- // add our old root as a child of the new root.
38
- if root != nil { // nil if it's the first node.
39
- if err := nroot.AddChild(root, db); err != nil {
40
- return nil, err
41
- }
42
- }
43
-
44
- // fill it up.
45
- if err := fillNodeRec(db, nroot, level, offset); err != nil {
46
- return nil, err
47
- }
48
-
49
- offset = nroot.FileSize()
50
- root = nroot
51
-
52
- }
53
- if root == nil {
54
- // this should only happen with an empty node, so return a leaf
55
- var err error
56
- root, err = db.NewLeaf(nil)
124
+ if db.Done() {
125
+ // No data, return just an empty node.
126
+ root, err := db.NewLeafNode(nil)
127
if err != nil {
128
return nil, err
129
}
130
+ // This works without Filestore support (`ProcessFileStore`).
131
+ // TODO: Why? Is there a test case missing?
132
+
133
+ return db.AddNodeAndClose(root)
134
}
135
62
- out, err := db.Add(root)
136
+ // The first `root` will be a single leaf node with data
137
+ // (corner case), after that subsequent `root` nodes will
138
+ // always be internal nodes (with a depth > 0) that can
139
+ // be handled by the loop.
140
+ root, fileSize, err := db.NewLeafDataNode()
141
if err != nil {
142
return nil, err
143
}
144
67
- err = db.Close()
68
- if err != nil {
69
- return nil, err
145
+ // Each time a DAG of a certain `depth` is filled (because it
146
+ // has reached its maximum capacity of `db.Maxlinks()` per node)
147
+ // extend it by making it a sub-DAG of a bigger DAG with `depth+1`.
148
+ for depth := 1; !db.Done(); depth++ {
149
+
150
+ // Add the old `root` as a child of the `newRoot`.
151
+ newRoot := db.NewFSNodeOverDag(ft.TFile)
152
+ newRoot.AddChild(root, fileSize, db)
153
+
154
+ // Fill the `newRoot` (that has the old `root` already as child)
155
+ // and make it the current `root` for the next iteration (when
156
+ // it will become "old").
157
+ root, fileSize, err = fillNodeRec(db, newRoot, depth)
158
+ if err != nil {
159
+ return nil, err
160
+ }
161
}
162
72
- return out, nil
163
+ return db.AddNodeAndClose(root)
164
}
165
75
-// fillNodeRec will fill the given node with data from the dagBuilders input
76
-// source down to an indirection depth as specified by 'depth'
77
-// it returns the total dataSize of the node, and a potential error
166
+// fillNodeRec will "fill" the given internal (non-leaf) `node` with data by
167
+// adding child nodes to it, either leaf data nodes (if `depth` is 1) or more
168
+// internal nodes with higher depth (and calling itself recursively on them
169
+// until *they* are filled with data). The data to fill the node with is
170
+// provided by DagBuilderHelper.
171
+//
172
+// `node` represents a (sub-)DAG root that is being filled. If called recursively,
173
+// it is `nil`, a new node is created. If it has been called from `Layout` (see
174
+// diagram below) it points to the new root (that increases the depth of the DAG),
175
+// it already has a child (the old root). New children will be added to this new
176
+// root, and those children will in turn be filled (calling `fillNodeRec`
177
+// recursively).
178
+//
179
+// +-------------+
180
+// | `node` |
181
+// | (new root) |
182
+// +-------------+
183
+// |
184
+// +-------------+ - - - - - - + - - - - - - - - - - - +
185
+// | | |
186
+// +--------------+ + - - - - - + + - - - - - +
187
+// | (old root) | | new child | | |
188
+// +--------------+ + - - - - - + + - - - - - +
189
+// | | |
190
+// +------+------+ + - - + - - - +
191
+// | | | |
192
+// +=========+ +=========+ + - - - - + + - - - - +
193
+// | Chunk 1 | | Chunk 2 | | Chunk 3 | | Chunk 4 |
194
+// +=========+ +=========+ + - - - - + + - - - - +
195
+//
196
+// The `node` to be filled uses the `FSNodeOverDag` abstraction that allows adding
197
+// child nodes without packing/unpacking the UnixFS layer node (having an internal
198
+// `ft.FSNode` cache).
199
+//
200
+// It returns the `ipld.Node` representation of the passed `node` filled with
201
+// children and the `nodeFileSize` with the total size of the file chunk (leaf)
202
+// nodes stored under this node (parent nodes store this to enable efficient
203
+// seeking through the DAG when reading data later).
204
//
205
// warning: **children** pinned indirectly, but input node IS NOT pinned.
80
-func fillNodeRec(db *h.DagBuilderHelper, node *h.UnixfsNode, depth int, offset uint64) error {
81
- if depth < 0 {
82
- return errors.New("attempt to fillNode at depth < 0")
206
+func fillNodeRec(db *h.DagBuilderHelper, node *h.FSNodeOverDag, depth int) (filledNode ipld.Node, nodeFileSize uint64, err error) {
207
+ if depth < 1 {
208
+ return nil, 0, errors.New("attempt to fillNode at depth < 1")
209
}
210
85
- // Base case
86
- if depth <= 0 { // catch accidental -1's in case error above is removed.
87
- child, err := db.GetNextDataNode()
88
- if err != nil {
89
- return err
90
- }
91
-
92
- node.Set(child)
93
- return nil
211
+ if node == nil {
212
+ node = db.NewFSNodeOverDag(ft.TFile)
213
}
214
96
- // while we have room AND we're not done
215
+ // Child node created on every iteration to add to parent `node`.
216
+ // It can be a leaf node or another internal node.
217
+ var childNode ipld.Node
218
+ // File size from the child node needed to update the `FSNode`
219
+ // in `node` when adding the child.
220
+ var childFileSize uint64
221
+
222
+ // While we have room and there is data available to be added.
223
for node.NumChildren() < db.Maxlinks() && !db.Done() {
98
- child := db.NewUnixfsNode()
99
- db.SetPosInfo(child, offset)
224
101
- err := fillNodeRec(db, child, depth-1, offset)
102
- if err != nil {
103
- return err
225
+ if depth == 1 {
226
+ // Base case: add leaf node with data.
227
+ childNode, childFileSize, err = db.NewLeafDataNode()
228
+ if err != nil {
229
+ return nil, 0, err
230
+ }
231
+ } else {
232
+ // Recursion case: create an internal node to in turn keep
233
+ // descending in the DAG and adding child nodes to it.
234
+ childNode, childFileSize, err = fillNodeRec(db, nil, depth-1)
235
+ if err != nil {
236
+ return nil, 0, err
237
+ }
238
}
239
106
- if err := node.AddChild(child, db); err != nil {
107
- return err
240
+ err = node.AddChild(childNode, childFileSize, db)
241
+ if err != nil {
242
+ return nil, 0, err
243
}
109
- offset += child.FileSize()
244
}
245
112
- return nil
246
+ nodeFileSize = node.FileSize()
247
+
248
+ // Get the final `dag.ProtoNode` with the `FSNode` data encoded inside.
249
+ filledNode, err = node.Commit()
250
+ if err != nil {
251
+ return nil, 0, err
252
+ }
253
+
254
+ return filledNode, nodeFileSize, nil
255
}