hypercore 10.0.0-alpha.7 → 10.0.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/README.md +83 -22
- package/index.js +587 -217
- package/lib/bitfield.js +109 -41
- package/lib/block-encryption.js +3 -2
- package/lib/block-store.js +6 -4
- package/lib/caps.js +32 -0
- package/lib/core.js +166 -35
- package/lib/errors.js +50 -0
- package/lib/info.js +23 -0
- package/lib/merkle-tree.js +181 -105
- package/lib/messages.js +249 -168
- package/lib/oplog.js +4 -3
- package/lib/remote-bitfield.js +28 -7
- package/lib/replicator.js +1415 -624
- package/lib/streams.js +56 -0
- package/package.json +20 -15
- package/.github/workflows/test-node.yml +0 -23
- package/CHANGELOG.md +0 -37
- package/UPGRADE.md +0 -9
- package/examples/announce.js +0 -19
- package/examples/basic.js +0 -10
- package/examples/http.js +0 -123
- package/examples/lookup.js +0 -20
- package/lib/extensions.js +0 -76
- package/lib/protocol.js +0 -524
- package/lib/random-iterator.js +0 -46
- package/test/basic.js +0 -90
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- package/test/encryption.js +0 -85
- package/test/extension.js +0 -71
- package/test/helpers/index.js +0 -23
- package/test/merkle-tree.js +0 -518
- package/test/mutex.js +0 -137
- package/test/oplog.js +0 -399
- package/test/preload.js +0 -72
- package/test/replicate.js +0 -372
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
package/lib/bitfield.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// TODO: needs massive improvements obvs
|
|
2
|
-
|
|
3
1
|
const BigSparseArray = require('big-sparse-array')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
const bits = require('bits-to-bytes')
|
|
4
4
|
|
|
5
5
|
class FixedBitfield {
|
|
6
6
|
constructor (index, bitfield) {
|
|
@@ -10,27 +10,26 @@ class FixedBitfield {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
get (index) {
|
|
13
|
-
|
|
14
|
-
const i = (index - j) / 32
|
|
15
|
-
|
|
16
|
-
return i < this.bitfield.length && (this.bitfield[i] & (1 << j)) !== 0
|
|
13
|
+
return bits.get(this.bitfield, index)
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
set (index, val) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const v = this.bitfield[i]
|
|
23
|
-
|
|
24
|
-
if (val === ((v & (1 << j)) !== 0)) return false
|
|
17
|
+
return bits.set(this.bitfield, index, val)
|
|
18
|
+
}
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
setRange (start, length, val) {
|
|
21
|
+
// Using fill instead of setRange is ~2 orders of magnitude faster, but does
|
|
22
|
+
// have the downside of not being able to tell if any bits actually changed.
|
|
23
|
+
bits.fill(this.bitfield, val, start, start + length)
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
firstSet (position) {
|
|
28
|
+
return bits.indexOf(this.bitfield, true, position)
|
|
29
|
+
}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
return true
|
|
31
|
+
lastSet (position) {
|
|
32
|
+
return bits.lastIndexOf(this.bitfield, true, position)
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -40,9 +39,14 @@ module.exports = class Bitfield {
|
|
|
40
39
|
this.pages = new BigSparseArray()
|
|
41
40
|
this.unflushed = []
|
|
42
41
|
this.storage = storage
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
this.resumed = !!(buf && buf.byteLength >= 4)
|
|
43
|
+
|
|
44
|
+
const all = this.resumed
|
|
45
|
+
? new Uint32Array(
|
|
46
|
+
buf.buffer,
|
|
47
|
+
buf.byteOffset,
|
|
48
|
+
Math.floor(buf.byteLength / 4)
|
|
49
|
+
)
|
|
46
50
|
: new Uint32Array(1024)
|
|
47
51
|
|
|
48
52
|
for (let i = 0; i < all.length; i += 1024) {
|
|
@@ -53,47 +57,106 @@ module.exports = class Bitfield {
|
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
get (index) {
|
|
56
|
-
const j = index &
|
|
57
|
-
const i = (index - j) /
|
|
60
|
+
const j = index & (this.pageSize - 1)
|
|
61
|
+
const i = (index - j) / this.pageSize
|
|
62
|
+
|
|
58
63
|
const p = this.pages.get(i)
|
|
59
64
|
|
|
60
65
|
return p ? p.get(j) : false
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
set (index, val) {
|
|
64
|
-
const j = index &
|
|
65
|
-
const i = (index - j) /
|
|
69
|
+
const j = index & (this.pageSize - 1)
|
|
70
|
+
const i = (index - j) / this.pageSize
|
|
66
71
|
|
|
67
72
|
let p = this.pages.get(i)
|
|
68
73
|
|
|
69
|
-
if (!p) {
|
|
70
|
-
if (!val) return
|
|
74
|
+
if (!p && val) {
|
|
71
75
|
p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
if (p && p.set(j, val) && !p.dirty) {
|
|
79
|
+
p.dirty = true
|
|
80
|
+
this.unflushed.push(p)
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
setRange (start, length, val) {
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
let j = start & (this.pageSize - 1)
|
|
86
|
+
let i = (start - j) / this.pageSize
|
|
87
|
+
|
|
88
|
+
while (length > 0) {
|
|
89
|
+
let p = this.pages.get(i)
|
|
90
|
+
|
|
91
|
+
if (!p && val) {
|
|
92
|
+
p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const end = Math.min(j + length, this.pageSize)
|
|
96
|
+
const range = end - j
|
|
97
|
+
|
|
98
|
+
if (p && p.setRange(j, range, val) && !p.dirty) {
|
|
99
|
+
p.dirty = true
|
|
100
|
+
this.unflushed.push(p)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
j = 0
|
|
104
|
+
i++
|
|
105
|
+
length -= range
|
|
83
106
|
}
|
|
84
107
|
}
|
|
85
108
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
firstSet (position) {
|
|
110
|
+
let j = position & (this.pageSize - 1)
|
|
111
|
+
let i = (position - j) / this.pageSize
|
|
112
|
+
|
|
113
|
+
while (i < this.pages.factor) {
|
|
114
|
+
const p = this.pages.get(i)
|
|
115
|
+
|
|
116
|
+
if (p) {
|
|
117
|
+
const index = p.firstSet(j)
|
|
118
|
+
|
|
119
|
+
if (index !== -1) {
|
|
120
|
+
return i * this.pageSize + index
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
j = 0
|
|
125
|
+
i++
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return -1
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lastSet (position) {
|
|
132
|
+
let j = position & (this.pageSize - 1)
|
|
133
|
+
let i = (position - j) / this.pageSize
|
|
134
|
+
|
|
135
|
+
while (i >= 0) {
|
|
136
|
+
const p = this.pages.get(i)
|
|
137
|
+
|
|
138
|
+
if (p) {
|
|
139
|
+
const index = p.lastSet(j)
|
|
140
|
+
|
|
141
|
+
if (index !== -1) {
|
|
142
|
+
return i * this.pageSize + index
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
j = this.pageSize - 1
|
|
147
|
+
i--
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return -1
|
|
90
151
|
}
|
|
91
152
|
|
|
92
153
|
clear () {
|
|
93
154
|
return new Promise((resolve, reject) => {
|
|
94
155
|
this.storage.del(0, Infinity, (err) => {
|
|
95
|
-
if (err) reject(err)
|
|
96
|
-
|
|
156
|
+
if (err) return reject(err)
|
|
157
|
+
this.pages = new BigSparseArray()
|
|
158
|
+
this.unflushed = []
|
|
159
|
+
resolve()
|
|
97
160
|
})
|
|
98
161
|
})
|
|
99
162
|
}
|
|
@@ -116,9 +179,14 @@ module.exports = class Bitfield {
|
|
|
116
179
|
let error = null
|
|
117
180
|
|
|
118
181
|
for (const page of this.unflushed) {
|
|
119
|
-
const
|
|
182
|
+
const buf = b4a.from(
|
|
183
|
+
page.bitfield.buffer,
|
|
184
|
+
page.bitfield.byteOffset,
|
|
185
|
+
page.bitfield.byteLength
|
|
186
|
+
)
|
|
187
|
+
|
|
120
188
|
page.dirty = false
|
|
121
|
-
this.storage.write(page.index * 4096,
|
|
189
|
+
this.storage.write(page.index * 4096, buf, done)
|
|
122
190
|
}
|
|
123
191
|
|
|
124
192
|
function done (err) {
|
|
@@ -147,7 +215,7 @@ module.exports = class Bitfield {
|
|
|
147
215
|
}
|
|
148
216
|
|
|
149
217
|
function ensureSize (uint32, size) {
|
|
150
|
-
if (uint32.
|
|
218
|
+
if (uint32.byteLength === size) return uint32
|
|
151
219
|
const a = new Uint32Array(1024)
|
|
152
220
|
a.set(uint32, 0)
|
|
153
221
|
return a
|
package/lib/block-encryption.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const sodium = require('sodium-universal')
|
|
2
2
|
const c = require('compact-encoding')
|
|
3
|
+
const b4a = require('b4a')
|
|
3
4
|
|
|
4
|
-
const nonce =
|
|
5
|
+
const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
|
|
5
6
|
|
|
6
7
|
module.exports = class BlockEncryption {
|
|
7
8
|
constructor (encryptionKey, hypercoreKey) {
|
|
8
|
-
const subKeys =
|
|
9
|
+
const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
|
|
9
10
|
|
|
10
11
|
this.key = encryptionKey
|
|
11
12
|
this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
|
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,12 +17,12 @@ 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
|
-
clear () {
|
|
23
|
+
clear (offset = 0, length = Infinity) {
|
|
22
24
|
return new Promise((resolve, reject) => {
|
|
23
|
-
this.storage.del(
|
|
25
|
+
this.storage.del(offset, length, (err) => {
|
|
24
26
|
if (err) reject(err)
|
|
25
27
|
else resolve()
|
|
26
28
|
})
|
|
@@ -49,7 +51,7 @@ module.exports = class BlockStore {
|
|
|
49
51
|
return new Promise((resolve, reject) => {
|
|
50
52
|
this.storage.write(offset, data, (err) => {
|
|
51
53
|
if (err) reject(err)
|
|
52
|
-
else resolve()
|
|
54
|
+
else resolve(offset + data.byteLength)
|
|
53
55
|
})
|
|
54
56
|
})
|
|
55
57
|
}
|
package/lib/caps.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
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
|
|
8
|
+
|
|
9
|
+
const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
|
|
10
|
+
|
|
11
|
+
exports.replicate = function (isInitiator, key, handshakeHash) {
|
|
12
|
+
const out = b4a.allocUnsafe(32)
|
|
13
|
+
sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
|
|
14
|
+
return out
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.treeSignable = function (hash, length, fork) {
|
|
18
|
+
const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
|
|
19
|
+
c.raw.encode(state, TREE)
|
|
20
|
+
c.raw.encode(state, hash)
|
|
21
|
+
c.uint64.encode(state, length)
|
|
22
|
+
c.uint64.encode(state, fork)
|
|
23
|
+
return state.buffer
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
exports.treeSignableLegacy = function (hash, length, fork) {
|
|
27
|
+
const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
|
|
28
|
+
c.raw.encode(state, hash)
|
|
29
|
+
c.uint64.encode(state, length)
|
|
30
|
+
c.uint64.encode(state, fork)
|
|
31
|
+
return state.buffer
|
|
32
|
+
}
|
package/lib/core.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
const Oplog = require('./oplog')
|
|
3
4
|
const Mutex = require('./mutex')
|
|
4
5
|
const MerkleTree = require('./merkle-tree')
|
|
5
6
|
const BlockStore = require('./block-store')
|
|
6
7
|
const Bitfield = require('./bitfield')
|
|
7
|
-
const {
|
|
8
|
+
const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('./errors')
|
|
9
|
+
const m = require('./messages')
|
|
8
10
|
|
|
9
11
|
module.exports = class Core {
|
|
10
|
-
constructor (header, crypto, oplog, tree, blocks, bitfield,
|
|
12
|
+
constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
|
|
11
13
|
this.onupdate = onupdate
|
|
12
14
|
this.header = header
|
|
13
15
|
this.crypto = crypto
|
|
@@ -15,7 +17,7 @@ module.exports = class Core {
|
|
|
15
17
|
this.tree = tree
|
|
16
18
|
this.blocks = blocks
|
|
17
19
|
this.bitfield = bitfield
|
|
18
|
-
this.
|
|
20
|
+
this.defaultAuth = auth
|
|
19
21
|
this.truncating = 0
|
|
20
22
|
|
|
21
23
|
this._maxOplogSize = 65536
|
|
@@ -23,6 +25,7 @@ module.exports = class Core {
|
|
|
23
25
|
this._verifies = null
|
|
24
26
|
this._verifiesFlushed = null
|
|
25
27
|
this._mutex = new Mutex()
|
|
28
|
+
this._legacy = legacy
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
static async open (storage, opts = {}) {
|
|
@@ -49,27 +52,46 @@ module.exports = class Core {
|
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
|
|
56
|
+
if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) {
|
|
57
|
+
throw BAD_ARGUMENT('Invalid key pair')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sign = opts.sign
|
|
61
|
+
? opts.sign
|
|
62
|
+
: secretKey
|
|
63
|
+
? (signable) => crypto.sign(signable, secretKey)
|
|
64
|
+
: undefined
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
sign,
|
|
68
|
+
verify (signable, signature) {
|
|
69
|
+
return crypto.verify(signable, signature, publicKey)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
|
|
59
|
-
|
|
75
|
+
let overwrite = opts.overwrite === true
|
|
76
|
+
|
|
77
|
+
const force = opts.force === true
|
|
60
78
|
const createIfMissing = opts.createIfMissing !== false
|
|
61
79
|
const crypto = opts.crypto || hypercoreCrypto
|
|
62
80
|
|
|
63
81
|
const oplog = new Oplog(oplogFile, {
|
|
64
|
-
headerEncoding:
|
|
65
|
-
entryEncoding:
|
|
82
|
+
headerEncoding: m.oplog.header,
|
|
83
|
+
entryEncoding: m.oplog.entry
|
|
66
84
|
})
|
|
67
85
|
|
|
68
86
|
let { header, entries } = await oplog.open()
|
|
69
87
|
|
|
70
|
-
if (
|
|
88
|
+
if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
|
|
89
|
+
overwrite = true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!header || overwrite) {
|
|
71
93
|
if (!createIfMissing) {
|
|
72
|
-
throw
|
|
94
|
+
throw STORAGE_EMPTY('No Hypercore is stored here')
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
header = {
|
|
@@ -84,14 +106,15 @@ module.exports = class Core {
|
|
|
84
106
|
signer: opts.keyPair || crypto.keyPair(),
|
|
85
107
|
hints: {
|
|
86
108
|
reorgs: []
|
|
87
|
-
}
|
|
109
|
+
},
|
|
110
|
+
contiguousLength: 0
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
await oplog.flush(header)
|
|
91
114
|
}
|
|
92
115
|
|
|
93
|
-
if (opts.keyPair && !header.signer.publicKey
|
|
94
|
-
throw
|
|
116
|
+
if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
|
|
117
|
+
throw STORAGE_CONFLICT('Another Hypercore is stored here')
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
|
|
@@ -102,9 +125,18 @@ module.exports = class Core {
|
|
|
102
125
|
await tree.clear()
|
|
103
126
|
await blocks.clear()
|
|
104
127
|
await bitfield.clear()
|
|
128
|
+
entries = []
|
|
129
|
+
} else if (bitfield.resumed && header.tree.length === 0) {
|
|
130
|
+
// If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
|
|
131
|
+
await bitfield.clear()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// compat from earlier version that do not store contig length
|
|
135
|
+
if (header.contiguousLength === 0) {
|
|
136
|
+
while (bitfield.get(header.contiguousLength)) header.contiguousLength++
|
|
105
137
|
}
|
|
106
138
|
|
|
107
|
-
const
|
|
139
|
+
const auth = opts.auth || this.createAuth(crypto, header.signer)
|
|
108
140
|
|
|
109
141
|
for (const e of entries) {
|
|
110
142
|
if (e.userData) {
|
|
@@ -118,7 +150,8 @@ module.exports = class Core {
|
|
|
118
150
|
}
|
|
119
151
|
|
|
120
152
|
if (e.bitfield) {
|
|
121
|
-
bitfield.setRange(e.bitfield.start, e.bitfield.length)
|
|
153
|
+
bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
|
|
154
|
+
updateContig(header, e.bitfield, bitfield)
|
|
122
155
|
}
|
|
123
156
|
|
|
124
157
|
if (e.treeUpgrade) {
|
|
@@ -135,7 +168,7 @@ module.exports = class Core {
|
|
|
135
168
|
}
|
|
136
169
|
}
|
|
137
170
|
|
|
138
|
-
return new this(header, crypto, oplog, tree, blocks, bitfield,
|
|
171
|
+
return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
|
|
139
172
|
}
|
|
140
173
|
|
|
141
174
|
_shouldFlush () {
|
|
@@ -176,7 +209,7 @@ module.exports = class Core {
|
|
|
176
209
|
|
|
177
210
|
for (const u of this.header.userData) {
|
|
178
211
|
if (u.key !== key) continue
|
|
179
|
-
if (value &&
|
|
212
|
+
if (value && b4a.equals(u.value, value)) return
|
|
180
213
|
empty = false
|
|
181
214
|
break
|
|
182
215
|
}
|
|
@@ -200,13 +233,13 @@ module.exports = class Core {
|
|
|
200
233
|
}
|
|
201
234
|
}
|
|
202
235
|
|
|
203
|
-
async truncate (length, fork,
|
|
236
|
+
async truncate (length, fork, auth = this.defaultAuth) {
|
|
204
237
|
this.truncating++
|
|
205
238
|
await this._mutex.lock()
|
|
206
239
|
|
|
207
240
|
try {
|
|
208
241
|
const batch = await this.tree.truncate(length, fork)
|
|
209
|
-
batch.signature = await sign(batch.signable())
|
|
242
|
+
batch.signature = await auth.sign(batch.signable())
|
|
210
243
|
await this._truncate(batch, null)
|
|
211
244
|
} finally {
|
|
212
245
|
this.truncating--
|
|
@@ -214,19 +247,63 @@ module.exports = class Core {
|
|
|
214
247
|
}
|
|
215
248
|
}
|
|
216
249
|
|
|
217
|
-
async
|
|
250
|
+
async clear (start, end) {
|
|
251
|
+
await this._mutex.lock()
|
|
252
|
+
|
|
253
|
+
const entry = {
|
|
254
|
+
userData: null,
|
|
255
|
+
treeNodes: null,
|
|
256
|
+
treeUpgrade: null,
|
|
257
|
+
bitfield: {
|
|
258
|
+
start,
|
|
259
|
+
length: end - start,
|
|
260
|
+
drop: true
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
await this.oplog.append([entry], false)
|
|
265
|
+
|
|
266
|
+
this.bitfield.setRange(start, end - start, false)
|
|
267
|
+
|
|
268
|
+
if (start < this.header.contiguousLength) {
|
|
269
|
+
this.header.contiguousLength = start
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
start = this.bitfield.lastSet(start) + 1
|
|
273
|
+
end = this.bitfield.firstSet(end)
|
|
274
|
+
|
|
275
|
+
if (end === -1) end = this.tree.length
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const offset = await this.tree.byteOffset(start * 2)
|
|
279
|
+
const [byteEnd, byteEndLength] = await this.tree.byteRange((end - 1) * 2)
|
|
280
|
+
const length = (byteEnd + byteEndLength) - offset
|
|
281
|
+
|
|
282
|
+
await this.blocks.clear(offset, length)
|
|
283
|
+
|
|
284
|
+
this.onupdate(0, entry.bitfield, null, null)
|
|
285
|
+
|
|
286
|
+
if (this._shouldFlush()) await this._flushOplog()
|
|
287
|
+
} finally {
|
|
288
|
+
this._mutex.unlock()
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async append (values, auth = this.defaultAuth, hooks = {}) {
|
|
218
293
|
await this._mutex.lock()
|
|
219
294
|
|
|
220
295
|
try {
|
|
221
296
|
if (hooks.preappend) await hooks.preappend(values)
|
|
222
297
|
|
|
223
|
-
if (!values.length)
|
|
298
|
+
if (!values.length) {
|
|
299
|
+
return { length: this.tree.length, byteLength: this.tree.byteLength }
|
|
300
|
+
}
|
|
224
301
|
|
|
225
302
|
const batch = this.tree.batch()
|
|
226
303
|
for (const val of values) batch.append(val)
|
|
227
304
|
|
|
228
305
|
const hash = batch.hash()
|
|
229
|
-
batch.signature = await sign(batch.signable(hash))
|
|
306
|
+
batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
|
|
230
307
|
|
|
231
308
|
const entry = {
|
|
232
309
|
userData: null,
|
|
@@ -239,7 +316,8 @@ module.exports = class Core {
|
|
|
239
316
|
}
|
|
240
317
|
}
|
|
241
318
|
|
|
242
|
-
await this._appendBlocks(values)
|
|
319
|
+
const byteLength = await this._appendBlocks(values)
|
|
320
|
+
|
|
243
321
|
await this.oplog.append([entry], false)
|
|
244
322
|
|
|
245
323
|
this.bitfield.setRange(batch.ancestors, batch.length - batch.ancestors, true)
|
|
@@ -248,21 +326,28 @@ module.exports = class Core {
|
|
|
248
326
|
this.header.tree.length = batch.length
|
|
249
327
|
this.header.tree.rootHash = hash
|
|
250
328
|
this.header.tree.signature = batch.signature
|
|
251
|
-
|
|
329
|
+
|
|
330
|
+
const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
|
|
331
|
+
this.onupdate(status, entry.bitfield, null, null)
|
|
252
332
|
|
|
253
333
|
if (this._shouldFlush()) await this._flushOplog()
|
|
254
334
|
|
|
255
|
-
return batch.
|
|
335
|
+
return { length: batch.length, byteLength }
|
|
256
336
|
} finally {
|
|
257
337
|
this._mutex.unlock()
|
|
258
338
|
}
|
|
259
339
|
}
|
|
260
340
|
|
|
341
|
+
_signed (batch, hash, auth = this.defaultAuth) {
|
|
342
|
+
const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
|
|
343
|
+
return auth.verify(signable, batch.signature)
|
|
344
|
+
}
|
|
345
|
+
|
|
261
346
|
async _verifyExclusive ({ batch, bitfield, value, from }) {
|
|
262
347
|
// TODO: move this to tree.js
|
|
263
348
|
const hash = batch.hash()
|
|
264
|
-
if (!batch.signature || !this.
|
|
265
|
-
throw
|
|
349
|
+
if (!batch.signature || !this._signed(batch, hash)) {
|
|
350
|
+
throw INVALID_SIGNATURE('Proof contains an invalid signature')
|
|
266
351
|
}
|
|
267
352
|
|
|
268
353
|
await this._mutex.lock()
|
|
@@ -281,14 +366,21 @@ module.exports = class Core {
|
|
|
281
366
|
|
|
282
367
|
await this.oplog.append([entry], false)
|
|
283
368
|
|
|
284
|
-
|
|
369
|
+
let status = 0b0001
|
|
370
|
+
|
|
371
|
+
if (bitfield) {
|
|
372
|
+
this.bitfield.set(bitfield.start, true)
|
|
373
|
+
status |= updateContig(this.header, bitfield, this.bitfield)
|
|
374
|
+
}
|
|
375
|
+
|
|
285
376
|
batch.commit()
|
|
286
377
|
|
|
287
378
|
this.header.tree.fork = batch.fork
|
|
288
379
|
this.header.tree.length = batch.length
|
|
289
380
|
this.header.tree.rootHash = batch.rootHash
|
|
290
381
|
this.header.tree.signature = batch.signature
|
|
291
|
-
|
|
382
|
+
|
|
383
|
+
this.onupdate(status, bitfield, value, from)
|
|
292
384
|
|
|
293
385
|
if (this._shouldFlush()) await this._flushOplog()
|
|
294
386
|
} finally {
|
|
@@ -335,9 +427,16 @@ module.exports = class Core {
|
|
|
335
427
|
continue
|
|
336
428
|
}
|
|
337
429
|
|
|
338
|
-
|
|
430
|
+
let status = 0
|
|
431
|
+
|
|
432
|
+
if (bitfield) {
|
|
433
|
+
this.bitfield.set(bitfield.start, true)
|
|
434
|
+
status = updateContig(this.header, bitfield, this.bitfield)
|
|
435
|
+
}
|
|
436
|
+
|
|
339
437
|
batch.commit()
|
|
340
|
-
|
|
438
|
+
|
|
439
|
+
this.onupdate(status, bitfield, value, from)
|
|
341
440
|
}
|
|
342
441
|
|
|
343
442
|
if (this._shouldFlush()) await this._flushOplog()
|
|
@@ -415,13 +514,15 @@ module.exports = class Core {
|
|
|
415
514
|
addReorgHint(this.header.hints.reorgs, this.tree, batch)
|
|
416
515
|
batch.commit()
|
|
417
516
|
|
|
418
|
-
const
|
|
517
|
+
const contigStatus = updateContig(this.header, entry.bitfield, this.bitfield)
|
|
518
|
+
const status = ((batch.length > batch.ancestors) ? 0b0011 : 0b0010) | contigStatus
|
|
419
519
|
|
|
420
520
|
this.header.tree.fork = batch.fork
|
|
421
521
|
this.header.tree.length = batch.length
|
|
422
522
|
this.header.tree.rootHash = batch.hash()
|
|
423
523
|
this.header.tree.signature = batch.signature
|
|
424
|
-
|
|
524
|
+
|
|
525
|
+
this.onupdate(status, entry.bitfield, null, from)
|
|
425
526
|
|
|
426
527
|
// TODO: there is a bug in the merkle tree atm where it cannot handle unflushed
|
|
427
528
|
// truncates if we append or download anything after the truncation point later on
|
|
@@ -442,6 +543,36 @@ module.exports = class Core {
|
|
|
442
543
|
}
|
|
443
544
|
}
|
|
444
545
|
|
|
546
|
+
function updateContig (header, upd, bitfield) {
|
|
547
|
+
const end = upd.start + upd.length
|
|
548
|
+
|
|
549
|
+
let c = header.contiguousLength
|
|
550
|
+
|
|
551
|
+
if (upd.drop) {
|
|
552
|
+
// If we dropped a block in the current contig range, "downgrade" it
|
|
553
|
+
if (c <= end && c > upd.start) {
|
|
554
|
+
c = upd.start
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
if (c <= end && c >= upd.start) {
|
|
558
|
+
c = end
|
|
559
|
+
while (bitfield.get(c)) c++
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (c === header.contiguousLength) {
|
|
564
|
+
return 0b0000
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (c > header.contiguousLength) {
|
|
568
|
+
header.contiguousLength = c
|
|
569
|
+
return 0b0100
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
header.contiguousLength = c
|
|
573
|
+
return 0b1000
|
|
574
|
+
}
|
|
575
|
+
|
|
445
576
|
function addReorgHint (list, tree, batch) {
|
|
446
577
|
if (tree.length === 0 || tree.fork === batch.fork) return
|
|
447
578
|
|