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/.github/workflows/test-node.yml +1 -2
- package/README.md +17 -1
- package/__snapshots__/test/storage.js.snapshot.cjs +15 -0
- package/index.js +135 -34
- 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/package.json +6 -4
- package/test/basic.js +12 -0
- package/test/encryption.js +123 -0
- package/test/replicate.js +76 -0
- package/test/storage.js +31 -0
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ 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 b4a = require('b4a')
|
|
7
|
+
const Xache = require('xache')
|
|
6
8
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
7
9
|
const codecs = require('codecs')
|
|
8
10
|
|
|
@@ -11,6 +13,7 @@ const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
|
11
13
|
const Replicator = require('./lib/replicator')
|
|
12
14
|
const Extensions = require('./lib/extensions')
|
|
13
15
|
const Core = require('./lib/core')
|
|
16
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
14
17
|
|
|
15
18
|
const promises = Symbol.for('hypercore.promises')
|
|
16
19
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -27,14 +30,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
27
30
|
opts = key
|
|
28
31
|
key = null
|
|
29
32
|
}
|
|
33
|
+
|
|
30
34
|
if (key && typeof key === 'string') {
|
|
31
|
-
key =
|
|
35
|
+
key = b4a.from(key, 'hex')
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
|
|
38
|
+
if (!opts) opts = {}
|
|
39
|
+
|
|
40
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
34
41
|
throw new Error('Hypercore key should be 32 bytes')
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
if (!opts) opts = {}
|
|
38
44
|
if (!storage) storage = opts.storage
|
|
39
45
|
|
|
40
46
|
this[promises] = true
|
|
@@ -43,7 +49,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
43
49
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
44
50
|
this.core = null
|
|
45
51
|
this.replicator = null
|
|
52
|
+
this.encryption = null
|
|
46
53
|
this.extensions = opts.extensions || new Extensions()
|
|
54
|
+
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
47
55
|
|
|
48
56
|
this.valueEncoding = null
|
|
49
57
|
this.key = key || null
|
|
@@ -59,6 +67,8 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
59
67
|
this.closing = null
|
|
60
68
|
this.opening = opts._opening || this._open(key, storage, opts)
|
|
61
69
|
this.opening.catch(noop)
|
|
70
|
+
|
|
71
|
+
this._preappend = preappend.bind(this)
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
[inspect] (depth, opts) {
|
|
@@ -89,8 +99,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
89
99
|
const directory = storage
|
|
90
100
|
const toLock = opts.lock || 'oplog'
|
|
91
101
|
return function createFile (name) {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
102
|
+
const locked = name === toLock || name.endsWith('/' + toLock)
|
|
103
|
+
const lock = locked ? fsctl.lock : null
|
|
104
|
+
const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
|
|
94
105
|
return raf(name, { directory, lock, sparse })
|
|
95
106
|
}
|
|
96
107
|
}
|
|
@@ -132,6 +143,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
132
143
|
this.discoveryKey = o.discoveryKey
|
|
133
144
|
this.core = o.core
|
|
134
145
|
this.replicator = o.replicator
|
|
146
|
+
this.encryption = o.encryption
|
|
135
147
|
this.writable = !!this.sign
|
|
136
148
|
this.autoClose = o.autoClose
|
|
137
149
|
}
|
|
@@ -201,7 +213,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
get byteLength () {
|
|
204
|
-
return this.core === null ? 0 : this.core.tree.byteLength
|
|
216
|
+
return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
get fork () {
|
|
@@ -212,6 +224,14 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
212
224
|
return this.replicator === null ? [] : this.replicator.peers
|
|
213
225
|
}
|
|
214
226
|
|
|
227
|
+
get encryptionKey () {
|
|
228
|
+
return this.encryption && this.encryption.key
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get padding () {
|
|
232
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
ready () {
|
|
216
236
|
return this.opening
|
|
217
237
|
}
|
|
@@ -265,6 +285,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
265
285
|
this.key = this.core.header.signer.publicKey
|
|
266
286
|
this.writable = !!this.sign
|
|
267
287
|
|
|
288
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
289
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
290
|
+
}
|
|
291
|
+
|
|
268
292
|
this.extensions.attach(this.replicator)
|
|
269
293
|
this.opened = true
|
|
270
294
|
|
|
@@ -280,8 +304,13 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
280
304
|
_oncoreupdate (status, bitfield, value, from) {
|
|
281
305
|
if (status !== 0) {
|
|
282
306
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
283
|
-
if ((status & 0b10) !== 0)
|
|
284
|
-
|
|
307
|
+
if ((status & 0b10) !== 0) {
|
|
308
|
+
if (this.cache) this.cache.clear()
|
|
309
|
+
this.sessions[i].emit('truncate', this.core.tree.fork)
|
|
310
|
+
}
|
|
311
|
+
if ((status & 0b01) !== 0) {
|
|
312
|
+
this.sessions[i].emit('append')
|
|
313
|
+
}
|
|
285
314
|
}
|
|
286
315
|
|
|
287
316
|
this.replicator.broadcastInfo()
|
|
@@ -294,8 +323,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
294
323
|
}
|
|
295
324
|
|
|
296
325
|
if (value) {
|
|
326
|
+
const byteLength = value.byteLength - this.padding
|
|
327
|
+
|
|
297
328
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
298
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
329
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
299
330
|
}
|
|
300
331
|
}
|
|
301
332
|
}
|
|
@@ -332,7 +363,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
332
363
|
async seek (bytes) {
|
|
333
364
|
if (this.opened === false) await this.opening
|
|
334
365
|
|
|
335
|
-
const s = this.core.tree.seek(bytes)
|
|
366
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
336
367
|
|
|
337
368
|
return (await s.update()) || this.replicator.requestSeek(s)
|
|
338
369
|
}
|
|
@@ -345,22 +376,52 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
345
376
|
|
|
346
377
|
async get (index, opts) {
|
|
347
378
|
if (this.opened === false) await this.opening
|
|
379
|
+
const c = this.cache && this.cache.get(index)
|
|
380
|
+
if (c) return c
|
|
381
|
+
const fork = this.core.tree.fork
|
|
382
|
+
const b = await this._get(index, opts)
|
|
383
|
+
if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
|
|
384
|
+
return b
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async _get (index, opts) {
|
|
348
388
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
349
389
|
|
|
350
|
-
|
|
351
|
-
|
|
390
|
+
let block
|
|
391
|
+
|
|
392
|
+
if (this.core.bitfield.get(index)) {
|
|
393
|
+
block = await this.core.blocks.get(index)
|
|
394
|
+
} else {
|
|
395
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
396
|
+
block = await this.replicator.requestBlock(index)
|
|
397
|
+
}
|
|
352
398
|
|
|
353
|
-
|
|
399
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
400
|
+
return this._decode(encoding, block)
|
|
354
401
|
}
|
|
355
402
|
|
|
356
403
|
download (range) {
|
|
357
|
-
const start = (range && range.start) || 0
|
|
358
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
359
404
|
const linear = !!(range && range.linear)
|
|
360
405
|
|
|
361
|
-
|
|
406
|
+
let start
|
|
407
|
+
let end
|
|
408
|
+
let filter
|
|
409
|
+
|
|
410
|
+
if (range && range.blocks) {
|
|
411
|
+
const blocks = range.blocks instanceof Set
|
|
412
|
+
? range.blocks
|
|
413
|
+
: new Set(range.blocks)
|
|
414
|
+
|
|
415
|
+
start = range.start || (blocks.size ? min(range.blocks) : 0)
|
|
416
|
+
end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
|
|
417
|
+
|
|
418
|
+
filter = (i) => blocks.has(i)
|
|
419
|
+
} else {
|
|
420
|
+
start = (range && range.start) || 0
|
|
421
|
+
end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
422
|
+
}
|
|
362
423
|
|
|
363
|
-
const r = Replicator.createRange(start, end, linear)
|
|
424
|
+
const r = Replicator.createRange(start, end, filter, linear)
|
|
364
425
|
|
|
365
426
|
if (this.opened) this.replicator.addRange(r)
|
|
366
427
|
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
@@ -393,22 +454,16 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
393
454
|
if (this.opened === false) await this.opening
|
|
394
455
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
395
456
|
|
|
396
|
-
|
|
397
|
-
const buffers = new Array(blks.length)
|
|
457
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
398
458
|
|
|
399
|
-
|
|
400
|
-
|
|
459
|
+
const preappend = this.encryption && this._preappend
|
|
460
|
+
const buffers = new Array(blocks.length)
|
|
401
461
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: this.valueEncoding
|
|
405
|
-
? c.encode(this.valueEncoding, blk)
|
|
406
|
-
: Buffer.from(blk)
|
|
407
|
-
|
|
408
|
-
buffers[i] = buf
|
|
462
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
463
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
409
464
|
}
|
|
410
465
|
|
|
411
|
-
return await this.core.append(buffers, this.sign)
|
|
466
|
+
return await this.core.append(buffers, this.sign, { preappend })
|
|
412
467
|
}
|
|
413
468
|
|
|
414
469
|
registerExtension (name, handlers) {
|
|
@@ -419,14 +474,38 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
419
474
|
onextensionupdate () {
|
|
420
475
|
if (this.replicator !== null) this.replicator.broadcastOptions()
|
|
421
476
|
}
|
|
422
|
-
}
|
|
423
477
|
|
|
424
|
-
|
|
478
|
+
_encode (enc, val) {
|
|
479
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
480
|
+
|
|
481
|
+
if (b4a.isBuffer(val)) {
|
|
482
|
+
if (state.start === 0) return val
|
|
483
|
+
state.end += val.byteLength
|
|
484
|
+
} else if (enc) {
|
|
485
|
+
enc.preencode(state, val)
|
|
486
|
+
} else {
|
|
487
|
+
val = b4a.from(val)
|
|
488
|
+
if (state.start === 0) return val
|
|
489
|
+
state.end += val.byteLength
|
|
490
|
+
}
|
|
425
491
|
|
|
426
|
-
|
|
427
|
-
|
|
492
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
493
|
+
|
|
494
|
+
if (enc) enc.encode(state, val)
|
|
495
|
+
else state.buffer.set(val, state.start)
|
|
496
|
+
|
|
497
|
+
return state.buffer
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
_decode (enc, block) {
|
|
501
|
+
block = block.subarray(this.padding)
|
|
502
|
+
if (enc) return c.decode(enc, block)
|
|
503
|
+
return block
|
|
504
|
+
}
|
|
428
505
|
}
|
|
429
506
|
|
|
507
|
+
function noop () {}
|
|
508
|
+
|
|
430
509
|
function isStream (s) {
|
|
431
510
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
432
511
|
}
|
|
@@ -440,5 +519,27 @@ function requireMaybe (name) {
|
|
|
440
519
|
}
|
|
441
520
|
|
|
442
521
|
function toHex (buf) {
|
|
443
|
-
return buf &&
|
|
522
|
+
return buf && b4a.toString(buf, 'hex')
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function reduce (iter, fn, acc) {
|
|
526
|
+
for (const item of iter) acc = fn(acc, item)
|
|
527
|
+
return acc
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function min (arr) {
|
|
531
|
+
return reduce(arr, (a, b) => Math.min(a, b), Infinity)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function max (arr) {
|
|
535
|
+
return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function preappend (blocks) {
|
|
539
|
+
const offset = this.core.tree.length
|
|
540
|
+
const fork = this.core.tree.fork
|
|
541
|
+
|
|
542
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
543
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
544
|
+
}
|
|
444
545
|
}
|
package/lib/bitfield.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// TODO: needs massive improvements obvs
|
|
2
2
|
|
|
3
3
|
const BigSparseArray = require('big-sparse-array')
|
|
4
|
+
const b4a = require('b4a')
|
|
4
5
|
|
|
5
6
|
class FixedBitfield {
|
|
6
7
|
constructor (index, bitfield) {
|
|
@@ -116,9 +117,9 @@ module.exports = class Bitfield {
|
|
|
116
117
|
let error = null
|
|
117
118
|
|
|
118
119
|
for (const page of this.unflushed) {
|
|
119
|
-
const
|
|
120
|
+
const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
|
|
120
121
|
page.dirty = false
|
|
121
|
-
this.storage.write(page.index * 4096,
|
|
122
|
+
this.storage.write(page.index * 4096, buf, done)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function done (err) {
|
|
@@ -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
|
@@ -214,10 +214,12 @@ module.exports = class Core {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
async append (values, sign = this.defaultSign) {
|
|
217
|
+
async append (values, sign = this.defaultSign, hooks = {}) {
|
|
218
218
|
await this._mutex.lock()
|
|
219
219
|
|
|
220
220
|
try {
|
|
221
|
+
if (hooks.preappend) await hooks.preappend(values)
|
|
222
|
+
|
|
221
223
|
if (!values.length) return this.tree.length
|
|
222
224
|
|
|
223
225
|
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) {
|
|
@@ -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
|