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 CHANGED
@@ -1,5 +1,5 @@
1
1
  const { EventEmitter } = require('events')
2
- const raf = require('random-access-file')
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') return storage
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
- return function createFile (name) {
169
- const locked = name === toLock || name.endsWith('/' + toLock)
170
- const lock = locked ? fsctl.lock : null
171
- const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
172
- return raf(name, { directory, lock, sparse })
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 truncated = (status & 0b10) !== 0
491
- const appended = (status & 0b01) !== 0
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
- s.emit('truncate', bitfield.start, this.core.tree.fork)
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
- // For sparse sessions, immediately emit appends. Non-sparse sessions
508
- // are handled separately and only emit appends when their contiguous
509
- // length is updated.
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
- this.replicator.onupgrade()
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 requireMaybe (name) {
856
- try {
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, oncontigupdate) {
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, opts.oncontigupdate || 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
- this.onupdate(0b01, entry.bitfield, null, null)
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._updateContiguousLength(bitfield.start, bitfield.length)
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
- this.onupdate(0b01, bitfield, value, from)
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._updateContiguousLength(bitfield.start, bitfield.length)
389
+ status = updateContig(this.header, bitfield, this.bitfield)
393
390
  }
394
391
 
395
392
  batch.commit()
396
393
 
397
- this.onupdate(0, bitfield, value, from)
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 appended = batch.length > batch.ancestors
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
- this.onupdate(appended ? 0b11 : 0b10, entry.bitfield, null, from)
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
 
@@ -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
- resolve({ index, size, hash })
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.51",
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": "^2.1.4",
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.0.0"
51
+ "xache": "^1.1.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "brittle": "^2.0.0",
55
- "hyperswarm": "next",
56
- "random-access-memory": "^4.1.0",
57
- "random-access-memory-overlay": "^1.0.0",
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
  }