hypercore 10.24.4 → 10.24.6

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
@@ -629,7 +629,7 @@ module.exports = class Hypercore extends EventEmitter {
629
629
  this.replicator.ontruncate(bitfield.start)
630
630
  }
631
631
 
632
- if ((status & 0b0011) !== 0) {
632
+ if ((status & 0b10011) !== 0) {
633
633
  this.replicator.onupgrade()
634
634
  }
635
635
 
@@ -958,12 +958,8 @@ module.exports = class Hypercore extends EventEmitter {
958
958
  }
959
959
 
960
960
  async append (blocks, opts = {}) {
961
- if (this._batch && this !== this._batch.session) throw BATCH_UNFLUSHED()
962
-
963
- // if a batched append, run unlocked as it already locked...
964
- const lock = !this._batch
965
-
966
961
  if (this.opened === false) await this.opening
962
+ if (this._batch) throw BATCH_UNFLUSHED()
967
963
 
968
964
  const { keyPair = this.keyPair, signature = null } = opts
969
965
  const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
@@ -982,7 +978,7 @@ module.exports = class Hypercore extends EventEmitter {
982
978
  }
983
979
  }
984
980
 
985
- return this.core.append(buffers, { lock, keyPair, signature, preappend })
981
+ return this.core.append(buffers, { keyPair, signature, preappend })
986
982
  }
987
983
 
