hypercore 11.0.37 → 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/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
|
}
|