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