hypercore 10.0.0-alpha.25 → 10.0.0-alpha.26

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
@@ -6,12 +6,12 @@ const c = require('compact-encoding')
6
6
  const b4a = require('b4a')
7
7
  const Xache = require('xache')
8
8
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
9
+ const Protomux = require('protomux')
9
10
  const codecs = require('codecs')
10
11
 
11
12
  const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
12
13
 
13
14
  const Replicator = require('./lib/replicator')
14
- const Extensions = require('./lib/extensions')
15
15
  const Core = require('./lib/core')
16
16
  const BlockEncryption = require('./lib/block-encryption')
17
17
  const { ReadStream, WriteStream } = require('./lib/streams')
@@ -51,15 +51,15 @@ module.exports = class Hypercore extends EventEmitter {
51
51
  this.core = null
52
52
  this.replicator = null
53
53
  this.encryption = null
54
- this.extensions = opts.extensions || new Extensions()
54
+ this.extensions = opts.extensions || new Map()
55
55
  this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
56
56
 
57
57
  this.valueEncoding = null
58
58
  this.encodeBatch = null
59
+ this.activeRequests = []
59
60
 
60
61
  this.key = key || null
61
62
  this.keyPair = null
62
- this.discoveryKey = null
63
63
  this.readable = true
64
64
  this.writable = false
65
65
  this.opened = false
@@ -94,10 +94,17 @@ module.exports = class Hypercore extends EventEmitter {
94
94
  indent + ')'
95
95
  }
96
96
 
97
+ static protomux (stream, opts) {
98
+ return stream.noiseStream.userData.open(opts)
99
+ }
100
+
97
101
  static createProtocolStream (isInitiator, opts = {}) {
98
- let outerStream = isStream(isInitiator)
99
- ? isInitiator
100
- : opts.stream
102
+ let outerStream = Protomux.isProtomux(isInitiator)
103
+ ? isInitiator.stream
104
+ : isStream(isInitiator)
105
+ ? isInitiator
106
+ : opts.stream
107
+
101
108
  let noiseStream = null
102
109
 
103
110
  if (outerStream) {
@@ -109,10 +116,16 @@ module.exports = class Hypercore extends EventEmitter {
109
116
  if (!noiseStream) throw new Error('Invalid stream')
110
117
 
111
118
  if (!noiseStream.userData) {
112
- const protocol = Replicator.createProtocol(noiseStream, opts)
113
- if (opts.keepAlive !== false) protocol.setKeepAlive(true)
119
+ const protocol = new Protomux(noiseStream)
120
+
121
+ if (opts.ondiscoverykey) {
122
+ protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
123
+ }
124
+ if (opts.keepAlive !== false) {
125
+ noiseStream.setKeepAlive(5000)
126
+ noiseStream.setTimeout(7000)
127
+ }
114
128
  noiseStream.userData = protocol
115
- noiseStream.on('error', noop) // All noise errors already propagate through outerStream
116
129
  }
117
130
 
118
131
  return outerStream
@@ -159,7 +172,6 @@ module.exports = class Hypercore extends EventEmitter {
159
172
  if (!this.sign) this.sign = o.sign
160
173
  this.crypto = o.crypto
161
174
  this.key = o.key
162
- this.discoveryKey = o.discoveryKey
163
175
  this.core = o.core
164
176
  this.replicator = o.replicator
165
177
  this.encryption = o.encryption
@@ -237,10 +249,12 @@ module.exports = class Hypercore extends EventEmitter {
237
249
  this.storage = Hypercore.defaultStorage(opts.storage || storage)
238
250
 
239
251
  this.core = await Core.open(this.storage, {
252
+ force: opts.force,
240
253
  createIfMissing: opts.createIfMissing,
241
254
  overwrite: opts.overwrite,
242
255
  keyPair,
243
256
  crypto: this.crypto,
257
+ legacy: opts.legacy,
244
258
  onupdate: this._oncoreupdate.bind(this)
245
259
  })
246
260
 
@@ -250,20 +264,19 @@ module.exports = class Hypercore extends EventEmitter {
250
264
  }
251
265
  }
252
266
 
253
- this.replicator = new Replicator(this.core, {
254
- onupdate: this._onpeerupdate.bind(this),
255
- onupload: this._onupload.bind(this)
256
- })
257
-
258
- this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
259
267
  this.key = this.core.header.signer.publicKey
260
268
  this.keyPair = this.core.header.signer
261
269
 
270
+ this.replicator = new Replicator(this.core, this.key, {
271
+ eagerUpdate: true,
272
+ allowFork: opts.allowFork !== false,
273
+ onpeerupdate: this._onpeerupdate.bind(this),
274
+ onupload: this._onupload.bind(this)
275
+ })
276
+
262
277
  if (!this.encryption && opts.encryptionKey) {
263
278
  this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
264
279
  }
265
-
266
- this.extensions.attach(this.replicator)
267
280
  }
268
281
 
269
282
  close () {
@@ -284,6 +297,16 @@ module.exports = class Hypercore extends EventEmitter {
284
297
  this.closed = true
285
298
  this.opened = false
286
299
 
300
+ const gc = []
301
+ for (const ext of this.extensions.values()) {
302
+ if (ext.session === this) gc.push(ext)
303
+ }
304
+ for (const ext of gc) ext.destroy()
305
+
306
+ if (this.replicator !== null) {
307
+ this.replicator.clearRequests(this.activeRequests)
308
+ }
309
+
287
310
  if (this.sessions.length) {
288
311
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
289
312
  if (this.sessions.length === 1 && this.autoClose) await this.sessions[0].close()
@@ -303,14 +326,18 @@ module.exports = class Hypercore extends EventEmitter {
303
326
  const protocol = noiseStream.userData
304
327
 
305
328
  if (this.opened) {
306
- this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
329
+ this.replicator.attachTo(protocol)
307
330
  } else {
308
- this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
331
+ this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
309
332
  }
310
333
 
311
334
  return protocolStream
312
335
  }
313
336
 
337
+ get discoveryKey () {
338
+ return this.replicator === null ? null : this.replicator.discoveryKey
339
+ }
340
+
314
341
  get length () {
315
342
  return this._snapshot
316
343
  ? this._snapshot.length
@@ -365,13 +392,11 @@ module.exports = class Hypercore extends EventEmitter {
365
392
  }
366
393
  }
367
394
 
368
- this.replicator.broadcastInfo()
395
+ this.replicator.signalUpgrade()
369
396
  }
370
397
 
371
- if (bitfield && !bitfield.drop) { // TODO: support drop!
372
- for (let i = 0; i < bitfield.length; i++) {
373
- this.replicator.broadcastBlock(bitfield.start + i)
374
- }
398
+ if (bitfield) {
399
+ this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
375
400
  }
376
401
 
377
402
  if (value) {
@@ -384,9 +409,14 @@ module.exports = class Hypercore extends EventEmitter {
384
409
  }
385
410
 
386
411
  _onpeerupdate (added, peer) {
387
- if (added) this.extensions.update(peer)
388
412
  const name = added ? 'peer-add' : 'peer-remove'
389
413
 
414
+ if (added) {
415
+ for (const ext of this.extensions.values()) {
416
+ peer.extensions.set(ext.name, ext)
417
+ }
418
+ }
419
+
390
420
  for (let i = 0; i < this.sessions.length; i++) {
391
421
  this.sessions[i].emit(name, peer)
392
422
  }
@@ -405,19 +435,32 @@ module.exports = class Hypercore extends EventEmitter {
405
435
  return null
406
436
  }
407
437
 
408
- async update () {
438
+ async update (opts) {
409
439
  if (this.opened === false) await this.opening
440
+
410
441
  // TODO: add an option where a writer can bootstrap it's state from the network also
411
- if (this.writable) return false
412
- return this.replicator.requestUpgrade()
442
+ if (this.writable || this.closing !== null) return false
443
+
444
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
445
+ const req = this.replicator.addUpgrade(activeRequests)
446
+
447
+ return req.promise
413
448
  }
414
449
 
415
- async seek (bytes) {
450
+ async seek (bytes, opts) {
416
451
  if (this.opened === false) await this.opening
417
452
 
418
453
  const s = this.core.tree.seek(bytes, this.padding)
419
454
 
420
- return (await s.update()) || this.replicator.requestSeek(s)
455
+ const offset = await s.update()
456
+ if (offset) return offset
457
+
458
+ if (this.closing !== null) throw new Error('Session is closed')
459
+
460
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
461
+ const req = this.replicator.addSeek(activeRequests, s)
462
+
463
+ return req.promise
421
464
  }
422
465
 
423
466
  async has (index) {
@@ -428,6 +471,8 @@ module.exports = class Hypercore extends EventEmitter {
428
471
 
429
472
  async get (index, opts) {
430
473
  if (this.opened === false) await this.opening
474
+ if (this.closing !== null) throw new Error('Session is closed')
475
+
431
476
  const c = this.cache && this.cache.get(index)
432
477
  if (c) return c
433
478
  const fork = this.core.tree.fork
@@ -446,7 +491,11 @@ module.exports = class Hypercore extends EventEmitter {
446
491
  } else {
447
492
  if (opts && opts.wait === false) return null
448
493
  if (opts && opts.onwait) opts.onwait(index)
449
- block = await this.replicator.requestBlock(index)
494
+
495
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
496
+ const req = this.replicator.addBlock(activeRequests, index)
497
+
498
+ block = await req.promise
450
499
  }
451
500
 
452
501
  if (this.encryption) this.encryption.decrypt(index, block)
@@ -462,37 +511,27 @@ module.exports = class Hypercore extends EventEmitter {
462
511
  }
463
512
 
464
513
  download (range) {
465
- const linear = !!(range && range.linear)
466
-
467
- let start
468
- let end
469
- let filter
470
-
471
- if (range && range.blocks) {
472
- const blocks = range.blocks instanceof Set
473
- ? range.blocks
474
- : new Set(range.blocks)
475
-
476
- start = range.start || (blocks.size ? min(range.blocks) : 0)
477
- end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
478
-
479
- filter = (i) => blocks.has(i)
480
- } else {
481
- start = (range && range.start) || 0
482
- end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
514
+ const reqP = this._download(range)
515
+
516
+ // do not crash in the background...
517
+ reqP.catch(noop)
518
+
519
+ // TODO: turn this into an actual object...
520
+ return {
521
+ async downloaded () {
522
+ const req = await reqP
523
+ return req.promise
524
+ },
525
+ destroy () {
526
+ reqP.then(req => req.context && req.context.detach(req), noop)
527
+ }
483
528
  }
484
-
485
- const r = Replicator.createRange(start, end, filter, linear)
486
-
487
- if (this.opened) this.replicator.addRange(r)
488
- else this.opening.then(() => this.replicator.addRange(r), noop)
489
-
490
- return r
491
529
  }
492
530
 
493
- // TODO: get rid of this / deprecate it?
494
- cancel (request) {
495
- // Do nothing for now
531
+ async _download (range) {
532
+ if (this.opened === false) await this.opening
533
+ const activeRequests = (range && range.activeRequests) || this.activeRequests
534
+ return this.replicator.addRange(activeRequests, range)
496
535
  }
497
536
 
498
537
  // TODO: get rid of this / deprecate it?
@@ -500,6 +539,11 @@ module.exports = class Hypercore extends EventEmitter {
500
539
  range.destroy(null)
501
540
  }
502
541
 
542
+ // TODO: get rid of this / deprecate it?
543
+ cancel (request) {
544
+ // Do nothing for now
545
+ }
546
+
503
547
  async truncate (newLength = 0, fork = -1) {
504
548
  if (this.opened === false) await this.opening
505
549
  if (this.writable === false) throw new Error('Core is not writable')
@@ -540,13 +584,48 @@ module.exports = class Hypercore extends EventEmitter {
540
584
  return this.crypto.tree(roots)
541
585
  }
542
586
 
543
- registerExtension (name, handlers) {
544
- return this.extensions.register(name, handlers)
545
- }
587
+ registerExtension (name, handlers = {}) {
588
+ if (this.extensions.has(name)) {
589
+ const ext = this.extensions.get(name)
590
+ ext.handlers = handlers
591
+ ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
592
+ ext.session = this
593
+ return ext
594
+ }
595
+
596
+ const ext = {
597
+ name,
598
+ handlers,
599
+ encoding: c.from(codecs(handlers.encoding) || c.buffer),
600
+ session: this,
601
+ send (message, peer) {
602
+ const buffer = c.encode(this.encoding, message)
603
+ peer.extension(name, buffer)
604
+ },
605
+ broadcast (message) {
606
+ const buffer = c.encode(this.encoding, message)
607
+ for (const peer of this.session.peers) {
608
+ peer.extension(name, buffer)
609
+ }
610
+ },
611
+ destroy () {
612
+ for (const peer of this.session.peers) {
613
+ peer.extensions.delete(name)
614
+ }
615
+ this.session.extensions.delete(name)
616
+ },
617
+ _onmessage (state, peer) {
618
+ const m = this.encoding.decode(state)
619
+ if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
620
+ }
621
+ }
546
622
 
547
- // called by the extensions
548
- onextensionupdate () {
549
- if (this.replicator !== null) this.replicator.broadcastOptions()
623
+ this.extensions.set(name, ext)
624
+ for (const peer of this.peers) {
625
+ peer.extensions.set(name, ext)
626
+ }
627
+
628
+ return ext
550
629
  }
551
630
 
552
631
  _encode (enc, val) {
@@ -596,19 +675,6 @@ function toHex (buf) {
596
675
  return buf && b4a.toString(buf, 'hex')
597
676
  }
598
677
 
599
- function reduce (iter, fn, acc) {
600
- for (const item of iter) acc = fn(acc, item)
601
- return acc
602
- }
603
-
604
- function min (arr) {
605
- return reduce(arr, (a, b) => Math.min(a, b), Infinity)
606
- }
607
-
608
- function max (arr) {
609
- return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
610
- }
611
-
612
678
  function preappend (blocks) {
613
679
  const offset = this.core.tree.length
614
680
  const fork = this.core.tree.fork
package/lib/bitfield.js CHANGED
@@ -41,8 +41,9 @@ module.exports = class Bitfield {
41
41
  this.pages = new BigSparseArray()
42
42
  this.unflushed = []
43
43
  this.storage = storage
44
+ this.resumed = !!(buf && buf.byteLength >= 4)
44
45
 
45
- const all = (buf && buf.byteLength >= 4)
46
+ const all = this.resumed
46
47
  ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
47
48
  : new Uint32Array(1024)
48
49
 
@@ -93,8 +94,10 @@ module.exports = class Bitfield {
93
94
  clear () {
94
95
  return new Promise((resolve, reject) => {
95
96
  this.storage.del(0, Infinity, (err) => {
96
- if (err) reject(err)
97
- else resolve()
97
+ if (err) return reject(err)
98
+ this.pages = new BigSparseArray()
99
+ this.unflushed = []
100
+ resolve()
98
101
  })
99
102
  })
100
103
  }
package/lib/caps.js ADDED
@@ -0,0 +1,34 @@
1
+ const crypto = require('hypercore-crypto')
2
+ const sodium = require('sodium-universal')
3
+ const b4a = require('b4a')
4
+ const c = require('compact-encoding')
5
+
6
+ // TODO: rename this to "crypto" and move everything hashing related etc in here
7
+ // Also lets move the tree stuff from hypercore-crypto here, and loose the types
8
+ // from the hashes there - they are not needed since we lock the indexes in the tree
9
+ // hash and just makes alignment etc harder in other languages
10
+
11
+ const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
12
+
13
+ exports.replicate = function (isInitiator, key, handshakeHash) {
14
+ const out = b4a.allocUnsafe(32)
15
+ sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
16
+ return out
17
+ }
18
+
19
+ exports.treeSignable = function (hash, length, fork) {
20
+ const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
21
+ c.raw.encode(state, TREE)
22
+ c.raw.encode(state, hash)
23
+ c.uint64.encode(state, length)
24
+ c.uint64.encode(state, fork)
25
+ return state.buffer
26
+ }
27
+
28
+ exports.treeSignableLegacy = function (hash, length, fork) {
29
+ const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
30
+ c.raw.encode(state, hash)
31
+ c.uint64.encode(state, length)
32
+ c.uint64.encode(state, fork)
33
+ return state.buffer
34
+ }
package/lib/core.js CHANGED
@@ -5,10 +5,10 @@ const Mutex = require('./mutex')
5
5
  const MerkleTree = require('./merkle-tree')
6
6
  const BlockStore = require('./block-store')
7
7
  const Bitfield = require('./bitfield')
8
- const { oplogHeader, oplogEntry } = require('./messages')
8
+ const m = require('./messages')
9
9
 
10
10
  module.exports = class Core {
11
- constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
11
+ constructor (header, crypto, oplog, tree, blocks, bitfield, sign, legacy, onupdate) {
12
12
  this.onupdate = onupdate
13
13
  this.header = header
14
14
  this.crypto = crypto
@@ -24,6 +24,7 @@ module.exports = class Core {
24
24
  this._verifies = null
25
25
  this._verifiesFlushed = null
26
26
  this._mutex = new Mutex()
27
+ this._legacy = legacy
27
28
  }
28
29
 
29
30
  static async open (storage, opts = {}) {
@@ -57,18 +58,24 @@ module.exports = class Core {
57
58
  }
58
59
 
59
60
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
60
- const overwrite = opts.overwrite === true
61
+ let overwrite = opts.overwrite === true
62
+
63
+ const force = opts.force === true
61
64
  const createIfMissing = opts.createIfMissing !== false
62
65
  const crypto = opts.crypto || hypercoreCrypto
63
66
 
64
67
  const oplog = new Oplog(oplogFile, {
65
- headerEncoding: oplogHeader,
66
- entryEncoding: oplogEntry
68
+ headerEncoding: m.oplog.header,
69
+ entryEncoding: m.oplog.entry
67
70
  })
68
71
 
69
72
  let { header, entries } = await oplog.open()
70
73
 
71
- if (!header || overwrite === true) {
74
+ if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
75
+ overwrite = true
76
+ }
77
+
78
+ if (!header || overwrite) {
72
79
  if (!createIfMissing) {
73
80
  throw new Error('No hypercore is stored here')
74
81
  }
@@ -103,6 +110,10 @@ module.exports = class Core {
103
110
  await tree.clear()
104
111
  await blocks.clear()
105
112
  await bitfield.clear()
113
+ entries = []
114
+ } else if (bitfield.resumed && header.tree.length === 0) {
115
+ // If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
116
+ await bitfield.clear()
106
117
  }
107
118
 
108
119
  const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
@@ -119,7 +130,7 @@ module.exports = class Core {
119
130
  }
120
131
 
121
132
  if (e.bitfield) {
122
- bitfield.setRange(e.bitfield.start, e.bitfield.length)
133
+ bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
123
134
  }
124
135
 
125
136
  if (e.treeUpgrade) {
@@ -136,7 +147,7 @@ module.exports = class Core {
136
147
  }
137
148
  }
138
149
 
139
- return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
150
+ return new this(header, crypto, oplog, tree, blocks, bitfield, sign, !!opts.legacy, opts.onupdate || noop)
140
151
  }
141
152
 
142
153
  _shouldFlush () {
@@ -227,7 +238,7 @@ module.exports = class Core {
227
238
  for (const val of values) batch.append(val)
228
239
 
229
240
  const hash = batch.hash()
230
- batch.signature = await sign(batch.signable(hash))
241
+ batch.signature = await sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
231
242
 
232
243
  const entry = {
233
244
  userData: null,
@@ -259,10 +270,15 @@ module.exports = class Core {
259
270
  }
260
271
  }
261
272
 
273
+ _signed (batch, hash) {
274
+ const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
275
+ return this.crypto.verify(signable, batch.signature, this.header.signer.publicKey)
276
+ }
277
+
262
278
  async _verifyExclusive ({ batch, bitfield, value, from }) {
263
279
  // TODO: move this to tree.js
264
280
  const hash = batch.hash()
265
- if (!batch.signature || !this.crypto.verify(batch.signable(hash), batch.signature, this.header.signer.publicKey)) {
281
+ if (!batch.signature || !this._signed(batch, hash)) {
266
282
  throw new Error('Remote signature does not match')
267
283
  }
268
284