autobee 2.0.0-rc.23 → 2.0.0-rc.26
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 +44 -11
- package/lib/boot.js +1 -1
- package/lib/fast-forward.js +1 -1
- package/lib/migrations.js +421 -0
- package/lib/writers.js +6 -1
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const asserts = require('./lib/asserts.js')
|
|
|
14
14
|
const boot = require('./lib/boot.js')
|
|
15
15
|
const { resolveWeight, currentWeight } = require('./lib/witness.js')
|
|
16
16
|
const encoding = require('./lib/encoding.js')
|
|
17
|
+
const { LEGACY_OPLOG_VERSION } = require('./lib/constants.js')
|
|
17
18
|
const FastForward = require('./lib/fast-forward.js')
|
|
18
19
|
const System = require('./lib/system.js')
|
|
19
20
|
const ApplyCalls = require('./lib/apply-calls.js')
|
|
@@ -22,7 +23,7 @@ const { ActiveWriters } = require('./lib/writers.js')
|
|
|
22
23
|
const TrustedPeers = require('./lib/trusted.js')
|
|
23
24
|
const ApplyView = require('./lib/apply-view.js')
|
|
24
25
|
const UpdateChanges = require('./lib/updates.js')
|
|
25
|
-
const migrations = require('./lib/migrations')
|
|
26
|
+
const migrations = require('./lib/migrations.js')
|
|
26
27
|
|
|
27
28
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
28
29
|
const INTERRUPT = new Error('Apply interrupted')
|
|
@@ -527,12 +528,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
527
528
|
this.stats.drains++
|
|
528
529
|
|
|
529
530
|
if (this.bootFrom) {
|
|
530
|
-
const { head = null, legacy = null, bootCondition = null } = this.bootFrom
|
|
531
|
+
const { head = null, legacy = null, bootCondition = null, wait = false } = this.bootFrom
|
|
531
532
|
|
|
532
533
|
this.bootFrom = null
|
|
533
534
|
|
|
534
535
|
if (legacy) await this._bootFromSystem(legacy)
|
|
535
|
-
else if (head) await this._bootFromHead(head, bootCondition)
|
|
536
|
+
else if (head) await this._bootFromHead(head, bootCondition, wait)
|
|
536
537
|
}
|
|
537
538
|
|
|
538
539
|
this._ackRequired = false
|
|
@@ -1195,9 +1196,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1195
1196
|
|
|
1196
1197
|
// the boot head seeds the view every trust decision here is made against,
|
|
1197
1198
|
// since our own view is still empty
|
|
1198
|
-
async _bootFromHead(head, bootCondition) {
|
|
1199
|
+
async _bootFromHead(head, bootCondition, wait = false) {
|
|
1199
1200
|
// just a head: one attempt, we do not wait around if it cannot be read
|
|
1200
|
-
if (bootCondition === null) {
|
|
1201
|
+
if (bootCondition === null && !wait) {
|
|
1201
1202
|
try {
|
|
1202
1203
|
const ff = await FastForward.fromHead(this, head, null, {
|
|
1203
1204
|
force: true,
|
|
@@ -1211,8 +1212,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1211
1212
|
}
|
|
1212
1213
|
}
|
|
1213
1214
|
|
|
1214
|
-
//
|
|
1215
|
-
//
|
|
1215
|
+
// park until the head can be read and, if a condition is set, something
|
|
1216
|
+
// acceptable turns up
|
|
1216
1217
|
let opened = null
|
|
1217
1218
|
|
|
1218
1219
|
try {
|
|
@@ -1220,10 +1221,28 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1220
1221
|
this._bootWait = rrp()
|
|
1221
1222
|
|
|
1222
1223
|
try {
|
|
1223
|
-
if (
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1224
|
+
if (bootCondition === null) {
|
|
1225
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1226
|
+
force: true,
|
|
1227
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1228
|
+
})
|
|
1229
|
+
|
|
1230
|
+
if (ff !== null && (await this._runFastForward(ff))) return true
|
|
1231
|
+
} else {
|
|
1232
|
+
if (opened === null) opened = await this._bootReference(head)
|
|
1233
|
+
|
|
1234
|
+
if (opened !== null) {
|
|
1235
|
+
if (await this._bootAttempt(head, bootCondition, opened.view)) {
|
|
1236
|
+
return true
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// the reference may be stale - resolve it afresh next attempt
|
|
1240
|
+
await opened.close()
|
|
1241
|
+
opened = null
|
|
1242
|
+
} else if (await this._bootLegacyHead(head)) {
|
|
1243
|
+
// boot legacy head through the migration pathway
|
|
1244
|
+
return true
|
|
1245
|
+
}
|
|
1227
1246
|
}
|
|
1228
1247
|
} catch (err) {
|
|
1229
1248
|
safetyCatch(err)
|
|
@@ -1258,6 +1277,20 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1258
1277
|
return ff !== null && (await this._runFastForward(ff))
|
|
1259
1278
|
}
|
|
1260
1279
|
|
|
1280
|
+
async _bootLegacyHead(head) {
|
|
1281
|
+
const oplog = await FastForward.flushHead(this, head, {
|
|
1282
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1283
|
+
})
|
|
1284
|
+
if (oplog === null || oplog.op.version > LEGACY_OPLOG_VERSION) return false
|
|
1285
|
+
|
|
1286
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1287
|
+
force: true,
|
|
1288
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1289
|
+
})
|
|
1290
|
+
|
|
1291
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1261
1294
|
async _bootReference(head) {
|
|
1262
1295
|
const oplog = await FastForward.flushHead(this, head, {
|
|
1263
1296
|
timeout: FastForward.DEFAULT_TIMEOUT
|
package/lib/boot.js
CHANGED
package/lib/fast-forward.js
CHANGED
|
@@ -3,7 +3,7 @@ const b4a = require('b4a')
|
|
|
3
3
|
const Hypercore = require('hypercore')
|
|
4
4
|
|
|
5
5
|
const System = require('./system.js')
|
|
6
|
-
const migrations = require('./migrations')
|
|
6
|
+
const migrations = require('./migrations.js')
|
|
7
7
|
const { LEGACY_AUTOBASE_VERSION } = require('./constants.js')
|
|
8
8
|
|
|
9
9
|
const DEFAULT_OP_TIMEOUT = 5_000
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
const c = require('compact-encoding')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
const crypto = require('hypercore-crypto')
|
|
4
|
+
const { AutobeeEncryption } = require('autobee-encryption')
|
|
5
|
+
const { decodeBlock } = require('hyperbee2/lib/encoding.js')
|
|
6
|
+
|
|
7
|
+
const encoding = require('./encoding.js')
|
|
8
|
+
const { assert, bail } = require('./asserts.js')
|
|
9
|
+
const { LEGACY_OPLOG_VERSION } = require('./constants.js')
|
|
10
|
+
|
|
11
|
+
const { getEncoding } = require('../encoding/spec/autobee')
|
|
12
|
+
|
|
13
|
+
const SystemInfoV1 = getEncoding('@autobase-compat/info-v1')
|
|
14
|
+
const SystemInfoV2 = getEncoding('@autobase-compat/info-v2')
|
|
15
|
+
|
|
16
|
+
const EMPTY = b4a.alloc(0)
|
|
17
|
+
const INDEX_VERSION = 1
|
|
18
|
+
const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)
|
|
19
|
+
|
|
20
|
+
// indexer rotations chain legacy systems - how many generations we chase
|
|
21
|
+
const MAX_MIGRATE_HOPS = 64
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
MAX_MIGRATE_HOPS,
|
|
25
|
+
checkAutobaseMigration,
|
|
26
|
+
readLegacySystemInfo,
|
|
27
|
+
findNewestSystem,
|
|
28
|
+
resolveLegacyViews,
|
|
29
|
+
inflateLegacyOplog,
|
|
30
|
+
decodeLegacySystemInfo,
|
|
31
|
+
deriveNamespace,
|
|
32
|
+
coreLength
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// boot-time migration of local legacy autobase storage
|
|
36
|
+
async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
|
|
37
|
+
const bootRecord = await local.getUserData('autobase/boot')
|
|
38
|
+
if (!bootRecord) return null
|
|
39
|
+
|
|
40
|
+
const { key } = encoding.decodeAutobaseBootRecord(bootRecord)
|
|
41
|
+
const core = store.get({ key: key, encryption: null })
|
|
42
|
+
await core.ready()
|
|
43
|
+
|
|
44
|
+
const system = { key, length: core.length }
|
|
45
|
+
|
|
46
|
+
// setup encryption
|
|
47
|
+
const encryptionKey = await local.getUserData('autobase/encryption')
|
|
48
|
+
await AutobeeEncryption.setSystemEncryption(bootstrap.key, encryptionKey, core)
|
|
49
|
+
|
|
50
|
+
const catchup = await getCatchupHeads(core)
|
|
51
|
+
const nodes = await Promise.all(
|
|
52
|
+
catchup.map((n) => getWriterBatch(store, n, bootstrap.key, encryptionKey))
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const info = await readLegacySystemInfo(core, core.length)
|
|
56
|
+
|
|
57
|
+
const indexerManifests = await Promise.all(
|
|
58
|
+
info.indexers.map((idx) => storeCoreManifest(store, idx.key))
|
|
59
|
+
)
|
|
60
|
+
const views = await Promise.all(info.views.map((v) => getPersistedView(store, v)))
|
|
61
|
+
const entropy = legacyEntropy(info, indexerManifests)
|
|
62
|
+
|
|
63
|
+
const getManifest = (key) => storeCoreManifest(store, key)
|
|
64
|
+
|
|
65
|
+
const mappedViews = new Map()
|
|
66
|
+
for (const name of legacyViews) {
|
|
67
|
+
const v = await findViewByName(
|
|
68
|
+
getManifest,
|
|
69
|
+
bootstrap.key,
|
|
70
|
+
encryptionKey,
|
|
71
|
+
indexerManifests,
|
|
72
|
+
views,
|
|
73
|
+
entropy,
|
|
74
|
+
name
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
mappedViews.set(name, v)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await core.close()
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
encryptionKey,
|
|
84
|
+
system,
|
|
85
|
+
views: mappedViews,
|
|
86
|
+
catchup: nodes
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// decode the legacy system info recorded in the system core's block at length
|
|
91
|
+
async function readLegacySystemInfo(core, length, opts = null) {
|
|
92
|
+
// Decode hyperbee block using hyperbee2 compat
|
|
93
|
+
const node = decodeBlock(await core.get(length - 1, opts))
|
|
94
|
+
assert(node.keys.length > 0, 'bad system block')
|
|
95
|
+
|
|
96
|
+
// Decode system info from hyperbee block using autobee compat
|
|
97
|
+
return decodeLegacySystemInfo(node.keys[0].value)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// read each indexer's newest legacy oplog stamp - its digest/checkpoint mark
|
|
101
|
+
// the newest legacy system that indexer knows. take the majority vote. an
|
|
102
|
+
// indexer that already moved to autobee announces its live autobee state
|
|
103
|
+
// instead - keep the newest of those as a direct fast-forward candidate and
|
|
104
|
+
// bisect down to its buried legacy stamps so its vote still counts
|
|
105
|
+
async function findNewestSystem(ff, info) {
|
|
106
|
+
const tally = new Map()
|
|
107
|
+
|
|
108
|
+
let autobee = null
|
|
109
|
+
|
|
110
|
+
for (const idx of info.indexers) {
|
|
111
|
+
const core = ff.auto.openCore(idx.key)
|
|
112
|
+
ff.cores.push(core)
|
|
113
|
+
await core.ready()
|
|
114
|
+
|
|
115
|
+
const length = await coreLength(core, ff.timeout)
|
|
116
|
+
if (!length || ff.destroyed) continue
|
|
117
|
+
|
|
118
|
+
let oplog = await flushHead(ff.auto, { key: idx.key, length }, { timeout: ff.timeout })
|
|
119
|
+
if (oplog === null || !oplog.op.views) continue
|
|
120
|
+
|
|
121
|
+
if (oplog.op.version > LEGACY_OPLOG_VERSION) {
|
|
122
|
+
if (autobee === null || oplog.op.views.flushes > autobee.op.views.flushes) {
|
|
123
|
+
autobee = oplog
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const boundary = await findLegacyBoundary(ff, core, idx.length, oplog.length - 1)
|
|
127
|
+
if (!boundary || ff.destroyed) continue
|
|
128
|
+
|
|
129
|
+
oplog = await flushHead(ff.auto, { key: idx.key, length: boundary }, { timeout: ff.timeout })
|
|
130
|
+
if (oplog === null || !oplog.op.views) continue
|
|
131
|
+
if (oplog.op.version > LEGACY_OPLOG_VERSION) continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const sys = oplog.op.views.system
|
|
135
|
+
if (!sys || !sys.key) continue
|
|
136
|
+
|
|
137
|
+
const hex = b4a.toString(sys.key, 'hex')
|
|
138
|
+
const t = tally.get(hex) || { key: sys.key, votes: 0, length: sys.length }
|
|
139
|
+
|
|
140
|
+
t.votes++
|
|
141
|
+
if (sys.length > t.length) t.length = sys.length
|
|
142
|
+
tally.set(hex, t)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const majority = (info.indexers.length >> 1) + 1
|
|
146
|
+
|
|
147
|
+
let best = null
|
|
148
|
+
for (const t of tally.values()) {
|
|
149
|
+
if (t.votes >= majority && (best === null || t.length > best.length)) best = t
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { legacy: best === null ? null : best.key, autobee }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// map the legacy views recorded in the system info by name
|
|
156
|
+
async function resolveLegacyViews(ff, info) {
|
|
157
|
+
const indexerManifests = await Promise.all(
|
|
158
|
+
info.indexers.map((idx) => ffCoreManifest(ff, idx.key, idx.length))
|
|
159
|
+
)
|
|
160
|
+
if (ff.destroyed || indexerManifests.some((m) => m === null)) return null
|
|
161
|
+
|
|
162
|
+
const entropy = legacyEntropy(info, indexerManifests)
|
|
163
|
+
|
|
164
|
+
const getManifest = (key, length) => ffCoreManifest(ff, key, length)
|
|
165
|
+
|
|
166
|
+
const views = new Map()
|
|
167
|
+
|
|
168
|
+
await Promise.all(
|
|
169
|
+
ff.auto.legacyViews.map(async (name) => {
|
|
170
|
+
const v = await findViewByName(
|
|
171
|
+
getManifest,
|
|
172
|
+
ff.auto.key,
|
|
173
|
+
ff.auto.encryptionKey,
|
|
174
|
+
indexerManifests,
|
|
175
|
+
info.views,
|
|
176
|
+
entropy,
|
|
177
|
+
name
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if (v) views.set(name, v)
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (ff.destroyed) return null
|
|
185
|
+
|
|
186
|
+
return views
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// legacy nodes always inflate, but only indexers carry the digest/checkpoint
|
|
190
|
+
// stamps that synthesise system info - callers must check op.views
|
|
191
|
+
async function inflateLegacyOplog(buf, core, seq, opts = null) {
|
|
192
|
+
const m = encoding.decodeRawOplog(buf)
|
|
193
|
+
|
|
194
|
+
const op = {
|
|
195
|
+
version: m.version,
|
|
196
|
+
timestamp: 0,
|
|
197
|
+
links: m.node.heads,
|
|
198
|
+
batch: { start: 0, end: m.node.batch - 1 },
|
|
199
|
+
views: null,
|
|
200
|
+
optimistic: !!m.optimistic,
|
|
201
|
+
value: m.node.value
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (m.digest === null || m.checkpoint === null || !m.checkpoint.system) return op
|
|
205
|
+
|
|
206
|
+
const fetches = []
|
|
207
|
+
|
|
208
|
+
fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, opts) : buf)
|
|
209
|
+
fetches.push(m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer, opts) : buf)
|
|
210
|
+
|
|
211
|
+
const [digestNode, checkpointNode] = await Promise.all(fetches)
|
|
212
|
+
// a wait: false get we could not satisfy locally
|
|
213
|
+
if (digestNode === null || checkpointNode === null) return op
|
|
214
|
+
|
|
215
|
+
const { digest } = encoding.decodeRawOplog(digestNode)
|
|
216
|
+
const { checkpoint } = encoding.decodeRawOplog(checkpointNode)
|
|
217
|
+
|
|
218
|
+
if (!checkpoint.system || !checkpoint.system.checkpoint) return op
|
|
219
|
+
|
|
220
|
+
op.views = {
|
|
221
|
+
system: {
|
|
222
|
+
key: digest.key,
|
|
223
|
+
start: 0,
|
|
224
|
+
length: checkpoint.system.checkpoint.length
|
|
225
|
+
},
|
|
226
|
+
flushes: seq
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return op
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function decodeLegacySystemInfo(buffer) {
|
|
233
|
+
const state = { start: 0, end: buffer.length, buffer }
|
|
234
|
+
const version = c.uint.decode(state)
|
|
235
|
+
|
|
236
|
+
state.start--
|
|
237
|
+
|
|
238
|
+
switch (version) {
|
|
239
|
+
case 1:
|
|
240
|
+
return SystemInfoV1.decode(state)
|
|
241
|
+
case 2:
|
|
242
|
+
return SystemInfoV2.decode(state)
|
|
243
|
+
default:
|
|
244
|
+
bail('Expected legacy system info')
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function deriveNamespace(name, bootstrap, entropy, encryptionKey) {
|
|
249
|
+
const encryptionId = crypto.hash(encryptionKey || EMPTY)
|
|
250
|
+
const version = c.encode(c.uint, INDEX_VERSION)
|
|
251
|
+
|
|
252
|
+
return crypto.hash([
|
|
253
|
+
NS_SIGNER_NAMESPACE,
|
|
254
|
+
version,
|
|
255
|
+
bootstrap,
|
|
256
|
+
encryptionId,
|
|
257
|
+
entropy,
|
|
258
|
+
b4a.from(name)
|
|
259
|
+
])
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function coreLength(core, timeout) {
|
|
263
|
+
if (core.length) return core.length
|
|
264
|
+
|
|
265
|
+
return new Promise((resolve) => {
|
|
266
|
+
core.on('append', () => resolve(core.length))
|
|
267
|
+
setTimeout(resolve, timeout, 0)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// a writer's oplog is a legacy prefix followed by autobee-format nodes -
|
|
272
|
+
// bisect for the boundary. lo is a length known to be legacy (the legacy
|
|
273
|
+
// system's recorded length for this writer), hi a seq known to be autobee.
|
|
274
|
+
// returns the legacy prefix length
|
|
275
|
+
async function findLegacyBoundary(ff, core, lo, hi) {
|
|
276
|
+
while (lo < hi) {
|
|
277
|
+
const mid = (lo + hi) >>> 1
|
|
278
|
+
|
|
279
|
+
const block = await core.get(mid, {
|
|
280
|
+
timeout: ff.timeout,
|
|
281
|
+
activeRequests: ff.activeRequests
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (encoding.decodeRawOplog(block).version > LEGACY_OPLOG_VERSION) hi = mid
|
|
285
|
+
else lo = mid + 1
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return lo
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function legacyEntropy(info, indexerManifests) {
|
|
292
|
+
return info.version > 1 && info.entropy ? info.entropy : indexerManifests[0].signers[0].namespace
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function findViewByName(
|
|
296
|
+
getManifest,
|
|
297
|
+
bootstrap,
|
|
298
|
+
encryptionKey,
|
|
299
|
+
indexerManifests,
|
|
300
|
+
views,
|
|
301
|
+
entropy,
|
|
302
|
+
name
|
|
303
|
+
) {
|
|
304
|
+
if (indexerManifests.length === 0) return null
|
|
305
|
+
|
|
306
|
+
const namespace = deriveNamespace(name, bootstrap, entropy, encryptionKey)
|
|
307
|
+
|
|
308
|
+
for (const v of views) {
|
|
309
|
+
const manifest = await getManifest(v.key, v.length)
|
|
310
|
+
if (!manifest) continue
|
|
311
|
+
|
|
312
|
+
if (manifest.signers.length === 0) continue
|
|
313
|
+
|
|
314
|
+
const signer = manifest.signers[0]
|
|
315
|
+
|
|
316
|
+
if (b4a.equals(signer.namespace, namespace)) return v
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// request-tracked manifest read over replication
|
|
323
|
+
async function ffCoreManifest(ff, key, length) {
|
|
324
|
+
const core = ff.auto.store.get(key)
|
|
325
|
+
ff.cores.push(core)
|
|
326
|
+
|
|
327
|
+
await core.ready()
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
if (!core.manifest) {
|
|
331
|
+
await core.get(length - 1, {
|
|
332
|
+
timeout: ff.timeout,
|
|
333
|
+
activeRequests: ff.activeRequests
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
} catch {
|
|
337
|
+
return null
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return core.manifest
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// local-storage manifest read
|
|
344
|
+
async function storeCoreManifest(store, key) {
|
|
345
|
+
const core = store.get(key)
|
|
346
|
+
await core.ready()
|
|
347
|
+
|
|
348
|
+
const manifest = core.manifest
|
|
349
|
+
await core.close()
|
|
350
|
+
|
|
351
|
+
return manifest
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function getPersistedView(store, view) {
|
|
355
|
+
const core = store.get(view.key)
|
|
356
|
+
await core.ready()
|
|
357
|
+
|
|
358
|
+
const length = core.length
|
|
359
|
+
await core.close()
|
|
360
|
+
|
|
361
|
+
return { key: view.key, length }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function getCatchupHeads(core) {
|
|
365
|
+
const session = core.session({ name: 'batch' })
|
|
366
|
+
await session.ready()
|
|
367
|
+
|
|
368
|
+
const nodes = []
|
|
369
|
+
for (let i = core.length; i < session.length; i++) {
|
|
370
|
+
const node = await session.get(i, { wait: false })
|
|
371
|
+
if (!node) throw new Error('Expect nodes to exist locally')
|
|
372
|
+
|
|
373
|
+
const entry = decodeBlock(node).keys[0]
|
|
374
|
+
if (entry.key[0] !== 0x01) continue
|
|
375
|
+
|
|
376
|
+
const { key, length } = encoding.decodeSystemWriter(entry.key, entry.value)
|
|
377
|
+
if (!length) continue
|
|
378
|
+
nodes.push({ key, length })
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
await session.close()
|
|
382
|
+
|
|
383
|
+
return nodes
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function getWriterBatch(store, head, key, encryptionKey, nodes = []) {
|
|
387
|
+
const batch = []
|
|
388
|
+
|
|
389
|
+
const core = store.get(head.key)
|
|
390
|
+
await core.ready()
|
|
391
|
+
|
|
392
|
+
if (encryptionKey) {
|
|
393
|
+
await core.setEncryption(AutobeeEncryption.getWriterEncryption(key, encryptionKey))
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let seq = head.length - 1
|
|
397
|
+
const block = await core.get(seq--, { wait: false })
|
|
398
|
+
if (!block) throw new Error('Expect writer node to exist locally')
|
|
399
|
+
|
|
400
|
+
const node = encoding.decodeOplog(block)
|
|
401
|
+
|
|
402
|
+
batch.unshift({ ...head, ...node, from: core })
|
|
403
|
+
|
|
404
|
+
while (seq >= 0) {
|
|
405
|
+
const block = await core.get(seq--, { wait: false })
|
|
406
|
+
if (!block) break
|
|
407
|
+
|
|
408
|
+
const node = encoding.decodeOplog(block)
|
|
409
|
+
if (!node.batch.end) break
|
|
410
|
+
|
|
411
|
+
batch.unshift({ key: head.key, length: seq + 2, from: core, ...node })
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return batch
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// lazy to avoid the require cycle - fast-forward imports this module
|
|
418
|
+
function flushHead(auto, head, opts) {
|
|
419
|
+
const FastForward = require('./fast-forward.js')
|
|
420
|
+
return FastForward.flushHead(auto, head, opts)
|
|
421
|
+
}
|
package/lib/writers.js
CHANGED
|
@@ -59,21 +59,26 @@ class ActiveWriters {
|
|
|
59
59
|
let w = this.active.get(id)
|
|
60
60
|
|
|
61
61
|
if (w === this.localWriter) return
|
|
62
|
+
|
|
63
|
+
let added = false
|
|
62
64
|
if (!w) {
|
|
63
65
|
// anchors are local-only and never replicated - nothing to fetch
|
|
64
66
|
const info = await this.auto.system.get(key, { unflushed: true })
|
|
65
67
|
if (info && info.isAnchor) return
|
|
66
68
|
w = await this.add(key)
|
|
67
69
|
if (!w) return
|
|
70
|
+
added = true
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
const advanced = length > w.hintedLength
|
|
74
|
+
|
|
70
75
|
// remember the announced length so gc doesn't drop a writer we haven't caught up on
|
|
71
76
|
w.hint(length)
|
|
72
77
|
|
|
73
78
|
// prefetch the oplog head for the fast-forward that runs right after us
|
|
74
79
|
if (w && length > 0) w.core.get(length - 1).catch(noop)
|
|
75
80
|
|
|
76
|
-
this.bump()
|
|
81
|
+
if (added || advanced) this.bump()
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
getLatestLocalOplog() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.26",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"lunte": "^1.6.0",
|
|
44
44
|
"prettier": "^3.8.1",
|
|
45
45
|
"prettier-config-holepunch": "^2.0.0",
|
|
46
|
-
"protomux-wakeup": "^2.9.0"
|
|
46
|
+
"protomux-wakeup": "^2.9.0",
|
|
47
|
+
"replication-simulator": "^3.0.0"
|
|
47
48
|
}
|
|
48
49
|
}
|