hypercore 10.38.2 → 11.0.1

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 CHANGED
@@ -1,14 +1,13 @@
1
1
  const hypercoreCrypto = require('hypercore-crypto')
2
2
  const flat = require('flat-tree')
3
- const c = require('compact-encoding')
4
3
  const b4a = require('b4a')
5
4
 
6
- const empty = b4a.alloc(32)
5
+ const BitInterlude = require('./bit-interlude')
7
6
 
8
7
  // this is optimised for speed over mem atm
9
8
  // can be tweaked in the future
10
9
 
11
- module.exports = async function auditCore (core) {
10
+ module.exports = async function auditCore (core, storage) {
12
11
  const corrections = {
13
12
  tree: 0,
14
13
  blocks: 0
@@ -16,8 +15,10 @@ module.exports = async function auditCore (core) {
16
15
 
17
16
  const length = core.header.tree.length
18
17
 
19
- const data = await readFullStorage(core.blocks.storage)
20
- const tree = await readFullStorage(core.tree.storage)
18
+ const bitfield = new BitInterlude()
19
+
20
+ const data = await readAllBlocks(core.storage)
21
+ const tree = await readAllTreeNodes(core.tree.storage)
21
22
 
22
23
  const valid = new Uint8Array(Math.ceil(tree.byteLength / 40))
23
24
  const stack = []
@@ -32,8 +33,8 @@ module.exports = async function auditCore (core) {
32
33
  if ((node.index & 1) === 0) continue
33
34
 
34
35
  const [left, right] = flat.children(node.index)
35
- const leftNode = getNode(left)
36
- const rightNode = getNode(right)
36
+ const leftNode = tree.get(left)
37
+ const rightNode = tree.get(right)
37
38
 
38
39
  if (!rightNode && !leftNode) continue
39
40
 
@@ -48,12 +49,8 @@ module.exports = async function auditCore (core) {
48
49
  }
49
50
  }
50
51
 
51
- if (leftNode.size) clearNode(leftNode)
52
- if (rightNode.size) clearNode(rightNode)
53
- }
54
-
55
- if (corrections.tree) {
56
- core.tree.cache.clear()
52
+ if (leftNode.size) clearNode(left)
53
+ if (rightNode.size) clearNode(right)
57
54
  }
58
55
 
59
56
  let i = 0
@@ -73,57 +70,52 @@ module.exports = async function auditCore (core) {
73
70
  try {
74
71
  nextOffset = await core.tree.byteOffset(i * 2)
75
72
  } catch {
76
- core._setBitfield(i, false)
73
+ storage.deleteBlock(i)
74
+ bitfield.set(i, false)
77
75
  corrections.blocks++
78
76
  i++
79
77
  continue
80
78
  }
81
79
  }
82
80
 
83
- const node = getNode(i * 2)
84
- const blk = data.subarray(nextOffset, nextOffset + node.size)
81
+ const node = tree.get(i * 2)
82
+ const blk = data.get(i)
85
83
  const hash = hypercoreCrypto.data(blk)
86
84
 
87
85
  nextOffset += blk.byteLength
88
86
 
89
87
  if (!b4a.equals(hash, node.hash)) {
90
- core._setBitfield(i, false)
88
+ storage.deleteBlock(i)
89
+ bitfield.set(i, false)
91
90
  corrections.blocks++
92
91
  }
93
92
 
94
93
  i++
95
94
  }
96
95
 
97
- return corrections
96
+ bitfield.flush(storage, core.bitfield)
98
97
 
99
- function getNode (index) {
100
- if (index * 40 + 40 > tree.byteLength) return null
101
- const state = { start: index * 40, end: index * 40 + 40, buffer: tree }
102
- const size = c.uint64.decode(state)
103
- const hash = c.fixed32.decode(state)
104
- if (size === 0 && hash.equals(empty)) return null
105
- return { index, size, hash }
106
- }
98
+ return corrections
107
99
 
108
100
  function clearNode (node) {
109
101
  valid[node.index] = 0
102
+ storage.deleteTreeNode(node.index)
103
+ corrections.tree++
104
+ }
105
+ }
110
106
 
111
- if (node.size) {
112
- b4a.fill(tree, 0, node.index * 40, node.index * 40 + 40)
113
- core.tree.unflushed.set(node.index, core.tree.blankNode(node.index))
114
- corrections.tree++
115
- }
107
+ async function readAllBlocks (storage) {
108
+ const data = new Map()
109
+ for await (const block of storage.createBlockStream()) {
110
+ data.set(block.index, block.value)
116
111
  }
112
+ return data
117
113
  }
118
114
 
119
- function readFullStorage (storage) {
120
- return new Promise((resolve, reject) => {
121
- storage.stat((_, st) => {
122
- if (!st) return resolve(b4a.alloc(0))
123
- storage.read(0, st.size, (err, data) => {
124
- if (err) reject(err)
125
- else resolve(data)
126
- })
127
- })
128
- })
115
+ async function readAllTreeNodes (storage) {
116
+ const nodes = new Map()
117
+ for await (const node of storage.createTreeNodeStream()) {
118
+ nodes.set(node.index, node)
119
+ }
120
+ return nodes
129
121
  }
@@ -0,0 +1,174 @@
1
+ const b4a = require('b4a')
2
+ const quickbit = require('./compat').quickbit
3
+
4
+ module.exports = class BitInterlude {
5
+ constructor () {
6
+ this.ranges = []
7
+ }
8
+
9
+ contiguousLength (from) {
10
+ for (const r of this.ranges) {
11
+ if (r.start > from) break
12
+ if (!r.value && r.start <= from) return r.start
13
+ }
14
+
15
+ // TODO: be smarter
16
+ while (this.get(from) === true) from++
17
+ return from
18
+ }
19
+
20
+ get (index) {
21
+ let start = 0
22
+ let end = this.ranges.length
23
+
24
+ while (start < end) {
25
+ const mid = (start + end) >> 1
26
+ const r = this.ranges[mid]
27
+
28
+ if (index < r.start) {
29
+ end = mid
30
+ continue
31
+ }
32
+
33
+ if (index >= r.end) {
34
+ if (mid === start) break
35
+ start = mid
36
+ continue
37
+ }
38
+
39
+ return r.value
40
+ }
41
+
42
+ return false
43
+ }
44
+
45
+ setRange (start, end, value) {
46
+ if (start === end) return
47
+
48
+ let r = null
49
+
50
+ for (let i = 0; i < this.ranges.length; i++) {
51
+ r = this.ranges[i]
52
+
53
+ // if already inside, stop
54
+ if (r.start <= start && end <= r.end) {
55
+ if (value === r.value) return
56
+
57
+ const ranges = mergeRanges(r, { start, end, value })
58
+ this.ranges.splice(i, 1, ...ranges)
59
+
60
+ return
61
+ }
62
+
63
+ // we wanna overun the interval
64
+ if (start > r.end) {
65
+ continue
66
+ }
67
+
68
+ // we overran but this interval is ending after us, move it back
69
+ if (end >= r.start && end <= r.end) {
70
+ r.start = r.value === value ? start : end
71
+ if (r.value !== value) this.ranges.splice(i, 0, { start, end, value })
72
+ return
73
+ }
74
+
75
+ // we overran but our start is contained in this interval, move start back
76
+ if (start >= r.start && start <= r.end) {
77
+ if (r.value !== value) {
78
+ this.ranges.splice(++i, 0, { start, end, value })
79
+ r.end = start
80
+ return
81
+ }
82
+
83
+ start = r.start
84
+ }
85
+
86
+ let remove = 0
87
+
88
+ for (let j = i; j < this.ranges.length; j++) {
89
+ const n = this.ranges[j]
90
+ if (n.start > end || n.value !== value) break
91
+ if (n.start <= end && n.end > end) end = n.end
92
+ remove++
93
+ }
94
+
95
+ this.ranges.splice(i, remove, { start, end, value })
96
+ return
97
+ }
98
+
99
+ if (r !== null) {
100
+ if (start <= r.end && end > r.end) {
101
+ r.end = end
102
+ return
103
+ }
104
+
105
+ // we never
106
+ if (r.end > start) return
107
+ }
108
+
109
+ this.ranges.push({ start, end, value })
110
+ }
111
+
112
+ flush (tx, bitfield) {
113
+ if (!this.ranges.length) return []
114
+
115
+ let index = this.ranges[0].start
116
+ const final = this.ranges[this.ranges.length - 1].end
117
+
118
+ let i = 0
119
+
120
+ while (index < final) {
121
+ const page = bitfield.getBitfield(index) // read only
122
+ const pageIndex = page ? page.index : bitfield.getPageIndex(index)
123
+
124
+ const buf = b4a.allocUnsafe(bitfield.getPageByteLength())
125
+
126
+ const view = new DataView(
127
+ buf.buffer,
128
+ buf.byteOffset,
129
+ buf.byteLength
130
+ )
131
+
132
+ if (page) {
133
+ for (let i = 0; i < page.bitfield.length; i++) {
134
+ view.setUint32(i * 4, page.bitfield[i], true)
135
+ }
136
+ }
137
+
138
+ const last = (pageIndex + 1) * (buf.byteLength << 3)
139
+ const offset = pageIndex * (buf.byteLength << 3)
140
+
141
+ let hasValue = false
142
+
143
+ while (i < this.ranges.length) {
144
+ const { start, end, value } = this.ranges[i]
145
+
146
+ if (!hasValue && value) hasValue = true
147
+
148
+ const from = start < index ? index : start
149
+ const to = end < last ? end : last
150
+
151
+ quickbit.fill(buf, value, from - offset, to - offset)
152
+
153
+ index = to
154
+
155
+ if (to === last) break
156
+
157
+ i++
158
+ }
159
+
160
+ if (page || hasValue) tx.putBitfieldPage(pageIndex, buf)
161
+ }
162
+
163
+ return this.ranges
164
+ }
165
+ }
166
+
167
+ function mergeRanges (a, b) {
168
+ const ranges = []
169
+ if (a.start < b.start) ranges.push({ start: a.start, end: b.start, value: a.value })
170
+ ranges.push({ start: b.start, end: b.end, value: b.value })
171
+ if (b.end < a.end) ranges.push({ start: b.end, end: a.end, value: a.value })
172
+
173
+ return ranges
174
+ }
package/lib/bitfield.js CHANGED
@@ -14,7 +14,6 @@ const SEGMENT_GROWTH_FACTOR = 4
14
14
 
15
15
  class BitfieldPage {
16
16
  constructor (index, segment) {
17
- this.dirty = false
18
17
  this.index = index
19
18
  this.offset = index * BYTES_PER_PAGE - segment.offset
20
19
  this.bitfield = null
@@ -27,7 +26,7 @@ class BitfieldPage {
27
26
  return this.segment.tree
28
27
  }
29
28
 
30
- get (index) {
29
+ get (index, dirty) {
31
30
  return quickbit.get(this.bitfield, index)
32
31
  }
33
32
 
@@ -37,11 +36,11 @@ class BitfieldPage {
37
36
  }
38
37
  }
39
38
 
40
- setRange (start, length, val) {
41
- quickbit.fill(this.bitfield, val, start, start + length)
39
+ setRange (start, end, val) {
40
+ quickbit.fill(this.bitfield, val, start, end)
42
41
 
43
42
  let i = Math.floor(start / 128)
44
- const n = i + Math.ceil(length / 128)
43
+ const n = i + Math.ceil((end - start) / 128)
45
44
 
46
45
  while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
47
46
  }
@@ -172,10 +171,11 @@ class BitfieldSegment {
172
171
  }
173
172
 
174
173
  module.exports = class Bitfield {
175
- constructor (storage, buffer) {
176
- this.unflushed = []
177
- this.storage = storage
178
- this.resumed = !!(buffer && buffer.byteLength >= 4)
174
+ static BITS_PER_PAGE = BITS_PER_PAGE
175
+ static BYTES_PER_PAGE = BYTES_PER_PAGE
176
+
177
+ constructor (buffer) {
178
+ this.resumed = !!(buffer && buffer.byteLength >= 0)
179
179
 
180
180
  this._pages = new BigSparseArray()
181
181
  this._segments = new BigSparseArray()
@@ -213,6 +213,10 @@ module.exports = class Bitfield {
213
213
  }
214
214
  }
215
215
 
216
+ static from (bitfield) {
217
+ return new Bitfield(bitfield.toBuffer(bitfield._pages.maxLength * BITS_PER_PAGE))
218
+ }
219
+
216
220
  toBuffer (length) {
217
221
  const pages = Math.ceil(length / BITS_PER_PAGE)
218
222
  const buffer = b4a.allocUnsafe(pages * BYTES_PER_PAGE)
@@ -238,13 +242,29 @@ module.exports = class Bitfield {
238
242
  }
239
243
 
240
244
  getBitfield (index) {
241
- const j = index & (BITS_PER_PAGE - 1)
242
- const i = (index - j) / BITS_PER_PAGE
245
+ const i = this.getPageIndex(index)
243
246
 
244
247
  const p = this._pages.get(i)
245
248
  return p || null
246
249
  }
247
250
 
251
+ merge (bitfield, length) {
252
+ let i = 0
253
+
254
+ while (i < length) {
255
+ const start = bitfield.firstSet(i)
256
+ if (start === -1) break
257
+
258
+ i = bitfield.firstUnset(start)
259
+
260
+ if (i === -1 || i > length) i = length
261
+
262
+ this.setRange(start, i, true)
263
+
264
+ if (i >= length) break
265
+ }
266
+ }
267
+
248
268
  get (index) {
249
269
  const j = index & (BITS_PER_PAGE - 1)
250
270
  const i = (index - j) / BITS_PER_PAGE
@@ -254,6 +274,32 @@ module.exports = class Bitfield {
254
274
  return p ? p.get(j) : false
255
275
  }
256
276
 
277
+ getPageByteLength () {
278
+ return BYTES_PER_PAGE
279
+ }
280
+
281
+ getPageIndex (index) {
282
+ const j = index & (BITS_PER_PAGE - 1)
283
+ return (index - j) / BITS_PER_PAGE
284
+ }
285
+
286
+ getPage (index, create) {
287
+ const i = this.getPageIndex(index)
288
+
289
+ let p = this._pages.get(i)
290
+
291
+ if (p) return p
292
+
293
+ if (!create) return null
294
+
295
+ const k = Math.floor(i / PAGES_PER_SEGMENT)
296
+ const s = this._segments.get(k) || this._segments.set(k, new BitfieldSegment(k, new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)))
297
+
298
+ p = this._pages.set(i, new BitfieldPage(i, s))
299
+
300
+ return p
301
+ }
302
+
257
303
  set (index, val) {
258
304
  const j = index & (BITS_PER_PAGE - 1)
259
305
  const i = (index - j) / BITS_PER_PAGE
@@ -267,21 +313,14 @@ module.exports = class Bitfield {
267
313
  p = this._pages.set(i, new BitfieldPage(i, s))
268
314
  }
269
315
 
270
- if (p) {
271
- p.set(j, val)
272
-
273
- if (!p.dirty) {
274
- p.dirty = true
275
- this.unflushed.push(p)
276
- }
277
- }
316
+ if (p) p.set(j, val)
278
317
  }
279
318
 
280
- setRange (start, length, val) {
319
+ setRange (start, end, val) {
281
320
  let j = start & (BITS_PER_PAGE - 1)
282
321
  let i = (start - j) / BITS_PER_PAGE
283
322
 
284
- while (length > 0) {
323
+ while (start < end) {
285
324
  let p = this._pages.get(i)
286
325
 
287
326
  if (!p && val) {
@@ -291,21 +330,14 @@ module.exports = class Bitfield {
291
330
  p = this._pages.set(i, new BitfieldPage(i, s))
292
331
  }
293
332
 
294
- const end = Math.min(j + length, BITS_PER_PAGE)
295
- const range = end - j
296
-
297
- if (p) {
298
- p.setRange(j, range, val)
333
+ const last = Math.min(end, BITS_PER_PAGE)
334
+ const range = last - j
299
335
 
300
- if (!p.dirty) {
301
- p.dirty = true
302
- this.unflushed.push(p)
303
- }
304
- }
336
+ if (p) p.setRange(j, last, val)
305
337
 
306
338
  j = 0
307
339
  i++
308
- length -= range
340
+ end -= range
309
341
  }
310
342
  }
311
343
 
@@ -420,68 +452,28 @@ module.exports = class Bitfield {
420
452
  }
421
453
  }
422
454
 
423
- clear () {
424
- return new Promise((resolve, reject) => {
425
- this.storage.truncate(0, (err) => {
426
- if (err) return reject(err)
427
- this._pages = new BigSparseArray()
428
- this.unflushed = []
429
- resolve()
430
- })
431
- })
455
+ clear (tx) {
456
+ return tx.deleteBitfieldPageRange(0, -1)
432
457
  }
433
458
 
434
- close () {
435
- return new Promise((resolve, reject) => {
436
- this.storage.close((err) => {
437
- if (err) reject(err)
438
- else resolve()
439
- })
440
- })
459
+ onupdate (ranges) {
460
+ for (const { start, end, value } of ranges) {
461
+ this.setRange(start, end, value)
462
+ }
441
463
  }
442
464
 
443
- flush () {
444
- return new Promise((resolve, reject) => {
445
- if (!this.unflushed.length) return resolve()
446
-
447
- const self = this
448
- let missing = this.unflushed.length
449
- let error = null
465
+ static async open (storage, length) {
466
+ if (length === 0) return new Bitfield(storage, null)
450
467
 
451
- for (const page of this.unflushed) {
452
- const buf = b4a.from(
453
- page.bitfield.buffer,
454
- page.bitfield.byteOffset,
455
- page.bitfield.byteLength
456
- )
468
+ const pages = Math.ceil(length / BITS_PER_PAGE)
469
+ const buffer = b4a.alloc(pages * BYTES_PER_PAGE)
470
+ const stream = storage.createBitfieldStream()
457
471
 
458
- page.dirty = false
459
- this.storage.write(page.index * BYTES_PER_PAGE, buf, done)
460
- }
472
+ for await (const { index, page } of stream) {
473
+ buffer.set(page, index * BYTES_PER_PAGE)
474
+ }
461
475
 
462
- function done (err) {
463
- if (err) error = err
464
- if (--missing) return
465
- if (error) return reject(error)
466
- self.unflushed = []
467
- resolve()
468
- }
469
- })
470
- }
471
-
472
- static open (storage, tree = null) {
473
- return new Promise((resolve, reject) => {
474
- storage.stat((err, st) => {
475
- if (err) return resolve(new Bitfield(storage, null))
476
- let size = st.size - (st.size & 3)
477
- if (!size) return resolve(new Bitfield(storage, null))
478
- if (tree) size = Math.min(size, ceilTo(tree.length / 8, 4096))
479
- storage.read(0, size, (err, data) => {
480
- if (err) return reject(err)
481
- resolve(new Bitfield(storage, data))
482
- })
483
- })
484
- })
476
+ return new Bitfield(buffer)
485
477
  }
486
478
  }
487
479
 
@@ -1,63 +1,25 @@
1
- const b4a = require('b4a')
2
- const { WRITE_FAILED } = require('hypercore-errors')
3
-
4
1
  module.exports = class BlockStore {
5
- constructor (storage, tree) {
2
+ constructor (storage) {
6
3
  this.storage = storage
7
- this.tree = tree
8
- }
9
-
10
- async get (i, tree) {
11
- if (!tree) tree = this.tree
12
- const [offset, size] = await tree.byteRange(2 * i)
13
- return this._read(offset, size)
14
4
  }
15
5
 
16
- async put (i, data, offset) {
17
- return this._write(offset, data)
6
+ async get (rx, i) {
7
+ return rx.getBlock(i)
18
8
  }
19
9
 
20
- putBatch (i, batch, offset) {
21
- if (batch.length === 0) return Promise.resolve()
22
- return this.put(i, batch.length === 1 ? batch[0] : b4a.concat(batch), offset)
10
+ put (tx, i, data) {
11
+ tx.putBlock(i, data)
23
12
  }
24
13
 
25
- clear (offset = 0, length = -1) {
26
- return new Promise((resolve, reject) => {
27
- if (length === -1) this.storage.truncate(offset, done)
28
- else this.storage.del(offset, length, done)
29
-
30
- function done (err) {
31
- if (err) reject(err)
32
- else resolve()
33
- }
34
- })
35
- }
36
-
37
- close () {
38
- return new Promise((resolve, reject) => {
39
- this.storage.close((err) => {
40
- if (err) reject(err)
41
- else resolve()
42
- })
43
- })
44
- }
14
+ putBatch (tx, i, blocks) {
15
+ if (blocks.length === 0) return Promise.resolve()
45
16
 
46
- _read (offset, size) {
47
- return new Promise((resolve, reject) => {
48
- this.storage.read(offset, size, (err, data) => {
49
- if (err) reject(err)
50
- else resolve(data)
51
- })
52
- })
17
+ for (let j = 0; j < blocks.length; j++) {
18
+ tx.putBlock(i + j, blocks[j])
19
+ }
53
20
  }
54
21
 
55
- _write (offset, data) {
56
- return new Promise((resolve, reject) => {
57
- this.storage.write(offset, data, (err) => {
58
- if (err) reject(WRITE_FAILED(err.message))
59
- else resolve(offset + data.byteLength)
60
- })
61
- })
22
+ clear (tx, start = 0, end = -1) {
23
+ tx.deleteBlockRange(start, end)
62
24
  }
63
25
  }