hypercore 10.0.0-alpha.25 → 10.0.0-alpha.28

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 = 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 getProtocolMuxer (stream) {
98
+ return stream.noiseStream.userData
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
@@ -144,7 +157,6 @@ module.exports = class Hypercore extends EventEmitter {
144
157
  const Clz = opts.class || Hypercore
145
158
  const s = new Clz(this.storage, this.key, {
146
159
  ...opts,
147
- extensions: this.extensions,
148
160
  _opening: this.opening,
149
161
  _sessions: this.sessions
150
162
  })
@@ -159,7 +171,6 @@ module.exports = class Hypercore extends EventEmitter {
159
171
  if (!this.sign) this.sign = o.sign
160
172
  this.crypto = o.crypto
161
173
  this.key = o.key
162
- this.discoveryKey = o.discoveryKey
163
174
  this.core = o.core
164
175
  this.replicator = o.replicator
165
176
  this.encryption = o.encryption
@@ -170,12 +181,7 @@ module.exports = class Hypercore extends EventEmitter {
170
181
  async _openFromExisting (from, opts) {
171
182
  await from.opening
172
183
 
173
- for (const [name, ext] of this.extensions) {
174
- from.extensions.register(name, null, ext)
175
- }
176
-
177
184
  this._passCapabilities(from)
178
- this.extensions = from.extensions
179
185
  this.sessions = from.sessions
180
186
  this.storage = from.storage
181
187
 
@@ -237,10 +243,12 @@ module.exports = class Hypercore extends EventEmitter {
237
243
  this.storage = Hypercore.defaultStorage(opts.storage || storage)
238
244
 
239
245
  this.core = await Core.open(this.storage, {
246
+ force: opts.force,
240
247
  createIfMissing: opts.createIfMissing,
241
248
  overwrite: opts.overwrite,
242
249
  keyPair,
243
250
  crypto: this.crypto,
251
+ legacy: opts.legacy,
244
252
  onupdate: this._oncoreupdate.bind(this)
245
253
  })
246
254
 
@@ -250,20 +258,19 @@ module.exports = class Hypercore extends EventEmitter {
250
258
  }
251
259
  }
252
260
 
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
261
  this.key = this.core.header.signer.publicKey
260
262
  this.keyPair = this.core.header.signer
261
263
 
264
+ this.replicator = new Replicator(this.core, this.key, {
265
+ eagerUpdate: true,
266
+ allowFork: opts.allowFork !== false,
267
+ onpeerupdate: this._onpeerupdate.bind(this),
268
+ onupload: this._onupload.bind(this)
269
+ })
270
+
262
271
  if (!this.encryption && opts.encryptionKey) {
263
272
  this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
264
273
  }
265
-
266
- this.extensions.attach(this.replicator)
267
274
  }
268
275
 
269
276
  close () {
@@ -284,6 +291,16 @@ module.exports = class Hypercore extends EventEmitter {
284
291
  this.closed = true
285
292
  this.opened = false
286
293
 
294
+ const gc = []
295
+ for (const ext of this.extensions.values()) {
296
+ if (ext.session === this) gc.push(ext)
297
+ }
298
+ for (const ext of gc) ext.destroy()
299
+
300
+ if (this.replicator !== null) {
301
+ this.replicator.clearRequests(this.activeRequests)
302
+ }
303
+
287
304
  if (this.sessions.length) {
288
305
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
289
306
  if (this.sessions.length === 1 && this.autoClose) await this.sessions[0].close()
@@ -303,14 +320,18 @@ module.exports = class Hypercore extends EventEmitter {
303
320
  const protocol = noiseStream.userData
304
321
 
305
322
  if (this.opened) {
306
- this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
323
+ this.replicator.attachTo(protocol)
307
324
  } else {
308
- this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
325
+ this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
309
326
  }
310
327
 
311
328
  return protocolStream
312
329
  }
313
330
 
331
+ get discoveryKey () {
332
+ return this.replicator === null ? null : this.replicator.discoveryKey
333
+ }
334
+
314
335
  get length () {
315
336
  return this._snapshot
316
337
  ? this._snapshot.length
@@ -365,13 +386,11 @@ module.exports = class Hypercore extends EventEmitter {
365
386
  }
366
387
  }
367
388
 
368
- this.replicator.broadcastInfo()
389
+ this.replicator.localUpgrade()
369
390
  }
370
391
 
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
- }
392
+ if (bitfield) {
393
+ this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
375
394
  }
376
395
 
377
396
  if (value) {
@@ -384,11 +403,16 @@ module.exports = class Hypercore extends EventEmitter {
384
403
  }
385
404
 
386
405
  _onpeerupdate (added, peer) {
387
- if (added) this.extensions.update(peer)
388
406
  const name = added ? 'peer-add' : 'peer-remove'
389
407
 
390
408
  for (let i = 0; i < this.sessions.length; i++) {
391
409
  this.sessions[i].emit(name, peer)
410
+
411
+ if (added) {
412
+ for (const ext of this.sessions[i].extensions.values()) {
413
+ peer.extensions.set(ext.name, ext)
414
+ }
415
+ }
392
416
  }
393
417
  }
394
418
 
@@ -405,19 +429,32 @@ module.exports = class Hypercore extends EventEmitter {
405
429
  return null
406
430
  }
407
431
 
408
- async update () {
432
+ async update (opts) {
409
433
  if (this.opened === false) await this.opening
434
+
410
435
  // 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()
436
+ if (this.writable || this.closing !== null) return false
437
+
438
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
439
+ const req = this.replicator.addUpgrade(activeRequests)
440
+
441
+ return req.promise
413
442
  }
414
443
 
415
- async seek (bytes) {
444
+ async seek (bytes, opts) {
416
445
  if (this.opened === false) await this.opening
417
446
 
418
447
  const s = this.core.tree.seek(bytes, this.padding)
419
448
 
420
- return (await s.update()) || this.replicator.requestSeek(s)
449
+ const offset = await s.update()
450
+ if (offset) return offset
451
+
452
+ if (this.closing !== null) throw new Error('Session is closed')
453
+
454
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
455
+ const req = this.replicator.addSeek(activeRequests, s)
456
+
457
+ return req.promise
421
458
  }
422
459
 
423
460
  async has (index) {
@@ -428,6 +465,8 @@ module.exports = class Hypercore extends EventEmitter {
428
465
 
429
466
  async get (index, opts) {
430
467
  if (this.opened === false) await this.opening
468
+ if (this.closing !== null) throw new Error('Session is closed')
469
+
431
470
  const c = this.cache && this.cache.get(index)
432
471
  if (c) return c
433
472
  const fork = this.core.tree.fork
@@ -446,7 +485,11 @@ module.exports = class Hypercore extends EventEmitter {
446
485
  } else {
447
486
  if (opts && opts.wait === false) return null
448
487
  if (opts && opts.onwait) opts.onwait(index)
449
- block = await this.replicator.requestBlock(index)
488
+
489
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
490
+ const req = this.replicator.addBlock(activeRequests, index)
491
+
492
+ block = await req.promise
450
493
  }
451
494
 
452
495
  if (this.encryption) this.encryption.decrypt(index, block)
@@ -462,37 +505,27 @@ module.exports = class Hypercore extends EventEmitter {
462
505
  }
463
506
 
464
507
  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
508
+ const reqP = this._download(range)
509
+
510
+ // do not crash in the background...
511
+ reqP.catch(noop)
512
+
513
+ // TODO: turn this into an actual object...
514
+ return {
515
+ async downloaded () {
516
+ const req = await reqP
517
+ return req.promise
518
+ },
519
+ destroy () {
520
+ reqP.then(req => req.context && req.context.detach(req), noop)
521
+ }
483
522
  }
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
523
  }
492
524
 
493
- // TODO: get rid of this / deprecate it?
494
- cancel (request) {
495
- // Do nothing for now
525
+ async _download (range) {
526
+ if (this.opened === false) await this.opening
527
+ const activeRequests = (range && range.activeRequests) || this.activeRequests
528
+ return this.replicator.addRange(activeRequests, range)
496
529
  }
497
530
 
498
531
  // TODO: get rid of this / deprecate it?
@@ -500,6 +533,11 @@ module.exports = class Hypercore extends EventEmitter {
500
533
  range.destroy(null)
501
534
  }
502
535
 
536
+ // TODO: get rid of this / deprecate it?
537
+ cancel (request) {
538
+ // Do nothing for now
539
+ }
540
+
503
541
  async truncate (newLength = 0, fork = -1) {
504
542
  if (this.opened === false) await this.opening
505
543
  if (this.writable === false) throw new Error('Core is not writable')
@@ -540,13 +578,48 @@ module.exports = class Hypercore extends EventEmitter {
540
578
  return this.crypto.tree(roots)
541
579
  }
542
580
 
543
- registerExtension (name, handlers) {
544
- return this.extensions.register(name, handlers)
545
- }
581
+ registerExtension (name, handlers = {}) {
582
+ if (this.extensions.has(name)) {
583
+ const ext = this.extensions.get(name)
584
+ ext.handlers = handlers
585
+ ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
586
+ ext.session = this
587
+ return ext
588
+ }
589
+
590
+ const ext = {
591
+ name,
592
+ handlers,
593
+ encoding: c.from(codecs(handlers.encoding) || c.buffer),
594
+ session: this,
595
+ send (message, peer) {
596
+ const buffer = c.encode(this.encoding, message)
597
+ peer.extension(name, buffer)
598
+ },
599
+ broadcast (message) {
600
+ const buffer = c.encode(this.encoding, message)
601
+ for (const peer of this.session.peers) {
602
+ peer.extension(name, buffer)
603
+ }
604
+ },
605
+ destroy () {
606
+ for (const peer of this.session.peers) {
607
+ if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
608
+ }
609
+ this.session.extensions.delete(name)
610
+ },
611
+ _onmessage (state, peer) {
612
+ const m = this.encoding.decode(state)
613
+ if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
614
+ }
615
+ }
546
616
 
547
- // called by the extensions
548
- onextensionupdate () {
549
- if (this.replicator !== null) this.replicator.broadcastOptions()
617
+ this.extensions.set(name, ext)
618
+ for (const peer of this.peers) {
619
+ peer.extensions.set(name, ext)
620
+ }
621
+
622
+ return ext
550
623
  }
551
624
 
552
625
  _encode (enc, val) {
@@ -596,19 +669,6 @@ function toHex (buf) {
596
669
  return buf && b4a.toString(buf, 'hex')
597
670
  }
598
671
 
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
672
  function preappend (blocks) {
613
673
  const offset = this.core.tree.length
614
674
  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