autobee 2.0.0-rc.8 → 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/compat.js +1 -1
- package/encoding/legacy.js +33 -8
- package/encoding/spec/autobee/index.js +312 -25
- package/encoding/spec/autobee/schema.json +228 -2
- package/index.js +555 -310
- 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 +110 -52
- 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 +5 -4
- 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
|
|
100
|
+
|
|
101
|
+
this.trusted = new TrustedPeers(handlers)
|
|
81
102
|
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
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,70 +640,148 @@ 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)
|
|
584
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
|
+
}
|
|
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
|
-
|
|
594
|
-
|
|
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
|
+
}
|
|
595
680
|
|
|
596
|
-
|
|
597
|
-
|
|
681
|
+
async _flushWakeup() {
|
|
682
|
+
const hints = await this._applyWakeupHints()
|
|
683
|
+
if (!hints.length) return
|
|
598
684
|
|
|
599
|
-
|
|
600
|
-
let op = encoding.decodeOplog(buf)
|
|
685
|
+
if (!this._ffEnabled) return
|
|
601
686
|
|
|
602
|
-
|
|
603
|
-
|
|
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
|
|
690
|
+
|
|
691
|
+
try {
|
|
692
|
+
const heads = await this._readCandidateHeads(hints, FastForward.DEFAULT_TIMEOUT)
|
|
693
|
+
|
|
694
|
+
const ff = await FastForward.fromHeads(this, heads, {
|
|
695
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
if (ff !== null) await this._runFastForward(ff)
|
|
699
|
+
} catch (err) {
|
|
700
|
+
safetyCatch(err)
|
|
604
701
|
}
|
|
702
|
+
}
|
|
605
703
|
|
|
606
|
-
|
|
704
|
+
async _readCandidateHeads(hints, timeout) {
|
|
705
|
+
const promises = []
|
|
607
706
|
|
|
608
|
-
|
|
707
|
+
for (const { key, length } of hints) {
|
|
708
|
+
if (length === 0) continue
|
|
709
|
+
promises.push(this._resolveOplogHint(key, length, timeout ? { timeout } : null))
|
|
710
|
+
}
|
|
609
711
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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 })
|
|
720
|
+
|
|
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)
|
|
726
|
+
}
|
|
614
727
|
}
|
|
728
|
+
|
|
729
|
+
return heads
|
|
615
730
|
}
|
|
616
731
|
|
|
617
|
-
async
|
|
618
|
-
const
|
|
619
|
-
|
|
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)
|
|
620
738
|
|
|
621
|
-
|
|
622
|
-
|
|
739
|
+
const block = await this.readOplog(core, max, opts)
|
|
740
|
+
if (max >= core.length) return block // best one
|
|
623
741
|
|
|
624
|
-
|
|
742
|
+
return await this.readOplog(core, core.length, opts)
|
|
743
|
+
} finally {
|
|
744
|
+
await core.close()
|
|
745
|
+
}
|
|
746
|
+
}
|
|
625
747
|
|
|
626
|
-
|
|
627
|
-
const
|
|
748
|
+
async _getOplog(key, length, opts = null) {
|
|
749
|
+
const core = this.openCore(key)
|
|
750
|
+
|
|
751
|
+
try {
|
|
752
|
+
return await this.readOplog(core, length, opts)
|
|
753
|
+
} finally {
|
|
754
|
+
await core.close()
|
|
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()
|
|
761
|
+
|
|
762
|
+
const { conservative = false, timeout } = opts === null ? {} : opts
|
|
763
|
+
|
|
764
|
+
const target = length >= 0 ? length : core.length
|
|
765
|
+
if (target === 0) return null
|
|
766
|
+
|
|
767
|
+
// conservative: only proceed when a connected peer can serve the head whole
|
|
768
|
+
if (conservative && core.remoteContiguousLength < target) return null
|
|
769
|
+
|
|
770
|
+
const buf = await core.get(target - 1, { timeout })
|
|
771
|
+
if (buf === null) return null
|
|
772
|
+
|
|
773
|
+
let op = encoding.decodeOplog(buf)
|
|
774
|
+
|
|
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)
|
|
779
|
+
}
|
|
628
780
|
|
|
629
781
|
return {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
batch: { start: 0, end: m.node.batch - 1 },
|
|
634
|
-
views: {
|
|
635
|
-
system: {
|
|
636
|
-
key: digest.key,
|
|
637
|
-
length: checkpoint.system.checkpoint.length
|
|
638
|
-
},
|
|
639
|
-
flushes: seq
|
|
640
|
-
},
|
|
641
|
-
optimistic: !!m.optimistic,
|
|
642
|
-
value: m.node.value
|
|
782
|
+
key: core.key,
|
|
783
|
+
length: target,
|
|
784
|
+
op
|
|
643
785
|
}
|
|
644
786
|
}
|
|
645
787
|
|
|
@@ -700,7 +842,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
700
842
|
await this.bootstrap.setUserData('autobee/local', this.local.key)
|
|
701
843
|
await oldLocal.close()
|
|
702
844
|
|
|
703
|
-
// done, soft
|
|
845
|
+
// done, soft restart
|
|
704
846
|
this.emit('rotate-local-writer')
|
|
705
847
|
}
|
|
706
848
|
|
|
@@ -764,86 +906,152 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
764
906
|
}
|
|
765
907
|
|
|
766
908
|
async _bumpMigratedWriters() {
|
|
909
|
+
const opened = new Set()
|
|
910
|
+
let updated = false
|
|
911
|
+
|
|
767
912
|
for (const batch of this._catchupMigratedNodes) {
|
|
768
913
|
await this._processBatch(batch)
|
|
914
|
+
updated = true
|
|
915
|
+
for (const node of batch) {
|
|
916
|
+
if (node.from) opened.add(node.from)
|
|
917
|
+
}
|
|
769
918
|
}
|
|
770
|
-
}
|
|
771
919
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (!this.writers.writable) return false
|
|
920
|
+
for (const core of opened) await core.close()
|
|
921
|
+
return updated
|
|
922
|
+
}
|
|
776
923
|
|
|
777
|
-
|
|
924
|
+
async _prefetchApprovals() {
|
|
925
|
+
if (this._prefetchingApprovals) return
|
|
926
|
+
this._prefetchingApprovals = true
|
|
778
927
|
|
|
779
|
-
const
|
|
780
|
-
const t = Math.max(this._now(), this.system.timestamp)
|
|
928
|
+
const activeRequests = this._approvalRequests
|
|
781
929
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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))
|
|
789
937
|
}
|
|
938
|
+
await Promise.allSettled(prefetch)
|
|
939
|
+
} finally {
|
|
940
|
+
this._prefetchingApprovals = false
|
|
790
941
|
}
|
|
791
|
-
|
|
792
|
-
this._ackRequired = false
|
|
793
|
-
return false
|
|
794
|
-
}
|
|
942
|
+
}
|
|
795
943
|
|
|
796
|
-
|
|
797
|
-
|
|
944
|
+
async _standing(activeRequests) {
|
|
945
|
+
const rec = await this.system.get(this.local.key, { activeRequests })
|
|
946
|
+
return currentWeight(rec)
|
|
947
|
+
}
|
|
948
|
+
|
|
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
|
|
798
957
|
}
|
|
958
|
+
return false
|
|
959
|
+
}
|
|
799
960
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
+
}
|
|
803
969
|
}
|
|
804
970
|
|
|
805
|
-
async
|
|
806
|
-
if (this.
|
|
807
|
-
|
|
808
|
-
|
|
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))
|
|
809
989
|
}
|
|
810
990
|
|
|
811
|
-
|
|
991
|
+
if (!approving.length) return null
|
|
812
992
|
|
|
813
|
-
|
|
993
|
+
try {
|
|
994
|
+
await Promise.all(approving)
|
|
995
|
+
} catch (err) {
|
|
996
|
+
if (this._interrupting) return null
|
|
997
|
+
throw err
|
|
998
|
+
}
|
|
814
999
|
|
|
815
|
-
|
|
816
|
-
const w = pending[i]
|
|
1000
|
+
if (!approvals.length) return null
|
|
817
1001
|
|
|
818
|
-
|
|
819
|
-
|
|
1002
|
+
for (const a of approvals) {
|
|
1003
|
+
this._approvedPending.set(b4a.toString(a.key, 'hex'), a.weight)
|
|
1004
|
+
}
|
|
820
1005
|
|
|
821
|
-
|
|
822
|
-
await this._processBatch(batch)
|
|
823
|
-
w.notify(batch)
|
|
824
|
-
updated = true
|
|
825
|
-
continue
|
|
826
|
-
}
|
|
1006
|
+
return approvals
|
|
827
1007
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
|
836
1044
|
}
|
|
1045
|
+
w.notify(batch)
|
|
1046
|
+
return true
|
|
837
1047
|
}
|
|
838
1048
|
|
|
839
|
-
return
|
|
1049
|
+
return true
|
|
840
1050
|
}
|
|
841
1051
|
|
|
842
1052
|
async _optimisticBatch(batch) {
|
|
843
1053
|
const rollbackSystem = this.system.bee.head()
|
|
844
1054
|
const rollbackView = this._workingBee.head()
|
|
845
|
-
const rollbackShared = this.system.shared
|
|
846
|
-
const rollbackAttestations = this.writers.attestations.length
|
|
847
1055
|
|
|
848
1056
|
const t = await this.prepareBatch(batch)
|
|
849
1057
|
if (t.view) this._workingBee.move(t.view)
|
|
@@ -865,9 +1073,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
865
1073
|
this._workingBee.move(rollbackView)
|
|
866
1074
|
this.system.bee.move(rollbackSystem)
|
|
867
1075
|
await this.system.reset()
|
|
868
|
-
this.system.shared = rollbackShared
|
|
869
|
-
// don't attest grants that were just undone
|
|
870
|
-
this.writers.attestations.length = rollbackAttestations
|
|
871
1076
|
return false
|
|
872
1077
|
}
|
|
873
1078
|
}
|
|
@@ -888,7 +1093,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
888
1093
|
const t = await topo.sort(this, batch)
|
|
889
1094
|
|
|
890
1095
|
if (t.undo) {
|
|
1096
|
+
this.stats.undos++
|
|
1097
|
+
this.trusted.clear()
|
|
891
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
|
+
}
|
|
892
1102
|
}
|
|
893
1103
|
|
|
894
1104
|
return t
|
|
@@ -908,7 +1118,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
908
1118
|
}
|
|
909
1119
|
|
|
910
1120
|
if (b4a.equals(batch[0].key, this.key) && batch[0].length === 1) {
|
|
911
|
-
await this._host.addWriter(batch[0].key)
|
|
1121
|
+
await this._host.addWriter(batch[0].key, { weight: this.bootstrapWeight })
|
|
912
1122
|
}
|
|
913
1123
|
|
|
914
1124
|
await this._applyBatch(batch, batch[0].optimistic)
|
|
@@ -931,33 +1141,39 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
931
1141
|
for (const node of batch) {
|
|
932
1142
|
this.system.addNode(node)
|
|
933
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
|
+
|
|
934
1155
|
// compat: autobase nodes may be null (legacy null decodes to 0-length buffer)
|
|
935
1156
|
if (node.value && node.value.length) userBatch.push(node)
|
|
936
1157
|
}
|
|
937
1158
|
|
|
938
1159
|
if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
|
|
1160
|
+
this.stats.applies++
|
|
939
1161
|
this._host.applying = batch
|
|
940
|
-
await this.
|
|
1162
|
+
await this._workingView.apply(userBatch)
|
|
941
1163
|
this._host.applying = null
|
|
942
1164
|
}
|
|
943
1165
|
|
|
944
|
-
const
|
|
1166
|
+
const changed = await this.system.flush(batch, this._workingBee)
|
|
1167
|
+
|
|
1168
|
+
if (this.system.promotions.changed) this._prefetchApprovals().catch(safetyCatch)
|
|
945
1169
|
|
|
946
1170
|
if (local) {
|
|
947
1171
|
this._localSystemLength = this.system.bee.context.local.length - this._localSystemStart
|
|
948
1172
|
this._localViewLength = this._workingBee.context.local.length - this._localViewStart
|
|
1173
|
+
this._localFlushes = this.system.flushes
|
|
949
1174
|
}
|
|
950
1175
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
await this._storeBoot()
|
|
954
|
-
|
|
955
|
-
for (const { key, added, isAnchor } of changed) {
|
|
956
|
-
if (isAnchor) {
|
|
957
|
-
// no Writer/ActiveWriters tracking, but still wake anything linked to it
|
|
958
|
-
this.writers.triggers.trigger(b4a.toString(key, 'hex'), 1)
|
|
959
|
-
continue
|
|
960
|
-
}
|
|
1176
|
+
for (const { key, added } of changed) {
|
|
961
1177
|
if (added) await this.writers.add(key)
|
|
962
1178
|
else await this.writers.remove(key)
|
|
963
1179
|
}
|
|
@@ -980,6 +1196,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
980
1196
|
return Promise.all(proms)
|
|
981
1197
|
}
|
|
982
1198
|
|
|
1199
|
+
_storeMigratedHead() {
|
|
1200
|
+
const value = this._migratedHead ? encoding.encodeMigratedHead(this._migratedHead) : null
|
|
1201
|
+
return this.local.setUserData('autobee/migrated-head', value)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
983
1204
|
static decodeValue(buf, opts) {
|
|
984
1205
|
return encoding.decodeValue(buf, opts)
|
|
985
1206
|
}
|
|
@@ -997,6 +1218,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
997
1218
|
async append(values, { optimistic = false } = {}) {
|
|
998
1219
|
if (this.closing) throw new Error('Autobee closed')
|
|
999
1220
|
|
|
1221
|
+
this.stats.appends++
|
|
1222
|
+
|
|
1000
1223
|
if (!Array.isArray(values)) values = [values]
|
|
1001
1224
|
|
|
1002
1225
|
if (!this.opened) await this.ready()
|
|
@@ -1013,27 +1236,17 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1013
1236
|
const t = Math.max(this._now(), this.system.timestamp)
|
|
1014
1237
|
const batch = []
|
|
1015
1238
|
|
|
1016
|
-
// witnesses only ride upgrade windows. witness.weight is the value the backer's
|
|
1017
|
-
// snapshot witnesses - verifiers recompute the same read and verify
|
|
1018
1239
|
const rec = await this.system.get(this.local.key)
|
|
1019
1240
|
let witness = null
|
|
1020
|
-
if (rec
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
for (const { key, length, attestation, manifest } of sorted) {
|
|
1026
|
-
if (attestation.weight > rec.maxWeight) continue
|
|
1027
|
-
|
|
1028
|
-
const backer = await this.system.get(key)
|
|
1029
|
-
if (!backer || backer.isRemoved || backer.length < length) continue
|
|
1030
|
-
|
|
1031
|
-
const { weight, signature } = attestation
|
|
1032
|
-
witness = { weight, backer: { key, length, signature, manifest } }
|
|
1033
|
-
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 } }
|
|
1034
1245
|
}
|
|
1035
1246
|
}
|
|
1036
1247
|
|
|
1248
|
+
const approvals = optimistic ? null : await this._collectApprovals()
|
|
1249
|
+
|
|
1037
1250
|
for (let i = 0; i < values.length; i++) {
|
|
1038
1251
|
const value = values[i]
|
|
1039
1252
|
const buffer = typeof value === 'string' ? b4a.from(value) : value
|
|
@@ -1044,7 +1257,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1044
1257
|
{ start: i, end: values.length - 1 - i },
|
|
1045
1258
|
lnk,
|
|
1046
1259
|
optimistic,
|
|
1047
|
-
i === 0 ? witness : null
|
|
1260
|
+
i === 0 ? witness : null,
|
|
1261
|
+
i === 0 ? approvals : null
|
|
1048
1262
|
)
|
|
1049
1263
|
batch.push(node)
|
|
1050
1264
|
}
|
|
@@ -1053,8 +1267,13 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1053
1267
|
}
|
|
1054
1268
|
|
|
1055
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
|
+
|
|
1056
1275
|
await this.writers.flushLocal({
|
|
1057
|
-
flushes: this.
|
|
1276
|
+
flushes: this._localFlushes,
|
|
1058
1277
|
system: {
|
|
1059
1278
|
key: this.system.bee.context.local.key,
|
|
1060
1279
|
start: this._localSystemStart,
|
|
@@ -1069,133 +1288,166 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1069
1288
|
|
|
1070
1289
|
this._localSystemStart = this.system.bee.context.local.length
|
|
1071
1290
|
this._localSystemLength = 0
|
|
1291
|
+
this._localFlushes = this.system.flushes
|
|
1072
1292
|
this._localViewStart = this._workingBee.context.local.length
|
|
1073
1293
|
this._localViewLength = 0
|
|
1074
1294
|
}
|
|
1075
1295
|
|
|
1076
|
-
async
|
|
1077
|
-
if (!this.
|
|
1078
|
-
if (this.
|
|
1079
|
-
if (!hints.size || this.rebooting || this.rebootTo || this.bootFrom) {
|
|
1080
|
-
return false
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
const promises = []
|
|
1084
|
-
const heads = []
|
|
1085
|
-
for (const [hex, length] of hints) {
|
|
1086
|
-
if (length === 0) continue
|
|
1087
|
-
const key = b4a.from(hex, 'hex')
|
|
1088
|
-
heads.push({ key, length })
|
|
1089
|
-
promises.push(this._getOplog(key, length))
|
|
1090
|
-
}
|
|
1296
|
+
async moveTo(head) {
|
|
1297
|
+
if (!this.opened) await this.ready()
|
|
1298
|
+
if (this.closing) throw new Error('Autobee closed')
|
|
1091
1299
|
|
|
1092
|
-
const
|
|
1093
|
-
if (
|
|
1300
|
+
const ff = await FastForward.fromHead(this, head, null, { force: true })
|
|
1301
|
+
if (ff === null) return null
|
|
1094
1302
|
|
|
1095
|
-
|
|
1096
|
-
let bestFlushes = -1
|
|
1303
|
+
if (!(await this._runFastForward(ff))) return null
|
|
1097
1304
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
if (res === null) continue
|
|
1305
|
+
return this.ff.promise
|
|
1306
|
+
}
|
|
1101
1307
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}
|
|
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
|
|
1106
1316
|
}
|
|
1317
|
+
}
|
|
1107
1318
|
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
+
}
|
|
1110
1335
|
}
|
|
1111
1336
|
|
|
1112
|
-
|
|
1337
|
+
// park until the head can be read and, if a condition is set, something
|
|
1338
|
+
// acceptable turns up
|
|
1339
|
+
let opened = null
|
|
1113
1340
|
|
|
1114
|
-
|
|
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
|
+
}
|
|
1115
1372
|
|
|
1116
|
-
|
|
1117
|
-
const view = this.bee.checkout({ key: v.key, length: v.start + v.length })
|
|
1373
|
+
if (this._interrupting) break
|
|
1118
1374
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
trusted = await this._handlers.onwakeup(head, view, this)
|
|
1122
|
-
if (!trusted || this.rebooting || this.rebootTo) return false
|
|
1375
|
+
await this._bootWait.promise
|
|
1376
|
+
}
|
|
1123
1377
|
} finally {
|
|
1124
|
-
|
|
1378
|
+
this._bootWait = null
|
|
1379
|
+
if (opened !== null) await opened.close()
|
|
1125
1380
|
}
|
|
1126
1381
|
|
|
1127
|
-
return
|
|
1382
|
+
return false
|
|
1128
1383
|
}
|
|
1129
1384
|
|
|
1130
|
-
async
|
|
1131
|
-
|
|
1132
|
-
|
|
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)
|
|
1133
1388
|
|
|
1134
|
-
const
|
|
1389
|
+
const candidates = await this._readCandidateHeads(result, FastForward.DEFAULT_TIMEOUT)
|
|
1135
1390
|
|
|
1136
|
-
|
|
1391
|
+
const ff = await FastForward.fromHeads(this, [head, ...candidates], {
|
|
1392
|
+
force: true,
|
|
1393
|
+
timeout: FastForward.DEFAULT_TIMEOUT,
|
|
1394
|
+
condition: bootCondition,
|
|
1395
|
+
reference
|
|
1396
|
+
})
|
|
1137
1397
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1398
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1399
|
+
}
|
|
1141
1400
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
op: trusted || head,
|
|
1146
|
-
flushes: verified.op.views.flushes
|
|
1147
|
-
}
|
|
1401
|
+
async _bootLegacyHead(head) {
|
|
1402
|
+
const oplog = await FastForward.flushHead(this, head, {
|
|
1403
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1148
1404
|
})
|
|
1405
|
+
if (oplog === null || oplog.op.version > LEGACY_OPLOG_VERSION) return false
|
|
1149
1406
|
|
|
1150
|
-
|
|
1407
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1408
|
+
force: true,
|
|
1409
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1410
|
+
})
|
|
1151
1411
|
|
|
1152
|
-
return null
|
|
1412
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1153
1413
|
}
|
|
1154
1414
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
return this._runReboot(new Reboot(this, head, tip))
|
|
1160
|
-
}
|
|
1415
|
+
async _bootReference(head) {
|
|
1416
|
+
const oplog = await FastForward.flushHead(this, head, {
|
|
1417
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1418
|
+
})
|
|
1161
1419
|
|
|
1162
|
-
return
|
|
1420
|
+
return oplog === null ? null : this.openViewAt(oplog)
|
|
1163
1421
|
}
|
|
1164
1422
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (await this._runReboot(new Reboot(this, systemHead, tip))) {
|
|
1170
|
-
return true
|
|
1423
|
+
async _runFastForward(ff) {
|
|
1424
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) {
|
|
1425
|
+
await ff.close()
|
|
1426
|
+
return false
|
|
1171
1427
|
}
|
|
1172
1428
|
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
async _runReboot(reboot) {
|
|
1177
|
-
this.rebooting = reboot
|
|
1429
|
+
this.fastForwarding = ff
|
|
1178
1430
|
|
|
1179
|
-
const result = await
|
|
1180
|
-
await
|
|
1431
|
+
const result = await ff.run()
|
|
1432
|
+
await ff.close()
|
|
1181
1433
|
|
|
1182
|
-
if (this.
|
|
1434
|
+
if (this.fastForwarding === ff) this.fastForwarding = null
|
|
1183
1435
|
|
|
1184
1436
|
if (!result) return false
|
|
1185
1437
|
|
|
1186
|
-
this.
|
|
1187
|
-
this.
|
|
1438
|
+
this.fastForwardTo = result
|
|
1439
|
+
this.ff = rrp()
|
|
1188
1440
|
|
|
1189
1441
|
this.bumpSoon()
|
|
1190
1442
|
|
|
1191
1443
|
return true
|
|
1192
1444
|
}
|
|
1193
1445
|
|
|
1194
|
-
async
|
|
1446
|
+
async _applyFastForward() {
|
|
1195
1447
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1196
1448
|
if (changes) changes.track()
|
|
1197
1449
|
|
|
1198
|
-
const { head,
|
|
1450
|
+
const { head, migrate } = this.fastForwardTo
|
|
1199
1451
|
|
|
1200
1452
|
const from = this.system.bee.head()
|
|
1201
1453
|
const to = head
|
|
@@ -1203,15 +1455,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1203
1455
|
this.system.bee.move(head)
|
|
1204
1456
|
await this.system.reset()
|
|
1205
1457
|
|
|
1206
|
-
this.system.shared = this.rebootTo.shared || {
|
|
1207
|
-
flushes: -1,
|
|
1208
|
-
view: EMPTY_HEAD,
|
|
1209
|
-
system: EMPTY_HEAD
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
1458
|
// migrate is set when fast-forwarding from a legacy head
|
|
1213
1459
|
if (migrate) {
|
|
1214
|
-
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()
|
|
1215
1468
|
this.bee.move(view)
|
|
1216
1469
|
this._workingBee.move(view)
|
|
1217
1470
|
} else {
|
|
@@ -1219,34 +1472,22 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1219
1472
|
this._workingBee.move(this.system.view)
|
|
1220
1473
|
}
|
|
1221
1474
|
|
|
1222
|
-
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()
|
|
1223
1480
|
await this.writers.refresh()
|
|
1224
1481
|
|
|
1482
|
+
// we moved, so ask our peers to tell us their heads again
|
|
1483
|
+
this._requestWakeup()
|
|
1484
|
+
|
|
1225
1485
|
await this._update(changes)
|
|
1486
|
+
await this._storeBoot()
|
|
1226
1487
|
|
|
1488
|
+
this.stats.fastForwards++
|
|
1227
1489
|
this.emit('move-to', to, from)
|
|
1228
|
-
this.
|
|
1229
|
-
|
|
1230
|
-
// tip is null during boot
|
|
1231
|
-
if (!tip) return
|
|
1232
|
-
|
|
1233
|
-
try {
|
|
1234
|
-
await this._reapply(tip)
|
|
1235
|
-
} catch (err) {
|
|
1236
|
-
throw err
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
async _reapply({ system, verified }) {
|
|
1241
|
-
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1242
|
-
if (changes) changes.track()
|
|
1243
|
-
|
|
1244
|
-
const sys = this.system.bee.checkout(system)
|
|
1245
|
-
const t = await topo.rollback(this, sys, verified)
|
|
1246
|
-
await sys.close()
|
|
1247
|
-
|
|
1248
|
-
await this.applyBacklog(t.tip)
|
|
1249
|
-
return this._update(changes)
|
|
1490
|
+
this.ff.resolve({ to, from })
|
|
1250
1491
|
}
|
|
1251
1492
|
|
|
1252
1493
|
replay() {
|
|
@@ -1258,6 +1499,17 @@ function isObject(o) {
|
|
|
1258
1499
|
return typeof o === 'object' && o && !b4a.isBuffer(o)
|
|
1259
1500
|
}
|
|
1260
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
|
+
|
|
1261
1513
|
function noop() {}
|
|
1262
1514
|
|
|
1263
1515
|
function createAnchorCore(store, prologue, manifestData) {
|
|
@@ -1286,10 +1538,3 @@ function crashSoon(err) {
|
|
|
1286
1538
|
})
|
|
1287
1539
|
throw err
|
|
1288
1540
|
}
|
|
1289
|
-
|
|
1290
|
-
function batchToHead(b) {
|
|
1291
|
-
return {
|
|
1292
|
-
key: b.key,
|
|
1293
|
-
length: b.start + b.length
|
|
1294
|
-
}
|
|
1295
|
-
}
|