hypercore 10.38.1 → 11.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/lib/core.js CHANGED
@@ -1,91 +1,154 @@
1
- const hypercoreCrypto = require('hypercore-crypto')
1
+ const crypto = require('hypercore-crypto')
2
2
  const b4a = require('b4a')
3
3
  const unslab = require('unslab')
4
- const Oplog = require('./oplog')
5
- const BigHeader = require('./big-header')
4
+ const z32 = require('z32')
6
5
  const Mutex = require('./mutex')
7
- const MerkleTree = require('./merkle-tree')
6
+ const { MerkleTree, ReorgBatch } = require('./merkle-tree')
8
7
  const BlockStore = require('./block-store')
8
+ const BitInterlude = require('./bit-interlude')
9
9
  const Bitfield = require('./bitfield')
10
10
  const RemoteBitfield = require('./remote-bitfield')
11
- const Info = require('./info')
12
11
  const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_OPERATION, INVALID_SIGNATURE, INVALID_CHECKSUM } = require('hypercore-errors')
13
- const m = require('./messages')
14
12
  const Verifier = require('./verifier')
15
13
  const audit = require('./audit')
14
+ const copyPrologue = require('./copy-prologue')
15
+ const SessionState = require('./session-state')
16
+ const Replicator = require('./replicator')
16
17
 
