autobee 2.0.0-rc.27 → 2.0.0-rc.28
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 +2 -24
- package/lib/boot.js +19 -1
- package/lib/fast-forward.js +3 -14
- package/lib/migrations.js +37 -14
- package/lib/topo.js +1 -58
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1182,7 +1182,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1182
1182
|
// legacy: ungated boot straight onto a system head
|
|
1183
1183
|
async _bootFromSystem(system) {
|
|
1184
1184
|
try {
|
|
1185
|
-
const ff = new FastForward(this, system,
|
|
1185
|
+
const ff = new FastForward(this, system, { timeout: FastForward.DEFAULT_TIMEOUT })
|
|
1186
1186
|
return await this._runFastForward(ff)
|
|
1187
1187
|
} catch (err) {
|
|
1188
1188
|
safetyCatch(err)
|
|
@@ -1322,7 +1322,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1322
1322
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1323
1323
|
if (changes) changes.track()
|
|
1324
1324
|
|
|
1325
|
-
const { head,
|
|
1325
|
+
const { head, migrate } = this.fastForwardTo
|
|
1326
1326
|
|
|
1327
1327
|
const from = this.system.bee.head()
|
|
1328
1328
|
const to = head
|
|
@@ -1362,28 +1362,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1362
1362
|
this.stats.fastForwards++
|
|
1363
1363
|
this.emit('move-to', to, from)
|
|
1364
1364
|
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
1365
|
}
|
|
1388
1366
|
|
|
1389
1367
|
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,18 @@ 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 session = core.session({ name: 'batch' })
|
|
366
|
-
await session.ready()
|
|
367
|
-
|
|
391
|
+
async function getCatchupHeads(session, from) {
|
|
368
392
|
const nodes = []
|
|
369
|
-
|
|
393
|
+
// block 0 is the bee header, never a node
|
|
394
|
+
for (let i = Math.max(from, 1); i < session.length; i++) {
|
|
370
395
|
const node = await session.get(i, { wait: false })
|
|
371
396
|
if (!node) throw new Error('Expect nodes to exist locally')
|
|
372
397
|
|
|
@@ -378,8 +403,6 @@ async function getCatchupHeads(core) {
|
|
|
378
403
|
nodes.push({ key, length })
|
|
379
404
|
}
|
|
380
405
|
|
|
381
|
-
await session.close()
|
|
382
|
-
|
|
383
406
|
return nodes
|
|
384
407
|
}
|
|
385
408
|
|
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
|
-
}
|