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 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`: only fast-forward once the system and view lengths the head stamps are held whole, either locally or by a connected peer.
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 on): only fast-forward onto a system and view held whole
114
- this._conservativeFF = fastForward === null || fastForward.conservative !== false
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
- const ff = await FastForward.fromHead(this, head, null, { force: true, timeout })
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
 
@@ -39,17 +39,33 @@ class FastForward {
39
39
 
40
40
  static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT
41
41
 
42
- static async fromHead(auto, head, trusted, { force = false, skipGap = false, timeout = 0 } = {}) {
43
- const conservative = !force && auto._conservativeFF
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 (!oplog.op.views || !verified.op.views) return null
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 promises = []
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(ops.map((res) => auto.trusted.isTrusted(res.key, reference)))
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 candidates = []
90
+ const landings = new Map()
91
+ const unverified = new Map()
83
92
 
84
- let bestTrusted = null
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
- if (!trust[i]) {
92
- candidates.push(res)
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
- if (res.op.views.flushes > bestTrustedFlushes) {
97
- bestTrustedFlushes = res.op.views.flushes
98
- bestTrusted = res
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
- if (bestTrusted !== null) {
103
- return FastForward.fromHead(auto, bestTrusted, null, { force, skipGap, timeout })
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
- candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)
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
- for (const res of candidates) {
109
- const trusted = await FastForward.mostRecentTrusted(auto, res, reference, timeout)
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 ff = await FastForward.fromHead(auto, res, trusted, { force, skipGap, timeout })
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 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))
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
- return withViews.filter((res, i) => reindexed[i])
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
- for (const shouldReindex of await Promise.all(checks)) {
139
- if (shouldReindex) return false
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.11.7",
3
+ "version": "2.12.0",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",