hypercore 10.0.0-alpha.23 → 10.0.0-alpha.26
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 +11 -1
- package/index.js +180 -81
- package/lib/bitfield.js +6 -3
- package/lib/caps.js +34 -0
- package/lib/core.js +26 -10
- package/lib/merkle-tree.js +129 -81
- package/lib/messages.js +245 -167
- package/lib/replicator.js +1254 -574
- package/package.json +5 -3
- package/lib/extensions.js +0 -76
- package/lib/protocol.js +0 -583
- package/lib/random-iterator.js +0 -46
package/README.md
CHANGED
|
@@ -101,6 +101,10 @@ Truncate the core to a smaller length.
|
|
|
101
101
|
Per default this will update the fork id of the core to `+ 1`, but you can set the fork id you prefer with the option.
|
|
102
102
|
Note that the fork id should be monotonely incrementing.
|
|
103
103
|
|
|
104
|
+
#### `const hash = await core.treeHash([length])`
|
|
105
|
+
|
|
106
|
+
Get the Merkle Tree hash of the core at a given length, defaulting to the current length of the core.
|
|
107
|
+
|
|
104
108
|
#### `const stream = core.createReadStream([options])`
|
|
105
109
|
|
|
106
110
|
Make a read stream. Options include:
|
|
@@ -212,6 +216,12 @@ Buffer containing the public key identifying this core.
|
|
|
212
216
|
|
|
213
217
|
Populated after `ready` has been emitted. Will be `null` before the event.
|
|
214
218
|
|
|
219
|
+
#### `core.keyPair`
|
|
220
|
+
|
|
221
|
+
Object containing buffers of the core's public and secret key
|
|
222
|
+
|
|
223
|
+
Populated after `ready` has been emitted. Will be `null` before the event.
|
|
224
|
+
|
|
215
225
|
#### `core.discoveryKey`
|
|
216
226
|
|
|
217
227
|
Buffer containing a key derived from the core's public key.
|
|
@@ -274,6 +284,6 @@ socket.pipe(localCore.replicate(true)).pipe(socket)
|
|
|
274
284
|
|
|
275
285
|
Emitted when the core has been appended to (i.e. has a new length / byteLength), either locally or remotely.
|
|
276
286
|
|
|
277
|
-
#### `core.on('truncate')`
|
|
287
|
+
#### `core.on('truncate', ancestors, forkId)`
|
|
278
288
|
|
|
279
289
|
Emitted when the core has been truncated, either locally or remotely.
|
package/index.js
CHANGED
|
@@ -6,12 +6,12 @@ const c = require('compact-encoding')
|
|
|
6
6
|
const b4a = require('b4a')
|
|
7
7
|
const Xache = require('xache')
|
|
8
8
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
9
|
+
const Protomux = require('protomux')
|
|
9
10
|
const codecs = require('codecs')
|
|
10
11
|
|
|
11
12
|
const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
12
13
|
|
|
13
14
|
const Replicator = require('./lib/replicator')
|
|
14
|
-
const Extensions = require('./lib/extensions')
|
|
15
15
|
const Core = require('./lib/core')
|
|
16
16
|
const BlockEncryption = require('./lib/block-encryption')
|
|
17
17
|
const { ReadStream, WriteStream } = require('./lib/streams')
|
|
@@ -51,14 +51,15 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
51
51
|
this.core = null
|
|
52
52
|
this.replicator = null
|
|
53
53
|
this.encryption = null
|
|
54
|
-
this.extensions = opts.extensions || new
|
|
54
|
+
this.extensions = opts.extensions || new Map()
|
|
55
55
|
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
56
56
|
|
|
57
57
|
this.valueEncoding = null
|
|
58
58
|
this.encodeBatch = null
|
|
59
|
+
this.activeRequests = []
|
|
59
60
|
|
|
60
61
|
this.key = key || null
|
|
61
|
-
this.
|
|
62
|
+
this.keyPair = null
|
|
62
63
|
this.readable = true
|
|
63
64
|
this.writable = false
|
|
64
65
|
this.opened = false
|
|
@@ -72,6 +73,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
72
73
|
this.opening.catch(noop)
|
|
73
74
|
|
|
74
75
|
this._preappend = preappend.bind(this)
|
|
76
|
+
this._snapshot = opts.snapshot || null
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
[inspect] (depth, opts) {
|
|
@@ -92,10 +94,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
92
94
|
indent + ')'
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
static protomux (stream, opts) {
|
|
98
|
+
return stream.noiseStream.userData.open(opts)
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
static createProtocolStream (isInitiator, opts = {}) {
|
|
96
|
-
let outerStream =
|
|
97
|
-
? isInitiator
|
|
98
|
-
:
|
|
102
|
+
let outerStream = Protomux.isProtomux(isInitiator)
|
|
103
|
+
? isInitiator.stream
|
|
104
|
+
: isStream(isInitiator)
|
|
105
|
+
? isInitiator
|
|
106
|
+
: opts.stream
|
|
107
|
+
|
|
99
108
|
let noiseStream = null
|
|
100
109
|
|
|
101
110
|
if (outerStream) {
|
|
@@ -107,10 +116,16 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
107
116
|
if (!noiseStream) throw new Error('Invalid stream')
|
|
108
117
|
|
|
109
118
|
if (!noiseStream.userData) {
|
|
110
|
-
const protocol =
|
|
111
|
-
|
|
119
|
+
const protocol = new Protomux(noiseStream)
|
|
120
|
+
|
|
121
|
+
if (opts.ondiscoverykey) {
|
|
122
|
+
protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
|
|
123
|
+
}
|
|
124
|
+
if (opts.keepAlive !== false) {
|
|
125
|
+
noiseStream.setKeepAlive(5000)
|
|
126
|
+
noiseStream.setTimeout(7000)
|
|
127
|
+
}
|
|
112
128
|
noiseStream.userData = protocol
|
|
113
|
-
noiseStream.on('error', noop) // All noise errors already propagate through outerStream
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
return outerStream
|
|
@@ -128,6 +143,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
128
143
|
}
|
|
129
144
|
}
|
|
130
145
|
|
|
146
|
+
snapshot () {
|
|
147
|
+
return this.session({ snapshot: { length: this.length, byteLength: this.byteLength, fork: this.fork } })
|
|
148
|
+
}
|
|
149
|
+
|
|
131
150
|
session (opts = {}) {
|
|
132
151
|
if (this.closing) {
|
|
133
152
|
// This makes the closing logic alot easier. If this turns out to be a problem
|
|
@@ -153,7 +172,6 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
153
172
|
if (!this.sign) this.sign = o.sign
|
|
154
173
|
this.crypto = o.crypto
|
|
155
174
|
this.key = o.key
|
|
156
|
-
this.discoveryKey = o.discoveryKey
|
|
157
175
|
this.core = o.core
|
|
158
176
|
this.replicator = o.replicator
|
|
159
177
|
this.encryption = o.encryption
|
|
@@ -231,10 +249,12 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
231
249
|
this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
232
250
|
|
|
233
251
|
this.core = await Core.open(this.storage, {
|
|
252
|
+
force: opts.force,
|
|
234
253
|
createIfMissing: opts.createIfMissing,
|
|
235
254
|
overwrite: opts.overwrite,
|
|
236
255
|
keyPair,
|
|
237
256
|
crypto: this.crypto,
|
|
257
|
+
legacy: opts.legacy,
|
|
238
258
|
onupdate: this._oncoreupdate.bind(this)
|
|
239
259
|
})
|
|
240
260
|
|
|
@@ -244,18 +264,19 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
244
264
|
}
|
|
245
265
|
}
|
|
246
266
|
|
|
247
|
-
this.replicator = new Replicator(this.core, {
|
|
248
|
-
onupdate: this._onpeerupdate.bind(this)
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
252
267
|
this.key = this.core.header.signer.publicKey
|
|
268
|
+
this.keyPair = this.core.header.signer
|
|
269
|
+
|
|
270
|
+
this.replicator = new Replicator(this.core, this.key, {
|
|
271
|
+
eagerUpdate: true,
|
|
272
|
+
allowFork: opts.allowFork !== false,
|
|
273
|
+
onpeerupdate: this._onpeerupdate.bind(this),
|
|
274
|
+
onupload: this._onupload.bind(this)
|
|
275
|
+
})
|
|
253
276
|
|
|
254
277
|
if (!this.encryption && opts.encryptionKey) {
|
|
255
278
|
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
256
279
|
}
|
|
257
|
-
|
|
258
|
-
this.extensions.attach(this.replicator)
|
|
259
280
|
}
|
|
260
281
|
|
|
261
282
|
close () {
|
|
@@ -274,6 +295,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
274
295
|
this.readable = false
|
|
275
296
|
this.writable = false
|
|
276
297
|
this.closed = true
|
|
298
|
+
this.opened = false
|
|
299
|
+
|
|
300
|
+
const gc = []
|
|
301
|
+
for (const ext of this.extensions.values()) {
|
|
302
|
+
if (ext.session === this) gc.push(ext)
|
|
303
|
+
}
|
|
304
|
+
for (const ext of gc) ext.destroy()
|
|
305
|
+
|
|
306
|
+
if (this.replicator !== null) {
|
|
307
|
+
this.replicator.clearRequests(this.activeRequests)
|
|
308
|
+
}
|
|
277
309
|
|
|
278
310
|
if (this.sessions.length) {
|
|
279
311
|
// if this is the last session and we are auto closing, trigger that first to enforce error handling
|
|
@@ -294,24 +326,34 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
294
326
|
const protocol = noiseStream.userData
|
|
295
327
|
|
|
296
328
|
if (this.opened) {
|
|
297
|
-
this.replicator.
|
|
329
|
+
this.replicator.attachTo(protocol)
|
|
298
330
|
} else {
|
|
299
|
-
this.opening.then(() => this.replicator.
|
|
331
|
+
this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
|
|
300
332
|
}
|
|
301
333
|
|
|
302
334
|
return protocolStream
|
|
303
335
|
}
|
|
304
336
|
|
|
337
|
+
get discoveryKey () {
|
|
338
|
+
return this.replicator === null ? null : this.replicator.discoveryKey
|
|
339
|
+
}
|
|
340
|
+
|
|
305
341
|
get length () {
|
|
306
|
-
return this.
|
|
342
|
+
return this._snapshot
|
|
343
|
+
? this._snapshot.length
|
|
344
|
+
: (this.core === null ? 0 : this.core.tree.length)
|
|
307
345
|
}
|
|
308
346
|
|
|
309
347
|
get byteLength () {
|
|
310
|
-
return this.
|
|
348
|
+
return this._snapshot
|
|
349
|
+
? this._snapshot.byteLength
|
|
350
|
+
: (this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding))
|
|
311
351
|
}
|
|
312
352
|
|
|
313
353
|
get fork () {
|
|
314
|
-
return this.
|
|
354
|
+
return this._snapshot
|
|
355
|
+
? this._snapshot.fork
|
|
356
|
+
: (this.core === null ? 0 : this.core.tree.fork)
|
|
315
357
|
}
|
|
316
358
|
|
|
317
359
|
get peers () {
|
|
@@ -330,25 +372,31 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
330
372
|
return this.opening
|
|
331
373
|
}
|
|
332
374
|
|
|
375
|
+
_onupload (index, value, from) {
|
|
376
|
+
const byteLength = value.byteLength - this.padding
|
|
377
|
+
|
|
378
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
379
|
+
this.sessions[i].emit('upload', index, byteLength, from)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
333
383
|
_oncoreupdate (status, bitfield, value, from) {
|
|
334
384
|
if (status !== 0) {
|
|
335
385
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
336
386
|
if ((status & 0b10) !== 0) {
|
|
337
387
|
if (this.cache) this.cache.clear()
|
|
338
|
-
this.sessions[i].emit('truncate', this.core.tree.fork)
|
|
388
|
+
this.sessions[i].emit('truncate', bitfield.start, this.core.tree.fork)
|
|
339
389
|
}
|
|
340
390
|
if ((status & 0b01) !== 0) {
|
|
341
391
|
this.sessions[i].emit('append')
|
|
342
392
|
}
|
|
343
393
|
}
|
|
344
394
|
|
|
345
|
-
this.replicator.
|
|
395
|
+
this.replicator.signalUpgrade()
|
|
346
396
|
}
|
|
347
397
|
|
|
348
|
-
if (bitfield
|
|
349
|
-
|
|
350
|
-
this.replicator.broadcastBlock(bitfield.start + i)
|
|
351
|
-
}
|
|
398
|
+
if (bitfield) {
|
|
399
|
+
this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
|
|
352
400
|
}
|
|
353
401
|
|
|
354
402
|
if (value) {
|
|
@@ -361,9 +409,14 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
361
409
|
}
|
|
362
410
|
|
|
363
411
|
_onpeerupdate (added, peer) {
|
|
364
|
-
if (added) this.extensions.update(peer)
|
|
365
412
|
const name = added ? 'peer-add' : 'peer-remove'
|
|
366
413
|
|
|
414
|
+
if (added) {
|
|
415
|
+
for (const ext of this.extensions.values()) {
|
|
416
|
+
peer.extensions.set(ext.name, ext)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
367
420
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
368
421
|
this.sessions[i].emit(name, peer)
|
|
369
422
|
}
|
|
@@ -382,19 +435,32 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
382
435
|
return null
|
|
383
436
|
}
|
|
384
437
|
|
|
385
|
-
async update () {
|
|
438
|
+
async update (opts) {
|
|
386
439
|
if (this.opened === false) await this.opening
|
|
440
|
+
|
|
387
441
|
// TODO: add an option where a writer can bootstrap it's state from the network also
|
|
388
|
-
if (this.writable) return false
|
|
389
|
-
|
|
442
|
+
if (this.writable || this.closing !== null) return false
|
|
443
|
+
|
|
444
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
445
|
+
const req = this.replicator.addUpgrade(activeRequests)
|
|
446
|
+
|
|
447
|
+
return req.promise
|
|
390
448
|
}
|
|
391
449
|
|
|
392
|
-
async seek (bytes) {
|
|
450
|
+
async seek (bytes, opts) {
|
|
393
451
|
if (this.opened === false) await this.opening
|
|
394
452
|
|
|
395
453
|
const s = this.core.tree.seek(bytes, this.padding)
|
|
396
454
|
|
|
397
|
-
|
|
455
|
+
const offset = await s.update()
|
|
456
|
+
if (offset) return offset
|
|
457
|
+
|
|
458
|
+
if (this.closing !== null) throw new Error('Session is closed')
|
|
459
|
+
|
|
460
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
461
|
+
const req = this.replicator.addSeek(activeRequests, s)
|
|
462
|
+
|
|
463
|
+
return req.promise
|
|
398
464
|
}
|
|
399
465
|
|
|
400
466
|
async has (index) {
|
|
@@ -405,6 +471,8 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
405
471
|
|
|
406
472
|
async get (index, opts) {
|
|
407
473
|
if (this.opened === false) await this.opening
|
|
474
|
+
if (this.closing !== null) throw new Error('Session is closed')
|
|
475
|
+
|
|
408
476
|
const c = this.cache && this.cache.get(index)
|
|
409
477
|
if (c) return c
|
|
410
478
|
const fork = this.core.tree.fork
|
|
@@ -423,7 +491,11 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
423
491
|
} else {
|
|
424
492
|
if (opts && opts.wait === false) return null
|
|
425
493
|
if (opts && opts.onwait) opts.onwait(index)
|
|
426
|
-
|
|
494
|
+
|
|
495
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
496
|
+
const req = this.replicator.addBlock(activeRequests, index)
|
|
497
|
+
|
|
498
|
+
block = await req.promise
|
|
427
499
|
}
|
|
428
500
|
|
|
429
501
|
if (this.encryption) this.encryption.decrypt(index, block)
|
|
@@ -439,37 +511,27 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
439
511
|
}
|
|
440
512
|
|
|
441
513
|
download (range) {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
filter = (i) => blocks.has(i)
|
|
457
|
-
} else {
|
|
458
|
-
start = (range && range.start) || 0
|
|
459
|
-
end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
514
|
+
const reqP = this._download(range)
|
|
515
|
+
|
|
516
|
+
// do not crash in the background...
|
|
517
|
+
reqP.catch(noop)
|
|
518
|
+
|
|
519
|
+
// TODO: turn this into an actual object...
|
|
520
|
+
return {
|
|
521
|
+
async downloaded () {
|
|
522
|
+
const req = await reqP
|
|
523
|
+
return req.promise
|
|
524
|
+
},
|
|
525
|
+
destroy () {
|
|
526
|
+
reqP.then(req => req.context && req.context.detach(req), noop)
|
|
527
|
+
}
|
|
460
528
|
}
|
|
461
|
-
|
|
462
|
-
const r = Replicator.createRange(start, end, filter, linear)
|
|
463
|
-
|
|
464
|
-
if (this.opened) this.replicator.addRange(r)
|
|
465
|
-
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
466
|
-
|
|
467
|
-
return r
|
|
468
529
|
}
|
|
469
530
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
531
|
+
async _download (range) {
|
|
532
|
+
if (this.opened === false) await this.opening
|
|
533
|
+
const activeRequests = (range && range.activeRequests) || this.activeRequests
|
|
534
|
+
return this.replicator.addRange(activeRequests, range)
|
|
473
535
|
}
|
|
474
536
|
|
|
475
537
|
// TODO: get rid of this / deprecate it?
|
|
@@ -477,6 +539,11 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
477
539
|
range.destroy(null)
|
|
478
540
|
}
|
|
479
541
|
|
|
542
|
+
// TODO: get rid of this / deprecate it?
|
|
543
|
+
cancel (request) {
|
|
544
|
+
// Do nothing for now
|
|
545
|
+
}
|
|
546
|
+
|
|
480
547
|
async truncate (newLength = 0, fork = -1) {
|
|
481
548
|
if (this.opened === false) await this.opening
|
|
482
549
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
@@ -507,13 +574,58 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
507
574
|
return await this.core.append(buffers, this.sign, { preappend })
|
|
508
575
|
}
|
|
509
576
|
|
|
510
|
-
|
|
511
|
-
|
|
577
|
+
async treeHash (length) {
|
|
578
|
+
if (length === undefined) {
|
|
579
|
+
await this.ready()
|
|
580
|
+
length = this.core.length
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const roots = await this.core.tree.getRoots(length)
|
|
584
|
+
return this.crypto.tree(roots)
|
|
512
585
|
}
|
|
513
586
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
587
|
+
registerExtension (name, handlers = {}) {
|
|
588
|
+
if (this.extensions.has(name)) {
|
|
589
|
+
const ext = this.extensions.get(name)
|
|
590
|
+
ext.handlers = handlers
|
|
591
|
+
ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
|
|
592
|
+
ext.session = this
|
|
593
|
+
return ext
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const ext = {
|
|
597
|
+
name,
|
|
598
|
+
handlers,
|
|
599
|
+
encoding: c.from(codecs(handlers.encoding) || c.buffer),
|
|
600
|
+
session: this,
|
|
601
|
+
send (message, peer) {
|
|
602
|
+
const buffer = c.encode(this.encoding, message)
|
|
603
|
+
peer.extension(name, buffer)
|
|
604
|
+
},
|
|
605
|
+
broadcast (message) {
|
|
606
|
+
const buffer = c.encode(this.encoding, message)
|
|
607
|
+
for (const peer of this.session.peers) {
|
|
608
|
+
peer.extension(name, buffer)
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
destroy () {
|
|
612
|
+
for (const peer of this.session.peers) {
|
|
613
|
+
peer.extensions.delete(name)
|
|
614
|
+
}
|
|
615
|
+
this.session.extensions.delete(name)
|
|
616
|
+
},
|
|
617
|
+
_onmessage (state, peer) {
|
|
618
|
+
const m = this.encoding.decode(state)
|
|
619
|
+
if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.extensions.set(name, ext)
|
|
624
|
+
for (const peer of this.peers) {
|
|
625
|
+
peer.extensions.set(name, ext)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return ext
|
|
517
629
|
}
|
|
518
630
|
|
|
519
631
|
_encode (enc, val) {
|
|
@@ -563,19 +675,6 @@ function toHex (buf) {
|
|
|
563
675
|
return buf && b4a.toString(buf, 'hex')
|
|
564
676
|
}
|
|
565
677
|
|
|
566
|
-
function reduce (iter, fn, acc) {
|
|
567
|
-
for (const item of iter) acc = fn(acc, item)
|
|
568
|
-
return acc
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function min (arr) {
|
|
572
|
-
return reduce(arr, (a, b) => Math.min(a, b), Infinity)
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function max (arr) {
|
|
576
|
-
return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
|
|
577
|
-
}
|
|
578
|
-
|
|
579
678
|
function preappend (blocks) {
|
|
580
679
|
const offset = this.core.tree.length
|
|
581
680
|
const fork = this.core.tree.fork
|
package/lib/bitfield.js
CHANGED
|
@@ -41,8 +41,9 @@ module.exports = class Bitfield {
|
|
|
41
41
|
this.pages = new BigSparseArray()
|
|
42
42
|
this.unflushed = []
|
|
43
43
|
this.storage = storage
|
|
44
|
+
this.resumed = !!(buf && buf.byteLength >= 4)
|
|
44
45
|
|
|
45
|
-
const all =
|
|
46
|
+
const all = this.resumed
|
|
46
47
|
? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
|
|
47
48
|
: new Uint32Array(1024)
|
|
48
49
|
|
|
@@ -93,8 +94,10 @@ module.exports = class Bitfield {
|
|
|
93
94
|
clear () {
|
|
94
95
|
return new Promise((resolve, reject) => {
|
|
95
96
|
this.storage.del(0, Infinity, (err) => {
|
|
96
|
-
if (err) reject(err)
|
|
97
|
-
|
|
97
|
+
if (err) return reject(err)
|
|
98
|
+
this.pages = new BigSparseArray()
|
|
99
|
+
this.unflushed = []
|
|
100
|
+
resolve()
|
|
98
101
|
})
|
|
99
102
|
})
|
|
100
103
|
}
|
package/lib/caps.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const crypto = require('hypercore-crypto')
|
|
2
|
+
const sodium = require('sodium-universal')
|
|
3
|
+
const b4a = require('b4a')
|
|
4
|
+
const c = require('compact-encoding')
|
|
5
|
+
|
|
6
|
+
// TODO: rename this to "crypto" and move everything hashing related etc in here
|
|
7
|
+
// Also lets move the tree stuff from hypercore-crypto here, and loose the types
|
|
8
|
+
// from the hashes there - they are not needed since we lock the indexes in the tree
|
|
9
|
+
// hash and just makes alignment etc harder in other languages
|
|
10
|
+
|
|
11
|
+
const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
|
|
12
|
+
|
|
13
|
+
exports.replicate = function (isInitiator, key, handshakeHash) {
|
|
14
|
+
const out = b4a.allocUnsafe(32)
|
|
15
|
+
sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
|
|
16
|
+
return out
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.treeSignable = function (hash, length, fork) {
|
|
20
|
+
const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
|
|
21
|
+
c.raw.encode(state, TREE)
|
|
22
|
+
c.raw.encode(state, hash)
|
|
23
|
+
c.uint64.encode(state, length)
|
|
24
|
+
c.uint64.encode(state, fork)
|
|
25
|
+
return state.buffer
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.treeSignableLegacy = function (hash, length, fork) {
|
|
29
|
+
const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
|
|
30
|
+
c.raw.encode(state, hash)
|
|
31
|
+
c.uint64.encode(state, length)
|
|
32
|
+
c.uint64.encode(state, fork)
|
|
33
|
+
return state.buffer
|
|
34
|
+
}
|
package/lib/core.js
CHANGED
|
@@ -5,10 +5,10 @@ const Mutex = require('./mutex')
|
|
|
5
5
|
const MerkleTree = require('./merkle-tree')
|
|
6
6
|
const BlockStore = require('./block-store')
|
|
7
7
|
const Bitfield = require('./bitfield')
|
|
8
|
-
const
|
|
8
|
+
const m = require('./messages')
|
|
9
9
|
|
|
10
10
|
module.exports = class Core {
|
|
11
|
-
constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
|
|
11
|
+
constructor (header, crypto, oplog, tree, blocks, bitfield, sign, legacy, onupdate) {
|
|
12
12
|
this.onupdate = onupdate
|
|
13
13
|
this.header = header
|
|
14
14
|
this.crypto = crypto
|
|
@@ -24,6 +24,7 @@ module.exports = class Core {
|
|
|
24
24
|
this._verifies = null
|
|
25
25
|
this._verifiesFlushed = null
|
|
26
26
|
this._mutex = new Mutex()
|
|
27
|
+
this._legacy = legacy
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
static async open (storage, opts = {}) {
|
|
@@ -57,18 +58,24 @@ module.exports = class Core {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
|
|
60
|
-
|
|
61
|
+
let overwrite = opts.overwrite === true
|
|
62
|
+
|
|
63
|
+
const force = opts.force === true
|
|
61
64
|
const createIfMissing = opts.createIfMissing !== false
|
|
62
65
|
const crypto = opts.crypto || hypercoreCrypto
|
|
63
66
|
|
|
64
67
|
const oplog = new Oplog(oplogFile, {
|
|
65
|
-
headerEncoding:
|
|
66
|
-
entryEncoding:
|
|
68
|
+
headerEncoding: m.oplog.header,
|
|
69
|
+
entryEncoding: m.oplog.entry
|
|
67
70
|
})
|
|
68
71
|
|
|
69
72
|
let { header, entries } = await oplog.open()
|
|
70
73
|
|
|
71
|
-
if (
|
|
74
|
+
if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
|
|
75
|
+
overwrite = true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!header || overwrite) {
|
|
72
79
|
if (!createIfMissing) {
|
|
73
80
|
throw new Error('No hypercore is stored here')
|
|
74
81
|
}
|
|
@@ -103,6 +110,10 @@ module.exports = class Core {
|
|
|
103
110
|
await tree.clear()
|
|
104
111
|
await blocks.clear()
|
|
105
112
|
await bitfield.clear()
|
|
113
|
+
entries = []
|
|
114
|
+
} else if (bitfield.resumed && header.tree.length === 0) {
|
|
115
|
+
// If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
|
|
116
|
+
await bitfield.clear()
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
|
|
@@ -119,7 +130,7 @@ module.exports = class Core {
|
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
if (e.bitfield) {
|
|
122
|
-
bitfield.setRange(e.bitfield.start, e.bitfield.length)
|
|
133
|
+
bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
if (e.treeUpgrade) {
|
|
@@ -136,7 +147,7 @@ module.exports = class Core {
|
|
|
136
147
|
}
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
|
|
150
|
+
return new this(header, crypto, oplog, tree, blocks, bitfield, sign, !!opts.legacy, opts.onupdate || noop)
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
_shouldFlush () {
|
|
@@ -227,7 +238,7 @@ module.exports = class Core {
|
|
|
227
238
|
for (const val of values) batch.append(val)
|
|
228
239
|
|
|
229
240
|
const hash = batch.hash()
|
|
230
|
-
batch.signature = await sign(batch.signable(hash))
|
|
241
|
+
batch.signature = await sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
|
|
231
242
|
|
|
232
243
|
const entry = {
|
|
233
244
|
userData: null,
|
|
@@ -259,10 +270,15 @@ module.exports = class Core {
|
|
|
259
270
|
}
|
|
260
271
|
}
|
|
261
272
|
|
|
273
|
+
_signed (batch, hash) {
|
|
274
|
+
const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
|
|
275
|
+
return this.crypto.verify(signable, batch.signature, this.header.signer.publicKey)
|
|
276
|
+
}
|
|
277
|
+
|
|
262
278
|
async _verifyExclusive ({ batch, bitfield, value, from }) {
|
|
263
279
|
// TODO: move this to tree.js
|
|
264
280
|
const hash = batch.hash()
|
|
265
|
-
if (!batch.signature || !this.
|
|
281
|
+
if (!batch.signature || !this._signed(batch, hash)) {
|
|
266
282
|
throw new Error('Remote signature does not match')
|
|
267
283
|
}
|
|
268
284
|
|