autobee 2.7.3 → 2.8.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 CHANGED
@@ -69,7 +69,15 @@ module.exports = class Autobee extends ReadyResource {
69
69
  this.id = null
70
70
  this.bootstrap = null
71
71
  this._handlers = handlers
72
- this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }
72
+ this.stats = {
73
+ undos: 0,
74
+ fastForwards: 0,
75
+ drains: 0,
76
+ applies: 0,
77
+ appends: 0,
78
+ writersOpened: 0,
79
+ writersClosed: 0
80
+ }
73
81
 
74
82
  const systemStore = this.store.session()
75
83
  this.system = new System(this, systemStore, {
@@ -205,9 +213,13 @@ module.exports = class Autobee extends ReadyResource {
205
213
  if (this._acking) this.bumpSoon()
206
214
  }
207
215
 
216
+ isLocalTrusted() {
217
+ return this.trusted.isTrusted(this.local.key, this._workingView.view)
218
+ }
219
+
208
220
  async _updateAcking() {
209
221
  if (this._interrupting) return
210
- this.setAcking(await this.trusted.isTrusted(this.local.key, this._workingView.view))
222
+ this.setAcking(await this.isLocalTrusted())
211
223
  }
212
224
 
213
225
  // network free: only a migration needs peers, so ready() awaits the full
@@ -1032,10 +1044,9 @@ module.exports = class Autobee extends ReadyResource {
1032
1044
  return anchor
1033
1045
  }
1034
1046
 
1035
- // see CatchupOrder - sort order instead of legacy INFO order, so the catchup
1036
- // does not rebase on itself
1047
+ // apply in linearizer order so the catchup never undoes. weights are fixed,
1048
+ // legacy nodes carry no witness
1037
1049
  async _bumpMigratedWriters() {
1038
- const order = new migrations.CatchupOrder(this._catchupMigratedNodes)
1039
1050
  const opened = new Set()
1040
1051
  let updated = false
1041
1052
 
@@ -1046,16 +1057,14 @@ module.exports = class Autobee extends ReadyResource {
1046
1057
  }
1047
1058
 
1048
1059
  try {
1049
- while (order.size) {
1050
- const candidates = order.candidates()
1051
- // recomputed per step - prepareBatch does the same right after
1052
- for (const batch of candidates) batch[0].weight = await resolveWeight(this, batch[0])
1060
+ for (const batch of this._catchupMigratedNodes) {
1061
+ const weight = await resolveWeight(this, batch[0])
1062
+ for (const node of batch) node.weight = weight
1063
+ }
1053
1064
 
1054
- const best = order.pick(candidates)
1055
- await this._processBatch(best)
1065
+ for (const batch of topo.linearize(this._catchupMigratedNodes)) {
1066
+ await this._processBatch(batch)
1056
1067
  updated = true
1057
-
1058
- order.shift(best)
1059
1068
  }
1060
1069
  } finally {
1061
1070
  for (const core of opened) await core.close()
package/lib/migrations.js CHANGED
@@ -6,7 +6,6 @@ const { decodeBlock } = require('hyperbee2/lib/encoding.js')
6
6
 
7
7
  const encoding = require('./encoding.js')
8
8
  const { assert, bail } = require('./asserts.js')
9
- const { sortsBefore } = require('./writers.js')
10
9
 
11
10
  const { getEncoding } = require('../encoding/spec/autobee')
12
11
 
@@ -18,77 +17,13 @@ const INDEX_VERSION = 1
18
17
  const INFO = b4a.from([0x00, 0x00, 0x69, 0x6e, 0x66, 0x6f])
19
18
  const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)
20
19
 
21
- // hands out the catchup in sort order rather than legacy INFO order, so it
22
- // does not rebase on itself. only heads whose links are satisfied are
23
- // candidates, and the best of those by sortsBefore goes first. the caller
24
- // resolves weights on the candidates before picking
25
- class CatchupOrder {
26
- constructor(batches) {
27
- this.queues = new Map()
28
- this.applied = new Map()
29
-
30
- for (const batch of batches) {
31
- const id = b4a.toString(batch[0].key, 'hex')
32
- const q = this.queues.get(id)
33
- if (q) q.push(batch)
34
- else this.queues.set(id, [batch])
35
- }
36
-
37
- for (const [id, q] of this.queues) this.applied.set(id, q[0][0].length - 1)
38
- }
39
-
40
- get size() {
41
- return this.queues.size
42
- }
43
-
44
- candidates() {
45
- const batches = []
46
-
47
- for (const q of this.queues.values()) {
48
- if (this._isCandidate(q[0][0])) batches.push(q[0])
49
- }
50
-
51
- assert(batches.length > 0 || this.queues.size === 0, 'Migration catchup has unresolvable links')
52
-
53
- return batches
54
- }
55
-
56
- pick(candidates) {
57
- let best = null
58
- for (const batch of candidates) {
59
- if (best === null || sortsBefore(batch[0], best[0])) best = batch
60
- }
61
- return best
62
- }
63
-
64
- shift(batch) {
65
- const id = b4a.toString(batch[0].key, 'hex')
66
- const q = this.queues.get(id)
67
-
68
- assert(q && q[0] === batch, 'Catchup batch shifted out of order')
69
-
70
- q.shift()
71
- this.applied.set(id, batch[batch.length - 1].length)
72
- if (!q.length) this.queues.delete(id)
73
- }
74
-
75
- _isCandidate(node) {
76
- for (const l of node.links) {
77
- const length = this.applied.get(b4a.toString(l.key, 'hex'))
78
- if (length !== undefined && length < l.length) return false
79
- }
80
- return true
81
- }
82
- }
83
-
84
20
  module.exports = {
85
21
  getCoreManifest,
86
22
  getViewMap,
87
23
  checkAutobaseMigration,
88
24
  readLegacySystemInfo,
89
25
  inflateLegacyOplog,
90
- coreLength,
91
- CatchupOrder
26
+ coreLength
92
27
  }
93
28
 
94
29
  // a peer with a sparse copy may not hold block 0, but the recorded tail is the
package/lib/topo.js CHANGED
@@ -9,6 +9,8 @@ const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
9
9
 
10
10
  exports.sort = sort
11
11
  exports.replay = replay
12
+ exports.linearize = linearize
13
+ exports.getOplogBatch = getOplogBatch
12
14
 
13
15
  exports.isLinking = isLinking
14
16
  exports.isLinkingAll = isLinkingAll
@@ -92,6 +94,18 @@ async function sort(auto, batch) {
92
94
  }
93
95
  }
94
96
 
97
+ // the order the batches settle in - applying them in it never undoes.
98
+ // batches must arrive after the batches they link
99
+ function linearize(batches) {
100
+ const list = []
101
+
102
+ for (const batch of batches) {
103
+ addSorted(list, { undo: null, batch, index: -1 })
104
+ }
105
+
106
+ return list.map((e) => e.batch)
107
+ }
108
+
95
109
  function addSorted(list, entry) {
96
110
  const node = entry.batch[0]
97
111
  const clock = new Clock()
@@ -215,16 +229,17 @@ async function getOplogBatch(undo, core, length, weight, index, readOpts = null)
215
229
  const head = createNode(core, length, weight, oplog)
216
230
  const legacy = oplog.version <= LEGACY_OPLOG_VERSION
217
231
 
218
- // if this is legacy the start pointer is always 0, fix
219
- if (legacy && !oplog.links.length) {
232
+ // legacy start is always 0, walk back to the previous head (end 0). links
233
+ // do not mark the start, legacy batch nodes link their own predecessor.
234
+ // only the block before the batch can be missing (legacy ff), so a miss is
235
+ // the boundary
236
+ if (legacy) {
220
237
  for (let i = length - 1; i > 0; i--) {
221
- const block = await core.get(i - 1, readOpts)
222
- if (block === null) return null // wait: false in replay helper
238
+ const block = await core.get(i - 1, { wait: false })
239
+ if (block === null) break
223
240
 
224
- const oplog = encoding.decodeOplog(block)
225
- if (oplog.batch.end === 0) break
241
+ if (encoding.decodeOplog(block).batch.end === 0) break
226
242
  head.batch.start++
227
- if (oplog.links.length > 0) break // always start of batch in legacy
228
243
  }
229
244
  }
230
245
 
package/lib/writers.js CHANGED
@@ -10,6 +10,7 @@ const WRITER_PREFETCH = 10
10
10
 
11
11
  const BATCH_WRITER_SANITY = 1024
12
12
  const BATCH_OPTIMISTIC_SANITY = 32
13
+ const DEFAULT_MAX_UNTRUSTED_WRITERS = 128
13
14
 
14
15
  const MAX_FORWARD_TIME_DRIFT = 2 * 3600 * 1000
15
16
 
@@ -24,6 +25,11 @@ class ActiveWriters {
24
25
  this.writable = false
25
26
  this.pending = []
26
27
 
28
+ this.maxUntrustedWriters =
29
+ auto._handlers.maxUntrustedWriters === undefined
30
+ ? DEFAULT_MAX_UNTRUSTED_WRITERS
31
+ : auto._handlers.maxUntrustedWriters || Infinity
32
+
27
33
  this._pendingBuffer = new Map()
28
34
  this._adding = new Map()
29
35
  this._lock = new ReadWriteLock()
@@ -250,6 +256,14 @@ class ActiveWriters {
250
256
  let w = this.active.get(id)
251
257
  if (w) return w
252
258
 
259
+ // sanity limit while untrusted, a dropped writer is requested again by the
260
+ // links waiting on it or by peers announcing it once the round has gc'd
261
+ if (this.active.size >= this.maxUntrustedWriters && !(await this.auto.isLocalTrusted())) {
262
+ return null
263
+ }
264
+
265
+ this.auto.stats.writersOpened++
266
+
253
267
  const local = b4a.equals(key, this.auto.local.key)
254
268
  const encryption = this.auto.encryptionKey ? new WriterEncryption(this.auto) : null
255
269
 
@@ -446,6 +460,7 @@ class Writer {
446
460
  }
447
461
 
448
462
  close() {
463
+ if (!this.isClosed && this !== this.writers.localWriter) this.writers.auto.stats.writersClosed++
449
464
  this.isClosed = true
450
465
 
451
466
  if (this.waiting) {
@@ -677,25 +692,33 @@ class Writer {
677
692
 
678
693
  const has = await Promise.all(promises)
679
694
 
695
+ let missing = false
696
+
680
697
  for (let i = 0; i < links.length; i++) {
681
698
  if (has[i]) continue
682
699
  const link = links[i]
683
- const id = b4a.toString(link.key, 'hex')
684
700
 
685
- this.waiting = this.writers.triggers.add(id, link.length, this)
701
+ // one trigger per writer, wait on the first missing link
702
+ if (!missing) {
703
+ const id = b4a.toString(link.key, 'hex')
686
704
 
687
- // check it didn't arrive underneath us
688
- if (await this.system.has(link)) {
689
- this.writers.triggers.remove(this.waiting)
690
- this.waiting = null
691
- continue
705
+ this.waiting = this.writers.triggers.add(id, link.length, this)
706
+
707
+ // check it didn't arrive underneath us
708
+ if (await this.system.has(link)) {
709
+ this.writers.triggers.remove(this.waiting)
710
+ this.waiting = null
711
+ continue
712
+ }
713
+
714
+ missing = true
692
715
  }
693
716
 
717
+ // but wake up every missing link so they fetch in parallel
694
718
  this.writers.wakeup(link.key, link.length).catch(noop)
695
- return false
696
719
  }
697
720
 
698
- return true
721
+ return !missing
699
722
  }
700
723
 
701
724
  get appending() {
@@ -773,7 +796,6 @@ function sortsBefore(a, b) {
773
796
  }
774
797
 
775
798
  exports.ActiveWriters = ActiveWriters
776
- exports.sortsBefore = sortsBefore
777
799
  exports.Writer = Writer
778
800
  exports.createNode = createNode
779
801
  exports.finalizeNodeBatch = finalizeNodeBatch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.7.3",
3
+ "version": "2.8.0",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",