hypercore 10.0.0-alpha.36 → 10.0.0-alpha.39

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
@@ -15,6 +15,7 @@ const Replicator = require('./lib/replicator')
15
15
  const Core = require('./lib/core')
16
16
  const BlockEncryption = require('./lib/block-encryption')
17
17
  const { ReadStream, WriteStream } = require('./lib/streams')
18
+ const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors')
18
19
 
19
20
  const promises = Symbol.for('hypercore.promises')
20
21
  const inspect = Symbol.for('nodejs.util.inspect.custom')
@@ -39,7 +40,7 @@ module.exports = class Hypercore extends EventEmitter {
39
40
  if (!opts) opts = {}
40
41
 
41
42
  if (!opts.crypto && key && key.byteLength !== 32) {
42
- throw new Error('Hypercore key should be 32 bytes')
43
+ throw BAD_ARGUMENT('Hypercore key should be 32 bytes')
43
44
  }
44
45
 
45
46
  if (!storage) storage = opts.storage
@@ -64,6 +65,7 @@ module.exports = class Hypercore extends EventEmitter {
64
65
  this.writable = false
65
66
  this.opened = false
66
67
  this.closed = false
68
+ this.snapshotted = !!opts.snapshot
67
69
  this.sessions = opts._sessions || [this]
68
70
  this.auth = opts.auth || null
69
71
  this.autoClose = !!opts.autoClose
@@ -73,7 +75,7 @@ module.exports = class Hypercore extends EventEmitter {
73
75
  this.opening.catch(noop)
74
76
 
75
77
  this._preappend = preappend.bind(this)
76
- this._snapshot = opts.snapshot || null
78
+ this._snapshot = null
77
79
  this._findingPeers = 0
78
80
  }
79
81
 
@@ -83,15 +85,40 @@ module.exports = class Hypercore extends EventEmitter {
83
85
  while (indent.length < opts.indentationLvl) indent += ' '
84
86
  }
85
87
 
88
+ let peers = ''
89
+ const min = Math.min(this.peers.length, 5)
90
+
91
+ for (let i = 0; i < min; i++) {
92
+ const peer = this.peers[i]
93
+
94
+ peers += indent + ' Peer(\n'
95
+ peers += indent + ' remotePublicKey: ' + opts.stylize(toHex(peer.remotePublicKey), 'string') + '\n'
96
+ peers += indent + ' remoteLength: ' + opts.stylize(peer.remoteLength, 'number') + '\n'
97
+ peers += indent + ' remoteFork: ' + opts.stylize(peer.remoteFork, 'number') + '\n'
98
+ peers += indent + ' remoteCanUpgrade: ' + opts.stylize(peer.remoteCanUpgrade, 'boolean') + '\n'
99
+ peers += indent + ' )' + '\n'
100
+ }
101
+
102
+ if (this.peers.length > 5) {
103
+ peers += indent + ' ... and ' + (this.peers.length - 5) + ' more\n'
104
+ }
105
+
106
+ if (peers) peers = '[\n' + peers + indent + ' ]'
107
+ else peers = '[ ' + opts.stylize(0, 'number') + ' ]'
108
+
86
109
  return this.constructor.name + '(\n' +
87
- indent + ' key: ' + opts.stylize((toHex(this.key)), 'string') + '\n' +
110
+ indent + ' key: ' + opts.stylize(toHex(this.key), 'string') + '\n' +
88
111
  indent + ' discoveryKey: ' + opts.stylize(toHex(this.discoveryKey), 'string') + '\n' +
89
112
  indent + ' opened: ' + opts.stylize(this.opened, 'boolean') + '\n' +
113
+ indent + ' closed: ' + opts.stylize(this.closed, 'boolean') + '\n' +
114
+ indent + ' snapshotted: ' + opts.stylize(this.snapshotted, 'boolean') + '\n' +
90
115
  indent + ' writable: ' + opts.stylize(this.writable, 'boolean') + '\n' +
91
- indent + ' sessions: ' + opts.stylize(this.sessions.length, 'number') + '\n' +
92
- indent + ' peers: [ ' + opts.stylize(this.peers.length, 'number') + ' ]\n' +
93
116
  indent + ' length: ' + opts.stylize(this.length, 'number') + '\n' +
94
117
  indent + ' byteLength: ' + opts.stylize(this.byteLength, 'number') + '\n' +
118
+ indent + ' fork: ' + opts.stylize(this.fork, 'number') + '\n' +
119
+ indent + ' sessions: [ ' + opts.stylize(this.sessions.length, 'number') + ' ]\n' +
120
+ indent + ' activeRequests: [ ' + opts.stylize(this.activeRequests.length, 'number') + ' ]\n' +
121
+ indent + ' peers: ' + peers + '\n' +
95
122
  indent + ')'
96
123
  }
97
124
 
@@ -114,7 +141,7 @@ module.exports = class Hypercore extends EventEmitter {
114
141
  noiseStream = new NoiseSecretStream(isInitiator, null, opts)
115
142
  outerStream = noiseStream.rawStream
116
143
  }
117
- if (!noiseStream) throw new Error('Invalid stream')
144
+ if (!noiseStream) throw BAD_ARGUMENT('Invalid stream')
118
145
 
119
146
  if (!noiseStream.userData) {
120
147
  const protocol = new Protomux(noiseStream)
@@ -144,15 +171,15 @@ module.exports = class Hypercore extends EventEmitter {
144
171
  }
145
172
  }
146
173
 
147
- snapshot () {
148
- return this.session({ snapshot: { length: this.length, byteLength: this.byteLength, fork: this.fork } })
174
+ snapshot (opts) {
175
+ return this.session({ ...opts, snapshot: true })
149
176
  }
150
177
 
151
178
  session (opts = {}) {
152
179
  if (this.closing) {
153
180
  // This makes the closing logic alot easier. If this turns out to be a problem
154
181
  // in practive, open an issue and we'll try to make a solution for it.
155
- throw new Error('Cannot make sessions on a closing core')
182
+ throw SESSION_CLOSED('Cannot make sessions on a closing core')
156
183
  }
157
184
 
158
185
  const Clz = opts.class || Hypercore
@@ -170,6 +197,7 @@ module.exports = class Hypercore extends EventEmitter {
170
197
 
171
198
  _passCapabilities (o) {
172
199
  if (!this.auth) this.auth = o.auth
200
+
173
201
  this.crypto = o.crypto
174
202
  this.key = o.key
175
203
  this.core = o.core
@@ -177,6 +205,8 @@ module.exports = class Hypercore extends EventEmitter {
177
205
  this.encryption = o.encryption
178
206
  this.writable = !!(this.auth && this.auth.sign)
179
207
  this.autoClose = o.autoClose
208
+
209
+ if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
180
210
  }
181
211
 
182
212
  async _openFromExisting (from, opts) {
@@ -280,6 +310,19 @@ module.exports = class Hypercore extends EventEmitter {
280
310
  }
281
311
  }
282
312
 
313
+ _updateSnapshot () {
314
+ const prev = this._snapshot
315
+ const next = this._snapshot = {
316
+ length: this.core.tree.length,
317
+ byteLength: this.core.tree.byteLength,
318
+ fork: this.core.tree.fork,
319
+ compatLength: this.core.tree.length
320
+ }
321
+
322
+ if (!prev) return true
323
+ return prev.length !== next.length || prev.fork !== next.fork
324
+ }
325
+
283
326
  close () {
284
327
  if (this.closing) return this.closing
285
328
  this.closing = this._close()
@@ -325,24 +368,34 @@ module.exports = class Hypercore extends EventEmitter {
325
368
  }
326
369
 
327
370
  replicate (isInitiator, opts = {}) {
371
+ // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
372
+ // because it doesn't really make a lot of sense.
373
+ if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator, opts)
374
+
328
375
  const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
329
376
  const noiseStream = protocolStream.noiseStream
330
377
  const protocol = noiseStream.userData
331
378
 
379
+ this._attachToMuxer(protocol, opts)
380
+
381
+ return protocolStream
382
+ }
383
+
384
+ _attachToMuxer (mux, opts) {
332
385
  // If the user wants to, we can make this replication run in a session
333
386
  // that way the core wont close "under them" during replication
334
387
  if (opts.session) {
335
388
  const s = this.session()
336
- protocolStream.on('close', () => s.close().catch(noop))
389
+ mux.stream.on('close', () => s.close().catch(noop))
337
390
  }
338
391
 
339
392
  if (this.opened) {
340
- this.replicator.attachTo(protocol)
393
+ this.replicator.attachTo(mux)
341
394
  } else {
342
- this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
395
+ this.opening.then(() => this.replicator.attachTo(mux), mux.destroy.bind(mux))
343
396
  }
344
397
 
345
- return protocolStream
398
+ return mux
346
399
  }
347
400
 
348
401
  get discoveryKey () {
@@ -362,9 +415,7 @@ module.exports = class Hypercore extends EventEmitter {
362
415
  }
363
416
 
364
417
  get fork () {
365
- return this._snapshot
366
- ? this._snapshot.fork
367
- : (this.core === null ? 0 : this.core.tree.fork)
418
+ return this.core === null ? 0 : this.core.tree.fork
368
419
  }
369
420
 
370
421
  get peers () {
@@ -393,21 +444,33 @@ module.exports = class Hypercore extends EventEmitter {
393
444
 
394
445
  _oncoreupdate (status, bitfield, value, from) {
395
446
  if (status !== 0) {
447
+ const truncated = (status & 0b10) !== 0
448
+ const appended = (status & 0b01) !== 0
449
+
450
+ if (truncated) {
451
+ this.replicator.ontruncate(bitfield.start)
452
+ }
453
+
396
454
  for (let i = 0; i < this.sessions.length; i++) {
397
- if ((status & 0b10) !== 0) {
398
- if (this.cache) this.cache.clear()
399
- this.sessions[i].emit('truncate', bitfield.start, this.core.tree.fork)
455
+ const s = this.sessions[i]
456
+
457
+ if (truncated) {
458
+ if (s.cache) s.cache.clear()
459
+ s.emit('truncate', bitfield.start, this.core.tree.fork)
460
+ // If snapshotted, make sure to update our compat so we can fail gets
461
+ if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
400
462
  }
401
- if ((status & 0b01) !== 0) {
402
- this.sessions[i].emit('append')
463
+
464
+ if (appended) {
465
+ s.emit('append')
403
466
  }
404
467
  }
405
468
 
406
- this.replicator.localUpgrade()
469
+ this.replicator.onupgrade()
407
470
  }
408
471
 
409
472
  if (bitfield) {
410
- this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
473
+ this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
411
474
  }
412
475
 
413
476
  if (value) {
@@ -464,15 +527,21 @@ module.exports = class Hypercore extends EventEmitter {
464
527
 
465
528
  async update (opts) {
466
529
  if (this.opened === false) await this.opening
530
+ if (this.closing !== null) return false
467
531
 
468
532
  // TODO: add an option where a writer can bootstrap it's state from the network also
469
- if (this.writable || this.closing !== null) return false
533
+ if (this.writable) {
534
+ if (!this.snapshotted) return false
535
+ return this._updateSnapshot()
536
+ }
470
537
 
471
538
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
472
539
  const req = this.replicator.addUpgrade(activeRequests)
473
540
 
474
- // TODO: if snapshot, also update the length/byteLength to latest
475
- return req.promise
541
+ if (!this.snapshotted) return req.promise
542
+ if (!(await req.promise)) return false
543
+
544
+ return this._updateSnapshot()
476
545
  }
477
546
 
478
547
  async seek (bytes, opts) {
@@ -483,7 +552,7 @@ module.exports = class Hypercore extends EventEmitter {
483
552
  const offset = await s.update()
484
553
  if (offset) return offset
485
554
 
486
- if (this.closing !== null) throw new Error('Session is closed')
555
+ if (this.closing !== null) throw SESSION_CLOSED()
487
556
 
488
557
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
489
558
  const req = this.replicator.addSeek(activeRequests, s)
@@ -499,7 +568,8 @@ module.exports = class Hypercore extends EventEmitter {
499
568
 
500
569
  async get (index, opts) {
501
570
  if (this.opened === false) await this.opening
502
- if (this.closing !== null) throw new Error('Session is closed')
571
+ if (this.closing !== null) throw SESSION_CLOSED()
572
+ if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
503
573
 
504
574
  const c = this.cache && this.cache.get(index)
505
575
  if (c) return c
@@ -574,7 +644,7 @@ module.exports = class Hypercore extends EventEmitter {
574
644
 
575
645
  async truncate (newLength = 0, fork = -1) {
576
646
  if (this.opened === false) await this.opening
577
- if (this.writable === false) throw new Error('Core is not writable')
647
+ if (this.writable === false) throw SESSION_NOT_WRITABLE()
578
648
 
579
649
  if (fork === -1) fork = this.core.tree.fork + 1
580
650
  await this.core.truncate(newLength, fork, this.auth)
@@ -585,7 +655,7 @@ module.exports = class Hypercore extends EventEmitter {
585
655
 
586
656
  async append (blocks) {
587
657
  if (this.opened === false) await this.opening
588
- if (this.writable === false) throw new Error('Core is not writable')
658
+ if (this.writable === false) throw SESSION_NOT_WRITABLE()
589
659
 
590
660
  blocks = Array.isArray(blocks) ? blocks : [blocks]
591
661
 
package/lib/core.js CHANGED
@@ -5,6 +5,7 @@ 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 { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('./errors')
8
9
  const m = require('./messages')
9
10
 
10
11
  module.exports = class Core {
@@ -52,7 +53,9 @@ module.exports = class Core {
52
53
  }
53
54
 
54
55
  static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
55
- if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) throw new Error('Invalid key pair')
56
+ if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) {
57
+ throw BAD_ARGUMENT('Invalid key pair')
58
+ }
56
59
 
57
60
  const sign = opts.sign
58
61
  ? opts.sign
@@ -88,7 +91,7 @@ module.exports = class Core {
88
91
 
89
92
  if (!header || overwrite) {
90
93
  if (!createIfMissing) {
91
- throw new Error('No hypercore is stored here')
94
+ throw STORAGE_EMPTY('No Hypercore is stored here')
92
95
  }
93
96
 
94
97
  header = {
@@ -110,7 +113,7 @@ module.exports = class Core {
110
113
  }
111
114
 
112
115
  if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
113
- throw new Error('Another hypercore is stored here')
116
+ throw STORAGE_CONFLICT('Another Hypercore is stored here')
114
117
  }
115
118
 
116
119
  const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
@@ -290,7 +293,7 @@ module.exports = class Core {
290
293
  // TODO: move this to tree.js
291
294
  const hash = batch.hash()
292
295
  if (!batch.signature || !this._signed(batch, hash)) {
293
- throw new Error('Remote signature does not match')
296
+ throw INVALID_SIGNATURE('Proof contains an invalid signature')
294
297
  }
295
298
 
296
299
  await this._mutex.lock()
package/lib/errors.js ADDED
@@ -0,0 +1,42 @@
1
+ module.exports = class HypercoreError extends Error {
2
+ constructor (msg, code) {
3
+ super(msg)
4
+ this.code = code
5
+ }
6
+
7
+ static BAD_ARGUMENT (msg) {
8
+ return new HypercoreError(msg, 'BAD_ARGUMENT')
9
+ }
10
+
11
+ static STORAGE_EMPTY (msg) {
12
+ return new HypercoreError(msg, 'STORAGE_EMPTY')
13
+ }
14
+
15
+ static STORAGE_CONFLICT (msg) {
16
+ return new HypercoreError(msg, 'STORAGE_CONFLICT')
17
+ }
18
+
19
+ static INVALID_SIGNATURE (msg) {
20
+ return new HypercoreError(msg, 'INVALID_SIGNATURE')
21
+ }
22
+
23
+ static INVALID_CAPABILITY (msg) {
24
+ return new HypercoreError(msg, 'INVALID_CAPABILITY')
25
+ }
26
+
27
+ static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available') {
28
+ return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE')
29
+ }
30
+
31
+ static REQUEST_CANCELLED (msg = 'Request was cancelled') {
32
+ return new HypercoreError(msg, 'REQUEST_CANCELLED')
33
+ }
34
+
35
+ static SESSION_NOT_WRITABLE (msg = 'Session is not writable') {
36
+ return new HypercoreError(msg, 'SESSION_NOT_WRITABLE')
37
+ }
38
+
39
+ static SESSION_CLOSED (msg = 'Session is closed') {
40
+ return new HypercoreError(msg, 'SESSION_CLOSED')
41
+ }
42
+ }
@@ -228,6 +228,7 @@ class ReorgBatch extends MerkleTreeBatch {
228
228
 
229
229
  let diff = null
230
230
  const ite = flat.iterator(this.diff.index)
231
+ const startingDiff = this.diff
231
232
 
232
233
  while ((ite.index & 1) !== 0) {
233
234
  const left = n.get(ite.leftChild())
@@ -243,6 +244,7 @@ class ReorgBatch extends MerkleTreeBatch {
243
244
 
244
245
  if ((this.diff.index & 1) === 0) return true
245
246
  if (diff === null) return false
247
+ if (startingDiff !== this.diff) return false
246
248
 
247
249
  return this._updateDiffRoot(diff)
248
250
  }
@@ -255,10 +257,6 @@ class ReorgBatch extends MerkleTreeBatch {
255
257
  const end = Math.min(this.treeLength, spans[1] / 2 + 1)
256
258
  const len = end - start
257
259
 
258
- if (this.diff !== null && len >= this.want.end - this.want.start) {
259
- return false
260
- }
261
-
262
260
  this.ancestors = start
263
261
  this.diff = diff
264
262
 
package/lib/replicator.js CHANGED
@@ -2,6 +2,7 @@ const b4a = require('b4a')
2
2
  const safetyCatch = require('safety-catch')
3
3
  const RandomIterator = require('random-array-iterator')
4
4
  const RemoteBitfield = require('./remote-bitfield')
5
+ const { REQUEST_CANCELLED, INVALID_CAPABILITY, SNAPSHOT_NOT_AVAILABLE } = require('./errors')
5
6
  const m = require('./messages')
6
7
  const caps = require('./caps')
7
8
 
@@ -19,6 +20,7 @@ class Attachable {
19
20
  session,
20
21
  sindex: 0,
21
22
  rindex: 0,
23
+ snapshot: true,
22
24
  resolve: null,
23
25
  reject: null,
24
26
  promise: null
@@ -34,11 +36,11 @@ class Attachable {
34
36
  return r
35
37
  }
36
38
 
37
- detach (r) {
39
+ detach (r, err = null) {
38
40
  if (r.context !== this) return false
39
41
 
40
42
  this._detach(r)
41
- this._cancel(r)
43
+ this._cancel(r, err)
42
44
  this.gc()
43
45
 
44
46
  return true
@@ -60,8 +62,8 @@ class Attachable {
60
62
  if (this.refs.length === 0) this._unref()
61
63
  }
62
64
 
63
- _cancel (r) {
64
- r.reject(new Error('Request cancelled'))
65
+ _cancel (r, err) {
66
+ r.reject(err || REQUEST_CANCELLED())
65
67
  }
66
68
 
67
69
  _unref () {
@@ -84,10 +86,9 @@ class Attachable {
84
86
  }
85
87
 
86
88
  class BlockRequest extends Attachable {
87
- constructor (tracker, fork, index) {
89
+ constructor (tracker, index) {
88
90
  super()
89
91
 
90
- this.fork = fork
91
92
  this.index = index
92
93
  this.inflight = []
93
94
  this.queued = false
@@ -96,15 +97,14 @@ class BlockRequest extends Attachable {
96
97
 
97
98
  _unref () {
98
99
  if (this.inflight.length > 0) return
99
- this.tracker.remove(this.fork, this.index)
100
+ this.tracker.remove(this.index)
100
101
  }
101
102
  }
102
103
 
103
104
  class RangeRequest extends Attachable {
104
- constructor (ranges, fork, start, end, linear, blocks) {
105
+ constructor (ranges, start, end, linear, blocks) {
105
106
  super()
106
107
 
107
- this.fork = fork
108
108
  this.start = start
109
109
  this.end = end
110
110
  this.linear = linear
@@ -149,10 +149,9 @@ class UpgradeRequest extends Attachable {
149
149
  }
150
150
 
151
151
  class SeekRequest extends Attachable {
152
- constructor (seeks, fork, seeker) {
152
+ constructor (seeks, seeker) {
153
153
  super()
154
154
 
155
- this.fork = fork
156
155
  this.seeker = seeker
157
156
  this.inflight = []
158
157
  this.seeks = seeks
@@ -200,87 +199,40 @@ class InflightTracker {
200
199
  }
201
200
 
202
201
  class BlockTracker {
203
- constructor (core) {
204
- this._core = core
205
- this._fork = core.tree.fork
206
-
207
- this._indexed = new Map()
208
- this._additional = []
202
+ constructor () {
203
+ this._map = new Map()
209
204
  }
210
205
 
211
- * [Symbol.iterator] () {
212
- yield * this._indexed.values()
213
- yield * this._additional
206
+ [Symbol.iterator] () {
207
+ return this._map.values()
214
208
  }
215
209
 
216
210
  isEmpty () {
217
- return this._indexed.size === 0 && this._additional.length === 0
211
+ return this._map.size === 0
218
212
  }
219
213
 
220
- has (fork, index) {
221
- return this.get(fork, index) !== null
214
+ has (index) {
215
+ return this._map.has(index)
222
216
  }
223
217
 
224
- get (fork, index) {
225
- if (this._fork === fork) return this._indexed.get(index) || null
226
- for (const b of this._additional) {
227
- if (b.index === index && b.fork === fork) return b
228
- }
229
- return null
218
+ get (index) {
219
+ return this._map.get(index) || null
230
220
  }
231
221
 
232
- add (fork, index) {
233
- // TODO: just rely on someone calling .update(fork) instead
234
- if (this._fork !== this._core.tree.fork) this.update(this._core.tree.fork)
235
-
236
- let b = this.get(fork, index)
222
+ add (index) {
223
+ let b = this._map.get(index)
237
224
  if (b) return b
238
225
 
239
- b = new BlockRequest(this, fork, index)
240
-
241
- if (fork === this._fork) this._indexed.set(index, b)
242
- else this._additional.push(b)
226
+ b = new BlockRequest(this, index)
227
+ this._map.set(index, b)
243
228
 
244
229
  return b
245
230
  }
246
231
 
247
- remove (fork, index) {
248
- if (this._fork === fork) {
249
- const b = this._indexed.get(index)
250
- this._indexed.delete(index)
251
- return b || null
252
- }
253
-
254
- for (let i = 0; i < this._additional.length; i++) {
255
- const b = this._additional[i]
256
- if (b.index !== index || b.fork !== fork) continue
257
- if (i === this._additional.length - 1) this._additional.pop()
258
- else this._additional[i] = this._additional.pop()
259
- return b
260
- }
261
-
262
- return null
263
- }
264
-
265
- update (fork) {
266
- if (this._fork === fork) return
267
-
268
- const additional = this._additional
269
- this._additional = []
270
-
271
- for (const b of this._indexed.values()) {
272
- // TODO: this is only needed cause we hot patch the fork ids below, revert that later
273
- if (b.fork !== this._fork) additional.push(b)
274
- else this._additional.push(b)
275
- }
276
- this._indexed.clear()
277
-
278
- for (const b of additional) {
279
- if (b.fork === fork) this._indexed.set(b.index, b)
280
- else this._additional.push(b)
281
- }
282
-
283
- this._fork = fork
232
+ remove (index) {
233
+ const b = this.get(index)
234
+ this._map.delete(index)
235
+ return b
284
236
  }
285
237
  }
286
238
 
@@ -290,6 +242,7 @@ class Peer {
290
242
  this.replicator = replicator
291
243
  this.stream = protomux.stream
292
244
  this.protomux = protomux
245
+ this.remotePublicKey = this.stream.remotePublicKey
293
246
 
294
247
  this.channel = channel
295
248
  this.channel.userData = this
@@ -387,7 +340,7 @@ class Peer {
387
340
  const expected = caps.replicate(this.stream.isInitiator === false, this.replicator.key, this.stream.handshakeHash)
388
341
 
389
342
  if (b4a.equals(capability, expected) !== true) { // TODO: change this to a rejection instead, less leakage
390
- throw new Error('Remote sent an invalid capability')
343
+ throw INVALID_CAPABILITY('Remote sent an invalid replication capability')
391
344
  }
392
345
 
393
346
  if (this.remoteOpened === true) return
@@ -425,7 +378,7 @@ class Peer {
425
378
 
426
379
  async onsync ({ fork, length, remoteLength, canUpgrade, uploading, downloading }) {
427
380
  const lengthChanged = length !== this.remoteLength
428
- const sameFork = (fork === this.core.tree.fork)
381
+ const sameFork = fork === this.core.tree.fork
429
382
 
430
383
  this.remoteSynced = true
431
384
  this.remoteFork = fork
@@ -468,12 +421,11 @@ class Peer {
468
421
  }
469
422
 
470
423
  async _updateCanUpgradeAndSync () {
471
- const len = this.core.tree.length
472
- const fork = this.core.tree.fork
424
+ const { length, fork } = this.core.tree
473
425
 
474
426
  const canUpgrade = await this._canUpgrade(this.remoteLength, this.remoteFork)
475
427
 
476
- if (this.syncsProcessing > 0 || len !== this.core.tree.length || fork !== this.core.tree.fork) {
428
+ if (this.syncsProcessing > 0 || length !== this.core.tree.length || fork !== this.core.tree.fork) {
477
429
  return
478
430
  }
479
431
  if (canUpgrade === this.canUpgrade) {
@@ -632,19 +584,19 @@ class Peer {
632
584
  this.replicator.updatePeer(this)
633
585
  }
634
586
 
635
- _makeRequest (fork, needsUpgrade) {
587
+ _makeRequest (needsUpgrade) {
636
588
  if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
637
589
  return null
638
590
  }
639
591
 
640
- if (needsUpgrade === false && fork === this.core.tree.fork && this.replicator._autoUpgrade(this) === true) {
592
+ if (needsUpgrade === false && this.replicator._autoUpgrade(this) === true) {
641
593
  needsUpgrade = true
642
594
  }
643
595
 
644
596
  return {
645
597
  peer: this,
646
598
  id: 0,
647
- fork,
599
+ fork: this.remoteFork,
648
600
  block: null,
649
601
  hash: null,
650
602
  seek: null,
@@ -655,7 +607,7 @@ class Peer {
655
607
  }
656
608
 
657
609
  _requestUpgrade (u) {
658
- const req = this._makeRequest(u.fork, true)
610
+ const req = this._makeRequest(true)
659
611
  if (req === null) return false
660
612
 
661
613
  this._send(req)
@@ -664,8 +616,12 @@ class Peer {
664
616
  }
665
617
 
666
618
  _requestSeek (s) {
667
- if (s.seeker.start >= this.core.tree.length) {
668
- const req = this._makeRequest(s.fork, true)
619
+ const { length, fork } = this.core.tree
620
+
621
+ if (fork !== this.remoteFork) return false
622
+
623
+ if (s.seeker.start >= length) {
624
+ const req = this._makeRequest(true)
669
625
 
670
626
  // We need an upgrade for the seek, if non can be provided, skip
671
627
  if (req === null) return false
@@ -689,14 +645,14 @@ class Peer {
689
645
  if (this.core.bitfield.get(index) === true) continue
690
646
 
691
647
  // Check if this block is currently inflight - if so pick another
692
- const b = this.replicator._blocks.get(s.fork, index)
648
+ const b = this.replicator._blocks.get(index)
693
649
  if (b !== null && b.inflight.length > 0) continue
694
650
 
695
651
  // Block is not inflight, but we only want the hash, check if that is inflight
696
- const h = this.replicator._hashes.add(s.fork, index)
652
+ const h = this.replicator._hashes.add(index)
697
653
  if (h.inflight.length > 0) continue
698
654
 
699
- const req = this._makeRequest(s.fork, false)
655
+ const req = this._makeRequest(false)
700
656
 
701
657
  req.hash = { index: 2 * index, nodes: 0 }
702
658
  req.seek = { bytes: s.seeker.bytes }
@@ -717,9 +673,11 @@ class Peer {
717
673
  }
718
674
 
719
675
  _requestBlock (b) {
720
- if (this.remoteBitfield.get(b.index) === false) return false
676
+ const { length, fork } = this.core.tree
677
+
678
+ if (this.remoteBitfield.get(b.index) === false || fork !== this.remoteFork) return false
721
679
 
722
- const req = this._makeRequest(b.fork, b.index >= this.core.tree.length)
680
+ const req = this._makeRequest(b.index >= length)
723
681
  if (req === null) return false
724
682
 
725
683
  req.block = { index: b.index, nodes: 0 }
@@ -731,8 +689,10 @@ class Peer {
731
689
  }
732
690
 
733
691
  _requestRange (r) {
692
+ const { length, fork } = this.core.tree
693
+
734
694
  const end = Math.min(r.end === -1 ? this.remoteLength : r.end, this.remoteLength)
735
- if (end < r.start || r.fork !== this.remoteFork) return false
695
+ if (end < r.start || fork !== this.remoteFork) return false
736
696
 
737
697
  const len = end - r.start
738
698
  const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))
@@ -750,10 +710,10 @@ class Peer {
750
710
  if (this.remoteBitfield.get(index) === false) continue
751
711
  if (this.core.bitfield.get(index) === true) continue
752
712
 
753
- const b = this.replicator._blocks.add(r.fork, index)
713
+ const b = this.replicator._blocks.add(index)
754
714
  if (b.inflight.length > 0) continue
755
715
 
756
- const req = this._makeRequest(r.fork, index >= this.core.tree.length)
716
+ const req = this._makeRequest(index >= length)
757
717
 
758
718
  // If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
759
719
  if (req === null) {
@@ -773,7 +733,7 @@ class Peer {
773
733
  }
774
734
 
775
735
  _requestForkProof (f) {
776
- const req = this._makeRequest(f.fork, false)
736
+ const req = this._makeRequest(false)
777
737
 
778
738
  req.upgrade = { start: 0, length: this.remoteLength }
779
739
 
@@ -796,7 +756,7 @@ class Peer {
796
756
 
797
757
  if (this.remoteBitfield.get(index) === false) continue
798
758
 
799
- const req = this._makeRequest(f.fork, false)
759
+ const req = this._makeRequest(false)
800
760
 
801
761
  req.hash = { index: 2 * index, nodes: f.batch.want.nodes }
802
762
 
@@ -821,8 +781,12 @@ class Peer {
821
781
  }
822
782
 
823
783
  try {
824
- if (req.block !== null && req.fork === fork) req.block.nodes = await this.core.tree.missingNodes(2 * req.block.index)
825
- if (req.hash !== null && req.fork === fork) req.hash.nodes = await this.core.tree.missingNodes(req.hash.index)
784
+ if (req.block !== null && req.fork === fork) {
785
+ req.block.nodes = await this.core.tree.missingNodes(2 * req.block.index)
786
+ }
787
+ if (req.hash !== null && req.fork === fork) {
788
+ req.hash.nodes = await this.core.tree.missingNodes(req.hash.index)
789
+ }
826
790
  } catch (err) {
827
791
  this.stream.destroy(err)
828
792
  return
@@ -845,8 +809,8 @@ module.exports = class Replicator {
845
809
  this.findingPeers = 0 // updateable from the outside
846
810
 
847
811
  this._inflight = new InflightTracker()
848
- this._blocks = new BlockTracker(core)
849
- this._hashes = new BlockTracker(core)
812
+ this._blocks = new BlockTracker()
813
+ this._hashes = new BlockTracker()
850
814
 
851
815
  this._queued = []
852
816
 
@@ -869,14 +833,34 @@ module.exports = class Replicator {
869
833
  for (const peer of this.peers) peer.protomux.uncork()
870
834
  }
871
835
 
872
- broadcastRange (start, length, drop = false) {
836
+ // Called externally when a range of new blocks has been processed/removed
837
+ onhave (start, length, drop = false) {
873
838
  for (const peer of this.peers) peer.broadcastRange(start, length, drop)
874
839
  }
875
840
 
876
- localUpgrade () {
841
+ // Called externally when a truncation upgrade has been processed
842
+ ontruncate (newLength) {
843
+ const notify = []
844
+
845
+ for (const blk of this._blocks) {
846
+ if (blk.index < newLength) continue
847
+ notify.push(blk)
848
+ }
849
+
850
+ for (const blk of notify) {
851
+ for (const r of blk.refs) {
852
+ if (r.snapshot === false) continue
853
+ blk.detach(r, SNAPSHOT_NOT_AVAILABLE())
854
+ }
855
+ }
856
+ }
857
+
858
+ // Called externally when a upgrade has been processed
859
+ onupgrade () {
877
860
  for (const peer of this.peers) peer.signalUpgrade()
878
861
  if (this._blocks.isEmpty() === false) this._resolveBlocksLocally()
879
862
  if (this._upgrade !== null) this._resolveUpgradeRequest(null)
863
+ if (this._ranges.length !== 0 || this._seeks.length !== 0) this._updateNonPrimary()
880
864
  }
881
865
 
882
866
  addUpgrade (session) {
@@ -901,8 +885,8 @@ module.exports = class Replicator {
901
885
  return ref
902
886
  }
903
887
 
904
- addBlock (session, index, fork = this.core.tree.fork) {
905
- const b = this._blocks.add(fork, index)
888
+ addBlock (session, index) {
889
+ const b = this._blocks.add(index)
906
890
  const ref = b.attach(session)
907
891
 
908
892
  this._queueBlock(b)
@@ -912,7 +896,7 @@ module.exports = class Replicator {
912
896
  }
913
897
 
914
898
  addSeek (session, seeker) {
915
- const s = new SeekRequest(this._seeks, this.core.tree.fork, seeker)
899
+ const s = new SeekRequest(this._seeks, seeker)
916
900
  const ref = s.attach(session)
917
901
 
918
902
  this._seeks.push(s)
@@ -927,7 +911,7 @@ module.exports = class Replicator {
927
911
  if (length === -1 || start + length > blocks.length) length = blocks.length - start
928
912
  }
929
913
 
930
- const r = new RangeRequest(this._ranges, this.core.tree.fork, start, length === -1 ? -1 : start + length, linear, blocks)
914
+ const r = new RangeRequest(this._ranges, start, length === -1 ? -1 : start + length, linear, blocks)
931
915
  const ref = r.attach(session)
932
916
 
933
917
  this._ranges.push(r)
@@ -940,13 +924,13 @@ module.exports = class Replicator {
940
924
  }
941
925
 
942
926
  cancel (ref) {
943
- ref.context.detach(ref)
927
+ ref.context.detach(ref, null)
944
928
  }
945
929
 
946
930
  clearRequests (session) {
947
931
  while (session.length > 0) {
948
932
  const ref = session[session.length - 1]
949
- ref.context.detach(ref)
933
+ ref.context.detach(ref, null)
950
934
  }
951
935
 
952
936
  this.updateAll()
@@ -1046,7 +1030,7 @@ module.exports = class Replicator {
1046
1030
  }
1047
1031
 
1048
1032
  _autoUpgrade (peer) {
1049
- return this._upgrade !== null && this._shouldUpgrade(peer)
1033
+ return this._upgrade !== null && peer.remoteFork === this.core.tree.fork && this._shouldUpgrade(peer)
1050
1034
  }
1051
1035
 
1052
1036
  _addPeer (peer) {
@@ -1099,12 +1083,12 @@ module.exports = class Replicator {
1099
1083
  // Currently the block tracker does not support deletes during iteration, so we make
1100
1084
  // sure to clear them afterwards.
1101
1085
  for (const b of clear) {
1102
- this._blocks.remove(b.fork, b.index)
1086
+ this._blocks.remove(b.index)
1103
1087
  }
1104
1088
  }
1105
1089
 
1106
- _resolveBlockRequest (tracker, fork, index, value, req) {
1107
- const b = tracker.remove(fork, index)
1090
+ _resolveBlockRequest (tracker, index, value, req) {
1091
+ const b = tracker.remove(index)
1108
1092
  if (b === null) return false
1109
1093
 
1110
1094
  removeInflight(b.inflight, req)
@@ -1130,7 +1114,7 @@ module.exports = class Replicator {
1130
1114
  _clearInflightBlock (tracker, req) {
1131
1115
  const isBlock = tracker === this._blocks
1132
1116
  const index = isBlock === true ? req.block.index : req.hash.index / 2
1133
- const b = tracker.get(req.fork, index)
1117
+ const b = tracker.get(index)
1134
1118
 
1135
1119
  if (b === null || removeInflight(b.inflight, req) === false) return
1136
1120
 
@@ -1251,11 +1235,11 @@ module.exports = class Replicator {
1251
1235
 
1252
1236
  _ondata (peer, req, data) {
1253
1237
  if (data.block !== null) {
1254
- this._resolveBlockRequest(this._blocks, data.fork, data.block.index, data.block.value, req)
1238
+ this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
1255
1239
  }
1256
1240
 
1257
1241
  if (data.hash !== null && (data.hash.index & 1) === 0) {
1258
- this._resolveBlockRequest(this._hashes, data.fork, data.hash.index / 2, null, req)
1242
+ this._resolveBlockRequest(this._hashes, data.hash.index / 2, null, req)
1259
1243
  }
1260
1244
 
1261
1245
  if (this._upgrade !== null) {
@@ -1286,14 +1270,14 @@ module.exports = class Replicator {
1286
1270
 
1287
1271
  if (f.batch) {
1288
1272
  await f.batch.update(data)
1289
- } else {
1273
+ } else if (data.upgrade) {
1290
1274
  f.batch = await this.core.tree.reorg(data)
1291
1275
 
1292
1276
  // Remove "older" reorgs in progress as we just verified this one.
1293
1277
  this._clearOldReorgs(f.fork)
1294
1278
  }
1295
1279
 
1296
- if (f.batch.finished) {
1280
+ if (f.batch && f.batch.finished) {
1297
1281
  if (this._addUpgradeMaybe() !== null) {
1298
1282
  await this._applyReorg(f)
1299
1283
  }
@@ -1302,6 +1286,7 @@ module.exports = class Replicator {
1302
1286
  this.updateAll()
1303
1287
  }
1304
1288
 
1289
+ // Never throws, allowed to run in the background
1305
1290
  async _applyReorg (f) {
1306
1291
  // TODO: more optimal here to check if potentially a better reorg
1307
1292
  // is available, ie higher fork, and request that one first.
@@ -1330,13 +1315,10 @@ module.exports = class Replicator {
1330
1315
 
1331
1316
  // TODO: all the remaining is a tmp workaround until we have a flag/way for ANY_FORK
1332
1317
  for (const r of this._ranges) {
1333
- r.fork = this.core.tree.fork
1334
1318
  r.start = r.userStart
1335
1319
  r.end = r.userEnd
1336
1320
  }
1337
- for (const s of this._seeks) s.fork = this.core.tree.fork
1338
- for (const b of this._blocks) b.fork = this.core.tree.fork
1339
- this._blocks.update(this.core.tree.fork)
1321
+
1340
1322
  this.updateAll()
1341
1323
  }
1342
1324
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.36",
3
+ "version": "10.0.0-alpha.39",
4
4
  "description": "Hypercore 10",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -53,7 +53,7 @@
53
53
  "devDependencies": {
54
54
  "brittle": "^2.0.0",
55
55
  "hyperswarm": "next",
56
- "random-access-memory": "^3.1.2",
56
+ "random-access-memory": "^4.1.0",
57
57
  "standard": "^16.0.3",
58
58
  "tmp-promise": "^3.0.2"
59
59
  },