hypercore 11.0.36 → 11.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -6
- package/lib/audit.js +111 -82
- package/lib/core.js +2 -10
- package/lib/replicator.js +4 -2
- package/lib/session-state.js +22 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Make a new Hypercore instance.
|
|
|
40
40
|
const core = new Hypercore('./directory') // store data in ./directory
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Alternatively you can pass a [Hypercore Storage](https://github.com/holepunchto/hypercore-storage) or use a [Corestore](https://github.com/holepunchto/corestore) if you want to make many Hypercores efficiently.
|
|
43
|
+
Alternatively you can pass a [Hypercore Storage](https://github.com/holepunchto/hypercore-storage) or use a [Corestore](https://github.com/holepunchto/corestore) if you want to make many Hypercores efficiently. Note that `random-access-storage` is no longer supported.
|
|
44
44
|
|
|
45
45
|
`key` can be set to a Hypercore key which is a hash of Hypercore's internal auth manifest, describing how to validate the Hypercore. If you do not set this, it will be loaded from storage. If nothing is previously stored, a new auth manifest will be generated giving you local write access to it.
|
|
46
46
|
|
|
@@ -50,15 +50,17 @@ Alternatively you can pass a [Hypercore Storage](https://github.com/holepunchto/
|
|
|
50
50
|
{
|
|
51
51
|
createIfMissing: true, // create a new Hypercore key pair if none was present in storage
|
|
52
52
|
overwrite: false, // overwrite any old Hypercore that might already exist
|
|
53
|
-
sparse: true, // enable sparse mode, counting unavailable blocks towards core.length and core.byteLength
|
|
54
53
|
valueEncoding: 'json' | 'utf-8' | 'binary', // defaults to binary
|
|
55
54
|
encodeBatch: batch => { ... }, // optionally apply an encoding to complete batches
|
|
56
55
|
keyPair: kp, // optionally pass the public key and secret key as a key pair
|
|
57
|
-
|
|
56
|
+
encryption: { key: buffer }, // the block encryption key
|
|
58
57
|
onwait: () => {}, // hook that is called if gets are waiting for download
|
|
59
58
|
timeout: 0, // wait at max some milliseconds (0 means no timeout)
|
|
60
59
|
writable: true, // disable appends and truncates
|
|
61
|
-
inflightRange: null // Advanced option. Set to [minInflight, maxInflight] to change the min and max inflight blocks per peer when downloading.
|
|
60
|
+
inflightRange: null, // Advanced option. Set to [minInflight, maxInflight] to change the min and max inflight blocks per peer when downloading.
|
|
61
|
+
ongc: (session) => { ... }, // A callback called when the session is garbage collected
|
|
62
|
+
notDownloadingLinger: 20000, // How many milliseconds to wait after downloading finishes keeping the connection open. Defaults to a random number between 20-40s
|
|
63
|
+
allowFork: true, // Enables updating core when it forks
|
|
62
64
|
}
|
|
63
65
|
```
|
|
64
66
|
|
|
@@ -292,7 +294,42 @@ You must close any session you make.
|
|
|
292
294
|
|
|
293
295
|
Options are inherited from the parent instance, unless they are re-set.
|
|
294
296
|
|
|
295
|
-
`options` are the same as in the constructor
|
|
297
|
+
`options` are the same as in the constructor with the follow additions:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
{
|
|
301
|
+
weak: false // Creates the session as a "weak ref" which closes when all non-weak sessions are closed
|
|
302
|
+
exclusive: false, // Create a session with exclusive access to the core. Creating an exclusive session on a core with other exclusive sessions, will wait for the session with access to close before the next exclusive session is `ready`
|
|
303
|
+
checkout: undefined, // A index to checkout the core at. Checkout sessions must be an atom or a named session
|
|
304
|
+
atom: undefined, // A storage atom for making atomic batch changes across hypercores
|
|
305
|
+
name: null, // Name the session creating a persisted branch of the core. Still beta so may break in the future
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Atoms allow making atomic changes across multiple hypercores. Atoms can be created using a `core`'s `storage` (eg. `const atom = core.state.storage.createAtom()`). Changes made with an atom based session is not persisted until the atom is flushed via `await atom.flush()`, but can be read at any time. When atoms flush, all changes made outside of the atom will be clobbered as the core blocks will now match the atom's blocks. For example:
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
const core = new Hypercore('./atom-example')
|
|
313
|
+
await core.ready()
|
|
314
|
+
|
|
315
|
+
await core.append('block 1')
|
|
316
|
+
|
|
317
|
+
const atom = core.state.storage.createAtom()
|
|
318
|
+
const atomicSession = core.session({ atom })
|
|
319
|
+
|
|
320
|
+
await core.append('block 2') // Add blocks not using the atom
|
|
321
|
+
|
|
322
|
+
await atomicSession.append('atom block 2') // Add different block to atom
|
|
323
|
+
await atom.flush()
|
|
324
|
+
|
|
325
|
+
console.log((await core.get(core.length - 1)).toString()) // prints 'atom block 2' not 'block 2'
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### `const { byteLength, length } = core.commit(session, opts = {})`
|
|
329
|
+
|
|
330
|
+
Attempt to apply blocks from the session to the `core`. `core` must be a default core, aka a non-named session.
|
|
331
|
+
|
|
332
|
+
Returns `null` if committing failed.
|
|
296
333
|
|
|
297
334
|
#### `const snapshot = core.snapshot([options])`
|
|
298
335
|
|
|
@@ -394,7 +431,13 @@ Buffer containing the optional block encryption key of this core. Will be `null`
|
|
|
394
431
|
|
|
395
432
|
#### `core.length`
|
|
396
433
|
|
|
397
|
-
How many blocks of data are available on this core
|
|
434
|
+
How many blocks of data are available on this core.
|
|
435
|
+
|
|
436
|
+
Populated after `ready` has been emitted. Will be `0` before the event.
|
|
437
|
+
|
|
438
|
+
#### `core.signedLength`
|
|
439
|
+
|
|
440
|
+
How many blocks of data are available on this core that have been signed by a quorum. This is equal to `core.length` for Hypercores's with a single signer.
|
|
398
441
|
|
|
399
442
|
Populated after `ready` has been emitted. Will be `0` before the event.
|
|
400
443
|
|
package/lib/audit.js
CHANGED
|
@@ -1,121 +1,150 @@
|
|
|
1
|
-
const
|
|
1
|
+
const crypto = require('hypercore-crypto')
|
|
2
2
|
const flat = require('flat-tree')
|
|
3
3
|
const b4a = require('b4a')
|
|
4
|
+
const { MerkleTree } = require('./merkle-tree')
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
module.exports = async function auditCore (core, { tree = true, blocks = true, bitfield = true, dryRun = false } = {}) {
|
|
7
|
+
const length = core.state.length
|
|
8
|
+
const stats = { treeNodes: 0, blocks: 0, bits: 0, droppedTreeNodes: 0, droppedBlocks: 0, droppedBits: 0, corrupt: false }
|
|
6
9
|
|
|
7
|
-
//
|
|
8
|
-
|
|
10
|
+
// audit the tree
|
|
11
|
+
if (tree) {
|
|
12
|
+
let tx = null
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
tree: 0,
|
|
13
|
-
blocks: 0
|
|
14
|
-
}
|
|
14
|
+
const roots = await MerkleTree.getRootsFromStorage(core.state.storage, length)
|
|
15
|
+
const stack = []
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
for (const r of roots) {
|
|
18
|
+
if (r === null) {
|
|
19
|
+
if (!dryRun) {
|
|
20
|
+
const ptr = core.storage.core
|
|
21
|
+
await core.storage.store.deleteCore(ptr)
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
stats.corrupt = true
|
|
26
|
+
}
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
stack.push(r)
|
|
29
|
+
}
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
const stack = []
|
|
31
|
+
stats.treeNodes += roots.length
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stack.push(r)
|
|
29
|
-
}
|
|
33
|
+
while (stack.length > 0) {
|
|
34
|
+
const node = stack.pop()
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
const node = stack.pop()
|
|
33
|
-
if ((node.index & 1) === 0) continue
|
|
36
|
+
if ((node.index & 1) === 0) continue
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
const leftNode = tree.get(left)
|
|
37
|
-
const rightNode = tree.get(right)
|
|
38
|
+
const [left, right] = flat.children(node.index)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
const rx = core.state.storage.read()
|
|
41
|
+
const leftNodePromise = rx.getTreeNode(left)
|
|
42
|
+
const rightNodePromise = rx.getTreeNode(right)
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
rx.tryFlush()
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const [leftNode, rightNode] = await Promise.all([leftNodePromise, rightNodePromise])
|
|
47
|
+
|
|
48
|
+
if (isBadTree(node, leftNode, rightNode)) {
|
|
49
|
+
if (!tx && !stats.corrupt) tx = core.state.storage.write()
|
|
50
|
+
const [l, r] = flat.spans(node.index)
|
|
51
|
+
tx.deleteTreeNodeRange(l, r + 1)
|
|
52
|
+
stats.droppedTreeNodes++
|
|
48
53
|
continue
|
|
49
54
|
}
|
|
55
|
+
|
|
56
|
+
if (!leftNode) continue
|
|
57
|
+
|
|
58
|
+
stats.treeNodes += 2
|
|
59
|
+
stack.push(leftNode, rightNode)
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
if (
|
|
53
|
-
if (rightNode.size) clearNode(right)
|
|
62
|
+
if (tx && !dryRun) await tx.flush()
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const has = core.bitfield.get(i)
|
|
60
|
-
|
|
61
|
-
if (!has) {
|
|
62
|
-
if (i + 1 === length) break
|
|
63
|
-
i = core.bitfield.findFirst(true, i + 1)
|
|
64
|
-
if (i < 0) break
|
|
65
|
-
nextOffset = -1
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
65
|
+
// audit the blocks
|
|
66
|
+
if (blocks) {
|
|
67
|
+
let tx = null
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
bitfield.set(i, false)
|
|
75
|
-
corrections.blocks++
|
|
76
|
-
i++
|
|
77
|
-
continue
|
|
69
|
+
for await (const block of core.state.storage.createBlockStream()) {
|
|
70
|
+
if (!core.bitfield.get(block.index)) {
|
|
71
|
+
if (!tx && !stats.corrupt) tx = core.state.storage.write()
|
|
72
|
+
tx.deleteBlock(block.index)
|
|
73
|
+
stats.droppedBlocks++
|
|
78
74
|
}
|
|
79
|
-
}
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
const rx = core.state.storage.read()
|
|
77
|
+
const treeNodePromise = rx.getTreeNode(2 * block.index)
|
|
78
|
+
|
|
79
|
+
rx.tryFlush()
|
|
84
80
|
|
|
85
|
-
|
|
81
|
+
const treeNode = await treeNodePromise
|
|
82
|
+
|
|
83
|
+
if (isBadBlock(treeNode, block.value)) {
|
|
84
|
+
if (!tx && !stats.corrupt) tx = core.state.storage.write()
|
|
85
|
+
tx.deleteBlock(block.index)
|
|
86
|
+
stats.droppedBlocks++
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
storage.deleteBlock(i)
|
|
89
|
-
bitfield.set(i, false)
|
|
90
|
-
corrections.blocks++
|
|
90
|
+
stats.blocks++
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
if (tx && !dryRun) await tx.flush()
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
if (bitfield) {
|
|
97
|
+
let tx = null
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
for (const index of allBits(core.bitfield)) {
|
|
100
|
+
const rx = core.state.storage.read()
|
|
101
|
+
const blockPromise = rx.getBlock(index)
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
rx.tryFlush()
|
|
104
|
+
|
|
105
|
+
const block = await blockPromise
|
|
106
|
+
if (!block) {
|
|
107
|
+
stats.droppedBits++
|
|
108
|
+
if (dryRun) continue
|
|
109
|
+
|
|
110
|
+
if (!tx && !stats.corrupt) tx = core.state.storage.write()
|
|
111
|
+
|
|
112
|
+
core.bitfield.set(index, false)
|
|
113
|
+
|
|
114
|
+
const page = core.bitfield.getBitfield(index)
|
|
115
|
+
if (page.bitfield) tx.setBitfieldPage(page.index, page.bitfield)
|
|
116
|
+
else tx.deleteBitfieldPage(page.idnex)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
stats.bits++
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (tx && !dryRun) await tx.flush()
|
|
104
124
|
}
|
|
125
|
+
|
|
126
|
+
return stats
|
|
105
127
|
}
|
|
106
128
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
return data
|
|
129
|
+
function isBadBlock (node, block) {
|
|
130
|
+
if (!node) return true
|
|
131
|
+
const hash = crypto.data(block)
|
|
132
|
+
return !b4a.equals(hash, node.hash) || node.size !== block.byteLength
|
|
113
133
|
}
|
|
114
134
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
function isBadTree (parent, left, right) {
|
|
136
|
+
if (!left && !right) return false
|
|
137
|
+
if (!left || !right) return true
|
|
138
|
+
const hash = crypto.parent(left, right)
|
|
139
|
+
return !b4a.equals(hash, parent.hash) || parent.size !== (left.size + right.size)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function * allBits (bitfield) {
|
|
143
|
+
let i = 0
|
|
144
|
+
if (bitfield.get(0)) yield 0
|
|
145
|
+
while (true) {
|
|
146
|
+
i = bitfield.findFirst(true, i + 1)
|
|
147
|
+
if (i === -1) break
|
|
148
|
+
yield i
|
|
119
149
|
}
|
|
120
|
-
return nodes
|
|
121
150
|
}
|
package/lib/core.js
CHANGED
|
@@ -286,19 +286,11 @@ module.exports = class Core {
|
|
|
286
286
|
this._manifestFlushed = !!header.manifest
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
async audit () {
|
|
289
|
+
async audit (opts) {
|
|
290
290
|
await this.state.mutex.lock()
|
|
291
291
|
|
|
292
292
|
try {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// TODO: refactor audit
|
|
296
|
-
const corrections = await audit(this, tx)
|
|
297
|
-
if (corrections.blocks || corrections.tree) {
|
|
298
|
-
await this.state.flushUpdate(tx)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return corrections
|
|
293
|
+
return await audit(this, opts)
|
|
302
294
|
} finally {
|
|
303
295
|
this.state._unlock()
|
|
304
296
|
}
|
package/lib/replicator.js
CHANGED
|
@@ -351,11 +351,13 @@ class ProofRequest {
|
|
|
351
351
|
async fulfill () {
|
|
352
352
|
if (this.proof === null) return null
|
|
353
353
|
|
|
354
|
-
const proof = await this.proof.settle()
|
|
354
|
+
const [proof, block] = await Promise.all([this.proof.settle(), this.block])
|
|
355
355
|
|
|
356
356
|
if (this.manifest) proof.manifest = this.manifest
|
|
357
|
-
if (this.block) proof.block.value = await this.block
|
|
358
357
|
|
|
358
|
+
if (!block && proof.block) return null
|
|
359
|
+
|
|
360
|
+
if (block) proof.block.value = block
|
|
359
361
|
return proof
|
|
360
362
|
}
|
|
361
363
|
}
|
package/lib/session-state.js
CHANGED
|
@@ -114,16 +114,19 @@ module.exports = class SessionState {
|
|
|
114
114
|
return this.treeFork === -1 ? this.core.header.tree.fork : this.treeFork
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
async
|
|
117
|
+
async updateSnapshotStorage (storage) {
|
|
118
118
|
if (!this.atomized || !this.atomized.flushing) return this.treeInfo()
|
|
119
119
|
await this.atomized.flushed()
|
|
120
120
|
|
|
121
121
|
let rx = storage.read()
|
|
122
122
|
const headPromise = rx.getHead()
|
|
123
123
|
const authPromise = rx.getAuth()
|
|
124
|
+
const depPromise = rx.getDependency()
|
|
124
125
|
|
|
125
126
|
rx.tryFlush()
|
|
126
|
-
const [head, auth] = await Promise.all([headPromise, authPromise])
|
|
127
|
+
const [head, auth, dep] = await Promise.all([headPromise, authPromise, depPromise])
|
|
128
|
+
|
|
129
|
+
storage.setDependencyHead(dep)
|
|
127
130
|
|
|
128
131
|
const fork = head ? head.fork : 0
|
|
129
132
|
const length = head ? head.length : 0
|
|
@@ -188,7 +191,7 @@ module.exports = class SessionState {
|
|
|
188
191
|
|
|
189
192
|
async snapshot () {
|
|
190
193
|
const storage = this.storage.snapshot()
|
|
191
|
-
const treeInfo = await this.
|
|
194
|
+
const treeInfo = await this.updateSnapshotStorage(storage)
|
|
192
195
|
|
|
193
196
|
const s = new SessionState(
|
|
194
197
|
this.core,
|
|
@@ -258,6 +261,14 @@ module.exports = class SessionState {
|
|
|
258
261
|
try {
|
|
259
262
|
const currLength = this.length
|
|
260
263
|
|
|
264
|
+
// load dependency into memory
|
|
265
|
+
const rx = this.storage.read()
|
|
266
|
+
const dependencyPromise = rx.getDependency()
|
|
267
|
+
|
|
268
|
+
rx.tryFlush()
|
|
269
|
+
|
|
270
|
+
const dependency = await dependencyPromise
|
|
271
|
+
|
|
261
272
|
this.fork = src.fork
|
|
262
273
|
this.length = src.length
|
|
263
274
|
this.byteLength = src.byteLength
|
|
@@ -271,15 +282,7 @@ module.exports = class SessionState {
|
|
|
271
282
|
signature: this.signature
|
|
272
283
|
}
|
|
273
284
|
|
|
274
|
-
|
|
275
|
-
const dependencyPromise = rx.getDependency()
|
|
276
|
-
|
|
277
|
-
rx.tryFlush()
|
|
278
|
-
|
|
279
|
-
const dependency = await dependencyPromise
|
|
280
|
-
if (dependency && dependency.length > this.flushedLength()) {
|
|
281
|
-
this.storage.updateDependencyLength(dependency.length, false, true)
|
|
282
|
-
}
|
|
285
|
+
if (dependency) this.storage.setDependencyHead(dependency)
|
|
283
286
|
|
|
284
287
|
const truncated = bitfield ? bitfield.truncated : -1
|
|
285
288
|
|
|
@@ -398,7 +401,7 @@ module.exports = class SessionState {
|
|
|
398
401
|
this.roots = roots
|
|
399
402
|
this.signature = tree.signature
|
|
400
403
|
|
|
401
|
-
if (dependency) this.storage.
|
|
404
|
+
if (dependency) this.storage.setDependencyHead(dependency)
|
|
402
405
|
|
|
403
406
|
this.ontruncate(tree, tree.length, batch.treeLength, flushed)
|
|
404
407
|
} finally {
|
|
@@ -424,7 +427,7 @@ module.exports = class SessionState {
|
|
|
424
427
|
this.roots = batch.roots
|
|
425
428
|
this.signature = batch.signature
|
|
426
429
|
|
|
427
|
-
if (dependency) this.storage.
|
|
430
|
+
if (dependency) this.storage.setDependencyHead(dependency)
|
|
428
431
|
|
|
429
432
|
this.ontruncate(tree, batch.ancestors, batch.treeLength, flushed)
|
|
430
433
|
} finally {
|
|
@@ -481,7 +484,7 @@ module.exports = class SessionState {
|
|
|
481
484
|
|
|
482
485
|
const flushed = await this.flush()
|
|
483
486
|
|
|
484
|
-
if (dependency) this.storage.
|
|
487
|
+
if (dependency) this.storage.setDependencyHead(dependency)
|
|
485
488
|
|
|
486
489
|
// todo: atomic event handle
|
|
487
490
|
if (this.isDefault() && flushed) {
|
|
@@ -658,7 +661,7 @@ module.exports = class SessionState {
|
|
|
658
661
|
const truncating = sharedLength < origLength
|
|
659
662
|
|
|
660
663
|
for (const node of roots) {
|
|
661
|
-
if (node === null) throw
|
|
664
|
+
if (node === null) throw INVALID_OPERATION('Invalid catchup length, tree nodes not available')
|
|
662
665
|
}
|
|
663
666
|
|
|
664
667
|
const fork = truncating ? this.fork + 1 : this.fork
|
|
@@ -685,9 +688,7 @@ module.exports = class SessionState {
|
|
|
685
688
|
|
|
686
689
|
const flushed = await this.flush()
|
|
687
690
|
|
|
688
|
-
|
|
689
|
-
this.storage.updateDependencyLength(sharedLength, true)
|
|
690
|
-
this.storage.updateDependencyLength(length, false)
|
|
691
|
+
this.storage.setDependencyHead(dep)
|
|
691
692
|
|
|
692
693
|
this.fork = tree.fork
|
|
693
694
|
this.roots = roots
|
|
@@ -846,7 +847,7 @@ module.exports = class SessionState {
|
|
|
846
847
|
|
|
847
848
|
await state.flush(tx)
|
|
848
849
|
|
|
849
|
-
if (dependency) state.storage.
|
|
850
|
+
if (dependency) state.storage.setDependencyHead(dependency)
|
|
850
851
|
}
|
|
851
852
|
|
|
852
853
|
const bitfield = { start: treeLength, length: length - treeLength, drop: false }
|
|
@@ -953,7 +954,7 @@ module.exports = class SessionState {
|
|
|
953
954
|
if (truncation) {
|
|
954
955
|
const { dependency } = truncation
|
|
955
956
|
|
|
956
|
-
if (dependency) this.storage.
|
|
957
|
+
if (dependency) this.storage.setDependencyHead(dependency)
|
|
957
958
|
truncated = { to: treeLength, fork }
|
|
958
959
|
}
|
|
959
960
|
|