autobee 2.11.8 → 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 +2 -10
- package/lib/fast-forward.js +89 -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
|
package/lib/fast-forward.js
CHANGED
|
@@ -45,16 +45,24 @@ class FastForward {
|
|
|
45
45
|
trusted,
|
|
46
46
|
{ force = false, skipGap = false, rewind = false, timeout = 0 } = {}
|
|
47
47
|
) {
|
|
48
|
-
const conservative = !force && auto._conservativeFF
|
|
49
|
-
|
|
50
48
|
const oplog = await FastForward.flushHead(auto, head, { timeout })
|
|
51
49
|
if (!oplog) return null
|
|
52
50
|
|
|
53
51
|
const verified = trusted ? await FastForward.flushHead(auto, trusted, { timeout }) : oplog
|
|
54
52
|
if (!verified) return null
|
|
55
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
|
+
|
|
56
64
|
// legacy nodes from non-indexers have no system info to fast-forward from
|
|
57
|
-
if (!
|
|
65
|
+
if (!verified.op.views) return null
|
|
58
66
|
|
|
59
67
|
// never move backwards, even when forced or the gap is skipped
|
|
60
68
|
if (!rewind && verified.op.views.flushes < auto.system.flushes) return null
|
|
@@ -71,69 +79,95 @@ class FastForward {
|
|
|
71
79
|
|
|
72
80
|
static async fromHeads(auto, heads, { force = false, skipGap = false, timeout = 0 } = {}) {
|
|
73
81
|
const reference = auto._workingView.view
|
|
82
|
+
const opTimeout = timeout || DEFAULT_OP_TIMEOUT
|
|
74
83
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
for (const head of heads) {
|
|
78
|
-
if (head.length === 0) continue
|
|
79
|
-
promises.push(FastForward.flushHead(auto, head, { timeout }))
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const ops = await FastForward.reindexedCandidates(auto, await Promise.all(promises), {
|
|
83
|
-
timeout: timeout || DEFAULT_OP_TIMEOUT
|
|
84
|
-
})
|
|
84
|
+
const hints = await FastForward.flushHeads(auto, heads, timeout)
|
|
85
85
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
86
86
|
|
|
87
|
-
const trust = await Promise.all(
|
|
87
|
+
const trust = await Promise.all(hints.map((res) => auto.trusted.isTrusted(res.key, reference)))
|
|
88
88
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
89
89
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
let bestTrusted = null
|
|
93
|
-
let bestTrustedFlushes = -1
|
|
90
|
+
const landings = new Map()
|
|
91
|
+
const unverified = new Map()
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
const res = ops[i]
|
|
97
|
-
if (res.op.views.flushes < auto.system.flushes) continue
|
|
98
|
-
if (!force && !skipGap && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue
|
|
93
|
+
const promises = []
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
for (let i = 0; i < hints.length; i++) {
|
|
96
|
+
if (trust[i]) {
|
|
97
|
+
addOplog(landings, hints[i])
|
|
102
98
|
continue
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
)
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
|
|
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
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (trusted === null) continue
|
|
127
|
+
const head = await FastForward.mostRecentTrusted(auto, sig, reference, timeout)
|
|
128
|
+
if (head === null) continue
|
|
120
129
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
121
130
|
|
|
122
|
-
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 })
|
|
123
137
|
if (ff !== null) return ff
|
|
124
138
|
}
|
|
125
139
|
|
|
126
140
|
return null
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
static async
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
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))
|
|
133
165
|
)
|
|
134
|
-
|
|
166
|
+
|
|
167
|
+
return oplogs.map((oplog, i) => ({ oplog, trusted: !!trust[i] }))
|
|
135
168
|
}
|
|
136
169
|
|
|
170
|
+
// never rejects, the search may return before awaiting every check
|
|
137
171
|
static async isReindexed(auto, res, timeout) {
|
|
138
172
|
const { system, view } = res.op.views
|
|
139
173
|
const checks = [system, view]
|
|
@@ -144,8 +178,13 @@ class FastForward {
|
|
|
144
178
|
return auto._shouldReindex(key, { unknown: true, length, timeout })
|
|
145
179
|
})
|
|
146
180
|
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
149
188
|
}
|
|
150
189
|
|
|
151
190
|
return true
|
|
@@ -404,6 +443,14 @@ function isContiguous(core, length) {
|
|
|
404
443
|
return core.contiguousLength >= length || core.remoteContiguousLength >= length
|
|
405
444
|
}
|
|
406
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
|
+
|
|
407
454
|
function batchToHead(b) {
|
|
408
455
|
return {
|
|
409
456
|
key: b.key,
|