autobee 2.0.0-rc.31 → 2.0.0-rc.34

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
@@ -129,12 +129,10 @@ module.exports = class Autobee extends ReadyResource {
129
129
  this._hasApply = !!handlers.apply
130
130
  this._hasUpdate = !!handlers.update
131
131
  this._needsUpdate = false
132
- this._ackRequired = false
133
132
  this._approvalCheck = true
134
133
  this._approvedPending = new Map()
135
134
  this._approvalRequests = []
136
135
  this._prefetchingApprovals = false
137
- this._ackedHeads = new Map()
138
136
  this._updateLocalCore = null
139
137
  this._host = new ApplyCalls(this)
140
138
  this._notifyHandler = null
@@ -385,7 +383,6 @@ module.exports = class Autobee extends ReadyResource {
385
383
  }
386
384
 
387
385
  this.local.setEncryption(this._getEncryptionProvider())
388
- this.local.setActive(true)
389
386
 
390
387
  this.writers = new ActiveWriters(this)
391
388
 
@@ -464,6 +461,11 @@ module.exports = class Autobee extends ReadyResource {
464
461
  for (const head of this.system.heads) {
465
462
  await this.writers.add(head.key)
466
463
  }
464
+
465
+ if (!this.system.heads.length) {
466
+ await this.writers.add(this.bootstrap.key)
467
+ }
468
+
467
469
  await this._bump()
468
470
  }
469
471
 
@@ -554,8 +556,6 @@ module.exports = class Autobee extends ReadyResource {
554
556
  else if (head) await this._bootFromHead(head, bootCondition, wait)
555
557
  }
556
558
 
557
- this._ackRequired = false
558
-
559
559
  const changes = this._hasUpdate ? new UpdateChanges(this) : null
560
560
  if (changes) changes.track()
561
561
 
@@ -578,16 +578,7 @@ module.exports = class Autobee extends ReadyResource {
578
578
  break // revaluate conditions...
579
579
  }
580
580
 
581
- if (await this._bumpPendingWriters()) {
582
- this._needsUpdate = true
583
- continue
584
- }
585
-
586
- if (!this._appendAck()) break
587
- this._needsUpdate = true
588
- }
589
-
590
- if (!this._interrupting && (await this._appendApprovals())) {
581
+ if (!(await this._bumpPendingWriters())) break
591
582
  this._needsUpdate = true
592
583
  }
593
584
 
