hypercore 10.0.0-alpha.2 → 10.0.0-alpha.6

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.
@@ -1,5 +1,4 @@
1
1
  name: Build Status
2
-
3
2
  on:
4
3
  push:
5
4
  branches:
@@ -11,7 +10,7 @@ jobs:
11
10
  build:
12
11
  strategy:
13
12
  matrix:
14
- node-version: [14.x]
13
+ node-version: [lts/*]
15
14
  os: [ubuntu-latest, macos-latest, windows-latest]
16
15
  runs-on: ${{ matrix.os }}
17
16
  steps:
package/README.md CHANGED
@@ -113,6 +113,7 @@ A range can have the following properties:
113
113
  {
114
114
  start: startIndex,
115
115
  end: nonInclusiveEndIndex,
116
+ blocks: [index1, index2, ...],
116
117
  linear: false // download range linearly and not randomly
117
118
  }
118
119
  ```
@@ -125,6 +126,12 @@ To download the full core continously (often referred to as non sparse mode) do
125
126
  core.download({ start: 0, end: -1 })
126
127
  ```
127
128
 
129
+ To downloaded a discrete range of blocks pass a list of indices.
130
+
131
+ ```js
132
+ core.download({ blocks: [4, 9, 7] });
133
+ ```
134
+
128
135
  To cancel downloading a range simply destroy the range instance.
129
136
 
130
137
  ``` js
package/index.js CHANGED
@@ -3,6 +3,7 @@ const raf = require('random-access-file')
3
3
  const isOptions = require('is-options')
4
4
  const hypercoreCrypto = require('hypercore-crypto')
5
5
  const c = require('compact-encoding')
6
+ const Xache = require('xache')
6
7
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
7
8
  const codecs = require('codecs')
8
9
 
@@ -27,14 +28,17 @@ module.exports = class Hypercore extends EventEmitter {
27
28
  opts = key
28
29
  key = null
29
30
  }
31
+
30
32
  if (key && typeof key === 'string') {
31
33
  key = Buffer.from(key, 'hex')
32
34
  }
33
- if (key && key.byteLength !== 32) {
35
+
36
+ if (!opts) opts = {}
37
+
38
+ if (!opts.crypto && key && key.byteLength !== 32) {
34
39
  throw new Error('Hypercore key should be 32 bytes')
35
40
  }
36
41
 
37
- if (!opts) opts = {}
38
42
  if (!storage) storage = opts.storage
39
43
 
40
44
  this[promises] = true
@@ -44,6 +48,7 @@ module.exports = class Hypercore extends EventEmitter {
44
48
  this.core = null
45
49
  this.replicator = null
46
50
  this.extensions = opts.extensions || new Extensions()
51
+ this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
47
52
 
48
53
  this.valueEncoding = null
49
54
  this.key = key || null
@@ -91,7 +96,7 @@ module.exports = class Hypercore extends EventEmitter {
91
96
  return function createFile (name) {
92
97
  const locked = name === toLock || name.endsWith('/' + toLock)
93
98
  const lock = locked ? fsctl.lock : null
94
- const sparse = locked ? null : fsctl.sparse
99
+ const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
95
100
  return raf(name, { directory, lock, sparse })
96
101
  }
97
102
  }
@@ -281,8 +286,13 @@ module.exports = class Hypercore extends EventEmitter {
281
286
  _oncoreupdate (status, bitfield, value, from) {
282
287
  if (status !== 0) {
283
288
  for (let i = 0; i < this.sessions.length; i++) {
284
- if ((status & 0b10) !== 0) this.sessions[i].emit('truncate', this.core.tree.fork)
285
- if ((status & 0b01) !== 0) this.sessions[i].emit('append')
289
+ if ((status & 0b10) !== 0) {
290
+ if (this.cache) this.cache.clear()
291
+ this.sessions[i].emit('truncate', this.core.tree.fork)
292
+ }
293
+ if ((status & 0b01) !== 0) {
294
+ this.sessions[i].emit('append')
295
+ }
286
296
  }
287
297
 
288
298
  this.replicator.broadcastInfo()
@@ -346,6 +356,15 @@ module.exports = class Hypercore extends EventEmitter {
346
356
 
347
357
  async get (index, opts) {
348
358
  if (this.opened === false) await this.opening
359
+ const c = this.cache && this.cache.get(index)
360
+ if (c) return c
361
+ const fork = this.core.tree.fork
362
+ const b = await this._get(index, opts)
363
+ if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
364
+ return b
365
+ }
366
+
367
+ async _get (index, opts) {
349
368
  const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
350
369
 
351
370
  if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
@@ -355,13 +374,27 @@ module.exports = class Hypercore extends EventEmitter {
355
374
  }
356
375
 
357
376
  download (range) {
358
- const start = (range && range.start) || 0
359
- const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
360
377
  const linear = !!(range && range.linear)
361
378
 
362
- // TODO: support range.blocks
379
+ let start
380
+ let end
381
+ let filter
382
+
383
+ if (range && range.blocks) {
384
+ const blocks = range.blocks instanceof Set
385
+ ? range.blocks
386
+ : new Set(range.blocks)
387
+
388
+ start = range.start || (blocks.size ? min(range.blocks) : 0)
389
+ end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
363
390
 
364
- const r = Replicator.createRange(start, end, linear)
391
+ filter = (i) => blocks.has(i)
392
+ } else {
393
+ start = (range && range.start) || 0
394
+ end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
395
+ }
396
+
397
+ const r = Replicator.createRange(start, end, filter, linear)
365
398
 
366
399
  if (this.opened) this.replicator.addRange(r)
367
400
  else this.opening.then(() => this.replicator.addRange(r), noop)
@@ -443,3 +476,16 @@ function requireMaybe (name) {
443
476
  function toHex (buf) {
444
477
  return buf && buf.toString('hex')
445
478
  }
479
+
480
+ function reduce (iter, fn, acc) {
481
+ for (const item of iter) acc = fn(acc, item)
482
+ return acc
483
+ }
484
+
485
+ function min (arr) {
486
+ return reduce(arr, (a, b) => Math.min(a, b), Infinity)
487
+ }
488
+
489
+ function max (arr) {
490
+ return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
491
+ }
package/lib/protocol.js CHANGED
@@ -125,6 +125,7 @@ class Peer {
125
125
  this.resend = false
126
126
  this.state = state
127
127
  this.extensions = new Map()
128
+ this.destroyed = false
128
129
 
129
130
  this._destroyer = this._safeDestroy.bind(this)
130
131
  }
@@ -229,6 +230,7 @@ class Peer {
229
230
  }
230
231
 
231
232
  destroy (err) {
233
+ this.destroyed = true
232
234
  return this.protocol.unregisterPeer(this, err)
233
235
  }
234
236
  }
@@ -296,7 +298,7 @@ module.exports = class Protocol {
296
298
  peer.remoteAlias = -1
297
299
  }
298
300
 
299
- peer.handlers.onunregister(this, err)
301
+ peer.handlers.onunregister(peer, err)
300
302
 
301
303
  if (err) this.noiseStream.destroy(err)
302
304
  }
package/lib/replicator.js CHANGED
@@ -25,11 +25,12 @@ class InvertedPromise {
25
25
  }
26
26
 
27
27
  class Request {
28
- constructor (index, seek) {
28
+ constructor (index, seek, nodes) {
29
29
  this.peer = null
30
30
  this.index = index
31
31
  this.seek = seek
32
32
  this.value = seek === 0
33
+ this.nodes = nodes
33
34
  this.promises = []
34
35
  }
35
36
 
@@ -119,9 +120,10 @@ class Seek {
119
120
  }
120
121
 
121
122
  class Range {
122
- constructor (start, end, linear) {
123
+ constructor (start, end, filter, linear) {
123
124
  this.start = start
124
125
  this.end = end
126
+ this.filter = filter
125
127
  this.linear = !!linear
126
128
  this.promise = null
127
129
  this.done = false
@@ -144,7 +146,7 @@ class Range {
144
146
  }
145
147
 
146
148
  for (; this._start < this.end; this._start++) {
147
- if (!bitfield.get(this._start)) return false
149
+ if (this.filter(this._start) && !bitfield.get(this._start)) return false
148
150
  }
149
151
 
150
152
  return true
@@ -278,7 +280,9 @@ class RequestPool {
278
280
 
279
281
  _updateSeek (peer, seek) {
280
282
  if (seek.request) return false
281
- seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes)
283
+ // We have to snapshot the nodes here now, due to the caching of the request...
284
+ const nodes = log2(seek.seeker.end - seek.seeker.start)
285
+ seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes, nodes)
282
286
  return seek.request !== null
283
287
  }
284
288
 
@@ -296,10 +300,13 @@ class RequestPool {
296
300
  const end = range.end === -1 ? peer.state.length : range.end
297
301
  if (end <= range._start) return false
298
302
 
299
- if (range.linear) return !!this._requestRange(peer, range._start, end, 0)
303
+ if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0, range.filter)
300
304
 
301
305
  const r = range._start + Math.floor(Math.random() * (end - range._start))
302
- return !!(this._requestRange(peer, r, end, 0) || this._requestRange(peer, range._start, r, 0))
306
+ return !!(
307
+ this._requestRange(peer, r, end, 0, 0, range.filter) ||
308
+ this._requestRange(peer, range._start, r, 0, 0, range.filter)
309
+ )
303
310
  }
304
311
 
305
312
  _updateUpgrade (peer) {
@@ -328,7 +335,7 @@ class RequestPool {
328
335
  this.pendingUpgrade = null
329
336
  }
330
337
 
331
- _requestRange (peer, start, end, seek) {
338
+ _requestRange (peer, start, end, seek, nodes, filter = tautology) {
332
339
  const remote = peer.state.bitfield
333
340
  const local = this.core.bitfield
334
341
 
@@ -336,12 +343,12 @@ class RequestPool {
336
343
  if (end === -1) end = peer.state.length
337
344
 
338
345
  for (let i = start; i < end; i++) {
339
- if (!remote.get(i) || local.get(i)) continue
346
+ if (!filter(i) || !remote.get(i) || local.get(i)) continue
340
347
  // TODO: if this was a NO_VALUE request, retry if no blocks can be found elsewhere
341
348
  if (this.requests.has(i)) continue
342
349
 
343
350
  // TODO: if seeking and i >= core.length, let that takes precendance in the upgrade req
344
- const req = new Request(i, i < this.core.tree.length ? seek : 0)
351
+ const req = new Request(i, i < this.core.tree.length ? seek : 0, nodes)
345
352
  this.requests.set(i, req)
346
353
  this.send(peer, req)
347
354
  return req
@@ -390,16 +397,28 @@ class RequestPool {
390
397
 
391
398
  if (data.block.index < this.core.tree.length || this.core.truncating > 0) {
392
399
  try {
393
- data.block.nodes = await this.core.tree.nodes(data.block.index * 2)
400
+ data.block.nodes = Math.max(req.nodes, await this.core.tree.nodes(data.block.index * 2))
394
401
  } catch (err) {
395
402
  console.error('TODO handle me:', err.stack)
396
403
  }
397
404
  }
398
405
 
406
+ if (peer.destroyed) {
407
+ req.peer = null
408
+ this.pending.push(req)
409
+ if (upgrading) {
410
+ this.upgrading.resolve()
411
+ this.upgrading = null
412
+ }
413
+ this.replicator.updateAll()
414
+ return
415
+ }
416
+
399
417
  if (fork !== this.core.tree.fork || paused(peer, this.core.tree.fork) || this.core.truncating > 0) {
400
418
  if (peer.state.inflight > 0) peer.state.inflight--
401
419
  if (req.promises.length) { // someone is eagerly waiting for this request
402
420
  req.peer = null // resend on some other peer
421
+ this.pending.push(req)
403
422
  } else { // otherwise delete the request
404
423
  this.requests.delete(req.index)
405
424
  }
@@ -594,7 +613,7 @@ class RequestPool {
594
613
  return e.createPromise()
595
614
  }
596
615
 
597
- const r = new Request(index, 0)
616
+ const r = new Request(index, 0, 0)
598
617
 
599
618
  this.requests.set(index, r)
600
619
  this.pending.push(r)
@@ -662,8 +681,18 @@ module.exports = class Replicator {
662
681
  return promise
663
682
  }
664
683
 
665
- static createRange (start, end, linear) {
666
- return new Range(start, end, linear)
684
+ static createRange (start, end, filter, linear) {
685
+ // createRange(start, end)
686
+ if (filter === undefined) {
687
+ filter = tautology
688
+
689
+ // createRange(start, end, linear)
690
+ } else if (typeof filter === 'boolean') {
691
+ linear = filter
692
+ filter = tautology
693
+ }
694
+
695
+ return new Range(start, end, filter, linear)
667
696
  }
668
697
 
669
698
  addRange (range) {
@@ -810,3 +839,18 @@ function pages (core) {
810
839
  }
811
840
 
812
841
  function noop () {}
842
+
843
+ function tautology () {
844
+ return true
845
+ }
846
+
847
+ function log2 (n) {
848
+ let res = 1
849
+
850
+ while (n > 2) {
851
+ n /= 2
852
+ res++
853
+ }
854
+
855
+ return res
856
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.2",
3
+ "version": "10.0.0-alpha.6",
4
4
  "description": "Hypercore 10",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -39,7 +39,8 @@
39
39
  "random-access-file": "^2.1.4",
40
40
  "random-array-iterator": "^1.0.0",
41
41
  "safety-catch": "^1.0.1",
42
- "uint64le": "^1.0.0"
42
+ "uint64le": "^1.0.0",
43
+ "xache": "^1.0.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "brittle": "^1.4.1",
package/test/basic.js CHANGED
@@ -76,3 +76,15 @@ test('storage options', async function (t) {
76
76
  t.alike(await core.get(0), Buffer.from('hello'))
77
77
  t.end()
78
78
  })
79
+
80
+ test(
81
+ 'allow publicKeys with different byteLength that 32, if opts.crypto were passed',
82
+ function (t) {
83
+ const key = Buffer.alloc(33).fill('a')
84
+
85
+ const core = new Hypercore(ram, key, { crypto: {} })
86
+
87
+ t.is(core.key, key)
88
+ t.pass('creating a core with more than 32 byteLength key did not throw')
89
+ }
90
+ )
package/test/replicate.js CHANGED
@@ -294,3 +294,79 @@ test('multiplexing multiple times over the same stream', async function (t) {
294
294
  t.is(b1.length, a1.length, 'same length')
295
295
  t.end()
296
296
  })
297
+
298
+ test('destroying a stream and re-replicating works', async function (t) {
299
+ const core = await create()
300
+
301
+ while (core.length < 33) await core.append(Buffer.from('#' + core.length))
302
+
303
+ const clone = await create(core.key)
304
+
305
+ let s1 = core.replicate(true)
306
+ let s2 = clone.replicate(false)
307
+
308
+ s1.pipe(s2).pipe(s1)
309
+
310
+ await s2.opened
311
+
312
+ const all = []
313
+ for (let i = 0; i < 33; i++) {
314
+ all.push(clone.get(i))
315
+ }
316
+
317
+ clone.once('download', function () {
318
+ // simulate stream failure in the middle of bulk downloading
319
+ s1.destroy()
320
+ })
321
+
322
+ await new Promise((resolve) => s1.once('close', resolve))
323
+
324
+ // retry
325
+ s1 = core.replicate(true)
326
+ s2 = clone.replicate(false)
327
+
328
+ s1.pipe(s2).pipe(s1)
329
+
330
+ const blocks = await Promise.all(all)
331
+
332
+ t.is(blocks.length, 33, 'downloaded 33 blocks')
333
+ })
334
+
335
+ test('replicate discrete range', async function (t) {
336
+ const a = await create()
337
+
338
+ await a.append(['a', 'b', 'c', 'd', 'e'])
339
+
340
+ const b = await create(a.key)
341
+
342
+ let d = 0
343
+ b.on('download', () => d++)
344
+
345
+ replicate(a, b, t)
346
+
347
+ const r = b.download({ blocks: [0, 2, 3] })
348
+ await r.downloaded()
349
+
350
+ t.is(d, 3)
351
+ t.alike(await b.get(0), Buffer.from('a'))
352
+ t.alike(await b.get(2), Buffer.from('c'))
353
+ t.alike(await b.get(3), Buffer.from('d'))
354
+ })
355
+
356
+ test('replicate discrete empty range', async function (t) {
357
+ const a = await create()
358
+
359
+ await a.append(['a', 'b', 'c', 'd', 'e'])
360
+
361
+ const b = await create(a.key)
362
+
363
+ let d = 0
364
+ b.on('download', () => d++)
365
+
366
+ replicate(a, b, t)
367
+
368
+ const r = b.download({ blocks: [] })
369
+ await r.downloaded()
370
+
371
+ t.is(d, 0)
372
+ })