hypercore 10.0.0-alpha.20 → 10.0.0-alpha.24

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/README.md CHANGED
@@ -212,6 +212,12 @@ Buffer containing the public key identifying this core.
212
212
 
213
213
  Populated after `ready` has been emitted. Will be `null` before the event.
214
214
 
215
+ #### `core.keyPair`
216
+
217
+ Object containing buffers of the core's public and secret key
218
+
219
+ Populated after `ready` has been emitted. Will be `null` before the event.
220
+
215
221
  #### `core.discoveryKey`
216
222
 
217
223
  Buffer containing a key derived from the core's public key.
@@ -274,6 +280,6 @@ socket.pipe(localCore.replicate(true)).pipe(socket)
274
280
 
275
281
  Emitted when the core has been appended to (i.e. has a new length / byteLength), either locally or remotely.
276
282
 
277
- #### `core.on('truncate')`
283
+ #### `core.on('truncate', ancestors, forkId)`
278
284
 
279
285
  Emitted when the core has been truncated, either locally or remotely.
package/index.js CHANGED
@@ -58,6 +58,7 @@ module.exports = class Hypercore extends EventEmitter {
58
58
  this.encodeBatch = null
59
59
 
60
60
  this.key = key || null
61
+ this.keyPair = null
61
62
  this.discoveryKey = null
62
63
  this.readable = true
63
64
  this.writable = false
@@ -107,7 +108,7 @@ module.exports = class Hypercore extends EventEmitter {
107
108
  if (!noiseStream) throw new Error('Invalid stream')
108
109
 
109
110
  if (!noiseStream.userData) {
110
- const protocol = Replicator.createProtocol(noiseStream)
111
+ const protocol = Replicator.createProtocol(noiseStream, opts)
111
112
  if (opts.keepAlive !== false) protocol.setKeepAlive(true)
112
113
  noiseStream.userData = protocol
113
114
  noiseStream.on('error', noop) // All noise errors already propagate through outerStream
@@ -231,6 +232,8 @@ module.exports = class Hypercore extends EventEmitter {
231
232
  this.storage = Hypercore.defaultStorage(opts.storage || storage)
232
233
 
233
234
  this.core = await Core.open(this.storage, {
235
+ createIfMissing: opts.createIfMissing,
236
+ overwrite: opts.overwrite,
234
237
  keyPair,
235
238
  crypto: this.crypto,
236
239
  onupdate: this._oncoreupdate.bind(this)
@@ -243,11 +246,13 @@ module.exports = class Hypercore extends EventEmitter {
243
246
  }
244
247
 
245
248
  this.replicator = new Replicator(this.core, {
246
- onupdate: this._onpeerupdate.bind(this)
249
+ onupdate: this._onpeerupdate.bind(this),
250
+ onupload: this._onupload.bind(this)
247
251
  })
248
252
 
249
253
  this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
250
254
  this.key = this.core.header.signer.publicKey
255
+ this.keyPair = this.core.header.signer
251
256
 
252
257
  if (!this.encryption && opts.encryptionKey) {
253
258
  this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
@@ -328,12 +333,20 @@ module.exports = class Hypercore extends EventEmitter {
328
333
  return this.opening
329
334
  }
330
335
 
336
+ _onupload (index, value, from) {
337
+ const byteLength = value.byteLength - this.padding
338
+
339
+ for (let i = 0; i < this.sessions.length; i++) {
340
+ this.sessions[i].emit('upload', index, byteLength, from)
341
+ }
342
+ }
343
+
331
344
  _oncoreupdate (status, bitfield, value, from) {
332
345
  if (status !== 0) {
333
346
  for (let i = 0; i < this.sessions.length; i++) {
334
347
  if ((status & 0b10) !== 0) {
335
348
  if (this.cache) this.cache.clear()
336
- this.sessions[i].emit('truncate', this.core.tree.fork)
349
+ this.sessions[i].emit('truncate', bitfield.start, this.core.tree.fork)
337
350
  }
338
351
  if ((status & 0b01) !== 0) {
339
352
  this.sessions[i].emit('append')
package/lib/messages.js CHANGED
@@ -256,16 +256,25 @@ exports.info = {
256
256
  preencode (state, i) {
257
257
  c.uint.preencode(state, i.length)
258
258
  c.uint.preencode(state, i.fork)
259
+ c.uint.preencode(state, 1) // flags
259
260
  },
260
261
  encode (state, i) {
261
262
  c.uint.encode(state, i.length)
262
263
  c.uint.encode(state, i.fork)
264
+ c.uint.encode(state, (i.uploading ? 1 : 0) | (i.downloading ? 2 : 0))
263
265
  },
264
266
  decode (state) {
265
- return {
267
+ const i = {
266
268
  length: c.uint.decode(state),
267
- fork: c.uint.decode(state)
269
+ fork: c.uint.decode(state),
270
+ uploading: true,
271
+ downloading: true
268
272
  }
273
+ if (state.end <= state.start) return i // backwards compat with prev alphas
274
+ const flags = c.uint.decode(state)
275
+ i.uploading = (flags & 1) !== 0
276
+ i.downloading = (flags & 2) !== 0
277
+ return i
269
278
  }
270
279
  }
271
280
 
package/lib/protocol.js CHANGED
@@ -2,9 +2,19 @@ const { uint, from: fromEncoding } = require('compact-encoding')
2
2
  const b4a = require('b4a')
3
3
  const safetyCatch = require('safety-catch')
4
4
  const codecs = require('codecs')
5
+ const sodium = require('sodium-universal')
5
6
 
6
7
  const messages = require('./messages')
7
8
 
9
+ const slab = b4a.alloc(96)
10
+ const NS = slab.subarray(0, 32)
11
+ const NS_HYPERCORE_INITIATOR = slab.subarray(32, 64)
12
+ const NS_HYPERCORE_RESPONDER = slab.subarray(64, 96)
13
+
14
+ sodium.crypto_generichash(NS, b4a.from('hypercore'))
15
+ sodium.crypto_generichash(NS_HYPERCORE_INITIATOR, b4a.from([0]), NS)
16
+ sodium.crypto_generichash(NS_HYPERCORE_RESPONDER, b4a.from([1]), NS)
17
+
8
18
  class Extension {
9
19
  constructor (protocol, type, name, handlers) {
10
20
  this.protocol = protocol
@@ -126,11 +136,34 @@ class Peer {
126
136
  this.resend = false
127
137
  this.state = state
128
138
  this.extensions = new Map()
139
+ this.uploading = true
140
+ this.downloading = true
129
141
  this.destroyed = false
130
142
 
131
143
  this._destroyer = this._safeDestroy.bind(this)
132
144
  }
133
145
 
146
+ setUploading (uploading) {
147
+ if (uploading === this.uploading) return
148
+ this.uploading = uploading
149
+ this._sendInfo()
150
+ }
151
+
152
+ setDownloading (downloading) {
153
+ if (downloading === this.downloading) return
154
+ this.downloading = downloading
155
+ this._sendInfo()
156
+ }
157
+
158
+ _sendInfo () {
159
+ this.info({
160
+ length: this.handlers.core.tree.length,
161
+ fork: this.handlers.core.tree.fork,
162
+ uploading: this.uploading,
163
+ downloading: this.downloading
164
+ })
165
+ }
166
+
134
167
  onmessage (type, state) {
135
168
  const handlers = this.handlers
136
169
 
@@ -257,10 +290,14 @@ module.exports = class Protocol {
257
290
  this._remoteExtensions = []
258
291
  this._extensions = new Map()
259
292
  this._keepAliveInterval = null
293
+ this._pendingCaps = []
260
294
 
261
295
  this._destroyer = this._safeDestroy.bind(this)
262
296
  this.noiseStream.on('data', this.onmessage.bind(this))
263
297
  this.noiseStream.on('end', this.noiseStream.end) // no half open
298
+ this.noiseStream.on('finish', () => {
299
+ this.setKeepAlive(false)
300
+ })
264
301
  this.noiseStream.on('close', () => {
265
302
  this.setKeepAlive(false)
266
303
  // TODO: If the stream was destroyed with an error, we probably want to forward it here
@@ -346,6 +383,11 @@ module.exports = class Protocol {
346
383
 
347
384
  if (batch.length === 0) return
348
385
 
386
+ while (this._pendingCaps.length > 0) {
387
+ const [key, cap] = this._pendingCaps.pop()
388
+ hypercoreCapability(this.noiseStream.isInitiator, this.noiseStream.handshakeHash, key, cap)
389
+ }
390
+
349
391
  const state = { start: 0, end: 0, buffer: null }
350
392
  const lens = new Array(batch.length)
351
393
 
@@ -386,10 +428,18 @@ module.exports = class Protocol {
386
428
  }
387
429
 
388
430
  _announceCore (alias, key, discoveryKey) {
431
+ const cap = b4a.alloc(32)
432
+
433
+ if (!this.noiseStream.handshakeHash) {
434
+ this._pendingCaps.push([key, cap]) // encode it later...
435
+ } else {
436
+ hypercoreCapability(this.noiseStream.isInitiator, this.noiseStream.handshakeHash, key, cap)
437
+ }
438
+
389
439
  this.send(2, messages.core, -1, {
390
440
  alias: alias,
391
441
  discoveryKey: discoveryKey,
392
- capability: b4a.alloc(32) // TODO
442
+ capability: cap
393
443
  })
394
444
  }
395
445
 
@@ -469,7 +519,11 @@ module.exports = class Protocol {
469
519
  if (m.alias === this._remoteAliases.length) this._remoteAliases.push(null)
470
520
 
471
521
  if (peer) {
472
- // TODO: check cap
522
+ const expectedCap = hypercoreCapability(!this.noiseStream.isInitiator, this.noiseStream.handshakeHash, peer.key)
523
+ if (!b4a.equals(expectedCap, m.capability)) {
524
+ this.destroy(new Error('Remote sent an invalid capability'))
525
+ return
526
+ }
473
527
 
474
528
  if (m.alias >= this._remoteAliases.length) {
475
529
  this.destroy(new Error('Remote alias out of bounds'))
@@ -544,3 +598,9 @@ function noop () {}
544
598
  function isPromise (p) {
545
599
  return !!p && typeof p.then === 'function'
546
600
  }
601
+
602
+ function hypercoreCapability (initiator, handshakeHash, key, cap = b4a.alloc(32)) {
603
+ const ns = initiator ? NS_HYPERCORE_INITIATOR : NS_HYPERCORE_RESPONDER
604
+ sodium.crypto_generichash_batch(cap, [handshakeHash, key], ns)
605
+ return cap
606
+ }
package/lib/replicator.js CHANGED
@@ -14,6 +14,8 @@ class RemoteState {
14
14
  this.bitfield = new RemoteBitfield()
15
15
  this.length = 0
16
16
  this.fork = 0
17
+ this.uploading = true
18
+ this.downloading = true
17
19
  }
18
20
  }
19
21
 
@@ -241,6 +243,7 @@ class RequestPool {
241
243
 
242
244
  update (peer) {
243
245
  if (peer.state.inflight >= peer.state.maxInflight) return false
246
+ if (peer.downloading === false || peer.state.uploading === false) return false
244
247
  if (this.paused) return false
245
248
 
246
249
  if (peer.state.fork > this.core.tree.fork) {
@@ -624,17 +627,19 @@ class RequestPool {
624
627
  }
625
628
 
626
629
  module.exports = class Replicator {
627
- constructor (core, { onupdate }) {
630
+ constructor (core, { onupdate, onupload }) {
628
631
  this.core = core
629
632
  this.peers = []
630
633
  this.requests = new RequestPool(this, core)
631
634
  this.updating = null
632
635
  this.pendingPeers = new Set()
633
636
  this.onupdate = onupdate
637
+ this.onupload = onupload
634
638
  }
635
639
 
636
- static createProtocol (noiseStream) {
640
+ static createProtocol (noiseStream, opts) {
637
641
  return new Protocol(noiseStream, {
642
+ ...opts,
638
643
  protocolVersion: 0,
639
644
  userAgent: USER_AGENT
640
645
  })
@@ -658,8 +663,14 @@ module.exports = class Replicator {
658
663
  }
659
664
 
660
665
  broadcastInfo () {
661
- const msg = { length: this.core.tree.length, fork: this.core.tree.fork }
662
- for (const peer of this.peers) peer.info(msg)
666
+ for (const peer of this.peers) {
667
+ peer.info({
668
+ length: this.core.tree.length,
669
+ fork: this.core.tree.fork,
670
+ uploading: peer.uploading,
671
+ downloading: peer.downloading
672
+ })
673
+ }
663
674
  this.updateAll()
664
675
  }
665
676
 
@@ -737,7 +748,12 @@ module.exports = class Replicator {
737
748
  this.peers.push(peer)
738
749
  this.onupdate(true, peer)
739
750
 
740
- peer.info({ length: this.core.tree.length, fork: this.core.tree.fork })
751
+ peer.info({
752
+ length: this.core.tree.length,
753
+ fork: this.core.tree.fork,
754
+ uploading: peer.uploading,
755
+ downloading: peer.downloading
756
+ })
741
757
 
742
758
  // YOLO send over all the pages for now
743
759
  const p = pages(this.core)
@@ -752,12 +768,14 @@ module.exports = class Replicator {
752
768
  // This is a no-op because there isn't any state to dealloc currently
753
769
  }
754
770
 
755
- oninfo ({ length, fork }, peer) {
771
+ oninfo ({ length, fork, uploading, downloading }, peer) {
756
772
  const len = peer.state.length
757
773
  const forked = peer.state.fork !== fork
758
774
 
759
775
  peer.state.length = length
760
776
  peer.state.fork = fork
777
+ peer.state.downloading = downloading
778
+ peer.state.uploading = uploading
761
779
 
762
780
  if (forked) {
763
781
  for (let i = peer.state.length; i < len; i++) {
@@ -813,11 +831,13 @@ module.exports = class Replicator {
813
831
  async onrequest (req, peer) {
814
832
  const fork = req.fork || peer.state.fork
815
833
  if (fork !== this.core.tree.fork) return
834
+ if (!peer.uploading) return
816
835
 
817
836
  const proof = await this.core.tree.proof(req)
818
837
 
819
838
  if (req.block && req.block.value) {
820
839
  proof.block.value = await this.core.blocks.get(req.block.index)
840
+ this.onupload(req.block.index, proof.block.value, peer)
821
841
  }
822
842
 
823
843
  peer.data(proof)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.20",
3
+ "version": "10.0.0-alpha.24",
4
4
  "description": "Hypercore 10",
5
5
  "main": "index.js",
6
6
  "scripts": {