hypercore 10.0.0-alpha.1 → 10.0.0-alpha.10

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/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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.1",
3
+ "version": "10.0.0-alpha.10",
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
+ "flat-tree": "^1.9.0",
37
38
  "hypercore-crypto": "^2.1.1",
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
@@ -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
+ )
@@ -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/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
+ })
@@ -0,0 +1,31 @@
1
+ const test = require('brittle')
2
+ const sodium = require('sodium-universal')
3
+ const crypto = require('hypercore-crypto')
4
+ const RAM = require('random-access-memory')
5
+ const Hypercore = require('..')
6
+
7
+ const keyPair = crypto.keyPair(Buffer.alloc(sodium.crypto_sign_SEEDBYTES, 'seed'))
8
+
9
+ const encryptionKey = Buffer.alloc(sodium.crypto_stream_KEYBYTES, 'encryption key')
10
+
11
+ test('storage layout', async function (t) {
12
+ const core = new Hypercore(RAM, { keyPair })
13
+
14
+ for (let i = 0; i < 10000; i++) {
15
+ await core.append(Buffer.from([i]))
16
+ }
17
+
18
+ t.snapshot(core.core.blocks.storage.toBuffer().toString('base64'), 'blocks')
19
+ t.snapshot(core.core.tree.storage.toBuffer().toString('base64'), 'tree')
20
+ })
21
+
22
+ test('encrypted storage layout', async function (t) {
23
+ const core = new Hypercore(RAM, { keyPair, encryptionKey })
24
+
25
+ for (let i = 0; i < 10000; i++) {
26
+ await core.append(Buffer.from([i]))
27
+ }
28
+
29
+ t.snapshot(core.core.blocks.storage.toBuffer().toString('base64'), 'blocks')
30
+ t.snapshot(core.core.tree.storage.toBuffer().toString('base64'), 'tree')
31
+ })