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 +1 -50
- package/lib/batch.js +25 -8
- package/lib/core.js +71 -88
- package/lib/merkle-tree.js +94 -1
- package/lib/remote-bitfield.js +5 -1
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
for (const blk of additional) srcBatch.append(blk)
|
|
281
276
|
|
|
282
|
-
|
|
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
|
-
|
|
289
|
-
await this.blocks.putBatch(segmentStart, segment, offset)
|
|
279
|
+
// all hashes are correct, lets copy
|
|
290
280
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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.
|
|
298
|
+
await this.oplog.append([entry], false)
|
|
299
|
+
this.tree.addNodes(entry.treeNodes)
|
|
310
300
|
|
|
311
|
-
this.tree.
|
|
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
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
321
|
-
throw INVALID_OPERATION('Insufficient additional nodes were passed')
|
|
322
|
-
}
|
|
316
|
+
await this._flushOplog()
|
|
323
317
|
|
|
324
|
-
|
|
318
|
+
// no more additional blocks now and we should be consistant on disk
|
|
319
|
+
// copy over all existing segments...
|
|
325
320
|
|
|
326
|
-
|
|
327
|
-
batch.length = source.length
|
|
328
|
-
batch.byteLength = source.byteLength
|
|
321
|
+
let segmentEnd = 0
|
|
329
322
|
|
|
330
|
-
|
|
323
|
+
while (segmentEnd < srcLength) {
|
|
324
|
+
const segmentStart = maximumSegmentStart(segmentEnd, src.bitfield, this.bitfield)
|
|
325
|
+
if (segmentStart >= srcLength || segmentStart < 0) break
|
|
331
326
|
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
330
|
+
const treeNodes = await src.tree.getNeededNodes(srcLength, segmentStart, segmentEnd)
|
|
331
|
+
const bitfield = {
|
|
336
332
|
drop: false,
|
|
337
|
-
start:
|
|
338
|
-
length:
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
343
|
+
const offset = await src.tree.byteOffset(2 * segmentStart)
|
|
344
|
+
await this.blocks.putBatch(segmentStart, segment, offset)
|
|
352
345
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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 {
|
package/lib/merkle-tree.js
CHANGED
|
@@ -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
|
|
922
|
+
if (start < prologue.length && (start !== 0 || length < prologue.length)) {
|
|
830
923
|
throw INVALID_PROOF('Upgrade does not satisfy prologue')
|
|
831
924
|
}
|
|
832
925
|
}
|
package/lib/remote-bitfield.js
CHANGED
|
@@ -230,7 +230,11 @@ module.exports = class RemoteBitfield {
|
|
|
230
230
|
i++
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
|
|
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) {
|