hypercore 10.36.2 → 10.36.4

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/lib/audit.js ADDED
@@ -0,0 +1,123 @@
1
+ const hypercoreCrypto = require('hypercore-crypto')
2
+ const flat = require('flat-tree')
3
+ const c = require('compact-encoding')
4
+ const b4a = require('b4a')
5
+
6
+ // this is optimised for speed over mem atm
7
+ // can be tweaked in the future
8
+
9
+ module.exports = async function auditCore (core) {
10
+ const corrections = {
11
+ tree: 0,
12
+ blocks: 0
13
+ }
14
+
15
+ const length = core.header.tree.length
16
+
17
+ const data = await readFullStorage(core.blocks.storage)
18
+ const tree = await readFullStorage(core.tree.storage)
19
+
20
+ const valid = new Uint8Array(Math.ceil(tree.byteLength / 40))
21
+ const stack = []
22
+
23
+ for (const r of core.tree.roots) {
24
+ valid[r.index] = 1
25
+ stack.push(r)
26
+ }
27
+
28
+ while (stack.length > 0) {
29
+ const node = stack.pop()
30
+ if ((node.index & 1) === 0) continue
31
+
32
+ const [left, right] = flat.children(node.index)
33
+ const leftNode = getNode(left)
34
+ const rightNode = getNode(right)
35
+
36
+ stack.push(leftNode, rightNode)
37
+
38
+ if (valid[node.index]) {
39
+ const hash = hypercoreCrypto.parent(leftNode, rightNode)
40
+ if (b4a.equals(hash, node.hash) && node.size === (leftNode.size + rightNode.size)) {
41
+ valid[leftNode.index] = 1
42
+ valid[rightNode.index] = 1
43
+ continue
44
+ }
45
+ }
46
+
47
+ if (leftNode.size) clearNode(leftNode)
48
+ if (rightNode.size) clearNode(rightNode)
49
+ }
50
+
51
+ if (corrections.tree) {
52
+ core.tree.cache.clear()
53
+ }
54
+
55
+ let i = 0
56
+ let nextOffset = -1
57
+ while (i < length) {
58
+ const has = core.bitfield.get(i)
59
+
60
+ if (!has) {
61
+ if (i + 1 === length) break
62
+ i = core.bitfield.findFirst(true, i + 1)
63
+ if (i < 0) break
64
+ nextOffset = -1
65
+ continue
66
+ }
67
+
68
+ if (nextOffset === -1) {
69
+ try {
70
+ nextOffset = await core.tree.byteOffset(i * 2)
71
+ } catch {
72
+ core._setBitfield(i, false)
73
+ corrections.blocks++
74
+ i++
75
+ continue
76
+ }
77
+ }
78
+
79
+ const node = getNode(i * 2)
80
+ const blk = data.subarray(nextOffset, nextOffset + node.size)
81
+ const hash = hypercoreCrypto.data(blk)
82
+
83
+ nextOffset += blk.byteLength
84
+
85
+ if (!b4a.equals(hash, node.hash)) {
86
+ core._setBitfield(i, false)
87
+ corrections.blocks++
88
+ }
89
+
90
+ i++
91
+ }
92
+
93
+ return corrections
94
+
95
+ function getNode (index) {
96
+ const state = { start: index * 40, end: index * 40 + 40, buffer: tree }
97
+ const size = c.uint64.decode(state)
98
+ const hash = c.fixed32.decode(state)
99
+ return { index, size, hash }
100
+ }
101
+
102
+ function clearNode (node) {
103
+ valid[node.index] = 0
104
+
105
+ if (node.size) {
106
+ b4a.fill(tree, 0, node.index * 40, node.index * 40 + 40)
107
+ core.tree.unflushed.set(node.index, core.tree.blankNode(node.index))
108
+ corrections.tree++
109
+ }
110
+ }
111
+ }
112
+
113
+ function readFullStorage (storage) {
114
+ return new Promise((resolve, reject) => {
115
+ storage.stat((_, st) => {
116
+ if (!st) return resolve(b4a.alloc(0))
117
+ storage.read(0, st.size, (err, data) => {
118
+ if (err) reject(err)
119
+ else resolve(data)
120
+ })
121
+ })
122
+ })
123
+ }
package/lib/core.js CHANGED
@@ -11,6 +11,7 @@ const Info = require('./info')
11
11
  const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_OPERATION, INVALID_SIGNATURE, INVALID_CHECKSUM } = require('hypercore-errors')
12
12
  const m = require('./messages')
13
13
  const Verifier = require('./verifier')
14
+ const audit = require('./audit')
14
15
  const { createTracer } = require('hypertrace')
15
16
 
16
17
  module.exports = class Core {
@@ -194,6 +195,19 @@ module.exports = class Core {
194
195
  return new this(header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, legacy, opts.onupdate || noop, opts.onconflict || noop)
195
196
  }
196
197
 
198
+ async audit () {
199
+ await this._mutex.lock()
200
+
201
+ try {
202
+ await this._flushOplog()
203
+ const corrections = await audit(this)
204
+ if (corrections.blocks || corrections.tree) await this._flushOplog()
205
+ return corrections
206
+ } finally {
207
+ await this._mutex.unlock()
208
+ }
209
+ }
210
+
197
211
  async setManifest (manifest) {
198
212
  await this._mutex.lock()
199
213
 
@@ -601,7 +615,7 @@ module.exports = class Core {
601
615
  }
602
616
  }
603
617
 
604
- await this.blocks.putBatch(treeLength, adding > values.length ? values.slice(0, adding) : values, byteOffset)
618
+ await this.blocks.putBatch(treeLength, adding < values.length ? values.slice(0, adding) : values, byteOffset)
605
619
  await this.oplog.append([entry], false)
606
620
 
607
621
  this._setBitfieldRange(entry.bitfield.start, entry.bitfield.length, true)
@@ -4,7 +4,7 @@ const c = require('compact-encoding')
4
4
  const Xache = require('xache')
5
5
  const b4a = require('b4a')
6
6
  const caps = require('./caps')
7
- const { INVALID_PROOF, INVALID_CHECKSUM, INVALID_OPERATION, BAD_ARGUMENT } = require('hypercore-errors')
7
+ const { INVALID_PROOF, INVALID_CHECKSUM, INVALID_OPERATION, BAD_ARGUMENT, ASSERTION } = require('hypercore-errors')
8
8
 
9
9
  const BLANK_HASH = b4a.alloc(32)
10
10
  const OLD_TREE = b4a.from([5, 2, 87, 2, 0, 0, 40, 7, 66, 76, 65, 75, 69, 50, 98])
@@ -444,6 +444,10 @@ module.exports = class MerkleTree {
444
444
  return true
445
445
  }
446
446
 
447
+ blankNode (index) {
448
+ return blankNode(index)
449
+ }
450
+
447
451
  get (index, error = true) {
448
452
  const c = this.cache.get(index)
449
453
  if (c) return c
@@ -763,6 +767,8 @@ async function getByteOffset (tree, index) {
763
767
 
764
768
  return offset
765
769
  }
770
+
771
+ throw ASSERTION('Failed to find offset')
766
772
  }
767
773
 
768
774
  // All the methods needed for proof verification
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.36.2",
3
+ "version": "10.36.4",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {