autobee 2.0.0-rc.9 → 2.0.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/README.md +48 -1
- package/encoding/legacy.js +30 -5
- package/encoding/spec/autobee/index.js +289 -10
- package/encoding/spec/autobee/schema.json +217 -1
- package/index.js +548 -321
- package/lib/apply-calls.js +5 -6
- package/lib/apply-view.js +25 -0
- package/lib/boot.js +14 -182
- package/lib/constants.js +3 -6
- package/lib/encoding.js +72 -3
- package/lib/fast-forward.js +346 -0
- package/lib/migrations.js +457 -0
- package/lib/promotions.js +164 -0
- package/lib/system.js +97 -179
- package/lib/topo.js +83 -88
- package/lib/trusted.js +44 -0
- package/lib/updates.js +1 -38
- package/lib/witness.js +34 -65
- package/lib/writers.js +180 -116
- package/package.json +4 -3
- package/lib/reboot.js +0 -238
package/index.js
CHANGED
|
@@ -14,16 +14,19 @@ 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
|
|
17
|
+
const { LEGACY_OPLOG_VERSION } = require('./lib/constants.js')
|
|
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')
|
|
20
21
|
const topo = require('./lib/topo.js')
|
|
21
22
|
const { ActiveWriters } = require('./lib/writers.js')
|
|
23
|
+
const TrustedPeers = require('./lib/trusted.js')
|
|
24
|
+
const ApplyView = require('./lib/apply-view.js')
|
|
22
25
|
const UpdateChanges = require('./lib/updates.js')
|
|
26
|
+
const migrations = require('./lib/migrations.js')
|
|
23
27
|
|
|
24
28
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
25
29
|
const INTERRUPT = new Error('Apply interrupted')
|
|
26
|
-
const MIN_FF_GAP = 32
|
|
27
30
|
|
|
28
31
|
module.exports = class Autobee extends ReadyResource {
|
|
29
32
|
constructor(store, key = null, handlers = {}) {
|
|
@@ -34,9 +37,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
34
37
|
key = null
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
const {
|
|
40
|
+
const {
|
|
41
|
+
name = null,
|
|
42
|
+
encrypted,
|
|
43
|
+
encryptionKey,
|
|
44
|
+
viewName = 'view',
|
|
45
|
+
bootstrapWeight = 2
|
|
46
|
+
} = handlers
|
|
38
47
|
|
|
39
48
|
this.encrypted = encrypted === true || !!encryptionKey
|
|
49
|
+
this.bootstrapWeight = bootstrapWeight
|
|
40
50
|
|
|
41
51
|
this.getSystemEncryption = this._getEncryptionProvider.bind(this, '_system')
|
|
42
52
|
this.getViewEncryption = this._getEncryptionProvider.bind(this, viewName)
|
|
@@ -56,7 +66,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
56
66
|
this.discoveryKey = null
|
|
57
67
|
this.id = null
|
|
58
68
|
this.bootstrap = null
|
|
59
|
-
this.
|
|
69
|
+
this._handlers = handlers
|
|
70
|
+
this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }
|
|
60
71
|
|
|
61
72
|
this.system = new System(this.store.namespace('system'), this.name, {
|
|
62
73
|
getEncryptionProvider: this.getSystemEncryption,
|
|
@@ -65,7 +76,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
65
76
|
this.system.auto = this
|
|
66
77
|
|
|
67
78
|
this.bee = bee.snapshot()
|
|
68
|
-
this.view =
|
|
79
|
+
this.view = ApplyView.open(this.bee, this)
|
|
69
80
|
this.optimistic = handlers.optimistic !== false // TODO: should default to false instead
|
|
70
81
|
|
|
71
82
|
this.name = name // for debugging
|
|
@@ -76,23 +87,36 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
76
87
|
this.writers = null
|
|
77
88
|
this.bumping = 0
|
|
78
89
|
|
|
79
|
-
//
|
|
80
|
-
|
|
90
|
+
// fastForward: false disables all fast-forwards - both wakeup and boot
|
|
91
|
+
const fastForward = handlers.fastForward === false ? null : handlers.fastForward || {}
|
|
92
|
+
|
|
93
|
+
this._ffEnabled = fastForward !== null
|
|
94
|
+
|
|
95
|
+
// oplog head to boot from: migrates or fast-forwards depending on its version
|
|
96
|
+
this.bootFrom = (fastForward && fastForward.boot) || null
|
|
97
|
+
|
|
98
|
+
// conservative (default on): only fast-forward onto a head someone can serve whole
|
|
99
|
+
this._conservativeFF = !fastForward || fastForward.conservative !== false
|
|
81
100
|
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
this.
|
|
101
|
+
this.trusted = new TrustedPeers(handlers)
|
|
102
|
+
|
|
103
|
+
this.ff = null
|
|
104
|
+
this.fastForwarding = null
|
|
105
|
+
this.fastForwardTo = null
|
|
85
106
|
|
|
86
107
|
this._workingBee = bee
|
|
87
|
-
this._workingView =
|
|
108
|
+
this._workingView = new ApplyView(this._workingBee, this)
|
|
88
109
|
|
|
89
110
|
this._localSystemStart = 0
|
|
90
111
|
this._localSystemLength = 0
|
|
112
|
+
this._localFlushes = 0
|
|
91
113
|
this._localViewStart = 0
|
|
92
114
|
this._localViewLength = 0
|
|
93
115
|
|
|
94
116
|
this._appending = []
|
|
95
117
|
this._draining = null
|
|
118
|
+
this._updating = null
|
|
119
|
+
this._bootWait = null
|
|
96
120
|
|
|
97
121
|
this.legacyViews = handlers.legacyViews || []
|
|
98
122
|
|
|
@@ -100,13 +124,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
100
124
|
this._bootingState = null
|
|
101
125
|
this._bootingAll = null
|
|
102
126
|
|
|
103
|
-
this._handlers = handlers
|
|
104
127
|
this._now = handlers.now || Date.now // overridable for clock-drift tests
|
|
128
|
+
this._preapply = handlers.preapply || null
|
|
129
|
+
this._preApplied = false
|
|
105
130
|
this._hasApply = !!handlers.apply
|
|
106
131
|
this._hasUpdate = !!handlers.update
|
|
107
132
|
this._needsUpdate = false
|
|
108
|
-
this.
|
|
109
|
-
this.
|
|
133
|
+
this._approvalCheck = true
|
|
134
|
+
this._approvedPending = new Map()
|
|
135
|
+
this._approvalRequests = []
|
|
136
|
+
this._prefetchingApprovals = false
|
|
110
137
|
this._updateLocalCore = null
|
|
111
138
|
this._host = new ApplyCalls(this)
|
|
112
139
|
this._notifyHandler = null
|
|
@@ -122,6 +149,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
122
149
|
this.previousDrain = 0
|
|
123
150
|
|
|
124
151
|
this._catchupMigratedNodes = null
|
|
152
|
+
this._migratedHead = null
|
|
125
153
|
|
|
126
154
|
this.ready().catch(noop)
|
|
127
155
|
}
|
|
@@ -149,6 +177,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
149
177
|
return this.system.flushes
|
|
150
178
|
}
|
|
151
179
|
|
|
180
|
+
get busy() {
|
|
181
|
+
return !!(this._draining || this._updating)
|
|
182
|
+
}
|
|
183
|
+
|
|
152
184
|
async _open() {
|
|
153
185
|
await this._preBoot()
|
|
154
186
|
|
|
@@ -159,14 +191,21 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
159
191
|
this._bootingAll.catch(safetyCatch)
|
|
160
192
|
|
|
161
193
|
await this.bee.ready()
|
|
194
|
+
await this._workingBee.ready()
|
|
162
195
|
await this._bootingState
|
|
163
196
|
|
|
164
197
|
this._localSystemStart = this.system.bee.context.local.length
|
|
165
198
|
this._localViewStart = this._workingBee.context.local.length
|
|
199
|
+
this._localFlushes = this.system.flushes
|
|
166
200
|
|
|
167
201
|
this.bumpSoon()
|
|
168
202
|
}
|
|
169
203
|
|
|
204
|
+
_requestWakeup() {
|
|
205
|
+
const session = this._wakeup._session
|
|
206
|
+
if (session) session.broadcastLookup()
|
|
207
|
+
}
|
|
208
|
+
|
|
170
209
|
_registerWakeup() {
|
|
171
210
|
if (!this.wakeupCapability) return
|
|
172
211
|
this._wakeup.recouple()
|
|
@@ -211,6 +250,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
211
250
|
|
|
212
251
|
async _close() {
|
|
213
252
|
this._interrupting = true
|
|
253
|
+
Hypercore.destroyRequests(this._approvalRequests, null)
|
|
254
|
+
if (this._bootWait !== null) this._bootWait.resolve()
|
|
214
255
|
if (this._notifyHandler) this._notifyHandler.destroy()
|
|
215
256
|
if (this._draining) {
|
|
216
257
|
// drain may be waiting on replicated blocks
|
|
@@ -218,19 +259,21 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
218
259
|
await this._draining
|
|
219
260
|
}
|
|
220
261
|
|
|
262
|
+
if (this._updating) await this._updating
|
|
263
|
+
|
|
221
264
|
// let in-flight writer adds finish
|
|
222
265
|
try {
|
|
223
266
|
await this._bootingAll
|
|
224
267
|
} catch {}
|
|
225
268
|
|
|
226
|
-
|
|
269
|
+
await ApplyView.close(this.view, this)
|
|
227
270
|
|
|
228
271
|
if (this.writers) await this.writers.close()
|
|
229
272
|
await this.local.close()
|
|
230
273
|
await this.system.close()
|
|
231
274
|
await this._wakeup.close()
|
|
232
275
|
if (this.bootstrap && !this.bootstrap.closed) await this.bootstrap.close()
|
|
233
|
-
await this.
|
|
276
|
+
await this._workingView.close()
|
|
234
277
|
await this.bee.close()
|
|
235
278
|
await this.store.close()
|
|
236
279
|
}
|
|
@@ -242,25 +285,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
242
285
|
session.clearRequests(session.activeRequests, closingError)
|
|
243
286
|
}
|
|
244
287
|
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async changesFrom({ system } = {}) {
|
|
248
|
-
const info = await this.system.getInfo()
|
|
249
|
-
|
|
250
|
-
const current = {
|
|
251
|
-
flushes: info ? info.flushes : 0,
|
|
252
|
-
system: this.system.bee.head(),
|
|
253
|
-
view: info ? info.view : EMPTY_HEAD
|
|
254
|
-
}
|
|
255
288
|
|
|
256
|
-
|
|
257
|
-
const shared = (await this.system.commonAncestor(system || EMPTY_HEAD)) || {
|
|
258
|
-
flushes: -1,
|
|
259
|
-
system: EMPTY_HEAD,
|
|
260
|
-
view: EMPTY_HEAD
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return UpdateChanges.from(shared, current)
|
|
289
|
+
if (this.fastForwarding) this.fastForwarding.clearRequests(closingError)
|
|
264
290
|
}
|
|
265
291
|
|
|
266
292
|
replicate(...args) {
|
|
@@ -277,6 +303,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
277
303
|
this._wakeup.hint(wakeup)
|
|
278
304
|
}
|
|
279
305
|
|
|
306
|
+
// the view an oplog head points at, opened and closed through the handlers
|
|
307
|
+
openViewAt(oplog) {
|
|
308
|
+
const v = oplog.op.views.view
|
|
309
|
+
if (!v) return null
|
|
310
|
+
|
|
311
|
+
return new ApplyView(this.bee.checkout({ key: v.key, length: v.start + v.length }), this)
|
|
312
|
+
}
|
|
313
|
+
|
|
280
314
|
openCore(key) {
|
|
281
315
|
const encryption = this.encryptionKey ? new WriterEncryption(this) : null
|
|
282
316
|
return this.store.get({ key, encryption })
|
|
@@ -302,7 +336,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
302
336
|
}
|
|
303
337
|
|
|
304
338
|
if (this.bootFrom) {
|
|
305
|
-
this.bootFrom = (await this.bootFrom)
|
|
339
|
+
this.bootFrom = getBootOption(await this.bootFrom)
|
|
306
340
|
}
|
|
307
341
|
}
|
|
308
342
|
|
|
@@ -354,7 +388,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
354
388
|
}
|
|
355
389
|
|
|
356
390
|
this.local.setEncryption(this._getEncryptionProvider())
|
|
357
|
-
this.local.setActive(true)
|
|
358
391
|
|
|
359
392
|
this.writers = new ActiveWriters(this)
|
|
360
393
|
|
|
@@ -380,15 +413,26 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
380
413
|
|
|
381
414
|
await this.system.boot(system)
|
|
382
415
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
416
|
+
const migrated = await this.local.getUserData('autobee/migrated-head')
|
|
417
|
+
if (migrated) this._migratedHead = encoding.decodeMigratedHead(migrated)
|
|
418
|
+
|
|
419
|
+
let view = this.system.view
|
|
420
|
+
if (!view) view = (this._migratedHead && this._migratedHead.view) || EMPTY_HEAD
|
|
386
421
|
|
|
387
422
|
// @todo migration
|
|
388
423
|
if (result.migration) {
|
|
389
|
-
if (this.
|
|
390
|
-
view =
|
|
424
|
+
if (this._handlers.migrate) {
|
|
425
|
+
view =
|
|
426
|
+
(await this._handlers.migrate(result.migration.views, result.migration.system)) ||
|
|
427
|
+
EMPTY_HEAD
|
|
391
428
|
this._catchupMigratedNodes = result.migration.catchup
|
|
429
|
+
|
|
430
|
+
this._migratedHead = {
|
|
431
|
+
system: result.migration.system,
|
|
432
|
+
view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
await this._storeMigratedHead()
|
|
392
436
|
}
|
|
393
437
|
|
|
394
438
|
// ff boot invalidated by migration
|
|
@@ -405,6 +449,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
405
449
|
}
|
|
406
450
|
}
|
|
407
451
|
|
|
452
|
+
// since we are doing this DURING the hyperbee2 boot we need to ready the core
|
|
453
|
+
// be good if we had better plumbing for it
|
|
454
|
+
await this._workingBee.core.ready()
|
|
455
|
+
await this.bee.core.ready()
|
|
456
|
+
|
|
408
457
|
this._workingBee.move(view)
|
|
409
458
|
this.bee.move(view)
|
|
410
459
|
|
|
@@ -414,10 +463,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
414
463
|
async _bootAll() {
|
|
415
464
|
if (!(await this._bootReady())) return
|
|
416
465
|
|
|
417
|
-
for
|
|
418
|
-
|
|
419
|
-
|
|
466
|
+
for (const head of this.system.heads) {
|
|
467
|
+
await this.writers.add(head.key)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!this.system.heads.length) {
|
|
471
|
+
await this.writers.add(this.bootstrap.key)
|
|
420
472
|
}
|
|
473
|
+
|
|
421
474
|
await this._bump()
|
|
422
475
|
}
|
|
423
476
|
|
|
@@ -428,7 +481,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
428
481
|
async _bump() {
|
|
429
482
|
if (!(await this._bootReady())) return
|
|
430
483
|
|
|
431
|
-
|
|
484
|
+
if (this._bootWait !== null) this._bootWait.resolve()
|
|
432
485
|
|
|
433
486
|
this.bumping++
|
|
434
487
|
|
|
@@ -483,18 +536,30 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
483
536
|
this.emit('error', err)
|
|
484
537
|
}
|
|
485
538
|
|
|
539
|
+
// one-shot user gate: nothing applies until the host has resolved whatever
|
|
540
|
+
// state apply depends on (e.g. legacy views recorded by a migration)
|
|
541
|
+
async _runPreApply() {
|
|
542
|
+
if (this._preapply === null || this._preApplied) return
|
|
543
|
+
|
|
544
|
+
this._preApplied = true
|
|
545
|
+
await this._preapply(this.view)
|
|
546
|
+
}
|
|
547
|
+
|
|
486
548
|
async _drain() {
|
|
487
549
|
if (this._updating) await this._updating
|
|
488
550
|
|
|
551
|
+
await this._runPreApply()
|
|
552
|
+
|
|
553
|
+
this.stats.drains++
|
|
554
|
+
|
|
489
555
|
if (this.bootFrom) {
|
|
490
|
-
|
|
491
|
-
this.bootFrom = null
|
|
492
|
-
}
|
|
556
|
+
const { head = null, legacy = null, bootCondition = null, wait = false } = this.bootFrom
|
|
493
557
|
|
|
494
|
-
|
|
495
|
-
this.system.shared = null
|
|
558
|
+
this.bootFrom = null
|
|
496
559
|
|
|
497
|
-
|
|
560
|
+
if (legacy) await this._bootFromSystem(legacy)
|
|
561
|
+
else if (head) await this._bootFromHead(head, bootCondition, wait)
|
|
562
|
+
}
|
|
498
563
|
|
|
499
564
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
500
565
|
if (changes) changes.track()
|
|
@@ -510,21 +575,21 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
510
575
|
|
|
511
576
|
try {
|
|
512
577
|
while (!this._interrupting) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
break // revaluate conditions...
|
|
516
|
-
}
|
|
578
|
+
await this._flushWakeup()
|
|
579
|
+
if (this._interrupting) break
|
|
517
580
|
|
|
518
|
-
if (
|
|
519
|
-
this.
|
|
520
|
-
|
|
581
|
+
if (this.fastForwardTo !== null) {
|
|
582
|
+
await this._applyFastForward()
|
|
583
|
+
break // revaluate conditions...
|
|
521
584
|
}
|
|
522
585
|
|
|
523
|
-
if (!this.
|
|
586
|
+
if (!(await this._bumpPendingWriters())) break
|
|
524
587
|
this._needsUpdate = true
|
|
525
588
|
}
|
|
526
589
|
|
|
527
590
|
await this._flushLocal()
|
|
591
|
+
|
|
592
|
+
if (!this._interrupting) await this.writers.refresh()
|
|
528
593
|
} finally {
|
|
529
594
|
if (this.bumping === 1) this.bumping = 0
|
|
530
595
|
else this.bumping = 1
|
|
@@ -534,16 +599,15 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
534
599
|
this._draining = null
|
|
535
600
|
if (this._interrupting) return
|
|
536
601
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
this._updating = updating.promise
|
|
602
|
+
const updating = rrp()
|
|
603
|
+
this._updating = updating.promise
|
|
540
604
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
605
|
+
try {
|
|
606
|
+
if (this._needsUpdate) await this._update(changes)
|
|
607
|
+
await this._storeBoot()
|
|
608
|
+
} finally {
|
|
609
|
+
this._updating = null
|
|
610
|
+
updating.resolve()
|
|
547
611
|
}
|
|
548
612
|
}
|
|
549
613
|
|
|
@@ -576,87 +640,149 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
576
640
|
}
|
|
577
641
|
}
|
|
578
642
|
|
|
579
|
-
|
|
643
|
+
// wakeup hints keep arriving asynchronously
|
|
644
|
+
async _applyWakeupHints() {
|
|
580
645
|
const hints = this._wakeup.flush()
|
|
646
|
+
if (!hints.size) return []
|
|
581
647
|
|
|
582
648
|
this.previousDrain = Date.now()
|
|
583
|
-
this.
|
|
649
|
+
const results = await this._filterHints(hints)
|
|
650
|
+
|
|
651
|
+
for (const { key, length } of results) {
|
|
652
|
+
// wakeup() itself no-ops the add for an already-active writer, but still
|
|
653
|
+
// needs to run so it can hint() the announced length - skipping it here
|
|
654
|
+
// for active writers left gc with no hint to protect them
|
|
655
|
+
await this.writers.wakeup(key, length)
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return results
|
|
659
|
+
}
|
|
584
660
|
|
|
661
|
+
async _filterHints(hints) {
|
|
662
|
+
const results = []
|
|
663
|
+
const promises = []
|
|
585
664
|
for (const [hex, length] of hints) {
|
|
586
665
|
const key = b4a.from(hex, 'hex')
|
|
587
|
-
|
|
588
|
-
await this.writers.wakeup(key, length)
|
|
666
|
+
promises.push(this._filterTracked(key, length, results))
|
|
589
667
|
}
|
|
668
|
+
await Promise.all(promises)
|
|
669
|
+
return results
|
|
590
670
|
}
|
|
591
671
|
|
|
592
|
-
async
|
|
593
|
-
|
|
672
|
+
async _filterTracked(key, length, results) {
|
|
673
|
+
if (length) {
|
|
674
|
+
const info = await this.system.get(key)
|
|
675
|
+
// if we've seen this already, ignore
|
|
676
|
+
if (info && info.length >= length) return
|
|
677
|
+
}
|
|
678
|
+
results.push({ key, length })
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async _flushWakeup() {
|
|
682
|
+
const hints = await this._applyWakeupHints()
|
|
683
|
+
if (!hints.length) return
|
|
684
|
+
|
|
685
|
+
if (!this._ffEnabled) return
|
|
686
|
+
|
|
687
|
+
// a scheduled fast-forward is applied by the drain before we look again
|
|
688
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) return
|
|
689
|
+
if (this._interrupting || this.closing || this.bootFrom) return
|
|
594
690
|
|
|
595
691
|
try {
|
|
596
|
-
await
|
|
692
|
+
const heads = await this._readCandidateHeads(hints, FastForward.DEFAULT_TIMEOUT)
|
|
597
693
|
|
|
598
|
-
const
|
|
599
|
-
|
|
694
|
+
const ff = await FastForward.fromHeads(this, heads, {
|
|
695
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
696
|
+
})
|
|
600
697
|
|
|
601
|
-
|
|
602
|
-
|
|
698
|
+
if (ff !== null) await this._runFastForward(ff)
|
|
699
|
+
} catch (err) {
|
|
700
|
+
safetyCatch(err)
|
|
701
|
+
}
|
|
702
|
+
}
|
|
603
703
|
|
|
604
|
-
|
|
704
|
+
async _readCandidateHeads(hints, timeout) {
|
|
705
|
+
const promises = []
|
|
605
706
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
707
|
+
for (const { key, length } of hints) {
|
|
708
|
+
if (length === 0) continue
|
|
709
|
+
promises.push(this._resolveOplogHint(key, length, timeout ? { timeout } : null))
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const ops = await Promise.all(promises)
|
|
713
|
+
const heads = []
|
|
714
|
+
|
|
715
|
+
for (const res of ops) {
|
|
716
|
+
if (res === null) continue
|
|
717
|
+
|
|
718
|
+
// the head we were woken on is a candidate in its own right
|
|
719
|
+
heads.push({ key: res.key, length: res.length })
|
|
611
720
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
721
|
+
if (!res.op.trusted) continue
|
|
722
|
+
|
|
723
|
+
for (const trusted of res.op.trusted) {
|
|
724
|
+
const head = this.trusted.read(trusted)
|
|
725
|
+
if (head !== null) heads.push(head)
|
|
616
726
|
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return heads
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async _resolveOplogHint(key, length, opts) {
|
|
733
|
+
const core = this.openCore(key)
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
await core.ready()
|
|
737
|
+
const max = Math.max(length, core.length)
|
|
738
|
+
|
|
739
|
+
const block = await this.readOplog(core, max, opts)
|
|
740
|
+
if (max >= core.length) return block // best one
|
|
741
|
+
|
|
742
|
+
return await this.readOplog(core, core.length, opts)
|
|
617
743
|
} finally {
|
|
618
744
|
await core.close()
|
|
619
745
|
}
|
|
620
746
|
}
|
|
621
747
|
|
|
622
|
-
async
|
|
623
|
-
const
|
|
748
|
+
async _getOplog(key, length, opts = null) {
|
|
749
|
+
const core = this.openCore(key)
|
|
624
750
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
batch: { start: 0, end: m.node.batch - 1 },
|
|
630
|
-
views: null,
|
|
631
|
-
optimistic: !!m.optimistic,
|
|
632
|
-
value: m.node.value
|
|
751
|
+
try {
|
|
752
|
+
return await this.readOplog(core, length, opts)
|
|
753
|
+
} finally {
|
|
754
|
+
await core.close()
|
|
633
755
|
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// reads on a core the caller owns, so a walk does not churn a session per step
|
|
759
|
+
async readOplog(core, length, opts = null) {
|
|
760
|
+
await core.ready()
|
|
634
761
|
|
|
635
|
-
|
|
762
|
+
const { conservative = false, timeout } = opts === null ? {} : opts
|
|
636
763
|
|
|
637
|
-
const
|
|
764
|
+
const target = length >= 0 ? length : core.length
|
|
765
|
+
if (target === 0) return null
|
|
638
766
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer, opts) : buf
|
|
642
|
-
)
|
|
767
|
+
// conservative: only proceed when a connected peer can serve the head whole
|
|
768
|
+
if (conservative && core.remoteContiguousLength < target) return null
|
|
643
769
|
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
if (digestNode === null || checkpointNode === null) return op
|
|
770
|
+
const buf = await core.get(target - 1, { timeout })
|
|
771
|
+
if (buf === null) return null
|
|
647
772
|
|
|
648
|
-
|
|
649
|
-
const { checkpoint } = encoding.decodeRawOplog(checkpointNode)
|
|
773
|
+
let op = encoding.decodeOplog(buf)
|
|
650
774
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
},
|
|
656
|
-
flushes: seq
|
|
775
|
+
// legacy nodes always inflate, but only indexers carry views - callers
|
|
776
|
+
// that need system info must check op.views
|
|
777
|
+
if (op.version < 3) {
|
|
778
|
+
op = await migrations.inflateLegacyOplog(buf, core, target - 1, timeout)
|
|
657
779
|
}
|
|
658
780
|
|
|
659
|
-
return
|
|
781
|
+
return {
|
|
782
|
+
key: core.key,
|
|
783
|
+
length: target,
|
|
784
|
+
op
|
|
785
|
+
}
|
|
660
786
|
}
|
|
661
787
|
|
|
662
788
|
async _update(changes) {
|
|
@@ -716,7 +842,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
716
842
|
await this.bootstrap.setUserData('autobee/local', this.local.key)
|
|
717
843
|
await oldLocal.close()
|
|
718
844
|
|
|
719
|
-
// done, soft
|
|
845
|
+
// done, soft restart
|
|
720
846
|
this.emit('rotate-local-writer')
|
|
721
847
|
}
|
|
722
848
|
|
|
@@ -780,86 +906,152 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
780
906
|
}
|
|
781
907
|
|
|
782
908
|
async _bumpMigratedWriters() {
|
|
909
|
+
const opened = new Set()
|
|
910
|
+
let updated = false
|
|
911
|
+
|
|
783
912
|
for (const batch of this._catchupMigratedNodes) {
|
|
784
913
|
await this._processBatch(batch)
|
|
914
|
+
updated = true
|
|
915
|
+
for (const node of batch) {
|
|
916
|
+
if (node.from) opened.add(node.from)
|
|
917
|
+
}
|
|
785
918
|
}
|
|
786
|
-
}
|
|
787
919
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (!this.writers.writable) return false
|
|
920
|
+
for (const core of opened) await core.close()
|
|
921
|
+
return updated
|
|
922
|
+
}
|
|
792
923
|
|
|
793
|
-
|
|
924
|
+
async _prefetchApprovals() {
|
|
925
|
+
if (this._prefetchingApprovals) return
|
|
926
|
+
this._prefetchingApprovals = true
|
|
794
927
|
|
|
795
|
-
const
|
|
796
|
-
const t = Math.max(this._now(), this.system.timestamp)
|
|
928
|
+
const activeRequests = this._approvalRequests
|
|
797
929
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
930
|
+
try {
|
|
931
|
+
const prefetch = []
|
|
932
|
+
const standing = await this._standing(activeRequests)
|
|
933
|
+
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))
|
|
805
937
|
}
|
|
938
|
+
await Promise.allSettled(prefetch)
|
|
939
|
+
} finally {
|
|
940
|
+
this._prefetchingApprovals = false
|
|
806
941
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async _standing(activeRequests) {
|
|
945
|
+
const rec = await this.system.get(this.local.key, { activeRequests })
|
|
946
|
+
return currentWeight(rec)
|
|
947
|
+
}
|
|
811
948
|
|
|
812
|
-
|
|
813
|
-
|
|
949
|
+
// only our own tiers: a request whose lower tiers are already anchored is
|
|
950
|
+
// somebody stronger's job, and must not keep every peer at this standing
|
|
951
|
+
// rescanning (or offering) while it waits
|
|
952
|
+
_pendingWork(standing) {
|
|
953
|
+
const digest = this.system.promotions.digest
|
|
954
|
+
const end = Math.min(standing, digest.length)
|
|
955
|
+
for (let w = 1; w <= end; w++) {
|
|
956
|
+
if (digest[w - 1]) return true
|
|
814
957
|
}
|
|
958
|
+
return false
|
|
959
|
+
}
|
|
815
960
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
961
|
+
// the requests we could serve and by how much - shared by the prefetch and
|
|
962
|
+
// 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 })) {
|
|
965
|
+
const amount = Math.min(standing, p.weight)
|
|
966
|
+
if (amount <= 0) continue
|
|
967
|
+
yield { key: p.key, amount }
|
|
968
|
+
}
|
|
819
969
|
}
|
|
820
970
|
|
|
821
|
-
async
|
|
822
|
-
if (this.
|
|
823
|
-
|
|
824
|
-
|
|
971
|
+
async _collectApprovals() {
|
|
972
|
+
if (!this.system.promotions.changed && !this._approvalCheck) return null
|
|
973
|
+
if (!this.writers.writable) return null
|
|
974
|
+
|
|
975
|
+
const activeRequests = this._approvalRequests
|
|
976
|
+
const standing = await this._standing(activeRequests)
|
|
977
|
+
if (standing <= 0) return null
|
|
978
|
+
|
|
979
|
+
this.system.promotions.changed = false
|
|
980
|
+
this._approvalCheck = false
|
|
981
|
+
|
|
982
|
+
if (!this._pendingWork(standing)) return null
|
|
983
|
+
|
|
984
|
+
const approvals = []
|
|
985
|
+
const approving = []
|
|
986
|
+
|
|
987
|
+
for await (const p of this._servableRequests(standing, activeRequests)) {
|
|
988
|
+
approving.push(fetchApproval.call(this, p.key, p.amount, approvals))
|
|
825
989
|
}
|
|
826
990
|
|
|
827
|
-
|
|
991
|
+
if (!approving.length) return null
|
|
828
992
|
|
|
829
|
-
|
|
993
|
+
try {
|
|
994
|
+
await Promise.all(approving)
|
|
995
|
+
} catch (err) {
|
|
996
|
+
if (this._interrupting) return null
|
|
997
|
+
throw err
|
|
998
|
+
}
|
|
830
999
|
|
|
831
|
-
|
|
832
|
-
const w = pending[i]
|
|
1000
|
+
if (!approvals.length) return null
|
|
833
1001
|
|
|
834
|
-
|
|
835
|
-
|
|
1002
|
+
for (const a of approvals) {
|
|
1003
|
+
this._approvedPending.set(b4a.toString(a.key, 'hex'), a.weight)
|
|
1004
|
+
}
|
|
836
1005
|
|
|
837
|
-
|
|
838
|
-
await this._processBatch(batch)
|
|
839
|
-
w.notify(batch)
|
|
840
|
-
updated = true
|
|
841
|
-
continue
|
|
842
|
-
}
|
|
1006
|
+
return approvals
|
|
843
1007
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1008
|
+
async function fetchApproval(key, weight, approvals) {
|
|
1009
|
+
const hex = b4a.toString(key, 'hex')
|
|
1010
|
+
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
|
+
])
|
|
1015
|
+
if (!target || target.isRemoved) return
|
|
1016
|
+
if (hint && hint.weight >= weight) return
|
|
1017
|
+
approvals.push({ key, weight })
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async _bumpPendingWriters({ local = false } = {}) {
|
|
1022
|
+
if (!local && this._catchupMigratedNodes !== null) {
|
|
1023
|
+
const updated = await this._bumpMigratedWriters()
|
|
1024
|
+
this._catchupMigratedNodes = null
|
|
1025
|
+
if (updated) return true
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// apply the best next node to keep the prefix stable
|
|
1029
|
+
const next = await this.writers.nextPendingNode({ local })
|
|
1030
|
+
if (next === null) return false
|
|
1031
|
+
|
|
1032
|
+
const { writer: w, batch } = next
|
|
1033
|
+
|
|
1034
|
+
if (w.isAdded || (w.isRemoved && w.hasReferrals())) {
|
|
1035
|
+
await this._processBatch(batch)
|
|
1036
|
+
w.notify(batch)
|
|
1037
|
+
return true
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (this.optimistic && !w.isRemoved && batch[0].optimistic) {
|
|
1041
|
+
if (!(await this._optimisticBatch(batch))) {
|
|
1042
|
+
w.removePending()
|
|
1043
|
+
return true
|
|
852
1044
|
}
|
|
1045
|
+
w.notify(batch)
|
|
1046
|
+
return true
|
|
853
1047
|
}
|
|
854
1048
|
|
|
855
|
-
return
|
|
1049
|
+
return true
|
|
856
1050
|
}
|
|
857
1051
|
|
|
858
1052
|
async _optimisticBatch(batch) {
|
|
859
1053
|
const rollbackSystem = this.system.bee.head()
|
|
860
1054
|
const rollbackView = this._workingBee.head()
|
|
861
|
-
const rollbackShared = this.system.shared
|
|
862
|
-
const rollbackAttestations = this.writers.attestations.length
|
|
863
1055
|
|
|
864
1056
|
const t = await this.prepareBatch(batch)
|
|
865
1057
|
if (t.view) this._workingBee.move(t.view)
|
|
@@ -881,9 +1073,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
881
1073
|
this._workingBee.move(rollbackView)
|
|
882
1074
|
this.system.bee.move(rollbackSystem)
|
|
883
1075
|
await this.system.reset()
|
|
884
|
-
this.system.shared = rollbackShared
|
|
885
|
-
// don't attest grants that were just undone
|
|
886
|
-
this.writers.attestations.length = rollbackAttestations
|
|
887
1076
|
return false
|
|
888
1077
|
}
|
|
889
1078
|
}
|
|
@@ -904,7 +1093,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
904
1093
|
const t = await topo.sort(this, batch)
|
|
905
1094
|
|
|
906
1095
|
if (t.undo) {
|
|
1096
|
+
this.stats.undos++
|
|
1097
|
+
this.trusted.clear()
|
|
907
1098
|
t.view = await this.system.undo(t.undo)
|
|
1099
|
+
if (!t.view.length && this._migratedHead && this._migratedHead.view) {
|
|
1100
|
+
t.view = this._migratedHead.view
|
|
1101
|
+
}
|
|
908
1102
|
}
|
|
909
1103
|
|
|
910
1104
|
return t
|
|
@@ -924,7 +1118,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
924
1118
|
}
|
|
925
1119
|
|
|
926
1120
|
if (b4a.equals(batch[0].key, this.key) && batch[0].length === 1) {
|
|
927
|
-
await this._host.addWriter(batch[0].key)
|
|
1121
|
+
await this._host.addWriter(batch[0].key, { weight: this.bootstrapWeight })
|
|
928
1122
|
}
|
|
929
1123
|
|
|
930
1124
|
await this._applyBatch(batch, batch[0].optimistic)
|
|
@@ -947,33 +1141,39 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
947
1141
|
for (const node of batch) {
|
|
948
1142
|
this.system.addNode(node)
|
|
949
1143
|
|
|
1144
|
+
if (node.approvals) {
|
|
1145
|
+
for (const a of node.approvals) {
|
|
1146
|
+
await this.system.addWriter(a.key, {
|
|
1147
|
+
weight: a.weight,
|
|
1148
|
+
coord: { key: node.key, length: node.length },
|
|
1149
|
+
carrier: node.weight,
|
|
1150
|
+
anchor: true
|
|
1151
|
+
})
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
950
1155
|
// compat: autobase nodes may be null (legacy null decodes to 0-length buffer)
|
|
951
1156
|
if (node.value && node.value.length) userBatch.push(node)
|
|
952
1157
|
}
|
|
953
1158
|
|
|
954
1159
|
if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
|
|
1160
|
+
this.stats.applies++
|
|
955
1161
|
this._host.applying = batch
|
|
956
|
-
await this.
|
|
1162
|
+
await this._workingView.apply(userBatch)
|
|
957
1163
|
this._host.applying = null
|
|
958
1164
|
}
|
|
959
1165
|
|
|
960
|
-
const
|
|
1166
|
+
const changed = await this.system.flush(batch, this._workingBee)
|
|
1167
|
+
|
|
1168
|
+
if (this.system.promotions.changed) this._prefetchApprovals().catch(safetyCatch)
|
|
961
1169
|
|
|
962
1170
|
if (local) {
|
|
963
1171
|
this._localSystemLength = this.system.bee.context.local.length - this._localSystemStart
|
|
964
1172
|
this._localViewLength = this._workingBee.context.local.length - this._localViewStart
|
|
1173
|
+
this._localFlushes = this.system.flushes
|
|
965
1174
|
}
|
|
966
1175
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
await this._storeBoot()
|
|
970
|
-
|
|
971
|
-
for (const { key, added, isAnchor } of changed) {
|
|
972
|
-
if (isAnchor) {
|
|
973
|
-
// no Writer/ActiveWriters tracking, but still wake anything linked to it
|
|
974
|
-
this.writers.triggers.trigger(b4a.toString(key, 'hex'), 1)
|
|
975
|
-
continue
|
|
976
|
-
}
|
|
1176
|
+
for (const { key, added } of changed) {
|
|
977
1177
|
if (added) await this.writers.add(key)
|
|
978
1178
|
else await this.writers.remove(key)
|
|
979
1179
|
}
|
|
@@ -996,6 +1196,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
996
1196
|
return Promise.all(proms)
|
|
997
1197
|
}
|
|
998
1198
|
|
|
1199
|
+
_storeMigratedHead() {
|
|
1200
|
+
const value = this._migratedHead ? encoding.encodeMigratedHead(this._migratedHead) : null
|
|
1201
|
+
return this.local.setUserData('autobee/migrated-head', value)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
999
1204
|
static decodeValue(buf, opts) {
|
|
1000
1205
|
return encoding.decodeValue(buf, opts)
|
|
1001
1206
|
}
|
|
@@ -1013,6 +1218,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1013
1218
|
async append(values, { optimistic = false } = {}) {
|
|
1014
1219
|
if (this.closing) throw new Error('Autobee closed')
|
|
1015
1220
|
|
|
1221
|
+
this.stats.appends++
|
|
1222
|
+
|
|
1016
1223
|
if (!Array.isArray(values)) values = [values]
|
|
1017
1224
|
|
|
1018
1225
|
if (!this.opened) await this.ready()
|
|
@@ -1029,27 +1236,17 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1029
1236
|
const t = Math.max(this._now(), this.system.timestamp)
|
|
1030
1237
|
const batch = []
|
|
1031
1238
|
|
|
1032
|
-
// witnesses only ride upgrade windows. witness.weight is the value the backer's
|
|
1033
|
-
// snapshot witnesses - verifiers recompute the same read and verify
|
|
1034
1239
|
const rec = await this.system.get(this.local.key)
|
|
1035
1240
|
let witness = null
|
|
1036
|
-
if (rec
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
for (const { key, length, attestation, manifest } of sorted) {
|
|
1042
|
-
if (attestation.weight > rec.maxWeight) continue
|
|
1043
|
-
|
|
1044
|
-
const backer = await this.system.get(key)
|
|
1045
|
-
if (!backer || backer.isRemoved || backer.length < length) continue
|
|
1046
|
-
|
|
1047
|
-
const { weight, signature } = attestation
|
|
1048
|
-
witness = { weight, backer: { key, length, signature, manifest } }
|
|
1049
|
-
break
|
|
1241
|
+
if (rec) {
|
|
1242
|
+
const hint = await this.system.grantHint(this.local.key)
|
|
1243
|
+
if (hint && hint.weight > currentWeight(rec)) {
|
|
1244
|
+
witness = { weight: hint.weight, link: { key: hint.key, length: hint.length } }
|
|
1050
1245
|
}
|
|
1051
1246
|
}
|
|
1052
1247
|
|
|
1248
|
+
const approvals = optimistic ? null : await this._collectApprovals()
|
|
1249
|
+
|
|
1053
1250
|
for (let i = 0; i < values.length; i++) {
|
|
1054
1251
|
const value = values[i]
|
|
1055
1252
|
const buffer = typeof value === 'string' ? b4a.from(value) : value
|
|
@@ -1060,7 +1257,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1060
1257
|
{ start: i, end: values.length - 1 - i },
|
|
1061
1258
|
lnk,
|
|
1062
1259
|
optimistic,
|
|
1063
|
-
i === 0 ? witness : null
|
|
1260
|
+
i === 0 ? witness : null,
|
|
1261
|
+
i === 0 ? approvals : null
|
|
1064
1262
|
)
|
|
1065
1263
|
batch.push(node)
|
|
1066
1264
|
}
|
|
@@ -1069,8 +1267,13 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1069
1267
|
}
|
|
1070
1268
|
|
|
1071
1269
|
async _flushLocal() {
|
|
1270
|
+
// pull any available local nodes in before flushing
|
|
1271
|
+
while (!this._interrupting && (await this._bumpPendingWriters({ local: true }))) {
|
|
1272
|
+
this._needsUpdate = true
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1072
1275
|
await this.writers.flushLocal({
|
|
1073
|
-
flushes: this.
|
|
1276
|
+
flushes: this._localFlushes,
|
|
1074
1277
|
system: {
|
|
1075
1278
|
key: this.system.bee.context.local.key,
|
|
1076
1279
|
start: this._localSystemStart,
|
|
@@ -1085,135 +1288,166 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1085
1288
|
|
|
1086
1289
|
this._localSystemStart = this.system.bee.context.local.length
|
|
1087
1290
|
this._localSystemLength = 0
|
|
1291
|
+
this._localFlushes = this.system.flushes
|
|
1088
1292
|
this._localViewStart = this._workingBee.context.local.length
|
|
1089
1293
|
this._localViewLength = 0
|
|
1090
1294
|
}
|
|
1091
1295
|
|
|
1092
|
-
async
|
|
1093
|
-
if (!this.
|
|
1094
|
-
if (this.
|
|
1095
|
-
if (!hints.size || this.rebooting || this.rebootTo || this.bootFrom) {
|
|
1096
|
-
return false
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
const promises = []
|
|
1100
|
-
const heads = []
|
|
1101
|
-
for (const [hex, length] of hints) {
|
|
1102
|
-
if (length === 0) continue
|
|
1103
|
-
const key = b4a.from(hex, 'hex')
|
|
1104
|
-
heads.push({ key, length })
|
|
1105
|
-
promises.push(this._getOplog(key, length))
|
|
1106
|
-
}
|
|
1296
|
+
async moveTo(head) {
|
|
1297
|
+
if (!this.opened) await this.ready()
|
|
1298
|
+
if (this.closing) throw new Error('Autobee closed')
|
|
1107
1299
|
|
|
1108
|
-
const
|
|
1109
|
-
if (
|
|
1300
|
+
const ff = await FastForward.fromHead(this, head, null, { force: true })
|
|
1301
|
+
if (ff === null) return null
|
|
1110
1302
|
|
|
1111
|
-
|
|
1112
|
-
let bestFlushes = -1
|
|
1303
|
+
if (!(await this._runFastForward(ff))) return null
|
|
1113
1304
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
if (res === null) continue
|
|
1305
|
+
return this.ff.promise
|
|
1306
|
+
}
|
|
1117
1307
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
}
|
|
1308
|
+
// legacy: ungated boot straight onto a system head
|
|
1309
|
+
async _bootFromSystem(system) {
|
|
1310
|
+
try {
|
|
1311
|
+
const ff = new FastForward(this, system, { timeout: FastForward.DEFAULT_TIMEOUT })
|
|
1312
|
+
return await this._runFastForward(ff)
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
safetyCatch(err)
|
|
1315
|
+
return false
|
|
1122
1316
|
}
|
|
1317
|
+
}
|
|
1123
1318
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1319
|
+
// the boot head seeds the view every trust decision here is made against,
|
|
1320
|
+
// since our own view is still empty
|
|
1321
|
+
async _bootFromHead(head, bootCondition, wait = false) {
|
|
1322
|
+
// just a head: one attempt, we do not wait around if it cannot be read
|
|
1323
|
+
if (bootCondition === null && !wait) {
|
|
1324
|
+
try {
|
|
1325
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1326
|
+
force: true,
|
|
1327
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
safetyCatch(err)
|
|
1333
|
+
return false
|
|
1334
|
+
}
|
|
1126
1335
|
}
|
|
1127
1336
|
|
|
1128
|
-
|
|
1337
|
+
// park until the head can be read and, if a condition is set, something
|
|
1338
|
+
// acceptable turns up
|
|
1339
|
+
let opened = null
|
|
1129
1340
|
|
|
1130
|
-
|
|
1341
|
+
try {
|
|
1342
|
+
while (!this._interrupting) {
|
|
1343
|
+
this._bootWait = rrp()
|
|
1344
|
+
|
|
1345
|
+
try {
|
|
1346
|
+
if (bootCondition === null) {
|
|
1347
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1348
|
+
force: true,
|
|
1349
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1350
|
+
})
|
|
1351
|
+
|
|
1352
|
+
if (ff !== null && (await this._runFastForward(ff))) return true
|
|
1353
|
+
} else {
|
|
1354
|
+
if (opened === null) opened = await this._bootReference(head)
|
|
1355
|
+
|
|
1356
|
+
if (opened !== null) {
|
|
1357
|
+
if (await this._bootAttempt(head, bootCondition, opened.view)) {
|
|
1358
|
+
return true
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// the reference may be stale - resolve it afresh next attempt
|
|
1362
|
+
await opened.close()
|
|
1363
|
+
opened = null
|
|
1364
|
+
} else if (await this._bootLegacyHead(head)) {
|
|
1365
|
+
// boot legacy head through the migration pathway
|
|
1366
|
+
return true
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
safetyCatch(err)
|
|
1371
|
+
}
|
|
1131
1372
|
|
|
1132
|
-
|
|
1133
|
-
const view = this.bee.checkout({ key: v.key, length: v.start + v.length })
|
|
1373
|
+
if (this._interrupting) break
|
|
1134
1374
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
trusted = await this._handlers.onwakeup(head, view, this)
|
|
1138
|
-
if (!trusted || this.rebooting || this.rebootTo) return false
|
|
1375
|
+
await this._bootWait.promise
|
|
1376
|
+
}
|
|
1139
1377
|
} finally {
|
|
1140
|
-
|
|
1378
|
+
this._bootWait = null
|
|
1379
|
+
if (opened !== null) await opened.close()
|
|
1141
1380
|
}
|
|
1142
1381
|
|
|
1143
|
-
return
|
|
1382
|
+
return false
|
|
1144
1383
|
}
|
|
1145
1384
|
|
|
1146
|
-
async
|
|
1147
|
-
|
|
1148
|
-
|
|
1385
|
+
async _bootAttempt(head, bootCondition, reference) {
|
|
1386
|
+
// peek, so the hints are still there for the drain once we have booted
|
|
1387
|
+
const result = await this._filterHints(this._wakeup.hints)
|
|
1149
1388
|
|
|
1150
|
-
const
|
|
1151
|
-
if (!verified) return null
|
|
1389
|
+
const candidates = await this._readCandidateHeads(result, FastForward.DEFAULT_TIMEOUT)
|
|
1152
1390
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1391
|
+
const ff = await FastForward.fromHeads(this, [head, ...candidates], {
|
|
1392
|
+
force: true,
|
|
1393
|
+
timeout: FastForward.DEFAULT_TIMEOUT,
|
|
1394
|
+
condition: bootCondition,
|
|
1395
|
+
reference
|
|
1396
|
+
})
|
|
1155
1397
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
}
|
|
1398
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1399
|
+
}
|
|
1159
1400
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
op: trusted || head,
|
|
1164
|
-
flushes: verified.op.views.flushes
|
|
1165
|
-
}
|
|
1401
|
+
async _bootLegacyHead(head) {
|
|
1402
|
+
const oplog = await FastForward.flushHead(this, head, {
|
|
1403
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1166
1404
|
})
|
|
1405
|
+
if (oplog === null || oplog.op.version > LEGACY_OPLOG_VERSION) return false
|
|
1167
1406
|
|
|
1168
|
-
|
|
1407
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1408
|
+
force: true,
|
|
1409
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1410
|
+
})
|
|
1169
1411
|
|
|
1170
|
-
return null
|
|
1412
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1171
1413
|
}
|
|
1172
1414
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
return this._runReboot(new Reboot(this, head, tip))
|
|
1178
|
-
}
|
|
1415
|
+
async _bootReference(head) {
|
|
1416
|
+
const oplog = await FastForward.flushHead(this, head, {
|
|
1417
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1418
|
+
})
|
|
1179
1419
|
|
|
1180
|
-
return
|
|
1420
|
+
return oplog === null ? null : this.openViewAt(oplog)
|
|
1181
1421
|
}
|
|
1182
1422
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
if (await this._runReboot(new Reboot(this, systemHead, tip))) {
|
|
1188
|
-
return true
|
|
1423
|
+
async _runFastForward(ff) {
|
|
1424
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) {
|
|
1425
|
+
await ff.close()
|
|
1426
|
+
return false
|
|
1189
1427
|
}
|
|
1190
1428
|
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
async _runReboot(reboot) {
|
|
1195
|
-
this.rebooting = reboot
|
|
1429
|
+
this.fastForwarding = ff
|
|
1196
1430
|
|
|
1197
|
-
const result = await
|
|
1198
|
-
await
|
|
1431
|
+
const result = await ff.run()
|
|
1432
|
+
await ff.close()
|
|
1199
1433
|
|
|
1200
|
-
if (this.
|
|
1434
|
+
if (this.fastForwarding === ff) this.fastForwarding = null
|
|
1201
1435
|
|
|
1202
1436
|
if (!result) return false
|
|
1203
1437
|
|
|
1204
|
-
this.
|
|
1205
|
-
this.
|
|
1438
|
+
this.fastForwardTo = result
|
|
1439
|
+
this.ff = rrp()
|
|
1206
1440
|
|
|
1207
1441
|
this.bumpSoon()
|
|
1208
1442
|
|
|
1209
1443
|
return true
|
|
1210
1444
|
}
|
|
1211
1445
|
|
|
1212
|
-
async
|
|
1446
|
+
async _applyFastForward() {
|
|
1213
1447
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1214
1448
|
if (changes) changes.track()
|
|
1215
1449
|
|
|
1216
|
-
const { head,
|
|
1450
|
+
const { head, migrate } = this.fastForwardTo
|
|
1217
1451
|
|
|
1218
1452
|
const from = this.system.bee.head()
|
|
1219
1453
|
const to = head
|
|
@@ -1221,15 +1455,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1221
1455
|
this.system.bee.move(head)
|
|
1222
1456
|
await this.system.reset()
|
|
1223
1457
|
|
|
1224
|
-
this.system.shared = this.rebootTo.shared || {
|
|
1225
|
-
flushes: -1,
|
|
1226
|
-
view: EMPTY_HEAD,
|
|
1227
|
-
system: EMPTY_HEAD
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
1458
|
// migrate is set when fast-forwarding from a legacy head
|
|
1231
1459
|
if (migrate) {
|
|
1232
|
-
const view = (await this.
|
|
1460
|
+
const view = (await this._handlers.migrate(migrate, head)) || EMPTY_HEAD
|
|
1461
|
+
|
|
1462
|
+
this._migratedHead = {
|
|
1463
|
+
system: head,
|
|
1464
|
+
view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
await this._storeMigratedHead()
|
|
1233
1468
|
this.bee.move(view)
|
|
1234
1469
|
this._workingBee.move(view)
|
|
1235
1470
|
} else {
|
|
@@ -1237,34 +1472,22 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1237
1472
|
this._workingBee.move(this.system.view)
|
|
1238
1473
|
}
|
|
1239
1474
|
|
|
1240
|
-
this.
|
|
1475
|
+
this._approvalCheck = true
|
|
1476
|
+
this.fastForwardTo = null
|
|
1477
|
+
|
|
1478
|
+
// process any wakeup while fast-forward itself was in flight
|
|
1479
|
+
await this._applyWakeupHints()
|
|
1241
1480
|
await this.writers.refresh()
|
|
1242
1481
|
|
|
1482
|
+
// we moved, so ask our peers to tell us their heads again
|
|
1483
|
+
this._requestWakeup()
|
|
1484
|
+
|
|
1243
1485
|
await this._update(changes)
|
|
1486
|
+
await this._storeBoot()
|
|
1244
1487
|
|
|
1488
|
+
this.stats.fastForwards++
|
|
1245
1489
|
this.emit('move-to', to, from)
|
|
1246
|
-
this.
|
|
1247
|
-
|
|
1248
|
-
// tip is null during boot
|
|
1249
|
-
if (!tip) return
|
|
1250
|
-
|
|
1251
|
-
try {
|
|
1252
|
-
await this._reapply(tip)
|
|
1253
|
-
} catch (err) {
|
|
1254
|
-
throw err
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
async _reapply({ system, verified }) {
|
|
1259
|
-
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1260
|
-
if (changes) changes.track()
|
|
1261
|
-
|
|
1262
|
-
const sys = this.system.bee.checkout(system)
|
|
1263
|
-
const t = await topo.rollback(this, sys, verified)
|
|
1264
|
-
await sys.close()
|
|
1265
|
-
|
|
1266
|
-
await this.applyBacklog(t.tip)
|
|
1267
|
-
return this._update(changes)
|
|
1490
|
+
this.ff.resolve({ to, from })
|
|
1268
1491
|
}
|
|
1269
1492
|
|
|
1270
1493
|
replay() {
|
|
@@ -1276,6 +1499,17 @@ function isObject(o) {
|
|
|
1276
1499
|
return typeof o === 'object' && o && !b4a.isBuffer(o)
|
|
1277
1500
|
}
|
|
1278
1501
|
|
|
1502
|
+
function getBootOption(boot) {
|
|
1503
|
+
if (!boot) return null
|
|
1504
|
+
|
|
1505
|
+
// oldest style, supported for now but will go away: a bare key is legacy
|
|
1506
|
+
if (boot.key) return { legacy: boot, head: null, bootCondition: null }
|
|
1507
|
+
|
|
1508
|
+
asserts.assert(!(boot.head && boot.legacy), 'Boot from either a head or a legacy pointer')
|
|
1509
|
+
|
|
1510
|
+
return boot
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1279
1513
|
function noop() {}
|
|
1280
1514
|
|
|
1281
1515
|
function createAnchorCore(store, prologue, manifestData) {
|
|
@@ -1304,10 +1538,3 @@ function crashSoon(err) {
|
|
|
1304
1538
|
})
|
|
1305
1539
|
throw err
|
|
1306
1540
|
}
|
|
1307
|
-
|
|
1308
|
-
function batchToHead(b) {
|
|
1309
|
-
return {
|
|
1310
|
-
key: b.key,
|
|
1311
|
-
length: b.start + b.length
|
|
1312
|
-
}
|
|
1313
|
-
}
|