hypercore 10.0.0-alpha.2 → 10.0.0-alpha.20
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 +34 -2
- package/index.js +266 -127
- package/lib/bitfield.js +3 -2
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/core.js +6 -3
- package/lib/merkle-tree.js +62 -35
- package/lib/oplog.js +4 -3
- package/lib/protocol.js +31 -7
- package/lib/replicator.js +60 -15
- package/lib/streams.js +56 -0
- package/package.json +14 -7
- package/.github/workflows/test-node.yml +0 -24
- package/UPGRADE.md +0 -9
- package/examples/announce.js +0 -19
- package/examples/basic.js +0 -10
- package/examples/http.js +0 -123
- package/examples/lookup.js +0 -20
- package/test/basic.js +0 -78
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- package/test/extension.js +0 -71
- package/test/helpers/index.js +0 -23
- package/test/merkle-tree.js +0 -518
- package/test/mutex.js +0 -137
- package/test/oplog.js +0 -399
- package/test/preload.js +0 -72
- package/test/replicate.js +0 -296
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const sodium = require('sodium-universal')
|
|
2
|
+
const c = require('compact-encoding')
|
|
3
|
+
const b4a = require('b4a')
|
|
4
|
+
|
|
5
|
+
const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
|
|
6
|
+
|
|
7
|
+
module.exports = class BlockEncryption {
|
|
8
|
+
constructor (encryptionKey, hypercoreKey) {
|
|
9
|
+
const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
|
|
10
|
+
|
|
11
|
+
this.key = encryptionKey
|
|
12
|
+
this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
|
|
13
|
+
this.blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)
|
|
14
|
+
this.padding = 8
|
|
15
|
+
|
|
16
|
+
sodium.crypto_generichash(this.blockKey, encryptionKey, hypercoreKey)
|
|
17
|
+
sodium.crypto_generichash(this.blindingKey, this.blockKey)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
encrypt (index, block, fork) {
|
|
21
|
+
const padding = block.subarray(0, this.padding)
|
|
22
|
+
block = block.subarray(this.padding)
|
|
23
|
+
|
|
24
|
+
c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
|
|
25
|
+
c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
|
|
26
|
+
|
|
27
|
+
// Zero out any previous padding.
|
|
28
|
+
nonce.fill(0, 8, 8 + padding.byteLength)
|
|
29
|
+
|
|
30
|
+
// Blind the fork ID, possibly risking reusing the nonce on a reorg of the
|
|
31
|
+
// Hypercore. This is fine as the blinding is best-effort and the latest
|
|
32
|
+
// fork ID shared on replication anyway.
|
|
33
|
+
sodium.crypto_stream_xor(
|
|
34
|
+
padding,
|
|
35
|
+
padding,
|
|
36
|
+
nonce,
|
|
37
|
+
this.blindingKey
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
nonce.set(padding, 8)
|
|
41
|
+
|
|
42
|
+
// The combination of a (blinded) fork ID and a block index is unique for a
|
|
43
|
+
// given Hypercore and is therefore a valid nonce for encrypting the block.
|
|
44
|
+
sodium.crypto_stream_xor(
|
|
45
|
+
block,
|
|
46
|
+
block,
|
|
47
|
+
nonce,
|
|
48
|
+
this.blockKey
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
decrypt (index, block) {
|
|
53
|
+
const padding = block.subarray(0, this.padding)
|
|
54
|
+
block = block.subarray(this.padding)
|
|
55
|
+
|
|
56
|
+
c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
|
|
57
|
+
|
|
58
|
+
nonce.set(padding, 8)
|
|
59
|
+
|
|
60
|
+
// Decrypt the block using the blinded fork ID.
|
|
61
|
+
sodium.crypto_stream_xor(
|
|
62
|
+
block,
|
|
63
|
+
block,
|
|
64
|
+
nonce,
|
|
65
|
+
this.blockKey
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
package/lib/block-store.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
2
|
+
|
|
1
3
|
module.exports = class BlockStore {
|
|
2
4
|
constructor (storage, tree) {
|
|
3
5
|
this.storage = storage
|
|
@@ -15,7 +17,7 @@ module.exports = class BlockStore {
|
|
|
15
17
|
|
|
16
18
|
putBatch (i, batch, offset) {
|
|
17
19
|
if (batch.length === 0) return Promise.resolve()
|
|
18
|
-
return this.put(i, batch.length === 1 ? batch[0] :
|
|
20
|
+
return this.put(i, batch.length === 1 ? batch[0] : b4a.concat(batch), offset)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
clear () {
|
package/lib/core.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
const Oplog = require('./oplog')
|
|
3
4
|
const Mutex = require('./mutex')
|
|
4
5
|
const MerkleTree = require('./merkle-tree')
|
|
@@ -90,7 +91,7 @@ module.exports = class Core {
|
|
|
90
91
|
await oplog.flush(header)
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
if (opts.keyPair && !header.signer.publicKey
|
|
94
|
+
if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
|
|
94
95
|
throw new Error('Another hypercore is stored here')
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -176,7 +177,7 @@ module.exports = class Core {
|
|
|
176
177
|
|
|
177
178
|
for (const u of this.header.userData) {
|
|
178
179
|
if (u.key !== key) continue
|
|
179
|
-
if (value &&
|
|
180
|
+
if (value && b4a.equals(u.value, value)) return
|
|
180
181
|
empty = false
|
|
181
182
|
break
|
|
182
183
|
}
|
|
@@ -214,10 +215,12 @@ module.exports = class Core {
|
|
|
214
215
|
}
|
|
215
216
|
}
|
|
216
217
|
|
|
217
|
-
async append (values, sign = this.defaultSign) {
|
|
218
|
+
async append (values, sign = this.defaultSign, hooks = {}) {
|
|
218
219
|
await this._mutex.lock()
|
|
219
220
|
|
|
220
221
|
try {
|
|
222
|
+
if (hooks.preappend) await hooks.preappend(values)
|
|
223
|
+
|
|
221
224
|
if (!values.length) return this.tree.length
|
|
222
225
|
|
|
223
226
|
const batch = this.tree.batch()
|
package/lib/merkle-tree.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const flat = require('flat-tree')
|
|
2
2
|
const crypto = require('hypercore-crypto')
|
|
3
|
-
const
|
|
3
|
+
const c = require('compact-encoding')
|
|
4
|
+
const b4a = require('b4a')
|
|
4
5
|
|
|
5
|
-
const BLANK_HASH =
|
|
6
|
-
const OLD_TREE =
|
|
6
|
+
const BLANK_HASH = b4a.alloc(32)
|
|
7
|
+
const OLD_TREE = b4a.from([5, 2, 87, 2, 0, 0, 40, 7, 66, 76, 65, 75, 69, 50, 98])
|
|
7
8
|
|
|
8
9
|
class NodeQueue {
|
|
9
10
|
constructor (nodes, extra = null) {
|
|
@@ -214,7 +215,7 @@ class ReorgBatch extends MerkleTreeBatch {
|
|
|
214
215
|
const nodes = []
|
|
215
216
|
const root = verifyBlock(proof, this.tree.crypto, nodes)
|
|
216
217
|
|
|
217
|
-
if (root === null || !root.hash
|
|
218
|
+
if (root === null || !b4a.equals(root.hash, this.diff.hash)) return false
|
|
218
219
|
|
|
219
220
|
this.nodes.push(...nodes)
|
|
220
221
|
return this._update(nodes)
|
|
@@ -232,7 +233,7 @@ class ReorgBatch extends MerkleTreeBatch {
|
|
|
232
233
|
if (!left) break
|
|
233
234
|
|
|
234
235
|
const existing = await this.tree.get(left.index, false)
|
|
235
|
-
if (!existing || !existing.hash
|
|
236
|
+
if (!existing || !b4a.equals(existing.hash, left.hash)) {
|
|
236
237
|
diff = left
|
|
237
238
|
} else {
|
|
238
239
|
diff = n.get(ite.sibling())
|
|
@@ -246,6 +247,8 @@ class ReorgBatch extends MerkleTreeBatch {
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
_updateDiffRoot (diff) {
|
|
250
|
+
if (this.want === null) return true
|
|
251
|
+
|
|
249
252
|
const spans = flat.spans(diff.index)
|
|
250
253
|
const start = spans[0] / 2
|
|
251
254
|
const end = Math.min(this.treeLength, spans[1] / 2 + 1)
|
|
@@ -272,11 +275,15 @@ class ReorgBatch extends MerkleTreeBatch {
|
|
|
272
275
|
}
|
|
273
276
|
|
|
274
277
|
class ByteSeeker {
|
|
275
|
-
constructor (tree, bytes) {
|
|
278
|
+
constructor (tree, bytes, padding = 0) {
|
|
276
279
|
this.tree = tree
|
|
277
280
|
this.bytes = bytes
|
|
278
|
-
this.
|
|
279
|
-
|
|
281
|
+
this.padding = padding
|
|
282
|
+
|
|
283
|
+
const size = tree.byteLength - (tree.length * padding)
|
|
284
|
+
|
|
285
|
+
this.start = bytes >= size ? tree.length : 0
|
|
286
|
+
this.end = bytes < size ? tree.length : 0
|
|
280
287
|
}
|
|
281
288
|
|
|
282
289
|
nodes () {
|
|
@@ -287,12 +294,12 @@ class ByteSeeker {
|
|
|
287
294
|
if (!bytes) return [0, 0]
|
|
288
295
|
|
|
289
296
|
for (const node of this.tree.roots) { // all async ticks happen once we find the root so safe
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
297
|
+
let size = node.size
|
|
298
|
+
if (this.padding > 0) size -= this.padding * flat.countLeaves(node.index)
|
|
293
299
|
|
|
294
|
-
if (bytes
|
|
295
|
-
|
|
300
|
+
if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
|
|
301
|
+
if (bytes > size) {
|
|
302
|
+
bytes -= size
|
|
296
303
|
continue
|
|
297
304
|
}
|
|
298
305
|
|
|
@@ -301,9 +308,12 @@ class ByteSeeker {
|
|
|
301
308
|
while ((ite.index & 1) !== 0) {
|
|
302
309
|
const l = await this.tree.get(ite.leftChild(), false)
|
|
303
310
|
if (l) {
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
311
|
+
let size = l.size
|
|
312
|
+
if (this.padding > 0) size -= this.padding * ite.countLeaves()
|
|
313
|
+
|
|
314
|
+
if (size === bytes) return [ite.rightSpan() + 2, 0]
|
|
315
|
+
if (size > bytes) continue
|
|
316
|
+
bytes -= size
|
|
307
317
|
ite.sibling()
|
|
308
318
|
} else {
|
|
309
319
|
ite.parent()
|
|
@@ -347,7 +357,7 @@ module.exports = class MerkleTree {
|
|
|
347
357
|
}
|
|
348
358
|
|
|
349
359
|
addNode (node) {
|
|
350
|
-
if (node.size === 0 && node.hash
|
|
360
|
+
if (node.size === 0 && b4a.equals(node.hash, BLANK_HASH)) node = blankNode(node.index)
|
|
351
361
|
this.unflushed.set(node.index, node)
|
|
352
362
|
}
|
|
353
363
|
|
|
@@ -355,8 +365,8 @@ module.exports = class MerkleTree {
|
|
|
355
365
|
return new MerkleTreeBatch(this)
|
|
356
366
|
}
|
|
357
367
|
|
|
358
|
-
seek (bytes) {
|
|
359
|
-
return new ByteSeeker(this, bytes)
|
|
368
|
+
seek (bytes, padding) {
|
|
369
|
+
return new ByteSeeker(this, bytes, padding)
|
|
360
370
|
}
|
|
361
371
|
|
|
362
372
|
hash () {
|
|
@@ -371,6 +381,17 @@ module.exports = class MerkleTree {
|
|
|
371
381
|
return this.signature !== null && this.crypto.verify(this.signable(), this.signature, key)
|
|
372
382
|
}
|
|
373
383
|
|
|
384
|
+
getRoots (length) {
|
|
385
|
+
const indexes = flat.fullRoots(2 * length)
|
|
386
|
+
const roots = new Array(indexes.length)
|
|
387
|
+
|
|
388
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
389
|
+
roots[i] = this.get(indexes[i], true)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return Promise.all(roots)
|
|
393
|
+
}
|
|
394
|
+
|
|
374
395
|
get (index, error = true) {
|
|
375
396
|
let node = this.unflushed.get(index)
|
|
376
397
|
|
|
@@ -433,17 +454,23 @@ module.exports = class MerkleTree {
|
|
|
433
454
|
// TODO: write neighbors together etc etc
|
|
434
455
|
// TODO: bench loading a full disk page and copy to that instead
|
|
435
456
|
return new Promise((resolve, reject) => {
|
|
436
|
-
const slab =
|
|
457
|
+
const slab = b4a.allocUnsafe(40 * this.flushing.size)
|
|
437
458
|
|
|
438
459
|
let error = null
|
|
439
460
|
let missing = this.flushing.size + 1
|
|
440
461
|
let offset = 0
|
|
441
462
|
|
|
442
463
|
for (const node of this.flushing.values()) {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
464
|
+
const state = {
|
|
465
|
+
start: 0,
|
|
466
|
+
end: 40,
|
|
467
|
+
buffer: slab.subarray(offset, offset += 40)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
c.uint64.encode(state, node.size)
|
|
471
|
+
c.raw.encode(state, node.hash)
|
|
472
|
+
|
|
473
|
+
this.storage.write(node.index * 40, state.buffer, done)
|
|
447
474
|
}
|
|
448
475
|
|
|
449
476
|
done(null)
|
|
@@ -520,7 +547,7 @@ module.exports = class MerkleTree {
|
|
|
520
547
|
|
|
521
548
|
for (const root of batch.roots) {
|
|
522
549
|
const existing = await this.get(root.index, false)
|
|
523
|
-
if (existing && existing.hash
|
|
550
|
+
if (existing && b4a.equals(existing.hash, root.hash)) continue
|
|
524
551
|
batch._updateDiffRoot(root)
|
|
525
552
|
break
|
|
526
553
|
}
|
|
@@ -548,7 +575,7 @@ module.exports = class MerkleTree {
|
|
|
548
575
|
|
|
549
576
|
if (unverified) {
|
|
550
577
|
const verified = await this.get(unverified.index)
|
|
551
|
-
if (!verified.hash
|
|
578
|
+
if (!b4a.equals(verified.hash, unverified.hash)) {
|
|
552
579
|
throw new Error('Invalid checksum at node ' + unverified.index)
|
|
553
580
|
}
|
|
554
581
|
}
|
|
@@ -693,7 +720,7 @@ module.exports = class MerkleTree {
|
|
|
693
720
|
await new Promise((resolve, reject) => {
|
|
694
721
|
storage.read(0, OLD_TREE.length, (err, buf) => {
|
|
695
722
|
if (err) return resolve()
|
|
696
|
-
if (
|
|
723
|
+
if (b4a.equals(buf, OLD_TREE)) return reject(new Error('Storage contains an incompatible merkle tree'))
|
|
697
724
|
resolve()
|
|
698
725
|
})
|
|
699
726
|
})
|
|
@@ -1038,10 +1065,10 @@ function getStoredNode (storage, index, error) {
|
|
|
1038
1065
|
return
|
|
1039
1066
|
}
|
|
1040
1067
|
|
|
1041
|
-
const hash = data.
|
|
1042
|
-
const size =
|
|
1068
|
+
const hash = data.subarray(8)
|
|
1069
|
+
const size = c.decode(c.uint64, data)
|
|
1043
1070
|
|
|
1044
|
-
if (size === 0 &&
|
|
1071
|
+
if (size === 0 && b4a.compare(hash, BLANK_HASH) === 0) {
|
|
1045
1072
|
if (error) reject(new Error('Could not load node: ' + index))
|
|
1046
1073
|
else resolve(null)
|
|
1047
1074
|
return
|
|
@@ -1088,9 +1115,9 @@ function log2 (n) {
|
|
|
1088
1115
|
}
|
|
1089
1116
|
|
|
1090
1117
|
function signable (hash, length, fork) {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
return
|
|
1118
|
+
const state = { start: 0, end: 48, buffer: b4a.alloc(48) }
|
|
1119
|
+
c.raw.encode(state, hash)
|
|
1120
|
+
c.uint64.encode(state, length)
|
|
1121
|
+
c.uint64.encode(state, fork)
|
|
1122
|
+
return state.buffer
|
|
1096
1123
|
}
|
package/lib/oplog.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const cenc = require('compact-encoding')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
const crc32 = require('crc32-universal')
|
|
3
4
|
|
|
4
5
|
module.exports = class Oplog {
|
|
@@ -133,7 +134,7 @@ module.exports = class Oplog {
|
|
|
133
134
|
return new Promise((resolve, reject) => {
|
|
134
135
|
this.storage.open(err => {
|
|
135
136
|
if (err && err.code !== 'ENOENT') return reject(err)
|
|
136
|
-
if (err) return resolve(
|
|
137
|
+
if (err) return resolve(b4a.alloc(0))
|
|
137
138
|
this.storage.stat((err, stat) => {
|
|
138
139
|
if (err && err.code !== 'ENOENT') return reject(err)
|
|
139
140
|
this.storage.read(0, stat.size, (err, buf) => {
|
|
@@ -151,7 +152,7 @@ module.exports = class Oplog {
|
|
|
151
152
|
const bit = (this._headers[i] + 1) & 1
|
|
152
153
|
|
|
153
154
|
this.headerEncoding.preencode(state, header)
|
|
154
|
-
state.buffer =
|
|
155
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
155
156
|
this.headerEncoding.encode(state, header)
|
|
156
157
|
this._addHeader(state, state.end - 8, bit, 0)
|
|
157
158
|
|
|
@@ -187,7 +188,7 @@ module.exports = class Oplog {
|
|
|
187
188
|
this.entryEncoding.preencode(state, batch[i])
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
state.buffer =
|
|
191
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
191
192
|
|
|
192
193
|
for (let i = 0; i < batch.length; i++) {
|
|
193
194
|
const start = state.start += 8 // space for header
|
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
|
|
|
@@ -38,7 +39,7 @@ class Extension {
|
|
|
38
39
|
_sendAlias (message, alias) {
|
|
39
40
|
if (this.destroyed) return
|
|
40
41
|
|
|
41
|
-
if (this.
|
|
42
|
+
if (this.remoteSupports) {
|
|
42
43
|
return this.protocol.send(this.type, this.encoding, alias, message)
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -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
|
}
|
|
@@ -253,11 +256,13 @@ module.exports = class Protocol {
|
|
|
253
256
|
this._localExtensions = 128
|
|
254
257
|
this._remoteExtensions = []
|
|
255
258
|
this._extensions = new Map()
|
|
259
|
+
this._keepAliveInterval = null
|
|
256
260
|
|
|
257
261
|
this._destroyer = this._safeDestroy.bind(this)
|
|
258
262
|
this.noiseStream.on('data', this.onmessage.bind(this))
|
|
259
263
|
this.noiseStream.on('end', this.noiseStream.end) // no half open
|
|
260
264
|
this.noiseStream.on('close', () => {
|
|
265
|
+
this.setKeepAlive(false)
|
|
261
266
|
// TODO: If the stream was destroyed with an error, we probably want to forward it here
|
|
262
267
|
for (const peer of this._peers.values()) {
|
|
263
268
|
peer.destroy(null)
|
|
@@ -267,6 +272,18 @@ module.exports = class Protocol {
|
|
|
267
272
|
this._sendHandshake()
|
|
268
273
|
}
|
|
269
274
|
|
|
275
|
+
setKeepAlive (enable) {
|
|
276
|
+
if (enable) {
|
|
277
|
+
if (this._keepAliveInterval) return
|
|
278
|
+
this._keepAliveInterval = setInterval(this.ping.bind(this), 5000)
|
|
279
|
+
if (this._keepAliveInterval.unref) this._keepAliveInterval.unref()
|
|
280
|
+
} else {
|
|
281
|
+
if (!this._keepAliveInterval) return
|
|
282
|
+
clearInterval(this._keepAliveInterval)
|
|
283
|
+
this._keepAliveInterval = null
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
270
287
|
_sendHandshake () {
|
|
271
288
|
const m = { protocolVersion: this.protocolVersion, userAgent: this.userAgent }
|
|
272
289
|
const state = { start: 0, end: 0, buffer: null }
|
|
@@ -283,20 +300,20 @@ module.exports = class Protocol {
|
|
|
283
300
|
|
|
284
301
|
registerPeer (key, discoveryKey, handlers = {}, state = null) {
|
|
285
302
|
const peer = new Peer(this, this._localAliases++, key, discoveryKey, handlers, state)
|
|
286
|
-
this._peers.set(
|
|
303
|
+
this._peers.set(b4a.toString(discoveryKey, 'hex'), peer)
|
|
287
304
|
this._announceCore(peer.alias, key, discoveryKey)
|
|
288
305
|
return peer
|
|
289
306
|
}
|
|
290
307
|
|
|
291
308
|
unregisterPeer (peer, err) {
|
|
292
|
-
this._peers.delete(peer.discoveryKey
|
|
309
|
+
this._peers.delete(b4a.toString(peer.discoveryKey, 'hex'))
|
|
293
310
|
|
|
294
311
|
if (peer.remoteAlias > -1) {
|
|
295
312
|
this._remoteAliases[peer.remoteAlias] = null
|
|
296
313
|
peer.remoteAlias = -1
|
|
297
314
|
}
|
|
298
315
|
|
|
299
|
-
peer.handlers.onunregister(
|
|
316
|
+
peer.handlers.onunregister(peer, err)
|
|
300
317
|
|
|
301
318
|
if (err) this.noiseStream.destroy(err)
|
|
302
319
|
}
|
|
@@ -372,11 +389,13 @@ module.exports = class Protocol {
|
|
|
372
389
|
this.send(2, messages.core, -1, {
|
|
373
390
|
alias: alias,
|
|
374
391
|
discoveryKey: discoveryKey,
|
|
375
|
-
capability:
|
|
392
|
+
capability: b4a.alloc(32) // TODO
|
|
376
393
|
})
|
|
377
394
|
}
|
|
378
395
|
|
|
379
396
|
_decode (buffer) {
|
|
397
|
+
if (buffer.byteLength === 0) return
|
|
398
|
+
|
|
380
399
|
const state = { start: 0, end: buffer.length, buffer }
|
|
381
400
|
|
|
382
401
|
if (this._firstMessage === true) {
|
|
@@ -441,7 +460,7 @@ module.exports = class Protocol {
|
|
|
441
460
|
}
|
|
442
461
|
|
|
443
462
|
_oncore (m) {
|
|
444
|
-
const hex = m.discoveryKey
|
|
463
|
+
const hex = b4a.toString(m.discoveryKey, 'hex')
|
|
445
464
|
const peer = this._peers.get(hex)
|
|
446
465
|
|
|
447
466
|
// allow one alloc
|
|
@@ -477,7 +496,7 @@ module.exports = class Protocol {
|
|
|
477
496
|
}
|
|
478
497
|
|
|
479
498
|
_onunknowncore (m) {
|
|
480
|
-
const peer = this._peers.get(m.discoveryKey
|
|
499
|
+
const peer = this._peers.get(b4a.toString(m.discoveryKey, 'hex'))
|
|
481
500
|
if (!peer) return
|
|
482
501
|
|
|
483
502
|
peer.resend = true
|
|
@@ -505,6 +524,11 @@ module.exports = class Protocol {
|
|
|
505
524
|
return this.noiseStream.write(state.buffer)
|
|
506
525
|
}
|
|
507
526
|
|
|
527
|
+
ping () {
|
|
528
|
+
const empty = this.noiseStream.alloc(0)
|
|
529
|
+
this.noiseStream.write(empty)
|
|
530
|
+
}
|
|
531
|
+
|
|
508
532
|
destroy (err) {
|
|
509
533
|
return this.noiseStream.destroy(err)
|
|
510
534
|
}
|
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
|
-
|
|
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 !!(
|
|
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
|
-
|
|
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 =
|
|
754
|
-
const bigger =
|
|
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
|
+
}
|