hypercore 10.0.0-alpha.51 → 10.0.0-alpha.54
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 +38 -36
- package/lib/core.js +54 -27
- package/lib/merkle-tree.js +15 -5
- package/package.json +6 -9
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { EventEmitter } = require('events')
|
|
2
|
-
const
|
|
2
|
+
const RAF = require('random-access-file')
|
|
3
3
|
const isOptions = require('is-options')
|
|
4
4
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
5
5
|
const c = require('compact-encoding')
|
|
@@ -9,8 +9,6 @@ const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
|
9
9
|
const Protomux = require('protomux')
|
|
10
10
|
const codecs = require('codecs')
|
|
11
11
|
|
|
12
|
-
const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
13
|
-
|
|
14
12
|
const Replicator = require('./lib/replicator')
|
|
15
13
|
const Core = require('./lib/core')
|
|
16
14
|
const BlockEncryption = require('./lib/block-encryption')
|
|
@@ -162,14 +160,25 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
static defaultStorage (storage, opts = {}) {
|
|
165
|
-
if (typeof storage !== 'string')
|
|
163
|
+
if (typeof storage !== 'string') {
|
|
164
|
+
if (!isRandomAccessClass(storage)) return storage
|
|
165
|
+
const Cls = storage // just to satisfy standard...
|
|
166
|
+
return name => new Cls(name)
|
|
167
|
+
}
|
|
168
|
+
|
|
166
169
|
const directory = storage
|
|
167
170
|
const toLock = opts.lock || 'oplog'
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
|
|
172
|
+
return createFile
|
|
173
|
+
|
|
174
|
+
function createFile (name) {
|
|
175
|
+
const lock = isFile(name, toLock)
|
|
176
|
+
const sparse = isFile(name, 'data') || isFile(name, 'bitfield') || isFile(name, 'tree')
|
|
177
|
+
return new RAF(name, { directory, lock, sparse })
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isFile (name, n) {
|
|
181
|
+
return name === n || name.endsWith('/' + n)
|
|
173
182
|
}
|
|
174
183
|
}
|
|
175
184
|
|
|
@@ -303,8 +312,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
303
312
|
crypto: this.crypto,
|
|
304
313
|
legacy: opts.legacy,
|
|
305
314
|
auth: opts.auth,
|
|
306
|
-
onupdate: this._oncoreupdate.bind(this)
|
|
307
|
-
oncontigupdate: this._oncorecontigupdate.bind(this)
|
|
315
|
+
onupdate: this._oncoreupdate.bind(this)
|
|
308
316
|
})
|
|
309
317
|
|
|
310
318
|
if (opts.userData) {
|
|
@@ -487,30 +495,38 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
487
495
|
|
|
488
496
|
_oncoreupdate (status, bitfield, value, from) {
|
|
489
497
|
if (status !== 0) {
|
|
490
|
-
const
|
|
491
|
-
const
|
|
498
|
+
const truncatedNonSparse = (status & 0b1000) !== 0
|
|
499
|
+
const appendedNonSparse = (status & 0b0100) !== 0
|
|
500
|
+
const truncated = (status & 0b0010) !== 0
|
|
501
|
+
const appended = (status & 0b0001) !== 0
|
|
492
502
|
|
|
493
503
|
if (truncated) {
|
|
494
504
|
this.replicator.ontruncate(bitfield.start)
|
|
495
505
|
}
|
|
496
506
|
|
|
507
|
+
if ((status & 0b0011) !== 0) {
|
|
508
|
+
this.replicator.onupgrade()
|
|
509
|
+
}
|
|
510
|
+
|
|
497
511
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
498
512
|
const s = this.sessions[i]
|
|
499
513
|
|
|
500
514
|
if (truncated) {
|
|
501
515
|
if (s.cache) s.cache.clear()
|
|
502
|
-
|
|
516
|
+
|
|
503
517
|
// If snapshotted, make sure to update our compat so we can fail gets
|
|
504
518
|
if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
|
|
505
519
|
}
|
|
506
520
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (appended && s.sparse) s.emit('append')
|
|
511
|
-
}
|
|
521
|
+
if (s.sparse ? truncated : truncatedNonSparse) {
|
|
522
|
+
s.emit('truncate', bitfield.start, this.core.tree.fork)
|
|
523
|
+
}
|
|
512
524
|
|
|
513
|
-
|
|
525
|
+
// For sparse sessions, immediately emit appends. If non-sparse, emit if contig length has updated
|
|
526
|
+
if (s.sparse ? appended : appendedNonSparse) {
|
|
527
|
+
s.emit('append')
|
|
528
|
+
}
|
|
529
|
+
}
|
|
514
530
|
}
|
|
515
531
|
|
|
516
532
|
if (bitfield) {
|
|
@@ -526,16 +542,6 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
526
542
|
}
|
|
527
543
|
}
|
|
528
544
|
|
|
529
|
-
_oncorecontigupdate () {
|
|
530
|
-
// For non-sparse sessions, emit appends only when the contiguous length is
|
|
531
|
-
// updated.
|
|
532
|
-
for (let i = 0; i < this.sessions.length; i++) {
|
|
533
|
-
const s = this.sessions[i]
|
|
534
|
-
|
|
535
|
-
if (!s.sparse) s.emit('append')
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
545
|
_onpeerupdate (added, peer) {
|
|
540
546
|
const name = added ? 'peer-add' : 'peer-remove'
|
|
541
547
|
|
|
@@ -852,12 +858,8 @@ function isStream (s) {
|
|
|
852
858
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
853
859
|
}
|
|
854
860
|
|
|
855
|
-
function
|
|
856
|
-
|
|
857
|
-
return require(name)
|
|
858
|
-
} catch (_) {
|
|
859
|
-
return null
|
|
860
|
-
}
|
|
861
|
+
function isRandomAccessClass (fn) {
|
|
862
|
+
return !!(typeof fn === 'function' && fn.prototype && typeof fn.prototype.open === 'function')
|
|
861
863
|
}
|
|
862
864
|
|
|
863
865
|
function toHex (buf) {
|
package/lib/core.js
CHANGED
|
@@ -9,9 +9,8 @@ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = req
|
|
|
9
9
|
const m = require('./messages')
|
|
10
10
|
|
|
11
11
|
module.exports = class Core {
|
|
12
|
-
constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate
|
|
12
|
+
constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
|
|
13
13
|
this.onupdate = onupdate
|
|
14
|
-
this.oncontigupdate = oncontigupdate
|
|
15
14
|
this.header = header
|
|
16
15
|
this.crypto = crypto
|
|
17
16
|
this.oplog = oplog
|
|
@@ -27,8 +26,6 @@ module.exports = class Core {
|
|
|
27
26
|
this._verifiesFlushed = null
|
|
28
27
|
this._mutex = new Mutex()
|
|
29
28
|
this._legacy = legacy
|
|
30
|
-
|
|
31
|
-
this._updateContiguousLength(header.contiguousLength)
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
static async open (storage, opts = {}) {
|
|
@@ -134,6 +131,11 @@ module.exports = class Core {
|
|
|
134
131
|
await bitfield.clear()
|
|
135
132
|
}
|
|
136
133
|
|
|
134
|
+
// compat from earlier version that do not store contig length
|
|
135
|
+
if (header.contiguousLength === 0) {
|
|
136
|
+
while (bitfield.get(header.contiguousLength)) header.contiguousLength++
|
|
137
|
+
}
|
|
138
|
+
|
|
137
139
|
const auth = opts.auth || this.createAuth(crypto, header.signer)
|
|
138
140
|
|
|
139
141
|
for (const e of entries) {
|
|
@@ -149,6 +151,7 @@ module.exports = class Core {
|
|
|
149
151
|
|
|
150
152
|
if (e.bitfield) {
|
|
151
153
|
bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
|
|
154
|
+
updateContig(header, e.bitfield, bitfield)
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
if (e.treeUpgrade) {
|
|
@@ -165,7 +168,7 @@ module.exports = class Core {
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop
|
|
171
|
+
return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
_shouldFlush () {
|
|
@@ -196,17 +199,6 @@ module.exports = class Core {
|
|
|
196
199
|
await this.blocks.put(index, value, byteOffset)
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
_updateContiguousLength (index, length = 0) {
|
|
200
|
-
if (index === this.header.contiguousLength) {
|
|
201
|
-
let i = this.header.contiguousLength + length
|
|
202
|
-
|
|
203
|
-
while (this.bitfield.get(i)) i++
|
|
204
|
-
|
|
205
|
-
this.header.contiguousLength = i
|
|
206
|
-
this.oncontigupdate()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
202
|
async userData (key, value) {
|
|
211
203
|
// TODO: each oplog append can set user data, so we should have a way
|
|
212
204
|
// to just hitch a ride on one of the other ongoing appends?
|
|
@@ -286,12 +278,12 @@ module.exports = class Core {
|
|
|
286
278
|
this.bitfield.setRange(batch.ancestors, batch.length - batch.ancestors, true)
|
|
287
279
|
batch.commit()
|
|
288
280
|
|
|
289
|
-
this.header.contiguousLength = batch.length
|
|
290
|
-
this.oncontigupdate()
|
|
291
281
|
this.header.tree.length = batch.length
|
|
292
282
|
this.header.tree.rootHash = hash
|
|
293
283
|
this.header.tree.signature = batch.signature
|
|
294
|
-
|
|
284
|
+
|
|
285
|
+
const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
|
|
286
|
+
this.onupdate(status, entry.bitfield, null, null)
|
|
295
287
|
|
|
296
288
|
if (this._shouldFlush()) await this._flushOplog()
|
|
297
289
|
|
|
@@ -329,9 +321,11 @@ module.exports = class Core {
|
|
|
329
321
|
|
|
330
322
|
await this.oplog.append([entry], false)
|
|
331
323
|
|
|
324
|
+
let status = 0b0001
|
|
325
|
+
|
|
332
326
|
if (bitfield) {
|
|
333
327
|
this.bitfield.set(bitfield.start, true)
|
|
334
|
-
this.
|
|
328
|
+
status |= updateContig(this.header, bitfield, this.bitfield)
|
|
335
329
|
}
|
|
336
330
|
|
|
337
331
|
batch.commit()
|
|
@@ -340,7 +334,8 @@ module.exports = class Core {
|
|
|
340
334
|
this.header.tree.length = batch.length
|
|
341
335
|
this.header.tree.rootHash = batch.rootHash
|
|
342
336
|
this.header.tree.signature = batch.signature
|
|
343
|
-
|
|
337
|
+
|
|
338
|
+
this.onupdate(status, bitfield, value, from)
|
|
344
339
|
|
|
345
340
|
if (this._shouldFlush()) await this._flushOplog()
|
|
346
341
|
} finally {
|
|
@@ -387,14 +382,16 @@ module.exports = class Core {
|
|
|
387
382
|
continue
|
|
388
383
|
}
|
|
389
384
|
|
|
385
|
+
let status = 0
|
|
386
|
+
|
|
390
387
|
if (bitfield) {
|
|
391
388
|
this.bitfield.set(bitfield.start, true)
|
|
392
|
-
this.
|
|
389
|
+
status = updateContig(this.header, bitfield, this.bitfield)
|
|
393
390
|
}
|
|
394
391
|
|
|
395
392
|
batch.commit()
|
|
396
393
|
|
|
397
|
-
this.onupdate(
|
|
394
|
+
this.onupdate(status, bitfield, value, from)
|
|
398
395
|
}
|
|
399
396
|
|
|
400
397
|
if (this._shouldFlush()) await this._flushOplog()
|
|
@@ -472,15 +469,15 @@ module.exports = class Core {
|
|
|
472
469
|
addReorgHint(this.header.hints.reorgs, this.tree, batch)
|
|
473
470
|
batch.commit()
|
|
474
471
|
|
|
475
|
-
const
|
|
472
|
+
const contigStatus = updateContig(this.header, entry.bitfield, this.bitfield)
|
|
473
|
+
const status = ((batch.length > batch.ancestors) ? 0b0011 : 0b0010) | contigStatus
|
|
476
474
|
|
|
477
|
-
this.header.contiguousLength = Math.min(batch.ancestors, this.header.contiguousLength)
|
|
478
|
-
this.oncontigupdate()
|
|
479
475
|
this.header.tree.fork = batch.fork
|
|
480
476
|
this.header.tree.length = batch.length
|
|
481
477
|
this.header.tree.rootHash = batch.hash()
|
|
482
478
|
this.header.tree.signature = batch.signature
|
|
483
|
-
|
|
479
|
+
|
|
480
|
+
this.onupdate(status, entry.bitfield, null, from)
|
|
484
481
|
|
|
485
482
|
// TODO: there is a bug in the merkle tree atm where it cannot handle unflushed
|
|
486
483
|
// truncates if we append or download anything after the truncation point later on
|
|
@@ -501,6 +498,36 @@ module.exports = class Core {
|
|
|
501
498
|
}
|
|
502
499
|
}
|
|
503
500
|
|
|
501
|
+
function updateContig (header, upd, bitfield) {
|
|
502
|
+
const end = upd.start + upd.length
|
|
503
|
+
|
|
504
|
+
let c = header.contiguousLength
|
|
505
|
+
|
|
506
|
+
if (upd.drop) {
|
|
507
|
+
// If we dropped a block in the current contig range, "downgrade" it
|
|
508
|
+
if (c <= end && c > upd.start) {
|
|
509
|
+
c = upd.start
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
if (c <= end && c >= upd.start) {
|
|
513
|
+
c = end
|
|
514
|
+
while (bitfield.get(c)) c++
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (c === header.contiguousLength) {
|
|
519
|
+
return 0b0000
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (c > header.contiguousLength) {
|
|
523
|
+
header.contiguousLength = c
|
|
524
|
+
return 0b0100
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
header.contiguousLength = c
|
|
528
|
+
return 0b1000
|
|
529
|
+
}
|
|
530
|
+
|
|
504
531
|
function addReorgHint (list, tree, batch) {
|
|
505
532
|
if (tree.length === 0 || tree.fork === batch.fork) return
|
|
506
533
|
|
package/lib/merkle-tree.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const flat = require('flat-tree')
|
|
2
2
|
const crypto = require('hypercore-crypto')
|
|
3
3
|
const c = require('compact-encoding')
|
|
4
|
+
const Xache = require('xache')
|
|
4
5
|
const b4a = require('b4a')
|
|
5
6
|
const caps = require('./caps')
|
|
6
7
|
|
|
7
8
|
const BLANK_HASH = b4a.alloc(32)
|
|
8
9
|
const OLD_TREE = b4a.from([5, 2, 87, 2, 0, 0, 40, 7, 66, 76, 65, 75, 69, 50, 98])
|
|
10
|
+
const TREE_CACHE = 128 // speeds up linear scans by A LOT
|
|
9
11
|
|
|
10
12
|
class NodeQueue {
|
|
11
13
|
constructor (nodes, extra = null) {
|
|
@@ -140,6 +142,7 @@ class MerkleTreeBatch {
|
|
|
140
142
|
: this.ancestors
|
|
141
143
|
|
|
142
144
|
this.tree.truncated = true
|
|
145
|
+
this.tree.cache = new Xache({ maxSize: this.tree.cache.maxSize })
|
|
143
146
|
truncateMap(this.tree.unflushed, this.ancestors)
|
|
144
147
|
if (this.tree.flushing !== null) truncateMap(this.tree.flushing, this.ancestors)
|
|
145
148
|
}
|
|
@@ -350,6 +353,7 @@ module.exports = class MerkleTree {
|
|
|
350
353
|
|
|
351
354
|
this.storage = storage
|
|
352
355
|
this.unflushed = new Map()
|
|
356
|
+
this.cache = new Xache({ maxSize: TREE_CACHE })
|
|
353
357
|
this.flushing = null
|
|
354
358
|
this.truncated = false
|
|
355
359
|
this.truncateTo = 0
|
|
@@ -403,6 +407,9 @@ module.exports = class MerkleTree {
|
|
|
403
407
|
}
|
|
404
408
|
|
|
405
409
|
get (index, error = true) {
|
|
410
|
+
const c = this.cache.get(index)
|
|
411
|
+
if (c) return c
|
|
412
|
+
|
|
406
413
|
let node = this.unflushed.get(index)
|
|
407
414
|
|
|
408
415
|
if (this.flushing !== null && node === undefined) {
|
|
@@ -422,7 +429,7 @@ module.exports = class MerkleTree {
|
|
|
422
429
|
return Promise.resolve(node)
|
|
423
430
|
}
|
|
424
431
|
|
|
425
|
-
return getStoredNode(this.storage, index, error)
|
|
432
|
+
return getStoredNode(this.storage, index, this.cache, error)
|
|
426
433
|
}
|
|
427
434
|
|
|
428
435
|
async flush () {
|
|
@@ -495,6 +502,7 @@ module.exports = class MerkleTree {
|
|
|
495
502
|
}
|
|
496
503
|
|
|
497
504
|
clear () {
|
|
505
|
+
this.cache = new Xache({ maxSize: this.cache.maxSize })
|
|
498
506
|
this.truncated = true
|
|
499
507
|
this.truncateTo = 0
|
|
500
508
|
this.roots = []
|
|
@@ -754,7 +762,7 @@ module.exports = class MerkleTree {
|
|
|
754
762
|
|
|
755
763
|
const roots = []
|
|
756
764
|
for (const index of flat.fullRoots(2 * length)) {
|
|
757
|
-
roots.push(await getStoredNode(storage, index, true))
|
|
765
|
+
roots.push(await getStoredNode(storage, index, null, true))
|
|
758
766
|
}
|
|
759
767
|
|
|
760
768
|
return new MerkleTree(storage, roots, opts.fork || 0, opts.signature || null)
|
|
@@ -1085,7 +1093,7 @@ function blankNode (index) {
|
|
|
1085
1093
|
|
|
1086
1094
|
// Storage methods
|
|
1087
1095
|
|
|
1088
|
-
function getStoredNode (storage, index, error) {
|
|
1096
|
+
function getStoredNode (storage, index, cache, error) {
|
|
1089
1097
|
return new Promise((resolve, reject) => {
|
|
1090
1098
|
storage.read(40 * index, 40, (err, data) => {
|
|
1091
1099
|
if (err) {
|
|
@@ -1103,7 +1111,9 @@ function getStoredNode (storage, index, error) {
|
|
|
1103
1111
|
return
|
|
1104
1112
|
}
|
|
1105
1113
|
|
|
1106
|
-
|
|
1114
|
+
const node = { index, size, hash }
|
|
1115
|
+
if (cache !== null) cache.set(index, node)
|
|
1116
|
+
resolve(node)
|
|
1107
1117
|
})
|
|
1108
1118
|
})
|
|
1109
1119
|
}
|
|
@@ -1122,7 +1132,7 @@ async function autoLength (storage) {
|
|
|
1122
1132
|
if (!nodes) return 0
|
|
1123
1133
|
const ite = flat.iterator(nodes - 1)
|
|
1124
1134
|
let index = nodes - 1
|
|
1125
|
-
while (await getStoredNode(storage, ite.parent(), false)) index = ite.index
|
|
1135
|
+
while (await getStoredNode(storage, ite.parent(), null, false)) index = ite.index
|
|
1126
1136
|
return flat.rightSpan(index) / 2 + 1
|
|
1127
1137
|
}
|
|
1128
1138
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypercore",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.54",
|
|
4
4
|
"description": "Hypercore 10",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -43,22 +43,19 @@
|
|
|
43
43
|
"hypercore-crypto": "^3.2.1",
|
|
44
44
|
"is-options": "^1.0.1",
|
|
45
45
|
"protomux": "^3.2.0",
|
|
46
|
-
"random-access-file": "^
|
|
46
|
+
"random-access-file": "^3.0.1",
|
|
47
47
|
"random-array-iterator": "^1.0.0",
|
|
48
48
|
"safety-catch": "^1.0.1",
|
|
49
49
|
"sodium-universal": "^3.0.4",
|
|
50
50
|
"streamx": "^2.12.4",
|
|
51
|
-
"xache": "^1.
|
|
51
|
+
"xache": "^1.1.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"brittle": "^2.0.0",
|
|
55
|
-
"hyperswarm": "
|
|
56
|
-
"random-access-memory": "^
|
|
57
|
-
"random-access-memory-overlay": "^
|
|
55
|
+
"hyperswarm": "^4.1.1",
|
|
56
|
+
"random-access-memory": "^5.0.0",
|
|
57
|
+
"random-access-memory-overlay": "^2.0.0",
|
|
58
58
|
"standard": "^16.0.3",
|
|
59
59
|
"tmp-promise": "^3.0.2"
|
|
60
|
-
},
|
|
61
|
-
"optionalDependencies": {
|
|
62
|
-
"fsctl": "^1.0.0"
|
|
63
60
|
}
|
|
64
61
|
}
|