autobee 2.0.0-rc.26 → 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 CHANGED
@@ -177,6 +177,7 @@ module.exports = class Autobee extends ReadyResource {
177
177
  this._bootingAll.catch(safetyCatch)
178
178
 
179
179
  await this.bee.ready()
180
+ await this._workingBee.ready()
180
181
  await this._bootingState
181
182
 
182
183
  this._localSystemStart = this.system.bee.context.local.length
@@ -1042,12 +1043,7 @@ module.exports = class Autobee extends ReadyResource {
1042
1043
 
1043
1044
  this.writers.attest(witnessed)
1044
1045
 
1045
- for (const { key, added, isAnchor } of changed) {
1046
- if (isAnchor) {
1047
- // no Writer/ActiveWriters tracking, but still wake anything linked to it
1048
- this.writers.triggers.trigger(b4a.toString(key, 'hex'), 1)
1049
- continue
1050
- }
1046
+ for (const { key, added } of changed) {
1051
1047
  if (added) await this.writers.add(key)
1052
1048
  else await this.writers.remove(key)
1053
1049
  }
@@ -1186,7 +1182,7 @@ module.exports = class Autobee extends ReadyResource {
1186
1182
  // legacy: ungated boot straight onto a system head
1187
1183
  async _bootFromSystem(system) {
1188
1184
  try {
1189
- const ff = new FastForward(this, system, null, { timeout: FastForward.DEFAULT_TIMEOUT })
1185
+ const ff = new FastForward(this, system, { timeout: FastForward.DEFAULT_TIMEOUT })
1190
1186
  return await this._runFastForward(ff)
1191
1187
  } catch (err) {
1192
1188
  safetyCatch(err)
@@ -1326,7 +1322,7 @@ module.exports = class Autobee extends ReadyResource {
1326
1322
  const changes = this._hasUpdate ? new UpdateChanges(this) : null
1327
1323
  if (changes) changes.track()
1328
1324
 
1329
- const { head, tip, migrate } = this.fastForwardTo
1325
+ const { head, migrate } = this.fastForwardTo
1330
1326
 
1331
1327
  const from = this.system.bee.head()
1332
1328
  const to = head
@@ -1366,28 +1362,6 @@ module.exports = class Autobee extends ReadyResource {
1366
1362
  this.stats.fastForwards++
1367
1363
  this.emit('move-to', to, from)
1368
1364
  this.ff.resolve({ to, from })
1369
-
1370
- // tip is null during boot
1371
- if (!tip) return
1372
-
1373
- try {
1374
- await this._reapply(tip)
1375
- } catch (err) {
1376
- throw err
1377
- }
1378
- }
1379
-
1380
- async _reapply({ system, verified }) {
1381
- const changes = this._hasUpdate ? new UpdateChanges(this) : null
1382
- if (changes) changes.track()
1383
-
1384
- const sys = this.system.bee.checkout(system)
1385
- const t = await topo.rollback(this, sys, verified)
1386
- await sys.close()
1387
-
1388
- if (t !== null) await this.applyBacklog(t.tip)
1389
-
1390
- return this._update(changes)
1391
1365
  }
1392
1366
 
1393
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 checkAutobaseMigration(
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'),
@@ -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, tip, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
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
- const tip = {
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, tip: null, migrate: result.migrate }
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 system = { key, length: core.length }
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 catchup = await getCatchupHeads(core)
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(await core.get(length - 1, opts))
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
- const length = core.length
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(core) {
365
- const session = core.session({ name: 'batch' })
366
- await session.ready()
367
-
391
+ async function getCatchupHeads(session, from) {
368
392
  const nodes = []
369
- for (let i = core.length; i < session.length; i++) {
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/system.js CHANGED
@@ -85,8 +85,8 @@ module.exports = class Systembee {
85
85
  this.update(key, length, -1, 0, false, false, true, false, false, -1, false)
86
86
  }
87
87
 
88
- addAnchor(key, { length = 1 } = {}) {
89
- this.update(key, length, -1, -1, false, true, false, false, true, -1, false)
88
+ addAnchor(key) {
89
+ this.update(key, 0, -1, -1, false, true, false, false, true, -1, false)
90
90
  }
91
91
 
92
92
  isGenesis() {
package/lib/topo.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const b4a = require('b4a')
2
2
  const encoding = require('./encoding.js')
3
- const { createNode } = require('./writers.js')
3
+ const { createNode, finalizeNodeBatch } = require('./writers.js')
4
4
  const asserts = require('./asserts.js')
5
+ const { LEGACY_OPLOG_VERSION } = require('./constants.js')
5
6
 
6
7
  const EMPTY_HEAD = { length: 0, key: null }
7
8
  const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
@@ -9,7 +10,6 @@ const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
9
10
  const DEFAULT_OP_TIMEOUT = 5000
10
11
 
11
12
  exports.sort = sort
12
- exports.rollback = rollback
13
13
  exports.replay = replay
14
14
  exports.getMostRecentHead = getMostRecentHead
15
15
 
@@ -48,46 +48,6 @@ async function getMostRecentHead(auto, system) {
48
48
  return null
49
49
  }
50
50
 
51
- async function rollback(auto, system, verified) {
52
- if (!(await isApplied(system, verified.op))) return null
53
-
54
- const tip = []
55
- const ch = system.createChangesStream()
56
-
57
- let found = false
58
-
59
- for await (const data of ch) {
60
- const { ref, info } = getOplog(data)
61
-
62
- if (sameNode(ref.oplog, verified.op)) {
63
- // stale views.flushes - the verified head describes a prefix this system
64
- // does not share, so nothing above it is ours to reapply
65
- if (info.flushes === verified.flushes) found = true
66
- break
67
- }
68
-
69
- // walking back, a writer's lengths only decrease - once this writer is
70
- // behind the verified head the marker cannot be further down
71
- if (b4a.equals(ref.oplog.key, verified.op.key) && ref.oplog.length < verified.op.length) {
72
- break
73
- }
74
-
75
- tip.push(ref)
76
- }
77
-
78
- if (!found) return null
79
-
80
- const inflated = await inflate(auto, tip.reverse())
81
-
82
- // @todo: verify sort
83
-
84
- return {
85
- undo: null,
86
- view: null,
87
- tip: inflated.map(getBatch)
88
- }
89
- }
90
-
91
51
  // nodes we cannot resolve are returned as null rather than dropped, so callers
92
52
  // can tell an incomplete replay from a short one
93
53
  async function replay(auto) {
@@ -108,14 +68,6 @@ async function replay(auto) {
108
68
  return inflated.flatMap((b) => (b === null ? [null] : b.batch))
109
69
  }
110
70
 
111
- async function isApplied(system, node) {
112
- const entry = await system.get(encoding.encodeSystemWriterKey(node.key))
113
- if (entry === null) return false
114
-
115
- const info = encoding.decodeSystemWriter(entry.key, entry.value)
116
- return info.length >= node.length
117
- }
118
-
119
71
  async function sort(auto, batch) {
120
72
  const node = batch[0]
121
73
  const ch = auto.system.bee.createChangesStream()
@@ -124,7 +76,7 @@ async function sort(auto, batch) {
124
76
  for await (const data of ch) {
125
77
  if (isLegacyNode(data)) break
126
78
 
127
- const { ref, info } = getOplog(data)
79
+ const { ref } = getOplog(data)
128
80
  const link = {
129
81
  key: ref.oplog.key,
130
82
  length: ref.oplog.length,
@@ -275,8 +227,20 @@ async function getOplogBatch(undo, core, length, weight, index, opts = null) {
275
227
  const seq = length - 1
276
228
  const block = await core.get(seq, opts)
277
229
  if (block === null) return null
230
+
278
231
  const oplog = encoding.decodeOplog(block)
279
232
  const head = createNode(core, length, weight, oplog)
233
+ const legacy = oplog.version <= LEGACY_OPLOG_VERSION
234
+
235
+ // if this is legacy the start pointer is always 0, fix
236
+ if (legacy) {
237
+ for (let i = length - 1; i > 0; i--) {
238
+ const block = await core.get(i - 1, opts)
239
+ const oplog = encoding.decodeOplog(block)
240
+ if (oplog.batch.end === 0) break
241
+ head.batch.start++
242
+ }
243
+ }
280
244
 
281
245
  const batch = []
282
246
  const result = { undo, batch, index }
@@ -304,6 +268,8 @@ async function getOplogBatch(undo, core, length, weight, index, opts = null) {
304
268
  }
305
269
 
306
270
  batch.push(head)
271
+ finalizeNodeBatch(batch)
272
+
307
273
  return result
308
274
  }
309
275
 
@@ -433,11 +399,3 @@ function isLegacyNode(data) {
433
399
  }
434
400
  return false
435
401
  }
436
-
437
- function sameNode(a, b) {
438
- return b4a.equals(a.key, b.key) && a.length === b.length
439
- }
440
-
441
- function getBatch(b) {
442
- return b.batch
443
- }
package/lib/writers.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const b4a = require('b4a')
2
2
  const encoding = require('./encoding.js')
3
3
  const Triggers = require('./triggers.js')
4
+ const { LEGACY_OPLOG_VERSION } = require('./constants.js')
4
5
  const { signAttestation, resolveWeight } = require('./witness.js')
5
6
  const { WriterEncryption } = require('autobee-encryption')
6
7
 
@@ -62,9 +63,6 @@ class ActiveWriters {
62
63
 
63
64
  let added = false
64
65
  if (!w) {
65
- // anchors are local-only and never replicated - nothing to fetch
66
- const info = await this.auto.system.get(key, { unflushed: true })
67
- if (info && info.isAnchor) return
68
66
  w = await this.add(key)
69
67
  if (!w) return
70
68
  added = true
@@ -453,7 +451,9 @@ class Writer {
453
451
  async update() {
454
452
  const info = await this.system.get(this.core.key)
455
453
 
456
- if (info && info.isAnchor && this !== this.writers.localWriter) {
454
+ // an anchor core is prologue-only, so once its single node has applied
455
+ // nothing more can ever come - retire the writer
456
+ if (info && info.isAnchor && info.length > 0 && this !== this.writers.localWriter) {
457
457
  await this.detachAndClose()
458
458
  return info.length
459
459
  }
@@ -572,7 +572,7 @@ class Writer {
572
572
  }
573
573
 
574
574
  batch.push(node)
575
- if (b.end === 0) return batch
575
+ if (b.end === 0) return finalizeNodeBatch(batch)
576
576
 
577
577
  // TODO: mark as dead
578
578
  if (batch.length >= BATCH_WRITER_SANITY) return null
@@ -763,6 +763,7 @@ function sortsBefore(a, b) {
763
763
  exports.ActiveWriters = ActiveWriters
764
764
  exports.Writer = Writer
765
765
  exports.createNode = createNode
766
+ exports.finalizeNodeBatch = finalizeNodeBatch
766
767
 
767
768
  function createNode(core, length, weight, oplog) {
768
769
  return {
@@ -784,4 +785,14 @@ function createNode(core, length, weight, oplog) {
784
785
  }
785
786
  }
786
787
 
788
+ function finalizeNodeBatch(batch) {
789
+ if (batch.length === 0 || batch[0].version > LEGACY_OPLOG_VERSION) return batch
790
+
791
+ for (let i = 0; i < batch.length; i++) {
792
+ batch[i].batch.start = i
793
+ }
794
+
795
+ return batch
796
+ }
797
+
787
798
  function noop() {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.0.0-rc.26",
3
+ "version": "2.0.0-rc.28",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",