17
18
  module.exports = class Core {
18
- constructor (header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, sessions, legacy, globalCache, onupdate, onconflict) {
19
- this.onupdate = onupdate
20
- this.onconflict = onconflict
19
+ constructor (db, opts = {}) {
20
+ this.db = db
21
+ this.storage = null
22
+ this.replicator = new Replicator(this, opts)
23
+ this.sessionStates = []
24
+ this.monitors = []
25
+ this.activeSessions = 0
26
+
27
+ this.id = opts.key ? z32.encode(opts.key) : null
28
+ this.key = opts.key || null
29
+ this.discoveryKey = opts.discoveryKey || (opts.key && crypto.discoveryKey(opts.key)) || null
30
+ this.manifest = null
31
+ this.opening = null
32
+ this.closing = null
33
+ this.exclusive = null
34
+
21
35
  this.preupdate = null
22
- this.header = header
23
- this.compat = compat
24
- this.crypto = crypto
25
- this.oplog = oplog
26
- this.bigHeader = bigHeader
27
- this.tree = tree
28
- this.blocks = blocks
29
- this.bitfield = bitfield
30
- this.verifier = verifier
36
+ this.header = null
37
+ this.compat = false
38
+ this.tree = null
39
+ this.blocks = null
40
+ this.bitfield = null
41
+ this.verifier = null
31
42
  this.truncating = 0
32
43
  this.updating = false
33
- this.closed = false
34
44
  this.skipBitfield = null
35
- this.active = sessions.length
36
- this.sessions = sessions
37
- this.globalCache = globalCache
45
+ this.globalCache = opts.globalCache || null
46
+ this.autoClose = opts.autoClose !== false
47
+ this.encryption = null
48
+ this.onidle = noop
49
+
50
+ this.state = null
51
+ this.opened = false
52
+ this.destroyed = false
53
+ this.closed = false
38
54
 
39
- this._manifestFlushed = !!header.manifest
40
- this._maxOplogSize = 65536
41
- this._autoFlush = 1
55
+ this._manifestFlushed = false
56
+ this._onflush = null
57
+ this._flushing = null
58
+ this._activeBatch = null
59
+ this._bitfield = null
42
60
  this._verifies = null
43
61
  this._verifiesFlushed = null
44
- this._mutex = new Mutex()
45
- this._legacy = legacy
62
+ this._legacy = !!opts.legacy
63
+
64
+ this.opening = this._open(opts)
65
+ this.opening.catch(noop)
66
+ }
67
+
68
+ ready () {
69
+ return this.opening
70
+ }
71
+
72
+ addMonitor (s) {
73
+ if (s._monitorIndex >= 0) return
74
+ s._monitorIndex = this.monitors.push(s) - 1
75
+ }
76
+
77
+ removeMonitor (s) {
78
+ if (s._monitorIndex < 0) return
79
+ const head = this.monitors.pop()
80
+ if (head !== s) this.monitors[(head._monitorIndex = s._monitorIndex)] = head
81
+ s._monitorIndex = -1
82
+ }
83
+
84
+ emitManifest () {
85
+ for (let i = this.monitors.length - 1; i >= 0; i--) {
86
+ this.monitors[i].emit('manifest')
87
+ }
46
88
  }
47
89
 
48
- static async open (storage, opts = {}) {
49
- const oplogFile = storage('oplog')
50
- const treeFile = storage('tree')
51
- const bitfieldFile = storage('bitfield')
52
- const dataFile = storage('data')
53
- const headerFile = storage('header')
90
+ createUserDataStream (opts, session = this.state) {
91
+ return session.storage.createUserDataStream(opts)
92
+ }
93
+
94
+ allSessions () {
95
+ const sessions = []
96
+ for (const state of this.sessionStates) {
97
+ if (state.sessions.length) sessions.push(...state.sessions)
98
+ }
99
+ return sessions
100
+ }
101
+
102
+ hasSession () {
103
+ return this.activeSessions !== 0
104
+ }
54
105
 
106
+ checkIfIdle () {
107
+ if (this.destroyed === true || this.hasSession() === true) return
108
+ if (this.replicator.idle() === false) return
109
+ if (this.state === null || this.state.mutex.idle() === false) return
110
+ this.onidle()
111
+ }
112
+
113
+ async lockExclusive () {
114
+ if (this.exclusive === null) this.exclusive = new Mutex()
115
+ await this.exclusive.lock()
116
+ }
117
+
118
+ unlockExclusive () {
119
+ if (this.exclusive !== null) this.exclusive.unlock()
120
+ }
121
+
122
+ async _open (opts) {
55
123
  try {
56
- return await this.resume(oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts)
124
+ await this._tryOpen(opts)
57
125
  } catch (err) {
58
- await closeAll(oplogFile, treeFile, bitfieldFile, dataFile, headerFile)
126
+ this.onidle()
59
127
  throw err
60
128
  }
129
+
130
+ this.opened = true
61
131
  }
62
132
 
63
- static async resume (oplogFile, treeFile, bitfieldFile, dataFile, headerFile, opts) {
133
+ async _tryOpen (opts) {
134
+ let storage = await this.db.resume(this.discoveryKey)
135
+
64
136
  let overwrite = opts.overwrite === true
65
137
 
66
138
  const force = opts.force === true
67
139
  const createIfMissing = opts.createIfMissing !== false
68
- const crypto = opts.crypto || hypercoreCrypto
69
140
  // kill this flag soon
70
141
  const legacy = !!opts.legacy
71
142
 
72
- const oplog = new Oplog(oplogFile, {
73
- headerEncoding: m.oplog.header,
74
- entryEncoding: m.oplog.entry,
75
- readonly: opts.readonly
76
- })
77
-
78
143
  // default to true for now if no manifest is provided
79
144
  let compat = opts.compat === true || (opts.compat !== false && !opts.manifest)
80
145
 
81
- let { header, entries } = await oplog.open()
146
+ let header = storage ? parseHeader(await getCoreInfo(storage)) : null
82
147
 
83
148
  if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
84
149
  overwrite = true
85
150
  }
86
151
 
87
- const bigHeader = new BigHeader(headerFile)
88
-
89
152
  if (!header || overwrite) {
90
153
  if (!createIfMissing) {
91
154
  throw STORAGE_EMPTY('No Hypercore is stored here')
@@ -98,11 +161,11 @@ module.exports = class Core {
98
161
  }
99
162
 
100
163
  const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())
164
+
101
165
  const defaultManifest = !opts.manifest && (!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
102
166
  const manifest = defaultManifest ? Verifier.defaultSignerManifest(opts.key || keyPair.publicKey) : Verifier.createManifest(opts.manifest)
103
167
 
104
168
  header = {
105
- external: null,
106
169
  key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
107
170
  manifest,
108
171
  keyPair: keyPair ? { publicKey: keyPair.publicKey, secretKey: keyPair.secretKey || null } : null,
@@ -119,15 +182,29 @@ module.exports = class Core {
119
182
  }
120
183
  }
121
184
 
122
- await flushHeader(oplog, bigHeader, header)
123
- } else if (header.external) {
124
- header = await bigHeader.load(header.external)
185
+ const discoveryKey = opts.discoveryKey || crypto.discoveryKey(header.key)
186
+
187
+ storage = await this.db.create({
188
+ key: header.key,
189
+ manifest,
190
+ keyPair,
191
+ discoveryKey,
192
+ userData: opts.userData || []
193
+ })
125
194
  }
126
195
 
127
196
  // unslab the long lived buffers to avoid keeping the slab alive
128
197
  header.key = unslab(header.key)
129
- header.tree.rootHash = unslab(header.tree.rootHash)
130
- header.tree.signature = unslab(header.tree.signature)
198
+
199
+ if (header.tree) {
200
+ header.tree.rootHash = unslab(header.tree.rootHash)
201
+ header.tree.signature = unslab(header.tree.signature)
202
+ }
203
+
204
+ if (header.keyPair) {
205
+ header.keyPair.publicKey = unslab(header.keyPair.publicKey)
206
+ header.keyPair.secretKey = unslab(header.keyPair.secretKey)
207
+ }
131
208
 
132
209
  if (header.keyPair) {
133
210
  header.keyPair.publicKey = unslab(header.keyPair.publicKey)
@@ -156,551 +233,160 @@ module.exports = class Core {
156
233
 
157
234
  const prologue = header.manifest ? header.manifest.prologue : null
158
235
 
159
- const tree = await MerkleTree.open(treeFile, { crypto, prologue, ...header.tree })
160
- const bitfield = await Bitfield.open(bitfieldFile)
161
- const blocks = new BlockStore(dataFile, tree)
236
+ const tree = await MerkleTree.open(storage)
237
+ const bitfield = await Bitfield.open(storage, header.tree.length)
238
+ const blocks = new BlockStore(storage)
239
+
240
+ const treeInfo = {
241
+ fork: header.tree.fork,
242
+ signature: header.tree.signature,
243
+ roots: header.tree.length ? await tree.getRoots(header.tree.length) : [],
244
+ prologue
245
+ }
162
246
 
163
247
  if (overwrite) {
164
- await tree.clear()
165
- await blocks.clear()
166
- await bitfield.clear()
167
- entries = []
248
+ const tx = storage.write()
249
+ tree.clear(tx)
250
+ blocks.clear(tx)
251
+ bitfield.clear(tx)
252
+ await tx.flush()
168
253
  }
169
254
 
170
- // compat from earlier version that do not store contig length
171
- if (header.hints.contiguousLength === 0) {
172
- while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
255
+ for await (const { key, value } of storage.createUserDataStream()) {
256
+ header.userData.push({ key, value: unslab(value) })
173
257
  }
174
258
 
259
+ // compat from earlier version that do not store contig length
260
+ // if (header.hints.contiguousLength === 0) {
261
+ // while (bitfield.get(header.hints.contiguousLength)) header.hints.contiguousLength++
262
+ // }
263
+
175
264
  // to unslab
176
265
  if (header.manifest) header.manifest = Verifier.createManifest(header.manifest)
177
266
 
178
267
  const verifier = header.manifest ? new Verifier(header.key, header.manifest, { crypto, legacy }) : null
179
268
 
180
- for (const e of entries) {
181
- if (e.userData) {
182
- updateUserData(header.userData, e.userData.key, e.userData.value)
183
- }
184
-
185
- if (e.treeNodes) {
186
- for (const node of e.treeNodes) {
187
- tree.addNode(node)
188
- }
189
- }
190
-
191
- if (e.bitfield) {
192
- bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
193
- updateContig(header, e.bitfield, bitfield)
194
- }
195
-
196
- if (e.treeUpgrade) {
197
- const batch = await tree.truncate(e.treeUpgrade.length, e.treeUpgrade.fork)
198
- batch.ancestors = e.treeUpgrade.ancestors
199
- batch.signature = unslab(e.treeUpgrade.signature)
200
- addReorgHint(header.hints.reorgs, tree, batch)
201
- batch.commit()
202
-
203
- header.tree.length = tree.length
204
- header.tree.fork = tree.fork
205
- header.tree.rootHash = tree.hash()
206
- header.tree.signature = tree.signature
207
- }
208
- }
269
+ this.storage = storage
270
+ this.header = header
271
+ this.compat = compat
272
+ this.tree = tree
273
+ this.blocks = blocks
274
+ this.bitfield = bitfield
275
+ this.verifier = verifier
276
+ this.state = new SessionState(this, null, storage, this.blocks, tree, treeInfo, null)
209
277
 
210
- for (const entry of header.userData) {
211
- entry.value = unslab(entry.value)
212
- }
278
+ if (this.key === null) this.key = this.header.key
279
+ if (this.discoveryKey === null) this.discoveryKey = crypto.discoveryKey(this.key)
280
+ if (this.id === null) this.id = z32.encode(this.key)
281
+ if (this.manifest === null) this.manifest = this.header.manifest
213
282
 
214
- return new this(header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, opts.sessions || [], legacy, opts.globalCache || null, opts.onupdate || noop, opts.onconflict || noop)
283
+ this._manifestFlushed = !!header.manifest
215
284
  }
216
285
 
217
286
  async audit () {
218
- await this._mutex.lock()
287
+ await this.state.mutex.lock()
219
288
 
220
289
  try {
221
- await this._flushOplog()
222
- const corrections = await audit(this)
223
- if (corrections.blocks || corrections.tree) await this._flushOplog()
290
+ const tx = this.state.createWriteBatch()
291
+
292
+ // TODO: refactor audit
293
+ const corrections = await audit(this, tx)
294
+ if (corrections.blocks || corrections.tree) {
295
+ await this.state.flushUpdate(tx)
296
+ }
297
+
224
298
  return corrections
225
299
  } finally {
226
- await this._mutex.unlock()
300
+ this.state._unlock()
227
301
  }
228
302
  }
229
303
 
230
304
  async setManifest (manifest) {
231
- await this._mutex.lock()
305
+ await this.state.mutex.lock()
232
306
 
233
307
  try {
234
308
  if (manifest && this.header.manifest === null) {
235
309
  if (!Verifier.isValidManifest(this.header.key, manifest)) throw INVALID_CHECKSUM('Manifest hash does not match')
236
- this._setManifest(Verifier.createManifest(manifest), null)
237
- await this._flushOplog()
310
+
311
+ const tx = this.state.createWriteBatch()
312
+ this._setManifest(tx, Verifier.createManifest(manifest), null)
313
+
314
+ if (await this.state.flush(tx)) this.replicator.onupgrade()
238
315
  }
239
316
  } finally {
240
- this._mutex.unlock()
317
+ this.state._unlock()
241
318
  }
242
319
  }
243
320
 
244
- _setManifest (manifest, keyPair) {
321
+ _setManifest (tx, manifest, keyPair) {
245
322
  if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) manifest = Verifier.defaultSignerManifest(this.header.key)
246
323
  if (!manifest) return
247
324
 
248
- const verifier = new Verifier(this.header.key, manifest, { crypto: this.crypto, legacy: this._legacy })
325
+ const verifier = new Verifier(this.header.key, manifest, { legacy: this._legacy })
326
+
327
+ if (verifier.prologue) this.state.prologue = Object.assign({}, verifier.prologue)
249
328
 
250
- if (verifier.prologue) this.tree.setPrologue(verifier.prologue)
329
+ this.manifest = this.header.manifest = manifest
330
+
331
+ tx.setAuth({
332
+ key: this.header.key,
333
+ discoveryKey: this.discoveryKey,
334
+ manifest,
335
+ keyPair: this.header.keyPair
336
+ // TODO: encryptionKey?
337
+ })
251
338
 
252
- this.header.manifest = manifest
253
339
  this.compat = verifier.compat
254
340
  this.verifier = verifier
255
341
  this._manifestFlushed = false
256
342
 
257
- this.onupdate(0b10000, null, null, null)
343
+ this.replicator.onupgrade()
344
+ this.emitManifest()
258
345
  }
259
346
 
260
- _shouldFlush () {
261
- // TODO: make something more fancy for auto flush mode (like fibonacci etc)
262
- if (--this._autoFlush <= 0 || this.oplog.byteLength >= this._maxOplogSize) {
263
- this._autoFlush = 4
264
- return true
265
- }
266
-
267
- if (!this._manifestFlushed && this.header.manifest) {
268
- this._manifestFlushed = true
269
- return true
270
- }
271
-
272
- return false
273
- }
274
-
275
- async copyPrologue (src, { additional = [] } = {}) {
276
- await this._mutex.lock()
347
+ async copyPrologue (src) {
348
+ await this.state.mutex.lock()
277
349
 
278
350
  try {
279
- await src._mutex.lock()
351
+ await src.mutex.lock()
280
352
  } catch (err) {
281
- this._mutex.unlock()
353
+ this.state.mutex.unlock()
282
354
  throw err
283
355
  }
284
356
 
285
357
  try {
286
- const prologue = this.header.manifest && this.header.manifest.prologue
287
- if (!prologue) throw INVALID_OPERATION('No prologue present')
288
-
289
- const srcLength = prologue.length - additional.length
290
- const srcBatch = srcLength !== src.tree.length ? await src.tree.truncate(srcLength) : src.tree.batch()
291
- const srcRoots = srcBatch.roots.slice(0)
292
- const srcByteLength = srcBatch.byteLength
293
-
294
- for (const blk of additional) srcBatch.append(blk)
295
-
296
- if (!b4a.equals(srcBatch.hash(), prologue.hash)) throw INVALID_OPERATION('Source tree is conflicting')
297
-
298
- // all hashes are correct, lets copy
299
-
300
- const entry = {
301
- userData: null,
302
- treeNodes: srcRoots,
303
- treeUpgrade: null,
304
- bitfield: null
305
- }
306
-
307
- if (additional.length) {
308
- await this.blocks.putBatch(srcLength, additional, srcByteLength)
309
- entry.treeNodes = entry.treeNodes.concat(srcBatch.nodes)
310
- entry.bitfield = {
311
- drop: false,
312
- start: srcLength,
313
- length: additional.length
314
- }
315
- }
316
-
317
- await this.oplog.append([entry], false)
318
- this.tree.addNodes(entry.treeNodes)
319
-
320
- if (this.header.tree.length < srcBatch.length) {
321
- this.header.tree.length = srcBatch.length
322
- this.header.tree.rootHash = srcBatch.hash()
323
-
324
- this.tree.length = srcBatch.length
325
- this.tree.byteLength = srcBatch.byteLength
326
- this.tree.roots = srcBatch.roots
327
- this.onupdate(0b0001, null, null, null)
328
- }
329
-
330
- if (entry.bitfield) {
331
- this._setBitfieldRange(entry.bitfield.start, entry.bitfield.length, true)
332
- this.onupdate(0, entry.bitfield, null, null)
333
- }
334
-
335
- await this._flushOplog()
336
-
337
- // no more additional blocks now and we should be consistant on disk
338
- // copy over all existing segments...
339
-
340
- let segmentEnd = 0
341
-
342
- while (segmentEnd < srcLength) {
343
- const segmentStart = maximumSegmentStart(segmentEnd, src.bitfield, this.bitfield)
344
- if (segmentStart >= srcLength || segmentStart < 0) break
345
-
346
- // max segment is 65536 to avoid running out of memory
347
- segmentEnd = Math.min(segmentStart + 65536, srcLength, minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield))
348
-
349
- const treeNodes = await src.tree.getNeededNodes(srcLength, segmentStart, segmentEnd)
350
- const bitfield = {
351
- drop: false,
352
- start: segmentStart,
353
- length: segmentEnd - segmentStart
354
- }
355
-
356
- const segment = []
357
- for (let i = segmentStart; i < segmentEnd; i++) {
358
- const blk = await src.blocks.get(i)
359
- segment.push(blk)
360
- }
361
-
362
- const offset = await src.tree.byteOffset(2 * segmentStart)
363
- await this.blocks.putBatch(segmentStart, segment, offset)
364
-
365
- const entry = {
366
- userData: null,
367
- treeNodes,
368
- treeUpgrade: null,
369
- bitfield
370
- }
371
-
372
- await this.oplog.append([entry], false)
373
- this.tree.addNodes(treeNodes)
374
- this._setBitfieldRange(bitfield.start, bitfield.length, true)
375
- this.onupdate(0, bitfield, null, null)
376
- await this._flushOplog()
377
- }
378
-
379
- this.header.userData = src.header.userData.slice(0)
380
- const contig = Math.min(src.header.hints.contiguousLength, srcBatch.length)
381
- if (this.header.hints.contiguousLength < contig) this.header.hints.contiguousLength = contig
382
-
383
- await this._flushOplog()
384
- } finally {
385
- src._mutex.unlock()
386
- this._mutex.unlock()
387
- }
388
- }
389
-
390
- async flush () {
391
- await this._mutex.lock()
392
- try {
393
- this._manifestFlushed = true
394
- this._autoFlush = 4
395
- await this._flushOplog()
358
+ await copyPrologue(src, this)
396
359
  } finally {
397
- this._mutex.unlock()
360
+ src.mutex.unlock()
361
+ this.state.mutex.unlock()
362
+ this.checkIfIdle()
398
363
  }
399
364
  }
400
365
 
401
- async _flushOplog () {
402
- // TODO: the apis using this, actually do not need to wait for the bitfields, tree etc to flush
403
- // as their mutations are already stored in the oplog. We could potentially just run this in the
404
- // background. Might be easier to impl that where it is called instead and keep this one simple.
405
- await this.bitfield.flush()
406
- await this.tree.flush()
407
-
408
- return flushHeader(this.oplog, this.bigHeader, this.header)
409
- }
410
-
411
- _appendBlocks (values) {
412
- return this.blocks.putBatch(this.tree.length, values, this.tree.byteLength)
366
+ get isFlushing () {
367
+ return !!(this._flushing || this.state._activeBatch)
413
368
  }
414
369
 
415
- async _writeBlock (batch, index, value) {
416
- const byteOffset = await batch.byteOffset(index * 2)
417
- await this.blocks.put(index, value, byteOffset)
370
+ flushed () {
371
+ return this.state.flushed()
418
372
  }
419
373
 
420
- async userData (key, value, flush) {
421
- // TODO: each oplog append can set user data, so we should have a way
422
- // to just hitch a ride on one of the other ongoing appends?
423
- await this._mutex.lock()
424
-
425
- try {
426
- let empty = true
427
-
428
- for (const u of this.header.userData) {
429
- if (u.key !== key) continue
430
- if (value && b4a.equals(u.value, value)) return
431
- empty = false
432
- break
433
- }
434
-
435
- if (empty && !value) return
436
-
437
- const entry = {
438
- userData: { key, value },
439
- treeNodes: null,
440
- treeUpgrade: null,
441
- bitfield: null
442
- }
443
-
444
- await this.oplog.append([entry], false)
445
-
446
- updateUserData(this.header.userData, key, value)
447
-
448
- if (this._shouldFlush() || flush) await this._flushOplog()
449
- } finally {
450
- this._mutex.unlock()
374
+ async _validateCommit (state, treeLength) {
375
+ if (this.state.length > state.length) {
376
+ throw new Error('Invalid commit: partial commit') // TODO: partial commit in the future if possible
451
377
  }
452
- }
453
-
454
- async truncate (length, fork, { signature, keyPair = this.header.keyPair } = {}) {
455
- if (this.tree.prologue && length < this.tree.prologue.length) {
456
- throw INVALID_OPERATION('Truncation breaks prologue')
457
- }
458
-
459
- this.truncating++
460
- await this._mutex.lock()
461
-
462
- // upsert compat manifest
463
- if (this.verifier === null && keyPair) this._setManifest(null, keyPair)
464
378
 
465
- try {
466
- const batch = await this.tree.truncate(length, fork)
467
- if (length > 0) batch.signature = signature || this.verifier.sign(batch, keyPair)
468
- await this._truncate(batch, null)
469
- } finally {
470
- this.truncating--
471
- this._mutex.unlock()
472
- }
473
- }
474
-
475
- async clearBatch () {
476
- await this._mutex.lock()
477
-
478
- try {
479
- const len = this.bitfield.findFirst(false, this.tree.length)
480
- if (len <= this.tree.length) return
481
-
482
- const batch = await this.tree.truncate(this.tree.length, this.tree.fork)
483
-
484
- batch.signature = this.tree.signature // same sig
485
-
486
- const entry = {
487
- userData: null,
488
- treeNodes: batch.nodes,
489
- treeUpgrade: batch,
490
- bitfield: {
491
- drop: true,
492
- start: batch.ancestors,
493
- length: len - batch.ancestors
379
+ if (this.state.length > treeLength) {
380
+ for (const root of this.state.roots) {
381
+ const batchRoot = await state.tree.get(root.index)
382
+ if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
383
+ throw new Error('Invalid commit: tree conflict')
494
384
  }
495
385
  }
496
-
497
- await this.oplog.append([entry], false)
498
-
499
- this._setBitfieldRange(batch.ancestors, len - batch.ancestors, false)
500
- batch.commit()
501
-
502
- // TODO: (see below todo)
503
- await this._flushOplog()
504
- } finally {
505
- this._mutex.unlock()
506
386
  }
507
- }
508
-
509
- async clear (start, end, cleared) {
510
- await this._mutex.lock()
511
-
512
- try {
513
- const entry = {
514
- userData: null,
515
- treeNodes: null,
516
- treeUpgrade: null,
517
- bitfield: {
518
- start,
519
- length: end - start,
520
- drop: true
521
- }
522
- }
523
-
524
- await this.oplog.append([entry], false)
525
-
526
- this._setBitfieldRange(start, end - start, false)
527
387
 
528
- if (start < this.header.hints.contiguousLength) {
529
- this.header.hints.contiguousLength = start
530
- }
531
-
532
- start = this.bitfield.lastSet(start) + 1
533
- end = this.bitfield.firstSet(end)
534
-
535
- if (end === -1) end = this.tree.length
536
- if (start >= end || start >= this.tree.length) return
537
-
538
- const offset = await this.tree.byteOffset(start * 2)
539
- const endOffset = await this.tree.byteOffset(end * 2)
540
- const length = endOffset - offset
541
-
542
- const before = cleared ? await Info.bytesUsed(this.blocks.storage) : null
543
-
544
- await this.blocks.clear(offset, length)
545
-
546
- const after = cleared ? await Info.bytesUsed(this.blocks.storage) : null
547
-
548
- if (cleared) cleared.blocks = Math.max(before - after, 0)
549
-
550
- this.onupdate(0, entry.bitfield, null, null)
551
-
552
- if (this._shouldFlush()) await this._flushOplog()
553
- } finally {
554
- this._mutex.unlock()
555
- }
556
- }
557
-
558
- async purge () {
559
- return new Promise((resolve, reject) => {
560
- let missing = 4
561
- let error = null
562
-
563
- this.oplog.storage.unlink(done)
564
- this.tree.storage.unlink(done)
565
- this.bitfield.storage.unlink(done)
566
- this.blocks.storage.unlink(done)
567
-
568
- function done (err) {
569
- if (err) error = err
570
- if (--missing) return
571
- if (error) reject(error)
572
- else resolve()
573
- }
574
- })
575
- }
576
-
577
- async insertBatch (batch, values, { signature, keyPair = this.header.keyPair, pending = false, treeLength = batch.treeLength } = {}) {
578
- await this._mutex.lock()
579
-
580
- try {
581
- // upsert compat manifest
582
- if (this.verifier === null && keyPair) this._setManifest(null, keyPair)
583
-
584
- if (this.tree.fork !== batch.fork) return null
585
-
586
- if (this.tree.length > batch.treeLength) {
587
- if (this.tree.length > batch.length) return null // TODO: partial commit in the future if possible
588
-
589
- for (const root of this.tree.roots) {
590
- const batchRoot = await batch.get(root.index)
591
- if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
592
- return null
593
- }
594
- }
595
- }
596
-
597
- const adding = batch.length - treeLength
598
-
599
- batch.upgraded = !pending && batch.length > this.tree.length
600
- batch.treeLength = this.tree.length
601
- batch.ancestors = this.tree.length
602
- if (batch.upgraded && !pending) batch.signature = signature || this.verifier.sign(batch, keyPair)
603
-
604
- let byteOffset = batch.byteLength
605
- for (let i = 0; i < adding; i++) byteOffset -= values[i].byteLength
606
-
607
- if (pending === true) batch.upgraded = false
608
-
609
- const entry = {
610
- userData: null,
611
- treeNodes: batch.nodes,
612
- treeUpgrade: batch.upgraded ? batch : null,
613
- bitfield: {
614
- drop: false,
615
- start: treeLength,
616
- length: adding
617
- }
618
- }
619
-
620
- await this.blocks.putBatch(treeLength, adding < values.length ? values.slice(0, adding) : values, byteOffset)
621
- await this.oplog.append([entry], false)
622
-
623
- this._setBitfieldRange(entry.bitfield.start, entry.bitfield.length, true)
624
- batch.commit()
625
-
626
- if (batch.upgraded) {
627
- this.header.tree.length = batch.length
628
- this.header.tree.rootHash = batch.hash()
629
- this.header.tree.signature = batch.signature
630
- }
631
-
632
- const status = (batch.upgraded ? 0b0001 : 0) | updateContig(this.header, entry.bitfield, this.bitfield)
633
- if (!pending) {
634
- // we already commit this, and now we signed it, so tell others
635
- if (entry.treeUpgrade && treeLength > batch.treeLength) {
636
- entry.bitfield.start = batch.treeLength
637
- entry.bitfield.length = treeLength - batch.treeLength
638
- }
639
-
640
- this.onupdate(status, entry.bitfield, null, null)
641
- }
642
-
643
- if (this._shouldFlush()) await this._flushOplog()
644
- } finally {
645
- this._mutex.unlock()
646
- }
647
-
648
- return { length: batch.length, byteLength: batch.byteLength }
649
- }
650
-
651
- async append (values, { signature, keyPair = this.header.keyPair, preappend } = {}) {
652
- await this._mutex.lock()
653
-
654
- try {
655
- // upsert compat manifest
656
- if (this.verifier === null && keyPair) this._setManifest(null, keyPair)
657
-
658
- if (preappend) await preappend(values)
659
-
660
- if (!values.length) {
661
- return { length: this.tree.length, byteLength: this.tree.byteLength }
662
- }
663
-
664
- const batch = this.tree.batch()
665
- for (const val of values) batch.append(val)
666
-
667
- // only multisig can have prologue so signature is always present
668
- if (this.tree.prologue && batch.length < this.tree.prologue.length) {
669
- throw INVALID_OPERATION('Append is not consistent with prologue')
670
- }
671
-
672
- batch.signature = signature || this.verifier.sign(batch, keyPair)
673
-
674
- const entry = {
675
- userData: null,
676
- treeNodes: batch.nodes,
677
- treeUpgrade: batch,
678
- bitfield: {
679
- drop: false,
680
- start: batch.ancestors,
681
- length: values.length
682
- }
683
- }
684
-
685
- const byteLength = await this._appendBlocks(values)
686
-
687
- await this.oplog.append([entry], false)
688
-
689
- this._setBitfieldRange(batch.ancestors, batch.length - batch.ancestors, true)
690
- batch.commit()
691
-
692
- this.header.tree.length = batch.length
693
- this.header.tree.rootHash = batch.hash()
694
- this.header.tree.signature = batch.signature
695
-
696
- const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
697
- this.onupdate(status, entry.bitfield, null, null)
698
-
699
- if (this._shouldFlush()) await this._flushOplog()
700
-
701
- return { length: batch.length, byteLength }
702
- } finally {
703
- this._mutex.unlock()
388
+ if (this.verifier === null) {
389
+ throw INVALID_OPERATION('Cannot commit without manifest') // easier to assert than upsert
704
390
  }
705
391
  }
706
392
 
@@ -715,61 +401,23 @@ module.exports = class Core {
715
401
 
716
402
  manifest = Verifier.createManifest(manifest) // To unslab
717
403
 
718
- const verifier = this.verifier || new Verifier(this.header.key, manifest, { crypto: this.crypto, legacy: this._legacy })
404
+ const verifier = this.verifier || new Verifier(this.header.key, manifest, { legacy: this._legacy })
719
405
 
720
406
  if (!verifier.verify(batch, batch.signature)) {
721
407
  throw INVALID_SIGNATURE('Proof contains an invalid signature')
722
408
  }
723
-
724
- if (!this.header.manifest) {
725
- this.header.manifest = manifest
726
- this.compat = verifier.compat
727
- this.verifier = verifier
728
- this.onupdate(0b10000, null, null, null)
729
- }
730
409
  }
731
410
 
732
- async _verifyExclusive ({ batch, bitfield, value, manifest, from }) {
411
+ async _verifyExclusive ({ batch, bitfield, value, manifest }) {
733
412
  this._verifyBatchUpgrade(batch, manifest)
413
+ if (!batch.commitable()) return false
734
414
 
735
- await this._mutex.lock()
736
-
737
- try {
738
- if (!batch.commitable()) return false
739
- this.updating = true
740
-
741
- const entry = {
742
- userData: null,
743
- treeNodes: batch.nodes,
744
- treeUpgrade: batch,
745
- bitfield
746
- }
747
-
748
- if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
749
- if (bitfield) await this._writeBlock(batch, bitfield.start, value)
750
-
751
- await this.oplog.append([entry], false)
752
-
753
- let status = 0b0001
754
-
755
- if (bitfield) {
756
- this._setBitfield(bitfield.start, true)
757
- status |= updateContig(this.header, bitfield, this.bitfield)
758
- }
759
-
760
- batch.commit()
761
-
762
- this.header.tree.fork = batch.fork
763
- this.header.tree.length = batch.length
764
- this.header.tree.rootHash = batch.hash()
765
- this.header.tree.signature = batch.signature
415
+ if (this.preupdate !== null) await this.preupdate(batch, this.header.key)
766
416
 
767
- this.onupdate(status, bitfield, value, from)
417
+ await this.state._verifyBlock(batch, bitfield, value, this.header.manifest ? null : manifest)
768
418
 
769
- if (this._shouldFlush()) await this._flushOplog()
770
- } finally {
771
- this.updating = false
772
- this._mutex.unlock()
419
+ if (!batch.upgraded && bitfield) {
420
+ this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
773
421
  }
774
422
 
775
423
  return true
@@ -778,73 +426,76 @@ module.exports = class Core {
778
426
  async _verifyShared () {
779
427
  if (!this._verifies.length) return false
780
428
 
781
- await this._mutex.lock()
429
+ await this.state.mutex.lock()
430
+
431
+ const tx = this.state.createWriteBatch()
782
432
 
783
433
  const verifies = this._verifies
784
434
  this._verifies = null
785
435
  this._verified = null
786
436
 
787
437
  try {
788
- const entries = []
789
-
790
438
  for (const { batch, bitfield, value } of verifies) {
791
439
  if (!batch.commitable()) continue
792
440
 
793
441
  if (bitfield) {
794
- await this._writeBlock(batch, bitfield.start, value)
442
+ tx.putBlock(bitfield.start, value)
795
443
  }
796
-
797
- entries.push({
798
- userData: null,
799
- treeNodes: batch.nodes,
800
- treeUpgrade: null,
801
- bitfield
802
- })
803
444
  }
804
445
 
805
- await this.oplog.append(entries, false)
446
+ const bits = new BitInterlude()
806
447
 
807
448
  for (let i = 0; i < verifies.length; i++) {
808
- const { batch, bitfield, value, manifest, from } = verifies[i]
449
+ const { batch, bitfield, manifest } = verifies[i]
809
450
 
810
451
  if (!batch.commitable()) {
811
452
  verifies[i] = null // signal that we cannot commit this one
812
453
  continue
813
454
  }
814
455
 
815
- let status = 0
816
-
817
456
  if (bitfield) {
818
- this._setBitfield(bitfield.start, true)
819
- status = updateContig(this.header, bitfield, this.bitfield)
457
+ bits.setRange(bitfield.start, bitfield.start + 1, true)
820
458
  }
821
459
 
822
460
  // if we got a manifest AND its strictly a non compat one, lets store it
823
461
  if (manifest && this.header.manifest === null) {
824
462
  if (!Verifier.isValidManifest(this.header.key, manifest)) throw INVALID_CHECKSUM('Manifest hash does not match')
825
- this._setManifest(manifest, null)
463
+ this._setManifest(tx, manifest, null)
826
464
  }
827
465
 
828
- batch.commit()
466
+ if (batch.commitable()) batch.commit(tx)
467
+ }
468
+
469
+ const ranges = bits.flush(tx, this.bitfield)
470
+
471
+ await this.state.flush(tx)
829
472
 
830
- this.onupdate(status, bitfield, value, from)
473
+ for (const { start, end, value } of ranges) {
474
+ this._setBitfieldRanges(start, end, value)
831
475
  }
832
476
 
833
- if (this._shouldFlush()) await this._flushOplog()
477
+ for (let i = 0; i < verifies.length; i++) {
478
+ const bitfield = verifies[i] && verifies[i].bitfield
479
+ if (bitfield) {
480
+ this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
481
+ this.updateContiguousLength(bitfield)
482
+ }
483
+ }
834
484
  } finally {
835
- this._mutex.unlock()
485
+ this.state._clearActiveBatch()
486
+ this.state.mutex.unlock()
836
487
  }
837
488
 
838
489
  return verifies[0] !== null
839
490
  }
840
491
 
841
492
  async checkConflict (proof, from) {
842
- if (this.tree.length < proof.upgrade.length || proof.fork !== this.tree.fork) {
493
+ if (this.state.length < proof.upgrade.length || proof.fork !== this.state.fork) {
843
494
  // out of date this proof - ignore for now
844
495
  return false
845
496
  }
846
497
 
847
- const batch = this.tree.verifyFullyRemote(proof)
498
+ const batch = this.tree.verifyFullyRemote(proof, this.state)
848
499
 
849
500
  try {
850
501
  this._verifyBatchUpgrade(batch, proof.manifest)
@@ -852,20 +503,32 @@ module.exports = class Core {
852
503
  return true
853
504
  }
854
505
 
855
- const remoteTreeHash = this.crypto.tree(proof.upgrade.nodes)
856
- const localTreeHash = this.crypto.tree(await this.tree.getRoots(proof.upgrade.length))
506
+ await this.state.mutex.lock()
507
+
508
+ try {
509
+ const tx = this.state.createWriteBatch()
510
+ if (this.header.manifest === null && proof.manifest) {
511
+ this._setManifest(tx, proof.manifest, null)
512
+ }
513
+
514
+ await this.state.flush(tx)
515
+ } finally {
516
+ this.state.mutex.unlock()
517
+ }
518
+
519
+ const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
520
+ const localTreeHash = crypto.tree(await this.tree.getRoots(proof.upgrade.length))
857
521
 
858
522
  if (b4a.equals(localTreeHash, remoteTreeHash)) return false
859
523
 
860
- await this.onconflict(proof)
524
+ await this._onconflict(proof)
861
525
  return true
862
526
  }
863
527
 
864
528
  async verifyReorg (proof) {
865
- const batch = await this.tree.reorg(proof)
866
-
529
+ const batch = new ReorgBatch(this.tree, this.state)
530
+ await this.tree.reorg(proof, batch)
867
531
  this._verifyBatchUpgrade(batch, proof.manifest)
868
-
869
532
  return batch
870
533
  }
871
534
 
@@ -873,9 +536,9 @@ module.exports = class Core {
873
536
  // We cannot apply "other forks" atm.
874
537
  // We should probably still try and they are likely super similar for non upgrades
875
538
  // but this is easy atm (and the above layer will just retry)
876
- if (proof.fork !== this.tree.fork) return false
539
+ if (proof.fork !== this.state.fork) return false
877
540
 
878
- const batch = await this.tree.verify(proof)
541
+ const batch = await this.tree.verify(proof, this.state)
879
542
  if (!batch.commitable()) return false
880
543
 
881
544
  const value = (proof.block && proof.block.value) || null
@@ -887,7 +550,9 @@ module.exports = class Core {
887
550
  from
888
551
  }
889
552
 
890
- if (batch.upgraded) return this._verifyExclusive(op)
553
+ if (batch.upgraded) {
554
+ return this._verifyExclusive(op)
555
+ }
891
556
 
892
557
  if (this._verifies !== null) {
893
558
  const verifies = this._verifies
@@ -898,102 +563,147 @@ module.exports = class Core {
898
563
 
899
564
  this._verifies = [op]
900
565
  this._verified = this._verifyShared()
566
+
901
567
  return this._verified
902
568
  }
903
569
 
904
- async reorg (batch, from) {
570
+ async reorg (batch) {
905
571
  if (!batch.commitable()) return false
906
572
 
907
573
  this.truncating++
908
- await this._mutex.lock()
909
574
 
910
575
  try {
911
- if (!batch.commitable()) return false
912
- await this._truncate(batch, from)
576
+ await this.state.reorg(batch)
913
577
  } finally {
914
578
  this.truncating--
915
- this._mutex.unlock()
916
579
  }
917
580
 
918
581
  return true
919
582
  }
920
583
 
921
- async _truncate (batch, from) {
922
- const entry = {
923
- userData: null,
924
- treeNodes: batch.nodes,
925
- treeUpgrade: batch,
926
- bitfield: {
927
- drop: true,
928
- start: batch.ancestors,
929
- length: this.tree.length - batch.ancestors
930
- }
584
+ openSkipBitfield () {
585
+ if (this.skipBitfield !== null) return this.skipBitfield
586
+ this.skipBitfield = new RemoteBitfield()
587
+ const buf = this.bitfield.toBuffer(this.state.length)
588
+ const bitfield = new Uint32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4)
589
+ this.skipBitfield.insert(0, bitfield)
590
+ return this.skipBitfield
591
+ }
592
+
593
+ _setBitfieldRanges (start, end, value) {
594
+ this.bitfield.setRange(start, end, value)
595
+ if (this.skipBitfield !== null) this.skipBitfield.setRange(start, end, value)
596
+ }
597
+
598
+ close () {
599
+ if (!this.closing) this.closing = this._close()
600
+ return this.closing
601
+ }
602
+
603
+ updateContiguousLength (bitfield) {
604
+ const contig = updateContigBatch(this.header.hints.contiguousLength, bitfield, this.bitfield)
605
+
606
+ if (contig.length !== -1 && contig.length !== this.header.hints.contiguousLength) {
607
+ this.header.hints.contiguousLength = contig.length
931
608
  }
609
+ }
932
610
 
933
- await this.oplog.append([entry], false)
611
+ onappend (tree, bitfield) {
612
+ this.header.tree = tree
934
613
 
935
- this._setBitfieldRange(batch.ancestors, this.tree.length - batch.ancestors, false)
936
- addReorgHint(this.header.hints.reorgs, this.tree, batch)
937
- batch.commit()
614
+ if (!bitfield) {
615
+ this.replicator.onupgrade()
616
+ return
617
+ }
938
618
 
939
- const contigStatus = updateContig(this.header, entry.bitfield, this.bitfield)
940
- const status = ((batch.length > batch.ancestors) ? 0b0011 : 0b0010) | contigStatus
619
+ this.replicator.cork()
941
620
 
942
- this.header.tree.fork = batch.fork
943
- this.header.tree.length = batch.length
944
- this.header.tree.rootHash = batch.hash()
945
- this.header.tree.signature = batch.signature
621
+ const { start, length, drop } = bitfield
946
622
 
947
- this.onupdate(status, entry.bitfield, null, from)
623
+ this._setBitfieldRanges(start, start + length, true)
624
+ this.updateContiguousLength({ start, length, drop: false })
948
625
 
949
- // TODO: there is a bug in the merkle tree atm where it cannot handle unflushed
950
- // truncates if we append or download anything after the truncation point later on
951
- // This is because tree.get checks the truncated flag. We should fix this so we can do
952
- // the later flush here as well
953
- // if (this._shouldFlush()) await this._flushOplog()
954
- await this._flushOplog()
626
+ this.replicator.onupgrade()
627
+ this.replicator.onhave(start, length, drop)
628
+ this.replicator.uncork()
955
629
  }
956
630
 
957
- openSkipBitfield () {
958
- if (this.skipBitfield !== null) return this.skipBitfield
959
- this.skipBitfield = new RemoteBitfield()
960
- const buf = this.bitfield.toBuffer(this.tree.length)
961
- const bitfield = new Uint32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4)
962
- this.skipBitfield.insert(0, bitfield)
963
- return this.skipBitfield
631
+ ontruncate (tree, { start, length }) {
632
+ if (tree) this.header.tree = tree
633
+
634
+ this.replicator.cork()
635
+
636
+ this.replicator.ontruncate(start, length)
637
+ this.replicator.onhave(start, length, true)
638
+ this.replicator.onupgrade()
639
+ this.replicator.uncork()
640
+
641
+ for (const sessionState of this.sessionStates) {
642
+ if (start < sessionState.snapshotCompatLength) sessionState.snapshotCompatLength = start
643
+ }
644
+
645
+ this._setBitfieldRanges(start, start + length, false)
646
+ this.updateContiguousLength({ start, length, drop: true })
964
647
  }
965
648
 
966
- _setBitfield (index, value) {
967
- this.bitfield.set(index, value)
968
- if (this.skipBitfield !== null) this.skipBitfield.set(index, value)
649
+ async _onconflict (proof, from) {
650
+ await this.replicator.onconflict(from)
651
+
652
+ for (let i = this.monitors.length - 1; i >= 0; i--) {
653
+ const s = this.monitors[i]
654
+ s.emit('conflict', proof.upgrade.length, proof.fork, proof)
655
+ }
656
+
657
+ const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
658
+ await this.closeAllSessions(err)
969
659
  }
970
660
 
971
- _setBitfieldRange (start, length, value) {
972
- this.bitfield.setRange(start, length, value)
973
- if (this.skipBitfield !== null) this.skipBitfield.setRange(start, length, value)
661
+ async closeAllSessions (err) {
662
+ // this.sessions modifies itself when a session closes
663
+ // This way we ensure we indeed iterate over all sessions
664
+ const sessions = this.allSessions()
665
+
666
+ const all = []
667
+ for (const s of sessions) all.push(s.close({ error: err, force: false })) // force false or else infinite recursion
668
+ await Promise.allSettled(all)
669
+ }
670
+
671
+ async destroy () {
672
+ if (this.destroyed === true) return
673
+ this.destroyed = true
674
+
675
+ if (this.hasSession() === true) throw new Error('Cannot destroy while sessions are open')
676
+
677
+ const weakSessions = this.allSessions()
678
+
679
+ if (this.replicator) this.replicator.destroy()
680
+ if (this.state) await this.state.close()
681
+
682
+ // close all pending weak sessions...
683
+ for (const s of weakSessions) s.close().catch(noop)
974
684
  }
975
685
 
976
- async close () {
686
+ async _close () {
687
+ if (this.opened === false) await this.opening
688
+ if (this.hasSession() === true) throw new Error('Cannot close while sessions are open')
689
+
690
+ if (this.replicator) await this.replicator.close()
691
+
692
+ await this.destroy()
693
+ if (this.autoClose) await this.storage.store.close()
694
+
977
695
  this.closed = true
978
- await this._mutex.destroy()
979
- await Promise.allSettled([
980
- this.oplog.close(),
981
- this.bitfield.close(),
982
- this.tree.close(),
983
- this.blocks.close(),
984
- this.bigHeader.close()
985
- ])
986
696
  }
987
697
  }
988
698
 
989
- function updateContig (header, upd, bitfield) {
699
+ function updateContigBatch (start, upd, bitfield) {
990
700
  const end = upd.start + upd.length
991
701
 
992
- let c = header.hints.contiguousLength
702
+ let c = start
993
703
 
994
704
  if (upd.drop) {
995
705
  // If we dropped a block in the current contig range, "downgrade" it
996
- if (c <= end && c > upd.start) {
706
+ if (c > upd.start) {
997
707
  c = upd.start
998
708
  }
999
709
  } else {
@@ -1003,104 +713,61 @@ function updateContig (header, upd, bitfield) {
1003
713
  }
1004
714
  }
1005
715
 
1006
- if (c === header.hints.contiguousLength) {
1007
- return 0b0000
716
+ if (c === start) {
717
+ return {
718
+ length: -1
719
+ }
1008
720
  }
1009
721
 
1010
- if (c > header.hints.contiguousLength) {
1011
- header.hints.contiguousLength = c
1012
- return 0b0100
722
+ if (c > start) {
723
+ return {
724
+ length: c
725
+ }
1013
726
  }
1014
727
 
1015
- header.hints.contiguousLength = c
1016
- return 0b1000
1017
- }
1018
-
1019
- function addReorgHint (list, tree, batch) {
1020
- if (tree.length === 0 || tree.fork === batch.fork) return
1021
-
1022
- while (list.length >= 4) list.shift() // 4 here is arbitrary, just want it to be small (hints only)
1023
- while (list.length > 0) {
1024
- if (list[list.length - 1].ancestors > batch.ancestors) list.pop()
1025
- else break
728
+ return {
729
+ length: c
1026
730
  }
1027
-
1028
- list.push({ from: tree.fork, to: batch.fork, ancestors: batch.ancestors })
1029
731
  }
1030
732
 
1031
- function updateUserData (list, key, value) {
1032
- value = unslab(value)
1033
-
1034
- for (let i = 0; i < list.length; i++) {
1035
- if (list[i].key === key) {
1036
- if (value) list[i].value = value
1037
- else list.splice(i, 1)
1038
- return
1039
- }
733
+ function getDefaultTree () {
734
+ return {
735
+ fork: 0,
736
+ length: 0,
737
+ rootHash: null,
738
+ signature: null
1040
739
  }
1041
- if (value) list.push({ key, value })
1042
740
  }
1043
741
 
1044
- function closeAll (...storages) {
1045
- let missing = 1
1046
- let error = null
1047
-
1048
- return new Promise((resolve, reject) => {
1049
- for (const s of storages) {
1050
- missing++
1051
- s.close(done)
1052
- }
1053
-
1054
- done(null)
742
+ function parseHeader (info) {
743
+ if (!info) return null
1055
744
 
1056
- function done (err) {
1057
- if (err) error = err
1058
- if (--missing) return
1059
- if (error) reject(error)
1060
- else resolve()
745
+ return {
746
+ key: info.key,
747
+ manifest: info.manifest,
748
+ external: null,
749
+ keyPair: info.keyPair,
750
+ userData: [],
751
+ tree: info.head || getDefaultTree(),
752
+ hints: {
753
+ reorgs: [],
754
+ contiguousLength: 0
1061
755
  }
1062
- })
1063
- }
1064
-
1065
- async function flushHeader (oplog, bigHeader, header) {
1066
- if (header.external) {
1067
- await bigHeader.flush(header)
1068
- }
1069
-
1070
- try {
1071
- await oplog.flush(header)
1072
- } catch (err) {
1073
- if (err.code !== 'OPLOG_HEADER_OVERFLOW') throw err
1074
- await bigHeader.flush(header)
1075
- await oplog.flush(header)
1076
756
  }
1077
757
  }
1078
758
 
1079
759
  function noop () {}
1080
760
 
1081
- function maximumSegmentStart (start, src, dst) {
1082
- while (true) {
1083
- const a = src.firstSet(start)
1084
- const b = dst.firstUnset(start)
761
+ async function getCoreInfo (storage) {
762
+ const r = storage.read()
1085
763
 
1086
- if (a === -1) return -1
1087
- if (b === -1) return a
764
+ const auth = r.getAuth()
765
+ const head = r.getHead()
1088
766
 
1089
- // if dst has the segment, restart
1090
- if (a < b) {
1091
- start = b
1092
- continue
1093
- }
767
+ r.tryFlush()
1094
768
 
1095
- return a
769
+ return {
770
+ ...await auth,
771
+ head: await head
1096
772
  }
1097
773
  }
1098
-
1099
- function minimumSegmentEnd (start, src, dst) {
1100
- const a = src.firstUnset(start)
1101
- const b = dst.firstSet(start)
1102
-
1103
- if (a === -1) return -1
1104
- if (b === -1) return a
1105
- return a < b ? a : b
1106
- }