988
984
  async treeHash (length) {
package/lib/batch.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const { BLOCK_NOT_AVAILABLE, SESSION_CLOSED } = require('hypercore-errors')
2
2
  const EventEmitter = require('events')
3
3
  const c = require('compact-encoding')
4
- const b4a = require('b4a')
5
4
 
6
5
  module.exports = class HypercoreBatch extends EventEmitter {
7
6
  constructor (session, autoClose) {
@@ -76,7 +75,6 @@ module.exports = class HypercoreBatch extends EventEmitter {
76
75
  async update (opts) {
77
76
  if (this.opened === false) await this.ready()
78
77
  await this.session.update(opts)
79
- await this._catchup()
80
78
  }
81
79
 
82
80
  setUserData (key, value, opts) {
@@ -253,64 +251,26 @@ module.exports = class HypercoreBatch extends EventEmitter {
253
251
  return flushed
254
252
  }
255
253
 
256
- async _catchup () {
257
- if (this.core.tree.length === this._sessionLength) return true
258
-
259
- const batchLength = this._sessionLength + this._appends.length
260
-
261
- if (this.core.tree.length > batchLength) return false
262
-
263
- const b = this.createTreeBatch()
264
-
265
- for (const root of this.core.tree.roots) {
266
- const batchRoot = await b.get(root.index)
267
-
268
- if (batchRoot === null) continue // in the shared tree
269
- if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
270
- // TODO: type this error
271
- throw new Error('Batch is uncommitable due to a conflict at ' + root.index)
272
- }
273
- }
274
-
275
- const offset = this.core.tree.length - this._sessionLength
276
- for (let i = 0; i < offset; i++) this._byteLength -= this._appends[i].byteLength
277
-
278
- await this.core.insertValuesUnsafe(this._appends.slice(0, offset), this._sessionLength, this._sessionByteLength, b.nodes)
279
-
280
- this._sessionLength = this.session.length
281
- this._sessionByteLength = this.session.byteLength
282
- this._sessionBatch = this.session.createTreeBatch()
283
- this._appends = this._appends.slice(offset)
284
-
285
- return true
286
- }
287
-
288
254
  async _flush (length, keyPair, signature) { // TODO: make this safe to interact with a parallel truncate...
289
- if (this._appends.length === 0) return true
290
- if (!(await this._catchup())) return false
255
+ const flushingLength = Math.min(length - this._sessionLength, this._appends.length)
256
+ if (flushingLength <= 0) return true
291
257
 
292
- await this.core._mutex.lock()
258
+ const batch = this.createTreeBatch(this._sessionLength + flushingLength)
259
+ if (batch === null) return false
293
260
 
294
- try {
295
- const flushingLength = Math.min(length - this._sessionLength, this._appends.length)
296
- if (flushingLength <= 0) return true
261
+ const info = await this.core.insertBatch(batch, this._appends, { keyPair, signature })
262
+ if (info === null) return false
297
263
 
298
- const blocks = flushingLength < this._appends.length ? this._appends.slice(0, flushingLength) : this._appends
264
+ const delta = info.byteLength - this._sessionByteLength
299
265
 
300
- const info = await this.session.append(blocks, { keyPair, signature })
301
- const delta = info.byteLength - this._sessionByteLength
302
-
303
- this._sessionLength = info.length
304
- this._sessionByteLength = info.byteLength
305
- this._sessionBatch = this.session.createTreeBatch()
266
+ this._sessionLength = info.length
267
+ this._sessionByteLength = info.byteLength
268
+ this._sessionBatch = this.session.createTreeBatch()
306
269
 
307
- this._appends = this._appends.slice(flushingLength)
308
- this._byteLength -= delta
270
+ this._appends = this._appends.slice(flushingLength)
271
+ this._byteLength -= delta
309
272
 
310
- this.emit('flush')
311
- } finally {
312
- this.core._mutex.unlock()
313
- }
273
+ this.emit('flush')
314
274
 
315
275
  return true
316
276
  }
package/lib/core.js CHANGED
@@ -432,40 +432,71 @@ module.exports = class Core {
432
432
  })
433
433
  }
434
434
 
435
- // assumes mutex is already acquried
436
- async insertValuesUnsafe (values, offset, byteOffset, treeNodes) {
437
- if (!values.length) return
435
+ async insertBatch (batch, values, { signature, keyPair = this.header.keyPair } = {}) {
436
+ await this._mutex.lock()
438
437
 
439
- const bitfield = {
440
- drop: false,
441
- start: offset,
442
- end: offset + values.length
443
- }
438
+ try {
439
+ // upsert compat manifest
440
+ if (this.verifier === null && keyPair) this.setManifest(null, keyPair)
444
441
 
445
- await this.blocks.putBatch(offset, values, byteOffset)
446
- await this.oplog.append({
447
- userData: null,
448
- treeNodes,
449
- treeUpgrade: null,
450
- bitfield
451
- }, false)
442
+ if (this.tree.fork !== batch.fork) return null
452
443
 
453
- this.bitfield.setRange(bitfield.start, values.length, true)
454
- for (const node of treeNodes) this.tree.unflushed.set(node.index, node)
444
+ if (this.tree.length > batch.treeLength) {
445
+ if (this.tree.length > batch.length) return null // TODO: partial commit in the future if possible
455
446
 
456
- const status = updateContig(this.header, bitfield, this.bitfield)
457
- this.onupdate(status, bitfield, null, null)
447
+ for (const root of this.tree.roots) {
448
+ const batchRoot = await batch.get(root.index)
449
+ if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
450
+ return null
451
+ }
452
+ }
453
+ }
458
454
 
459
- if (this._shouldFlush()) await this._flushOplog()
460
- }
455
+ const offset = batch.treeLength
456
+ const adding = batch.length - batch.treeLength
461
457
 
462
- async append (values, { lock, signature, keyPair = this.header.keyPair, preappend } = {}) {
463
- if (lock) await this._mutex.lock()
458
+ batch.upgraded = batch.length > this.tree.length
459
+ batch.treeLength = this.tree.length
460
+ if (batch.upgraded) batch.signature = signature || this.verifier.sign(batch, keyPair)
464
461
 
465
- // upsert compat manifest
466
- if (this.verifier === null && keyPair) this.setManifest(null, keyPair)
462
+ let byteOffset = batch.byteLength
463
+ for (let i = 0; i < adding; i++) byteOffset -= values[i].byteLength
464
+
465
+ const entry = {
466
+ userData: null,
467
+ treeNodes: batch.nodes,
468
+ treeUpgrade: batch.upgraded ? batch : null,
469
+ bitfield: {
470
+ drop: false,
471
+ start: offset,
472
+ length: adding
473
+ }
474
+ }
475
+
476
+ await this.blocks.putBatch(offset, adding > values.length ? values.slice(0, adding) : values, byteOffset)
477
+ await this.oplog.append([entry], false)
478
+
479
+ this.bitfield.setRange(entry.bitfield.start, entry.bitfield.length, true)
480
+ batch.commit()
481
+
482
+ const status = (batch.upgraded ? 0b0001 : 0) | updateContig(this.header, entry.bitfield, this.bitfield)
483
+ this.onupdate(status, entry.bitfield, null, null)
484
+
485
+ if (this._shouldFlush()) await this._flushOplog()
486
+ } finally {
487
+ this._mutex.unlock()
488
+ }
489
+
490
+ return { length: batch.length, byteLength: batch.byteLength }
491
+ }
492
+
493
+ async append (values, { signature, keyPair = this.header.keyPair, preappend } = {}) {
494
+ await this._mutex.lock()
467
495
 
468
496
  try {
497
+ // upsert compat manifest
498
+ if (this.verifier === null && keyPair) this.setManifest(null, keyPair)
499
+
469
500
  if (preappend) await preappend(values)
470
501
 
471
502
  if (!values.length) {
@@ -506,7 +537,7 @@ module.exports = class Core {
506
537
 
507
538
  return { length: batch.length, byteLength }
508
539
  } finally {
509
- if (lock) this._mutex.unlock()
540
+ this._mutex.unlock()
510
541
  }
511
542
  }
512
543
 
@@ -1188,6 +1188,12 @@ async function generateProof (tree, block, hash, seek, upgrade) {
1188
1188
  const from = upgrade ? upgrade.start * 2 : 0
1189
1189
  const to = upgrade ? from + upgrade.length * 2 : head
1190
1190
  const node = normalizeIndexed(block, hash)
1191
+
1192
+ const result = { fork, block: null, hash: null, seek: null, upgrade: null, manifest: null }
1193
+
1194
+ // can't do anything as we have no data...
1195
+ if (head === 0) return result
1196
+
1191
1197
  if (from >= to || to > head) {
1192
1198
  throw INVALID_OPERATION('Invalid upgrade')
1193
1199
  }
@@ -1218,7 +1224,6 @@ async function generateProof (tree, block, hash, seek, upgrade) {
1218
1224
  }
1219
1225
 
1220
1226
  const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(p)
1221
- const result = { fork, block: null, hash: null, seek: null, upgrade: null, manifest: null }
1222
1227
 
1223
1228
  if (block) {
1224
1229
  result.block = {
package/lib/mutex.js CHANGED
@@ -10,7 +10,7 @@ module.exports = class Mutex {
10
10
  }
11
11
 
12
12
  lock () {
13
- if (this.destroyed) return Promise.reject(this._destroyError)
13
+ if (this.destroyed) return Promise.reject(this._destroyError || new Error('Mutex has been destroyed'))
14
14
  if (this.locked) return new Promise(this._enqueue)
15
15
  this.locked = true
16
16
  return Promise.resolve()
@@ -28,7 +28,7 @@ module.exports = class Mutex {
28
28
  if (!this._destroying) this._destroying = this.locked ? this.lock().catch(() => {}) : Promise.resolve()
29
29
 
30
30
  this.destroyed = true
31
- this._destroyError = err || new Error('Mutex has been destroyed')
31
+ if (err) this._destroyError = err
32
32
 
33
33
  if (err) {
34
34
  while (this._queue.length) this._queue.shift()[1](err)
package/lib/replicator.js CHANGED
@@ -384,7 +384,7 @@ class Peer {
384
384
  remoteLength: this.core.tree.fork === this.remoteFork ? this.remoteLength : 0,
385
385
  canUpgrade: this.canUpgrade,
386
386
  uploading: true,
387
- downloading: this.replicator.downloading,
387
+ downloading: this.replicator.isDownloading(),
388
388
  hasManifest: !!this.core.header.manifest && this.core.compat === false
389
389
  })
390
390
  }
@@ -582,9 +582,9 @@ class Peer {
582
582
  if (msg.fork === this.core.tree.fork) {
583
583
  try {
584
584
  proof = await this._getProof(msg)
585
- } catch (err) { // TODO: better error handling here, ie custom errors
585
+ } catch (err) {
586
586
  safetyCatch(err)
587
- if (this.replicator.core.closed) throw err // just an extra safety check...
587
+ if (isCriticalError(err)) throw err
588
588
  }
589
589
  }
590
590
 
@@ -619,7 +619,7 @@ class Peer {
619
619
  if (!exists) return
620
620
 
621
621
  this.inflight--
622
- this.replicator._inflight.remove(id)
622
+ this.replicator._removeInflight(id)
623
623
 
624
624
  this.wireCancel.send({ request: id })
625
625
  }
@@ -660,7 +660,7 @@ class Peer {
660
660
  if (req !== null) {
661
661
  if (req.peer !== this) return
662
662
  this.inflight--
663
- this.replicator._inflight.remove(req.id)
663
+ this.replicator._removeInflight(req.id)
664
664
  }
665
665
 
666
666
  if (reorg === true) return this.replicator._onreorgdata(this, req, data)
@@ -674,6 +674,8 @@ class Peer {
674
674
  }
675
675
  } catch (err) {
676
676
  safetyCatch(err)
677
+ if (this.core.closed && !isCriticalError(err)) return
678
+
677
679
  // might be a fork, verify
678
680
  this._checkIfConflict(err)
679
681
  this.replicator._onnodata(this, req)
@@ -695,7 +697,7 @@ class Peer {
695
697
  if (req === null || req.peer !== this) return
696
698
 
697
699
  this.inflight--
698
- this.replicator._inflight.remove(req.id)
700
+ this.replicator._removeInflight(req.id)
699
701
  this.replicator._onnodata(this, req)
700
702
  }
701
703
 
@@ -1072,10 +1074,15 @@ module.exports = class Replicator {
1072
1074
  }
1073
1075
  }
1074
1076
 
1077
+ isDownloading () {
1078
+ return this.downloading || !this._inflight.idle
1079
+ }
1080
+
1075
1081
  setDownloading (downloading) {
1076
1082
  if (this.downloading === downloading) return
1077
1083
  this.downloading = downloading
1078
- for (const peer of this.peers) peer.sendSync()
1084
+ if (!downloading && this.isDownloading()) return
1085
+ for (const peer of this.peers) peer.signalUpgrade()
1079
1086
  }
1080
1087
 
1081
1088
  cork () {
@@ -1319,6 +1326,13 @@ module.exports = class Replicator {
1319
1326
  this.onpeerupdate(true, peer)
1320
1327
  }
1321
1328
 
1329
+ _removeInflight (id) {
1330
+ this._inflight.remove(id)
1331
+ if (this.isDownloading() === false) {
1332
+ for (const peer of this.peers) peer.signalUpgrade()
1333
+ }
1334
+ }
1335
+
1322
1336
  _removePeer (peer) {
1323
1337
  this.peers.splice(this.peers.indexOf(peer), 1)
1324
1338
 
@@ -1904,6 +1918,11 @@ function destroyRequestTimeout (req) {
1904
1918
  }
1905
1919
  }
1906
1920
 
1921
+ function isCriticalError (err) {
1922
+ // TODO: expose .critical or similar on the hypercore errors that are critical (if all not are)
1923
+ return err.name === 'HypercoreError'
1924
+ }
1925
+
1907
1926
  function onwireopen (m, c) {
1908
1927
  return c.userData.onopen(m)
1909
1928
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.24.4",
3
+ "version": "10.24.6",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {