autobee 2.0.0 → 2.1.0

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
@@ -51,11 +51,13 @@ module.exports = class Autobee extends ReadyResource {
51
51
  this.getSystemEncryption = this._getEncryptionProvider.bind(this, '_system')
52
52
  this.getViewEncryption = this._getEncryptionProvider.bind(this, viewName)
53
53
 
54
- const bee = new Hyperbee(store.namespace('view'), {
54
+ const beeStore = store.session()
55
+ const bee = new Hyperbee(beeStore, {
56
+ core: beeStore.get({ preload: this._getCorePreload(viewName) }),
55
57
  // defer one tick to ensure consistent state, then return state prom
56
58
  preload: async () => {
57
59
  await 1
58
- await this._bootReady()
60
+ await this._bootGuard.ready()
59
61
  },
60
62
  getEncryptionProvider: this.getViewEncryption
61
63
  })
@@ -69,11 +71,12 @@ module.exports = class Autobee extends ReadyResource {
69
71
  this._handlers = handlers
70
72
  this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }
71
73
 
72
- this.system = new System(this.store.namespace('system'), this.name, {
73
- getEncryptionProvider: this.getSystemEncryption,
74
- encrypted: this.encrypted
74
+ const systemStore = this.store.session()
75
+ this.system = new System(systemStore, this.name, {
76
+ core: systemStore.get({ preload: this._getCorePreload('system') }),
77
+ encrypted: this.encrypted,
78
+ getEncryptionProvider: this.getSystemEncryption
75
79
  })
76
- this.system.auto = this
77
80
 
78
81
  this.bee = bee.snapshot()
79
82
  this.view = ApplyView.open(this.bee, this)
@@ -120,9 +123,11 @@ module.exports = class Autobee extends ReadyResource {
120
123
 
121
124
  this.legacyViews = handlers.legacyViews || []
122
125
 
126
+ // the guards may be destroyed before anyone waits on them
123
127
  this._bootGuard = new ReadyGuard()
124
- this._bootingState = null
125
- this._bootingAll = null
128
+ this._bootGuard.ready().catch(safetyCatch)
129
+ this._bootOnlineGuard = new ReadyGuard()
130
+ this._bootOnlineGuard.ready().catch(safetyCatch)
126
131
 
127
132
  this._now = handlers.now || Date.now // overridable for clock-drift tests
128
133
  this._preapply = handlers.preapply || null
@@ -132,7 +137,6 @@ module.exports = class Autobee extends ReadyResource {
132
137
  this._needsUpdate = false
133
138
  this._approvalCheck = true
134
139
  this._approvedPending = new Map()
135
- this._approvalRequests = []
136
140
  this._prefetchingApprovals = false
137
141
  this._updateLocalCore = null
138
142
  this._host = new ApplyCalls(this)
@@ -150,6 +154,7 @@ module.exports = class Autobee extends ReadyResource {
150
154
 
151
155
  this._catchupMigratedNodes = null
152
156
  this._migratedHead = null
157
+ this._prebooting = null
153
158
 
154
159
  this.ready().catch(noop)
155
160
  }
@@ -181,24 +186,56 @@ module.exports = class Autobee extends ReadyResource {
181
186
  return !!(this._draining || this._updating)
182
187
  }
183
188
 
189
+ async _getCorePreload(name) {
190
+ await 1
191
+ const result = await this._prebooting
192
+ return {
193
+ name: 'autobee/' + result.local.id + '/' + name,
194
+ encryption: name === 'system' ? this.getSystemEncryption() : this.getViewEncryption(),
195
+ inflightRange: [256, 512]
196
+ }
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
184
202
  async _open() {
185
- await this._preBoot()
203
+ this._prebooting = this._preBoot()
186
204
 
187
- this._bootingState = this._bootState()
188
- this._bootingAll = this._bootAll()
205
+ let result = null
189
206
 
190
- this._bootingState.catch(safetyCatch)
191
- this._bootingAll.catch(safetyCatch)
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
+ }
192
221
 
193
- await this.bee.ready()
194
- await this._workingBee.ready()
195
- await this._bootingState
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
+ }
196
230
 
197
- this._localSystemStart = this.system.bee.context.local.length
198
- this._localViewStart = this._workingBee.context.local.length
199
- this._localFlushes = this.system.flushes
231
+ const booting = this._boot()
200
232
 
201
- this.bumpSoon()
233
+ // waits on the boot guard internally and destroys its own guard on failure
234
+ this._bootOnline()
235
+
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
202
239
  }
203
240
 
204
241
  _requestWakeup() {
@@ -250,43 +287,40 @@ module.exports = class Autobee extends ReadyResource {
250
287
 
251
288
  async _close() {
252
289
  this._interrupting = true
253
- Hypercore.destroyRequests(this._approvalRequests, null)
254
- if (this._bootWait !== null) this._bootWait.resolve()
255
- if (this._notifyHandler) this._notifyHandler.destroy()
256
- if (this._draining) {
257
- // drain may be waiting on replicated blocks
258
- this._clearRequests()
259
- await this._draining
260
- }
261
-
262
- if (this._updating) await this._updating
263
290
 
264
- // let in-flight writer adds finish
265
291
  try {
266
- await this._bootingAll
267
- } catch {}
268
-
269
- await ApplyView.close(this.view, this)
292
+ await ApplyView.close(this.view, this)
293
+ } catch (err) {
294
+ safetyCatch(err)
295
+ }
270
296
 
271
297
  if (this.writers) await this.writers.close()
272
298
  await this.local.close()
273
299
  await this.system.close()
274
300
  await this._wakeup.close()
275
- if (this.bootstrap && !this.bootstrap.closed) await this.bootstrap.close()
276
- await this._workingView.close()
277
- await this.bee.close()
278
- await this.store.close()
279
- }
301
+ if (this.bootstrap) await this.bootstrap.close()
280
302
 
281
- _clearRequests() {
282
- const closingError = new Error('Autobee is closing')
283
- for (const session of this.store.sessions) {
284
- if (session.opened && !session.closing) {
285
- session.clearRequests(session.activeRequests, closingError)
286
- }
303
+ try {
304
+ await this._workingView.close()
305
+ await this.bee.close()
306
+ } catch (err) {
307
+ safetyCatch(err)
287
308
  }
288
309
 
289
- if (this.fastForwarding) this.fastForwarding.clearRequests(closingError)
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 {}
290
324
  }
291
325
 
292
326
  replicate(...args) {
@@ -296,7 +330,7 @@ module.exports = class Autobee extends ReadyResource {
296
330
  }
297
331
 
298
332
  async flush() {
299
- await this._bootingAll
333
+ await this._bootOnlineGuard.ready()
300
334
  }
301
335
 
302
336
  hintWakeup(wakeup) {
@@ -322,6 +356,10 @@ module.exports = class Autobee extends ReadyResource {
322
356
  return new WriterEncryption(this)
323
357
  }
324
358
 
359
+ getMostRecentHead() {
360
+ return topo.getMostRecentHead(this, this.system.bee.snapshot())
361
+ }
362
+
325
363
  async _preBoot() {
326
364
  if (this._handlers.wait) await this._handlers.wait()
327
365
 
@@ -338,17 +376,18 @@ module.exports = class Autobee extends ReadyResource {
338
376
  if (this.bootFrom) {
339
377
  this.bootFrom = getBootOption(await this.bootFrom)
340
378
  }
341
- }
342
379
 
343
- getMostRecentHead() {
344
- return topo.getMostRecentHead(this, this.system.bee.snapshot())
380
+ return boot(this.store, this.key, this.legacyViews, {
381
+ encryptionKey: this.encryptionKey,
382
+ keyPair: this.keyPair
383
+ })
345
384
  }
346
385
 
347
- async _bootState() {
386
+ async _boot() {
348
387
  if (!this._bootGuard.enter()) return this._bootGuard.ready()
349
388
 
350
389
  try {
351
- await this._bootStateUnsafe()
390
+ await this._bootUnsafe()
352
391
  } catch (err) {
353
392
  this._bootGuard.destroy(err)
354
393
  throw err
@@ -356,40 +395,13 @@ module.exports = class Autobee extends ReadyResource {
356
395
 
357
396
  this._bootGuard.exit()
358
397
 
359
- return this._bootGuard.ready()
360
- }
398
+ this.bumpSoon()
361
399
 
362
- async _bootReady() {
363
- if (this._bootGuard.opened) return true
364
- try {
365
- await this._bootGuard.ready()
366
- return true
367
- } catch {
368
- return false
369
- }
400
+ return this._bootGuard.ready()
370
401
  }
371
402
 
372
- async _bootStateUnsafe() {
373
- const result = await boot(this.store, this.key, this.legacyViews, {
374
- encryptionKey: this.encryptionKey,
375
- keyPair: this.keyPair
376
- })
377
-
378
- this.key = result.key
379
- this.bootstrap = result.bootstrap
380
- this.discoveryKey = result.bootstrap.core.discoveryKey
381
- this.id = result.bootstrap.core.id
382
- this.encryptionKey = result.encryptionKey
383
- this.previousDrain = result.previousDrain
384
- this.local = result.local
385
-
386
- if (this.encrypted && this.encryptionKey === null) {
387
- throw new Error('Encryption key is expected')
388
- }
389
-
390
- this.local.setEncryption(this._getEncryptionProvider())
391
-
392
- this.writers = new ActiveWriters(this)
403
+ async _bootUnsafe() {
404
+ const result = await this._prebooting
393
405
 
394
406
  if (this._handlers.wakeupCapability) {
395
407
  this.wakeupCapability = await this._handlers.wakeupCapability
@@ -458,31 +470,53 @@ module.exports = class Autobee extends ReadyResource {
458
470
  this.bee.move(view)
459
471
 
460
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
461
482
  }
462
483
 
463
- async _bootAll() {
464
- if (!(await this._bootReady())) return
484
+ async _bootOnline() {
485
+ if (!this._bootOnlineGuard.enter()) return
465
486
 
466
- for (const head of this.system.heads) {
467
- await this.writers.add(head.key)
468
- }
487
+ try {
488
+ if (!this._bootGuard.opened) await this._bootGuard.ready()
489
+
490
+ for (const head of this.system.heads) {
491
+ await this.writers.add(head.key)
492
+ }
493
+
494
+ if (!this.system.heads.length) {
495
+ await this.writers.add(this.bootstrap.key)
496
+ }
469
497
 
470
- if (!this.system.heads.length) {
471
- await this.writers.add(this.bootstrap.key)
498
+ await this._bump(true)
499
+ } catch (err) {
500
+ this._bootOnlineGuard.destroy(err)
501
+ return
472
502
  }
473
503
 
474
- await this._bump()
504
+ this._bootOnlineGuard.exit()
475
505
  }
476
506
 
477
507
  bumpSoon() {
478
- this._bump().catch(safetyCatch)
508
+ this._bump(false).catch(safetyCatch)
479
509
  }
480
510
 
481
- async _bump() {
482
- if (!(await this._bootReady())) return
511
+ async _bump(force) {
512
+ if (!force && !this._bootGuard.opened) await this._bootGuard.ready()
483
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
484
516
  if (this._bootWait !== null) this._bootWait.resolve()
485
517
 
518
+ if (!force && !this._bootOnlineGuard.opened) await this._bootOnlineGuard.ready()
519
+
486
520
  this.bumping++
487
521
 
488
522
  if (!this._draining) {
@@ -493,11 +527,11 @@ module.exports = class Autobee extends ReadyResource {
493
527
  }
494
528
 
495
529
  update() {
496
- return this._bump()
530
+ return this._bump(false)
497
531
  }
498
532
 
499
533
  async updated() {
500
- if (this.opened === false) await this.ready()
534
+ if (!this._bootGuard.opened) await this._bootGuard.ready()
501
535
  if (this._draining) return this._draining
502
536
  return Promise.resolve()
503
537
  }
@@ -796,7 +830,7 @@ module.exports = class Autobee extends ReadyResource {
796
830
  }
797
831
 
798
832
  async setLocal(key, { keyPair } = {}) {
799
- if (!this.opened) await this.ready()
833
+ if (!this._bootGuard.opened) await this._bootGuard.ready()
800
834
  if (this.closing) throw new Error('Autobee closed')
801
835
 
802
836
  const manifest = keyPair
@@ -925,15 +959,13 @@ module.exports = class Autobee extends ReadyResource {
925
959
  if (this._prefetchingApprovals) return
926
960
  this._prefetchingApprovals = true
927
961
 
928
- const activeRequests = this._approvalRequests
929
-
930
962
  try {
931
963
  const prefetch = []
932
- const standing = await this._standing(activeRequests)
964
+ const standing = await this._standing()
933
965
  if (standing <= 0 || !this._pendingWork(standing)) return
934
- for await (const p of this._servableRequests(standing, activeRequests)) {
935
- prefetch.push(this.system.get(p.key, { activeRequests }).catch(safetyCatch))
936
- prefetch.push(this.system.grantHint(p.key, { activeRequests }).catch(safetyCatch))
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))
937
969
  }
938
970
  await Promise.allSettled(prefetch)
939
971
  } finally {
@@ -941,8 +973,8 @@ module.exports = class Autobee extends ReadyResource {
941
973
  }
942
974
  }
943
975
 
944
- async _standing(activeRequests) {
945
- const rec = await this.system.get(this.local.key, { activeRequests })
976
+ async _standing() {
977
+ const rec = await this.system.get(this.local.key)
946
978
  return currentWeight(rec)
947
979
  }
948
980
 
@@ -960,8 +992,8 @@ module.exports = class Autobee extends ReadyResource {
960
992
 
961
993
  // the requests we could serve and by how much - shared by the prefetch and
962
994
  // the collector so the two cannot drift on what counts as ours
963
- async *_servableRequests(standing, activeRequests) {
964
- for await (const p of this.system.listPendingPromotions({ activeRequests })) {
995
+ async *_servableRequests(standing) {
996
+ for await (const p of this.system.listPendingPromotions()) {
965
997
  const amount = Math.min(standing, p.weight)
966
998
  if (amount <= 0) continue
967
999
  yield { key: p.key, amount }
@@ -972,8 +1004,7 @@ module.exports = class Autobee extends ReadyResource {
972
1004
  if (!this.system.promotions.changed && !this._approvalCheck) return null
973
1005
  if (!this.writers.writable) return null
974
1006
 
975
- const activeRequests = this._approvalRequests
976
- const standing = await this._standing(activeRequests)
1007
+ const standing = await this._standing()
977
1008
  if (standing <= 0) return null
978
1009
 
979
1010
  this.system.promotions.changed = false
@@ -984,7 +1015,7 @@ module.exports = class Autobee extends ReadyResource {
984
1015
  const approvals = []
985
1016
  const approving = []
986
1017
 
987
- for await (const p of this._servableRequests(standing, activeRequests)) {
1018
+ for await (const p of this._servableRequests(standing)) {
988
1019
  approving.push(fetchApproval.call(this, p.key, p.amount, approvals))
989
1020
  }
990
1021
 
@@ -1008,10 +1039,7 @@ module.exports = class Autobee extends ReadyResource {
1008
1039
  async function fetchApproval(key, weight, approvals) {
1009
1040
  const hex = b4a.toString(key, 'hex')
1010
1041
  if ((this._approvedPending.get(hex) || 0) >= weight) return
1011
- const [target, hint] = await Promise.all([
1012
- this.system.get(key, { activeRequests }),
1013
- this.system.grantHint(key, { activeRequests })
1014
- ])
1042
+ const [target, hint] = await Promise.all([this.system.get(key), this.system.grantHint(key)])
1015
1043
  if (!target || target.isRemoved) return
1016
1044
  if (hint && hint.weight >= weight) return
1017
1045
  approvals.push({ key, weight })
@@ -1209,10 +1237,12 @@ module.exports = class Autobee extends ReadyResource {
1209
1237
  return encoding.encodeValue(value, opts)
1210
1238
  }
1211
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
1212
1242
  async wakeup({ key, length }) {
1213
- if (!(await this._bootReady())) return
1243
+ if (!this._bootGuard.opened) await this._bootGuard.ready()
1214
1244
  await this.writers.wakeup(key, length)
1215
- await this._bump()
1245
+ await this._bump(false)
1216
1246
  }
1217
1247
 
1218
1248
  async append(values, { optimistic = false } = {}) {
@@ -1222,7 +1252,7 @@ module.exports = class Autobee extends ReadyResource {
1222
1252
 
1223
1253
  if (!Array.isArray(values)) values = [values]
1224
1254
 
1225
- if (!this.opened) await this.ready()
1255
+ if (!this._bootGuard.opened) await this._bootGuard.ready()
1226
1256
 
1227
1257
  if (!optimistic && this.writers.localWriter.isRemoved) {
1228
1258
  throw new Error('Not writable')
@@ -1263,7 +1293,7 @@ module.exports = class Autobee extends ReadyResource {
1263
1293
  batch.push(node)
1264
1294
  }
1265
1295
 
1266
- return this._bump()
1296
+ return this._bump(false)
1267
1297
  }
1268
1298
 
1269
1299
  async _flushLocal() {
@@ -1294,7 +1324,7 @@ module.exports = class Autobee extends ReadyResource {
1294
1324
  }
1295
1325
 
1296
1326
  async moveTo(head) {
1297
- if (!this.opened) await this.ready()
1327
+ if (!this._bootGuard.opened) await this._bootGuard.ready()
1298
1328
  if (this.closing) throw new Error('Autobee closed')
1299
1329
 
1300
1330
  const ff = await FastForward.fromHead(this, head, null, { force: true })
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 { ref } = getOplog(data)
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
- result.ref = { undo: data.tail || EMPTY_HEAD, oplog: value }
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",