autobee 2.10.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +125 -4
- package/lib/constants.js +2 -0
- package/lib/fast-forward.js +36 -14
- package/package.json +4 -3
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const safetyCatch = require('safety-catch')
|
|
|
5
5
|
const Hyperbee = require('hyperbee2')
|
|
6
6
|
const ID = require('hypercore-id-encoding')
|
|
7
7
|
const rrp = require('resolve-reject-promise')
|
|
8
|
+
const Signal = require('signal-promise')
|
|
8
9
|
const { AutobeeEncryption, WriterEncryption, ViewEncryption } = require('autobee-encryption')
|
|
9
10
|
const AutobeeWakeup = require('autobee-wakeup')
|
|
10
11
|
const Hypercore = require('hypercore')
|
|
@@ -12,6 +13,7 @@ const crypto = require('hypercore-crypto')
|
|
|
12
13
|
const c = require('compact-encoding')
|
|
13
14
|
const asserts = require('./lib/asserts.js')
|
|
14
15
|
const boot = require('./lib/boot.js')
|
|
16
|
+
const { DEFAULT_MANIFEST_VERSION } = require('./lib/constants.js')
|
|
15
17
|
const { resolveWeight, currentWeight } = require('./lib/witness.js')
|
|
16
18
|
const encoding = require('./lib/encoding.js')
|
|
17
19
|
const FastForward = require('./lib/fast-forward.js')
|
|
@@ -26,6 +28,7 @@ const migrations = require('./lib/migrations.js')
|
|
|
26
28
|
|
|
27
29
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
28
30
|
const DEFAULT_ACK_THRESHOLD = 32
|
|
31
|
+
const BOOT_NETWORK_TIMEOUT = 5000
|
|
29
32
|
const INTERRUPT = new Error('Apply interrupted')
|
|
30
33
|
|
|
31
34
|
module.exports = class Autobee extends ReadyResource {
|
|
@@ -116,6 +119,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
116
119
|
this.fastForwardTo = null
|
|
117
120
|
this._ffCandidates = new Map()
|
|
118
121
|
this._ffSearching = null
|
|
122
|
+
this._bumpSignal = new Signal()
|
|
123
|
+
this._networkBooted = false
|
|
124
|
+
this._networkBooting = false
|
|
119
125
|
|
|
120
126
|
this._workingBee = bee
|
|
121
127
|
this._workingView = new ApplyView(this._workingBee, this)
|
|
@@ -143,6 +149,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
143
149
|
this._now = handlers.now || Date.now // overridable for clock-drift tests
|
|
144
150
|
this._preapply = handlers.preapply || null
|
|
145
151
|
this._preApplied = false
|
|
152
|
+
this._reindexed = false
|
|
146
153
|
this._warmup = handlers.warmup || null
|
|
147
154
|
this._hasApply = !!handlers.apply
|
|
148
155
|
this._hasUpdate = !!handlers.update
|
|
@@ -205,7 +212,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
205
212
|
await 1
|
|
206
213
|
const result = await this._prebooting
|
|
207
214
|
return {
|
|
208
|
-
name: 'autobee/' + result.local.id + '/' + name,
|
|
215
|
+
name: 'autobee/' + result.local.id + '/view/' + name,
|
|
216
|
+
manifestVersion: DEFAULT_MANIFEST_VERSION,
|
|
209
217
|
encryption: name === 'system' ? this.getSystemEncryption() : this.getViewEncryption(),
|
|
210
218
|
inflightRange: [256, 512]
|
|
211
219
|
}
|
|
@@ -380,6 +388,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
380
388
|
|
|
381
389
|
async _close() {
|
|
382
390
|
this._interrupting = true
|
|
391
|
+
this._bumpSignal.notify()
|
|
383
392
|
|
|
384
393
|
// the local core holds the exclusive lock but the local writer closes it
|
|
385
394
|
// early in the teardown, so hand the lock to a detached session that
|
|
@@ -583,6 +592,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
583
592
|
this._localFlushes = this.system.flushes
|
|
584
593
|
}
|
|
585
594
|
|
|
595
|
+
async _isReindexing() {
|
|
596
|
+
if (this._networkBooted || !this._ffEnabled || this._migrating) return false
|
|
597
|
+
return !(await this._isReindexed())
|
|
598
|
+
}
|
|
599
|
+
|
|
586
600
|
async _bootOnline() {
|
|
587
601
|
if (!this._bootOnlineGuard.enter()) return
|
|
588
602
|
|
|
@@ -607,6 +621,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
607
621
|
}
|
|
608
622
|
|
|
609
623
|
bumpSoon() {
|
|
624
|
+
this._bumpSignal.notify()
|
|
610
625
|
this._bump(false).catch(safetyCatch)
|
|
611
626
|
}
|
|
612
627
|
|
|
@@ -668,6 +683,58 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
668
683
|
this.emit('error', err)
|
|
669
684
|
}
|
|
670
685
|
|
|
686
|
+
async compactMaybe() {
|
|
687
|
+
if (!this.writers.writable) return 0
|
|
688
|
+
|
|
689
|
+
if (await this._isReindexed()) return 0
|
|
690
|
+
|
|
691
|
+
const view = await this._reindexBee(this._workingBee)
|
|
692
|
+
if (view > 0) this.bee.move(this._workingBee.head())
|
|
693
|
+
|
|
694
|
+
const system = await this._reindexBee(this.system.bee)
|
|
695
|
+
|
|
696
|
+
if (view + system > 0) this._reindexed = true
|
|
697
|
+
|
|
698
|
+
return view + system
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async _isReindexed() {
|
|
702
|
+
const head = this.system.bee.head()
|
|
703
|
+
if (head === null) return true
|
|
704
|
+
if (await this._shouldReindex(head.key)) return false
|
|
705
|
+
|
|
706
|
+
const view = this.system.view
|
|
707
|
+
if (!view || !view.key) return true
|
|
708
|
+
return !(await this._shouldReindex(view.key))
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async _reindexBee(bee) {
|
|
712
|
+
const head = bee.head()
|
|
713
|
+
const local = bee.context.local
|
|
714
|
+
if (head === null) return 0
|
|
715
|
+
if ((await this._shouldReindex(local.key)) && local.length > 0) return 0
|
|
716
|
+
if (!(await this._shouldReindex(head.key))) return 0
|
|
717
|
+
|
|
718
|
+
return bee.reindex(async (change) => !(await this._shouldReindex(change.head.key)))
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async _shouldReindex(key, { unknown = false, length = 0, timeout = 0 } = {}) {
|
|
722
|
+
const core = this.store.get({ key, active: false })
|
|
723
|
+
|
|
724
|
+
try {
|
|
725
|
+
await core.ready()
|
|
726
|
+
|
|
727
|
+
if (core.manifest === null && length > 0) {
|
|
728
|
+
await core.get(length - 1, { raw: true, timeout }).catch(safetyCatch)
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (core.manifest === null) return unknown
|
|
732
|
+
return core.manifest.version > 1 && core.manifest.version < DEFAULT_MANIFEST_VERSION
|
|
733
|
+
} finally {
|
|
734
|
+
await core.close()
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
671
738
|
// one-shot user gate: nothing applies until the host has resolved whatever
|
|
672
739
|
// state apply depends on (e.g. legacy views recorded by a migration)
|
|
673
740
|
async _runPreApply() {
|
|
@@ -688,6 +755,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
688
755
|
const { head = null, legacy = null } = this.bootFrom
|
|
689
756
|
|
|
690
757
|
this.bootFrom = null
|
|
758
|
+
this._networkBooted = true
|
|
691
759
|
|
|
692
760
|
if (legacy) {
|
|
693
761
|
await this._bootFromSystem(legacy)
|
|
@@ -695,8 +763,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
695
763
|
this._wakeup.hint({ key: head.key, length: head.length || 0 })
|
|
696
764
|
await this._bootFromHead(head)
|
|
697
765
|
}
|
|
766
|
+
} else if (await this._isReindexing()) {
|
|
767
|
+
this._networkBooted = true
|
|
768
|
+
await this._bootFromNetwork()
|
|
698
769
|
}
|
|
699
770
|
|
|
771
|
+
// preferably get a peer's compacted view during bootFrom
|
|
772
|
+
if (this.fastForwardTo === null) await this.compactMaybe()
|
|
773
|
+
|
|
700
774
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
701
775
|
if (changes) changes.track()
|
|
702
776
|
|
|
@@ -865,6 +939,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
865
939
|
const heads = await this._readCandidateHeads(hints, FastForward.DEFAULT_TIMEOUT)
|
|
866
940
|
|
|
867
941
|
const ff = await FastForward.fromHeads(this, heads, {
|
|
942
|
+
skipGap: this._networkBooting,
|
|
868
943
|
timeout: FastForward.DEFAULT_TIMEOUT
|
|
869
944
|
})
|
|
870
945
|
|
|
@@ -1208,12 +1283,15 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1208
1283
|
}
|
|
1209
1284
|
|
|
1210
1285
|
async _appendAck() {
|
|
1211
|
-
if (!this._acking) return false
|
|
1286
|
+
if (!this._acking && !this._reindexed) return false
|
|
1212
1287
|
if (!this.writers.writable) return false
|
|
1213
1288
|
|
|
1214
1289
|
if (this.writers.localWriter.pending !== null) return false
|
|
1215
|
-
|
|
1216
|
-
if (
|
|
1290
|
+
|
|
1291
|
+
if (!this._reindexed) {
|
|
1292
|
+
if ((await this._flushesBehind()) < this._ackThreshold) return false
|
|
1293
|
+
if (await this._allHeadsTrusted()) return false
|
|
1294
|
+
}
|
|
1217
1295
|
|
|
1218
1296
|
const links = this.system.getLinks(this.local.key)
|
|
1219
1297
|
const now = this._now()
|
|
@@ -1371,6 +1449,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1371
1449
|
|
|
1372
1450
|
const changed = await this.system.flush(batch, this._workingBee)
|
|
1373
1451
|
|
|
1452
|
+
if (this._reindexed && (await this._isReindexed())) {
|
|
1453
|
+
this._reindexed = false
|
|
1454
|
+
await this.local.setUserData(
|
|
1455
|
+
'autobee/head',
|
|
1456
|
+
encoding.encodeBootRecord(this.system.bootRecord())
|
|
1457
|
+
)
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1374
1460
|
if (this.system.promotions.changed) this._prefetchApprovals().catch(safetyCatch)
|
|
1375
1461
|
|
|
1376
1462
|
if (local) {
|
|
@@ -1551,6 +1637,41 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1551
1637
|
}
|
|
1552
1638
|
}
|
|
1553
1639
|
|
|
1640
|
+
async _bootFromNetwork() {
|
|
1641
|
+
this._networkBooting = true
|
|
1642
|
+
|
|
1643
|
+
try {
|
|
1644
|
+
await this._waitForNetworkBoot()
|
|
1645
|
+
} finally {
|
|
1646
|
+
this._networkBooting = false
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
async _waitForNetworkBoot() {
|
|
1651
|
+
const deadline = Date.now() + BOOT_NETWORK_TIMEOUT
|
|
1652
|
+
|
|
1653
|
+
this._requestWakeup()
|
|
1654
|
+
|
|
1655
|
+
while (!this._interrupting && !this.closing && this.fastForwardTo === null) {
|
|
1656
|
+
const hints = await this._applyWakeupHints()
|
|
1657
|
+
if (hints.length) this._queueFastForward(hints)
|
|
1658
|
+
|
|
1659
|
+
const remaining = deadline - Date.now()
|
|
1660
|
+
if (remaining <= 0 || this.fastForwardTo !== null) return
|
|
1661
|
+
|
|
1662
|
+
if (this._ffSearching !== null) {
|
|
1663
|
+
await Promise.race([this._ffSearching, this._bumpSignal.wait(remaining)])
|
|
1664
|
+
this._bumpSignal.notify()
|
|
1665
|
+
if (this._ffSearching === null) return
|
|
1666
|
+
continue
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (hints.length) return
|
|
1670
|
+
|
|
1671
|
+
await this._bumpSignal.wait(remaining)
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1554
1675
|
async _runFastForward(ff, { force = false } = {}) {
|
|
1555
1676
|
if (this.fastForwardTo !== null || this.fastForwarding !== null) {
|
|
1556
1677
|
await ff.close()
|
package/lib/constants.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const LEGACY_OPLOG_VERSION = 2
|
|
2
2
|
const OPLOG_VERSION = 4
|
|
3
|
+
const DEFAULT_MANIFEST_VERSION = 3
|
|
3
4
|
const LEGACY_AUTOBASE_VERSION = 2
|
|
4
5
|
const AUTOBEE_VERSION = 3
|
|
5
6
|
const DEFAULT_OP_TIMEOUT = 5000
|
|
6
7
|
|
|
7
8
|
module.exports = {
|
|
9
|
+
DEFAULT_MANIFEST_VERSION,
|
|
8
10
|
LEGACY_OPLOG_VERSION,
|
|
9
11
|
OPLOG_VERSION,
|
|
10
12
|
LEGACY_AUTOBASE_VERSION,
|
package/lib/fast-forward.js
CHANGED
|
@@ -7,7 +7,7 @@ const migrations = require('./migrations.js')
|
|
|
7
7
|
const { LEGACY_AUTOBASE_VERSION, LEGACY_OPLOG_VERSION } = require('./constants.js')
|
|
8
8
|
|
|
9
9
|
const DEFAULT_OP_TIMEOUT = 5_000
|
|
10
|
-
const MIN_FF_GAP =
|
|
10
|
+
const MIN_FF_GAP = 16
|
|
11
11
|
|
|
12
12
|
class FastForward {
|
|
13
13
|
constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT, conservative = false } = {}) {
|
|
@@ -39,7 +39,7 @@ class FastForward {
|
|
|
39
39
|
|
|
40
40
|
static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT
|
|
41
41
|
|
|
42
|
-
static async fromHead(auto, head, trusted, { force = false, timeout = 0 } = {}) {
|
|
42
|
+
static async fromHead(auto, head, trusted, { force = false, skipGap = false, timeout = 0 } = {}) {
|
|
43
43
|
const conservative = !force && auto._conservativeFF
|
|
44
44
|
|
|
45
45
|
const oplog = await FastForward.flushHead(auto, head, { timeout })
|
|
@@ -51,7 +51,7 @@ class FastForward {
|
|
|
51
51
|
// legacy nodes from non-indexers have no system info to fast-forward from
|
|
52
52
|
if (!oplog.op.views || !verified.op.views) return null
|
|
53
53
|
|
|
54
|
-
if (!force && verified.op.views.flushes - auto.system.flushes < MIN_FF_GAP) {
|
|
54
|
+
if (!force && !skipGap && verified.op.views.flushes - auto.system.flushes < MIN_FF_GAP) {
|
|
55
55
|
return null
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -61,7 +61,7 @@ class FastForward {
|
|
|
61
61
|
})
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
static async fromHeads(auto, heads, { force = false, timeout = 0 } = {}) {
|
|
64
|
+
static async fromHeads(auto, heads, { force = false, skipGap = false, timeout = 0 } = {}) {
|
|
65
65
|
const reference = auto._workingView.view
|
|
66
66
|
|
|
67
67
|
const promises = []
|
|
@@ -71,14 +71,12 @@ class FastForward {
|
|
|
71
71
|
promises.push(FastForward.flushHead(auto, head, { timeout }))
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const ops = await Promise.all(promises)
|
|
74
|
+
const ops = await FastForward.reindexedCandidates(auto, await Promise.all(promises), {
|
|
75
|
+
timeout: timeout || DEFAULT_OP_TIMEOUT
|
|
76
|
+
})
|
|
75
77
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
76
78
|
|
|
77
|
-
const trust = await Promise.all(
|
|
78
|
-
ops.map((res) => {
|
|
79
|
-
return res === null ? false : auto.trusted.isTrusted(res.key, reference)
|
|
80
|
-
})
|
|
81
|
-
)
|
|
79
|
+
const trust = await Promise.all(ops.map((res) => auto.trusted.isTrusted(res.key, reference)))
|
|
82
80
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
83
81
|
|
|
84
82
|
const candidates = []
|
|
@@ -88,8 +86,7 @@ class FastForward {
|
|
|
88
86
|
|
|
89
87
|
for (let i = 0; i < ops.length; i++) {
|
|
90
88
|
const res = ops[i]
|
|
91
|
-
if (
|
|
92
|
-
if (!force && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue
|
|
89
|
+
if (!force && !skipGap && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue
|
|
93
90
|
|
|
94
91
|
if (!trust[i]) {
|
|
95
92
|
candidates.push(res)
|
|
@@ -103,7 +100,7 @@ class FastForward {
|
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
if (bestTrusted !== null) {
|
|
106
|
-
return FastForward.fromHead(auto, bestTrusted, null, { force, timeout })
|
|
103
|
+
return FastForward.fromHead(auto, bestTrusted, null, { force, skipGap, timeout })
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)
|
|
@@ -113,13 +110,38 @@ class FastForward {
|
|
|
113
110
|
if (trusted === null) continue
|
|
114
111
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
115
112
|
|
|
116
|
-
const ff = await FastForward.fromHead(auto, res, trusted, { force, timeout })
|
|
113
|
+
const ff = await FastForward.fromHead(auto, res, trusted, { force, skipGap, timeout })
|
|
117
114
|
if (ff !== null) return ff
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
return null
|
|
121
118
|
}
|
|
122
119
|
|
|
120
|
+
static async reindexedCandidates(auto, ops, { timeout }) {
|
|
121
|
+
const withViews = ops.filter((res) => res !== null && res.op.views)
|
|
122
|
+
const reindexed = await Promise.all(
|
|
123
|
+
withViews.map((res) => FastForward.isReindexed(auto, res, timeout))
|
|
124
|
+
)
|
|
125
|
+
return withViews.filter((res, i) => reindexed[i])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static async isReindexed(auto, res, timeout) {
|
|
129
|
+
const { system, view } = res.op.views
|
|
130
|
+
const checks = [system, view]
|
|
131
|
+
.filter((v) => !!v)
|
|
132
|
+
.map((range) => {
|
|
133
|
+
const { key, length } = batchToHead(range)
|
|
134
|
+
if (length === 0) return false
|
|
135
|
+
return auto._shouldReindex(key, { unknown: true, length, timeout })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
for (const shouldReindex of await Promise.all(checks)) {
|
|
139
|
+
if (shouldReindex) return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
static async flushHead(auto, head, opts) {
|
|
124
146
|
const core = auto.openCore(head.key)
|
|
125
147
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"autobee-wakeup": "^1.1.3",
|
|
28
28
|
"b4a": "^1.8.0",
|
|
29
29
|
"compact-encoding": "^3.3.0",
|
|
30
|
-
"hyperbee2": "^2.
|
|
31
|
-
"hypercore": "^11.
|
|
30
|
+
"hyperbee2": "^2.17.0",
|
|
31
|
+
"hypercore": "^11.37.0",
|
|
32
32
|
"hypercore-id-encoding": "^1.3.0",
|
|
33
33
|
"hyperschema": "^1.26.2",
|
|
34
34
|
"read-write-mutexify": "^2.1.0",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"ready-resource": "^1.2.0",
|
|
37
37
|
"resolve-reject-promise": "^1.1.0",
|
|
38
38
|
"safety-catch": "^1.0.3",
|
|
39
|
+
"signal-promise": "^1.0.3",
|
|
39
40
|
"sodium-native": "^5.1.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|