hypercore 10.20.2 → 10.21.0
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/index.js +23 -29
- package/lib/big-header.js +55 -0
- package/lib/caps.js +15 -1
- package/lib/core.js +111 -46
- package/lib/merkle-tree.js +5 -2
- package/lib/messages.js +235 -22
- package/lib/replicator.js +17 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -243,6 +243,11 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
243
243
|
return s
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
setKeyPair (keyPair) {
|
|
247
|
+
this.auth = Core.createAuth(this.crypto, { keyPair })
|
|
248
|
+
this.writable = !this._readonly && !!this.auth && !!this.auth.sign
|
|
249
|
+
}
|
|
250
|
+
|
|
246
251
|
_passCapabilities (o) {
|
|
247
252
|
if (!this.auth) this.auth = o.auth
|
|
248
253
|
|
|
@@ -252,7 +257,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
252
257
|
this.core = o.core
|
|
253
258
|
this.replicator = o.replicator
|
|
254
259
|
this.encryption = o.encryption
|
|
255
|
-
this.writable = !this._readonly && !!
|
|
260
|
+
this.writable = !this._readonly && !!this.auth && !!this.auth.sign
|
|
256
261
|
this.autoClose = o.autoClose
|
|
257
262
|
|
|
258
263
|
if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
|
|
@@ -282,26 +287,16 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
282
287
|
if (!isFirst) await opts._opening
|
|
283
288
|
if (opts.preload) opts = { ...opts, ...(await this._retryPreload(opts.preload)) }
|
|
284
289
|
|
|
285
|
-
const keyPair =
|
|
286
|
-
? { ...opts.keyPair, publicKey: key }
|
|
287
|
-
: key
|
|
288
|
-
? { publicKey: key, secretKey: null }
|
|
289
|
-
: opts.keyPair
|
|
290
|
-
|
|
291
|
-
// This only works if the hypercore was fully loaded,
|
|
292
|
-
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
293
|
-
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
290
|
+
const keyPair = opts.keyPair
|
|
294
291
|
|
|
295
292
|
if (opts.auth) {
|
|
296
293
|
this.auth = opts.auth
|
|
297
|
-
} else if (opts.sign) {
|
|
298
|
-
this.auth = Core.createAuth(this.crypto, keyPair, opts)
|
|
299
294
|
} else if (keyPair && keyPair.secretKey) {
|
|
300
|
-
this.
|
|
295
|
+
this.setKeyPair(keyPair)
|
|
301
296
|
}
|
|
302
297
|
|
|
303
298
|
if (isFirst) {
|
|
304
|
-
await this._openCapabilities(
|
|
299
|
+
await this._openCapabilities(key, storage, opts)
|
|
305
300
|
// Only the root session should pass capabilities to other sessions.
|
|
306
301
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
307
302
|
const s = this.sessions[i]
|
|
@@ -318,7 +313,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
318
313
|
}
|
|
319
314
|
|
|
320
315
|
if (!this.auth) this.auth = this.core.defaultAuth
|
|
321
|
-
this.writable = !this._readonly && !!this.auth.sign
|
|
316
|
+
this.writable = !this._readonly && !!this.auth && !!this.auth.sign
|
|
322
317
|
|
|
323
318
|
if (opts.valueEncoding) {
|
|
324
319
|
this.valueEncoding = c.from(opts.valueEncoding)
|
|
@@ -350,18 +345,20 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
350
345
|
}
|
|
351
346
|
}
|
|
352
347
|
|
|
353
|
-
async _openCapabilities (
|
|
348
|
+
async _openCapabilities (key, storage, opts) {
|
|
354
349
|
if (opts.from) return this._openFromExisting(opts.from, opts)
|
|
355
350
|
|
|
356
351
|
const unlocked = !!opts.unlocked
|
|
357
352
|
this.storage = Hypercore.defaultStorage(opts.storage || storage, { unlocked, writable: !unlocked })
|
|
358
353
|
|
|
359
354
|
this.core = await Core.open(this.storage, {
|
|
355
|
+
compat: opts.compat !== false, // default to true for now
|
|
360
356
|
force: opts.force,
|
|
361
357
|
createIfMissing: opts.createIfMissing,
|
|
362
358
|
readonly: unlocked,
|
|
363
359
|
overwrite: opts.overwrite,
|
|
364
|
-
|
|
360
|
+
key,
|
|
361
|
+
keyPair: opts.keyPair,
|
|
365
362
|
crypto: this.crypto,
|
|
366
363
|
legacy: opts.legacy,
|
|
367
364
|
auth: opts.auth,
|
|
@@ -375,8 +372,8 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
375
372
|
}
|
|
376
373
|
}
|
|
377
374
|
|
|
378
|
-
this.key = this.core.header.
|
|
379
|
-
this.keyPair = this.core.header.
|
|
375
|
+
this.key = this.core.header.key
|
|
376
|
+
this.keyPair = this.core.header.keyPair
|
|
380
377
|
this.id = z32.encode(this.key)
|
|
381
378
|
|
|
382
379
|
this.replicator = new Replicator(this.core, this.key, {
|
|
@@ -404,10 +401,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
404
401
|
}
|
|
405
402
|
|
|
406
403
|
return {
|
|
407
|
-
length: this.core.header.contiguousLength,
|
|
404
|
+
length: this.core.header.hints.contiguousLength,
|
|
408
405
|
byteLength: 0,
|
|
409
406
|
fork: this.core.tree.fork,
|
|
410
|
-
compatLength: this.core.header.contiguousLength
|
|
407
|
+
compatLength: this.core.header.hints.contiguousLength
|
|
411
408
|
}
|
|
412
409
|
}
|
|
413
410
|
|
|
@@ -479,13 +476,8 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
479
476
|
let auth = this.core.defaultAuth
|
|
480
477
|
if (opts.auth) {
|
|
481
478
|
auth = opts.auth
|
|
482
|
-
} else if (opts.sign && keyPair) {
|
|
483
|
-
auth = Core.createAuth(this.crypto, keyPair, opts)
|
|
484
|
-
} else if (opts.sign) {
|
|
485
|
-
// TODO: dangerous to just update sign?
|
|
486
|
-
auth.sign = opts.sign
|
|
487
479
|
} else if (keyPair && keyPair.secretKey) {
|
|
488
|
-
auth = Core.createAuth(this.crypto, keyPair)
|
|
480
|
+
auth = Core.createAuth(this.crypto, { keyPair, manifest: { signer: { publicKey: keyPair.publicKey } } })
|
|
489
481
|
}
|
|
490
482
|
|
|
491
483
|
const upgrade = opts.upgrade === undefined ? null : opts.upgrade
|
|
@@ -496,8 +488,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
496
488
|
const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
|
|
497
489
|
|
|
498
490
|
const Clz = this.constructor
|
|
491
|
+
|
|
499
492
|
return new Clz(storage, key, {
|
|
500
493
|
...opts,
|
|
494
|
+
keyPair,
|
|
501
495
|
sparse,
|
|
502
496
|
wait,
|
|
503
497
|
onwait,
|
|
@@ -569,7 +563,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
569
563
|
}
|
|
570
564
|
|
|
571
565
|
get contiguousLength () {
|
|
572
|
-
return this.core === null ? 0 : this.core.header.contiguousLength
|
|
566
|
+
return this.core === null ? 0 : this.core.header.hints.contiguousLength
|
|
573
567
|
}
|
|
574
568
|
|
|
575
569
|
get contiguousByteLength () {
|
|
@@ -658,7 +652,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
658
652
|
}
|
|
659
653
|
}
|
|
660
654
|
|
|
661
|
-
const contig = this.core.header.contiguousLength
|
|
655
|
+
const contig = this.core.header.hints.contiguousLength
|
|
662
656
|
|
|
663
657
|
// When the contig length catches up, broadcast the non-sparse length to peers
|
|
664
658
|
if (appendedNonSparse && contig === this.core.tree.length) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const c = require('compact-encoding')
|
|
2
|
+
const { oplog } = require('./messages')
|
|
3
|
+
|
|
4
|
+
module.exports = class BigHeader {
|
|
5
|
+
constructor (storage) {
|
|
6
|
+
this.storage = storage
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async load (external) {
|
|
10
|
+
const buf = await new Promise((resolve, reject) => {
|
|
11
|
+
this.storage.read(external.start, external.length, (err, buf) => {
|
|
12
|
+
if (err) return reject(err)
|
|
13
|
+
resolve(buf)
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const header = c.decode(oplog.header, buf)
|
|
18
|
+
header.external = external
|
|
19
|
+
return header
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async flush (header) {
|
|
23
|
+
const external = header.external || { start: 0, length: 0 }
|
|
24
|
+
header.external = null
|
|
25
|
+
|
|
26
|
+
const buf = c.encode(oplog.header, header)
|
|
27
|
+
|
|
28
|
+
let start = 0
|
|
29
|
+
if (buf.byteLength > external.start) {
|
|
30
|
+
start = external.start + external.length
|
|
31
|
+
const rem = start & 4095
|
|
32
|
+
if (rem > 0) start += (4096 - rem)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
header.external = { start, length: buf.byteLength }
|
|
36
|
+
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
this.storage.write(start, buf, (err) => {
|
|
39
|
+
if (err) return reject(err)
|
|
40
|
+
resolve()
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return header
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
close () {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
this.storage.close((err) => {
|
|
50
|
+
if (err) return reject(err)
|
|
51
|
+
resolve()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
package/lib/caps.js
CHANGED
|
@@ -2,11 +2,14 @@ const crypto = require('hypercore-crypto')
|
|
|
2
2
|
const sodium = require('sodium-universal')
|
|
3
3
|
const b4a = require('b4a')
|
|
4
4
|
const c = require('compact-encoding')
|
|
5
|
+
const m = require('./messages')
|
|
5
6
|
|
|
6
7
|
// TODO: rename this to "crypto" and move everything hashing related etc in here
|
|
7
8
|
// Also lets move the tree stuff from hypercore-crypto here
|
|
8
9
|
|
|
9
|
-
const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore',
|
|
10
|
+
const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER, MANIFEST, DEFAULT_NAMESPACE] = crypto.namespace('hypercore', 5)
|
|
11
|
+
|
|
12
|
+
exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE
|
|
10
13
|
|
|
11
14
|
exports.replicate = function (isInitiator, key, handshakeHash) {
|
|
12
15
|
const out = b4a.allocUnsafe(32)
|
|
@@ -14,6 +17,17 @@ exports.replicate = function (isInitiator, key, handshakeHash) {
|
|
|
14
17
|
return out
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
exports.manifestHash = function (manifest) {
|
|
21
|
+
const state = { start: 0, end: 32, buffer: null }
|
|
22
|
+
m.manifest.preencode(state, manifest)
|
|
23
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
24
|
+
c.raw.encode(state, MANIFEST)
|
|
25
|
+
m.manifest.encode(state, manifest)
|
|
26
|
+
const out = b4a.allocUnsafe(32)
|
|
27
|
+
sodium.crypto_generichash(out, state.buffer)
|
|
28
|
+
return out
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
exports.treeSignable = function (hash, length, fork) {
|
|
18
32
|
const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
|
|
19
33
|
c.raw.encode(state, TREE)
|
package/lib/core.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
2
2
|
const b4a = require('b4a')
|
|
3
3
|
const Oplog = require('./oplog')
|
|
4
|
+
const BigHeader = require('./big-header')
|
|
4
5
|
const Mutex = require('./mutex')
|
|
5
6
|
const MerkleTree = require('./merkle-tree')
|
|
6
7
|
const BlockStore = require('./block-store')
|
|
@@ -8,15 +9,18 @@ const Bitfield = require('./bitfield')
|
|
|
8
9
|
const Info = require('./info')
|
|
9
10
|
const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('hypercore-errors')
|
|
10
11
|
const m = require('./messages')
|
|
12
|
+
const caps = require('./caps')
|
|
11
13
|
|
|
12
14
|
module.exports = class Core {
|
|
13
|
-
constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
|
|
15
|
+
constructor (header, crypto, oplog, bigHeader, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
|
|
14
16
|
this.onupdate = onupdate
|
|
15
17
|
this.onconflict = onconflict
|
|
16
18
|
this.preupdate = null
|
|
17
19
|
this.header = header
|
|
20
|
+
this.compat = !!(header.manifest && header.manifest.signer && b4a.equals(header.key, header.manifest.signer.publicKey))
|
|
18
21
|
this.crypto = crypto
|
|
19
22
|
this.oplog = oplog
|
|
23
|
+
this.bigHeader = bigHeader
|
|
20
24
|
this.tree = tree
|
|
21
25
|
this.blocks = blocks
|
|
22
26
|
this.bitfield = bitfield
|
|
@@ -24,6 +28,7 @@ module.exports = class Core {
|
|
|
24
28
|
this.truncating = 0
|
|
25
29
|
this.closed = false
|
|
26
30
|
|
|
31
|
+
this._manifestFlushed = !!header.manifest
|
|
27
32
|
this._maxOplogSize = 65536
|
|
28
33
|
this._autoFlush = 1
|
|
29
34
|
this._verifies = null
|
|
@@ -37,35 +42,31 @@ module.exports = class Core {
|
|
|
37
42
|
const treeFile = storage('tree')
|
|
38
43
|
const bitfieldFile = storage('bitfield')
|
|
39
44
|
const dataFile = storage('data')
|
|
45
|
+
const headerFile = storage('header')
|
|
40
46
|
|
|
41
47
|
try {
|
|
42
|
-
return await this.resume(oplogFile, treeFile, bitfieldFile, dataFile, opts)
|
|
48
|
+
return await this.resume(oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts)
|
|
43
49
|
} catch (err) {
|
|
44
|
-
await closeAll(oplogFile, treeFile, bitfieldFile, dataFile)
|
|
50
|
+
await closeAll(oplogFile, treeFile, bitfieldFile, dataFile, headerFile)
|
|
45
51
|
throw err
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
static createAuth (crypto,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const sign = opts.sign
|
|
55
|
-
? opts.sign
|
|
56
|
-
: secretKey
|
|
57
|
-
? (signable) => crypto.sign(signable, secretKey)
|
|
58
|
-
: undefined
|
|
55
|
+
static createAuth (crypto, header) {
|
|
56
|
+
const manifest = header.manifest || defaultSignerManifest(header.keyPair.publicKey)
|
|
57
|
+
const secretKey = header.keyPair && header.keyPair.secretKey
|
|
58
|
+
const publicKey = manifest.signer.publicKey
|
|
59
|
+
const sign = signable => crypto.sign(signable, secretKey)
|
|
59
60
|
|
|
60
61
|
return {
|
|
61
|
-
sign,
|
|
62
|
+
sign: secretKey ? sign : null,
|
|
62
63
|
verify (signable, signature) {
|
|
63
64
|
return crypto.verify(signable, signature, publicKey)
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
|
|
69
|
+
static async resume (oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts) {
|
|
69
70
|
let overwrite = opts.overwrite === true
|
|
70
71
|
|
|
71
72
|
const force = opts.force === true
|
|
@@ -80,17 +81,31 @@ module.exports = class Core {
|
|
|
80
81
|
|
|
81
82
|
let { header, entries } = await oplog.open()
|
|
82
83
|
|
|
83
|
-
if (force && opts.
|
|
84
|
+
if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
|
|
84
85
|
overwrite = true
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
const bigHeader = new BigHeader(headerFile)
|
|
89
|
+
|
|
87
90
|
if (!header || overwrite) {
|
|
88
91
|
if (!createIfMissing) {
|
|
89
92
|
throw STORAGE_EMPTY('No Hypercore is stored here')
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
if (opts.compat) {
|
|
96
|
+
if (opts.key && opts.keyPair && !b4a.equals(opts.key, opts.keyPair.publicKey)) {
|
|
97
|
+
throw BAD_ARGUMENT('Key must match publicKey when in compat mode.')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())
|
|
102
|
+
const manifest = opts.manifest || (opts.key && !opts.compat) ? null : defaultSignerManifest(opts.key || keyPair.publicKey)
|
|
103
|
+
|
|
92
104
|
header = {
|
|
93
|
-
|
|
105
|
+
external: null,
|
|
106
|
+
key: opts.key || (opts.compat ? manifest.signer.publicKey : caps.manifestHash(manifest)),
|
|
107
|
+
manifest,
|
|
108
|
+
keyPair,
|
|
94
109
|
userData: [],
|
|
95
110
|
tree: {
|
|
96
111
|
fork: 0,
|
|
@@ -98,17 +113,18 @@ module.exports = class Core {
|
|
|
98
113
|
rootHash: null,
|
|
99
114
|
signature: null
|
|
100
115
|
},
|
|
101
|
-
signer: opts.keyPair || crypto.keyPair(),
|
|
102
116
|
hints: {
|
|
103
|
-
reorgs: []
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
reorgs: [],
|
|
118
|
+
contiguousLength: 0
|
|
119
|
+
}
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
await oplog
|
|
122
|
+
await flushHeader(oplog, bigHeader, header)
|
|
123
|
+
} else if (header.external) {
|
|
124
|
+
header = await bigHeader.load(header.external)
|
|
109
125
|
}
|
|
110
126
|
|
|
111
|
-
if (opts.
|
|
127
|
+
if (opts.key && !b4a.equals(header.key, opts.key)) {
|
|
112
128
|
throw STORAGE_CONFLICT('Another Hypercore is stored here')
|
|
113
129
|
}
|
|
114
130
|
|
|
@@ -127,11 +143,11 @@ module.exports = class Core {
|
|
|
127
143
|
}
|
|
128
144
|
|
|
129
145
|
// compat from earlier version that do not store contig length
|
|
130
|
-
if (header.contiguousLength === 0) {
|
|
131
|
-
while (bitfield.get(header.contiguousLength)) header.contiguousLength++
|
|
146
|
+
if (header.hints.contiguousLength === 0) {
|
|
147
|
+
while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
|
|
132
148
|
}
|
|
133
149
|
|
|
134
|
-
const auth = opts.auth || this.createAuth(crypto, header
|
|
150
|
+
const auth = opts.auth || (header.manifest ? this.createAuth(crypto, header) : null)
|
|
135
151
|
|
|
136
152
|
for (const e of entries) {
|
|
137
153
|
if (e.userData) {
|
|
@@ -163,7 +179,7 @@ module.exports = class Core {
|
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
181
|
|
|
166
|
-
return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.onconflict || noop)
|
|
182
|
+
return new this(header, crypto, oplog, bigHeader, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.onconflict || noop)
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
_shouldFlush () {
|
|
@@ -173,6 +189,11 @@ module.exports = class Core {
|
|
|
173
189
|
return true
|
|
174
190
|
}
|
|
175
191
|
|
|
192
|
+
if (!this._manifestFlushed && this.header.manifest) {
|
|
193
|
+
this._manifestFlushed = true
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
176
197
|
return false
|
|
177
198
|
}
|
|
178
199
|
|
|
@@ -223,7 +244,7 @@ module.exports = class Core {
|
|
|
223
244
|
|
|
224
245
|
this.tree.signature = signature || auth.sign(this.tree.signable())
|
|
225
246
|
|
|
226
|
-
if (signature && !this.
|
|
247
|
+
if (signature && !this._verifyBatchUpgrade(this.tree, null)) {
|
|
227
248
|
// TODO: how to handle signature failure?
|
|
228
249
|
this.tree.signature = null
|
|
229
250
|
throw INVALID_SIGNATURE('Clone was provided with an invalid signature')
|
|
@@ -246,7 +267,8 @@ module.exports = class Core {
|
|
|
246
267
|
// background. Might be easier to impl that where it is called instead and keep this one simple.
|
|
247
268
|
await this.bitfield.flush()
|
|
248
269
|
await this.tree.flush()
|
|
249
|
-
|
|
270
|
+
|
|
271
|
+
return flushHeader(this.oplog, this.bigHeader, this.header)
|
|
250
272
|
}
|
|
251
273
|
|
|
252
274
|
_appendBlocks (values) {
|
|
@@ -325,8 +347,8 @@ module.exports = class Core {
|
|
|
325
347
|
|
|
326
348
|
this.bitfield.setRange(start, end - start, false)
|
|
327
349
|
|
|
328
|
-
if (start < this.header.contiguousLength) {
|
|
329
|
-
this.header.contiguousLength = start
|
|
350
|
+
if (start < this.header.hints.contiguousLength) {
|
|
351
|
+
this.header.hints.contiguousLength = start
|
|
330
352
|
}
|
|
331
353
|
|
|
332
354
|
start = this.bitfield.lastSet(start) + 1
|
|
@@ -423,18 +445,33 @@ module.exports = class Core {
|
|
|
423
445
|
}
|
|
424
446
|
}
|
|
425
447
|
|
|
426
|
-
|
|
448
|
+
_verifyBatchUpgrade (batch, manifest) {
|
|
427
449
|
const hash = batch.hash()
|
|
428
450
|
const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
|
|
429
|
-
|
|
451
|
+
|
|
452
|
+
if (!this.header.manifest) {
|
|
453
|
+
if (!manifest) { // compat mode, remove in future version
|
|
454
|
+
manifest = defaultSignerManifest(this.header.key)
|
|
455
|
+
} else if (!manifest || !b4a.equals(this.header.key, caps.manifestHash(manifest))) {
|
|
456
|
+
throw INVALID_SIGNATURE('Proof contains an invalid manifest') // TODO: proper error type
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const auth = this.defaultAuth || Core.createAuth(this.crypto, { ...this.header, manifest })
|
|
430
461
|
|
|
431
462
|
if (!batch.signature || !auth.verify(signable, batch.signature, batch)) {
|
|
432
463
|
throw INVALID_SIGNATURE('Proof contains an invalid signature')
|
|
433
464
|
}
|
|
465
|
+
|
|
466
|
+
this.defaultAuth = auth
|
|
467
|
+
if (!this.header.manifest) {
|
|
468
|
+
this.compat = !!manifest.signer && b4a.equals(this.header.key, manifest.signer.publicKey)
|
|
469
|
+
this.header.manifest = manifest
|
|
470
|
+
}
|
|
434
471
|
}
|
|
435
472
|
|
|
436
|
-
async _verifyExclusive ({ batch, bitfield, value, from }) {
|
|
437
|
-
this.
|
|
473
|
+
async _verifyExclusive ({ batch, bitfield, value, manifest, from }) {
|
|
474
|
+
this._verifyBatchUpgrade(batch, manifest)
|
|
438
475
|
|
|
439
476
|
await this._mutex.lock()
|
|
440
477
|
|
|
@@ -448,7 +485,7 @@ module.exports = class Core {
|
|
|
448
485
|
bitfield
|
|
449
486
|
}
|
|
450
487
|
|
|
451
|
-
if (this.preupdate !== null) await this.preupdate(batch, this.header.
|
|
488
|
+
if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
|
|
452
489
|
if (bitfield) await this._writeBlock(batch, bitfield.start, value)
|
|
453
490
|
|
|
454
491
|
await this.oplog.append([entry], false)
|
|
@@ -464,7 +501,7 @@ module.exports = class Core {
|
|
|
464
501
|
|
|
465
502
|
this.header.tree.fork = batch.fork
|
|
466
503
|
this.header.tree.length = batch.length
|
|
467
|
-
this.header.tree.rootHash = batch.
|
|
504
|
+
this.header.tree.rootHash = batch.hash()
|
|
468
505
|
this.header.tree.signature = batch.signature
|
|
469
506
|
|
|
470
507
|
this.onupdate(status, bitfield, value, from)
|
|
@@ -542,7 +579,7 @@ module.exports = class Core {
|
|
|
542
579
|
|
|
543
580
|
const batch = this.tree.verifyFullyRemote(proof)
|
|
544
581
|
|
|
545
|
-
this.
|
|
582
|
+
this._verifyBatchUpgrade(batch, proof.manifest)
|
|
546
583
|
|
|
547
584
|
const remoteTreeHash = this.crypto.tree(proof.upgrade.nodes)
|
|
548
585
|
const localTreeHash = this.crypto.tree(await this.tree.getRoots(proof.upgrade.length))
|
|
@@ -556,7 +593,7 @@ module.exports = class Core {
|
|
|
556
593
|
async verifyReorg (proof) {
|
|
557
594
|
const batch = await this.tree.reorg(proof)
|
|
558
595
|
|
|
559
|
-
this.
|
|
596
|
+
this._verifyBatchUpgrade(batch, proof.manifest)
|
|
560
597
|
|
|
561
598
|
return batch
|
|
562
599
|
}
|
|
@@ -565,7 +602,6 @@ module.exports = class Core {
|
|
|
565
602
|
// We cannot apply "other forks" atm.
|
|
566
603
|
// We should probably still try and they are likely super similar for non upgrades
|
|
567
604
|
// but this is easy atm (and the above layer will just retry)
|
|
568
|
-
|
|
569
605
|
if (proof.fork !== this.tree.fork) return false
|
|
570
606
|
|
|
571
607
|
const batch = await this.tree.verify(proof)
|
|
@@ -576,6 +612,7 @@ module.exports = class Core {
|
|
|
576
612
|
batch,
|
|
577
613
|
bitfield: value && { drop: false, start: proof.block.index, length: 1 },
|
|
578
614
|
value,
|
|
615
|
+
manifest: proof.manifest,
|
|
579
616
|
from
|
|
580
617
|
}
|
|
581
618
|
|
|
@@ -653,7 +690,8 @@ module.exports = class Core {
|
|
|
653
690
|
this.oplog.close(),
|
|
654
691
|
this.bitfield.close(),
|
|
655
692
|
this.tree.close(),
|
|
656
|
-
this.blocks.close()
|
|
693
|
+
this.blocks.close(),
|
|
694
|
+
this.bigHeader.close()
|
|
657
695
|
])
|
|
658
696
|
}
|
|
659
697
|
}
|
|
@@ -661,7 +699,7 @@ module.exports = class Core {
|
|
|
661
699
|
function updateContig (header, upd, bitfield) {
|
|
662
700
|
const end = upd.start + upd.length
|
|
663
701
|
|
|
664
|
-
let c = header.contiguousLength
|
|
702
|
+
let c = header.hints.contiguousLength
|
|
665
703
|
|
|
666
704
|
if (upd.drop) {
|
|
667
705
|
// If we dropped a block in the current contig range, "downgrade" it
|
|
@@ -675,16 +713,16 @@ function updateContig (header, upd, bitfield) {
|
|
|
675
713
|
}
|
|
676
714
|
}
|
|
677
715
|
|
|
678
|
-
if (c === header.contiguousLength) {
|
|
716
|
+
if (c === header.hints.contiguousLength) {
|
|
679
717
|
return 0b0000
|
|
680
718
|
}
|
|
681
719
|
|
|
682
|
-
if (c > header.contiguousLength) {
|
|
683
|
-
header.contiguousLength = c
|
|
720
|
+
if (c > header.hints.contiguousLength) {
|
|
721
|
+
header.hints.contiguousLength = c
|
|
684
722
|
return 0b0100
|
|
685
723
|
}
|
|
686
724
|
|
|
687
|
-
header.contiguousLength = c
|
|
725
|
+
header.hints.contiguousLength = c
|
|
688
726
|
return 0b1000
|
|
689
727
|
}
|
|
690
728
|
|
|
@@ -732,4 +770,31 @@ function closeAll (...storages) {
|
|
|
732
770
|
})
|
|
733
771
|
}
|
|
734
772
|
|
|
773
|
+
async function flushHeader (oplog, bigHeader, header) {
|
|
774
|
+
if (header.external) {
|
|
775
|
+
await bigHeader.flush(header)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
await oplog.flush(header)
|
|
780
|
+
} catch (err) {
|
|
781
|
+
if (err.code !== 'OPLOG_HEADER_OVERFLOW') throw err
|
|
782
|
+
await bigHeader.flush(header)
|
|
783
|
+
await oplog.flush(header)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function defaultSignerManifest (publicKey) {
|
|
788
|
+
return {
|
|
789
|
+
hash: 'blake2b',
|
|
790
|
+
static: null,
|
|
791
|
+
signer: {
|
|
792
|
+
signature: 'ed25519',
|
|
793
|
+
namespace: caps.DEFAULT_NAMESPACE,
|
|
794
|
+
publicKey
|
|
795
|
+
},
|
|
796
|
+
multipleSigners: null
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
735
800
|
function noop () {}
|
package/lib/merkle-tree.js
CHANGED
|
@@ -48,6 +48,7 @@ class MerkleTreeBatch {
|
|
|
48
48
|
this.ancestors = tree.length
|
|
49
49
|
this.byteLength = tree.byteLength
|
|
50
50
|
this.signature = null
|
|
51
|
+
this.hashCached = null
|
|
51
52
|
|
|
52
53
|
this.treeLength = tree.length
|
|
53
54
|
this.treeFork = tree.fork
|
|
@@ -74,7 +75,8 @@ class MerkleTreeBatch {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
hash () {
|
|
77
|
-
|
|
78
|
+
if (this.hashCached === null) this.hashCached = this.tree.crypto.tree(this.roots)
|
|
79
|
+
return this.hashCached
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
signable (hash = this.hash()) {
|
|
@@ -122,6 +124,7 @@ class MerkleTreeBatch {
|
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
appendRoot (node, ite) {
|
|
127
|
+
this.hashCached = null
|
|
125
128
|
this.upgraded = true
|
|
126
129
|
this.length += ite.factor / 2
|
|
127
130
|
this.byteLength += node.size
|
|
@@ -1215,7 +1218,7 @@ async function generateProof (tree, block, hash, seek, upgrade) {
|
|
|
1215
1218
|
}
|
|
1216
1219
|
|
|
1217
1220
|
const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(p)
|
|
1218
|
-
const result = { fork, block: null, hash: null, seek: null, upgrade: null }
|
|
1221
|
+
const result = { fork, block: null, hash: null, seek: null, upgrade: null, manifest: null }
|
|
1219
1222
|
|
|
1220
1223
|
if (block) {
|
|
1221
1224
|
result.block = {
|
package/lib/messages.js
CHANGED
|
@@ -4,6 +4,140 @@ const { INVALID_OPLOG_VERSION } = require('hypercore-errors')
|
|
|
4
4
|
|
|
5
5
|
const EMPTY = b4a.alloc(0)
|
|
6
6
|
|
|
7
|
+
const hashes = {
|
|
8
|
+
preencode (state, m) {
|
|
9
|
+
state.end++ // small uint
|
|
10
|
+
},
|
|
11
|
+
encode (state, m) {
|
|
12
|
+
if (m === 'blake2b') {
|
|
13
|
+
c.uint.encode(state, 0)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new Error('Unknown hash: ' + m)
|
|
18
|
+
},
|
|
19
|
+
decode (state) {
|
|
20
|
+
const n = c.uint.decode(state)
|
|
21
|
+
if (n === 0) return 'blake2b'
|
|
22
|
+
throw new Error('Unknown hash id: ' + n)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const signatures = {
|
|
27
|
+
preencode (state, m) {
|
|
28
|
+
state.end++ // small uint
|
|
29
|
+
},
|
|
30
|
+
encode (state, m) {
|
|
31
|
+
if (m === 'ed25519') {
|
|
32
|
+
c.uint.encode(state, 0)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new Error('Unknown signature: ' + m)
|
|
37
|
+
},
|
|
38
|
+
decode (state) {
|
|
39
|
+
const n = c.uint.decode(state)
|
|
40
|
+
if (n === 0) return 'ed25519'
|
|
41
|
+
throw new Error('Unknown signature id: ' + n)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const signer = {
|
|
46
|
+
preencode (state, m) {
|
|
47
|
+
signatures.preencode(state, m.signature)
|
|
48
|
+
c.fixed32.preencode(state, m.namespace)
|
|
49
|
+
c.fixed32.preencode(state, m.publicKey)
|
|
50
|
+
},
|
|
51
|
+
encode (state, m) {
|
|
52
|
+
signatures.encode(state, m.signature)
|
|
53
|
+
c.fixed32.encode(state, m.namespace)
|
|
54
|
+
c.fixed32.encode(state, m.publicKey)
|
|
55
|
+
},
|
|
56
|
+
decode (state) {
|
|
57
|
+
return {
|
|
58
|
+
signature: signatures.decode(state),
|
|
59
|
+
namespace: c.fixed32.decode(state),
|
|
60
|
+
publicKey: c.fixed32.decode(state)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const signerArray = c.array(signer)
|
|
66
|
+
|
|
67
|
+
const multipleSigners = {
|
|
68
|
+
preencode (state, m) {
|
|
69
|
+
state.end++ // flags
|
|
70
|
+
c.uint.preencode(state, m.quorum)
|
|
71
|
+
signerArray.preencode(state, m.signers)
|
|
72
|
+
},
|
|
73
|
+
encode (state, m) {
|
|
74
|
+
c.uint.encode(state, m.allowPatched ? 1 : 0)
|
|
75
|
+
c.uint.encode(state, m.quorum)
|
|
76
|
+
signerArray.encode(state, m.signers)
|
|
77
|
+
},
|
|
78
|
+
decode (state) {
|
|
79
|
+
const flags = c.uint.decode(state)
|
|
80
|
+
return {
|
|
81
|
+
allowPatched: (flags & 1) !== 0,
|
|
82
|
+
quorum: c.uint.decode(state),
|
|
83
|
+
signers: signerArray.decode(state)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const manifest = exports.manifest = {
|
|
89
|
+
preencode (state, m) {
|
|
90
|
+
c.uint.preencode(state, 0) // version
|
|
91
|
+
hashes.preencode(state, m.hash)
|
|
92
|
+
c.uint.preencode(state, 2) // type
|
|
93
|
+
|
|
94
|
+
if (m.static) {
|
|
95
|
+
c.fixed32.preencode(state, m.static)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (m.signer) {
|
|
99
|
+
signer.preencode(state, m.signer)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (m.multipleSigners) {
|
|
103
|
+
multipleSigners.preencode(state, m.multipleSigners)
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
encode (state, m) {
|
|
107
|
+
c.uint.encode(state, 0) // version
|
|
108
|
+
hashes.encode(state, m.hash)
|
|
109
|
+
c.uint.encode(state, m.signer ? 1 : m.multipleSigners ? 2 : 0)
|
|
110
|
+
|
|
111
|
+
if (m.static) {
|
|
112
|
+
c.fixed32.encode(state, m.static)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (m.signer) {
|
|
116
|
+
signer.encode(state, m.signer)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (m.multipleSigners) {
|
|
120
|
+
multipleSigners.encode(state, m.multipleSigners)
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
decode (state) {
|
|
124
|
+
const version = c.uint.decode(state)
|
|
125
|
+
if (version !== 0) throw new Error('Invalid version: ' + version)
|
|
126
|
+
|
|
127
|
+
const hash = hashes.decode(state)
|
|
128
|
+
const type = c.uint.decode(state)
|
|
129
|
+
|
|
130
|
+
if (type > 2) throw new Error('Unknown type: ' + type)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
hash,
|
|
134
|
+
static: type === 0 ? c.fixed32.decode(state) : null,
|
|
135
|
+
signer: type === 1 ? signer.decode(state) : null,
|
|
136
|
+
multipleSigners: type === 2 ? multipleSigners.decode(state) : null
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
7
141
|
const node = {
|
|
8
142
|
preencode (state, n) {
|
|
9
143
|
c.uint.preencode(state, n.index)
|
|
@@ -30,16 +164,17 @@ const wire = exports.wire = {}
|
|
|
30
164
|
|
|
31
165
|
wire.handshake = {
|
|
32
166
|
preencode (state, m) {
|
|
33
|
-
c.uint.preencode(state, 0)
|
|
167
|
+
c.uint.preencode(state, 0)
|
|
34
168
|
c.fixed32.preencode(state, m.capability)
|
|
35
169
|
},
|
|
36
170
|
encode (state, m) {
|
|
37
|
-
c.uint.encode(state, 0) // flags for the future
|
|
171
|
+
c.uint.encode(state, m.manifest ? 1 : 0) // flags for the future
|
|
38
172
|
c.fixed32.encode(state, m.capability)
|
|
39
173
|
},
|
|
40
174
|
decode (state) {
|
|
41
|
-
c.uint.decode(state)
|
|
175
|
+
const flags = c.uint.decode(state)
|
|
42
176
|
return {
|
|
177
|
+
manifest: (flags & 1) !== 0,
|
|
43
178
|
capability: c.fixed32.decode(state)
|
|
44
179
|
}
|
|
45
180
|
}
|
|
@@ -106,7 +241,7 @@ wire.request = {
|
|
|
106
241
|
if (m.priority) c.uint.preencode(state, m.priority)
|
|
107
242
|
},
|
|
108
243
|
encode (state, m) {
|
|
109
|
-
const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.
|
|
244
|
+
const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0) | (m.priority ? 32 : 0)
|
|
110
245
|
|
|
111
246
|
c.uint.encode(state, flags)
|
|
112
247
|
c.uint.encode(state, m.id)
|
|
@@ -128,7 +263,8 @@ wire.request = {
|
|
|
128
263
|
hash: flags & 2 ? requestBlock.decode(state) : null,
|
|
129
264
|
seek: flags & 4 ? requestSeek.decode(state) : null,
|
|
130
265
|
upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
|
|
131
|
-
|
|
266
|
+
manifest: (flags & 16) !== 0,
|
|
267
|
+
priority: flags & 32 ? c.uint.decode(state) : 0
|
|
132
268
|
}
|
|
133
269
|
}
|
|
134
270
|
}
|
|
@@ -237,9 +373,10 @@ wire.data = {
|
|
|
237
373
|
if (m.hash) dataHash.preencode(state, m.hash)
|
|
238
374
|
if (m.seek) dataSeek.preencode(state, m.seek)
|
|
239
375
|
if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
|
|
376
|
+
if (m.manifest) manifest.preencode(state, m.manifest)
|
|
240
377
|
},
|
|
241
378
|
encode (state, m) {
|
|
242
|
-
const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0)
|
|
379
|
+
const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0)
|
|
243
380
|
|
|
244
381
|
c.uint.encode(state, flags)
|
|
245
382
|
c.uint.encode(state, m.request)
|
|
@@ -249,6 +386,7 @@ wire.data = {
|
|
|
249
386
|
if (m.hash) dataHash.encode(state, m.hash)
|
|
250
387
|
if (m.seek) dataSeek.encode(state, m.seek)
|
|
251
388
|
if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
|
|
389
|
+
if (m.manifest) manifest.encode(state, m.manifest)
|
|
252
390
|
},
|
|
253
391
|
decode (state) {
|
|
254
392
|
const flags = c.uint.decode(state)
|
|
@@ -259,7 +397,8 @@ wire.data = {
|
|
|
259
397
|
block: flags & 1 ? dataBlock.decode(state) : null,
|
|
260
398
|
hash: flags & 2 ? dataHash.decode(state) : null,
|
|
261
399
|
seek: flags & 4 ? dataSeek.decode(state) : null,
|
|
262
|
-
upgrade: flags & 8 ? dataUpgrade.decode(state) : null
|
|
400
|
+
upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
|
|
401
|
+
manifest: flags & 16 ? manifest.decode(state) : null
|
|
263
402
|
}
|
|
264
403
|
}
|
|
265
404
|
}
|
|
@@ -562,13 +701,16 @@ const reorgHintArray = c.array(reorgHint)
|
|
|
562
701
|
const hints = {
|
|
563
702
|
preencode (state, h) {
|
|
564
703
|
reorgHintArray.preencode(state, h.reorgs)
|
|
704
|
+
c.uint.preencode(state, h.contiguousLength)
|
|
565
705
|
},
|
|
566
706
|
encode (state, h) {
|
|
567
707
|
reorgHintArray.encode(state, h.reorgs)
|
|
708
|
+
c.uint.encode(state, h.contiguousLength)
|
|
568
709
|
},
|
|
569
710
|
decode (state) {
|
|
570
711
|
return {
|
|
571
|
-
reorgs: reorgHintArray.decode(state)
|
|
712
|
+
reorgs: reorgHintArray.decode(state),
|
|
713
|
+
contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
|
|
572
714
|
}
|
|
573
715
|
}
|
|
574
716
|
}
|
|
@@ -616,41 +758,112 @@ const types = {
|
|
|
616
758
|
}
|
|
617
759
|
}
|
|
618
760
|
|
|
761
|
+
const externalHeader = {
|
|
762
|
+
preencode (state, m) {
|
|
763
|
+
c.uint.preencode(state, m.start)
|
|
764
|
+
c.uint.preencode(state, m.length)
|
|
765
|
+
},
|
|
766
|
+
encode (state, m) {
|
|
767
|
+
c.uint.encode(state, m.start)
|
|
768
|
+
c.uint.encode(state, m.length)
|
|
769
|
+
},
|
|
770
|
+
decode (state) {
|
|
771
|
+
return {
|
|
772
|
+
start: c.uint.decode(state),
|
|
773
|
+
length: c.uint.decode(state)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
619
778
|
const keyValueArray = c.array(keyValue)
|
|
620
779
|
|
|
621
780
|
oplog.header = {
|
|
622
781
|
preencode (state, h) {
|
|
623
|
-
state.end +=
|
|
624
|
-
|
|
782
|
+
state.end += 2 // version + flags
|
|
783
|
+
if (h.external) {
|
|
784
|
+
externalHeader.preencode(state, h.external)
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
c.fixed32.preencode(state, h.key)
|
|
788
|
+
if (h.manifest) manifest.preencode(state, h.manifest)
|
|
789
|
+
if (h.keyPair) keyPair.preencode(state, h.keyPair)
|
|
625
790
|
keyValueArray.preencode(state, h.userData)
|
|
626
791
|
treeHeader.preencode(state, h.tree)
|
|
627
|
-
keyPair.preencode(state, h.signer)
|
|
628
792
|
hints.preencode(state, h.hints)
|
|
629
|
-
c.uint.preencode(state, h.contiguousLength)
|
|
630
793
|
},
|
|
631
794
|
encode (state, h) {
|
|
632
|
-
|
|
633
|
-
|
|
795
|
+
c.uint.encode(state, 1)
|
|
796
|
+
if (h.external) {
|
|
797
|
+
c.uint.encode(state, 1) // ONLY set the first big for clarity
|
|
798
|
+
externalHeader.encode(state, h.external)
|
|
799
|
+
return
|
|
800
|
+
}
|
|
801
|
+
c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
|
|
802
|
+
c.fixed32.encode(state, h.key)
|
|
803
|
+
if (h.manifest) manifest.encode(state, h.manifest)
|
|
804
|
+
if (h.keyPair) keyPair.encode(state, h.keyPair)
|
|
634
805
|
keyValueArray.encode(state, h.userData)
|
|
635
806
|
treeHeader.encode(state, h.tree)
|
|
636
|
-
keyPair.encode(state, h.signer)
|
|
637
807
|
hints.encode(state, h.hints)
|
|
638
|
-
c.uint.encode(state, h.contiguousLength)
|
|
639
808
|
},
|
|
640
809
|
decode (state) {
|
|
641
810
|
const version = c.uint.decode(state)
|
|
642
811
|
|
|
643
|
-
if (version
|
|
644
|
-
throw INVALID_OPLOG_VERSION('Invalid header version. Expected
|
|
812
|
+
if (version > 1) {
|
|
813
|
+
throw INVALID_OPLOG_VERSION('Invalid header version. Expected <= 1, got ' + version)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (version === 0) {
|
|
817
|
+
const old = {
|
|
818
|
+
types: types.decode(state),
|
|
819
|
+
userData: keyValueArray.decode(state),
|
|
820
|
+
tree: treeHeader.decode(state),
|
|
821
|
+
signer: keyPair.decode(state),
|
|
822
|
+
hints: hints.decode(state)
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return {
|
|
826
|
+
key: old.signer.publicKey,
|
|
827
|
+
manifest: {
|
|
828
|
+
namespace: b4a.alloc(32),
|
|
829
|
+
hash: old.types.tree,
|
|
830
|
+
static: null,
|
|
831
|
+
signer: {
|
|
832
|
+
signature: old.types.signer,
|
|
833
|
+
entropy: b4a.alloc(32),
|
|
834
|
+
publicKey: old.signer.publicKey
|
|
835
|
+
},
|
|
836
|
+
multipleSigners: null
|
|
837
|
+
},
|
|
838
|
+
keyPair: old.signer.secretKey ? old.signer : null,
|
|
839
|
+
userData: old.userData,
|
|
840
|
+
tree: old.tree,
|
|
841
|
+
hints: old.hints
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const flags = c.uint.decode(state)
|
|
846
|
+
|
|
847
|
+
if (flags & 1) {
|
|
848
|
+
return {
|
|
849
|
+
external: externalHeader.decode(state),
|
|
850
|
+
key: null,
|
|
851
|
+
manifest: null,
|
|
852
|
+
keyPair: null,
|
|
853
|
+
userData: null,
|
|
854
|
+
tree: null,
|
|
855
|
+
hints: null
|
|
856
|
+
}
|
|
645
857
|
}
|
|
646
858
|
|
|
647
859
|
return {
|
|
648
|
-
|
|
860
|
+
external: null,
|
|
861
|
+
key: c.fixed32.decode(state),
|
|
862
|
+
manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
|
|
863
|
+
keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
|
|
649
864
|
userData: keyValueArray.decode(state),
|
|
650
865
|
tree: treeHeader.decode(state),
|
|
651
|
-
|
|
652
|
-
hints: hints.decode(state),
|
|
653
|
-
contiguousLength: state.end > state.start ? c.uint.decode(state) : 0
|
|
866
|
+
hints: hints.decode(state)
|
|
654
867
|
}
|
|
655
868
|
}
|
|
656
869
|
}
|
package/lib/replicator.js
CHANGED
|
@@ -313,6 +313,7 @@ class Peer {
|
|
|
313
313
|
this.remoteUploading = true
|
|
314
314
|
this.remoteDownloading = true
|
|
315
315
|
this.remoteSynced = false
|
|
316
|
+
this.remoteManifest = false
|
|
316
317
|
|
|
317
318
|
this.segmentsWanted = new Set()
|
|
318
319
|
this.broadcastedNonSparse = false
|
|
@@ -385,7 +386,7 @@ class Peer {
|
|
|
385
386
|
})
|
|
386
387
|
}
|
|
387
388
|
|
|
388
|
-
onopen ({ capability }) {
|
|
389
|
+
onopen ({ manifest, capability }) {
|
|
389
390
|
const expected = caps.replicate(this.stream.isInitiator === false, this.replicator.key, this.stream.handshakeHash)
|
|
390
391
|
|
|
391
392
|
if (b4a.equals(capability, expected) !== true) { // TODO: change this to a rejection instead, less leakage
|
|
@@ -394,6 +395,7 @@ class Peer {
|
|
|
394
395
|
|
|
395
396
|
if (this.remoteOpened === true) return
|
|
396
397
|
this.remoteOpened = true
|
|
398
|
+
this.remoteManifest = manifest
|
|
397
399
|
|
|
398
400
|
this.protomux.cork()
|
|
399
401
|
|
|
@@ -519,6 +521,10 @@ class Peer {
|
|
|
519
521
|
proof.block.value = await this.core.blocks.get(index)
|
|
520
522
|
}
|
|
521
523
|
|
|
524
|
+
if (msg.manifest && !this.core.compat) {
|
|
525
|
+
proof.manifest = this.core.header.manifest
|
|
526
|
+
}
|
|
527
|
+
|
|
522
528
|
return proof
|
|
523
529
|
}
|
|
524
530
|
|
|
@@ -575,7 +581,8 @@ class Peer {
|
|
|
575
581
|
block: proof.block,
|
|
576
582
|
hash: proof.hash,
|
|
577
583
|
seek: proof.seek,
|
|
578
|
-
upgrade: proof.upgrade
|
|
584
|
+
upgrade: proof.upgrade,
|
|
585
|
+
manifest: proof.manifest
|
|
579
586
|
})
|
|
580
587
|
return
|
|
581
588
|
}
|
|
@@ -752,12 +759,14 @@ class Peer {
|
|
|
752
759
|
upgrade: needsUpgrade === false
|
|
753
760
|
? null
|
|
754
761
|
: { start: this.core.tree.length, length: this.remoteLength - this.core.tree.length },
|
|
762
|
+
// remote manifest check can be removed eventually...
|
|
763
|
+
manifest: needsUpgrade && !this.core.header.manifest && this.remoteManifest && !this.core.compat,
|
|
755
764
|
priority
|
|
756
765
|
}
|
|
757
766
|
}
|
|
758
767
|
|
|
759
768
|
_requestUpgrade (u) {
|
|
760
|
-
const req = this._makeRequest(true)
|
|
769
|
+
const req = this._makeRequest(true, 0)
|
|
761
770
|
if (req === null) return false
|
|
762
771
|
|
|
763
772
|
this._send(req)
|
|
@@ -771,7 +780,7 @@ class Peer {
|
|
|
771
780
|
if (fork !== this.remoteFork) return false
|
|
772
781
|
|
|
773
782
|
if (s.seeker.start >= length) {
|
|
774
|
-
const req = this._makeRequest(true)
|
|
783
|
+
const req = this._makeRequest(true, 0)
|
|
775
784
|
|
|
776
785
|
// We need an upgrade for the seek, if non can be provided, skip
|
|
777
786
|
if (req === null) return false
|
|
@@ -913,9 +922,10 @@ class Peer {
|
|
|
913
922
|
}
|
|
914
923
|
|
|
915
924
|
_requestForkProof (f) {
|
|
916
|
-
const req = this._makeRequest(false)
|
|
925
|
+
const req = this._makeRequest(false, 0)
|
|
917
926
|
|
|
918
927
|
req.upgrade = { start: 0, length: this.remoteLength }
|
|
928
|
+
req.manifest = !this.core.header.manifest
|
|
919
929
|
|
|
920
930
|
f.inflight.push(req)
|
|
921
931
|
this._send(req)
|
|
@@ -936,7 +946,7 @@ class Peer {
|
|
|
936
946
|
|
|
937
947
|
if (this.remoteBitfield.get(index) === false) continue
|
|
938
948
|
|
|
939
|
-
const req = this._makeRequest(false)
|
|
949
|
+
const req = this._makeRequest(false, 0)
|
|
940
950
|
|
|
941
951
|
req.hash = { index: 2 * index, nodes: f.batch.want.nodes }
|
|
942
952
|
|
|
@@ -1770,6 +1780,7 @@ module.exports = class Replicator {
|
|
|
1770
1780
|
const stream = protomux.stream
|
|
1771
1781
|
|
|
1772
1782
|
peer.channel.open({
|
|
1783
|
+
manifest: true,
|
|
1773
1784
|
capability: caps.replicate(stream.isInitiator, this.key, stream.handshakeHash)
|
|
1774
1785
|
})
|
|
1775
1786
|
|