hypercore 10.30.4 → 10.31.0

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
@@ -391,7 +391,8 @@ module.exports = class Hypercore extends EventEmitter {
391
391
  eagerUpgrade: true,
392
392
  allowFork: opts.allowFork !== false,
393
393
  onpeerupdate: this._onpeerupdate.bind(this),
394
- onupload: this._onupload.bind(this)
394
+ onupload: this._onupload.bind(this),
395
+ oninvalid: this._oninvalid.bind(this)
395
396
  })
396
397
 
397
398
  this.replicator.findingPeers += this._findingPeers
@@ -589,7 +590,7 @@ module.exports = class Hypercore extends EventEmitter {
589
590
  }
590
591
 
591
592
  get contiguousLength () {
592
- return this.core === null ? 0 : this.core.header.hints.contiguousLength
593
+ return this.core === null ? 0 : Math.min(this.core.tree.length, this.core.header.hints.contiguousLength)
593
594
  }
594
595
 
595
596
  get contiguousByteLength () {
@@ -624,6 +625,12 @@ module.exports = class Hypercore extends EventEmitter {
624
625
  }
625
626
  }
626
627
 
628
+ _oninvalid (err, req, res, from) {
629
+ for (let i = 0; i < this.sessions.length; i++) {
630
+ this.sessions[i].emit('verification-error', err, req, res, from)
631
+ }
632
+ }
633
+
627
634
  async _oncoreconflict (proof, from) {
628
635
  await this.replicator.onconflict(from)
629
636
 
@@ -800,14 +807,15 @@ module.exports = class Hypercore extends EventEmitter {
800
807
  return true
801
808
  }
802
809
 
803
- batch ({ checkout = -1, autoClose = true, session = true } = {}) {
804
- return new Batch(session ? this.session() : this, checkout, autoClose)
810
+ batch ({ checkout = -1, autoClose = true, session = true, restore = false } = {}) {
811
+ return new Batch(session ? this.session() : this, checkout, autoClose, restore)
805
812
  }
806
813
 
807
814
  async seek (bytes, opts) {
808
815
  if (this.opened === false) await this.opening
809
816
 
810
- const s = this.core.tree.seek(bytes, this.padding)
817
+ const tree = (opts && opts.tree) || this.core.tree
818
+ const s = tree.seek(bytes, this.padding)
811
819
 
812
820
  const offset = await s.update()
813
821
  if (offset) return offset
@@ -887,7 +895,8 @@ module.exports = class Hypercore extends EventEmitter {
887
895
  let block
888
896
 
889
897
  if (this.core.bitfield.get(index)) {
890
- block = this.core.blocks.get(index)
898
+ const tree = (opts && opts.tree) || this.core.tree
899
+ block = this.core.blocks.get(index, tree)
891
900
 
892
901
  if (this.cache) this.cache.set(index, block)
893
902
  } else {
package/lib/batch.js CHANGED
@@ -4,7 +4,7 @@ const c = require('compact-encoding')
4
4
  const b4a = require('b4a')
5
5
 
6
6
  module.exports = class HypercoreBatch extends EventEmitter {
7
- constructor (session, checkoutLength, autoClose) {
7
+ constructor (session, checkoutLength, autoClose, restore) {
8
8
  super()
9
9
 
10
10
  this.session = session
@@ -14,6 +14,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
14
14
  this.closing = null
15
15
  this.writable = true // always writable...
16
16
  this.autoClose = autoClose
17
+ this.restore = restore
17
18
  this.fork = 0
18
19
 
19
20
  this._appends = []
@@ -41,7 +42,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
41
42
  }
42
43
 
43
44
  get indexedLength () {
44
- return this._sessionLength
45
+ return Math.min(this._sessionLength, this.session.core === null ? 0 : this.session.core.tree.length)
45
46
  }
46
47
 
47
48
  get indexedByteLength () {
@@ -77,9 +78,18 @@ module.exports = class HypercoreBatch extends EventEmitter {
77
78
  this._sessionByteLength = batch.byteLength
78
79
  this._sessionBatch = batch
79
80
  } else {
80
- this._sessionLength = this.session.length
81
- this._sessionByteLength = this.session.byteLength
82
- this._sessionBatch = this.session.createTreeBatch()
81
+ const last = this.restore ? this.session.core.bitfield.findFirst(false, this.session.length) : 0
82
+
83
+ if (last > this.session.length) {
84
+ const batch = await this.session.core.tree.restoreBatch(last)
85
+ this._sessionLength = batch.length
86
+ this._sessionByteLength = batch.byteLength - this.session.padding * batch.length
87
+ this._sessionBatch = batch
88
+ } else {
89
+ this._sessionLength = this.session.length
90
+ this._sessionByteLength = this.session.byteLength
91
+ this._sessionBatch = this.session.createTreeBatch()
92
+ }
83
93
  }
84
94
 
85
95
  this._appendsActual = this.session.encryption ? [] : this._appends
@@ -128,11 +138,11 @@ module.exports = class HypercoreBatch extends EventEmitter {
128
138
  return info
129
139
  }
130
140
 
131
- async seek (bytes, opts) {
141
+ async seek (bytes, opts = {}) {
132
142
  if (this.opened === false) await this.opening
133
143
  if (this.closing) throw SESSION_CLOSED()
134
144
 
135
- if (bytes < this._sessionByteLength) return await this.session.seek(bytes, opts)
145
+ if (bytes < this._sessionByteLength) return await this.session.seek(bytes, { ...opts, tree: this._sessionBatch })
136
146
 
137
147
  bytes -= this._sessionByteLength
138
148
 
@@ -154,7 +164,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
154
164
  if (this.closing) throw SESSION_CLOSED()
155
165
 
156
166
  const length = this._sessionLength
157
- if (index < length) return this.session.get(index, opts)
167
+ if (index < length) return this.session.get(index, { ...opts, tree: this._sessionBatch })
158
168
 
159
169
  const buffer = this._appends[index - length] || null
160
170
  if (!buffer) throw BLOCK_NOT_AVAILABLE()
@@ -173,6 +183,11 @@ module.exports = class HypercoreBatch extends EventEmitter {
173
183
  }
174
184
  }
175
185
 
186
+ async restoreBatch (length) {
187
+ if (this.opened === false) await this.opening
188
+ return this.session.core.tree.restoreBatch(length)
189
+ }
190
+
176
191
  createTreeBatch (length, blocks = []) {
177
192
  if (!length && length !== 0) length = this.length + blocks.length
178
193
 
@@ -293,10 +308,10 @@ module.exports = class HypercoreBatch extends EventEmitter {
293
308
  if (this.opened === false) await this.opening
294
309
  if (this.closing) throw SESSION_CLOSED()
295
310
 
296
- const { length = this.length, keyPair = this.session.keyPair, signature = null } = opts
311
+ const { length = this.length, keyPair = this.session.keyPair, signature = null, pending = !signature && !keyPair } = opts
297
312
 
298
313
  while (this._flushing) await this._flushing
299
- this._flushing = this._flush(length, keyPair, signature)
314
+ this._flushing = this._flush(length, keyPair, signature, pending)
300
315
 
301
316
  let flushed = false
302
317
 
@@ -311,12 +326,9 @@ module.exports = class HypercoreBatch extends EventEmitter {
311
326
  return flushed
312
327
  }
313
328
 
314
- async _flush (length, keyPair, signature) { // TODO: make this safe to interact with a parallel truncate...
329
+ async _flush (length, keyPair, signature, pending) { // TODO: make this safe to interact with a parallel truncate...
315
330
  if (this._sessionBatch.fork !== this.session.fork) return false // no truncs supported atm
316
331
 
317
- const flushingLength = Math.min(length - this._sessionLength, this._appends.length)
318
- if (flushingLength <= 0) return true
319
-
320
332
  if (this.session.replicator._upgrade) {
321
333
  for (const req of this.session.replicator._upgrade.inflight) {
322
334
  // yield to the remote inflight upgrade, TODO: if the remote upgrade fails, retry flushing...
@@ -326,17 +338,28 @@ module.exports = class HypercoreBatch extends EventEmitter {
326
338
  }
327
339
  }
328
340
 
341
+ const flushingLength = Math.min(length - this._sessionLength, this._appends.length)
342
+ if (flushingLength <= 0) {
343
+ if (this._sessionLength > this.core.tree.length && length > this.core.tree.length && !pending) {
344
+ const batch = await this.restoreBatch(length)
345
+ const info = await this.core.insertBatch(batch, [], { keyPair, signature, pending, treeLength: length })
346
+ return info !== null
347
+ }
348
+ return true
349
+ }
350
+
329
351
  const batch = this.createTreeBatch(this._sessionLength + flushingLength)
330
352
  if (batch === null) return false
331
353
 
332
- const info = await this.core.insertBatch(batch, this._appendsActual, { keyPair, signature })
354
+ const info = await this.core.insertBatch(batch, this._appendsActual, { keyPair, signature, pending, treeLength: this._sessionLength })
333
355
  if (info === null) return false
334
356
 
335
357
  const delta = info.byteLength - this._sessionByteLength
358
+ const newBatch = info.length !== this.session.length ? await this.restoreBatch(info.length) : this.session.createTreeBatch()
336
359
 
337
360
  this._sessionLength = info.length
338
361
  this._sessionByteLength = info.byteLength
339
- this._sessionBatch = this.session.createTreeBatch()
362
+ this._sessionBatch = newBatch
340
363
 
341
364
  const same = this._appends === this._appendsActual
342
365
 
@@ -6,8 +6,9 @@ module.exports = class BlockStore {
6
6
  this.tree = tree
7
7
  }
8
8
 
9
- async get (i) {
10
- const [offset, size] = await this.tree.byteRange(2 * i)
9
+ async get (i, tree) {
10
+ if (!tree) tree = this.tree
11
+ const [offset, size] = await tree.byteRange(2 * i)
11
12
  return this._read(offset, size)
12
13
  }
13
14
 
package/lib/core.js CHANGED
@@ -451,7 +451,7 @@ module.exports = class Core {
451
451
  })
452
452
  }
453
453
 
454
- async insertBatch (batch, values, { signature, keyPair = this.header.keyPair } = {}) {
454
+ async insertBatch (batch, values, { signature, keyPair = this.header.keyPair, pending = false, treeLength = batch.treeLength } = {}) {
455
455
  await this._mutex.lock()
456
456
 
457
457
  try {
@@ -471,29 +471,30 @@ module.exports = class Core {
471
471
  }
472
472
  }
473
473
 
474
- const offset = batch.treeLength
475
- const adding = batch.length - batch.treeLength
474
+ const adding = batch.length - treeLength
476
475
 
477
476
  batch.upgraded = batch.length > this.tree.length
478
477
  batch.treeLength = this.tree.length
479
478
  batch.ancestors = this.tree.length
480
- if (batch.upgraded) batch.signature = signature || this.verifier.sign(batch, keyPair)
479
+ if (batch.upgraded && !pending) batch.signature = signature || this.verifier.sign(batch, keyPair)
481
480
 
482
481
  let byteOffset = batch.byteLength
483
482
  for (let i = 0; i < adding; i++) byteOffset -= values[i].byteLength
484
483
 
484
+ if (pending === true) batch.upgraded = false
485
+
485
486
  const entry = {
486
487
  userData: null,
487
488
  treeNodes: batch.nodes,
488
489
  treeUpgrade: batch.upgraded ? batch : null,
489
490
  bitfield: {
490
491
  drop: false,
491
- start: offset,
492
+ start: treeLength,
492
493
  length: adding
493
494
  }
494
495
  }
495
496
 
496
- await this.blocks.putBatch(offset, adding > values.length ? values.slice(0, adding) : values, byteOffset)
497
+ await this.blocks.putBatch(treeLength, adding > values.length ? values.slice(0, adding) : values, byteOffset)
497
498
  await this.oplog.append([entry], false)
498
499
 
499
500
  this.bitfield.setRange(entry.bitfield.start, entry.bitfield.length, true)
@@ -506,7 +507,7 @@ module.exports = class Core {
506
507
  }
507
508
 
508
509
  const status = (batch.upgraded ? 0b0001 : 0) | updateContig(this.header, entry.bitfield, this.bitfield)
509
- this.onupdate(status, entry.bitfield, null, null)
510
+ if (!pending) this.onupdate(status, entry.bitfield, null, null)
510
511
 
511
512
  if (this._shouldFlush()) await this._flushOplog()
512
513
  } finally {
@@ -87,7 +87,7 @@ class MerkleTreeBatch {
87
87
  return caps.treeSignableCompat(this.hash(), this.length, this.fork, noHeader)
88
88
  }
89
89
 
90
- get (index) {
90
+ get (index, error) {
91
91
  if (index >= this.length * 2) {
92
92
  return null
93
93
  }
@@ -96,11 +96,7 @@ class MerkleTreeBatch {
96
96
  if (n.index === index) return n
97
97
  }
98
98
 
99
- if (index < this.treeLength * 2) {
100
- return this.tree.get(index)
101
- }
102
-
103
- return null
99
+ return this.tree.get(index, error)
104
100
  }
105
101
 
106
102
  proof ({ block, hash, seek, upgrade }) {
@@ -203,40 +199,17 @@ class MerkleTreeBatch {
203
199
  this.tree.signature = this.signature
204
200
  }
205
201
 
206
- // TODO: this is the only async method on the batch, so unsure if it should go here
207
- // this is important so you know where to right data without committing the batch
208
- // so we'll keep it here for now.
209
-
210
- async byteOffset (index) {
211
- if (2 * this.tree.length === index) return this.tree.byteLength
212
-
213
- const ite = flat.iterator(index)
214
-
215
- let treeOffset = 0
216
- let isRight = false
217
- let parent = null
218
-
219
- for (const node of this.nodes) {
220
- if (node.index === ite.index) {
221
- if (isRight && parent) treeOffset += node.size - parent.size
222
- parent = node
223
- isRight = ite.isRight()
224
- ite.parent()
225
- }
226
- }
227
-
228
- const r = this.roots.indexOf(parent)
229
- if (r > -1) {
230
- for (let i = 0; i < r; i++) {
231
- treeOffset += this.roots[i].size
232
- }
233
-
234
- return treeOffset
235
- }
202
+ seek (bytes, padding) {
203
+ return new ByteSeeker(this, bytes, padding)
204
+ }
236
205
 
237
- const byteOffset = await this.tree.byteOffset(parent ? parent.index : index)
206
+ byteRange (index) {
207
+ return getByteRange(this, index)
208
+ }
238
209
 
239
- return byteOffset + treeOffset
210
+ byteOffset (index) {
211
+ if (index === 2 * this.tree.length) return this.tree.byteLength
212
+ return getByteOffset(this, index)
240
213
  }
241
214
  }
242
215
 
@@ -337,10 +310,6 @@ class ByteSeeker {
337
310
  this.end = bytes < size ? tree.length : 0
338
311
  }
339
312
 
340
- nodes () {
341
- return this.tree.nodes(this.start * 2)
342
- }
343
-
344
313
  async _seek (bytes) {
345
314
  if (!bytes) return [0, 0]
346
315
 
@@ -357,6 +326,7 @@ class ByteSeeker {
357
326
 
358
327
  while ((ite.index & 1) !== 0) {
359
328
  const l = await this.tree.get(ite.leftChild(), false)
329
+
360
330
  if (l) {
361
331
  const size = getUnpaddedSize(l, this.padding, ite)
362
332
 
@@ -415,6 +385,22 @@ module.exports = class MerkleTree {
415
385
  return new MerkleTreeBatch(this)
416
386
  }
417
387
 
388
+ async restoreBatch (length) {
389
+ const batch = new MerkleTreeBatch(this)
390
+ if (length === this.length) return batch
391
+
392
+ const roots = await this.getRoots(length)
393
+
394
+ batch.roots = roots
395
+ batch.length = length
396
+ batch.byteLength = 0
397
+ batch.ancestors = length
398
+
399
+ for (const node of roots) batch.byteLength += node.size
400
+
401
+ return batch
402
+ }
403
+
418
404
  seek (bytes, padding) {
419
405
  return new ByteSeeker(this, bytes, padding)
420
406
  }
@@ -706,42 +692,12 @@ module.exports = class MerkleTree {
706
692
  return cnt
707
693
  }
708
694
 
709
- async byteRange (index) {
710
- const head = 2 * this.length
711
- if (((index & 1) === 0 ? index : flat.rightSpan(index)) >= head) {
712
- throw BAD_ARGUMENT('Index is out of bounds')
713
- }
714
- return [await this.byteOffset(index), (await this.get(index)).size]
695
+ byteRange (index) {
696
+ return getByteRange(this, index)
715
697
  }
716
698
 
717
- async byteOffset (index) {
718
- if (index === 2 * this.length) return this.byteLength
719
- if ((index & 1) === 1) index = flat.leftSpan(index)
720
-
721
- let head = 0
722
- let offset = 0
723
-
724
- for (const node of this.roots) { // all async ticks happen once we find the root so safe
725
- head += 2 * ((node.index - head) + 1)
726
-
727
- if (index >= head) {
728
- offset += node.size
729
- continue
730
- }
731
-
732
- const ite = flat.iterator(node.index)
733
-
734
- while (ite.index !== index) {
735
- if (index < ite.index) {
736
- ite.leftChild()
737
- } else {
738
- offset += (await this.get(ite.leftChild())).size
739
- ite.sibling()
740
- }
741
- }
742
-
743
- return offset
744
- }
699
+ byteOffset (index) {
700
+ return getByteOffset(this, index)
745
701
  }
746
702
 
747
703
  static async open (storage, opts = {}) {
@@ -766,6 +722,44 @@ module.exports = class MerkleTree {
766
722
  }
767
723
  }
768
724
 
725
+ async function getByteRange (tree, index) {
726
+ const head = 2 * tree.length
727
+ if (((index & 1) === 0 ? index : flat.rightSpan(index)) >= head) {
728
+ throw BAD_ARGUMENT('Index is out of bounds')
729
+ }
730
+ return [await tree.byteOffset(index), (await tree.get(index)).size]
731
+ }
732
+
733
+ async function getByteOffset (tree, index) {
734
+ if (index === 2 * tree.length) return tree.byteLength
735
+ if ((index & 1) === 1) index = flat.leftSpan(index)
736
+
737
+ let head = 0
738
+ let offset = 0
739
+
740
+ for (const node of tree.roots) { // all async ticks happen once we find the root so safe
741
+ head += 2 * ((node.index - head) + 1)
742
+
743
+ if (index >= head) {
744
+ offset += node.size
745
+ continue
746
+ }
747
+
748
+ const ite = flat.iterator(node.index)
749
+
750
+ while (ite.index !== index) {
751
+ if (index < ite.index) {
752
+ ite.leftChild()
753
+ } else {
754
+ offset += (await tree.get(ite.leftChild())).size
755
+ ite.sibling()
756
+ }
757
+ }
758
+
759
+ return offset
760
+ }
761
+ }
762
+
769
763
  // All the methods needed for proof verification
770
764
 
771
765
  function verifyTree ({ block, hash, seek }, crypto, nodes) {
package/lib/replicator.js CHANGED
@@ -408,7 +408,7 @@ class Peer {
408
408
 
409
409
  this.sendSync()
410
410
 
411
- const contig = this.core.header.hints.contiguousLength
411
+ const contig = Math.min(this.core.tree.length, this.core.header.hints.contiguousLength)
412
412
  if (contig > 0) {
413
413
  this.broadcastRange(0, contig, false)
414
414
 
@@ -636,11 +636,11 @@ class Peer {
636
636
  this.wireCancel.send({ request: id })
637
637
  }
638
638
 
639
- _checkIfConflict (err) {
639
+ _checkIfConflict () {
640
640
  this.paused = true
641
641
 
642
642
  const length = Math.min(this.core.tree.length, this.remoteLength)
643
- if (length === 0) throw err
643
+ if (length === 0) return // pause and ignore
644
644
 
645
645
  this.wireRequest.send({
646
646
  id: 0, // TODO: use an more explicit id for this eventually...
@@ -675,7 +675,14 @@ class Peer {
675
675
  this.replicator._removeInflight(req.id)
676
676
  }
677
677
 
678
- if (reorg === true) return this.replicator._onreorgdata(this, req, data)
678
+ try {
679
+ if (reorg === true) return await this.replicator._onreorgdata(this, req, data)
680
+ } catch (err) {
681
+ safetyCatch(err)
682
+ this.paused = true
683
+ this.replicator.oninvalid(err, req, data, this)
684
+ return
685
+ }
679
686
 
680
687
  this.dataProcessing++
681
688
 
@@ -690,9 +697,10 @@ class Peer {
690
697
 
691
698
  if (err.code !== 'INVALID_OPERATION') {
692
699
  // might be a fork, verify
693
- this._checkIfConflict(err)
700
+ this._checkIfConflict()
694
701
  }
695
702
  this.replicator._onnodata(this, req)
703
+ this.replicator.oninvalid(err, req, data, this)
696
704
  return
697
705
  } finally {
698
706
  this.dataProcessing--
@@ -1104,7 +1112,7 @@ class Peer {
1104
1112
  }
1105
1113
 
1106
1114
  module.exports = class Replicator {
1107
- constructor (core, key, { eagerUpgrade = true, allowFork = true, onpeerupdate = noop, onupload = noop } = {}) {
1115
+ constructor (core, key, { eagerUpgrade = true, allowFork = true, onpeerupdate = noop, onupload = noop, oninvalid = noop } = {}) {
1108
1116
  this.key = key
1109
1117
  this.discoveryKey = core.crypto.discoveryKey(key)
1110
1118
  this.core = core
@@ -1112,6 +1120,7 @@ module.exports = class Replicator {
1112
1120
  this.allowFork = allowFork
1113
1121
  this.onpeerupdate = onpeerupdate
1114
1122
  this.onupload = onupload
1123
+ this.oninvalid = oninvalid
1115
1124
  this.ondownloading = null // optional external hook for monitoring downloading status
1116
1125
  this.peers = []
1117
1126
  this.findingPeers = 0 // updateable from the outside
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.30.4",
3
+ "version": "10.31.0",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {