hypercore 10.36.4 → 10.37.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/index.js CHANGED
@@ -18,7 +18,7 @@ const BlockEncryption = require('./lib/block-encryption')
18
18
  const Info = require('./lib/info')
19
19
  const Download = require('./lib/download')
20
20
  const Batch = require('./lib/batch')
21
- const { manifestHash, defaultSignerManifest, createManifest, isCompat, sign } = require('./lib/verifier')
21
+ const { manifestHash, createManifest } = require('./lib/verifier')
22
22
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
23
23
  const {
24
24
  ASSERTION,
@@ -85,7 +85,6 @@ module.exports = class Hypercore extends EventEmitter {
85
85
  this.closing = null
86
86
  this.opening = null
87
87
 
88
- this._clone = opts.clone || null
89
88
  this._readonly = opts.writable === false
90
89
  this._preappend = preappend.bind(this)
91
90
  this._snapshot = null
@@ -322,14 +321,6 @@ module.exports = class Hypercore extends EventEmitter {
322
321
  const s = this.sessions[i]
323
322
  if (s !== this) s._passCapabilities(this)
324
323
  }
325
-
326
- // copy state over
327
- if (this._clone) {
328
- const { from, signature } = this._clone
329
- await from.opening
330
- await this.core.copyFrom(from.core, signature)
331
- this._clone = null
332
- }
333
324
  }
334
325
  } else {
335
326
  ensureEncryption(this, opts)
@@ -501,46 +492,6 @@ module.exports = class Hypercore extends EventEmitter {
501
492
  this.emit('close', true)
502
493
  }
503
494
 
504
- clone (keyPair, storage, opts = {}) {
505
- // TODO: current limitation is no forking
506
- if ((opts.fork && opts.fork !== 0) || this.fork !== 0) {
507
- throw BAD_ARGUMENT('Cannot clone a fork')
508
- }
509
-
510
- const manifest = opts.manifest || defaultSignerManifest(keyPair.publicKey)
511
- const key = opts.key || (opts.compat !== false ? manifest.signers[0].publicKey : manifestHash(manifest))
512
-
513
- if (b4a.equals(key, this.key)) {
514
- throw BAD_ARGUMENT('Clone cannot share verification information')
515
- }
516
-
517
- const signature = opts.signature === undefined
518
- ? sign(createManifest(manifest), this.core.tree.batch(), keyPair, { compat: isCompat(key, manifest) })
519
- : opts.signature
520
-
521
- const sparse = opts.sparse === false ? false : this.sparse
522
- const wait = opts.wait === false ? false : this.wait
523
- const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
524
- const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
525
-
526
- const Clz = this.constructor
527
-
528
- return new Clz(storage, key, {
529
- ...opts,
530
- keyPair,
531
- sparse,
532
- wait,
533
- onwait,
534
- timeout,
535
- manifest,
536
- overwrite: true,
537
- clone: {
538
- from: this,
539
- signature
540
- }
541
- })
542
- }
543
-
544
495
  replicate (isInitiator, opts = {}) {
545
496
  // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
546
497
  // because it doesn't really make a lot of sense.
package/lib/batch.js CHANGED
@@ -25,6 +25,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
25
25
  this._sessionLength = 0
26
26
  this._sessionByteLength = 0
27
27
  this._sessionBatch = null
28
+ this._cachedBatch = null
28
29
  this._flushing = null
29
30
  this._clear = clear
30
31
 
@@ -207,20 +208,32 @@ module.exports = class HypercoreBatch extends EventEmitter {
207
208
  return this.session.core.tree.restoreBatch(length)
208
209
  }
209
210
 
210
- createTreeBatch (length, blocks = []) {
211
+ _catchupBatch (clone) {
212
+ if (this._cachedBatch === null) this._cachedBatch = this._sessionBatch.clone()
213
+
214
+ if (this.length > this._cachedBatch.length) {
215
+ const offset = this._cachedBatch.length - this._sessionBatch.length
216
+
217
+ for (let i = offset; i < this._appendsActual.length; i++) {
218
+ this._cachedBatch.append(this._appendsActual[i])
219
+ }
220
+ }
221
+
222
+ return clone ? this._cachedBatch.clone() : this._cachedBatch
223
+ }
224
+
225
+ createTreeBatch (length, opts = {}) {
226
+ if (Array.isArray(opts)) opts = { blocks: opts }
227
+
228
+ const { blocks = [], clone = true } = opts
211
229
  if (!length && length !== 0) length = this.length + blocks.length
212
230
 
213
231
  const maxLength = this.length + blocks.length
214
- const b = this._sessionBatch.clone()
232
+ const b = this._catchupBatch(clone || (blocks.length > 0 || length !== this.length))
215
233
  const len = Math.min(length, this.length)
216
234
 
217
235
  if (len < this._sessionLength || length > maxLength) return null
218
-
219
- for (let i = 0; i < len - this._sessionLength; i++) {
220
- b.append(this._appendsActual[i])
221
- }
222
-
223
- if (len < this.length) return b
236
+ if (len < b.length) b.checkout(len, this._sessionBatch.roots)
224
237
 
225
238
  for (let i = 0; i < length - len; i++) {
226
239
  b.append(this._appendsActual === this._appends ? blocks[i] : this._encrypt(b.length, blocks[i]))
@@ -239,6 +252,8 @@ module.exports = class HypercoreBatch extends EventEmitter {
239
252
  if (typeof opts === 'number') opts = { fork: opts }
240
253
  const { fork = this.fork + 1, force = false } = opts
241
254
 
255
+ this._cachedBatch = null
256
+
242
257
  const length = this._sessionLength
243
258
  if (newLength < length) {
244
259
  if (!force) throw new Error('Cannot truncate committed blocks')
@@ -380,6 +395,8 @@ module.exports = class HypercoreBatch extends EventEmitter {
380
395
  this._sessionByteLength = info.byteLength
381
396
  this._sessionBatch = newBatch
382
397
 
398
+ if (this._cachedBatch !== null) this._cachedBatch.prune(info.length)
399
+
383
400
  const same = this._appends === this._appendsActual
384
401
 
385
402
  this._appends = this._appends.slice(flushingLength)
package/lib/core.js CHANGED
@@ -253,7 +253,7 @@ module.exports = class Core {
253
253
  return false
254
254
  }
255
255
 
256
- async copyFrom (src, signature, { length = src.tree.length, sourceLength = src.tree.length, fork = src.tree.fork, additional = [] } = {}) {
256
+ async copyPrologue (src, { additional = [] } = {}) {
257
257
  await this._mutex.lock()
258
258
 
259
259
  try {
@@ -263,120 +263,103 @@ module.exports = class Core {
263
263
  throw err
264
264
  }
265
265
 
266
- const initialLength = this.tree.length
267
-
268
266
  try {
269
- const updates = []
270
-
271
- let pos = 0
272
- const copyLength = Math.min(src.tree.length, length)
273
-
274
- while (pos < copyLength) {
275
- const segmentStart = maximumSegmentStart(pos, src.bitfield, this.bitfield)
276
- if (segmentStart >= length || segmentStart < 0) break
267
+ const prologue = this.header.manifest && this.header.manifest.prologue
268
+ if (!prologue) throw INVALID_OPERATION('No prologue present')
277
269
 
278
- const segmentEnd = Math.min(src.tree.length, minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield))
270
+ const srcLength = prologue.length - additional.length
271
+ const srcBatch = srcLength !== src.tree.length ? await src.tree.truncate(srcLength) : src.tree.batch()
272
+ const srcRoots = srcBatch.roots.slice(0)
273
+ const srcByteLength = srcBatch.byteLength
279
274
 
280
- const segment = []
275
+ for (const blk of additional) srcBatch.append(blk)
281
276
 
282
- pos = segmentStart
283
- while (pos < segmentEnd) {
284
- const val = await src.blocks.get(pos++)
285
- segment.push(val)
286
- }
277
+ if (!b4a.equals(srcBatch.hash(), prologue.hash)) throw INVALID_OPERATION('Source tree is conflicting')
287
278
 
288
- const [offset] = await src.tree.byteRange(2 * segmentStart)
289
- await this.blocks.putBatch(segmentStart, segment, offset)
279
+ // all hashes are correct, lets copy
290
280
 
291
- this._setBitfieldRange(segmentStart, segmentEnd - segmentStart, true)
292
-
293
- pos = segmentEnd + 1
294
-
295
- updates.push({
296
- drop: false,
297
- start: segmentStart,
298
- length: segmentEnd - segmentStart
299
- })
281
+ const entry = {
282
+ userData: null,
283
+ treeNodes: srcRoots,
284
+ treeUpgrade: null,
285
+ bitfield: null
300
286
  }
301
287
 
302
- for (let i = 0; i < copyLength * 2; i++) {
303
- const node = await src.tree.get(i, false)
304
- if (node === null) continue
305
-
306
- await this.tree.addNode(node)
288
+ if (additional.length) {
289
+ await this.blocks.putBatch(srcLength, additional, srcByteLength)
290
+ entry.treeNodes = entry.treeNodes.concat(srcBatch.nodes)
291
+ entry.bitfield = {
292
+ drop: false,
293
+ start: srcLength,
294
+ length: additional.length
295
+ }
307
296
  }
308
297
 
309
- await this.tree.flush()
298
+ await this.oplog.append([entry], false)
299
+ this.tree.addNodes(entry.treeNodes)
310
300
 
311
- this.tree.fork = fork
301
+ if (this.header.tree.length < srcBatch.length) {
302
+ this.header.tree.length = srcBatch.length
303
+ this.header.tree.rootHash = srcBatch.hash()
312
304
 
313
- let batch = this.tree.batch()
305
+ this.tree.length = srcBatch.length
306
+ this.tree.byteLength = srcBatch.byteLength
307
+ this.tree.roots = srcBatch.roots
308
+ this.onupdate(0b0001, null, null, null)
309
+ }
314
310
 
315
- // add additional blocks
316
- if (length > src.tree.length) {
317
- const missing = length - src.tree.length
318
- const offset = src.tree.length - sourceLength
311
+ if (entry.bitfield) {
312
+ this._setBitfieldRange(entry.bitfield.start, entry.bitfield.length, true)
313
+ this.onupdate(0, entry.bitfield, null, null)
314
+ }
319
315
 
320
- if (additional.length < missing + offset) {
321
- throw INVALID_OPERATION('Insufficient additional nodes were passed')
322
- }
316
+ await this._flushOplog()
323
317
 
324
- const source = src.tree.batch()
318
+ // no more additional blocks now and we should be consistant on disk
319
+ // copy over all existing segments...
325
320
 
326
- batch.roots = [...source.roots]
327
- batch.length = source.length
328
- batch.byteLength = source.byteLength
321
+ let segmentEnd = 0
329
322
 
330
- const blocks = additional.length === missing ? additional : additional.slice(offset, offset + missing)
323
+ while (segmentEnd < srcLength) {
324
+ const segmentStart = maximumSegmentStart(segmentEnd, src.bitfield, this.bitfield)
325
+ if (segmentStart >= srcLength || segmentStart < 0) break
331
326
 
332
- await this.blocks.putBatch(source.length, blocks, source.byteLength)
333
- this._setBitfieldRange(source.length, missing, true)
327
+ // max segment is 65536 to avoid running out of memory
328
+ segmentEnd = Math.min(segmentStart + 65536, srcLength, minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield))
334
329
 
335
- updates.push({
330
+ const treeNodes = await src.tree.getNeededNodes(srcLength, segmentStart, segmentEnd)
331
+ const bitfield = {
336
332
  drop: false,
337
- start: source.length,
338
- length: missing
339
- })
340
-
341
- for (const block of blocks) await batch.append(block)
342
- } else {
343
- const source = length < src.tree.length ? await src.tree.truncate(length) : src.tree.batch()
333
+ start: segmentStart,
334
+ length: segmentEnd - segmentStart
335
+ }
344
336
 
345
- this.tree.roots = [...source.roots]
346
- this.tree.length = source.length
347
- this.tree.byteLength = source.byteLength
337
+ const segment = []
338
+ for (let i = segmentStart; i < segmentEnd; i++) {
339
+ const blk = await src.blocks.get(i)
340
+ segment.push(blk)
341
+ }
348
342
 
349
- // update batch
350
- batch = this.tree.batch()
351
- }
343
+ const offset = await src.tree.byteOffset(2 * segmentStart)
344
+ await this.blocks.putBatch(segmentStart, segment, offset)
352
345
 
353
- // verify if upgraded
354
- if (batch.length > initialLength) {
355
- try {
356
- batch.signature = signature
357
-
358
- this._verifyBatchUpgrade(batch, this.header.manifest)
359
- this.tree.signature = signature
360
- } catch (err) {
361
- this.tree.signature = null
362
- // TODO: how to handle signature failure?
363
- throw err
346
+ const entry = {
347
+ userData: null,
348
+ treeNodes,
349
+ treeUpgrade: null,
350
+ bitfield
364
351
  }
365
- }
366
352
 
367
- await batch.commit()
368
-
369
- this.header.tree.length = this.tree.length
370
- this.header.tree.fork = this.tree.fork
371
- this.header.tree.rootHash = this.tree.hash()
372
- this.header.tree.signature = this.tree.signature
353
+ await this.oplog.append([entry], false)
354
+ this.tree.addNodes(treeNodes)
355
+ this._setBitfieldRange(bitfield.start, bitfield.length, true)
356
+ this.onupdate(0, bitfield, null, null)
357
+ await this._flushOplog()
358
+ }
373
359
 
374
360
  this.header.userData = src.header.userData.slice(0)
375
- this.header.hints.contiguousLength = Math.min(src.header.hints.contiguousLength, this.header.tree.length)
376
-
377
- for (const bitfield of updates) {
378
- this.onupdate(0b0001, bitfield, null, null)
379
- }
361
+ const contig = Math.min(src.header.hints.contiguousLength, srcBatch.length)
362
+ if (this.header.hints.contiguousLength < contig) this.header.hints.contiguousLength = contig
380
363
 
381
364
  await this._flushOplog()
382
365
  } finally {
@@ -57,6 +57,68 @@ class MerkleTreeBatch {
57
57
  this.upgraded = false
58
58
  }
59
59
 
60
+ checkout (length, additionalRoots) {
61
+ const roots = []
62
+ let r = 0
63
+
64
+ const head = 2 * length - 2
65
+ const gaps = new Set()
66
+ const all = new Map()
67
+
68
+ // additional roots is so the original roots can be passed (we mutate the array in appendRoot)
69
+ if (additionalRoots) {
70
+ for (const node of additionalRoots) all.set(node.index, node)
71
+ }
72
+
73
+ for (const node of this.nodes) all.set(node.index, node)
74
+
75
+ for (const index of flat.fullRoots(head + 2)) {
76
+ const left = flat.leftSpan(index)
77
+ if (left !== 0) gaps.add(left - 1)
78
+
79
+ if (r < this.roots.length && this.roots[r].index === index) {
80
+ roots.push(this.roots[r++])
81
+ continue
82
+ }
83
+ const node = all.get(index)
84
+ if (!node) throw new BAD_ARGUMENT('root missing for given length')
85
+ roots.push(node)
86
+ }
87
+
88
+ this.roots = roots
89
+ this.length = length
90
+ this.byteLength = totalSize(roots)
91
+ this.hashCached = null
92
+ this.signature = null
93
+
94
+ for (let i = 0; i < this.nodes.length; i++) {
95
+ const index = this.nodes[i].index
96
+ if (index <= head && !gaps.has(index)) continue
97
+ const last = this.nodes.pop()
98
+ if (i < this.nodes.length) this.nodes[i--] = last
99
+ }
100
+ }
101
+
102
+ prune (length) {
103
+ if (length === 0) return
104
+
105
+ const head = 2 * length - 2
106
+ const gaps = new Set()
107
+
108
+ // TODO: make a function for this in flat-tree
109
+ for (const index of flat.fullRoots(head + 2)) {
110
+ const left = flat.leftSpan(index)
111
+ if (left !== 0) gaps.add(left - 1)
112
+ }
113
+
114
+ for (let i = 0; i < this.nodes.length; i++) {
115
+ const index = this.nodes[i].index
116
+ if (index > head || gaps.has(index)) continue
117
+ const last = this.nodes.pop()
118
+ if (i < this.nodes.length) this.nodes[i--] = last
119
+ }
120
+ }
121
+
60
122
  clone () {
61
123
  const b = new MerkleTreeBatch(this.tree)
62
124
 
@@ -429,6 +491,37 @@ module.exports = class MerkleTree {
429
491
  this.prologue = { hash, length }
430
492
  }
431
493
 
494
+ addNodes (nodes) {
495
+ for (let i = 0; i < nodes.length; i++) {
496
+ const node = nodes[i]
497
+ this.unflushed.set(node.index, node)
498
+ }
499
+ }
500
+
501
+ getNeededNodes (length, start, end) {
502
+ const nodes = new Map()
503
+ const head = length * 2
504
+
505
+ for (let i = start; i < end; i++) {
506
+ const ite = flat.iterator(i * 2)
507
+
508
+ while (true) {
509
+ if (nodes.has(ite.index)) break
510
+ nodes.set(ite.index, this.get(ite.index, true))
511
+
512
+ const sibling = ite.sibling()
513
+
514
+ ite.parent()
515
+ if (ite.contains(head)) break
516
+
517
+ if (nodes.has(sibling)) break
518
+ nodes.set(sibling, this.get(sibling, true))
519
+ }
520
+ }
521
+
522
+ return Promise.all([...nodes.values()])
523
+ }
524
+
432
525
  async upgradeable (length) {
433
526
  const indexes = flat.fullRoots(2 * length)
434
527
  const roots = new Array(indexes.length)
@@ -826,7 +919,7 @@ function verifyUpgrade ({ fork, upgrade }, blockRoot, batch) {
826
919
 
827
920
  if (prologue) {
828
921
  const { start, length } = upgrade
829
- if (start < prologue.length && (start !== 0 || length !== prologue.length)) {
922
+ if (start < prologue.length && (start !== 0 || length < prologue.length)) {
830
923
  throw INVALID_PROOF('Upgrade does not satisfy prologue')
831
924
  }
832
925
  }
@@ -230,7 +230,11 @@ module.exports = class RemoteBitfield {
230
230
  i++
231
231
  }
232
232
 
233
- return val ? -1 : this._maxSegments * BITS_PER_SEGMENT
233
+ // For the val === false case, we always return at least
234
+ // the 'position', also if nothing was found
235
+ return val
236
+ ? -1
237
+ : Math.max(position, this._maxSegments * BITS_PER_SEGMENT)
234
238
  }
235
239
 
236
240
  firstSet (position) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.36.4",
3
+ "version": "10.37.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {