autobee 2.0.1 → 2.1.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/index.js +124 -114
- package/lib/migrations.js +56 -16
- package/lib/system.js +0 -9
- package/lib/topo.js +5 -9
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -57,7 +57,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
57
57
|
// defer one tick to ensure consistent state, then return state prom
|
|
58
58
|
preload: async () => {
|
|
59
59
|
await 1
|
|
60
|
-
await this.
|
|
60
|
+
await this._bootGuard.ready()
|
|
61
61
|
},
|
|
62
62
|
getEncryptionProvider: this.getViewEncryption
|
|
63
63
|
})
|
|
@@ -123,9 +123,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
123
123
|
|
|
124
124
|
this.legacyViews = handlers.legacyViews || []
|
|
125
125
|
|
|
126
|
+
// the guards may be destroyed before anyone waits on them
|
|
126
127
|
this._bootGuard = new ReadyGuard()
|
|
127
|
-
this.
|
|
128
|
-
this.
|
|
128
|
+
this._bootGuard.ready().catch(safetyCatch)
|
|
129
|
+
this._bootOnlineGuard = new ReadyGuard()
|
|
130
|
+
this._bootOnlineGuard.ready().catch(safetyCatch)
|
|
129
131
|
|
|
130
132
|
this._now = handlers.now || Date.now // overridable for clock-drift tests
|
|
131
133
|
this._preapply = handlers.preapply || null
|
|
@@ -135,7 +137,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
135
137
|
this._needsUpdate = false
|
|
136
138
|
this._approvalCheck = true
|
|
137
139
|
this._approvedPending = new Map()
|
|
138
|
-
this._approvalRequests = []
|
|
139
140
|
this._prefetchingApprovals = false
|
|
140
141
|
this._updateLocalCore = null
|
|
141
142
|
this._host = new ApplyCalls(this)
|
|
@@ -195,26 +196,46 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
// network free: only a migration needs peers, so ready() awaits the full
|
|
200
|
+
// state boot unless one is pending and defers it to the background if so -
|
|
201
|
+
// anything needing the booted system must wait on the boot guard
|
|
198
202
|
async _open() {
|
|
199
203
|
this._prebooting = this._preBoot()
|
|
200
204
|
|
|
201
|
-
|
|
202
|
-
this._bootingAll = this._bootAll()
|
|
205
|
+
let result = null
|
|
203
206
|
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
try {
|
|
208
|
+
result = await this._prebooting
|
|
209
|
+
|
|
210
|
+
this.key = result.key
|
|
211
|
+
this.bootstrap = result.bootstrap
|
|
212
|
+
this.discoveryKey = result.bootstrap.core.discoveryKey
|
|
213
|
+
this.id = result.bootstrap.core.id
|
|
214
|
+
this.encryptionKey = result.encryptionKey
|
|
215
|
+
this.previousDrain = result.previousDrain
|
|
216
|
+
this.local = result.local
|
|
217
|
+
|
|
218
|
+
if (this.encrypted && this.encryptionKey === null) {
|
|
219
|
+
throw new Error('Encryption key is expected')
|
|
220
|
+
}
|
|
206
221
|
|
|
207
|
-
|
|
222
|
+
this.local.setEncryption(this._getEncryptionProvider())
|
|
223
|
+
this.writers = new ActiveWriters(this)
|
|
224
|
+
} catch (err) {
|
|
225
|
+
// unblock the guard awaiters (bee preloads, bumps, wakeups, flushes)
|
|
226
|
+
this._bootGuard.destroy(err)
|
|
227
|
+
this._bootOnlineGuard.destroy(err)
|
|
228
|
+
throw err
|
|
229
|
+
}
|
|
208
230
|
|
|
209
|
-
|
|
210
|
-
await this._workingBee.ready()
|
|
211
|
-
await this._bootingState
|
|
231
|
+
const booting = this._boot()
|
|
212
232
|
|
|
213
|
-
|
|
214
|
-
this.
|
|
215
|
-
this._localFlushes = this.system.flushes
|
|
233
|
+
// waits on the boot guard internally and destroys its own guard on failure
|
|
234
|
+
this._bootOnline()
|
|
216
235
|
|
|
217
|
-
|
|
236
|
+
// if migrating, we might need network io, if not lets wait on it - less surprises
|
|
237
|
+
if (result.migration) booting.catch(this._onErrorBound)
|
|
238
|
+
else await booting
|
|
218
239
|
}
|
|
219
240
|
|
|
220
241
|
_requestWakeup() {
|
|
@@ -266,43 +287,40 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
266
287
|
|
|
267
288
|
async _close() {
|
|
268
289
|
this._interrupting = true
|
|
269
|
-
Hypercore.destroyRequests(this._approvalRequests, null)
|
|
270
|
-
if (this._bootWait !== null) this._bootWait.resolve()
|
|
271
|
-
if (this._notifyHandler) this._notifyHandler.destroy()
|
|
272
|
-
if (this._draining) {
|
|
273
|
-
// drain may be waiting on replicated blocks
|
|
274
|
-
this._clearRequests()
|
|
275
|
-
await this._draining
|
|
276
|
-
}
|
|
277
290
|
|
|
278
|
-
if (this._updating) await this._updating
|
|
279
|
-
|
|
280
|
-
// let in-flight writer adds finish
|
|
281
291
|
try {
|
|
282
|
-
await this.
|
|
283
|
-
} catch {
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
await ApplyView.close(this.view, this)
|
|
293
|
+
} catch (err) {
|
|
294
|
+
safetyCatch(err)
|
|
295
|
+
}
|
|
286
296
|
|
|
287
297
|
if (this.writers) await this.writers.close()
|
|
288
298
|
await this.local.close()
|
|
289
299
|
await this.system.close()
|
|
290
300
|
await this._wakeup.close()
|
|
291
|
-
if (this.bootstrap
|
|
292
|
-
await this._workingView.close()
|
|
293
|
-
await this.bee.close()
|
|
294
|
-
await this.store.close()
|
|
295
|
-
}
|
|
301
|
+
if (this.bootstrap) await this.bootstrap.close()
|
|
296
302
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
+
try {
|
|
304
|
+
await this._workingView.close()
|
|
305
|
+
await this.bee.close()
|
|
306
|
+
} catch (err) {
|
|
307
|
+
safetyCatch(err)
|
|
303
308
|
}
|
|
304
309
|
|
|
305
|
-
|
|
310
|
+
// don't rely on the store teardown to stop the notify watcher
|
|
311
|
+
if (this._notifyHandler) this._notifyHandler.destroy()
|
|
312
|
+
|
|
313
|
+
// rugpull the rest
|
|
314
|
+
await this.store.close()
|
|
315
|
+
|
|
316
|
+
if (this._bootWait !== null) this._bootWait.resolve()
|
|
317
|
+
if (this._updating) await this._updating
|
|
318
|
+
if (this._draining) await this._draining
|
|
319
|
+
|
|
320
|
+
// let in-flight writer adds finish
|
|
321
|
+
try {
|
|
322
|
+
await this._bootOnlineGuard.ready()
|
|
323
|
+
} catch {}
|
|
306
324
|
}
|
|
307
325
|
|
|
308
326
|
replicate(...args) {
|
|
@@ -312,7 +330,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
312
330
|
}
|
|
313
331
|
|
|
314
332
|
async flush() {
|
|
315
|
-
await this.
|
|
333
|
+
await this._bootOnlineGuard.ready()
|
|
316
334
|
}
|
|
317
335
|
|
|
318
336
|
hintWakeup(wakeup) {
|
|
@@ -338,6 +356,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
338
356
|
return new WriterEncryption(this)
|
|
339
357
|
}
|
|
340
358
|
|
|
359
|
+
getMostRecentHead() {
|
|
360
|
+
return topo.getMostRecentHead(this, this.system.bee.snapshot())
|
|
361
|
+
}
|
|
362
|
+
|
|
341
363
|
async _preBoot() {
|
|
342
364
|
if (this._handlers.wait) await this._handlers.wait()
|
|
343
365
|
|
|
@@ -361,15 +383,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
361
383
|
})
|
|
362
384
|
}
|
|
363
385
|
|
|
364
|
-
|
|
365
|
-
return topo.getMostRecentHead(this, this.system.bee.snapshot())
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async _bootState() {
|
|
386
|
+
async _boot() {
|
|
369
387
|
if (!this._bootGuard.enter()) return this._bootGuard.ready()
|
|
370
388
|
|
|
371
389
|
try {
|
|
372
|
-
await this.
|
|
390
|
+
await this._bootUnsafe()
|
|
373
391
|
} catch (err) {
|
|
374
392
|
this._bootGuard.destroy(err)
|
|
375
393
|
throw err
|
|
@@ -377,38 +395,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
377
395
|
|
|
378
396
|
this._bootGuard.exit()
|
|
379
397
|
|
|
380
|
-
|
|
381
|
-
}
|
|
398
|
+
this.bumpSoon()
|
|
382
399
|
|
|
383
|
-
|
|
384
|
-
if (this._bootGuard.opened) return true
|
|
385
|
-
try {
|
|
386
|
-
await this._bootGuard.ready()
|
|
387
|
-
return true
|
|
388
|
-
} catch {
|
|
389
|
-
return false
|
|
390
|
-
}
|
|
400
|
+
return this._bootGuard.ready()
|
|
391
401
|
}
|
|
392
402
|
|
|
393
|
-
async
|
|
403
|
+
async _bootUnsafe() {
|
|
394
404
|
const result = await this._prebooting
|
|
395
405
|
|
|
396
|
-
this.key = result.key
|
|
397
|
-
this.bootstrap = result.bootstrap
|
|
398
|
-
this.discoveryKey = result.bootstrap.core.discoveryKey
|
|
399
|
-
this.id = result.bootstrap.core.id
|
|
400
|
-
this.encryptionKey = result.encryptionKey
|
|
401
|
-
this.previousDrain = result.previousDrain
|
|
402
|
-
this.local = result.local
|
|
403
|
-
|
|
404
|
-
if (this.encrypted && this.encryptionKey === null) {
|
|
405
|
-
throw new Error('Encryption key is expected')
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
this.local.setEncryption(this._getEncryptionProvider())
|
|
409
|
-
|
|
410
|
-
this.writers = new ActiveWriters(this)
|
|
411
|
-
|
|
412
406
|
if (this._handlers.wakeupCapability) {
|
|
413
407
|
this.wakeupCapability = await this._handlers.wakeupCapability
|
|
414
408
|
} else {
|
|
@@ -476,33 +470,53 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
476
470
|
this.bee.move(view)
|
|
477
471
|
|
|
478
472
|
await this.writers.updateLocalState()
|
|
473
|
+
|
|
474
|
+
// roots are moved, so these resolve without re-entering the boot guard
|
|
475
|
+
await this.bee.ready()
|
|
476
|
+
await this._workingBee.ready()
|
|
477
|
+
|
|
478
|
+
// baseline before the guard opens so an early drain can't flush against stale offsets
|
|
479
|
+
this._localSystemStart = this.system.bee.context.local.length
|
|
480
|
+
this._localViewStart = this._workingBee.context.local.length
|
|
481
|
+
this._localFlushes = this.system.flushes
|
|
479
482
|
}
|
|
480
483
|
|
|
481
|
-
async
|
|
482
|
-
if (!
|
|
484
|
+
async _bootOnline() {
|
|
485
|
+
if (!this._bootOnlineGuard.enter()) return
|
|
483
486
|
|
|
484
|
-
|
|
487
|
+
try {
|
|
488
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
485
489
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
490
|
+
for (const head of this.system.heads) {
|
|
491
|
+
await this.writers.add(head.key)
|
|
492
|
+
}
|
|
489
493
|
|
|
490
|
-
|
|
491
|
-
|
|
494
|
+
if (!this.system.heads.length) {
|
|
495
|
+
await this.writers.add(this.bootstrap.key)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
await this._bump(true)
|
|
499
|
+
} catch (err) {
|
|
500
|
+
this._bootOnlineGuard.destroy(err)
|
|
501
|
+
return
|
|
492
502
|
}
|
|
493
503
|
|
|
494
|
-
|
|
504
|
+
this._bootOnlineGuard.exit()
|
|
495
505
|
}
|
|
496
506
|
|
|
497
507
|
bumpSoon() {
|
|
498
|
-
this._bump().catch(safetyCatch)
|
|
508
|
+
this._bump(false).catch(safetyCatch)
|
|
499
509
|
}
|
|
500
510
|
|
|
501
|
-
async _bump() {
|
|
502
|
-
if (!
|
|
511
|
+
async _bump(force) {
|
|
512
|
+
if (!force && !this._bootGuard.opened) await this._bootGuard.ready()
|
|
503
513
|
|
|
514
|
+
// resolve before the bootAll gate: a parked boot-from retries on any bump,
|
|
515
|
+
// and its drain is the one _bootOnline itself is waiting on
|
|
504
516
|
if (this._bootWait !== null) this._bootWait.resolve()
|
|
505
517
|
|
|
518
|
+
if (!force && !this._bootOnlineGuard.opened) await this._bootOnlineGuard.ready()
|
|
519
|
+
|
|
506
520
|
this.bumping++
|
|
507
521
|
|
|
508
522
|
if (!this._draining) {
|
|
@@ -513,11 +527,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
513
527
|
}
|
|
514
528
|
|
|
515
529
|
update() {
|
|
516
|
-
return this._bump()
|
|
530
|
+
return this._bump(false)
|
|
517
531
|
}
|
|
518
532
|
|
|
519
533
|
async updated() {
|
|
520
|
-
if (this.opened
|
|
534
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
521
535
|
if (this._draining) return this._draining
|
|
522
536
|
return Promise.resolve()
|
|
523
537
|
}
|
|
@@ -816,7 +830,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
816
830
|
}
|
|
817
831
|
|
|
818
832
|
async setLocal(key, { keyPair } = {}) {
|
|
819
|
-
if (!this.opened) await this.ready()
|
|
833
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
820
834
|
if (this.closing) throw new Error('Autobee closed')
|
|
821
835
|
|
|
822
836
|
const manifest = keyPair
|
|
@@ -945,15 +959,13 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
945
959
|
if (this._prefetchingApprovals) return
|
|
946
960
|
this._prefetchingApprovals = true
|
|
947
961
|
|
|
948
|
-
const activeRequests = this._approvalRequests
|
|
949
|
-
|
|
950
962
|
try {
|
|
951
963
|
const prefetch = []
|
|
952
|
-
const standing = await this._standing(
|
|
964
|
+
const standing = await this._standing()
|
|
953
965
|
if (standing <= 0 || !this._pendingWork(standing)) return
|
|
954
|
-
for await (const p of this._servableRequests(standing
|
|
955
|
-
prefetch.push(this.system.get(p.key
|
|
956
|
-
prefetch.push(this.system.grantHint(p.key
|
|
966
|
+
for await (const p of this._servableRequests(standing)) {
|
|
967
|
+
prefetch.push(this.system.get(p.key).catch(safetyCatch))
|
|
968
|
+
prefetch.push(this.system.grantHint(p.key).catch(safetyCatch))
|
|
957
969
|
}
|
|
958
970
|
await Promise.allSettled(prefetch)
|
|
959
971
|
} finally {
|
|
@@ -961,8 +973,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
961
973
|
}
|
|
962
974
|
}
|
|
963
975
|
|
|
964
|
-
async _standing(
|
|
965
|
-
const rec = await this.system.get(this.local.key
|
|
976
|
+
async _standing() {
|
|
977
|
+
const rec = await this.system.get(this.local.key)
|
|
966
978
|
return currentWeight(rec)
|
|
967
979
|
}
|
|
968
980
|
|
|
@@ -980,8 +992,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
980
992
|
|
|
981
993
|
// the requests we could serve and by how much - shared by the prefetch and
|
|
982
994
|
// the collector so the two cannot drift on what counts as ours
|
|
983
|
-
async *_servableRequests(standing
|
|
984
|
-
for await (const p of this.system.listPendingPromotions(
|
|
995
|
+
async *_servableRequests(standing) {
|
|
996
|
+
for await (const p of this.system.listPendingPromotions()) {
|
|
985
997
|
const amount = Math.min(standing, p.weight)
|
|
986
998
|
if (amount <= 0) continue
|
|
987
999
|
yield { key: p.key, amount }
|
|
@@ -992,8 +1004,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
992
1004
|
if (!this.system.promotions.changed && !this._approvalCheck) return null
|
|
993
1005
|
if (!this.writers.writable) return null
|
|
994
1006
|
|
|
995
|
-
const
|
|
996
|
-
const standing = await this._standing(activeRequests)
|
|
1007
|
+
const standing = await this._standing()
|
|
997
1008
|
if (standing <= 0) return null
|
|
998
1009
|
|
|
999
1010
|
this.system.promotions.changed = false
|
|
@@ -1004,7 +1015,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1004
1015
|
const approvals = []
|
|
1005
1016
|
const approving = []
|
|
1006
1017
|
|
|
1007
|
-
for await (const p of this._servableRequests(standing
|
|
1018
|
+
for await (const p of this._servableRequests(standing)) {
|
|
1008
1019
|
approving.push(fetchApproval.call(this, p.key, p.amount, approvals))
|
|
1009
1020
|
}
|
|
1010
1021
|
|
|
@@ -1028,10 +1039,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1028
1039
|
async function fetchApproval(key, weight, approvals) {
|
|
1029
1040
|
const hex = b4a.toString(key, 'hex')
|
|
1030
1041
|
if ((this._approvedPending.get(hex) || 0) >= weight) return
|
|
1031
|
-
const [target, hint] = await Promise.all([
|
|
1032
|
-
this.system.get(key, { activeRequests }),
|
|
1033
|
-
this.system.grantHint(key, { activeRequests })
|
|
1034
|
-
])
|
|
1042
|
+
const [target, hint] = await Promise.all([this.system.get(key), this.system.grantHint(key)])
|
|
1035
1043
|
if (!target || target.isRemoved) return
|
|
1036
1044
|
if (hint && hint.weight >= weight) return
|
|
1037
1045
|
approvals.push({ key, weight })
|
|
@@ -1229,10 +1237,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1229
1237
|
return encoding.encodeValue(value, opts)
|
|
1230
1238
|
}
|
|
1231
1239
|
|
|
1240
|
+
// gates on the state boot only, so hints can still feed a parked boot-from -
|
|
1241
|
+
// the trailing bump waits for bootAll like any other drain
|
|
1232
1242
|
async wakeup({ key, length }) {
|
|
1233
|
-
if (!
|
|
1243
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
1234
1244
|
await this.writers.wakeup(key, length)
|
|
1235
|
-
await this._bump()
|
|
1245
|
+
await this._bump(false)
|
|
1236
1246
|
}
|
|
1237
1247
|
|
|
1238
1248
|
async append(values, { optimistic = false } = {}) {
|
|
@@ -1242,7 +1252,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1242
1252
|
|
|
1243
1253
|
if (!Array.isArray(values)) values = [values]
|
|
1244
1254
|
|
|
1245
|
-
if (!this.opened) await this.ready()
|
|
1255
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
1246
1256
|
|
|
1247
1257
|
if (!optimistic && this.writers.localWriter.isRemoved) {
|
|
1248
1258
|
throw new Error('Not writable')
|
|
@@ -1283,7 +1293,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1283
1293
|
batch.push(node)
|
|
1284
1294
|
}
|
|
1285
1295
|
|
|
1286
|
-
return this._bump()
|
|
1296
|
+
return this._bump(false)
|
|
1287
1297
|
}
|
|
1288
1298
|
|
|
1289
1299
|
async _flushLocal() {
|
|
@@ -1314,7 +1324,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1314
1324
|
}
|
|
1315
1325
|
|
|
1316
1326
|
async moveTo(head) {
|
|
1317
|
-
if (!this.opened) await this.ready()
|
|
1327
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
1318
1328
|
if (this.closing) throw new Error('Autobee closed')
|
|
1319
1329
|
|
|
1320
1330
|
const ff = await FastForward.fromHead(this, head, null, { force: true })
|
package/lib/migrations.js
CHANGED
|
@@ -15,6 +15,7 @@ const SystemInfoV2 = getEncoding('@autobase-compat/info-v2')
|
|
|
15
15
|
|
|
16
16
|
const EMPTY = b4a.alloc(0)
|
|
17
17
|
const INDEX_VERSION = 1
|
|
18
|
+
const INFO = b4a.from([0x00, 0x00, 0x69, 0x6e, 0x66, 0x6f])
|
|
18
19
|
const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)
|
|
19
20
|
|
|
20
21
|
// indexer rotations chain legacy systems - how many generations we chase
|
|
@@ -41,15 +42,13 @@ async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
|
|
|
41
42
|
const core = store.get({ key: key, encryption: null })
|
|
42
43
|
await core.ready()
|
|
43
44
|
|
|
44
|
-
const length = await findLocalSystemLength(core, systemLength)
|
|
45
|
-
assert(length > 0, 'Expected system block to exist locally')
|
|
46
|
-
|
|
47
|
-
const system = { key, length }
|
|
48
|
-
|
|
49
45
|
// setup encryption
|
|
50
46
|
const encryptionKey = await local.getUserData('autobase/encryption')
|
|
51
47
|
await AutobeeEncryption.setSystemEncryption(bootstrap.key, encryptionKey, core)
|
|
52
48
|
|
|
49
|
+
const length = await findLocalSystemLength(core, systemLength)
|
|
50
|
+
const system = { key, length }
|
|
51
|
+
|
|
53
52
|
const session = core.session({ name: 'batch' })
|
|
54
53
|
await session.ready()
|
|
55
54
|
|
|
@@ -284,8 +283,21 @@ function deriveNamespace(name, bootstrap, entropy, encryptionKey) {
|
|
|
284
283
|
// scan back for the newest block the signed core actually has locally
|
|
285
284
|
async function findLocalSystemLength(core, systemLength) {
|
|
286
285
|
let length = Math.min(core.length, systemLength)
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
|
|
287
|
+
while (length > 0) {
|
|
288
|
+
const seq = length - 1
|
|
289
|
+
const node = await core.get(seq, { wait: false })
|
|
290
|
+
|
|
291
|
+
if (node) {
|
|
292
|
+
const blk = decodeBlock(node)
|
|
293
|
+
const entry = blk.keys[0]
|
|
294
|
+
if (b4a.equals(entry.key, INFO)) return length
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
length--
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
assert(false, 'Expected system block to exist locally')
|
|
289
301
|
}
|
|
290
302
|
|
|
291
303
|
function coreLength(core, timeout) {
|
|
@@ -392,28 +404,56 @@ async function getPersistedView(store, view) {
|
|
|
392
404
|
return { key: view.key, length }
|
|
393
405
|
}
|
|
394
406
|
|
|
407
|
+
function getOplog(info, batch) {
|
|
408
|
+
if (batch.length === 1) return batch[0].keys[0]
|
|
409
|
+
|
|
410
|
+
const { heads } = encoding.decodeSystemInfo(info.value)
|
|
411
|
+
|
|
412
|
+
for (const b of batch) {
|
|
413
|
+
const entry = b.keys[0]
|
|
414
|
+
const w = encoding.decodeSystemWriter(entry.key, entry.value)
|
|
415
|
+
|
|
416
|
+
for (const { key } of heads) {
|
|
417
|
+
if (b4a.equals(w.key, key)) return b.keys[0]
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
throw new Error('Could not infer oplog node')
|
|
422
|
+
}
|
|
423
|
+
|
|
395
424
|
async function getCatchupHeads(session, from) {
|
|
396
425
|
const seen = new Map()
|
|
397
426
|
const nodes = []
|
|
398
427
|
|
|
428
|
+
let batch = []
|
|
429
|
+
|
|
399
430
|
// block 0 is the bee header, never a node
|
|
400
431
|
for (let i = Math.max(from, 1); i < session.length; i++) {
|
|
401
432
|
const node = await session.get(i, { wait: false })
|
|
402
433
|
if (!node) throw new Error('Expect nodes to exist locally')
|
|
403
434
|
|
|
404
|
-
const
|
|
405
|
-
|
|
435
|
+
const blk = decodeBlock(node)
|
|
436
|
+
const entry = blk.keys[0]
|
|
437
|
+
|
|
438
|
+
if (b4a.equals(entry.key, INFO)) {
|
|
439
|
+
const oplog = getOplog(entry, batch)
|
|
440
|
+
const { key, length } = encoding.decodeSystemWriter(oplog.key, oplog.value)
|
|
406
441
|
|
|
407
|
-
|
|
408
|
-
if (!length) continue
|
|
442
|
+
batch = []
|
|
409
443
|
|
|
410
|
-
|
|
411
|
-
const current = seen.get(id) || 0
|
|
444
|
+
if (!length) continue
|
|
412
445
|
|
|
413
|
-
|
|
446
|
+
const id = b4a.toString(key, 'hex')
|
|
447
|
+
const current = seen.get(id) || 0
|
|
448
|
+
|
|
449
|
+
if (current >= length) continue
|
|
450
|
+
|
|
451
|
+
seen.set(id, length)
|
|
452
|
+
nodes.push({ key, length })
|
|
453
|
+
continue
|
|
454
|
+
}
|
|
414
455
|
|
|
415
|
-
|
|
416
|
-
nodes.push({ key, length })
|
|
456
|
+
if (entry.key[0] === 0x01) batch.push(blk)
|
|
417
457
|
}
|
|
418
458
|
|
|
419
459
|
return nodes
|
package/lib/system.js
CHANGED
|
@@ -8,8 +8,6 @@ const topo = require('./topo.js')
|
|
|
8
8
|
|
|
9
9
|
const INFO_KEY = b4a.from([0])
|
|
10
10
|
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
|
|
11
|
-
const INDEXER_GTE = b4a.from([2])
|
|
12
|
-
const INDEXER_LT = b4a.from([3])
|
|
13
11
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
14
12
|
const EMPTY = b4a.from([])
|
|
15
13
|
|
|
@@ -124,13 +122,6 @@ module.exports = class Systembee {
|
|
|
124
122
|
return null
|
|
125
123
|
}
|
|
126
124
|
|
|
127
|
-
async *getIndexers() {
|
|
128
|
-
for await (const data of this.bee.createReadStream({ gte: INDEXER_GTE, lt: INDEXER_LT })) {
|
|
129
|
-
const key = data.key.subarray(1)
|
|
130
|
-
yield key
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
125
|
async reset({ timeout } = {}) {
|
|
135
126
|
const info = await this.getInfo({ timeout })
|
|
136
127
|
|
package/lib/topo.js
CHANGED
|
@@ -76,7 +76,7 @@ async function sort(auto, batch) {
|
|
|
76
76
|
for await (const data of ch) {
|
|
77
77
|
if (isLegacyNode(data)) break
|
|
78
78
|
|
|
79
|
-
const
|
|
79
|
+
const ref = getOplog(data)
|
|
80
80
|
const link = {
|
|
81
81
|
key: ref.oplog.key,
|
|
82
82
|
length: ref.oplog.length,
|
|
@@ -318,23 +318,18 @@ async function* oplogIterator(bee) {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
function getOplog(data) {
|
|
321
|
-
const result = { ref: null, info: null }
|
|
322
321
|
for (const { keys } of data.batch) {
|
|
322
|
+
if (!keys) continue
|
|
323
|
+
|
|
323
324
|
for (const k of keys) {
|
|
324
325
|
const prefix = k.key[0]
|
|
325
326
|
|
|
326
|
-
if (prefix === 0) {
|
|
327
|
-
result.info = encoding.decodeSystemInfo(k.value)
|
|
328
|
-
if (result.ref) return result
|
|
329
|
-
}
|
|
330
|
-
|
|
331
327
|
if (prefix === 1) {
|
|
332
328
|
// expect inlined for now
|
|
333
329
|
const value = encoding.decodeSystemWriter(k.key, k.value)
|
|
334
330
|
if (!value.isOplog) continue
|
|
335
331
|
|
|
336
|
-
|
|
337
|
-
if (result.info) return result
|
|
332
|
+
return { undo: data.tail || EMPTY_HEAD, oplog: value }
|
|
338
333
|
}
|
|
339
334
|
}
|
|
340
335
|
}
|
|
@@ -395,6 +390,7 @@ function isLinkingAll(node, heads) {
|
|
|
395
390
|
|
|
396
391
|
function isLegacyNode(data) {
|
|
397
392
|
for (const { keys } of data.batch) {
|
|
393
|
+
if (!keys) continue
|
|
398
394
|
for (const k of keys) {
|
|
399
395
|
if (b4a.equals(k.key, INFO_LEGACY_KEY)) return true
|
|
400
396
|
}
|