autobee 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +217 -15
- package/lib/boot.js +7 -6
- package/lib/encoding.js +10 -0
- package/lib/fast-forward.js +71 -0
- package/lib/topo.js +27 -7
- package/lib/writers.js +23 -4
- package/package.json +5 -3
package/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const ReadyResource = require('ready-resource')
|
|
2
|
+
const ReadyGuard = require('ready-guard')
|
|
2
3
|
const b4a = require('b4a')
|
|
3
4
|
const safetyCatch = require('safety-catch')
|
|
4
5
|
const Hyperbee = require('hyperbee2')
|
|
5
6
|
const ID = require('hypercore-id-encoding')
|
|
7
|
+
const rrp = require('resolve-reject-promise')
|
|
6
8
|
const { AutobeeEncryption, WriterEncryption } = require('autobee-encryption')
|
|
7
9
|
const AutobeeWakeup = require('autobee-wakeup')
|
|
8
10
|
const Hypercore = require('hypercore')
|
|
@@ -11,6 +13,7 @@ const c = require('compact-encoding')
|
|
|
11
13
|
const asserts = require('./lib/asserts.js')
|
|
12
14
|
const boot = require('./lib/boot.js')
|
|
13
15
|
const encoding = require('./lib/encoding.js')
|
|
16
|
+
const FastForward = require('./lib/fast-forward.js')
|
|
14
17
|
const System = require('./lib/system.js')
|
|
15
18
|
const ApplyCalls = require('./lib/apply-calls.js')
|
|
16
19
|
const topo = require('./lib/topo.js')
|
|
@@ -19,6 +22,7 @@ const UpdateChanges = require('./lib/updates.js')
|
|
|
19
22
|
|
|
20
23
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
21
24
|
const INTERRUPT = new Error('Apply interrupted')
|
|
25
|
+
const MIN_FF_GAP = 32
|
|
22
26
|
|
|
23
27
|
module.exports = class Autobee extends ReadyResource {
|
|
24
28
|
constructor(store, key = null, handlers = {}) {
|
|
@@ -32,14 +36,15 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
32
36
|
const { name = null, encrypted, encryptionKey } = handlers
|
|
33
37
|
|
|
34
38
|
this.encrypted = encrypted === true || !!encryptionKey
|
|
39
|
+
this._getEncryptionProviderBound = this._getEncryptionProvider.bind(this)
|
|
35
40
|
|
|
36
41
|
const bee = new Hyperbee(store.namespace('view'), {
|
|
37
42
|
// defer one tick to ensure consistent state, then return state prom
|
|
38
43
|
preload: async () => {
|
|
39
44
|
await 1
|
|
40
|
-
await this.
|
|
45
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
41
46
|
},
|
|
42
|
-
getEncryptionProvider:
|
|
47
|
+
getEncryptionProvider: this._getEncryptionProviderBound
|
|
43
48
|
})
|
|
44
49
|
|
|
45
50
|
this.store = store
|
|
@@ -50,7 +55,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
50
55
|
this.bootstrap = null
|
|
51
56
|
|
|
52
57
|
this.system = new System(this.store.namespace('system'), this.name, {
|
|
53
|
-
getEncryptionProvider:
|
|
58
|
+
getEncryptionProvider: this._getEncryptionProviderBound,
|
|
54
59
|
encrypted: this.encrypted
|
|
55
60
|
})
|
|
56
61
|
|
|
@@ -66,12 +71,18 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
66
71
|
this.writers = null
|
|
67
72
|
this.bumping = 0
|
|
68
73
|
|
|
74
|
+
this.fastForwardBoot = handlers.fastForward || null
|
|
75
|
+
this.fastForward = null
|
|
76
|
+
this.fastForwarding = null
|
|
77
|
+
this.fastForwardTo = null
|
|
78
|
+
|
|
69
79
|
this._workingBee = bee
|
|
70
80
|
this._workingView = handlers.open ? handlers.open(this._workingBee, this) : this._workingBee
|
|
71
81
|
|
|
72
82
|
this._appending = []
|
|
73
83
|
this._draining = null
|
|
74
84
|
|
|
85
|
+
this._bootGuard = new ReadyGuard()
|
|
75
86
|
this._bootingState = null
|
|
76
87
|
this._bootingAll = null
|
|
77
88
|
|
|
@@ -81,13 +92,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
81
92
|
this._needsUpdate = false
|
|
82
93
|
this._updateLocalCore = null
|
|
83
94
|
this._host = new ApplyCalls(this)
|
|
95
|
+
this._notifyHandler = null
|
|
84
96
|
|
|
85
97
|
this.interrupted = null
|
|
86
98
|
this._interrupting = false
|
|
87
99
|
this._onErrorBound = this._onError.bind(this)
|
|
100
|
+
this._bumpSoonBound = this.bumpSoon.bind(this)
|
|
88
101
|
|
|
89
|
-
this._wakeup = new AutobeeWakeup(this, handlers)
|
|
90
102
|
this.wakeupCapability = null
|
|
103
|
+
this._wakeup = new AutobeeWakeup(this, handlers)
|
|
104
|
+
this.previousDrain = 0
|
|
91
105
|
|
|
92
106
|
this.ready().catch(noop)
|
|
93
107
|
}
|
|
@@ -127,6 +141,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
127
141
|
}
|
|
128
142
|
|
|
129
143
|
_registerWakeup() {
|
|
144
|
+
if (!this.wakeupCapability) return
|
|
130
145
|
this._wakeup.recouple()
|
|
131
146
|
this._wakeup.setCapability(this.wakeupCapability.key, this.wakeupCapability.discoveryKey)
|
|
132
147
|
}
|
|
@@ -140,7 +155,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
140
155
|
return keys
|
|
141
156
|
}
|
|
142
157
|
|
|
143
|
-
|
|
158
|
+
getWriterViews(key) {
|
|
144
159
|
const id = b4a.toString(key, 'hex')
|
|
145
160
|
const w = this.writers.active.get(id)
|
|
146
161
|
if (!w) return []
|
|
@@ -160,6 +175,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
160
175
|
|
|
161
176
|
async _close() {
|
|
162
177
|
this._interrupting = true
|
|
178
|
+
if (this._notifyHandler) this._notifyHandler.destroy()
|
|
163
179
|
if (this._draining) await this._draining
|
|
164
180
|
|
|
165
181
|
if (this._handlers.close) await this._handlers.close(this.view)
|
|
@@ -215,6 +231,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
async _bootState() {
|
|
234
|
+
if (!this._bootGuard.enter()) return this._bootGuard.ready()
|
|
235
|
+
|
|
218
236
|
const result = await boot(this.store, this.key, {
|
|
219
237
|
encryptionKey: this.encryptionKey,
|
|
220
238
|
keyPair: this.keyPair
|
|
@@ -225,6 +243,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
225
243
|
this.discoveryKey = result.bootstrap.core.discoveryKey
|
|
226
244
|
this.id = result.bootstrap.core.id
|
|
227
245
|
this.encryptionKey = result.encryptionKey
|
|
246
|
+
this.previousDrain = result.previousDrain
|
|
228
247
|
|
|
229
248
|
if (this.encrypted) {
|
|
230
249
|
asserts.assert(this.encryptionKey !== null, 'Encryption key is expected')
|
|
@@ -244,6 +263,15 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
244
263
|
|
|
245
264
|
this._registerWakeup()
|
|
246
265
|
|
|
266
|
+
if (this.wakeupCapability) {
|
|
267
|
+
if (this.bootstrap !== this.local) {
|
|
268
|
+
await this.bootstrap.setGroup(this.wakeupCapability.discoveryKey)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this._notifyHandler = this.store.notifyGroup(this.wakeupCapability.discoveryKey)
|
|
272
|
+
this._notifyHandler.on('update', this._bumpSoonBound)
|
|
273
|
+
}
|
|
274
|
+
|
|
247
275
|
const system = result.system || EMPTY_HEAD
|
|
248
276
|
|
|
249
277
|
await this.system.boot(system)
|
|
@@ -256,10 +284,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
256
284
|
this.bee.move(view)
|
|
257
285
|
|
|
258
286
|
await this.writers.updateLocalState()
|
|
287
|
+
|
|
288
|
+
this._bootGuard.exit()
|
|
289
|
+
|
|
290
|
+
return this._bootGuard.ready()
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
async _bootAll() {
|
|
262
|
-
await this.
|
|
294
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
263
295
|
|
|
264
296
|
for await (const node of this.system.list()) {
|
|
265
297
|
await this.writers.add(node.key)
|
|
@@ -272,7 +304,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
272
304
|
}
|
|
273
305
|
|
|
274
306
|
async _bump() {
|
|
307
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
308
|
+
|
|
275
309
|
await this._flushWakeup()
|
|
310
|
+
|
|
276
311
|
this.bumping++
|
|
277
312
|
|
|
278
313
|
if (!this._draining) {
|
|
@@ -327,18 +362,30 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
327
362
|
}
|
|
328
363
|
|
|
329
364
|
async _drain() {
|
|
330
|
-
if (this.
|
|
331
|
-
|
|
365
|
+
if (this.fastForwardBoot) {
|
|
366
|
+
this._initFastForward(this.fastForwardBoot)
|
|
367
|
+
this.fastForwardBoot = null
|
|
332
368
|
}
|
|
333
369
|
|
|
334
370
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
335
371
|
if (changes) changes.track()
|
|
336
372
|
|
|
373
|
+
// Anything expecting work to be done during bumpSoon should do it here
|
|
337
374
|
while (!this._interrupting && this.bumping > 0) {
|
|
338
|
-
if (this._interrupting)
|
|
375
|
+
if (this._interrupting) break
|
|
376
|
+
|
|
377
|
+
// Ensure we catch updates during the drain (i.e. setLocal will bump)
|
|
378
|
+
if (this._updateLocalCore !== null) {
|
|
379
|
+
await this._rotateLocalWriter(this._updateLocalCore)
|
|
380
|
+
}
|
|
339
381
|
|
|
340
382
|
try {
|
|
341
383
|
while (!this._interrupting) {
|
|
384
|
+
if (this.fastForwardTo !== null) {
|
|
385
|
+
await this._applyFastForward()
|
|
386
|
+
break // revaluate conditions...
|
|
387
|
+
}
|
|
388
|
+
|
|
342
389
|
if (!(await this._bumpPendingWriters())) break
|
|
343
390
|
this._needsUpdate = true
|
|
344
391
|
}
|
|
@@ -361,6 +408,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
361
408
|
async _flushWakeup() {
|
|
362
409
|
const hints = this._wakeup.flush()
|
|
363
410
|
|
|
411
|
+
if (this._notifyHandler) {
|
|
412
|
+
for await (const key of this._notifyHandler.updates({ since: this.previousDrain })) {
|
|
413
|
+
const hex = b4a.toString(key, 'hex')
|
|
414
|
+
hints.set(hex, -1)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this.previousDrain = Date.now()
|
|
419
|
+
this.queueWakeupFastForward(hints).catch(noop)
|
|
420
|
+
|
|
364
421
|
for (const [hex, length] of hints) {
|
|
365
422
|
const key = b4a.from(hex, 'hex')
|
|
366
423
|
if (this.writers.has(hex)) continue
|
|
@@ -372,6 +429,25 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
372
429
|
}
|
|
373
430
|
}
|
|
374
431
|
|
|
432
|
+
_initFastForward({ key, length }) {
|
|
433
|
+
this.moveTo({ key, length })
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async _getOplog(key, length) {
|
|
437
|
+
const core = this.openCore(key)
|
|
438
|
+
await core.ready()
|
|
439
|
+
|
|
440
|
+
const target = length >= 0 ? length : core.length
|
|
441
|
+
if (target === 0) return null
|
|
442
|
+
|
|
443
|
+
const buf = await core.get(target - 1)
|
|
444
|
+
await core.close()
|
|
445
|
+
|
|
446
|
+
if (buf === null) return null
|
|
447
|
+
|
|
448
|
+
return encoding.decodeOplog(buf)
|
|
449
|
+
}
|
|
450
|
+
|
|
375
451
|
_update(changes) {
|
|
376
452
|
this._needsUpdate = false
|
|
377
453
|
this.bee.update(this._workingBee.root)
|
|
@@ -407,7 +483,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
407
483
|
|
|
408
484
|
let runs = 0
|
|
409
485
|
while (!this._interrupting && this.appending && runs++ < 16) await this.update()
|
|
410
|
-
|
|
486
|
+
this.bumpSoon()
|
|
411
487
|
}
|
|
412
488
|
|
|
413
489
|
async _rotateLocalWriter(newLocal) {
|
|
@@ -573,6 +649,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
573
649
|
this._workingBee.move(t.view)
|
|
574
650
|
}
|
|
575
651
|
|
|
652
|
+
return this._processApplyBatch(t)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async _processApplyBatch(t) {
|
|
576
656
|
// first writer is always added with full permissions
|
|
577
657
|
if (this.system.isGenesis()) {
|
|
578
658
|
await this._host.addWriter(t.tip[0][0].key)
|
|
@@ -609,9 +689,20 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
609
689
|
}
|
|
610
690
|
|
|
611
691
|
_storeBoot() {
|
|
692
|
+
const proms = []
|
|
693
|
+
proms.push(
|
|
694
|
+
this.local.setUserData(
|
|
695
|
+
'autobee/previous-drain',
|
|
696
|
+
encoding.encodePreviousDrain(this.previousDrain)
|
|
697
|
+
)
|
|
698
|
+
)
|
|
699
|
+
|
|
612
700
|
const boot = this.system.bootRecord()
|
|
613
|
-
if (
|
|
614
|
-
|
|
701
|
+
if (boot) {
|
|
702
|
+
proms.push(this.local.setUserData('autobee/head', encoding.encodeBootRecord(boot)))
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return Promise.all(proms)
|
|
615
706
|
}
|
|
616
707
|
|
|
617
708
|
static decodeValue(buf, opts) {
|
|
@@ -623,7 +714,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
623
714
|
}
|
|
624
715
|
|
|
625
716
|
async wakeup({ key, length }) {
|
|
626
|
-
await this.
|
|
717
|
+
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
627
718
|
await this.writers.wakeup(key, length)
|
|
628
719
|
await this._bump()
|
|
629
720
|
}
|
|
@@ -655,8 +746,119 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
655
746
|
await this.writers.flushLocal(this._workingBee.head())
|
|
656
747
|
}
|
|
657
748
|
|
|
658
|
-
|
|
659
|
-
|
|
749
|
+
moveTo(head, tip) {
|
|
750
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) return null
|
|
751
|
+
return this._runFastForward(new FastForward(this, head, tip)).catch(noop)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async queueWakeupFastForward(hints) {
|
|
755
|
+
if (!this._handlers.onwakeup) return false
|
|
756
|
+
if (!hints.size || this.fastForwarding || this.fastForwardTo || this.fastForwardBoot) {
|
|
757
|
+
return false
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const promises = []
|
|
761
|
+
for (const [hex, length] of hints) {
|
|
762
|
+
if (length === 0) continue
|
|
763
|
+
const key = b4a.from(hex, 'hex')
|
|
764
|
+
promises.push(this._getOplog(key, length))
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const ops = await Promise.all(promises)
|
|
768
|
+
if (this.fastForwarding || this.fastForwardTo) return false
|
|
769
|
+
|
|
770
|
+
let best = null
|
|
771
|
+
let bestFlushes = -1
|
|
772
|
+
|
|
773
|
+
for (const msg of ops) {
|
|
774
|
+
if (msg === null) continue
|
|
775
|
+
|
|
776
|
+
if (msg.views && msg.views.flushes > bestFlushes) {
|
|
777
|
+
bestFlushes = msg.views.flushes
|
|
778
|
+
best = msg.views
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (best === null || bestFlushes - this.system.flushes < MIN_FF_GAP) return false
|
|
783
|
+
|
|
784
|
+
const view = this.bee.checkout(best.view)
|
|
785
|
+
|
|
786
|
+
let trusted = null
|
|
787
|
+
try {
|
|
788
|
+
trusted = await this._handlers.onwakeup(view, this)
|
|
789
|
+
if (!trusted || this.fastForwarding || this.fastForwardTo) return false
|
|
790
|
+
} finally {
|
|
791
|
+
view.close()
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const oplog = await this._getOplog(trusted.key, trusted.length)
|
|
795
|
+
|
|
796
|
+
return this.moveTo(oplog.views.system, {
|
|
797
|
+
system: best.system,
|
|
798
|
+
verified: {
|
|
799
|
+
node: trusted,
|
|
800
|
+
flushes: oplog.views.flushes
|
|
801
|
+
}
|
|
802
|
+
})
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async _runFastForward(ff) {
|
|
806
|
+
this.fastForwarding = ff
|
|
807
|
+
|
|
808
|
+
const result = await ff.upgrade()
|
|
809
|
+
await ff.close()
|
|
810
|
+
|
|
811
|
+
if (this.fastForwarding === ff) this.fastForwarding = null
|
|
812
|
+
|
|
813
|
+
if (!result) return null
|
|
814
|
+
|
|
815
|
+
this.fastForwardTo = result
|
|
816
|
+
this.fastForward = rrp()
|
|
817
|
+
|
|
818
|
+
this.bumpSoon()
|
|
819
|
+
|
|
820
|
+
return this.fastForward.promise
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async _applyFastForward() {
|
|
824
|
+
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
825
|
+
if (changes) changes.track(this._applyState)
|
|
826
|
+
|
|
827
|
+
const { head, tip } = this.fastForwardTo
|
|
828
|
+
|
|
829
|
+
const from = this.system.bee.head()
|
|
830
|
+
const to = head
|
|
831
|
+
|
|
832
|
+
this.system.bee.move(head)
|
|
833
|
+
await this.system.reset()
|
|
834
|
+
|
|
835
|
+
this.bee.move(this.system.view)
|
|
836
|
+
this._workingBee.move(this.system.view)
|
|
837
|
+
|
|
838
|
+
this.fastForwardTo = null
|
|
839
|
+
|
|
840
|
+
await this.writers.refresh()
|
|
841
|
+
|
|
842
|
+
if (changes) {
|
|
843
|
+
changes.finalise()
|
|
844
|
+
await this._handlers.update(this.view, changes)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
this.emit('move-to', to, from)
|
|
848
|
+
this.fastForward.resolve({ to, from })
|
|
849
|
+
|
|
850
|
+
// tip is null when handlers.fastForward set
|
|
851
|
+
if (!tip) return
|
|
852
|
+
|
|
853
|
+
return this._reapply(tip)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
async _reapply({ system, verified }) {
|
|
857
|
+
const sys = this.system.bee.checkout(system)
|
|
858
|
+
const t = await topo.rollback(this, sys, verified)
|
|
859
|
+
await sys.close()
|
|
860
|
+
|
|
861
|
+
return this._processApplyBatch(t)
|
|
660
862
|
}
|
|
661
863
|
}
|
|
662
864
|
|
package/lib/boot.js
CHANGED
|
@@ -11,7 +11,8 @@ module.exports = async function boot(
|
|
|
11
11
|
bootstrap: null,
|
|
12
12
|
encryptionKey: null,
|
|
13
13
|
system: null,
|
|
14
|
-
flushes: null
|
|
14
|
+
flushes: null,
|
|
15
|
+
previousDrain: 0
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const manifest = keyPair
|
|
@@ -91,15 +92,15 @@ module.exports = async function boot(
|
|
|
91
92
|
await result.local.setUserData('referrer', result.key)
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const [systemHead, encryptionKeyBuffer] = await Promise.all([
|
|
95
|
+
const [systemHead, encryptionKeyBuffer, prevDrainBuffer] = await Promise.all([
|
|
95
96
|
result.local.getUserData('autobee/head'),
|
|
96
|
-
result.local.getUserData('autobee/encryption')
|
|
97
|
+
result.local.getUserData('autobee/encryption'),
|
|
98
|
+
result.local.getUserData('autobee/previous-drain')
|
|
97
99
|
])
|
|
98
100
|
|
|
99
101
|
if (systemHead) result.system = encoding.decodeBootRecord(systemHead)
|
|
100
|
-
if (encryptionKeyBuffer)
|
|
101
|
-
|
|
102
|
-
}
|
|
102
|
+
if (encryptionKeyBuffer) result.encryptionKey = encryptionKeyBuffer
|
|
103
|
+
if (prevDrainBuffer) result.previousDrain = encoding.decodePreviousDrain(prevDrainBuffer)
|
|
103
104
|
|
|
104
105
|
if (!result.encryptionKey && (encryptionKey || encrypt)) {
|
|
105
106
|
if (!encryptionKey) {
|
package/lib/encoding.js
CHANGED
|
@@ -18,6 +18,8 @@ exports.Oplog = Oplog
|
|
|
18
18
|
exports.ManifestData = ManifestData
|
|
19
19
|
exports.encodeBootRecord = encodeBootRecord
|
|
20
20
|
exports.decodeBootRecord = decodeBootRecord
|
|
21
|
+
exports.encodePreviousDrain = encodePreviousDrain
|
|
22
|
+
exports.decodePreviousDrain = decodePreviousDrain
|
|
21
23
|
exports.encodeSystemWriter = encodeSystemWriter
|
|
22
24
|
exports.decodeSystemWriter = decodeSystemWriter
|
|
23
25
|
|
|
@@ -29,6 +31,14 @@ function decodeBootRecord(value) {
|
|
|
29
31
|
return c.decode(Link, value)
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
function encodePreviousDrain(d) {
|
|
35
|
+
return c.encode(c.uint, d)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function decodePreviousDrain(value) {
|
|
39
|
+
return c.decode(c.uint, value)
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
function encodeSystemWriter(m) {
|
|
33
43
|
return c.encode(SystemWriter, m)
|
|
34
44
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const safetyCatch = require('safety-catch')
|
|
2
|
+
|
|
3
|
+
const System = require('./system.js')
|
|
4
|
+
|
|
5
|
+
const DEFAULT_OP_TIMEOUT = 5_000
|
|
6
|
+
|
|
7
|
+
module.exports = class FastForward {
|
|
8
|
+
constructor(auto, head, tip, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
9
|
+
this.auto = auto
|
|
10
|
+
this.system = new System(auto.store.namespace('fast-forward'), null, {
|
|
11
|
+
getEncryptionProvider: this.auto._getEncryptionProviderBound,
|
|
12
|
+
encrypted: this.encrypted
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
this.head = head
|
|
16
|
+
this.timeout = timeout
|
|
17
|
+
this.tip = tip || null
|
|
18
|
+
this.destroyed = false
|
|
19
|
+
this.upgrading = null
|
|
20
|
+
this.failed = false
|
|
21
|
+
this.cores = []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async upgrade() {
|
|
25
|
+
try {
|
|
26
|
+
if (!this.upgrading) this.upgrading = this._upgrade()
|
|
27
|
+
|
|
28
|
+
if (!(await this.upgrading)) return null
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
head: this.head,
|
|
32
|
+
tip: this.tip
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
safetyCatch(err)
|
|
36
|
+
this.failed = true
|
|
37
|
+
return null
|
|
38
|
+
} finally {
|
|
39
|
+
await this.close()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async _upgrade() {
|
|
44
|
+
await this.system.boot(this.head)
|
|
45
|
+
|
|
46
|
+
const promises = []
|
|
47
|
+
|
|
48
|
+
// ensure local key is locally available always
|
|
49
|
+
promises.push(this.system.get(this.auto.local.key, { timeout: this.timeout }))
|
|
50
|
+
|
|
51
|
+
const view = this.auto.store.get({ key: this.system.view.key, active: true })
|
|
52
|
+
this.cores.push(view)
|
|
53
|
+
|
|
54
|
+
promises.push(view.get(this.system.view.length - 1, { timeout: this.timeout }))
|
|
55
|
+
|
|
56
|
+
for (const head of this.system.heads) {
|
|
57
|
+
promises.push(this.system.get(head.key, { timeout: this.timeout }))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await Promise.all(promises)
|
|
61
|
+
if (this.destroyed) return false
|
|
62
|
+
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async close() {
|
|
67
|
+
this.destroyed = true
|
|
68
|
+
if (this.system) await this.system.close()
|
|
69
|
+
for (const core of this.cores) await core.close()
|
|
70
|
+
}
|
|
71
|
+
}
|
package/lib/topo.js
CHANGED
|
@@ -5,8 +5,8 @@ const asserts = require('./asserts.js')
|
|
|
5
5
|
|
|
6
6
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
7
7
|
|
|
8
|
-
exports.replay = replay
|
|
9
8
|
exports.sort = sort
|
|
9
|
+
exports.rollback = rollback
|
|
10
10
|
|
|
11
11
|
exports.isLinking = isLinking
|
|
12
12
|
exports.isLinkingAll = isLinkingAll
|
|
@@ -29,17 +29,29 @@ class Clock {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async function
|
|
33
|
-
const
|
|
34
|
-
const
|
|
32
|
+
async function rollback(auto, system, verified) {
|
|
33
|
+
const tip = []
|
|
34
|
+
const ch = system.createChangesStream()
|
|
35
35
|
|
|
36
36
|
for await (const data of ch) {
|
|
37
37
|
const { ref, info } = getOplog(data)
|
|
38
|
-
|
|
38
|
+
if (sameNode(ref.oplog, verified.node)) {
|
|
39
|
+
asserts.assert(info.flushes === verified.flushes, 'Expect tip to be continuous')
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
tip.push(ref)
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
const inflated = await inflate(auto,
|
|
42
|
-
|
|
46
|
+
const inflated = await inflate(auto, tip)
|
|
47
|
+
|
|
48
|
+
// @todo: verify sort
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
undo: null,
|
|
52
|
+
view: null,
|
|
53
|
+
tip: inflated.map(getBatch)
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
async function sort(auto, batch) {
|
|
@@ -269,3 +281,11 @@ function isLinkingAll(node, heads) {
|
|
|
269
281
|
|
|
270
282
|
return true
|
|
271
283
|
}
|
|
284
|
+
|
|
285
|
+
function sameNode(a, b) {
|
|
286
|
+
return b4a.equals(a.key, b.key) && a.length === b.length
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getBatch(b) {
|
|
290
|
+
return b.batch
|
|
291
|
+
}
|
package/lib/writers.js
CHANGED
|
@@ -37,7 +37,7 @@ class ActiveWriters {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
bump() {
|
|
40
|
-
this.auto.
|
|
40
|
+
this.auto.bumpSoon()
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async wakeup(key, length) {
|
|
@@ -66,6 +66,7 @@ class ActiveWriters {
|
|
|
66
66
|
|
|
67
67
|
this.localWriter = new Writer(this, this.auto.system, true, core, b4a.toString(core.key, 'hex'))
|
|
68
68
|
this.localWriter.addPending()
|
|
69
|
+
this.auto.emit('writer', this.localWriter)
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
clearLocal() {
|
|
@@ -76,6 +77,20 @@ class ActiveWriters {
|
|
|
76
77
|
return this.localWriter.flush(head)
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
async refresh() {
|
|
81
|
+
if (this.localWriter && !this.localWriter.closed) {
|
|
82
|
+
await this.updateLocalState()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const writer of this.active.values()) {
|
|
86
|
+
await writer.update()
|
|
87
|
+
|
|
88
|
+
if (writer !== this.localWriter && !writer.isIndexer) {
|
|
89
|
+
await writer.detachAndClose()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
79
94
|
async updateLocalState() {
|
|
80
95
|
const w = this.localWriter
|
|
81
96
|
const bootstrapping = w.core.length === 0 && b4a.equals(this.auto.key, w.core.key)
|
|
@@ -96,7 +111,8 @@ class ActiveWriters {
|
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
async add(key) {
|
|
99
|
-
|
|
114
|
+
const local = b4a.equals(key, this.auto.local.key)
|
|
115
|
+
if (local) await this.updateLocalState()
|
|
100
116
|
|
|
101
117
|
const id = b4a.toString(key, 'hex')
|
|
102
118
|
let w = this.active.get(id)
|
|
@@ -109,8 +125,10 @@ class ActiveWriters {
|
|
|
109
125
|
|
|
110
126
|
const core = this.auto.store.get({
|
|
111
127
|
key,
|
|
112
|
-
encryption
|
|
128
|
+
encryption,
|
|
129
|
+
group: local || !this.auto.wakeupCapability ? null : this.auto.wakeupCapability.discoveryKey
|
|
113
130
|
})
|
|
131
|
+
|
|
114
132
|
await core.ready()
|
|
115
133
|
await core.setUserData('referrer', this.auto.key)
|
|
116
134
|
|
|
@@ -239,13 +257,14 @@ class Writer {
|
|
|
239
257
|
if (this.isAttached) return
|
|
240
258
|
this.isAttached = true
|
|
241
259
|
this.writers.active.set(this.id, this)
|
|
260
|
+
this.writers.auto.emit('writer', this)
|
|
242
261
|
this.addPending()
|
|
243
262
|
}
|
|
244
263
|
|
|
245
264
|
detach() {
|
|
246
265
|
if (!this.isAttached) return
|
|
247
266
|
this.isAttached = false
|
|
248
|
-
this.download.destroy()
|
|
267
|
+
if (this.download) this.download.destroy()
|
|
249
268
|
this.download = null
|
|
250
269
|
this.writers.active.delete(this.id)
|
|
251
270
|
this.removePending()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
@@ -28,15 +28,17 @@
|
|
|
28
28
|
"b4a": "^1.8.0",
|
|
29
29
|
"compact-encoding": "^2.18.0",
|
|
30
30
|
"hyperbee2": "^2.8.0",
|
|
31
|
-
"hypercore": "^11.
|
|
31
|
+
"hypercore": "^11.33.0",
|
|
32
32
|
"hypercore-id-encoding": "^1.3.0",
|
|
33
33
|
"hyperschema": "^1.20.1",
|
|
34
|
+
"ready-guard": "^1.0.0",
|
|
34
35
|
"ready-resource": "^1.2.0",
|
|
36
|
+
"resolve-reject-promise": "^1.1.0",
|
|
35
37
|
"safety-catch": "^1.0.3"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
40
|
"brittle": "^3.19.1",
|
|
39
|
-
"corestore": "^7.
|
|
41
|
+
"corestore": "^7.10.0",
|
|
40
42
|
"hypercore-crypto": "^3.6.1",
|
|
41
43
|
"lunte": "^1.6.0",
|
|
42
44
|
"prettier": "^3.8.1",
|