autobee 2.0.0-rc.2 → 2.0.0-rc.21
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 +3 -3
- package/encoding/spec/autobee/index.js +104 -22
- package/encoding/spec/autobee/schema.json +63 -1
- package/index.js +392 -197
- package/lib/apply-calls.js +5 -0
- package/lib/apply-view.js +25 -0
- package/lib/boot.js +6 -1
- package/lib/encoding.js +12 -0
- package/lib/{reboot.js → fast-forward.js} +213 -19
- package/lib/system.js +38 -62
- package/lib/topo.js +121 -18
- package/lib/trusted.js +44 -0
- package/lib/updates.js +1 -38
- package/lib/writers.js +90 -15
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -14,16 +14,17 @@ 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 FastForward = require('./lib/fast-forward.js')
|
|
18
18
|
const System = require('./lib/system.js')
|
|
19
19
|
const ApplyCalls = require('./lib/apply-calls.js')
|
|
20
20
|
const topo = require('./lib/topo.js')
|
|
21
21
|
const { ActiveWriters } = require('./lib/writers.js')
|
|
22
|
+
const TrustedPeers = require('./lib/trusted.js')
|
|
23
|
+
const ApplyView = require('./lib/apply-view.js')
|
|
22
24
|
const UpdateChanges = require('./lib/updates.js')
|
|
23
25
|
|
|
24
26
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
25
27
|
const INTERRUPT = new Error('Apply interrupted')
|
|
26
|
-
const MIN_FF_GAP = 32
|
|
27
28
|
|
|
28
29
|
module.exports = class Autobee extends ReadyResource {
|
|
29
30
|
constructor(store, key = null, handlers = {}) {
|
|
@@ -56,7 +57,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
56
57
|
this.discoveryKey = null
|
|
57
58
|
this.id = null
|
|
58
59
|
this.bootstrap = null
|
|
59
|
-
this.
|
|
60
|
+
this._handlers = handlers
|
|
61
|
+
this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }
|
|
60
62
|
|
|
61
63
|
this.system = new System(this.store.namespace('system'), this.name, {
|
|
62
64
|
getEncryptionProvider: this.getSystemEncryption,
|
|
@@ -65,7 +67,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
65
67
|
this.system.auto = this
|
|
66
68
|
|
|
67
69
|
this.bee = bee.snapshot()
|
|
68
|
-
this.view =
|
|
70
|
+
this.view = ApplyView.open(this.bee, this)
|
|
69
71
|
this.optimistic = handlers.optimistic !== false // TODO: should default to false instead
|
|
70
72
|
|
|
71
73
|
this.name = name // for debugging
|
|
@@ -76,23 +78,32 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
76
78
|
this.writers = null
|
|
77
79
|
this.bumping = 0
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
const fastForward = handlers.fastForward || {}
|
|
82
|
+
|
|
83
|
+
// oplog head to boot from: migrates or fast-forwards depending on its version
|
|
84
|
+
this.bootFrom = fastForward.boot || null
|
|
85
|
+
|
|
86
|
+
// conservative (default on): only fast-forward onto a head someone can serve whole
|
|
87
|
+
this._conservativeFF = fastForward.conservative !== false
|
|
88
|
+
|
|
89
|
+
this.trusted = new TrustedPeers(handlers)
|
|
81
90
|
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
91
|
+
this.ff = null
|
|
92
|
+
this.fastForwarding = null
|
|
93
|
+
this.fastForwardTo = null
|
|
85
94
|
|
|
86
95
|
this._workingBee = bee
|
|
87
|
-
this._workingView =
|
|
96
|
+
this._workingView = new ApplyView(this._workingBee, this)
|
|
88
97
|
|
|
89
98
|
this._localSystemStart = 0
|
|
90
99
|
this._localSystemLength = 0
|
|
100
|
+
this._localFlushes = 0
|
|
91
101
|
this._localViewStart = 0
|
|
92
102
|
this._localViewLength = 0
|
|
93
103
|
|
|
94
104
|
this._appending = []
|
|
95
105
|
this._draining = null
|
|
106
|
+
this._bootWait = null
|
|
96
107
|
|
|
97
108
|
this.legacyViews = handlers.legacyViews || []
|
|
98
109
|
|
|
@@ -100,11 +111,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
100
111
|
this._bootingState = null
|
|
101
112
|
this._bootingAll = null
|
|
102
113
|
|
|
103
|
-
this._handlers = handlers
|
|
104
114
|
this._now = handlers.now || Date.now // overridable for clock-drift tests
|
|
105
115
|
this._hasApply = !!handlers.apply
|
|
106
116
|
this._hasUpdate = !!handlers.update
|
|
107
117
|
this._needsUpdate = false
|
|
118
|
+
this._ackRequired = false
|
|
119
|
+
this._ackedHeads = new Map()
|
|
108
120
|
this._updateLocalCore = null
|
|
109
121
|
this._host = new ApplyCalls(this)
|
|
110
122
|
this._notifyHandler = null
|
|
@@ -120,6 +132,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
120
132
|
this.previousDrain = 0
|
|
121
133
|
|
|
122
134
|
this._catchupMigratedNodes = null
|
|
135
|
+
this._migratedHead = null
|
|
123
136
|
|
|
124
137
|
this.ready().catch(noop)
|
|
125
138
|
}
|
|
@@ -161,10 +174,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
161
174
|
|
|
162
175
|
this._localSystemStart = this.system.bee.context.local.length
|
|
163
176
|
this._localViewStart = this._workingBee.context.local.length
|
|
177
|
+
this._localFlushes = this.system.flushes
|
|
164
178
|
|
|
165
179
|
this.bumpSoon()
|
|
166
180
|
}
|
|
167
181
|
|
|
182
|
+
_requestWakeup() {
|
|
183
|
+
const session = this._wakeup._session
|
|
184
|
+
if (session) session.broadcastLookup()
|
|
185
|
+
}
|
|
186
|
+
|
|
168
187
|
_registerWakeup() {
|
|
169
188
|
if (!this.wakeupCapability) return
|
|
170
189
|
this._wakeup.recouple()
|
|
@@ -209,6 +228,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
209
228
|
|
|
210
229
|
async _close() {
|
|
211
230
|
this._interrupting = true
|
|
231
|
+
if (this._bootWait !== null) this._bootWait.resolve()
|
|
212
232
|
if (this._notifyHandler) this._notifyHandler.destroy()
|
|
213
233
|
if (this._draining) {
|
|
214
234
|
// drain may be waiting on replicated blocks
|
|
@@ -216,18 +236,21 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
216
236
|
await this._draining
|
|
217
237
|
}
|
|
218
238
|
|
|
239
|
+
if (this._updating) await this._updating
|
|
240
|
+
|
|
219
241
|
// let in-flight writer adds finish
|
|
220
242
|
try {
|
|
221
243
|
await this._bootingAll
|
|
222
244
|
} catch {}
|
|
223
245
|
|
|
224
|
-
|
|
246
|
+
await ApplyView.close(this.view, this)
|
|
225
247
|
|
|
226
248
|
if (this.writers) await this.writers.close()
|
|
227
249
|
await this.local.close()
|
|
228
250
|
await this.system.close()
|
|
229
251
|
await this._wakeup.close()
|
|
230
|
-
await this.
|
|
252
|
+
if (this.bootstrap && !this.bootstrap.closed) await this.bootstrap.close()
|
|
253
|
+
await this._workingView.close()
|
|
231
254
|
await this.bee.close()
|
|
232
255
|
await this.store.close()
|
|
233
256
|
}
|
|
@@ -239,20 +262,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
239
262
|
session.clearRequests(session.activeRequests, closingError)
|
|
240
263
|
}
|
|
241
264
|
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async changesFrom({ system } = {}) {
|
|
245
|
-
const info = await this.system.getInfo()
|
|
246
|
-
|
|
247
|
-
const current = {
|
|
248
|
-
flushes: info ? info.flushes : 0,
|
|
249
|
-
system: this.system.bee.head(),
|
|
250
|
-
view: info ? info.view : EMPTY_HEAD
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const shared = await this.system.commonAncestor(system || EMPTY_HEAD)
|
|
254
265
|
|
|
255
|
-
|
|
266
|
+
if (this.fastForwarding) this.fastForwarding.clearRequests(closingError)
|
|
256
267
|
}
|
|
257
268
|
|
|
258
269
|
replicate(...args) {
|
|
@@ -269,6 +280,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
269
280
|
this._wakeup.hint(wakeup)
|
|
270
281
|
}
|
|
271
282
|
|
|
283
|
+
// the view an oplog head points at, opened and closed through the handlers
|
|
284
|
+
openViewAt(oplog) {
|
|
285
|
+
const v = oplog.op.views.view
|
|
286
|
+
if (!v) return null
|
|
287
|
+
|
|
288
|
+
return new ApplyView(this.bee.checkout({ key: v.key, length: v.start + v.length }), this)
|
|
289
|
+
}
|
|
290
|
+
|
|
272
291
|
openCore(key) {
|
|
273
292
|
const encryption = this.encryptionKey ? new WriterEncryption(this) : null
|
|
274
293
|
return this.store.get({ key, encryption })
|
|
@@ -294,7 +313,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
294
313
|
}
|
|
295
314
|
|
|
296
315
|
if (this.bootFrom) {
|
|
297
|
-
this.bootFrom = (await this.bootFrom)
|
|
316
|
+
this.bootFrom = getBootOption(await this.bootFrom)
|
|
298
317
|
}
|
|
299
318
|
}
|
|
300
319
|
|
|
@@ -372,15 +391,26 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
372
391
|
|
|
373
392
|
await this.system.boot(system)
|
|
374
393
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
394
|
+
const migrated = await this.local.getUserData('autobee/migrated-head')
|
|
395
|
+
if (migrated) this._migratedHead = encoding.decodeMigratedHead(migrated)
|
|
396
|
+
|
|
397
|
+
let view = this.system.view
|
|
398
|
+
if (!view) view = (this._migratedHead && this._migratedHead.view) || EMPTY_HEAD
|
|
378
399
|
|
|
379
400
|
// @todo migration
|
|
380
401
|
if (result.migration) {
|
|
381
|
-
if (this.
|
|
382
|
-
view =
|
|
402
|
+
if (this._handlers.migrate) {
|
|
403
|
+
view =
|
|
404
|
+
(await this._handlers.migrate(result.migration.views, result.migration.system)) ||
|
|
405
|
+
EMPTY_HEAD
|
|
383
406
|
this._catchupMigratedNodes = result.migration.catchup
|
|
407
|
+
|
|
408
|
+
this._migratedHead = {
|
|
409
|
+
system: result.migration.system,
|
|
410
|
+
view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
await this._storeMigratedHead()
|
|
384
414
|
}
|
|
385
415
|
|
|
386
416
|
// ff boot invalidated by migration
|
|
@@ -420,7 +450,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
420
450
|
async _bump() {
|
|
421
451
|
if (!(await this._bootReady())) return
|
|
422
452
|
|
|
423
|
-
|
|
453
|
+
if (this._bootWait !== null) this._bootWait.resolve()
|
|
424
454
|
|
|
425
455
|
this.bumping++
|
|
426
456
|
|
|
@@ -476,13 +506,20 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
476
506
|
}
|
|
477
507
|
|
|
478
508
|
async _drain() {
|
|
509
|
+
if (this._updating) await this._updating
|
|
510
|
+
|
|
511
|
+
this.stats.drains++
|
|
512
|
+
|
|
479
513
|
if (this.bootFrom) {
|
|
480
|
-
|
|
514
|
+
const { head = null, legacy = null, bootCondition = null } = this.bootFrom
|
|
515
|
+
|
|
481
516
|
this.bootFrom = null
|
|
517
|
+
|
|
518
|
+
if (legacy) await this._bootFromSystem(legacy)
|
|
519
|
+
else if (head) await this._bootFromHead(head, bootCondition)
|
|
482
520
|
}
|
|
483
521
|
|
|
484
|
-
|
|
485
|
-
this.system.shared = null
|
|
522
|
+
this._ackRequired = false
|
|
486
523
|
|
|
487
524
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
488
525
|
if (changes) changes.track()
|
|
@@ -498,16 +535,26 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
498
535
|
|
|
499
536
|
try {
|
|
500
537
|
while (!this._interrupting) {
|
|
501
|
-
|
|
502
|
-
|
|
538
|
+
await this._flushWakeup()
|
|
539
|
+
if (this._interrupting) break
|
|
540
|
+
|
|
541
|
+
if (this.fastForwardTo !== null) {
|
|
542
|
+
await this._applyFastForward()
|
|
503
543
|
break // revaluate conditions...
|
|
504
544
|
}
|
|
505
545
|
|
|
506
|
-
if (
|
|
546
|
+
if (await this._bumpPendingWriters()) {
|
|
547
|
+
this._needsUpdate = true
|
|
548
|
+
continue
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!this._appendAck()) break
|
|
507
552
|
this._needsUpdate = true
|
|
508
553
|
}
|
|
509
554
|
|
|
510
555
|
await this._flushLocal()
|
|
556
|
+
|
|
557
|
+
if (!this._interrupting) await this.writers.refresh()
|
|
511
558
|
} finally {
|
|
512
559
|
if (this.bumping === 1) this.bumping = 0
|
|
513
560
|
else this.bumping = 1
|
|
@@ -517,8 +564,15 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
517
564
|
this._draining = null
|
|
518
565
|
if (this._interrupting) return
|
|
519
566
|
|
|
520
|
-
|
|
521
|
-
|
|
567
|
+
const updating = rrp()
|
|
568
|
+
this._updating = updating.promise
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
if (this._needsUpdate) await this._update(changes)
|
|
572
|
+
await this._storeBoot()
|
|
573
|
+
} finally {
|
|
574
|
+
this._updating = null
|
|
575
|
+
updating.resolve()
|
|
522
576
|
}
|
|
523
577
|
}
|
|
524
578
|
|
|
@@ -551,71 +605,153 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
551
605
|
}
|
|
552
606
|
}
|
|
553
607
|
|
|
554
|
-
|
|
608
|
+
// wakeup hints keep arriving asynchronously
|
|
609
|
+
async _applyWakeupHints() {
|
|
555
610
|
const hints = this._wakeup.flush()
|
|
611
|
+
if (!hints.size) return hints
|
|
556
612
|
|
|
557
613
|
this.previousDrain = Date.now()
|
|
558
|
-
this.rebootFromHeads(hints).catch(noop)
|
|
559
614
|
|
|
560
615
|
for (const [hex, length] of hints) {
|
|
561
616
|
const key = b4a.from(hex, 'hex')
|
|
562
|
-
|
|
617
|
+
// wakeup() itself no-ops the add for an already-active writer, but still
|
|
618
|
+
// needs to run so it can hint() the announced length - skipping it here
|
|
619
|
+
// for active writers left gc with no hint to protect them
|
|
563
620
|
await this.writers.wakeup(key, length)
|
|
564
621
|
}
|
|
622
|
+
|
|
623
|
+
return hints
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async _flushWakeup() {
|
|
627
|
+
const hints = await this._applyWakeupHints()
|
|
628
|
+
if (!hints.size) return
|
|
629
|
+
|
|
630
|
+
// a scheduled fast-forward is applied by the drain before we look again
|
|
631
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) return
|
|
632
|
+
if (this._interrupting || this.closing || this.bootFrom) return
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
const heads = await this._readCandidateHeads(hints, FastForward.DEFAULT_TIMEOUT)
|
|
636
|
+
|
|
637
|
+
const ff = await FastForward.fromHeads(this, heads, {
|
|
638
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
if (ff !== null) await this._runFastForward(ff)
|
|
642
|
+
} catch (err) {
|
|
643
|
+
safetyCatch(err)
|
|
644
|
+
}
|
|
565
645
|
}
|
|
566
646
|
|
|
567
|
-
async
|
|
647
|
+
async _readCandidateHeads(hints, timeout) {
|
|
648
|
+
const promises = []
|
|
649
|
+
|
|
650
|
+
for (const [hex, length] of hints) {
|
|
651
|
+
if (length === 0) continue
|
|
652
|
+
const key = b4a.from(hex, 'hex')
|
|
653
|
+
promises.push(this._getOplog(key, length, timeout ? { timeout } : null))
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const ops = await Promise.all(promises)
|
|
657
|
+
const heads = []
|
|
658
|
+
|
|
659
|
+
for (const res of ops) {
|
|
660
|
+
if (res === null) continue
|
|
661
|
+
|
|
662
|
+
// the head we were woken on is a candidate in its own right
|
|
663
|
+
heads.push({ key: res.key, length: res.length })
|
|
664
|
+
|
|
665
|
+
if (!res.op.trusted) continue
|
|
666
|
+
|
|
667
|
+
for (const trusted of res.op.trusted) {
|
|
668
|
+
const head = this.trusted.read(trusted)
|
|
669
|
+
if (head !== null) heads.push(head)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return heads
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async _getOplog(key, length, opts = null) {
|
|
568
677
|
const core = this.openCore(key)
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
return await this.readOplog(core, length, opts)
|
|
681
|
+
} finally {
|
|
682
|
+
await core.close()
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// reads on a core the caller owns, so a walk does not churn a session per step
|
|
687
|
+
async readOplog(core, length, opts = null) {
|
|
569
688
|
await core.ready()
|
|
570
689
|
|
|
571
690
|
const target = length >= 0 ? length : core.length
|
|
572
691
|
if (target === 0) return null
|
|
573
692
|
|
|
574
|
-
|
|
693
|
+
// conservative: only proceed when a connected peer can serve the head whole
|
|
694
|
+
if (opts && opts.conservative && core.remoteContiguousLength < target) return null
|
|
695
|
+
|
|
696
|
+
const buf = await core.get(target - 1, opts)
|
|
697
|
+
if (buf === null) return null
|
|
698
|
+
|
|
575
699
|
let op = encoding.decodeOplog(buf)
|
|
576
700
|
|
|
701
|
+
// legacy nodes always inflate, but only indexers carry views - callers
|
|
702
|
+
// that need system info must check op.views
|
|
577
703
|
if (op.version < 3) {
|
|
578
|
-
op = await this._inflateLegacyOplog(buf, core, target - 1)
|
|
704
|
+
op = await this._inflateLegacyOplog(buf, core, target - 1, opts)
|
|
579
705
|
}
|
|
580
706
|
|
|
581
|
-
await core.close()
|
|
582
|
-
|
|
583
|
-
if (buf === null) return null
|
|
584
|
-
|
|
585
707
|
return {
|
|
586
|
-
key,
|
|
708
|
+
key: core.key,
|
|
587
709
|
length: target,
|
|
588
710
|
op
|
|
589
711
|
}
|
|
590
712
|
}
|
|
591
713
|
|
|
592
|
-
async _inflateLegacyOplog(buf, core, seq) {
|
|
714
|
+
async _inflateLegacyOplog(buf, core, seq, opts = null) {
|
|
593
715
|
const m = encoding.decodeRawOplog(buf)
|
|
716
|
+
|
|
717
|
+
const op = {
|
|
718
|
+
version: m.version,
|
|
719
|
+
timestamp: 0,
|
|
720
|
+
links: m.node.heads,
|
|
721
|
+
batch: { start: 0, end: m.node.batch - 1 },
|
|
722
|
+
views: null,
|
|
723
|
+
optimistic: !!m.optimistic,
|
|
724
|
+
value: m.node.value
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (m.digest === null || m.checkpoint === null || !m.checkpoint.system) return op
|
|
728
|
+
|
|
594
729
|
const fetches = []
|
|
595
730
|
|
|
596
|
-
fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer) : buf)
|
|
597
|
-
fetches.push(
|
|
731
|
+
fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, opts) : buf)
|
|
732
|
+
fetches.push(
|
|
733
|
+
m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer, opts) : buf
|
|
734
|
+
)
|
|
598
735
|
|
|
599
736
|
const [digestNode, checkpointNode] = await Promise.all(fetches)
|
|
737
|
+
// a wait: false get we could not satisfy locally
|
|
738
|
+
if (digestNode === null || checkpointNode === null) return op
|
|
600
739
|
|
|
601
740
|
const { digest } = encoding.decodeRawOplog(digestNode)
|
|
602
741
|
const { checkpoint } = encoding.decodeRawOplog(checkpointNode)
|
|
603
742
|
|
|
604
|
-
return
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
key: digest.key,
|
|
612
|
-
length: checkpoint.system.checkpoint.length
|
|
613
|
-
},
|
|
614
|
-
flushes: seq
|
|
743
|
+
if (!checkpoint.system || !checkpoint.system.checkpoint) return op
|
|
744
|
+
|
|
745
|
+
op.views = {
|
|
746
|
+
system: {
|
|
747
|
+
key: digest.key,
|
|
748
|
+
start: 0,
|
|
749
|
+
length: checkpoint.system.checkpoint.length
|
|
615
750
|
},
|
|
616
|
-
|
|
617
|
-
value: m.node.value
|
|
751
|
+
flushes: seq
|
|
618
752
|
}
|
|
753
|
+
|
|
754
|
+
return op
|
|
619
755
|
}
|
|
620
756
|
|
|
621
757
|
async _update(changes) {
|
|
@@ -675,7 +811,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
675
811
|
await this.bootstrap.setUserData('autobee/local', this.local.key)
|
|
676
812
|
await oldLocal.close()
|
|
677
813
|
|
|
678
|
-
// done, soft
|
|
814
|
+
// done, soft restart
|
|
679
815
|
this.emit('rotate-local-writer')
|
|
680
816
|
}
|
|
681
817
|
|
|
@@ -739,9 +875,49 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
739
875
|
}
|
|
740
876
|
|
|
741
877
|
async _bumpMigratedWriters() {
|
|
878
|
+
const opened = new Set()
|
|
879
|
+
|
|
742
880
|
for (const batch of this._catchupMigratedNodes) {
|
|
743
881
|
await this._processBatch(batch)
|
|
882
|
+
for (const node of batch) {
|
|
883
|
+
if (node.from) opened.add(node.from)
|
|
884
|
+
}
|
|
744
885
|
}
|
|
886
|
+
|
|
887
|
+
for (const core of opened) await core.close()
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// append a null value node to ack writer
|
|
891
|
+
_appendAck() {
|
|
892
|
+
if (!this._ackRequired) return false
|
|
893
|
+
if (!this.writers.writable) return false
|
|
894
|
+
|
|
895
|
+
if (this.writers.localWriter.pending !== null) return false
|
|
896
|
+
|
|
897
|
+
const links = this.system.getLinks(this.local.key)
|
|
898
|
+
const t = Math.max(this._now(), this.system.timestamp)
|
|
899
|
+
|
|
900
|
+
let unlinked = false
|
|
901
|
+
for (const { key, length } of links) {
|
|
902
|
+
const hex = b4a.toString(key, 'hex')
|
|
903
|
+
const acked = this._ackedHeads.get(hex) || 0
|
|
904
|
+
if (length > acked) {
|
|
905
|
+
unlinked = true
|
|
906
|
+
break
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (!unlinked) {
|
|
910
|
+
this._ackRequired = false
|
|
911
|
+
return false
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
for (const { key, length } of links) {
|
|
915
|
+
this._ackedHeads.set(b4a.toString(key, 'hex'), length)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
this.writers.appendLocal(null, t, { start: 0, end: 0 }, links, false, null)
|
|
919
|
+
this._ackRequired = false
|
|
920
|
+
return true
|
|
745
921
|
}
|
|
746
922
|
|
|
747
923
|
async _bumpPendingWriters() {
|
|
@@ -750,41 +926,33 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
750
926
|
this._catchupMigratedNodes = null
|
|
751
927
|
}
|
|
752
928
|
|
|
753
|
-
|
|
929
|
+
// apply the best next node to keep the prefix stable
|
|
930
|
+
const next = await this.writers.nextPendingNode()
|
|
931
|
+
if (next === null) return false
|
|
754
932
|
|
|
755
|
-
const
|
|
933
|
+
const { writer: w, batch } = next
|
|
756
934
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
if (w.isAdded || (w.isRemoved && w.hasReferrals())) {
|
|
764
|
-
await this._processBatch(batch)
|
|
765
|
-
w.notify(batch)
|
|
766
|
-
updated = true
|
|
767
|
-
continue
|
|
768
|
-
}
|
|
935
|
+
if (w.isAdded || (w.isRemoved && w.hasReferrals())) {
|
|
936
|
+
await this._processBatch(batch)
|
|
937
|
+
w.notify(batch)
|
|
938
|
+
return true
|
|
939
|
+
}
|
|
769
940
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
w.notify(batch)
|
|
776
|
-
updated = true
|
|
777
|
-
continue
|
|
941
|
+
if (this.optimistic && !w.isRemoved && batch[0].optimistic) {
|
|
942
|
+
if (!(await this._optimisticBatch(batch))) {
|
|
943
|
+
w.removePending()
|
|
944
|
+
return true
|
|
778
945
|
}
|
|
946
|
+
w.notify(batch)
|
|
947
|
+
return true
|
|
779
948
|
}
|
|
780
949
|
|
|
781
|
-
return
|
|
950
|
+
return true
|
|
782
951
|
}
|
|
783
952
|
|
|
784
953
|
async _optimisticBatch(batch) {
|
|
785
954
|
const rollbackSystem = this.system.bee.head()
|
|
786
955
|
const rollbackView = this._workingBee.head()
|
|
787
|
-
const rollbackShared = this.system.shared
|
|
788
956
|
const rollbackAttestations = this.writers.attestations.length
|
|
789
957
|
|
|
790
958
|
const t = await this.prepareBatch(batch)
|
|
@@ -807,7 +975,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
807
975
|
this._workingBee.move(rollbackView)
|
|
808
976
|
this.system.bee.move(rollbackSystem)
|
|
809
977
|
await this.system.reset()
|
|
810
|
-
this.system.shared = rollbackShared
|
|
811
978
|
// don't attest grants that were just undone
|
|
812
979
|
this.writers.attestations.length = rollbackAttestations
|
|
813
980
|
return false
|
|
@@ -830,7 +997,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
830
997
|
const t = await topo.sort(this, batch)
|
|
831
998
|
|
|
832
999
|
if (t.undo) {
|
|
1000
|
+
this.stats.undos++
|
|
1001
|
+
this.trusted.clear()
|
|
833
1002
|
t.view = await this.system.undo(t.undo)
|
|
1003
|
+
if (!t.view.length && this._migratedHead && this._migratedHead.view) {
|
|
1004
|
+
t.view = this._migratedHead.view
|
|
1005
|
+
}
|
|
834
1006
|
}
|
|
835
1007
|
|
|
836
1008
|
return t
|
|
@@ -878,8 +1050,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
878
1050
|
}
|
|
879
1051
|
|
|
880
1052
|
if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
|
|
1053
|
+
this.stats.applies++
|
|
881
1054
|
this._host.applying = batch
|
|
882
|
-
await this.
|
|
1055
|
+
await this._workingView.apply(userBatch)
|
|
883
1056
|
this._host.applying = null
|
|
884
1057
|
}
|
|
885
1058
|
|
|
@@ -888,12 +1061,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
888
1061
|
if (local) {
|
|
889
1062
|
this._localSystemLength = this.system.bee.context.local.length - this._localSystemStart
|
|
890
1063
|
this._localViewLength = this._workingBee.context.local.length - this._localViewStart
|
|
1064
|
+
this._localFlushes = this.system.flushes
|
|
891
1065
|
}
|
|
892
1066
|
|
|
893
1067
|
this.writers.attest(witnessed)
|
|
894
1068
|
|
|
895
|
-
await this._storeBoot()
|
|
896
|
-
|
|
897
1069
|
for (const { key, added, isAnchor } of changed) {
|
|
898
1070
|
if (isAnchor) {
|
|
899
1071
|
// no Writer/ActiveWriters tracking, but still wake anything linked to it
|
|
@@ -922,6 +1094,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
922
1094
|
return Promise.all(proms)
|
|
923
1095
|
}
|
|
924
1096
|
|
|
1097
|
+
_storeMigratedHead() {
|
|
1098
|
+
const value = this._migratedHead ? encoding.encodeMigratedHead(this._migratedHead) : null
|
|
1099
|
+
return this.local.setUserData('autobee/migrated-head', value)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
925
1102
|
static decodeValue(buf, opts) {
|
|
926
1103
|
return encoding.decodeValue(buf, opts)
|
|
927
1104
|
}
|
|
@@ -939,6 +1116,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
939
1116
|
async append(values, { optimistic = false } = {}) {
|
|
940
1117
|
if (this.closing) throw new Error('Autobee closed')
|
|
941
1118
|
|
|
1119
|
+
this.stats.appends++
|
|
1120
|
+
|
|
942
1121
|
if (!Array.isArray(values)) values = [values]
|
|
943
1122
|
|
|
944
1123
|
if (!this.opened) await this.ready()
|
|
@@ -996,7 +1175,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
996
1175
|
|
|
997
1176
|
async _flushLocal() {
|
|
998
1177
|
await this.writers.flushLocal({
|
|
999
|
-
flushes: this.
|
|
1178
|
+
flushes: this._localFlushes,
|
|
1000
1179
|
system: {
|
|
1001
1180
|
key: this.system.bee.context.local.key,
|
|
1002
1181
|
start: this._localSystemStart,
|
|
@@ -1011,133 +1190,135 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1011
1190
|
|
|
1012
1191
|
this._localSystemStart = this.system.bee.context.local.length
|
|
1013
1192
|
this._localSystemLength = 0
|
|
1193
|
+
this._localFlushes = this.system.flushes
|
|
1014
1194
|
this._localViewStart = this._workingBee.context.local.length
|
|
1015
1195
|
this._localViewLength = 0
|
|
1016
1196
|
}
|
|
1017
1197
|
|
|
1018
|
-
async
|
|
1019
|
-
if (!this.
|
|
1020
|
-
if (this.
|
|
1021
|
-
if (!hints.size || this.rebooting || this.rebootTo || this.bootFrom) {
|
|
1022
|
-
return false
|
|
1023
|
-
}
|
|
1198
|
+
async moveTo(head) {
|
|
1199
|
+
if (!this.opened) await this.ready()
|
|
1200
|
+
if (this.closing) throw new Error('Autobee closed')
|
|
1024
1201
|
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
for (const [hex, length] of hints) {
|
|
1028
|
-
if (length === 0) continue
|
|
1029
|
-
const key = b4a.from(hex, 'hex')
|
|
1030
|
-
heads.push({ key, length })
|
|
1031
|
-
promises.push(this._getOplog(key, length))
|
|
1032
|
-
}
|
|
1202
|
+
const ff = await FastForward.fromHead(this, head, null, { force: true })
|
|
1203
|
+
if (ff === null) return null
|
|
1033
1204
|
|
|
1034
|
-
|
|
1035
|
-
if (this.rebooting || this.rebootTo) return false
|
|
1205
|
+
if (!(await this._runFastForward(ff))) return null
|
|
1036
1206
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1207
|
+
return this.ff.promise
|
|
1208
|
+
}
|
|
1039
1209
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1210
|
+
// legacy: ungated boot straight onto a system head
|
|
1211
|
+
async _bootFromSystem(system) {
|
|
1212
|
+
try {
|
|
1213
|
+
const ff = new FastForward(this, system, null, { timeout: FastForward.DEFAULT_TIMEOUT })
|
|
1214
|
+
return await this._runFastForward(ff)
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
safetyCatch(err)
|
|
1217
|
+
return false
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1043
1220
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1221
|
+
// the boot head seeds the view every trust decision here is made against,
|
|
1222
|
+
// since our own view is still empty
|
|
1223
|
+
async _bootFromHead(head, bootCondition) {
|
|
1224
|
+
// just a head: one attempt, we do not wait around if it cannot be read
|
|
1225
|
+
if (bootCondition === null) {
|
|
1226
|
+
try {
|
|
1227
|
+
const ff = await FastForward.fromHead(this, head, null, {
|
|
1228
|
+
force: true,
|
|
1229
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
safetyCatch(err)
|
|
1235
|
+
return false
|
|
1047
1236
|
}
|
|
1048
1237
|
}
|
|
1049
1238
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1239
|
+
// a condition needs the view at head to check against, so park until we
|
|
1240
|
+
// can read it and something acceptable turns up
|
|
1241
|
+
let opened = null
|
|
1053
1242
|
|
|
1054
|
-
|
|
1243
|
+
try {
|
|
1244
|
+
while (!this._interrupting) {
|
|
1245
|
+
this._bootWait = rrp()
|
|
1055
1246
|
|
|
1056
|
-
|
|
1247
|
+
try {
|
|
1248
|
+
if (opened === null) opened = await this._bootReference(head)
|
|
1057
1249
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1250
|
+
if (opened !== null && (await this._bootAttempt(head, bootCondition, opened.view))) {
|
|
1251
|
+
return true
|
|
1252
|
+
}
|
|
1253
|
+
} catch (err) {
|
|
1254
|
+
safetyCatch(err)
|
|
1255
|
+
}
|
|
1060
1256
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1257
|
+
if (this._interrupting) break
|
|
1258
|
+
|
|
1259
|
+
await this._bootWait.promise
|
|
1260
|
+
}
|
|
1065
1261
|
} finally {
|
|
1066
|
-
|
|
1262
|
+
this._bootWait = null
|
|
1263
|
+
if (opened !== null) await opened.close()
|
|
1067
1264
|
}
|
|
1068
1265
|
|
|
1069
|
-
return
|
|
1266
|
+
return false
|
|
1070
1267
|
}
|
|
1071
1268
|
|
|
1072
|
-
async
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
if (!verified.op.views) return null
|
|
1079
|
-
|
|
1080
|
-
if (!force && verified.op.views.flushes - this.system.flushes < MIN_FF_GAP) {
|
|
1081
|
-
return false
|
|
1082
|
-
}
|
|
1269
|
+
async _bootAttempt(head, bootCondition, reference) {
|
|
1270
|
+
// peek, so the hints are still there for the drain once we have booted
|
|
1271
|
+
const candidates = await this._readCandidateHeads(
|
|
1272
|
+
this._wakeup.hints,
|
|
1273
|
+
FastForward.DEFAULT_TIMEOUT
|
|
1274
|
+
)
|
|
1083
1275
|
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1276
|
+
const ff = await FastForward.fromHeads(this, [head, ...candidates], {
|
|
1277
|
+
force: true,
|
|
1278
|
+
timeout: FastForward.DEFAULT_TIMEOUT,
|
|
1279
|
+
condition: bootCondition,
|
|
1280
|
+
reference
|
|
1090
1281
|
})
|
|
1091
1282
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
return null
|
|
1283
|
+
return ff !== null && (await this._runFastForward(ff))
|
|
1095
1284
|
}
|
|
1096
1285
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
return this._runReboot(new Reboot(this, head, tip))
|
|
1102
|
-
}
|
|
1286
|
+
async _bootReference(head) {
|
|
1287
|
+
const oplog = await FastForward.flushHead(this, head, {
|
|
1288
|
+
timeout: FastForward.DEFAULT_TIMEOUT
|
|
1289
|
+
})
|
|
1103
1290
|
|
|
1104
|
-
return
|
|
1291
|
+
return oplog === null ? null : this.openViewAt(oplog)
|
|
1105
1292
|
}
|
|
1106
1293
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
if (await this._runReboot(new Reboot(this, systemHead, tip))) {
|
|
1112
|
-
return true
|
|
1294
|
+
async _runFastForward(ff) {
|
|
1295
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) {
|
|
1296
|
+
await ff.close()
|
|
1297
|
+
return false
|
|
1113
1298
|
}
|
|
1114
1299
|
|
|
1115
|
-
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
async _runReboot(reboot) {
|
|
1119
|
-
this.rebooting = reboot
|
|
1300
|
+
this.fastForwarding = ff
|
|
1120
1301
|
|
|
1121
|
-
const result = await
|
|
1122
|
-
await
|
|
1302
|
+
const result = await ff.run()
|
|
1303
|
+
await ff.close()
|
|
1123
1304
|
|
|
1124
|
-
if (this.
|
|
1305
|
+
if (this.fastForwarding === ff) this.fastForwarding = null
|
|
1125
1306
|
|
|
1126
1307
|
if (!result) return false
|
|
1127
1308
|
|
|
1128
|
-
this.
|
|
1129
|
-
this.
|
|
1309
|
+
this.fastForwardTo = result
|
|
1310
|
+
this.ff = rrp()
|
|
1130
1311
|
|
|
1131
1312
|
this.bumpSoon()
|
|
1132
1313
|
|
|
1133
1314
|
return true
|
|
1134
1315
|
}
|
|
1135
1316
|
|
|
1136
|
-
async
|
|
1317
|
+
async _applyFastForward() {
|
|
1137
1318
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1138
1319
|
if (changes) changes.track()
|
|
1139
1320
|
|
|
1140
|
-
const { head, tip, migrate } = this.
|
|
1321
|
+
const { head, tip, migrate } = this.fastForwardTo
|
|
1141
1322
|
|
|
1142
1323
|
const from = this.system.bee.head()
|
|
1143
1324
|
const to = head
|
|
@@ -1145,15 +1326,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1145
1326
|
this.system.bee.move(head)
|
|
1146
1327
|
await this.system.reset()
|
|
1147
1328
|
|
|
1148
|
-
this.system.shared = this.rebootTo.shared || {
|
|
1149
|
-
flushes: -1,
|
|
1150
|
-
view: EMPTY_HEAD,
|
|
1151
|
-
system: EMPTY_HEAD
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
1329
|
// migrate is set when fast-forwarding from a legacy head
|
|
1155
1330
|
if (migrate) {
|
|
1156
|
-
const view = (await this.
|
|
1331
|
+
const view = (await this._handlers.migrate(migrate, head)) || EMPTY_HEAD
|
|
1332
|
+
|
|
1333
|
+
this._migratedHead = {
|
|
1334
|
+
system: head,
|
|
1335
|
+
view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
await this._storeMigratedHead()
|
|
1157
1339
|
this.bee.move(view)
|
|
1158
1340
|
this._workingBee.move(view)
|
|
1159
1341
|
} else {
|
|
@@ -1161,13 +1343,21 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1161
1343
|
this._workingBee.move(this.system.view)
|
|
1162
1344
|
}
|
|
1163
1345
|
|
|
1164
|
-
this.
|
|
1346
|
+
this.fastForwardTo = null
|
|
1347
|
+
|
|
1348
|
+
// process any wakeup while fast-forward itself was in flight
|
|
1349
|
+
await this._applyWakeupHints()
|
|
1165
1350
|
await this.writers.refresh()
|
|
1166
1351
|
|
|
1352
|
+
// we moved, so ask our peers to tell us their heads again
|
|
1353
|
+
this._requestWakeup()
|
|
1354
|
+
|
|
1167
1355
|
await this._update(changes)
|
|
1356
|
+
await this._storeBoot()
|
|
1168
1357
|
|
|
1358
|
+
this.stats.fastForwards++
|
|
1169
1359
|
this.emit('move-to', to, from)
|
|
1170
|
-
this.
|
|
1360
|
+
this.ff.resolve({ to, from })
|
|
1171
1361
|
|
|
1172
1362
|
// tip is null during boot
|
|
1173
1363
|
if (!tip) return
|
|
@@ -1187,7 +1377,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1187
1377
|
const t = await topo.rollback(this, sys, verified)
|
|
1188
1378
|
await sys.close()
|
|
1189
1379
|
|
|
1190
|
-
await this.applyBacklog(t.tip)
|
|
1380
|
+
if (t !== null) await this.applyBacklog(t.tip)
|
|
1381
|
+
|
|
1191
1382
|
return this._update(changes)
|
|
1192
1383
|
}
|
|
1193
1384
|
|
|
@@ -1200,6 +1391,17 @@ function isObject(o) {
|
|
|
1200
1391
|
return typeof o === 'object' && o && !b4a.isBuffer(o)
|
|
1201
1392
|
}
|
|
1202
1393
|
|
|
1394
|
+
function getBootOption(boot) {
|
|
1395
|
+
if (!boot) return null
|
|
1396
|
+
|
|
1397
|
+
// oldest style, supported for now but will go away: a bare key is legacy
|
|
1398
|
+
if (boot.key) return { legacy: boot, head: null, bootCondition: null }
|
|
1399
|
+
|
|
1400
|
+
asserts.assert(!(boot.head && boot.legacy), 'Boot from either a head or a legacy pointer')
|
|
1401
|
+
|
|
1402
|
+
return boot
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1203
1405
|
function noop() {}
|
|
1204
1406
|
|
|
1205
1407
|
function createAnchorCore(store, prologue, manifestData) {
|
|
@@ -1228,10 +1430,3 @@ function crashSoon(err) {
|
|
|
1228
1430
|
})
|
|
1229
1431
|
throw err
|
|
1230
1432
|
}
|
|
1231
|
-
|
|
1232
|
-
function batchToHead(b) {
|
|
1233
|
-
return {
|
|
1234
|
-
key: b.key,
|
|
1235
|
-
length: b.start + b.length
|
|
1236
|
-
}
|
|
1237
|
-
}
|