autobee 2.0.0-rc.27 → 2.0.0-rc.29
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 +7 -25
- package/lib/boot.js +19 -1
- package/lib/fast-forward.js +3 -14
- package/lib/migrations.js +46 -14
- package/lib/topo.js +1 -58
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -853,15 +853,18 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
853
853
|
|
|
854
854
|
async _bumpMigratedWriters() {
|
|
855
855
|
const opened = new Set()
|
|
856
|
+
let updated = false
|
|
856
857
|
|
|
857
858
|
for (const batch of this._catchupMigratedNodes) {
|
|
858
859
|
await this._processBatch(batch)
|
|
860
|
+
updated = true
|
|
859
861
|
for (const node of batch) {
|
|
860
862
|
if (node.from) opened.add(node.from)
|
|
861
863
|
}
|
|
862
864
|
}
|
|
863
865
|
|
|
864
866
|
for (const core of opened) await core.close()
|
|
867
|
+
return updated
|
|
865
868
|
}
|
|
866
869
|
|
|
867
870
|
// append a null value node to ack writer
|
|
@@ -899,8 +902,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
899
902
|
|
|
900
903
|
async _bumpPendingWriters() {
|
|
901
904
|
if (this._catchupMigratedNodes !== null) {
|
|
902
|
-
await this._bumpMigratedWriters()
|
|
905
|
+
const updated = await this._bumpMigratedWriters()
|
|
903
906
|
this._catchupMigratedNodes = null
|
|
907
|
+
if (updated) return true
|
|
904
908
|
}
|
|
905
909
|
|
|
906
910
|
// apply the best next node to keep the prefix stable
|
|
@@ -1182,7 +1186,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1182
1186
|
// legacy: ungated boot straight onto a system head
|
|
1183
1187
|
async _bootFromSystem(system) {
|
|
1184
1188
|
try {
|
|
1185
|
-
const ff = new FastForward(this, system,
|
|
1189
|
+
const ff = new FastForward(this, system, { timeout: FastForward.DEFAULT_TIMEOUT })
|
|
1186
1190
|
return await this._runFastForward(ff)
|
|
1187
1191
|
} catch (err) {
|
|
1188
1192
|
safetyCatch(err)
|
|
@@ -1322,7 +1326,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1322
1326
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1323
1327
|
if (changes) changes.track()
|
|
1324
1328
|
|
|
1325
|
-
const { head,
|
|
1329
|
+
const { head, migrate } = this.fastForwardTo
|
|
1326
1330
|
|
|
1327
1331
|
const from = this.system.bee.head()
|
|
1328
1332
|
const to = head
|
|
@@ -1362,28 +1366,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1362
1366
|
this.stats.fastForwards++
|
|
1363
1367
|
this.emit('move-to', to, from)
|
|
1364
1368
|
this.ff.resolve({ to, from })
|
|
1365
|
-
|
|
1366
|
-
// tip is null during boot
|
|
1367
|
-
if (!tip) return
|
|
1368
|
-
|
|
1369
|
-
try {
|
|
1370
|
-
await this._reapply(tip)
|
|
1371
|
-
} catch (err) {
|
|
1372
|
-
throw err
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
async _reapply({ system, verified }) {
|
|
1377
|
-
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1378
|
-
if (changes) changes.track()
|
|
1379
|
-
|
|
1380
|
-
const sys = this.system.bee.checkout(system)
|
|
1381
|
-
const t = await topo.rollback(this, sys, verified)
|
|
1382
|
-
await sys.close()
|
|
1383
|
-
|
|
1384
|
-
if (t !== null) await this.applyBacklog(t.tip)
|
|
1385
|
-
|
|
1386
|
-
return this._update(changes)
|
|
1387
1369
|
}
|
|
1388
1370
|
|
|
1389
1371
|
replay() {
|
package/lib/boot.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
1
2
|
const encoding = require('./encoding.js')
|
|
2
3
|
const { checkAutobaseMigration } = require('./migrations.js')
|
|
3
4
|
|
|
@@ -94,7 +95,7 @@ module.exports = async function boot(
|
|
|
94
95
|
await result.bootstrap.setUserData('autobee/local', result.local.key)
|
|
95
96
|
await result.local.setUserData('referrer', result.key)
|
|
96
97
|
|
|
97
|
-
result.migration = await
|
|
98
|
+
result.migration = await migrateWithFallback(
|
|
98
99
|
corestore,
|
|
99
100
|
result.local,
|
|
100
101
|
result.bootstrap,
|
|
@@ -138,6 +139,23 @@ module.exports = async function boot(
|
|
|
138
139
|
return result
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
// the bootstrap pointer names the rotated-to writer, whose legacy boot record
|
|
143
|
+
// can be unusable. the namespace local still carries the pre-rotation record,
|
|
144
|
+
// which points at the last system core that was actually signed
|
|
145
|
+
async function migrateWithFallback(corestore, local, bootstrap, legacyViews) {
|
|
146
|
+
try {
|
|
147
|
+
return await checkAutobaseMigration(corestore, local, bootstrap, legacyViews)
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const prev = corestore.get({ name: 'local', active: false })
|
|
150
|
+
await prev.ready()
|
|
151
|
+
|
|
152
|
+
if (b4a.equals(prev.key, local.key)) throw err
|
|
153
|
+
if (!(await prev.getUserData('autobase/boot'))) throw err
|
|
154
|
+
|
|
155
|
+
return checkAutobaseMigration(corestore, prev, bootstrap, legacyViews)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
141
159
|
async function getLocalKey(bootstrap) {
|
|
142
160
|
const [legacy, current] = await Promise.all([
|
|
143
161
|
bootstrap.getUserData('autobase/local'),
|
package/lib/fast-forward.js
CHANGED
|
@@ -14,7 +14,7 @@ const MIN_FF_GAP = 32
|
|
|
14
14
|
const MAX_FLUSH_SEARCH = 64
|
|
15
15
|
|
|
16
16
|
module.exports = class FastForward {
|
|
17
|
-
constructor(auto, head,
|
|
17
|
+
constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
18
18
|
this.auto = auto
|
|
19
19
|
|
|
20
20
|
this.activeRequests = []
|
|
@@ -30,7 +30,6 @@ module.exports = class FastForward {
|
|
|
30
30
|
// length -1 marks an uninitialised head: we know the key but not the length
|
|
31
31
|
this.head = { key: head.key, length: head.length ?? 0 }
|
|
32
32
|
this.timeout = timeout
|
|
33
|
-
this.tip = tip || null
|
|
34
33
|
this.destroyed = false
|
|
35
34
|
this.running = null
|
|
36
35
|
this.failed = false
|
|
@@ -68,15 +67,7 @@ module.exports = class FastForward {
|
|
|
68
67
|
return null
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
system: batchToHead(oplog.op.views.system),
|
|
73
|
-
verified: {
|
|
74
|
-
op: { key: verified.key, length: verified.length },
|
|
75
|
-
flushes: verified.op.views.flushes
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return new FastForward(auto, batchToHead(verified.op.views.system), tip)
|
|
70
|
+
return new FastForward(auto, batchToHead(verified.op.views.system))
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
static async fromHeads(
|
|
@@ -246,7 +237,7 @@ module.exports = class FastForward {
|
|
|
246
237
|
} catch (err) {
|
|
247
238
|
safetyCatch(err)
|
|
248
239
|
this.head = result.head
|
|
249
|
-
return { head: result.head,
|
|
240
|
+
return { head: result.head, migrate: result.migrate }
|
|
250
241
|
}
|
|
251
242
|
|
|
252
243
|
// fall through to the plain fast-forward below
|
|
@@ -278,7 +269,6 @@ module.exports = class FastForward {
|
|
|
278
269
|
|
|
279
270
|
return {
|
|
280
271
|
head: this.head,
|
|
281
|
-
tip: this.tip,
|
|
282
272
|
migrate: null
|
|
283
273
|
}
|
|
284
274
|
}
|
|
@@ -331,7 +321,6 @@ module.exports = class FastForward {
|
|
|
331
321
|
|
|
332
322
|
return {
|
|
333
323
|
head,
|
|
334
|
-
tip: null,
|
|
335
324
|
migrate: views,
|
|
336
325
|
autobee: autobee === null ? null : batchToHead(autobee.op.views.system)
|
|
337
326
|
}
|
package/lib/migrations.js
CHANGED
|
@@ -37,23 +37,36 @@ async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
|
|
|
37
37
|
const bootRecord = await local.getUserData('autobase/boot')
|
|
38
38
|
if (!bootRecord) return null
|
|
39
39
|
|
|
40
|
-
const { key } = encoding.decodeAutobaseBootRecord(bootRecord)
|
|
40
|
+
const { key, systemLength } = encoding.decodeAutobaseBootRecord(bootRecord)
|
|
41
41
|
const core = store.get({ key: key, encryption: null })
|
|
42
42
|
await core.ready()
|
|
43
43
|
|
|
44
|
-
const
|
|
44
|
+
const length = await findLocalSystemLength(core, systemLength)
|
|
45
|
+
assert(length > 0, 'Expected system block to exist locally')
|
|
46
|
+
|
|
47
|
+
const system = { key, length }
|
|
45
48
|
|
|
46
49
|
// setup encryption
|
|
47
50
|
const encryptionKey = await local.getUserData('autobase/encryption')
|
|
48
51
|
await AutobeeEncryption.setSystemEncryption(bootstrap.key, encryptionKey, core)
|
|
49
52
|
|
|
50
|
-
const
|
|
53
|
+
const session = core.session({ name: 'batch' })
|
|
54
|
+
await session.ready()
|
|
55
|
+
|
|
56
|
+
let catchup
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
catchup = await getCatchupHeads(session, length)
|
|
60
|
+
} finally {
|
|
61
|
+
await session.close()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const info = await readLegacySystemInfo(core, length, { wait: false })
|
|
65
|
+
|
|
51
66
|
const nodes = await Promise.all(
|
|
52
67
|
catchup.map((n) => getWriterBatch(store, n, bootstrap.key, encryptionKey))
|
|
53
68
|
)
|
|
54
69
|
|
|
55
|
-
const info = await readLegacySystemInfo(core, core.length)
|
|
56
|
-
|
|
57
70
|
const indexerManifests = await Promise.all(
|
|
58
71
|
info.indexers.map((idx) => storeCoreManifest(store, idx.key))
|
|
59
72
|
)
|
|
@@ -89,8 +102,11 @@ async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
|
|
|
89
102
|
|
|
90
103
|
// decode the legacy system info recorded in the system core's block at length
|
|
91
104
|
async function readLegacySystemInfo(core, length, opts = null) {
|
|
105
|
+
const block = await core.get(length - 1, opts)
|
|
106
|
+
assert(block !== null, 'Expected system block to exist locally')
|
|
107
|
+
|
|
92
108
|
// Decode hyperbee block using hyperbee2 compat
|
|
93
|
-
const node = decodeBlock(
|
|
109
|
+
const node = decodeBlock(block)
|
|
94
110
|
assert(node.keys.length > 0, 'bad system block')
|
|
95
111
|
|
|
96
112
|
// Decode system info from hyperbee block using autobee compat
|
|
@@ -259,6 +275,15 @@ function deriveNamespace(name, bootstrap, entropy, encryptionKey) {
|
|
|
259
275
|
])
|
|
260
276
|
}
|
|
261
277
|
|
|
278
|
+
// the record's length can run past the signed core into the local batch
|
|
279
|
+
// session, and the signed core can carry verified-but-undownloaded blocks -
|
|
280
|
+
// scan back for the newest block the signed core actually has locally
|
|
281
|
+
async function findLocalSystemLength(core, systemLength) {
|
|
282
|
+
let length = Math.min(core.length, systemLength)
|
|
283
|
+
while (length > 0 && !(await core.has(length - 1))) length--
|
|
284
|
+
return length
|
|
285
|
+
}
|
|
286
|
+
|
|
262
287
|
function coreLength(core, timeout) {
|
|
263
288
|
if (core.length) return core.length
|
|
264
289
|
|
|
@@ -355,18 +380,20 @@ async function getPersistedView(store, view) {
|
|
|
355
380
|
const core = store.get(view.key)
|
|
356
381
|
await core.ready()
|
|
357
382
|
|
|
358
|
-
|
|
383
|
+
// core.length can run past what was downloaded - the system's recorded
|
|
384
|
+
// length is the last one we know is local
|
|
385
|
+
const length = Math.min(core.length, view.length)
|
|
359
386
|
await core.close()
|
|
360
387
|
|
|
361
388
|
return { key: view.key, length }
|
|
362
389
|
}
|
|
363
390
|
|
|
364
|
-
async function getCatchupHeads(
|
|
365
|
-
const
|
|
366
|
-
await session.ready()
|
|
367
|
-
|
|
391
|
+
async function getCatchupHeads(session, from) {
|
|
392
|
+
const seen = new Map()
|
|
368
393
|
const nodes = []
|
|
369
|
-
|
|
394
|
+
|
|
395
|
+
// block 0 is the bee header, never a node
|
|
396
|
+
for (let i = Math.max(from, 1); i < session.length; i++) {
|
|
370
397
|
const node = await session.get(i, { wait: false })
|
|
371
398
|
if (!node) throw new Error('Expect nodes to exist locally')
|
|
372
399
|
|
|
@@ -375,11 +402,16 @@ async function getCatchupHeads(core) {
|
|
|
375
402
|
|
|
376
403
|
const { key, length } = encoding.decodeSystemWriter(entry.key, entry.value)
|
|
377
404
|
if (!length) continue
|
|
405
|
+
|
|
406
|
+
const id = b4a.toString(key, 'hex')
|
|
407
|
+
const current = seen.get(id) || 0
|
|
408
|
+
|
|
409
|
+
if (current >= length) continue
|
|
410
|
+
|
|
411
|
+
seen.set(id, length)
|
|
378
412
|
nodes.push({ key, length })
|
|
379
413
|
}
|
|
380
414
|
|
|
381
|
-
await session.close()
|
|
382
|
-
|
|
383
415
|
return nodes
|
|
384
416
|
}
|
|
385
417
|
|
package/lib/topo.js
CHANGED
|
@@ -10,7 +10,6 @@ const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
|
|
|
10
10
|
const DEFAULT_OP_TIMEOUT = 5000
|
|
11
11
|
|
|
12
12
|
exports.sort = sort
|
|
13
|
-
exports.rollback = rollback
|
|
14
13
|
exports.replay = replay
|
|
15
14
|
exports.getMostRecentHead = getMostRecentHead
|
|
16
15
|
|
|
@@ -49,46 +48,6 @@ async function getMostRecentHead(auto, system) {
|
|
|
49
48
|
return null
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
async function rollback(auto, system, verified) {
|
|
53
|
-
if (!(await isApplied(system, verified.op))) return null
|
|
54
|
-
|
|
55
|
-
const tip = []
|
|
56
|
-
const ch = system.createChangesStream()
|
|
57
|
-
|
|
58
|
-
let found = false
|
|
59
|
-
|
|
60
|
-
for await (const data of ch) {
|
|
61
|
-
const { ref, info } = getOplog(data)
|
|
62
|
-
|
|
63
|
-
if (sameNode(ref.oplog, verified.op)) {
|
|
64
|
-
// stale views.flushes - the verified head describes a prefix this system
|
|
65
|
-
// does not share, so nothing above it is ours to reapply
|
|
66
|
-
if (info.flushes === verified.flushes) found = true
|
|
67
|
-
break
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// walking back, a writer's lengths only decrease - once this writer is
|
|
71
|
-
// behind the verified head the marker cannot be further down
|
|
72
|
-
if (b4a.equals(ref.oplog.key, verified.op.key) && ref.oplog.length < verified.op.length) {
|
|
73
|
-
break
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
tip.push(ref)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!found) return null
|
|
80
|
-
|
|
81
|
-
const inflated = await inflate(auto, tip.reverse())
|
|
82
|
-
|
|
83
|
-
// @todo: verify sort
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
undo: null,
|
|
87
|
-
view: null,
|
|
88
|
-
tip: inflated.map(getBatch)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
51
|
// nodes we cannot resolve are returned as null rather than dropped, so callers
|
|
93
52
|
// can tell an incomplete replay from a short one
|
|
94
53
|
async function replay(auto) {
|
|
@@ -109,14 +68,6 @@ async function replay(auto) {
|
|
|
109
68
|
return inflated.flatMap((b) => (b === null ? [null] : b.batch))
|
|
110
69
|
}
|
|
111
70
|
|
|
112
|
-
async function isApplied(system, node) {
|
|
113
|
-
const entry = await system.get(encoding.encodeSystemWriterKey(node.key))
|
|
114
|
-
if (entry === null) return false
|
|
115
|
-
|
|
116
|
-
const info = encoding.decodeSystemWriter(entry.key, entry.value)
|
|
117
|
-
return info.length >= node.length
|
|
118
|
-
}
|
|
119
|
-
|
|
120
71
|
async function sort(auto, batch) {
|
|
121
72
|
const node = batch[0]
|
|
122
73
|
const ch = auto.system.bee.createChangesStream()
|
|
@@ -125,7 +76,7 @@ async function sort(auto, batch) {
|
|
|
125
76
|
for await (const data of ch) {
|
|
126
77
|
if (isLegacyNode(data)) break
|
|
127
78
|
|
|
128
|
-
const { ref
|
|
79
|
+
const { ref } = getOplog(data)
|
|
129
80
|
const link = {
|
|
130
81
|
key: ref.oplog.key,
|
|
131
82
|
length: ref.oplog.length,
|
|
@@ -448,11 +399,3 @@ function isLegacyNode(data) {
|
|
|
448
399
|
}
|
|
449
400
|
return false
|
|
450
401
|
}
|
|
451
|
-
|
|
452
|
-
function sameNode(a, b) {
|
|
453
|
-
return b4a.equals(a.key, b.key) && a.length === b.length
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function getBatch(b) {
|
|
457
|
-
return b.batch
|
|
458
|
-
}
|