@@ -647,24 +638,44 @@ module.exports = class Autobee extends ReadyResource {
647
638
  // wakeup hints keep arriving asynchronously
648
639
  async _applyWakeupHints() {
649
640
  const hints = this._wakeup.flush()
650
- if (!hints.size) return hints
641
+ if (!hints.size) return []
651
642
 
652
643
  this.previousDrain = Date.now()
644
+ const results = await this._filterHints(hints)
653
645
 
654
- for (const [hex, length] of hints) {
655
- const key = b4a.from(hex, 'hex')
646
+ for (const { key, length } of results) {
656
647
  // wakeup() itself no-ops the add for an already-active writer, but still
657
648
  // needs to run so it can hint() the announced length - skipping it here
658
649
  // for active writers left gc with no hint to protect them
659
650
  await this.writers.wakeup(key, length)
660
651
  }
661
652
 
662
- return hints
653
+ return results
654
+ }
655
+
656
+ async _filterHints(hints) {
657
+ const results = []
658
+ const promises = []
659
+ for (const [hex, length] of hints) {
660
+ const key = b4a.from(hex, 'hex')
661
+ promises.push(this._filterTracked(key, length, results))
662
+ }
663
+ await Promise.all(promises)
664
+ return results
665
+ }
666
+
667
+ async _filterTracked(key, length, results) {
668
+ if (length) {
669
+ const info = await this.system.get(key)
670
+ // if we've seen this already, ignore
671
+ if (info && info.length >= length) return
672
+ }
673
+ results.push({ key, length })
663
674
  }
664
675
 
665
676
  async _flushWakeup() {
666
677
  const hints = await this._applyWakeupHints()
667
- if (!hints.size) return
678
+ if (!hints.length) return
668
679
 
669
680
  if (!this._ffEnabled) return
670
681
 
@@ -688,10 +699,9 @@ module.exports = class Autobee extends ReadyResource {
688
699
  async _readCandidateHeads(hints, timeout) {
689
700
  const promises = []
690
701
 
691
- for (const [hex, length] of hints) {
702
+ for (const { key, length } of hints) {
692
703
  if (length === 0) continue
693
- const key = b4a.from(hex, 'hex')
694
- promises.push(this._getOplog(key, length, timeout ? { timeout } : null))
704
+ promises.push(this._resolveOplogHint(key, length, timeout ? { timeout } : null))
695
705
  }
696
706
 
697
707
  const ops = await Promise.all(promises)
@@ -714,6 +724,22 @@ module.exports = class Autobee extends ReadyResource {
714
724
  return heads
715
725
  }
716
726
 
727
+ async _resolveOplogHint(key, length, opts) {
728
+ const core = this.openCore(key)
729
+
730
+ try {
731
+ await core.ready()
732
+ const max = Math.max(length, core.length)
733
+
734
+ const block = await this.readOplog(core, max, opts)
735
+ if (max >= core.length) return block // best one
736
+
737
+ return await this.readOplog(core, core.length, opts)
738
+ } finally {
739
+ await core.close()
740
+ }
741
+ }
742
+
717
743
  async _getOplog(key, length, opts = null) {
718
744
  const core = this.openCore(key)
719
745
 
@@ -987,51 +1013,6 @@ module.exports = class Autobee extends ReadyResource {
987
1013
  }
988
1014
  }
989
1015
 
990
- async _appendApprovals() {
991
- if (!this.writers.writable) return false
992
-
993
- const approvals = await this._collectApprovals()
994
- if (!approvals) return false
995
-
996
- const links = this.system.getLinks(this.local.key)
997
- const t = Math.max(this._now(), this.system.timestamp)
998
- this.writers.appendLocal(null, t, { start: 0, end: 0 }, links, false, null, approvals)
999
- return true
1000
- }
1001
-
1002
- // append a null value node to ack writer
1003
- _appendAck() {
1004
- if (!this._ackRequired) return false
1005
- if (!this.writers.writable) return false
1006
-
1007
- if (this.writers.localWriter.pending !== null) return false
1008
-
1009
- const links = this.system.getLinks(this.local.key)
1010
- const t = Math.max(this._now(), this.system.timestamp)
1011
-
1012
- let unlinked = false
1013
- for (const { key, length } of links) {
1014
- const hex = b4a.toString(key, 'hex')
1015
- const acked = this._ackedHeads.get(hex) || 0
1016
- if (length > acked) {
1017
- unlinked = true
1018
- break
1019
- }
1020
- }
1021
- if (!unlinked) {
1022
- this._ackRequired = false
1023
- return false
1024
- }
1025
-
1026
- for (const { key, length } of links) {
1027
- this._ackedHeads.set(b4a.toString(key, 'hex'), length)
1028
- }
1029
-
1030
- this.writers.appendLocal(null, t, { start: 0, end: 0 }, links, false, null)
1031
- this._ackRequired = false
1032
- return true
1033
- }
1034
-
1035
1016
  async _bumpPendingWriters({ local = false } = {}) {
1036
1017
  if (!local && this._catchupMigratedNodes !== null) {
1037
1018
  const updated = await this._bumpMigratedWriters()
@@ -1398,10 +1379,9 @@ module.exports = class Autobee extends ReadyResource {
1398
1379
 
1399
1380
  async _bootAttempt(head, bootCondition, reference) {
1400
1381
  // peek, so the hints are still there for the drain once we have booted
1401
- const candidates = await this._readCandidateHeads(
1402
- this._wakeup.hints,
1403
- FastForward.DEFAULT_TIMEOUT
1404
- )
1382
+ const result = await this._filterHints(this._wakeup.hints)
1383
+
1384
+ const candidates = await this._readCandidateHeads(result, FastForward.DEFAULT_TIMEOUT)
1405
1385
 
1406
1386
  const ff = await FastForward.fromHeads(this, [head, ...candidates], {
1407
1387
  force: true,
@@ -1,5 +1,4 @@
1
1
  const ID = require('hypercore-id-encoding')
2
- const b4a = require('b4a')
3
2
 
4
3
  class ApplyCalls {
5
4
  constructor(auto) {
@@ -44,10 +43,6 @@ class ApplyCalls {
44
43
 
45
44
  ackWriter(key) {
46
45
  if (typeof key === 'string') key = ID.decode(key)
47
- // acking an optimistic node means we owe it a linking node
48
- if (this.applying && this.applying[0].optimistic && !b4a.equals(key, this.auto.local.key)) {
49
- this.auto._ackRequired = true
50
- }
51
46
  return this.auto.system.ackWriter(key)
52
47
  }
53
48
 
@@ -9,10 +9,6 @@ const { LEGACY_AUTOBASE_VERSION } = require('./constants.js')
9
9
  const DEFAULT_OP_TIMEOUT = 5_000
10
10
  const MIN_FF_GAP = 32
11
11
 
12
- // views are only stamped on the last node of a flush, so a head may sit above
13
- // the most recent one - how far back we look for it
14
- const MAX_FLUSH_SEARCH = 64
15
-
16
12
  module.exports = class FastForward {
17
13
  constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
18
14
  this.auto = auto
@@ -147,18 +143,9 @@ module.exports = class FastForward {
147
143
  const core = auto.openCore(head.key)
148
144
 
149
145
  try {
150
- let length = head.length
151
-
152
- for (let i = 0; i < MAX_FLUSH_SEARCH; i++) {
153
- const oplog = await auto.readOplog(core, length, opts)
154
- if (oplog === null) return null
155
- if (oplog.op.views) return oplog
156
-
157
- length = oplog.length - 1
158
- if (length <= 0) return null
159
- }
160
-
161
- return null
146
+ const oplog = await auto.readOplog(core, head.length, opts)
147
+ if (oplog === null) return null
148
+ return oplog.op.views ? oplog : null
162
149
  } finally {
163
150
  await core.close()
164
151
  }
package/lib/migrations.js CHANGED
@@ -223,7 +223,9 @@ async function inflateLegacyOplog(buf, core, seq, timeout) {
223
223
 
224
224
  fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, { timeout }) : buf)
225
225
  fetches.push(
226
- m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer, { timeout }) : buf
226
+ m.checkpoint.system.checkpointer
227
+ ? core.get(seq - m.checkpoint.system.checkpointer, { timeout })
228
+ : buf
227
229
  )
228
230
 
229
231
  const [digestNode, checkpointNode] = await Promise.all(fetches)
package/lib/system.js CHANGED
@@ -71,12 +71,12 @@ module.exports = class Systembee {
71
71
  const added = info && info.isAdded !== false
72
72
  if (added && !info.isRemoved && length <= info.length && !hint && pending === -1) return
73
73
 
74
- this.update(key, length, -1, -1, false, true, false, false, -1, hint, pending)
74
+ this.update(key, length, -1, -1, false, true, false, -1, hint, pending)
75
75
  return
76
76
  }
77
77
 
78
78
  const w = isGenesis ? effective : -1
79
- this.update(key, length, w, effective, isGenesis, true, false, false, -1, hint, pending)
79
+ this.update(key, length, w, effective, isGenesis, true, false, -1, hint, pending)
80
80
  }
81
81
 
82
82
  async ackWriter(key, { length = 0 } = {}) {
@@ -84,7 +84,7 @@ module.exports = class Systembee {
84
84
  const info = await this.get(key, { unflushed: false })
85
85
  length = info ? info.length : 0
86
86
  }
87
- this.update(key, length, -1, -1, false, false, false, true, -1, null)
87
+ this.update(key, length, -1, -1, false, false, false, -1, null)
88
88
  }
89
89
 
90
90
  async removeWriter(key, { length = 0 } = {}) {
@@ -92,17 +92,24 @@ module.exports = class Systembee {
92
92
  const info = await this.get(key, { unflushed: true })
93
93
  length = info ? info.length : 0
94
94
  }
95
- this.update(key, length, -1, 0, false, false, true, false, -1, null)
95
+ this.update(key, length, -1, 0, false, false, true, -1, null)
96
96
  }
97
97
 
98
98
  addAnchor(key) {
99
- this.update(key, 0, -1, -1, false, true, false, false, -1, null)
99
+ this.update(key, 0, -1, -1, false, true, false, -1, null)
100
100
  }
101
101
 
102
102
  isGenesis() {
103
103
  return this.bee.head() === null || this.bee.head().length === 0
104
104
  }
105
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
+
106
113
  async boot(view, { timeout } = {}) {
107
114
  await this.bee.ready()
108
115
  this.bee.move(view)
@@ -184,18 +191,7 @@ module.exports = class Systembee {
184
191
 
185
192
  // resolved sort weight, re-stamped on every reapplication - this is what
186
193
  // topo reads back out of the changes stream
187
- this.update(
188
- node.key,
189
- node.length,
190
- node.weight,
191
- -1,
192
- false,
193
- false,
194
- false,
195
- false,
196
- node.timestamp,
197
- null
198
- )
194
+ this.update(node.key, node.length, node.weight, -1, false, false, false, node.timestamp, null)
199
195
  }
200
196
 
201
197
  async canApply(key, optimistic) {
@@ -236,14 +232,12 @@ module.exports = class Systembee {
236
232
  isGenesis: upd.maxWeight !== -1 && !!upd.isGenesis,
237
233
  length: upd.length,
238
234
  clock: 0,
239
- isAdded: upd.isAdded,
240
- isAcked: upd.isAcked
235
+ isAdded: upd.isAdded
241
236
  }
242
237
  }
243
238
 
244
239
  if (upd.isAdded) info.isAdded = true
245
240
  if (upd.isRemoved) info.isRemoved = true
246
- if (upd.isAcked) info.isAcked = true
247
241
  if (upd.maxWeight !== -1) {
248
242
  info.isGenesis = upd.isGenesis
249
243
  info.maxWeight = upd.maxWeight
@@ -284,7 +278,6 @@ module.exports = class Systembee {
284
278
  isGenesis,
285
279
  isAdded,
286
280
  isRemoved,
287
- isAcked,
288
281
  timestamp,
289
282
  grant,
290
283
  pending = -1
@@ -308,7 +301,6 @@ module.exports = class Systembee {
308
301
  isGenesis: false,
309
302
  isRemoved: false,
310
303
  isAdded: false,
311
- isAcked: false,
312
304
  timestamp: 0,
313
305
  grant: null,
314
306
  pending: -1
@@ -330,9 +322,6 @@ module.exports = class Systembee {
330
322
  upd.isRemoved = true
331
323
  upd.isAdded = false
332
324
  }
333
- if (isAcked) {
334
- upd.isAcked = true
335
- }
336
325
  if (length > upd.length) {
337
326
  upd.length = length
338
327
  }
package/lib/witness.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const b4a = require('b4a')
2
2
  const encoding = require('./encoding.js')
3
- const { DEFAULT_OP_TIMEOUT } = require('./constants.js')
4
3
 
5
4
  exports.currentWeight = currentWeight
6
5
  exports.resolveWeight = resolveWeight
@@ -56,7 +55,7 @@ async function readOplogNode(auto, key, length) {
56
55
 
57
56
  try {
58
57
  await core.ready()
59
- const block = await core.get(length - 1, { timeout: DEFAULT_OP_TIMEOUT })
58
+ const block = await core.get(length - 1)
60
59
  return encoding.decodeOplog(block)
61
60
  } finally {
62
61
  await core.close()
package/lib/writers.js CHANGED
@@ -18,17 +18,20 @@ class ActiveWriters {
18
18
 
19
19
  this.auto = auto
20
20
  this.active = new Map()
21
- this._adding = new Map()
22
21
  this.triggers = new Triggers()
23
- this.localWriter = new Writer(this, auto.system, true, local, b4a.toString(local.key, 'hex'))
22
+ this.localWriter = null
24
23
  this.writable = false
25
24
  this.pending = []
26
25
 
27
26
  this._pendingBuffer = new Map()
27
+ this._adding = new Map()
28
28
 
29
+ this.localWriter = new Writer(this, auto.system, true, local, b4a.toString(local.key, 'hex'))
29
30
  this.localWriter.attach()
31
+ }
30
32
 
31
- this._bootstrap()
33
+ get size() {
34
+ return this.active.size
32
35
  }
33
36
 
34
37
  [Symbol.iterator]() {
@@ -195,10 +198,7 @@ class ActiveWriters {
195
198
  if (local) await this.updateLocalState()
196
199
 
197
200
  let w = this.active.get(id)
198
- if (w) {
199
- w.couple() // couple if we weren't ready first time
200
- return w
201
- }
201
+ if (w) return w
202
202
 
203
203
  // no new sessions once teardown started
204
204
  if (this.auto._interrupting) return null
@@ -206,6 +206,7 @@ class ActiveWriters {
206
206
  const encryption = this.auto.encryptionKey ? new WriterEncryption(this.auto) : null
207
207
 
208
208
  const core = this.auto.store.get({
209
+ active: false,
209
210
  key,
210
211
  encryption,
211
212
  group: local || !this.auto.wakeupCapability ? null : this.auto.wakeupCapability.discoveryKey
@@ -222,7 +223,6 @@ class ActiveWriters {
222
223
 
223
224
  w = new Writer(this, this.auto.system, false, core, id)
224
225
  w.attach()
225
- w.couple()
226
226
 
227
227
  return w
228
228
  }
@@ -250,29 +250,6 @@ class ActiveWriters {
250
250
  w.decouple()
251
251
  if (w !== this.localWriter) await w.close()
252
252
  }
253
-
254
- _bootstrap() {
255
- const bootstrap = this.auto.bootstrap
256
-
257
- bootstrap.setEncryption(this.auto._getEncryptionProvider())
258
- bootstrap.setActive(true)
259
-
260
- const id = b4a.toString(bootstrap.key, 'hex')
261
- let w = this.active.get(id)
262
- if (w) {
263
- w.isBootstrap = true
264
- w.couple() // couple if we weren't ready first time
265
- return
266
- }
267
-
268
- const session = bootstrap.session()
269
- session.setEncryption(this.auto._getEncryptionProvider())
270
-
271
- w = new Writer(this, this.auto.system, false, session, id)
272
- w.isBootstrap = true // the root of the autobase, never gc'd
273
- w.attach()
274
- w.couple()
275
- }
276
253
  }
277
254
 
278
255
  class Writer {
@@ -298,20 +275,26 @@ class Writer {
298
275
  this.isRemoved = false
299
276
  this.isCoupled = false
300
277
  this.isFrozen = false
301
- this.isBootstrap = false
278
+ this.isHead = false
302
279
 
303
280
  this._onchangeBound = this._onchange.bind(this)
304
281
 
305
282
  if (!local) this.core.on('append', this._onchangeBound)
306
283
  if (!local) this.core.on('download', this._onchangeBound)
307
-
308
- this.couple()
309
284
  }
310
285
 
311
286
  get isIndexer() {
312
287
  return this.weight >= 2
313
288
  }
314
289
 
290
+ isSystemHead() {
291
+ if (this.system.heads.length === 0) {
292
+ return b4a.equals(this.core.key, this.writers.auto.key)
293
+ }
294
+
295
+ return this.system.isHead(this.core.key)
296
+ }
297
+
315
298
  couple() {
316
299
  if (this.isCoupled) return
317
300
  // if we don't have coupler yet we can't add
@@ -357,7 +340,10 @@ class Writer {
357
340
 
358
341
  // a peer announced this length for us, so we're not caught up until we've seen it
359
342
  hint(length) {
360
- if (length > this.hintedLength) this.hintedLength = length
343
+ if (length > this.hintedLength) {
344
+ this.hintedLength = length
345
+ this.core.setActive(true)
346
+ }
361
347
  }
362
348
 
363
349
  _onchange() {
@@ -400,7 +386,7 @@ class Writer {
400
386
  // don't drop any local nodes left in buffer, or work a peer told us about
401
387
  if (length < this.core.length || length < this.hintedLength || this.appending) {
402
388
  this.addPending()
403
- } else if (this !== this.writers.localWriter && !this.isIndexer && !this.isBootstrap) {
389
+ } else if (this !== this.writers.localWriter && !this.isHead) {
404
390
  await this.detachAndClose()
405
391
  }
406
392
  }
@@ -447,12 +433,24 @@ class Writer {
447
433
 
448
434
  async update() {
449
435
  const info = await this.system.get(this.core.key)
436
+ const length = info ? info.length : 0
450
437
 
451
- // an anchor core is prologue-only, so once its single node has applied
452
- // nothing more can ever come - retire the writer
453
- if (info && info.length > 0 && isAnchor(this.core)) {
454
- await this.detachAndClose()
455
- return info.length
438
+ const isHead = this.isSystemHead()
439
+
440
+ if (isHead) {
441
+ this.isHead = true
442
+ this.couple()
443
+ } else {
444
+ this.isHead = false
445
+ this.decouple()
446
+ }
447
+
448
+ const active = length < this.core.length || length < this.hintedLength
449
+
450
+ if (this.isHead || active) {
451
+ this.core.setActive(true)
452
+ } else {
453
+ this.core.setActive(false)
456
454
  }
457
455
 
458
456
  if (!info) {
@@ -468,7 +466,7 @@ class Writer {
468
466
  this.writers._updateLocalState()
469
467
  }
470
468
 
471
- return info ? info.length : 0
469
+ return length
472
470
  }
473
471
 
474
472
  next() {
@@ -693,6 +691,7 @@ class Writer {
693
691
  head.views = views
694
692
 
695
693
  head.trusted = trusted ? [{ key: trusted.key, length: trusted.length }] : null
694
+ this.writers.auto.emit('appending', this.pending)
696
695
 
697
696
  const buffers = []
698
697
  for (let i = 0; i < this.processed; i++) {
@@ -762,7 +761,3 @@ function finalizeNodeBatch(batch) {
762
761
  }
763
762
 
764
763
  function noop() {}
765
-
766
- function isAnchor(core) {
767
- return !!(core.manifest && core.manifest.signers.length === 0 && core.length === 1)
768
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.0.0-rc.31",
3
+ "version": "2.0.0-rc.34",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",