autobee 2.11.7 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/index.js +9 -13
- package/lib/fast-forward.js +98 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,10 +285,12 @@ Pass one of `head` or `legacy`, not both.
|
|
|
285
285
|
|
|
286
286
|
#### `fastForward.conservative`
|
|
287
287
|
|
|
288
|
-
Defaults to `true
|
|
288
|
+
Defaults to `false`. When `true`, only fast-forward once the system and view lengths the head stamps are held whole, either locally or by a connected peer.
|
|
289
289
|
|
|
290
290
|
The check runs at the end of the fast-forward, after the system has booted and the view head has been fetched, so a sparse view nobody can serve is skipped instead of landed on.
|
|
291
291
|
|
|
292
|
+
A node that landed via fast-forward holds its cores sparse, so it never satisfies the check for others. Leave it off unless every peer is warmed up to hold the view whole.
|
|
293
|
+
|
|
292
294
|
#### `await db.moveTo(head, [options])`
|
|
293
295
|
|
|
294
296
|
Fast-forward onto an oplog head, ignoring the usual distance and conservative checks. Resolves `{ to, from }`, or `null` if the head could not be booted.
|
package/index.js
CHANGED
|
@@ -110,8 +110,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
110
110
|
// oplog head to boot from: migrates or fast-forwards depending on its version
|
|
111
111
|
this.bootFrom = (fastForward && fastForward.boot) || null
|
|
112
112
|
|
|
113
|
-
// conservative (default
|
|
114
|
-
this._conservativeFF = fastForward
|
|
113
|
+
// conservative (default off): only fast-forward onto a system and view held whole
|
|
114
|
+
this._conservativeFF = fastForward !== null && fastForward.conservative === true
|
|
115
115
|
|
|
116
116
|
this.trusted = new TrustedPeers(this, handlers)
|
|
117
117
|
|
|
@@ -987,15 +987,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
987
987
|
for (const res of ops) {
|
|
988
988
|
if (res === null) continue
|
|
989
989
|
|
|
990
|
-
// the head we were woken on is a candidate in its own right
|
|
991
990
|
heads.push({ key: res.key, length: res.length })
|
|
992
|
-
|
|
993
|
-
if (!res.op.trusted) continue
|
|
994
|
-
|
|
995
|
-
for (const trusted of res.op.trusted) {
|
|
996
|
-
const head = this.trusted.read(trusted)
|
|
997
|
-
if (head !== null) heads.push(head)
|
|
998
|
-
}
|
|
999
991
|
}
|
|
1000
992
|
|
|
1001
993
|
return heads
|
|
@@ -1612,10 +1604,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1612
1604
|
if (!this._bootGuard.opened) await this._bootGuard.ready()
|
|
1613
1605
|
if (this.closing) throw new Error('Autobee closed')
|
|
1614
1606
|
|
|
1615
|
-
|
|
1607
|
+
// an explicit move goes wherever it is pointed, backwards included
|
|
1608
|
+
const ff = await FastForward.fromHead(this, head, null, { force: true, rewind: true, timeout })
|
|
1616
1609
|
if (ff === null) return null
|
|
1617
1610
|
|
|
1618
|
-
if (!(await this._runFastForward(ff, { force: true }))) return null
|
|
1611
|
+
if (!(await this._runFastForward(ff, { force: true, rewind: true }))) return null
|
|
1619
1612
|
|
|
1620
1613
|
return this.ff.promise
|
|
1621
1614
|
}
|
|
@@ -1681,7 +1674,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1681
1674
|
}
|
|
1682
1675
|
}
|
|
1683
1676
|
|
|
1684
|
-
async _runFastForward(ff, { force = false } = {}) {
|
|
1677
|
+
async _runFastForward(ff, { force = false, rewind = false } = {}) {
|
|
1685
1678
|
if (this.fastForwardTo !== null || this.fastForwarding !== null) {
|
|
1686
1679
|
await ff.close()
|
|
1687
1680
|
return false
|
|
@@ -1697,6 +1690,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1697
1690
|
|
|
1698
1691
|
if (!result) return false
|
|
1699
1692
|
|
|
1693
|
+
// never move backwards, a boot head can be older than what we already have
|
|
1694
|
+
if (!rewind && flushes < this.system.flushes) return false
|
|
1695
|
+
|
|
1700
1696
|
// apply ran while we searched, only drop it if it no longer moves us forward
|
|
1701
1697
|
if (!force && flushes <= this.system.flushes) return false
|
|
1702
1698
|
|
package/lib/fast-forward.js
CHANGED
|
@@ -39,17 +39,33 @@ class FastForward {
|
|
|
39
39
|
|
|
40
40
|
static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT
|
|
41
41
|
|
|
42
|
-
static async fromHead(
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
static async fromHead(
|
|
43
|
+
auto,
|
|
44
|
+
head,
|
|
45
|
+
trusted,
|
|
46
|
+
{ force = false, skipGap = false, rewind = false, timeout = 0 } = {}
|
|
47
|
+
) {
|
|
45
48
|
const oplog = await FastForward.flushHead(auto, head, { timeout })
|
|
46
49
|
if (!oplog) return null
|
|
47
50
|
|
|
48
51
|
const verified = trusted ? await FastForward.flushHead(auto, trusted, { timeout }) : oplog
|
|
49
52
|
if (!verified) return null
|
|
50
53
|
|
|
54
|
+
return FastForward.fromVerified(auto, verified, { force, skipGap, rewind, timeout })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static fromVerified(
|
|
58
|
+
auto,
|
|
59
|
+
verified,
|
|
60
|
+
{ force = false, skipGap = false, rewind = false, timeout = 0 } = {}
|
|
61
|
+
) {
|
|
62
|
+
const conservative = !force && auto._conservativeFF
|
|
63
|
+
|
|
51
64
|
// legacy nodes from non-indexers have no system info to fast-forward from
|
|
52
|
-
if (!
|
|
65
|
+
if (!verified.op.views) return null
|
|
66
|
+
|
|
67
|
+
// never move backwards, even when forced or the gap is skipped
|
|
68
|
+
if (!rewind && verified.op.views.flushes < auto.system.flushes) return null
|
|
53
69
|
|
|
54
70
|
if (!force && !skipGap && verified.op.views.flushes - auto.system.flushes < MIN_FF_GAP) {
|
|
55
71
|
return null
|
|
@@ -63,68 +79,95 @@ class FastForward {
|
|
|
63
79
|
|
|
64
80
|
static async fromHeads(auto, heads, { force = false, skipGap = false, timeout = 0 } = {}) {
|
|
65
81
|
const reference = auto._workingView.view
|
|
82
|
+
const opTimeout = timeout || DEFAULT_OP_TIMEOUT
|
|
66
83
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
for (const head of heads) {
|
|
70
|
-
if (head.length === 0) continue
|
|
71
|
-
promises.push(FastForward.flushHead(auto, head, { timeout }))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const ops = await FastForward.reindexedCandidates(auto, await Promise.all(promises), {
|
|
75
|
-
timeout: timeout || DEFAULT_OP_TIMEOUT
|
|
76
|
-
})
|
|
84
|
+
const hints = await FastForward.flushHeads(auto, heads, timeout)
|
|
77
85
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
78
86
|
|
|
79
|
-
const trust = await Promise.all(
|
|
87
|
+
const trust = await Promise.all(hints.map((res) => auto.trusted.isTrusted(res.key, reference)))
|
|
80
88
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
81
89
|
|
|
82
|
-
const
|
|
90
|
+
const landings = new Map()
|
|
91
|
+
const unverified = new Map()
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
let bestTrustedFlushes = -1
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < ops.length; i++) {
|
|
88
|
-
const res = ops[i]
|
|
89
|
-
if (!force && !skipGap && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue
|
|
93
|
+
const promises = []
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
for (let i = 0; i < hints.length; i++) {
|
|
96
|
+
if (trust[i]) {
|
|
97
|
+
addOplog(landings, hints[i])
|
|
93
98
|
continue
|
|
94
99
|
}
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
promises.push(
|
|
102
|
+
FastForward.signalled(auto, hints[i], reference, timeout).then((sig) => {
|
|
103
|
+
for (const { oplog, trusted } of sig) addOplog(trusted ? landings : unverified, oplog)
|
|
104
|
+
})
|
|
105
|
+
)
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
await Promise.all(promises)
|
|
109
|
+
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
110
|
+
|
|
111
|
+
const ranked = [...landings.values()].sort(byFlushes)
|
|
112
|
+
|
|
113
|
+
const reindexed = ranked.map((res) => FastForward.isReindexed(auto, res, opTimeout))
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < ranked.length; i++) {
|
|
116
|
+
if (!(await reindexed[i])) continue
|
|
117
|
+
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
118
|
+
|
|
119
|
+
const ff = FastForward.fromVerified(auto, ranked[i], { force, skipGap, timeout })
|
|
120
|
+
if (ff !== null) return ff
|
|
104
121
|
}
|
|
105
122
|
|
|
106
|
-
|
|
123
|
+
for (const sig of [...unverified.values()].sort(byFlushes)) {
|
|
124
|
+
if (sig.op.views.flushes < auto.system.flushes) continue
|
|
125
|
+
if (!force && !skipGap && sig.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue
|
|
107
126
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (trusted === null) continue
|
|
127
|
+
const head = await FastForward.mostRecentTrusted(auto, sig, reference, timeout)
|
|
128
|
+
if (head === null) continue
|
|
111
129
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
112
130
|
|
|
113
|
-
const
|
|
131
|
+
const verified = await FastForward.flushHead(auto, head, { timeout })
|
|
132
|
+
if (verified === null) continue
|
|
133
|
+
if (!(await FastForward.isReindexed(auto, verified, opTimeout))) continue
|
|
134
|
+
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
135
|
+
|
|
136
|
+
const ff = FastForward.fromVerified(auto, verified, { force, skipGap, timeout })
|
|
114
137
|
if (ff !== null) return ff
|
|
115
138
|
}
|
|
116
139
|
|
|
117
140
|
return null
|
|
118
141
|
}
|
|
119
142
|
|
|
120
|
-
static async
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
static async flushHeads(auto, heads, timeout) {
|
|
144
|
+
const promises = []
|
|
145
|
+
|
|
146
|
+
for (const head of heads) {
|
|
147
|
+
if (head.length === 0) continue
|
|
148
|
+
promises.push(FastForward.flushHead(auto, head, { timeout }))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (await Promise.all(promises)).filter((res) => res !== null)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static async signalled(auto, res, reference, timeout) {
|
|
155
|
+
const heads = []
|
|
156
|
+
|
|
157
|
+
for (const entry of res.op.trusted || []) {
|
|
158
|
+
const head = auto.trusted.read(entry)
|
|
159
|
+
if (head !== null && head.length > 0) heads.push(head)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const oplogs = await FastForward.flushHeads(auto, heads, timeout)
|
|
163
|
+
const trust = await Promise.all(
|
|
164
|
+
oplogs.map((oplog) => auto.trusted.isTrusted(oplog.key, reference))
|
|
124
165
|
)
|
|
125
|
-
|
|
166
|
+
|
|
167
|
+
return oplogs.map((oplog, i) => ({ oplog, trusted: !!trust[i] }))
|
|
126
168
|
}
|
|
127
169
|
|
|
170
|
+
// never rejects, the search may return before awaiting every check
|
|
128
171
|
static async isReindexed(auto, res, timeout) {
|
|
129
172
|
const { system, view } = res.op.views
|
|
130
173
|
const checks = [system, view]
|
|
@@ -135,8 +178,13 @@ class FastForward {
|
|
|
135
178
|
return auto._shouldReindex(key, { unknown: true, length, timeout })
|
|
136
179
|
})
|
|
137
180
|
|
|
138
|
-
|
|
139
|
-
|
|
181
|
+
try {
|
|
182
|
+
for (const shouldReindex of await Promise.all(checks)) {
|
|
183
|
+
if (shouldReindex) return false
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
safetyCatch(err)
|
|
187
|
+
return false
|
|
140
188
|
}
|
|
141
189
|
|
|
142
190
|
return true
|
|
@@ -395,6 +443,14 @@ function isContiguous(core, length) {
|
|
|
395
443
|
return core.contiguousLength >= length || core.remoteContiguousLength >= length
|
|
396
444
|
}
|
|
397
445
|
|
|
446
|
+
function byFlushes(a, b) {
|
|
447
|
+
return b.op.views.flushes - a.op.views.flushes
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function addOplog(map, oplog) {
|
|
451
|
+
map.set(b4a.toString(oplog.key, 'hex') + ':' + oplog.length, oplog)
|
|
452
|
+
}
|
|
453
|
+
|
|
398
454
|
function batchToHead(b) {
|
|
399
455
|
return {
|
|
400
456
|
key: b.key,
|