hypercore 10.0.0-alpha.1 → 10.0.0-alpha.13
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/.github/workflows/test-node.yml +1 -2
- package/README.md +30 -1
- package/UPGRADE.md +6 -0
- package/__snapshots__/test/storage.js.snapshot.cjs +15 -0
- package/index.js +230 -105
- package/lib/bitfield.js +3 -2
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/core.js +3 -1
- package/lib/merkle-tree.js +43 -29
- package/lib/oplog.js +4 -3
- package/lib/protocol.js +9 -6
- package/lib/replicator.js +60 -15
- package/lib/streams.js +39 -0
- package/package.json +7 -5
- package/test/basic.js +12 -0
- package/test/encryption.js +123 -0
- package/test/replicate.js +76 -0
- package/test/sessions.js +11 -1
- package/test/storage.js +31 -0
- package/test/streams.js +55 -0
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) {
|
|
@@ -272,11 +273,15 @@ class ReorgBatch extends MerkleTreeBatch {
|
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
class ByteSeeker {
|
|
275
|
-
constructor (tree, bytes) {
|
|
276
|
+
constructor (tree, bytes, padding = 0) {
|
|
276
277
|
this.tree = tree
|
|
277
278
|
this.bytes = bytes
|
|
278
|
-
this.
|
|
279
|
-
|
|
279
|
+
this.padding = padding
|
|
280
|
+
|
|
281
|
+
const size = tree.byteLength - (tree.length * padding)
|
|
282
|
+
|
|
283
|
+
this.start = bytes >= size ? tree.length : 0
|
|
284
|
+
this.end = bytes < size ? tree.length : 0
|
|
280
285
|
}
|
|
281
286
|
|
|
282
287
|
nodes () {
|
|
@@ -287,12 +292,12 @@ class ByteSeeker {
|
|
|
287
292
|
if (!bytes) return [0, 0]
|
|
288
293
|
|
|
289
294
|
for (const node of this.tree.roots) { // all async ticks happen once we find the root so safe
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
295
|
+
let size = node.size
|
|
296
|
+
if (this.padding > 0) size -= this.padding * flat.countLeaves(node.index)
|
|
293
297
|
|
|
294
|
-
if (bytes
|
|
295
|
-
|
|
298
|
+
if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
|
|
299
|
+
if (bytes > size) {
|
|
300
|
+
bytes -= size
|
|
296
301
|
continue
|
|
297
302
|
}
|
|
298
303
|
|
|
@@ -301,9 +306,12 @@ class ByteSeeker {
|
|
|
301
306
|
while ((ite.index & 1) !== 0) {
|
|
302
307
|
const l = await this.tree.get(ite.leftChild(), false)
|
|
303
308
|
if (l) {
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
|
|
309
|
+
let size = l.size
|
|
310
|
+
if (this.padding > 0) size -= this.padding * ite.countLeaves()
|
|
311
|
+
|
|
312
|
+
if (size === bytes) return [ite.rightSpan() + 2, 0]
|
|
313
|
+
if (size > bytes) continue
|
|
314
|
+
bytes -= size
|
|
307
315
|
ite.sibling()
|
|
308
316
|
} else {
|
|
309
317
|
ite.parent()
|
|
@@ -355,8 +363,8 @@ module.exports = class MerkleTree {
|
|
|
355
363
|
return new MerkleTreeBatch(this)
|
|
356
364
|
}
|
|
357
365
|
|
|
358
|
-
seek (bytes) {
|
|
359
|
-
return new ByteSeeker(this, bytes)
|
|
366
|
+
seek (bytes, padding) {
|
|
367
|
+
return new ByteSeeker(this, bytes, padding)
|
|
360
368
|
}
|
|
361
369
|
|
|
362
370
|
hash () {
|
|
@@ -433,17 +441,23 @@ module.exports = class MerkleTree {
|
|
|
433
441
|
// TODO: write neighbors together etc etc
|
|
434
442
|
// TODO: bench loading a full disk page and copy to that instead
|
|
435
443
|
return new Promise((resolve, reject) => {
|
|
436
|
-
const slab =
|
|
444
|
+
const slab = b4a.allocUnsafe(40 * this.flushing.size)
|
|
437
445
|
|
|
438
446
|
let error = null
|
|
439
447
|
let missing = this.flushing.size + 1
|
|
440
448
|
let offset = 0
|
|
441
449
|
|
|
442
450
|
for (const node of this.flushing.values()) {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
451
|
+
const state = {
|
|
452
|
+
start: 0,
|
|
453
|
+
end: 40,
|
|
454
|
+
buffer: slab.subarray(offset, offset += 40)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
c.uint64.encode(state, node.size)
|
|
458
|
+
c.raw.encode(state, node.hash)
|
|
459
|
+
|
|
460
|
+
this.storage.write(node.index * 40, state.buffer, done)
|
|
447
461
|
}
|
|
448
462
|
|
|
449
463
|
done(null)
|
|
@@ -1038,10 +1052,10 @@ function getStoredNode (storage, index, error) {
|
|
|
1038
1052
|
return
|
|
1039
1053
|
}
|
|
1040
1054
|
|
|
1041
|
-
const hash = data.
|
|
1042
|
-
const size =
|
|
1055
|
+
const hash = data.subarray(8)
|
|
1056
|
+
const size = c.decode(c.uint64, data)
|
|
1043
1057
|
|
|
1044
|
-
if (size === 0 &&
|
|
1058
|
+
if (size === 0 && b4a.compare(hash, BLANK_HASH) === 0) {
|
|
1045
1059
|
if (error) reject(new Error('Could not load node: ' + index))
|
|
1046
1060
|
else resolve(null)
|
|
1047
1061
|
return
|
|
@@ -1088,9 +1102,9 @@ function log2 (n) {
|
|
|
1088
1102
|
}
|
|
1089
1103
|
|
|
1090
1104
|
function signable (hash, length, fork) {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
return
|
|
1105
|
+
const state = { start: 0, end: 48, buffer: b4a.alloc(48) }
|
|
1106
|
+
c.raw.encode(state, hash)
|
|
1107
|
+
c.uint64.encode(state, length)
|
|
1108
|
+
c.uint64.encode(state, fork)
|
|
1109
|
+
return state.buffer
|
|
1096
1110
|
}
|
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
|
|
|
@@ -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(
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
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.
|
|
3
|
+
"version": "10.0.0-alpha.13",
|
|
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.
|
|
35
|
+
"compact-encoding": "^2.5.0",
|
|
35
36
|
"crc32-universal": "^1.0.1",
|
|
36
|
-
"flat-tree": "^1.
|
|
37
|
-
"hypercore-crypto": "^
|
|
37
|
+
"flat-tree": "^1.9.0",
|
|
38
|
+
"hypercore-crypto": "^3.1.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
|
-
"
|
|
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
|
+
})
|