autobee 2.0.0-rc.9 → 2.0.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/lib/system.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const Hyperbee = require('hyperbee2')
2
+ const Promotions = require('./promotions.js')
2
3
  const b4a = require('b4a')
3
4
 
4
5
  const { AUTOBEE_VERSION } = require('./constants')
@@ -43,31 +44,39 @@ module.exports = class Systembee {
43
44
  this.version = 0
44
45
  this.timestamp = 0
45
46
  this.flushes = 0
46
- this.indexers = null // legacy
47
47
  this.updates = new Map()
48
48
  this.writers = new Map()
49
- this.shared = null
50
49
  this.encrypted = opts.encrypted === true
50
+ this.promotions = new Promotions(this.bee)
51
51
  }
52
52
 
53
- // clamps weight to max(requested, current). order-independent, so
54
- // concurrent races converge and honest weights never decrease
55
- async addWriter(key, { length = 0, weight = 1, isGenesis = false } = {}) {
53
+ async addWriter(
54
+ key,
55
+ { length = 0, weight = 1, isGenesis = false, coord = null, carrier = -1, anchor = false } = {}
56
+ ) {
56
57
  const info = await this.get(key, { unflushed: true })
57
58
  if (length === 0) {
58
59
  length = info ? info.length : 0
59
60
  }
60
61
 
62
+ const effective = coord && carrier < weight ? Math.max(carrier, 0) : weight
63
+ const hint =
64
+ anchor && coord ? { key: coord.key, length: coord.length, weight: effective } : null
65
+ const pending = coord && !isGenesis ? weight : -1
66
+
61
67
  const current = info ? info.maxWeight : 0
62
- if (weight <= current) {
63
- if (info && !info.isRemoved && length <= info.length) return
68
+ if (effective <= current) {
69
+ // isAdded is false only when nothing has recorded the add yet - skipping
70
+ // the update then leaves the writer undiscoverable, never announced
71
+ const added = info && info.isAdded !== false
72
+ if (added && !info.isRemoved && length <= info.length && !hint && pending === -1) return
64
73
 
65
- this.update(key, length, -1, -1, false, true, false, false, false, -1, false)
74
+ this.update(key, length, -1, -1, false, true, false, -1, hint, pending)
66
75
  return
67
76
  }
68
77
 
69
- const w = isGenesis ? weight : -1
70
- this.update(key, length, w, weight, isGenesis, true, false, false, false, -1, true)
78
+ const w = isGenesis ? effective : -1
79
+ this.update(key, length, w, effective, isGenesis, true, false, -1, hint, pending)
71
80
  }
72
81
 
73
82
  async ackWriter(key, { length = 0 } = {}) {
@@ -75,7 +84,7 @@ module.exports = class Systembee {
75
84
  const info = await this.get(key, { unflushed: false })
76
85
  length = info ? info.length : 0
77
86
  }
78
- this.update(key, length, -1, -1, false, false, false, true, false, -1, false)
87
+ this.update(key, length, -1, -1, false, false, false, -1, null)
79
88
  }
80
89
 
81
90
  async removeWriter(key, { length = 0 } = {}) {
@@ -83,17 +92,24 @@ module.exports = class Systembee {
83
92
  const info = await this.get(key, { unflushed: true })
84
93
  length = info ? info.length : 0
85
94
  }
86
- this.update(key, length, -1, 0, false, false, true, false, false, -1, false)
95
+ this.update(key, length, -1, 0, false, false, true, -1, null)
87
96
  }
88
97
 
89
- addAnchor(key, { length = 1 } = {}) {
90
- this.update(key, length, -1, -1, false, true, false, false, true, -1, false)
98
+ addAnchor(key) {
99
+ this.update(key, 0, -1, -1, false, true, false, -1, null)
91
100
  }
92
101
 
93
102
  isGenesis() {
94
103
  return this.bee.head() === null || this.bee.head().length === 0
95
104
  }
96
105
 
106
+ isHead(key) {
107
+ for (const head of this.heads) {
108
+ if (b4a.equals(head.key, key)) return true
109
+ }
110
+ return false
111
+ }
112
+
97
113
  async boot(view, { timeout } = {}) {
98
114
  await this.bee.ready()
99
115
  this.bee.move(view)
@@ -115,60 +131,6 @@ module.exports = class Systembee {
115
131
  }
116
132
  }
117
133
 
118
- async commonAncestor(head) {
119
- const snap = this.bee.checkout(head)
120
-
121
- const ca = infoIterator(this.bee)
122
- const cb = infoIterator(snap)
123
-
124
- let l = await next(ca)
125
- let r = await next(cb)
126
-
127
- while (l && r) {
128
- if (l.legacy && r.legacy) {
129
- // legacy nodes just compare head
130
- if (sameHead(l.node.head, r.node.head)) {
131
- return {
132
- flushes: l.info.flushes,
133
- system: l.node.head,
134
- view: l.info.view || EMPTY_HEAD
135
- }
136
- }
137
-
138
- // different legacy cores never converge - genuinely unrelated history
139
- if (!b4a.equals(l.node.head.key, r.node.head.key)) return null
140
-
141
- if (l.node.head.length >= r.node.head.length) l = await next(ca)
142
- else r = await next(cb)
143
- continue
144
- }
145
-
146
- if (l.legacy !== r.legacy) {
147
- // current-format history sits above the legacy band: descend it
148
- if (!l.legacy) l = await next(ca)
149
- else r = await next(cb)
150
- continue
151
- }
152
-
153
- if (l.info.flushes === r.info.flushes && sameHead(l.info.view, r.info.view)) {
154
- return {
155
- flushes: l.info.flushes,
156
- system: l.node.head,
157
- view: l.info.view || EMPTY_HEAD
158
- }
159
- }
160
-
161
- const lf = l.info.flushes
162
- const rf = r.info.flushes
163
-
164
- if (lf >= rf) l = await next(ca)
165
- if (rf >= lf) r = await next(cb)
166
- }
167
-
168
- // no common ancestor
169
- return null
170
- }
171
-
172
134
  async reset({ timeout } = {}) {
173
135
  const info = await this.getInfo({ timeout })
174
136
 
@@ -177,10 +139,9 @@ module.exports = class Systembee {
177
139
  this.heads = info ? info.heads : []
178
140
  this.timestamp = info ? info.timestamp : 0
179
141
  this.flushes = info ? info.flushes : 0
180
- this.indexers = info ? info.indexers : null
181
- this.shared = null
182
142
  this.updates.clear()
183
143
  this.writers.clear()
144
+ this.promotions.reset(info)
184
145
 
185
146
  if (this.version > AUTOBEE_VERSION) {
186
147
  throw new Error('Autobee signals newer version than locally supported')
@@ -200,16 +161,8 @@ module.exports = class Systembee {
200
161
  async undo(head) {
201
162
  this.bee.move(head)
202
163
 
203
- const shared = this.shared
204
164
  await this.reset()
205
165
 
206
- // deepest rewind since last mark - everything at or below it is untouched
207
- if (shared === null || this.flushes < shared.flushes) {
208
- this.shared = { flushes: this.flushes, view: this.view || EMPTY_HEAD, system: head }
209
- } else if (shared) {
210
- this.shared = shared
211
- }
212
-
213
166
  return this.view || EMPTY_HEAD
214
167
  }
215
168
 
@@ -238,19 +191,7 @@ module.exports = class Systembee {
238
191
 
239
192
  // resolved sort weight, re-stamped on every reapplication - this is what
240
193
  // topo reads back out of the changes stream
241
- this.update(
242
- node.key,
243
- node.length,
244
- node.weight,
245
- -1,
246
- false,
247
- false,
248
- false,
249
- false,
250
- false,
251
- node.timestamp,
252
- false
253
- )
194
+ this.update(node.key, node.length, node.weight, -1, false, false, false, node.timestamp, null)
254
195
  }
255
196
 
256
197
  async canApply(key, optimistic) {
@@ -269,8 +210,11 @@ module.exports = class Systembee {
269
210
  return new SystemSnapshot(bee)
270
211
  }
271
212
 
272
- async get(key, { unflushed = false, timeout } = {}) {
273
- const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
213
+ async get(key, { unflushed = false, timeout, activeRequests } = {}) {
214
+ const node = await this.bee.get(encoding.encodeSystemWriterKey(key), {
215
+ timeout,
216
+ activeRequests
217
+ })
274
218
  const info = node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
275
219
  if (!unflushed) return info
276
220
 
@@ -288,16 +232,12 @@ module.exports = class Systembee {
288
232
  isGenesis: upd.maxWeight !== -1 && !!upd.isGenesis,
289
233
  length: upd.length,
290
234
  clock: 0,
291
- isAdded: upd.isAdded,
292
- isAcked: upd.isAcked,
293
- isAnchor: upd.isAnchor
235
+ isAdded: upd.isAdded
294
236
  }
295
237
  }
296
238
 
297
239
  if (upd.isAdded) info.isAdded = true
298
240
  if (upd.isRemoved) info.isRemoved = true
299
- if (upd.isAcked) info.isAcked = true
300
- if (upd.isAnchor) info.isAnchor = true
301
241
  if (upd.maxWeight !== -1) {
302
242
  info.isGenesis = upd.isGenesis
303
243
  info.maxWeight = upd.maxWeight
@@ -338,17 +278,16 @@ module.exports = class Systembee {
338
278
  isGenesis,
339
279
  isAdded,
340
280
  isRemoved,
341
- isAcked,
342
- isAnchor,
343
281
  timestamp,
344
- needsWitness
282
+ grant,
283
+ pending = -1
345
284
  ) {
346
285
  const id = b4a.toString(key, 'hex')
347
286
 
348
287
  if (isAdded) {
349
- this.writers.set(id, { key, added: true, isAnchor: !!isAnchor })
288
+ this.writers.set(id, { key, added: true })
350
289
  } else if (isRemoved) {
351
- this.writers.set(id, { key, added: false, isAnchor: !!isAnchor })
290
+ this.writers.set(id, { key, added: false })
352
291
  }
353
292
 
354
293
  let upd = this.updates.get(id)
@@ -362,10 +301,9 @@ module.exports = class Systembee {
362
301
  isGenesis: false,
363
302
  isRemoved: false,
364
303
  isAdded: false,
365
- isAcked: false,
366
- isAnchor: false,
367
- timestamp: -1,
368
- needsWitness
304
+ timestamp: 0,
305
+ grant: null,
306
+ pending: -1
369
307
  }
370
308
  this.updates.set(id, upd)
371
309
  }
@@ -376,7 +314,6 @@ module.exports = class Systembee {
376
314
  upd.maxWeight = maxWeight
377
315
  upd.isGenesis = isGenesis
378
316
  }
379
- if (isAnchor) upd.isAnchor = true
380
317
  if (isAdded) {
381
318
  upd.isAdded = true
382
319
  upd.isRemoved = false
@@ -385,18 +322,16 @@ module.exports = class Systembee {
385
322
  upd.isRemoved = true
386
323
  upd.isAdded = false
387
324
  }
388
- if (isAcked) {
389
- upd.isAcked = true
390
- }
391
325
  if (length > upd.length) {
392
326
  upd.length = length
393
327
  }
394
328
 
395
- if (timestamp !== -1 && upd.timestamp === -1) {
329
+ if (timestamp > -1) {
396
330
  upd.timestamp = timestamp
397
331
  }
398
- if (needsWitness && !upd.needsWitness) {
399
- upd.needsWitness = true
332
+ if (pending > upd.pending) upd.pending = pending
333
+ if (grant && (upd.grant === null || grant.weight > upd.grant.weight)) {
334
+ upd.grant = grant
400
335
  }
401
336
  }
402
337
 
@@ -411,55 +346,54 @@ module.exports = class Systembee {
411
346
  const isOplog = b4a.equals(upd.key, oplog)
412
347
 
413
348
  const record = {
414
- version: 4,
349
+ version: 5,
415
350
  key: upd.key,
416
351
  isRemoved: upd.isRemoved ? true : upd.isAdded ? false : v ? v.isRemoved : false,
417
352
  isOplog,
353
+ isGenesis: upd.maxWeight !== -1 ? !!upd.isGenesis : v ? v.isGenesis : false,
418
354
  weight: upd.weight !== -1 ? upd.weight : prevWeight,
419
355
  maxWeight: upd.maxWeight !== -1 ? upd.maxWeight : prevMaxWeight,
420
- isGenesis: upd.maxWeight !== -1 ? !!upd.isGenesis : v ? v.isGenesis : false,
421
356
  length: Math.max(upd.length, v ? v.length : 0),
422
- clock: this.flushes,
423
- isAnchor: upd.isAnchor || (v ? v.isAnchor : false),
424
- timestamp: isOplog ? upd.timestamp : -1
357
+ timestamp: isOplog ? upd.timestamp : 0
425
358
  }
426
359
 
427
360
  return {
428
361
  key: k,
429
362
  value: encoding.encodeSystemWriter(record),
430
363
  add: null,
431
- remove: record.isRemoved ? encoding.encodeSystemIndexerKey(upd.key) : null,
364
+ remove: null,
432
365
  maxWeight: record.maxWeight
433
366
  }
434
367
  }
435
368
 
436
369
  async flush(batch, bee) {
437
- if (batch.length === 0) return { witnessed: [], changed: [] }
370
+ if (batch.length === 0) return []
438
371
 
439
372
  const oplog = batch[batch.length - 1].key
440
373
 
441
374
  const updates = [...this.updates.values()]
442
- const results = await Promise.all(updates.map((upd) => this._updateWriter(upd, oplog)))
443
-
444
- const witnessed = []
445
- for (let i = 0; i < updates.length; i++) {
446
- if (updates[i].needsWitness) {
447
- witnessed.push({ key: updates[i].key, weight: results[i].maxWeight })
448
- }
449
- }
375
+ const results = await Promise.all(updates.map((upd) => this._flushUpdate(upd, oplog)))
376
+ const pendings = await this.promotions.flushPending(updates)
450
377
 
451
378
  const w = this.bee.write()
452
379
 
453
- // migrate
454
- if (this.indexers) {
455
- for (const { key } of this.indexers) w.tryPut(encoding.encodeSystemIndexerKey(key), EMPTY)
456
- this.indexers = null
380
+ for (const { writer } of results) {
381
+ w.tryPut(writer.key, writer.value)
382
+ if (writer.add) w.tryPut(writer.add, EMPTY)
383
+ if (writer.remove) w.tryDelete(writer.remove)
457
384
  }
458
385
 
459
- for (const { key, value, add, remove } of results) {
460
- w.tryPut(key, value)
461
- if (add) w.tryPut(add, EMPTY)
462
- if (remove) w.tryDelete(remove)
386
+ for (const { grant } of results) {
387
+ if (!grant) continue
388
+ if (grant.remove) w.tryDelete(grant.key)
389
+ else w.tryPut(grant.key, grant.value)
390
+ }
391
+
392
+ if (pendings) {
393
+ for (const p of pendings) {
394
+ if (p.remove) w.tryDelete(p.key)
395
+ else w.tryPut(p.key, p.value)
396
+ }
463
397
  }
464
398
 
465
399
  this.flushes++
@@ -470,11 +404,15 @@ module.exports = class Systembee {
470
404
  heads: this.heads,
471
405
  timestamp: this.timestamp,
472
406
  flushes: this.flushes,
473
- indexers: null // legacy
407
+ indexers: null, // legacy
408
+ pending: this.promotions.digest
474
409
  }
475
410
 
476
411
  w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))
477
412
 
413
+ this.version = info.version
414
+ this.view = info.view
415
+
478
416
  await w.flush()
479
417
 
480
418
  // Can enable if needed during debuggin
@@ -482,64 +420,43 @@ module.exports = class Systembee {
482
420
 
483
421
  this.updates.clear()
484
422
 
485
- if (this.writers.size === 0) return { witnessed, changed: [] }
423
+ if (this.writers.size === 0) return []
486
424
 
487
425
  const changes = [...this.writers.values()]
488
426
 
489
427
  this.writers.clear()
490
428
 
491
- return {
492
- witnessed,
493
- changed: changes
494
- }
429
+ return changes
495
430
  }
496
- }
497
431
 
498
- function getInfo(data) {
499
- for (const { keys } of data.batch) {
500
- for (const k of keys) {
501
- const prefix = k.key[0]
432
+ async _flushUpdate(upd, oplog) {
433
+ const [writer, grant] = await Promise.all([
434
+ this._updateWriter(upd, oplog),
435
+ this.promotions.updateHint(upd)
436
+ ])
502
437
 
503
- if (prefix === 0) {
504
- try {
505
- return encoding.decodeSystemInfo(k.value)
506
- } catch {
507
- return null
508
- }
509
- }
510
- }
438
+ return { writer, grant }
511
439
  }
512
440
 
513
- return null
514
- }
515
-
516
- function sameHead(a, b) {
517
- if (!a || !b || a.length !== b.length) return false
518
- if (!a.key || !b.key) return a.key === b.key
519
- return b4a.equals(a.key, b.key)
520
- }
441
+ pendingPromotion(key, opts) {
442
+ return this.promotions.pending(key, opts)
443
+ }
521
444
 
522
- async function* infoIterator(bee) {
523
- try {
524
- for await (const node of bee.createChangesStream()) {
525
- const legacy = topo.isLegacyNode(node)
526
- const info = legacy ? getLegacyInfo(node) : getInfo(node)
527
- if (info) yield { node, info, legacy }
528
- }
529
- } catch {
530
- // fall through
445
+ listPendingPromotions(opts) {
446
+ return this.promotions.list(opts)
531
447
  }
532
- }
533
448
 
534
- async function next(it) {
535
- const res = await it.next()
536
- return res.done ? null : res.value
449
+ grantHint(key, opts) {
450
+ return this.promotions.hint(key, opts)
451
+ }
537
452
  }
538
453
 
539
- function getLegacyInfo(data) {
454
+ function getInfo(data) {
540
455
  for (const { keys } of data.batch) {
541
456
  for (const k of keys) {
542
- if (b4a.equals(k.key, INFO_LEGACY_KEY)) {
457
+ const prefix = k.key[0]
458
+
459
+ if (prefix === 0) {
543
460
  try {
544
461
  return encoding.decodeSystemInfo(k.value)
545
462
  } catch {
@@ -548,5 +465,6 @@ function getLegacyInfo(data) {
548
465
  }
549
466
  }
550
467
  }
468
+
551
469
  return null
552
470
  }