hypercore 10.38.2 → 11.0.1
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 +13 -30
- package/index.js +388 -444
- package/lib/audit.js +33 -41
- package/lib/bit-interlude.js +174 -0
- package/lib/bitfield.js +79 -87
- package/lib/block-store.js +12 -50
- package/lib/copy-prologue.js +236 -0
- package/lib/core.js +414 -746
- package/lib/download.js +42 -4
- package/lib/merkle-tree.js +263 -406
- package/lib/multisig.js +9 -6
- package/lib/mutex.js +4 -0
- package/lib/remote-bitfield.js +9 -9
- package/lib/replicator.js +247 -177
- package/lib/session-state.js +949 -0
- package/lib/verifier.js +20 -13
- package/package.json +2 -2
- package/lib/batch.js +0 -431
- package/lib/big-header.js +0 -55
- package/lib/oplog.js +0 -228
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
const crypto = require('hypercore-crypto')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
const assert = require('nanoassert')
|
|
4
|
+
const flat = require('flat-tree')
|
|
5
|
+
const quickbit = require('quickbit-universal')
|
|
6
|
+
|
|
7
|
+
const { STORAGE_CONFLICT, INVALID_OPERATION, INVALID_SIGNATURE } = require('hypercore-errors')
|
|
8
|
+
|
|
9
|
+
const Mutex = require('./mutex')
|
|
10
|
+
const Bitfield = require('./bitfield')
|
|
11
|
+
const { MerkleTree, MerkleTreeBatch } = require('./merkle-tree')
|
|
12
|
+
|
|
13
|
+
module.exports = class SessionState {
|
|
14
|
+
constructor (core, parent, storage, blocks, tree, treeInfo, name) {
|
|
15
|
+
this.core = core
|
|
16
|
+
this.index = this.core.sessionStates.push(this) - 1
|
|
17
|
+
|
|
18
|
+
this.storage = storage
|
|
19
|
+
this.name = name
|
|
20
|
+
this.sessions = []
|
|
21
|
+
|
|
22
|
+
this.parent = parent
|
|
23
|
+
this.mutex = new Mutex()
|
|
24
|
+
|
|
25
|
+
this.blocks = blocks
|
|
26
|
+
this.tree = tree
|
|
27
|
+
|
|
28
|
+
// merkle state
|
|
29
|
+
this.roots = []
|
|
30
|
+
this.length = 0
|
|
31
|
+
this.fork = treeInfo.fork || 0
|
|
32
|
+
this.prologue = treeInfo.prologue || null
|
|
33
|
+
this.signature = treeInfo.signature || null
|
|
34
|
+
|
|
35
|
+
const deps = this.storage.dependencies
|
|
36
|
+
this.dependencyLength = deps.length ? deps[deps.length - 1].length : Infinity
|
|
37
|
+
|
|
38
|
+
if (treeInfo.roots.length) this.setRoots(treeInfo.roots)
|
|
39
|
+
|
|
40
|
+
this.snapshotCompatLength = this.isSnapshot() ? this.length : -1
|
|
41
|
+
|
|
42
|
+
this.active = 0
|
|
43
|
+
|
|
44
|
+
this._onflush = null
|
|
45
|
+
this._flushing = null
|
|
46
|
+
this._activeTx = null
|
|
47
|
+
this._pendingBitfield = null
|
|
48
|
+
|
|
49
|
+
this.ref()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isSnapshot () {
|
|
53
|
+
return this.storage.snapshotted
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
isDefault () {
|
|
57
|
+
return this.core.state === this
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
createTreeBatch () {
|
|
61
|
+
return new MerkleTreeBatch(this.tree, this)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
addSession (s) {
|
|
65
|
+
if (s._stateIndex !== -1) return
|
|
66
|
+
s._stateIndex = this.sessions.push(s) - 1
|
|
67
|
+
if (s.weak === false) this.core.activeSessions++
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
removeSession (s) {
|
|
71
|
+
if (s._stateIndex === -1) return
|
|
72
|
+
const head = this.sessions.pop()
|
|
73
|
+
if (head !== s) this.sessions[(head._stateIndex = s._stateIndex)] = head
|
|
74
|
+
s._stateIndex = -1
|
|
75
|
+
if (s.weak === false) this.core.activeSessions--
|
|
76
|
+
this.core.checkIfIdle()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
flushedLength () {
|
|
80
|
+
if (this.isDefault() || this.isSnapshot()) return this.length
|
|
81
|
+
return this.dependencyLength
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
unref () {
|
|
85
|
+
if (--this.active > 0) return
|
|
86
|
+
this.close().catch(noop) // technically async, but only for the last db session
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ref () {
|
|
90
|
+
this.active++
|
|
91
|
+
return this
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
hash () {
|
|
95
|
+
return MerkleTree.hash(this)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setRoots (roots) {
|
|
99
|
+
this.roots = roots
|
|
100
|
+
this.length = MerkleTree.span(roots) / 2
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get byteLength () {
|
|
104
|
+
return MerkleTree.size(this.roots)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
treeInfo () {
|
|
108
|
+
return {
|
|
109
|
+
fork: this.fork,
|
|
110
|
+
roots: this.roots.slice(),
|
|
111
|
+
length: this.length,
|
|
112
|
+
prologue: this.prologue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async close () {
|
|
117
|
+
if (this.index === -1) return
|
|
118
|
+
|
|
119
|
+
this.active = 0
|
|
120
|
+
this.mutex.destroy(new Error('Closed')).catch(noop)
|
|
121
|
+
|
|
122
|
+
const closing = this.storage.close()
|
|
123
|
+
|
|
124
|
+
const head = this.core.sessionStates.pop()
|
|
125
|
+
if (head !== this) this.core.sessionStates[(head.index = this.index)] = head
|
|
126
|
+
|
|
127
|
+
this.index = -1
|
|
128
|
+
this.core.checkIfIdle()
|
|
129
|
+
|
|
130
|
+
return closing
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
snapshot () {
|
|
134
|
+
const s = new SessionState(
|
|
135
|
+
this.core,
|
|
136
|
+
null,
|
|
137
|
+
this.storage.snapshot(),
|
|
138
|
+
this.blocks,
|
|
139
|
+
this.tree.clone(),
|
|
140
|
+
this.treeInfo(),
|
|
141
|
+
this.name
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return s
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
updateDependency (storage, length) {
|
|
148
|
+
const dependency = updateDependency(this, length)
|
|
149
|
+
if (dependency) {
|
|
150
|
+
this.dependencyLength = dependency.length
|
|
151
|
+
storage.setDependency(dependency)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return dependency
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_clearActiveBatch (err) {
|
|
158
|
+
if (!this._activeTx) return
|
|
159
|
+
this._activeTx = null
|
|
160
|
+
|
|
161
|
+
if (this._onflush) this._onflush(err)
|
|
162
|
+
|
|
163
|
+
this._onflush = null
|
|
164
|
+
this._flushing = null
|
|
165
|
+
|
|
166
|
+
this._activeTx = null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
createWriteBatch () {
|
|
170
|
+
assert(!this._activeTx && !this.storage.snapshotted)
|
|
171
|
+
|
|
172
|
+
this._activeTx = this.storage.write()
|
|
173
|
+
return this._activeTx
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_unlock (lock) {
|
|
177
|
+
this._clearActiveBatch()
|
|
178
|
+
this.mutex.unlock()
|
|
179
|
+
this.core.checkIfIdle()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async flush () {
|
|
183
|
+
const tx = this._activeTx
|
|
184
|
+
this._activeTx = null
|
|
185
|
+
|
|
186
|
+
const flushing = tx.flush()
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
if (!this._flushing) this._flushing = flushing
|
|
190
|
+
|
|
191
|
+
return flushing
|
|
192
|
+
} finally {
|
|
193
|
+
this._clearActiveBatch()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_commit () {
|
|
198
|
+
const bitfield = this._pendingBitfield
|
|
199
|
+
this._pendingBitfield = null
|
|
200
|
+
|
|
201
|
+
return this.parent._oncommit(this, bitfield)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async _oncommit (src, bitfield) {
|
|
205
|
+
this.fork = src.fork
|
|
206
|
+
this.length = src.length
|
|
207
|
+
this.roots = src.roots.slice()
|
|
208
|
+
this.signature = src.signature
|
|
209
|
+
|
|
210
|
+
const tree = {
|
|
211
|
+
fork: this.fork,
|
|
212
|
+
length: this.length,
|
|
213
|
+
rootHash: this.hash(),
|
|
214
|
+
signature: this.signature
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// handle migration
|
|
218
|
+
if (src.core !== this.core) {
|
|
219
|
+
this.prologue = src.prologue
|
|
220
|
+
this.storage = await src.core.state.storage.resumeSession(this.name)
|
|
221
|
+
this.tree = new MerkleTree(this.storage)
|
|
222
|
+
|
|
223
|
+
for (let i = this.core.sessionStates.length - 1; i >= 0; i--) {
|
|
224
|
+
const state = this.core.sessionStates[i]
|
|
225
|
+
if (state === this) continue
|
|
226
|
+
if (state.name === this.name) state._moveToCore(src.core)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this._moveToCore(src.core)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!bitfield || !bitfield.drop) {
|
|
233
|
+
this.onappend(tree, bitfield, true)
|
|
234
|
+
} else {
|
|
235
|
+
this.ontruncate(tree, bitfield.start, bitfield.start + bitfield.length, true)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
flushed () {
|
|
240
|
+
if (!this._activeTx) return
|
|
241
|
+
|
|
242
|
+
if (this._flushing) return this._flushing
|
|
243
|
+
|
|
244
|
+
this._flushing = new Promise(resolve => {
|
|
245
|
+
this._onflush = resolve
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
return this._flushing
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async setUserData (key, value) {
|
|
252
|
+
await this.mutex.lock()
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const tx = this.createWriteBatch()
|
|
256
|
+
tx.putUserData(key, value)
|
|
257
|
+
|
|
258
|
+
return await this.flush()
|
|
259
|
+
} finally {
|
|
260
|
+
this._unlock()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async _verifyBlock (batch, bitfield, value, manifest, from) {
|
|
265
|
+
await this.mutex.lock()
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const tx = this.createWriteBatch()
|
|
269
|
+
this.updating = true
|
|
270
|
+
|
|
271
|
+
if (bitfield) this.blocks.put(tx, bitfield.start, value)
|
|
272
|
+
|
|
273
|
+
if (bitfield && this.isDefault()) {
|
|
274
|
+
await storeBitfieldRange(this.storage, tx, bitfield.start, bitfield.start + 1, true)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (manifest) this.core._setManifest(tx, manifest, null)
|
|
278
|
+
|
|
279
|
+
if (batch.commitable()) {
|
|
280
|
+
batch.commit(tx)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const head = {
|
|
284
|
+
fork: batch.fork,
|
|
285
|
+
length: batch.length,
|
|
286
|
+
rootHash: batch.hash(),
|
|
287
|
+
signature: batch.signature
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (batch.upgraded) tx.setHead(head)
|
|
291
|
+
|
|
292
|
+
const flushed = await this.flush()
|
|
293
|
+
|
|
294
|
+
if (batch.upgraded) {
|
|
295
|
+
this.roots = batch.roots
|
|
296
|
+
this.length = batch.length
|
|
297
|
+
this.fork = batch.fork
|
|
298
|
+
this.signature = batch.signature
|
|
299
|
+
|
|
300
|
+
this.onappend(head, bitfield, flushed)
|
|
301
|
+
}
|
|
302
|
+
} finally {
|
|
303
|
+
this._clearActiveBatch()
|
|
304
|
+
this.updating = false
|
|
305
|
+
this.mutex.unlock()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async truncate (length, fork, { signature, keyPair } = {}) {
|
|
310
|
+
if (this.prologue && length < this.prologue.length) {
|
|
311
|
+
throw INVALID_OPERATION('Truncation breaks prologue')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair
|
|
315
|
+
|
|
316
|
+
await this.mutex.lock()
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const batch = this.createTreeBatch()
|
|
320
|
+
await this.tree.truncate(length, batch, fork)
|
|
321
|
+
|
|
322
|
+
if (!signature && keyPair && length > 0) signature = this.core.verifier.sign(batch, keyPair)
|
|
323
|
+
if (signature) batch.signature = signature
|
|
324
|
+
|
|
325
|
+
const tx = this.createWriteBatch()
|
|
326
|
+
|
|
327
|
+
// upsert compat manifest
|
|
328
|
+
if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)
|
|
329
|
+
|
|
330
|
+
const { dependency, tree, roots } = await this._truncate(tx, batch)
|
|
331
|
+
|
|
332
|
+
const flushed = await this.flush()
|
|
333
|
+
|
|
334
|
+
this.fork = tree.fork
|
|
335
|
+
this.length = tree.length
|
|
336
|
+
this.roots = roots
|
|
337
|
+
this.signature = tree.signature
|
|
338
|
+
|
|
339
|
+
if (dependency) this.storage.updateDependencyLength(this.dependencyLength)
|
|
340
|
+
|
|
341
|
+
this.ontruncate(tree, tree.length, batch.treeLength, flushed)
|
|
342
|
+
} finally {
|
|
343
|
+
this._unlock()
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async reorg (batch) {
|
|
348
|
+
await this.mutex.lock()
|
|
349
|
+
|
|
350
|
+
const storage = this.createWriteBatch()
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
if (!batch.commitable()) return false
|
|
354
|
+
|
|
355
|
+
const { dependency, tree } = await this._truncate(storage, batch)
|
|
356
|
+
|
|
357
|
+
const flushed = await this.flush()
|
|
358
|
+
|
|
359
|
+
this.fork = batch.fork
|
|
360
|
+
this.length = batch.length
|
|
361
|
+
this.roots = batch.roots
|
|
362
|
+
this.signature = batch.signature
|
|
363
|
+
|
|
364
|
+
if (dependency) this.storage.updateDependencyLength(this.dependencyLength)
|
|
365
|
+
|
|
366
|
+
this.ontruncate(tree, batch.ancestors, batch.treeLength, flushed)
|
|
367
|
+
} finally {
|
|
368
|
+
this._unlock()
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async _truncate (storage, batch) {
|
|
373
|
+
storage.deleteBlockRange(batch.ancestors, batch.treeLength)
|
|
374
|
+
|
|
375
|
+
if (batch.commitable()) batch.commit(storage)
|
|
376
|
+
|
|
377
|
+
const tree = {
|
|
378
|
+
fork: batch.fork,
|
|
379
|
+
length: batch.length,
|
|
380
|
+
rootHash: batch.hash(),
|
|
381
|
+
signature: batch.signature
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (tree) storage.setHead(tree)
|
|
385
|
+
|
|
386
|
+
const truncated = batch.length < this.flushedLength()
|
|
387
|
+
const dependency = truncated ? updateDependency(this, batch.length) : null
|
|
388
|
+
|
|
389
|
+
if (dependency) this.dependencyLength = dependency.length
|
|
390
|
+
|
|
391
|
+
if (this.isDefault()) {
|
|
392
|
+
await storeBitfieldRange(this.storage, storage, batch.ancestors, batch.treeLength, false)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return { dependency, tree, roots: batch.roots }
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async clear (start, end, cleared) {
|
|
399
|
+
await this.mutex.lock()
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const tx = this.createWriteBatch()
|
|
403
|
+
|
|
404
|
+
if (this.isDefault()) await storeBitfieldRange(this.storage, tx, start, end, false)
|
|
405
|
+
|
|
406
|
+
this.blocks.clear(tx, start, end)
|
|
407
|
+
|
|
408
|
+
const dependency = start < this.flushedLength() ? updateDependency(this, start) : null
|
|
409
|
+
|
|
410
|
+
if (dependency) this.dependencyLength = dependency.length
|
|
411
|
+
|
|
412
|
+
const flushed = await this.flush()
|
|
413
|
+
|
|
414
|
+
if (dependency) this.storage.updateDependencyLength(this.dependencyLength)
|
|
415
|
+
|
|
416
|
+
// todo: atomic event handle
|
|
417
|
+
if (this.isDefault() && flushed) {
|
|
418
|
+
const length = end - start
|
|
419
|
+
this.core.updateContiguousLength({ start, length, drop: true })
|
|
420
|
+
this.core._setBitfieldRanges(start, end, false)
|
|
421
|
+
this.core.replicator.onhave(start, length, true)
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
this._unlock()
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async append (values, { signature, keyPair, preappend } = {}) {
|
|
429
|
+
if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair
|
|
430
|
+
|
|
431
|
+
await this.mutex.lock()
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const tx = this.createWriteBatch()
|
|
435
|
+
|
|
436
|
+
// upsert compat manifest
|
|
437
|
+
if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)
|
|
438
|
+
|
|
439
|
+
if (preappend) await preappend(values)
|
|
440
|
+
|
|
441
|
+
if (!values.length) {
|
|
442
|
+
await this.flush()
|
|
443
|
+
return { length: this.length, byteLength: this.byteLength }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const batch = this.createTreeBatch()
|
|
447
|
+
for (const val of values) batch.append(val)
|
|
448
|
+
|
|
449
|
+
// only multisig can have prologue so signature is always present
|
|
450
|
+
if (this.prologue && batch.length < this.prologue.length) {
|
|
451
|
+
throw INVALID_OPERATION('Append is not consistent with prologue')
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (!signature && keyPair) signature = this.core.verifier.sign(batch, keyPair)
|
|
455
|
+
if (signature) batch.signature = signature
|
|
456
|
+
|
|
457
|
+
batch.commit(tx)
|
|
458
|
+
|
|
459
|
+
const tree = {
|
|
460
|
+
fork: batch.fork,
|
|
461
|
+
length: batch.length,
|
|
462
|
+
rootHash: batch.hash(),
|
|
463
|
+
signature: batch.signature
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
tx.setHead(tree)
|
|
467
|
+
|
|
468
|
+
if (this.isDefault()) await storeBitfieldRange(this.storage, tx, batch.ancestors, batch.length, true)
|
|
469
|
+
|
|
470
|
+
this.blocks.putBatch(tx, this.length, values)
|
|
471
|
+
|
|
472
|
+
const bitfield = {
|
|
473
|
+
drop: false,
|
|
474
|
+
start: batch.ancestors,
|
|
475
|
+
length: values.length
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const flushed = await this.flush()
|
|
479
|
+
|
|
480
|
+
this.fork = batch.fork
|
|
481
|
+
this.roots = batch.roots
|
|
482
|
+
this.length = batch.length
|
|
483
|
+
this.signature = batch.signature
|
|
484
|
+
|
|
485
|
+
this.onappend(tree, bitfield, flushed)
|
|
486
|
+
|
|
487
|
+
return { length: this.length, byteLength: this.byteLength }
|
|
488
|
+
} finally {
|
|
489
|
+
this._unlock()
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
onappend (tree, bitfield, flushed) {
|
|
494
|
+
if (!flushed) this._updateBitfield(bitfield)
|
|
495
|
+
else if (this.isDefault()) this.core.onappend(tree, bitfield)
|
|
496
|
+
|
|
497
|
+
for (let i = this.sessions.length - 1; i >= 0; i--) {
|
|
498
|
+
this.sessions[i].emit('append')
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
ontruncate (tree, to, from, flushed) {
|
|
503
|
+
const bitfield = { start: to, length: from - to, drop: true }
|
|
504
|
+
|
|
505
|
+
if (!flushed) this._updateBitfield(bitfield)
|
|
506
|
+
else if (this.isDefault()) this.core.ontruncate(tree, bitfield)
|
|
507
|
+
|
|
508
|
+
for (let i = this.sessions.length - 1; i >= 0; i--) {
|
|
509
|
+
this.sessions[i].emit('truncate', to, tree.fork)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
_updateBitfield (bitfield, flushed) {
|
|
514
|
+
const p = this._pendingBitfield
|
|
515
|
+
|
|
516
|
+
if (p === null) {
|
|
517
|
+
if (bitfield) this._pendingBitfield = bitfield
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (p.drop) {
|
|
522
|
+
const from = bitfield.start + bitfield.length
|
|
523
|
+
if (!bitfield.drop || p.start !== from) throw INVALID_OPERATION('Atomic truncations must be contiguous')
|
|
524
|
+
|
|
525
|
+
p.length += bitfield.length
|
|
526
|
+
p.start = bitfield.start
|
|
527
|
+
} else {
|
|
528
|
+
p.length = bitfield.start + bitfield.length - p.start
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async _overwrite (source, fork, length, treeLength, signature, isDependent, shallow) {
|
|
533
|
+
const blockPromises = []
|
|
534
|
+
const treePromises = []
|
|
535
|
+
const rootPromises = []
|
|
536
|
+
|
|
537
|
+
const rx = source.storage.read()
|
|
538
|
+
|
|
539
|
+
for (const root of flat.fullRoots(length * 2)) {
|
|
540
|
+
rootPromises.push(rx.getTreeNode(root))
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (shallow !== true) {
|
|
544
|
+
for (const index of flat.patch(treeLength * 2, length * 2)) {
|
|
545
|
+
treePromises.push(rx.getTreeNode(index))
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
for (let i = treeLength; i < length; i++) {
|
|
549
|
+
treePromises.push(rx.getTreeNode(i * 2))
|
|
550
|
+
treePromises.push(rx.getTreeNode(i * 2 + 1))
|
|
551
|
+
blockPromises.push(rx.getBlock(i))
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
rx.tryFlush()
|
|
556
|
+
|
|
557
|
+
const blocks = await Promise.all(blockPromises)
|
|
558
|
+
const nodes = await Promise.all(treePromises)
|
|
559
|
+
const roots = await Promise.all(rootPromises)
|
|
560
|
+
|
|
561
|
+
if (this.core.destroyed) throw new Error('Core destroyed')
|
|
562
|
+
|
|
563
|
+
if (signature) {
|
|
564
|
+
const batch = this.createTreeBatch()
|
|
565
|
+
batch.roots = roots
|
|
566
|
+
batch.length = length
|
|
567
|
+
|
|
568
|
+
if (!this.core.verifier.verify(batch, signature)) {
|
|
569
|
+
throw INVALID_SIGNATURE('Signature is not valid over committed tree')
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const tx = this.createWriteBatch()
|
|
574
|
+
|
|
575
|
+
// truncate existing tree
|
|
576
|
+
if (treeLength < this.length) {
|
|
577
|
+
tx.deleteBlockRange(treeLength, this.length)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
for (const node of nodes) {
|
|
581
|
+
if (node !== null) tx.putTreeNode(node)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
585
|
+
tx.putBlock(i + treeLength, blocks[i])
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const totalLength = Math.max(length, this.length)
|
|
589
|
+
|
|
590
|
+
const firstPage = getBitfieldPage(treeLength)
|
|
591
|
+
const lastPage = getBitfieldPage(totalLength)
|
|
592
|
+
|
|
593
|
+
let index = treeLength
|
|
594
|
+
|
|
595
|
+
for (let i = firstPage; i <= lastPage; i++) {
|
|
596
|
+
const page = b4a.alloc(Bitfield.BYTES_PER_PAGE)
|
|
597
|
+
tx.putBitfieldPage(i, page)
|
|
598
|
+
|
|
599
|
+
if (index < length) {
|
|
600
|
+
index = fillBitfieldPage(page, index, length, i, true)
|
|
601
|
+
if (index < length) continue
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (index < this.length) {
|
|
605
|
+
index = fillBitfieldPage(page, index, this.length, i, false)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const tree = {
|
|
610
|
+
fork,
|
|
611
|
+
length,
|
|
612
|
+
rootHash: crypto.tree(roots),
|
|
613
|
+
signature
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const upgraded = treeLength < this.length || this.length < length || tree.fork !== this.tree.fork
|
|
617
|
+
|
|
618
|
+
if (upgraded) tx.setHead(tree)
|
|
619
|
+
|
|
620
|
+
const dependency = isDependent ? updateDependency(this, length) : null
|
|
621
|
+
|
|
622
|
+
if (dependency) this.dependencyLength = dependency.length
|
|
623
|
+
|
|
624
|
+
const flushed = await this.flush()
|
|
625
|
+
|
|
626
|
+
this.fork = tree.fork
|
|
627
|
+
this.roots = roots
|
|
628
|
+
this.length = length
|
|
629
|
+
this.signature = signature
|
|
630
|
+
|
|
631
|
+
if (dependency) this.storage.updateDependencyLength(this.dependencyLength)
|
|
632
|
+
|
|
633
|
+
return { tree, flushed }
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async overwrite (state, { length = state.tree.length, treeLength = state.flushedLength(), shallow = false } = {}) {
|
|
637
|
+
assert(!this.isDefault(), 'Cannot overwrite signed state') // TODO: make this check better
|
|
638
|
+
|
|
639
|
+
await this.mutex.lock()
|
|
640
|
+
|
|
641
|
+
try {
|
|
642
|
+
const origLength = this.length
|
|
643
|
+
const fork = treeLength < origLength ? this.fork + 1 : this.fork
|
|
644
|
+
|
|
645
|
+
const { tree, flushed } = await this._overwrite(state, fork, length, treeLength, null, state === this.core.state, shallow)
|
|
646
|
+
|
|
647
|
+
const bitfield = { start: treeLength, length: tree.length - treeLength, drop: false }
|
|
648
|
+
|
|
649
|
+
if (treeLength < origLength) this.ontruncate(tree, treeLength, origLength, flushed)
|
|
650
|
+
if (treeLength < tree.length) this.onappend(tree, bitfield, flushed)
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
length: this.length,
|
|
654
|
+
byteLength: this.byteLength
|
|
655
|
+
}
|
|
656
|
+
} finally {
|
|
657
|
+
this._clearActiveBatch()
|
|
658
|
+
this.updating = false
|
|
659
|
+
this.mutex.unlock()
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async commit (state, { signature, keyPair, length = state.length, treeLength = state.flushedLength(), overwrite = false } = {}) {
|
|
664
|
+
assert(this.isDefault() || (this.parent && this.parent.isDefault()), 'Can only commit into default state')
|
|
665
|
+
|
|
666
|
+
let srcLocked = false
|
|
667
|
+
await this.mutex.lock()
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
await state.mutex.lock()
|
|
671
|
+
srcLocked = true
|
|
672
|
+
|
|
673
|
+
await this.core._validateCommit(state, treeLength)
|
|
674
|
+
|
|
675
|
+
if (this.length < length && !signature) {
|
|
676
|
+
if (!keyPair) keyPair = this.core.header.keyPair
|
|
677
|
+
const batch = state.createTreeBatch()
|
|
678
|
+
if (length !== batch.length) await batch.restore(length)
|
|
679
|
+
signature = this.core.verifier.sign(batch, keyPair)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const { tree, flushed } = await this._overwrite(state, this.fork, length, treeLength, signature, false, false)
|
|
683
|
+
|
|
684
|
+
// gc blocks from source
|
|
685
|
+
if (treeLength < length) {
|
|
686
|
+
const tx = state.createWriteBatch()
|
|
687
|
+
|
|
688
|
+
state.blocks.clear(tx, treeLength, length)
|
|
689
|
+
const dependency = state.updateDependency(tx, length)
|
|
690
|
+
|
|
691
|
+
await state.flush(tx)
|
|
692
|
+
|
|
693
|
+
if (dependency) {
|
|
694
|
+
state.storage.updateDependencyLength(dependency.length)
|
|
695
|
+
state.dependencyLength = dependency.length
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const bitfield = { start: treeLength, length: length - treeLength, drop: false }
|
|
700
|
+
this.onappend(tree, bitfield, flushed)
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
length: this.length,
|
|
704
|
+
byteLength: this.byteLength
|
|
705
|
+
}
|
|
706
|
+
} finally {
|
|
707
|
+
this.updating = false
|
|
708
|
+
this.mutex.unlock()
|
|
709
|
+
|
|
710
|
+
if (srcLocked) {
|
|
711
|
+
state.mutex.unlock()
|
|
712
|
+
state._clearActiveBatch()
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
this.core.checkIfIdle()
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async _getTreeHeadAt (length) {
|
|
720
|
+
if (length === null) return this.treeInfo()
|
|
721
|
+
|
|
722
|
+
const head = getDefaultTree()
|
|
723
|
+
|
|
724
|
+
head.length = length
|
|
725
|
+
|
|
726
|
+
const roots = await this.tree.getRoots(length)
|
|
727
|
+
const rootHash = crypto.tree(roots)
|
|
728
|
+
|
|
729
|
+
head.fork = this.fork
|
|
730
|
+
head.rootHash = rootHash
|
|
731
|
+
|
|
732
|
+
return head
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
_moveToCore (core) {
|
|
736
|
+
const head = this.core.sessionStates.pop()
|
|
737
|
+
if (head !== this) this.core.sessionStates[(head.index = this.index)] = head
|
|
738
|
+
|
|
739
|
+
this.core = core
|
|
740
|
+
this.index = this.core.sessionStates.push(this) - 1
|
|
741
|
+
|
|
742
|
+
for (let i = this.sessions.length - 1; i >= 0; i--) this.sessions[i].transferSession(this.core)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async moveTo (core, length) {
|
|
746
|
+
const state = core.state
|
|
747
|
+
|
|
748
|
+
await this.mutex.lock()
|
|
749
|
+
|
|
750
|
+
try {
|
|
751
|
+
if (state.storage && (await state.storage.resumeSession(this.name)) !== null) {
|
|
752
|
+
throw STORAGE_CONFLICT('Batch has already been created')
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const treeLength = this.length
|
|
756
|
+
|
|
757
|
+
if (!this.isSnapshot()) {
|
|
758
|
+
const truncation = length < this.length ? await truncateAndFlush(this, length) : null
|
|
759
|
+
|
|
760
|
+
const treeInfo = truncation ? truncation.tree : await state._getTreeHeadAt(this.length)
|
|
761
|
+
|
|
762
|
+
treeInfo.fork = truncation ? this.fork + 1 : this.fork
|
|
763
|
+
|
|
764
|
+
// todo: validate treeInfo
|
|
765
|
+
|
|
766
|
+
if (!this.storage.atom) {
|
|
767
|
+
this.storage = await state.storage.createSession(this.name, treeInfo)
|
|
768
|
+
} else {
|
|
769
|
+
const s = state.storage.atomize(this.storage.atom)
|
|
770
|
+
this.storage = await s.createSession(this.name, treeInfo)
|
|
771
|
+
await s.close()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
this.tree = new MerkleTree(this.storage)
|
|
775
|
+
|
|
776
|
+
this.prologue = state.prologue
|
|
777
|
+
this.fork = treeInfo.fork
|
|
778
|
+
this.length = length
|
|
779
|
+
this.roots = await this.tree.getRoots(length)
|
|
780
|
+
|
|
781
|
+
if (truncation) {
|
|
782
|
+
const { dependency, tree, flushed } = truncation
|
|
783
|
+
|
|
784
|
+
if (dependency) this.storage.updateDependencyLength(this.dependencyLength)
|
|
785
|
+
this.ontruncate(tree, tree.length, treeLength, flushed)
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (!this.storage.atom) {
|
|
790
|
+
for (let i = this.core.sessionStates.length - 1; i >= 0; i--) {
|
|
791
|
+
const state = this.core.sessionStates[i]
|
|
792
|
+
if (state === this) continue
|
|
793
|
+
if (state.name === this.name) state._moveToCore(core)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
this._moveToCore(core)
|
|
798
|
+
} finally {
|
|
799
|
+
this.mutex.unlock()
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async createSession (name, length, overwrite, atom) {
|
|
804
|
+
let storage = null
|
|
805
|
+
let treeInfo = null
|
|
806
|
+
|
|
807
|
+
if (!atom && !overwrite && this.storage) {
|
|
808
|
+
storage = await this.storage.resumeSession(name)
|
|
809
|
+
|
|
810
|
+
if (storage !== null) {
|
|
811
|
+
treeInfo = (await getCoreHead(storage)) || getDefaultTree()
|
|
812
|
+
if (length !== -1 && treeInfo.length !== length) throw STORAGE_CONFLICT('Different batch stored here')
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (length === -1) length = treeInfo ? treeInfo.length : this.length
|
|
817
|
+
|
|
818
|
+
if (storage === null) {
|
|
819
|
+
treeInfo = await this._getTreeHeadAt(length)
|
|
820
|
+
|
|
821
|
+
if (atom) {
|
|
822
|
+
storage = await this.storage.createAtomicSession(atom, treeInfo)
|
|
823
|
+
} else {
|
|
824
|
+
storage = await this.storage.createSession(name, treeInfo)
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const tree = new MerkleTree(storage)
|
|
829
|
+
|
|
830
|
+
const head = {
|
|
831
|
+
fork: this.fork,
|
|
832
|
+
roots: length === this.length ? this.roots.slice() : await tree.getRoots(length),
|
|
833
|
+
length,
|
|
834
|
+
prologue: this.prologue
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const state = new SessionState(
|
|
838
|
+
this.core,
|
|
839
|
+
atom ? this : null,
|
|
840
|
+
storage,
|
|
841
|
+
this.core.blocks,
|
|
842
|
+
tree,
|
|
843
|
+
head,
|
|
844
|
+
atom ? this.name : name
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
if (atom) atom.onflush(state._commit.bind(state))
|
|
848
|
+
|
|
849
|
+
return state
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function noop () {}
|
|
854
|
+
|
|
855
|
+
function getBitfieldPage (index) {
|
|
856
|
+
return Math.floor(index / Bitfield.BITS_PER_PAGE)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function getBitfieldOffset (index) {
|
|
860
|
+
return index & (Bitfield.BITS_PER_PAGE - 1)
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function fillBitfieldPage (page, start, end, pageIndex, value) {
|
|
864
|
+
const last = ((pageIndex + 1) * Bitfield.BITS_PER_PAGE) - 1
|
|
865
|
+
const from = getBitfieldOffset(start)
|
|
866
|
+
|
|
867
|
+
const index = last < end ? last : end
|
|
868
|
+
const to = getBitfieldOffset(index)
|
|
869
|
+
|
|
870
|
+
quickbit.fill(page, value, from, to)
|
|
871
|
+
|
|
872
|
+
return index
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async function storeBitfieldRange (storage, tx, from, to, value) {
|
|
876
|
+
const firstPage = getBitfieldPage(from)
|
|
877
|
+
const lastPage = getBitfieldPage(to)
|
|
878
|
+
|
|
879
|
+
let index = from
|
|
880
|
+
|
|
881
|
+
const rx = storage.read()
|
|
882
|
+
|
|
883
|
+
const promises = []
|
|
884
|
+
for (let i = firstPage; i <= lastPage; i++) {
|
|
885
|
+
promises.push(rx.getBitfieldPage(i))
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
rx.tryFlush()
|
|
889
|
+
const pages = await Promise.all(promises)
|
|
890
|
+
|
|
891
|
+
for (let i = 0; i <= lastPage - firstPage; i++) {
|
|
892
|
+
const pageIndex = i + firstPage
|
|
893
|
+
if (!pages[i]) pages[i] = b4a.alloc(Bitfield.BYTES_PER_PAGE)
|
|
894
|
+
|
|
895
|
+
index = fillBitfieldPage(pages[i], index, to, pageIndex, true)
|
|
896
|
+
tx.putBitfieldPage(pageIndex, pages[i])
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async function truncateAndFlush (s, length) {
|
|
901
|
+
const batch = s.createTreeBatch()
|
|
902
|
+
await s.tree.truncate(length, batch, s.tree.fork)
|
|
903
|
+
const tx = s.createWriteBatch()
|
|
904
|
+
|
|
905
|
+
const info = await s._truncate(tx, batch)
|
|
906
|
+
const flushed = await s.flush()
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
tree: info.tree,
|
|
910
|
+
roots: info.roots,
|
|
911
|
+
dependency: info.dependency,
|
|
912
|
+
flushed
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function updateDependency (state, length) {
|
|
917
|
+
const dependency = findDependency(state.storage, length)
|
|
918
|
+
if (dependency === null) return null // skip default state and overlays of default
|
|
919
|
+
|
|
920
|
+
return {
|
|
921
|
+
dataPointer: dependency.dataPointer,
|
|
922
|
+
length
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function findDependency (storage, length) {
|
|
927
|
+
for (let i = storage.dependencies.length - 1; i >= 0; i--) {
|
|
928
|
+
const dep = storage.dependencies[i]
|
|
929
|
+
if (dep.length < length) return dep
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return null
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function getDefaultTree () {
|
|
936
|
+
return {
|
|
937
|
+
fork: 0,
|
|
938
|
+
length: 0,
|
|
939
|
+
rootHash: null,
|
|
940
|
+
signature: null
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function getCoreHead (storage) {
|
|
945
|
+
const b = storage.read()
|
|
946
|
+
const p = b.getHead()
|
|
947
|
+
b.tryFlush()
|
|
948
|
+
return p
|
|
949
|
+
}
|