hypercore 10.31.12 → 10.32.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
@@ -9,6 +9,7 @@ const NoiseSecretStream = require('@hyperswarm/secret-stream')
9
9
  const Protomux = require('protomux')
10
10
  const z32 = require('z32')
11
11
  const id = require('hypercore-id-encoding')
12
+ const { createTracer } = require('hypertrace')
12
13
 
13
14
  const Replicator = require('./lib/replicator')
14
15
  const Core = require('./lib/core')
@@ -16,7 +17,7 @@ const BlockEncryption = require('./lib/block-encryption')
16
17
  const Info = require('./lib/info')
17
18
  const Download = require('./lib/download')
18
19
  const Batch = require('./lib/batch')
19
- const { manifestHash, defaultSignerManifest, createVerifier, createManifest, isCompat } = require('./lib/manifest')
20
+ const { manifestHash, defaultSignerManifest, createManifest, isCompat, sign } = require('./lib/verifier')
20
21
  const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
21
22
  const {
22
23
  BAD_ARGUMENT,
@@ -48,6 +49,7 @@ module.exports = class Hypercore extends EventEmitter {
48
49
 
49
50
  this[promises] = true
50
51
 
52
+ this.tracer = createTracer(this)
51
53
  this.storage = null
52
54
  this.crypto = opts.crypto || hypercoreCrypto
53
55
  this.core = null
@@ -133,7 +135,7 @@ module.exports = class Hypercore extends EventEmitter {
133
135
  }
134
136
 
135
137
  static key (manifest, { compat } = {}) {
136
- return compat ? manifest.signer.publicKey : manifestHash(createManifest(manifest))
138
+ return compat ? manifest.signers[0].publicKey : manifestHash(createManifest(manifest))
137
139
  }
138
140
 
139
141
  static discoveryKey (key) {
@@ -266,6 +268,8 @@ module.exports = class Hypercore extends EventEmitter {
266
268
  this.writable = this._isWritable()
267
269
  this.autoClose = o.autoClose
268
270
 
271
+ if (o.core) this.tracer.setParent(o.core.tracer)
272
+
269
273
  if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
270
274
  }
271
275
 
@@ -376,6 +380,7 @@ module.exports = class Hypercore extends EventEmitter {
376
380
  onupdate: this._oncoreupdate.bind(this),
377
381
  onconflict: this._oncoreconflict.bind(this)
378
382
  })
383
+ this.tracer.setParent(this.core.tracer)
379
384
 
380
385
  if (opts.userData) {
381
386
  for (const [key, value] of Object.entries(opts.userData)) {
@@ -489,14 +494,14 @@ module.exports = class Hypercore extends EventEmitter {
489
494
  }
490
495
 
491
496
  const manifest = opts.manifest || defaultSignerManifest(keyPair.publicKey)
492
- const key = opts.key || (opts.compat !== false ? manifest.signer.publicKey : manifestHash(manifest))
497
+ const key = opts.key || (opts.compat !== false ? manifest.signers[0].publicKey : manifestHash(manifest))
493
498
 
494
499
  if (b4a.equals(key, this.key)) {
495
500
  throw BAD_ARGUMENT('Clone cannot share verification information')
496
501
  }
497
502
 
498
503
  const signature = opts.signature === undefined
499
- ? createVerifier(createManifest(manifest), { compat: isCompat(key, manifest) }).sign(this.core.tree.batch(), keyPair)
504
+ ? sign(createManifest(manifest), this.core.tree.batch(), keyPair, { compat: isCompat(key, manifest) })
500
505
  : opts.signature
501
506
 
502
507
  const sparse = opts.sparse === false ? false : this.sparse
@@ -847,6 +852,9 @@ module.exports = class Hypercore extends EventEmitter {
847
852
 
848
853
  async get (index, opts) {
849
854
  if (this.opened === false) await this.opening
855
+
856
+ this.tracer.trace('get', { index })
857
+
850
858
  if (this.closing !== null) throw SESSION_CLOSED()
851
859
  if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
852
860
 
@@ -961,6 +969,8 @@ module.exports = class Hypercore extends EventEmitter {
961
969
  async _download (range) {
962
970
  if (this.opened === false) await this.opening
963
971
 
972
+ this.tracer.trace('download', { range })
973
+
964
974
  const activeRequests = (range && range.activeRequests) || this.activeRequests
965
975
 
966
976
  return this.replicator.addRange(activeRequests, range)
@@ -1003,6 +1013,7 @@ module.exports = class Hypercore extends EventEmitter {
1003
1013
  if (writable === false) throw SESSION_NOT_WRITABLE()
1004
1014
 
1005
1015
  blocks = Array.isArray(blocks) ? blocks : [blocks]
1016
+ this.tracer.trace('append', { blocks })
1006
1017
 
1007
1018
  const preappend = this.encryption && this._preappend
1008
1019
 
package/lib/batch.js CHANGED
@@ -187,9 +187,9 @@ module.exports = class HypercoreBatch extends EventEmitter {
187
187
  }
188
188
  }
189
189
 
190
- async restoreBatch (length) {
190
+ async restoreBatch (length, blocks) {
191
191
  if (this.opened === false) await this.opening
192
- if (length >= this._sessionLength) return this.createTreeBatch(length)
192
+ if (length >= this._sessionLength) return this.createTreeBatch(length, blocks)
193
193
  return this.session.core.tree.restoreBatch(length)
194
194
  }
195
195
 
package/lib/core.js CHANGED
@@ -7,12 +7,14 @@ const MerkleTree = require('./merkle-tree')
7
7
  const BlockStore = require('./block-store')
8
8
  const Bitfield = require('./bitfield')
9
9
  const Info = require('./info')
10
- const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE, INVALID_CHECKSUM } = require('hypercore-errors')
10
+ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_OPERATION, INVALID_SIGNATURE, INVALID_CHECKSUM } = require('hypercore-errors')
11
11
  const m = require('./messages')
12
- const { manifestHash, createVerifier, createManifest, defaultSignerManifest, isCompat } = require('./manifest')
12
+ const Verifier = require('./verifier')
13
+ const { createTracer } = require('hypertrace')
13
14
 
14
15
  module.exports = class Core {
15
16
  constructor (header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, legacy, onupdate, onconflict) {
17
+ this.tracer = createTracer(this)
16
18
  this.onupdate = onupdate
17
19
  this.onconflict = onconflict
18
20
  this.preupdate = null
@@ -92,11 +94,11 @@ module.exports = class Core {
92
94
 
93
95
  const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())
94
96
  const defaultManifest = !opts.manifest && (!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
95
- const manifest = defaultManifest ? defaultSignerManifest(opts.key || keyPair.publicKey) : createManifest(opts.manifest)
97
+ const manifest = defaultManifest ? Verifier.defaultSignerManifest(opts.key || keyPair.publicKey) : Verifier.createManifest(opts.manifest)
96
98
 
97
99
  header = {
98
100
  external: null,
99
- key: opts.key || (compat ? manifest.signer.publicKey : manifestHash(manifest)),
101
+ key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
100
102
  manifest,
101
103
  keyPair,
102
104
  userData: [],
@@ -119,7 +121,7 @@ module.exports = class Core {
119
121
 
120
122
  if (opts.manifest) {
121
123
  // if we provide a manifest and no key, verify that the stored key is the same
122
- if (!opts.key && !isValidManifest(header.key, createManifest(opts.manifest))) {
124
+ if (!opts.key && !Verifier.isValidManifest(header.key, Verifier.createManifest(opts.manifest))) {
123
125
  throw STORAGE_CONFLICT('Manifest does not hash to provided key')
124
126
  }
125
127
  }
@@ -129,13 +131,15 @@ module.exports = class Core {
129
131
  }
130
132
 
131
133
  // if we signalled compat, but already now this core isn't disable it
132
- if (compat && header.manifest && !isCompat(header.key, header.manifest)) {
134
+ if (compat && header.manifest && !Verifier.isCompat(header.key, header.manifest)) {
133
135
  compat = false
134
- } else if (!compat && header.manifest && isCompat(header.key, header.manifest)) {
136
+ } else if (!compat && header.manifest && Verifier.isCompat(header.key, header.manifest)) {
135
137
  compat = true
136
138
  }
137
139
 
138
- const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
140
+ const prologue = header.manifest ? header.manifest.prologue : null
141
+
142
+ const tree = await MerkleTree.open(treeFile, { crypto, prologue, ...header.tree })
139
143
  const bitfield = await Bitfield.open(bitfieldFile, tree)
140
144
  const blocks = new BlockStore(dataFile, tree)
141
145
 
@@ -154,7 +158,7 @@ module.exports = class Core {
154
158
  while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
155
159
  }
156
160
 
157
- const verifier = header.manifest ? createVerifier(header.manifest, { compat: isCompat(header.key, header.manifest), crypto, legacy }) : null
161
+ const verifier = header.manifest ? new Verifier(header.manifest, { compat: Verifier.isCompat(header.key, header.manifest), crypto, legacy }) : null
158
162
 
159
163
  for (const e of entries) {
160
164
  if (e.userData) {
@@ -190,11 +194,13 @@ module.exports = class Core {
190
194
  }
191
195
 
192
196
  setManifest (manifest, keyPair) {
193
- if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) manifest = defaultSignerManifest(this.header.key)
197
+ if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) manifest = Verifier.defaultSignerManifest(this.header.key)
194
198
  if (!manifest) return
195
199
 
196
- const compat = isCompat(this.header.key, manifest)
197
- const verifier = createVerifier(manifest, { compat, crypto: this.crypto, legacy: this._legacy })
200
+ const compat = Verifier.isCompat(this.header.key, manifest)
201
+ const verifier = new Verifier(manifest, { compat, crypto: this.crypto, legacy: this._legacy })
202
+
203
+ if (verifier.prologue) this.tree.setPrologue(verifier.prologue)
198
204
 
199
205
  this.compat = compat
200
206
  this.header.manifest = manifest
@@ -219,7 +225,7 @@ module.exports = class Core {
219
225
  return false
220
226
  }
221
227
 
222
- async copyFrom (src, signature, { length = src.tree.length } = {}) {
228
+ async copyFrom (src, signature, { length = src.tree.length, additional = [] } = {}) {
223
229
  await this._mutex.lock()
224
230
 
225
231
  try {
@@ -229,16 +235,19 @@ module.exports = class Core {
229
235
  throw err
230
236
  }
231
237
 
238
+ const initialLength = this.tree.length
239
+
232
240
  try {
233
241
  const updates = []
234
242
 
235
243
  let pos = 0
244
+ const copyLength = Math.min(src.tree.length, length)
236
245
 
237
- while (pos < length) {
246
+ while (pos < copyLength) {
238
247
  const segmentStart = maximumSegmentStart(pos, src.bitfield, this.bitfield)
239
248
  if (segmentStart >= length || segmentStart < 0) break
240
249
 
241
- const segmentEnd = Math.min(length, minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield))
250
+ const segmentEnd = Math.min(src.tree.length, minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield))
242
251
 
243
252
  const segment = []
244
253
 
@@ -262,7 +271,7 @@ module.exports = class Core {
262
271
  })
263
272
  }
264
273
 
265
- for (let i = 0; i < length * 2; i++) {
274
+ for (let i = 0; i < copyLength * 2; i++) {
266
275
  const node = await src.tree.get(i, false)
267
276
  if (node === null) continue
268
277
 
@@ -271,22 +280,50 @@ module.exports = class Core {
271
280
 
272
281
  await this.tree.flush()
273
282
 
274
- if (length > this.tree.length) {
275
- this.tree.fork = src.tree.fork
276
- this.tree.roots = [...src.tree.roots]
277
- this.tree.length = src.tree.length
278
- this.tree.byteLength = src.tree.byteLength
279
-
280
- if (length < this.tree.length) {
281
- const batch = await src.tree.truncate(length)
282
- this.tree.roots = [...batch.roots]
283
- this.tree.length = batch.length
284
- this.tree.byteLength = batch.byteLength
283
+ let batch = this.tree.batch()
284
+
285
+ // add additional blocks
286
+ if (length > src.tree.length) {
287
+ const missing = length - src.tree.length
288
+
289
+ if (additional.length < missing) {
290
+ throw INVALID_OPERATION('Insufficient additional nodes were passed')
285
291
  }
286
292
 
293
+ const source = src.tree.batch()
294
+
295
+ batch.roots = [...source.roots]
296
+ batch.length = source.length
297
+ batch.byteLength = source.byteLength
298
+
299
+ const blocks = additional.length === missing ? additional : additional.slice(0, missing)
300
+
301
+ await this.blocks.putBatch(source.length, blocks, source.byteLength)
302
+ this.bitfield.setRange(source.length, missing, true)
303
+
304
+ updates.push({
305
+ drop: false,
306
+ start: source.length,
307
+ length: missing
308
+ })
309
+
310
+ for (const block of blocks) await batch.append(block)
311
+ } else {
312
+ const source = length < src.tree.length ? await src.tree.truncate(length) : src.tree.batch()
313
+
314
+ this.tree.roots = [...source.roots]
315
+ this.tree.length = source.length
316
+ this.tree.byteLength = source.byteLength
317
+
318
+ // update batch
319
+ batch = this.tree.batch()
320
+ }
321
+
322
+ // verify if upgraded
323
+ if (batch.length > initialLength) {
287
324
  try {
288
- const batch = this.tree.batch()
289
325
  batch.signature = signature
326
+
290
327
  this._verifyBatchUpgrade(batch, this.header.manifest)
291
328
  this.tree.signature = signature
292
329
  } catch (err) {
@@ -294,12 +331,16 @@ module.exports = class Core {
294
331
  // TODO: how to handle signature failure?
295
332
  throw err
296
333
  }
297
-
298
- this.header.tree.length = this.tree.length
299
- this.header.tree.rootHash = this.tree.hash()
300
- this.header.tree.signature = this.tree.signature
301
334
  }
302
335
 
336
+ await batch.commit()
337
+
338
+ this.tree.fork = src.tree.fork
339
+
340
+ this.header.tree.length = this.tree.length
341
+ this.header.tree.rootHash = this.tree.hash()
342
+ this.header.tree.signature = this.tree.signature
343
+
303
344
  this.header.userData = src.header.userData.slice(0)
304
345
  this.header.hints.contiguousLength = Math.min(src.header.hints.contiguousLength, this.header.tree.length)
305
346
 
@@ -379,6 +420,10 @@ module.exports = class Core {
379
420
  }
380
421
 
381
422
  async truncate (length, fork, { signature, keyPair = this.header.keyPair } = {}) {
423
+ if (this.tree.prologue && length < this.tree.prologue.length) {
424
+ throw INVALID_OPERATION('Truncation breaks prologue')
425
+ }
426
+
382
427
  this.truncating++
383
428
  await this._mutex.lock()
384
429
 
@@ -553,6 +598,11 @@ module.exports = class Core {
553
598
  const batch = this.tree.batch()
554
599
  for (const val of values) batch.append(val)
555
600
 
601
+ // only multisig can have prologue so signature is always present
602
+ if (this.tree.prologue && batch.length < this.tree.prologue.length) {
603
+ throw INVALID_OPERATION('Append is not consistent with prologue')
604
+ }
605
+
556
606
  batch.signature = signature || this.verifier.sign(batch, keyPair)
557
607
 
558
608
  const entry = {
@@ -590,21 +640,21 @@ module.exports = class Core {
590
640
 
591
641
  _verifyBatchUpgrade (batch, manifest) {
592
642
  if (!this.header.manifest) {
593
- if (!manifest && this.compat) manifest = defaultSignerManifest(this.header.key)
643
+ if (!manifest && this.compat) manifest = Verifier.defaultSignerManifest(this.header.key)
594
644
 
595
- if (!manifest || !(isValidManifest(this.header.key, manifest) || (this.compat && isCompat(this.header.key, manifest)))) {
645
+ if (!manifest || !(Verifier.isValidManifest(this.header.key, manifest) || (this.compat && Verifier.isCompat(this.header.key, manifest)))) {
596
646
  throw INVALID_SIGNATURE('Proof contains an invalid manifest') // TODO: proper error type
597
647
  }
598
648
  }
599
649
 
600
- const verifier = this.verifier || createVerifier(manifest, { compat: isCompat(this.header.key, manifest), crypto: this.crypto, legacy: this._legacy })
650
+ const verifier = this.verifier || new Verifier(manifest, { compat: Verifier.isCompat(this.header.key, manifest), crypto: this.crypto, legacy: this._legacy })
601
651
 
602
652
  if (!verifier.verify(batch, batch.signature)) {
603
653
  throw INVALID_SIGNATURE('Proof contains an invalid signature')
604
654
  }
605
655
 
606
656
  if (!this.header.manifest) {
607
- this.compat = isCompat(this.header.key, manifest)
657
+ this.compat = Verifier.isCompat(this.header.key, manifest)
608
658
  this.header.manifest = manifest
609
659
  this.verifier = verifier
610
660
  this.onupdate(0b10000, null, null, null)
@@ -703,7 +753,7 @@ module.exports = class Core {
703
753
 
704
754
  // if we got a manifest AND its strictly a non compat one, lets store it
705
755
  if (manifest && this.header.manifest === null) {
706
- if (!isValidManifest(this.header.key, manifest)) throw INVALID_CHECKSUM('Manifest hash does not match')
756
+ if (!Verifier.isValidManifest(this.header.key, manifest)) throw INVALID_CHECKSUM('Manifest hash does not match')
707
757
  this.setManifest(manifest, null)
708
758
  }
709
759
 
@@ -879,10 +929,6 @@ function updateContig (header, upd, bitfield) {
879
929
  return 0b1000
880
930
  }
881
931
 
882
- function isValidManifest (key, manifest) {
883
- return b4a.equals(key, manifestHash(manifest))
884
- }
885
-
886
932
  function addReorgHint (list, tree, batch) {
887
933
  if (tree.length === 0 || tree.fork === batch.fork) return
888
934
 
@@ -360,13 +360,14 @@ class ByteSeeker {
360
360
  }
361
361
 
362
362
  module.exports = class MerkleTree {
363
- constructor (storage, roots, fork, signature) {
363
+ constructor (storage, roots, fork, signature, prologue) {
364
364
  this.crypto = crypto
365
365
  this.fork = fork
366
366
  this.roots = roots
367
367
  this.length = roots.length ? totalSpan(roots) / 2 : 0
368
368
  this.byteLength = totalSize(roots)
369
369
  this.signature = signature
370
+ this.prologue = prologue
370
371
 
371
372
  this.storage = storage
372
373
  this.unflushed = new Map()
@@ -424,6 +425,10 @@ module.exports = class MerkleTree {
424
425
  return Promise.all(roots)
425
426
  }
426
427
 
428
+ setPrologue ({ hash, length }) {
429
+ this.prologue = { hash, length }
430
+ }
431
+
427
432
  async upgradeable (length) {
428
433
  const indexes = flat.fullRoots(2 * length)
429
434
  const roots = new Array(indexes.length)
@@ -718,7 +723,7 @@ module.exports = class MerkleTree {
718
723
  roots.push(await getStoredNode(storage, index, null, true))
719
724
  }
720
725
 
721
- return new MerkleTree(storage, roots, opts.fork || 0, opts.signature || null)
726
+ return new MerkleTree(storage, roots, opts.fork || 0, opts.signature || null, opts.prologue || null)
722
727
  }
723
728
  }
724
729
 
@@ -811,6 +816,15 @@ function verifyTree ({ block, hash, seek }, crypto, nodes) {
811
816
  }
812
817
 
813
818
  function verifyUpgrade ({ fork, upgrade }, blockRoot, batch) {
819
+ const prologue = batch.tree.prologue
820
+
821
+ if (prologue) {
822
+ const { start, length } = upgrade
823
+ if (start < prologue.length && (start !== 0 || length !== prologue.length)) {
824
+ throw INVALID_PROOF('Upgrade does not satisfy prologue')
825
+ }
826
+ }
827
+
814
828
  const q = new NodeQueue(upgrade.nodes, blockRoot)
815
829
 
816
830
  let grow = batch.roots.length > 0
@@ -840,6 +854,12 @@ function verifyUpgrade ({ fork, upgrade }, blockRoot, batch) {
840
854
  batch.appendRoot(q.shift(ite.index), ite)
841
855
  }
842
856
 
857
+ if (prologue && batch.length === prologue.length) {
858
+ if (!b4a.equals(prologue.hash, batch.hash())) {
859
+ throw INVALID_PROOF('Invalid hash')
860
+ }
861
+ }
862
+
843
863
  const extra = upgrade.additionalNodes
844
864
 
845
865
  ite.seek(batch.roots[batch.roots.length - 1].index)
@@ -1176,6 +1196,11 @@ async function generateProof (tree, block, hash, seek, upgrade) {
1176
1196
  // Important that this does not throw inbetween making the promise arrays
1177
1197
  // and finalise being called, otherwise there will be lingering promises in the background
1178
1198
 
1199
+ if (tree.prologue && upgrade) {
1200
+ upgrade.start = upgrade.start < tree.prologue.length ? 0 : upgrade.start
1201
+ upgrade.length = upgrade.start < tree.prologue.length ? tree.prologue.length : upgrade.length
1202
+ }
1203
+
1179
1204
  const fork = tree.fork
1180
1205
  const signature = tree.signature
1181
1206
  const head = 2 * tree.length