hypercore 10.0.0-alpha.0 → 10.0.0-alpha.12

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/lib/protocol.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const { uint, from: fromEncoding } = require('compact-encoding')
2
+ const b4a = require('b4a')
2
3
  const safetyCatch = require('safety-catch')
3
4
  const codecs = require('codecs')
4
5
 
@@ -125,6 +126,7 @@ class Peer {
125
126
  this.resend = false
126
127
  this.state = state
127
128
  this.extensions = new Map()
129
+ this.destroyed = false
128
130
 
129
131
  this._destroyer = this._safeDestroy.bind(this)
130
132
  }
@@ -229,6 +231,7 @@ class Peer {
229
231
  }
230
232
 
231
233
  destroy (err) {
234
+ this.destroyed = true
232
235
  return this.protocol.unregisterPeer(this, err)
233
236
  }
234
237
  }
@@ -283,20 +286,20 @@ module.exports = class Protocol {
283
286
 
284
287
  registerPeer (key, discoveryKey, handlers = {}, state = null) {
285
288
  const peer = new Peer(this, this._localAliases++, key, discoveryKey, handlers, state)
286
- this._peers.set(discoveryKey.toString('hex'), peer)
289
+ this._peers.set(b4a.toString(discoveryKey, 'hex'), peer)
287
290
  this._announceCore(peer.alias, key, discoveryKey)
288
291
  return peer
289
292
  }
290
293
 
291
294
  unregisterPeer (peer, err) {
292
- this._peers.delete(peer.discoveryKey.toString('hex'))
295
+ this._peers.delete(b4a.toString(peer.discoveryKey, 'hex'))
293
296
 
294
297
  if (peer.remoteAlias > -1) {
295
298
  this._remoteAliases[peer.remoteAlias] = null
296
299
  peer.remoteAlias = -1
297
300
  }
298
301
 
299
- peer.handlers.onunregister(this, err)
302
+ peer.handlers.onunregister(peer, err)
300
303
 
301
304
  if (err) this.noiseStream.destroy(err)
302
305
  }
@@ -372,7 +375,7 @@ module.exports = class Protocol {
372
375
  this.send(2, messages.core, -1, {
373
376
  alias: alias,
374
377
  discoveryKey: discoveryKey,
375
- capability: Buffer.alloc(32) // TODO
378
+ capability: b4a.alloc(32) // TODO
376
379
  })
377
380
  }
378
381
 
@@ -441,7 +444,7 @@ module.exports = class Protocol {
441
444
  }
442
445
 
443
446
  _oncore (m) {
444
- const hex = m.discoveryKey.toString('hex')
447
+ const hex = b4a.toString(m.discoveryKey, 'hex')
445
448
  const peer = this._peers.get(hex)
446
449
 
447
450
  // allow one alloc
@@ -477,7 +480,7 @@ module.exports = class Protocol {
477
480
  }
478
481
 
479
482
  _onunknowncore (m) {
480
- const peer = this._peers.get(m.discoveryKey.toString('hex'))
483
+ const peer = this._peers.get(b4a.toString(m.discoveryKey, 'hex'))
481
484
  if (!peer) return
482
485
 
483
486
  peer.resend = true
package/lib/replicator.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const Protocol = require('./protocol')
2
2
  const RemoteBitfield = require('./remote-bitfield')
3
3
  const RandomIterator = require('random-array-iterator')
4
+ const b4a = require('b4a')
4
5
 
5
6
  const PKG = require('../package.json')
6
7
  const USER_AGENT = PKG.name + '/' + PKG.version + '@nodejs'
@@ -25,11 +26,12 @@ class InvertedPromise {
25
26
  }
26
27
 
27
28
  class Request {
28
- constructor (index, seek) {
29
+ constructor (index, seek, nodes) {
29
30
  this.peer = null
30
31
  this.index = index
31
32
  this.seek = seek
32
33
  this.value = seek === 0
34
+ this.nodes = nodes
33
35
  this.promises = []
34
36
  }
35
37
 
@@ -119,9 +121,10 @@ class Seek {
119
121
  }
120
122
 
121
123
  class Range {
122
- constructor (start, end, linear) {
124
+ constructor (start, end, filter, linear) {
123
125
  this.start = start
124
126
  this.end = end
127
+ this.filter = filter
125
128
  this.linear = !!linear
126
129
  this.promise = null
127
130
  this.done = false
@@ -144,7 +147,7 @@ class Range {
144
147
  }
145
148
 
146
149
  for (; this._start < this.end; this._start++) {
147
- if (!bitfield.get(this._start)) return false
150
+ if (this.filter(this._start) && !bitfield.get(this._start)) return false
148
151
  }
149
152
 
150
153
  return true
@@ -278,7 +281,9 @@ class RequestPool {
278
281
 
279
282
  _updateSeek (peer, seek) {
280
283
  if (seek.request) return false
281
- seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes)
284
+ // We have to snapshot the nodes here now, due to the caching of the request...
285
+ const nodes = log2(seek.seeker.end - seek.seeker.start)
286
+ seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes, nodes)
282
287
  return seek.request !== null
283
288
  }
284
289
 
@@ -296,10 +301,13 @@ class RequestPool {
296
301
  const end = range.end === -1 ? peer.state.length : range.end
297
302
  if (end <= range._start) return false
298
303
 
299
- if (range.linear) return !!this._requestRange(peer, range._start, end, 0)
304
+ if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0, range.filter)
300
305
 
301
306
  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))
307
+ return !!(
308
+ this._requestRange(peer, r, end, 0, 0, range.filter) ||
309
+ this._requestRange(peer, range._start, r, 0, 0, range.filter)
310
+ )
303
311
  }
304
312
 
305
313
  _updateUpgrade (peer) {
@@ -328,7 +336,7 @@ class RequestPool {
328
336
  this.pendingUpgrade = null
329
337
  }
330
338
 
331
- _requestRange (peer, start, end, seek) {
339
+ _requestRange (peer, start, end, seek, nodes, filter = tautology) {
332
340
  const remote = peer.state.bitfield
333
341
  const local = this.core.bitfield
334
342
 
@@ -336,12 +344,12 @@ class RequestPool {
336
344
  if (end === -1) end = peer.state.length
337
345
 
338
346
  for (let i = start; i < end; i++) {
339
- if (!remote.get(i) || local.get(i)) continue
347
+ if (!filter(i) || !remote.get(i) || local.get(i)) continue
340
348
  // TODO: if this was a NO_VALUE request, retry if no blocks can be found elsewhere
341
349
  if (this.requests.has(i)) continue
342
350
 
343
351
  // 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)
352
+ const req = new Request(i, i < this.core.tree.length ? seek : 0, nodes)
345
353
  this.requests.set(i, req)
346
354
  this.send(peer, req)
347
355
  return req
@@ -390,16 +398,28 @@ class RequestPool {
390
398
 
391
399
  if (data.block.index < this.core.tree.length || this.core.truncating > 0) {
392
400
  try {
393
- data.block.nodes = await this.core.tree.nodes(data.block.index * 2)
401
+ data.block.nodes = Math.max(req.nodes, await this.core.tree.nodes(data.block.index * 2))
394
402
  } catch (err) {
395
403
  console.error('TODO handle me:', err.stack)
396
404
  }
397
405
  }
398
406
 
407
+ if (peer.destroyed) {
408
+ req.peer = null
409
+ this.pending.push(req)
410
+ if (upgrading) {
411
+ this.upgrading.resolve()
412
+ this.upgrading = null
413
+ }
414
+ this.replicator.updateAll()
415
+ return
416
+ }
417
+
399
418
  if (fork !== this.core.tree.fork || paused(peer, this.core.tree.fork) || this.core.truncating > 0) {
400
419
  if (peer.state.inflight > 0) peer.state.inflight--
401
420
  if (req.promises.length) { // someone is eagerly waiting for this request
402
421
  req.peer = null // resend on some other peer
422
+ this.pending.push(req)
403
423
  } else { // otherwise delete the request
404
424
  this.requests.delete(req.index)
405
425
  }
@@ -594,7 +614,7 @@ class RequestPool {
594
614
  return e.createPromise()
595
615
  }
596
616
 
597
- const r = new Request(index, 0)
617
+ const r = new Request(index, 0, 0)
598
618
 
599
619
  this.requests.set(index, r)
600
620
  this.pending.push(r)
@@ -662,8 +682,18 @@ module.exports = class Replicator {
662
682
  return promise
663
683
  }
664
684
 
665
- static createRange (start, end, linear) {
666
- return new Range(start, end, linear)
685
+ static createRange (start, end, filter, linear) {
686
+ // createRange(start, end)
687
+ if (filter === undefined) {
688
+ filter = tautology
689
+
690
+ // createRange(start, end, linear)
691
+ } else if (typeof filter === 'boolean') {
692
+ linear = filter
693
+ filter = tautology
694
+ }
695
+
696
+ return new Range(start, end, filter, linear)
667
697
  }
668
698
 
669
699
  addRange (range) {
@@ -750,8 +780,8 @@ module.exports = class Replicator {
750
780
 
751
781
  onbitfield ({ start, bitfield }, peer) {
752
782
  if (bitfield.length < 1024) {
753
- const buf = Buffer.from(bitfield.buffer, bitfield.byteOffset, bitfield.byteLength)
754
- const bigger = Buffer.concat([buf, Buffer.alloc(4096 - buf.length)])
783
+ const buf = b4a.from(bitfield.buffer, bitfield.byteOffset, bitfield.byteLength)
784
+ const bigger = b4a.concat([buf, b4a.alloc(4096 - buf.length)])
755
785
  bitfield = new Uint32Array(bigger.buffer, bigger.byteOffset, 1024)
756
786
  }
757
787
  peer.state.bitfield.pages.set(start, bitfield)
@@ -810,3 +840,18 @@ function pages (core) {
810
840
  }
811
841
 
812
842
  function noop () {}
843
+
844
+ function tautology () {
845
+ return true
846
+ }
847
+
848
+ function log2 (n) {
849
+ let res = 1
850
+
851
+ while (n > 2) {
852
+ n /= 2
853
+ res++
854
+ }
855
+
856
+ return res
857
+ }
package/lib/streams.js ADDED
@@ -0,0 +1,39 @@
1
+ const { Readable } = require('streamx')
2
+
3
+ class ReadStream extends Readable {
4
+ constructor (core, opts = {}) {
5
+ super()
6
+
7
+ this.core = core
8
+ this.start = opts.start || 0
9
+ this.end = typeof opts.end === 'number' ? opts.end : -1
10
+ this.snapshot = !opts.live && opts.snapshot !== false
11
+ this.live = !!opts.live
12
+ }
13
+
14
+ _open (cb) {
15
+ this._openP().then(cb, cb)
16
+ }
17
+
18
+ _read (cb) {
19
+ this._readP().then(cb, cb)
20
+ }
21
+
22
+ async _openP () {
23
+ if (this.end === -1) await this.core.update()
24
+ else await this.core.ready()
25
+ if (this.snapshot && this.end === -1) this.end = this.core.length
26
+ }
27
+
28
+ async _readP () {
29
+ const end = this.live ? -1 : (this.end === -1 ? this.core.length : this.end)
30
+ if (end >= 0 && this.start >= end) {
31
+ this.push(null)
32
+ return
33
+ }
34
+
35
+ this.push(await this.core.get(this.start++))
36
+ }
37
+ }
38
+
39
+ exports.ReadStream = ReadStream
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.0",
3
+ "version": "10.0.0-alpha.12",
4
4
  "description": "Hypercore 10",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -29,17 +29,19 @@
29
29
  "homepage": "https://github.com/hypercore-protocol/hypercore#readme",
30
30
  "dependencies": {
31
31
  "@hyperswarm/secret-stream": "^5.0.0",
32
+ "b4a": "^1.1.0",
32
33
  "big-sparse-array": "^1.0.2",
33
34
  "codecs": "^2.2.0",
34
- "compact-encoding": "^2.0.0",
35
+ "compact-encoding": "^2.5.0",
35
36
  "crc32-universal": "^1.0.1",
36
- "flat-tree": "^1.7.0",
37
- "hypercore-crypto": "^2.1.1",
37
+ "flat-tree": "^1.9.0",
38
+ "hypercore-crypto": "^3.0.0",
38
39
  "is-options": "^1.0.1",
39
40
  "random-access-file": "^2.1.4",
40
41
  "random-array-iterator": "^1.0.0",
41
42
  "safety-catch": "^1.0.1",
42
- "uint64le": "^1.0.0"
43
+ "sodium-universal": "^3.0.4",
44
+ "xache": "^1.0.0"
43
45
  },
44
46
  "devDependencies": {
45
47
  "brittle": "^1.4.1",
package/test/basic.js CHANGED
@@ -56,19 +56,18 @@ test('close multiple', async function (t) {
56
56
  const core = await create()
57
57
  await core.append('hello world')
58
58
 
59
- const expected = ['close event', 'close 1', 'close 2', 'close 3']
59
+ const ev = t.test('events')
60
60
 
61
- core.on('close', () => done('close event'))
62
- core.close().then(() => done('close 1'))
63
- core.close().then(() => done('close 2'))
64
- core.close().then(() => done('close 3'))
61
+ ev.plan(4)
65
62
 
66
- await core.close()
67
- t.is(expected.length, 0, 'all event passed')
63
+ let i = 0
68
64
 
69
- function done (event) {
70
- t.is(event, expected.shift())
71
- }
65
+ core.on('close', () => ev.is(i++, 0, 'on close'))
66
+ core.close().then(() => ev.is(i++, 1, 'first close'))
67
+ core.close().then(() => ev.is(i++, 2, 'second close'))
68
+ core.close().then(() => ev.is(i++, 3, 'third close'))
69
+
70
+ await ev
72
71
  })
73
72
 
74
73
  test('storage options', async function (t) {
@@ -77,3 +76,15 @@ test('storage options', async function (t) {
77
76
  t.alike(await core.get(0), Buffer.from('hello'))
78
77
  t.end()
79
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
+ )
@@ -0,0 +1,123 @@
1
+ const test = require('brittle')
2
+ const RAM = require('random-access-memory')
3
+ const Hypercore = require('..')
4
+ const { create, replicate } = require('./helpers')
5
+
6
+ const encryptionKey = Buffer.alloc(32, 'hello world')
7
+
8
+ test('encrypted append and get', async function (t) {
9
+ const a = await create({ encryptionKey })
10
+
11
+ t.alike(a.encryptionKey, encryptionKey)
12
+
13
+ await a.append(['hello'])
14
+
15
+ t.is(a.byteLength, 5)
16
+ t.is(a.core.tree.byteLength, 5 + a.padding)
17
+
18
+ const unencrypted = await a.get(0)
19
+ t.alike(unencrypted, Buffer.from('hello'))
20
+
21
+ const encrypted = await a.core.blocks.get(0)
22
+ t.absent(encrypted.includes('hello'))
23
+ })
24
+
25
+ test('encrypted seek', async function (t) {
26
+ const a = await create({ encryptionKey })
27
+
28
+ await a.append(['hello', 'world', '!'])
29
+
30
+ t.alike(await a.seek(0), [0, 0])
31
+ t.alike(await a.seek(4), [0, 4])
32
+ t.alike(await a.seek(5), [1, 0])
33
+ t.alike(await a.seek(6), [1, 1])
34
+ t.alike(await a.seek(6), [1, 1])
35
+ t.alike(await a.seek(9), [1, 4])
36
+ t.alike(await a.seek(10), [2, 0])
37
+ t.alike(await a.seek(11), [3, 0])
38
+ })
39
+
40
+ test('encrypted replication', async function (t) {
41
+ const a = await create({ encryptionKey })
42
+
43
+ await a.append(['a', 'b', 'c', 'd', 'e'])
44
+
45
+ t.test('with encryption key', async function (t) {
46
+ const b = await create(a.key, { encryptionKey })
47
+
48
+ replicate(a, b, t)
49
+
50
+ await t.test('through direct download', async function (t) {
51
+ const r = b.download({ start: 0, end: a.length })
52
+ await r.downloaded()
53
+
54
+ for (let i = 0; i < 5; i++) {
55
+ t.alike(await b.get(i), await a.get(i))
56
+ }
57
+ })
58
+
59
+ await t.test('through indirect download', async function (t) {
60
+ await a.append(['f', 'g', 'h', 'i', 'j'])
61
+
62
+ for (let i = 5; i < 10; i++) {
63
+ t.alike(await b.get(i), await a.get(i))
64
+ }
65
+ })
66
+ })
67
+
68
+ t.test('without encryption key', async function (t) {
69
+ const b = await create(a.key)
70
+
71
+ replicate(a, b, t)
72
+
73
+ await t.test('through direct download', async function (t) {
74
+ const r = b.download({ start: 0, end: a.length })
75
+ await r.downloaded()
76
+
77
+ for (let i = 0; i < 5; i++) {
78
+ t.alike(await b.get(i), await a.core.blocks.get(i))
79
+ }
80
+ })
81
+
82
+ await t.test('through indirect download', async function (t) {
83
+ await a.append(['f', 'g', 'h', 'i', 'j'])
84
+
85
+ for (let i = 5; i < 10; i++) {
86
+ t.alike(await b.get(i), await a.core.blocks.get(i))
87
+ }
88
+ })
89
+ })
90
+ })
91
+
92
+ test('encrypted session', async function (t) {
93
+ const a = await create({ encryptionKey })
94
+
95
+ await a.append(['hello'])
96
+
97
+ const s = a.session()
98
+
99
+ t.alike(a.encryptionKey, s.encryptionKey)
100
+ t.alike(await s.get(0), Buffer.from('hello'))
101
+
102
+ await s.append(['world'])
103
+
104
+ const unencrypted = await s.get(1)
105
+ t.alike(unencrypted, Buffer.from('world'))
106
+ t.alike(await a.get(1), unencrypted)
107
+
108
+ const encrypted = await s.core.blocks.get(1)
109
+ t.absent(encrypted.includes('world'))
110
+ t.alike(await a.core.blocks.get(1), encrypted)
111
+ })
112
+
113
+ test('encrypted session before ready core', async function (t) {
114
+ const a = new Hypercore(RAM, { encryptionKey })
115
+ const s = a.session()
116
+
117
+ await a.ready()
118
+
119
+ t.alike(a.encryptionKey, s.encryptionKey)
120
+
121
+ await a.append(['hello'])
122
+ t.alike(await s.get(0), Buffer.from('hello'))
123
+ })
package/test/extension.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const test = require('brittle')
2
- const { create, replicate } = require('./helpers')
2
+ const { create, replicate, eventFlush } = require('./helpers')
3
3
 
4
4
  test('basic extension', async function (t) {
5
5
  const messages = ['world', 'hello']
@@ -18,15 +18,15 @@ test('basic extension', async function (t) {
18
18
  encoding: 'utf-8'
19
19
  })
20
20
 
21
- replicate(a, b)
21
+ replicate(a, b, t)
22
22
 
23
- await new Promise(resolve => setImmediate(resolve))
23
+ await eventFlush()
24
24
  t.is(b.peers.length, 1)
25
25
 
26
26
  bExt.send('hello', b.peers[0])
27
27
  bExt.send('world', b.peers[0])
28
28
 
29
- await new Promise(resolve => setImmediate(resolve))
29
+ await eventFlush()
30
30
  t.absent(messages.length)
31
31
 
32
32
  t.end()
@@ -38,7 +38,7 @@ test('two extensions', async function (t) {
38
38
  const a = await create()
39
39
  const b = await create(a.key)
40
40
 
41
- replicate(a, b)
41
+ replicate(a, b, t)
42
42
 
43
43
  b.registerExtension('test-extension-1', {
44
44
  encoding: 'utf-8'
@@ -47,12 +47,12 @@ test('two extensions', async function (t) {
47
47
  encoding: 'utf-8'
48
48
  })
49
49
 
50
- await new Promise(resolve => setImmediate(resolve))
50
+ await eventFlush()
51
51
  t.is(b.peers.length, 1)
52
52
 
53
53
  bExt2.send('world', b.peers[0])
54
54
 
55
- await new Promise(resolve => setImmediate(resolve))
55
+ await eventFlush()
56
56
 
57
57
  a.registerExtension('test-extension-2', {
58
58
  encoding: 'utf-8',
@@ -64,7 +64,7 @@ test('two extensions', async function (t) {
64
64
 
65
65
  bExt2.send('hello', b.peers[0])
66
66
 
67
- await new Promise(resolve => setImmediate(resolve))
67
+ await eventFlush()
68
68
  t.is(messages.length, 1) // First message gets ignored
69
69
 
70
70
  t.end()
@@ -8,12 +8,16 @@ module.exports = {
8
8
  return core
9
9
  },
10
10
 
11
- replicate (a, b) {
11
+ replicate (a, b, t) {
12
12
  const s1 = a.replicate(true)
13
13
  const s2 = b.replicate(false)
14
- s1.on('error', err => console.log('STREAM ERROR:', err))
15
- s2.on('error', err => console.log('STREAM ERROR:', err))
14
+ s1.on('error', err => t.comment(`STREAM ERROR: ${err}`))
15
+ s2.on('error', err => t.comment(`STREAM ERROR: ${err}`))
16
16
  s1.pipe(s2).pipe(s1)
17
17
  return [s1, s2]
18
+ },
19
+
20
+ async eventFlush () {
21
+ await new Promise(resolve => setImmediate(resolve))
18
22
  }
19
23
  }