autobee 2.0.0-rc.0 → 2.0.0-rc.10
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/encoding/compat.js +1 -1
- package/encoding/legacy.js +3 -3
- package/encoding/spec/autobee/index.js +27 -19
- package/encoding/spec/autobee/schema.json +11 -1
- package/index.js +133 -40
- package/lib/apply-calls.js +5 -0
- package/lib/reboot.js +14 -1
- package/lib/system.js +107 -28
- package/lib/topo.js +80 -12
- package/lib/writers.js +4 -1
- package/package.json +2 -2
package/encoding/compat.js
CHANGED
package/encoding/legacy.js
CHANGED
|
@@ -153,18 +153,18 @@ const Node = {
|
|
|
153
153
|
preencode(state, m) {
|
|
154
154
|
Clock.preencode(state, m.heads)
|
|
155
155
|
c.uint.preencode(state, m.batch)
|
|
156
|
-
c.
|
|
156
|
+
c.optionalBuffer.preencode(state, m.value)
|
|
157
157
|
},
|
|
158
158
|
encode(state, m) {
|
|
159
159
|
Clock.encode(state, m.heads)
|
|
160
160
|
c.uint.encode(state, m.batch)
|
|
161
|
-
c.
|
|
161
|
+
c.optionalBuffer.encode(state, m.value)
|
|
162
162
|
},
|
|
163
163
|
decode(state, m) {
|
|
164
164
|
return {
|
|
165
165
|
heads: Clock.decode(state),
|
|
166
166
|
batch: c.uint.decode(state),
|
|
167
|
-
value: c.
|
|
167
|
+
value: c.optionalBuffer.decode(state)
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
}
|
|
@@ -97,8 +97,9 @@ const encoding4 = {
|
|
|
97
97
|
// @autobase-compat/boot-record
|
|
98
98
|
const encoding5 = {
|
|
99
99
|
preencode(state, m) {
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
const v = m.version ?? 3
|
|
101
|
+
c.uint.preencode(state, v)
|
|
102
|
+
switch (v) {
|
|
102
103
|
case 0:
|
|
103
104
|
encoding3.preencode(state, m)
|
|
104
105
|
break
|
|
@@ -112,8 +113,9 @@ const encoding5 = {
|
|
|
112
113
|
}
|
|
113
114
|
},
|
|
114
115
|
encode(state, m) {
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const v = m.version ?? 3
|
|
117
|
+
c.uint.encode(state, v)
|
|
118
|
+
switch (v) {
|
|
117
119
|
case 0:
|
|
118
120
|
encoding3.encode(state, m)
|
|
119
121
|
break
|
|
@@ -236,17 +238,17 @@ const encoding9 = {
|
|
|
236
238
|
preencode(state, m) {
|
|
237
239
|
encoding1.preencode(state, m.heads)
|
|
238
240
|
c.uint.preencode(state, m.batch)
|
|
239
|
-
c.
|
|
241
|
+
c.optionalBuffer.preencode(state, m.value)
|
|
240
242
|
},
|
|
241
243
|
encode(state, m) {
|
|
242
244
|
encoding1.encode(state, m.heads)
|
|
243
245
|
c.uint.encode(state, m.batch)
|
|
244
|
-
c.
|
|
246
|
+
c.optionalBuffer.encode(state, m.value)
|
|
245
247
|
},
|
|
246
248
|
decode(state) {
|
|
247
249
|
const r0 = encoding1.decode(state)
|
|
248
250
|
const r1 = c.uint.decode(state)
|
|
249
|
-
const r2 = c.
|
|
251
|
+
const r2 = c.optionalBuffer.decode(state)
|
|
250
252
|
|
|
251
253
|
return {
|
|
252
254
|
heads: r0,
|
|
@@ -496,8 +498,9 @@ const encoding18 = {
|
|
|
496
498
|
// @autobee/system-info
|
|
497
499
|
const encoding19 = {
|
|
498
500
|
preencode(state, m) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
+
const v = m.version ?? 3
|
|
502
|
+
c.uint.preencode(state, v)
|
|
503
|
+
switch (v) {
|
|
501
504
|
case 0:
|
|
502
505
|
case 1:
|
|
503
506
|
encoding15.preencode(state, m)
|
|
@@ -513,8 +516,9 @@ const encoding19 = {
|
|
|
513
516
|
}
|
|
514
517
|
},
|
|
515
518
|
encode(state, m) {
|
|
516
|
-
|
|
517
|
-
|
|
519
|
+
const v = m.version ?? 3
|
|
520
|
+
c.uint.encode(state, v)
|
|
521
|
+
switch (v) {
|
|
518
522
|
case 0:
|
|
519
523
|
case 1:
|
|
520
524
|
encoding15.encode(state, m)
|
|
@@ -603,8 +607,9 @@ const encoding20 = {
|
|
|
603
607
|
// @autobee/system-writer
|
|
604
608
|
const encoding21 = {
|
|
605
609
|
preencode(state, m) {
|
|
606
|
-
|
|
607
|
-
|
|
610
|
+
const v = m.version ?? 4
|
|
611
|
+
c.uint.preencode(state, v)
|
|
612
|
+
switch (v) {
|
|
608
613
|
case 0:
|
|
609
614
|
case 1:
|
|
610
615
|
case 2:
|
|
@@ -619,8 +624,9 @@ const encoding21 = {
|
|
|
619
624
|
}
|
|
620
625
|
},
|
|
621
626
|
encode(state, m) {
|
|
622
|
-
|
|
623
|
-
|
|
627
|
+
const v = m.version ?? 4
|
|
628
|
+
c.uint.encode(state, v)
|
|
629
|
+
switch (v) {
|
|
624
630
|
case 0:
|
|
625
631
|
case 1:
|
|
626
632
|
case 2:
|
|
@@ -905,8 +911,9 @@ const encoding29 = {
|
|
|
905
911
|
// @autobee/oplog
|
|
906
912
|
const encoding30 = {
|
|
907
913
|
preencode(state, m) {
|
|
908
|
-
|
|
909
|
-
|
|
914
|
+
const v = m.version ?? 3
|
|
915
|
+
c.uint.preencode(state, v)
|
|
916
|
+
switch (v) {
|
|
910
917
|
case 0:
|
|
911
918
|
encoding12.preencode(state, m)
|
|
912
919
|
break
|
|
@@ -924,8 +931,9 @@ const encoding30 = {
|
|
|
924
931
|
}
|
|
925
932
|
},
|
|
926
933
|
encode(state, m) {
|
|
927
|
-
|
|
928
|
-
|
|
934
|
+
const v = m.version ?? 3
|
|
935
|
+
c.uint.encode(state, v)
|
|
936
|
+
switch (v) {
|
|
929
937
|
case 0:
|
|
930
938
|
encoding12.encode(state, m)
|
|
931
939
|
break
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
{
|
|
195
195
|
"name": "value",
|
|
196
196
|
"required": true,
|
|
197
|
-
"type": "
|
|
197
|
+
"type": "optionalBuffer",
|
|
198
198
|
"version": 1
|
|
199
199
|
}
|
|
200
200
|
]
|
|
@@ -805,5 +805,15 @@
|
|
|
805
805
|
}
|
|
806
806
|
]
|
|
807
807
|
}
|
|
808
|
+
],
|
|
809
|
+
"namespaces": [
|
|
810
|
+
{
|
|
811
|
+
"name": "autobee",
|
|
812
|
+
"external": "../../legacy.js"
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
"name": "autobase-compat",
|
|
816
|
+
"external": "../../legacy.js"
|
|
817
|
+
}
|
|
808
818
|
]
|
|
809
819
|
}
|
package/index.js
CHANGED
|
@@ -105,6 +105,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
105
105
|
this._hasApply = !!handlers.apply
|
|
106
106
|
this._hasUpdate = !!handlers.update
|
|
107
107
|
this._needsUpdate = false
|
|
108
|
+
this._ackRequired = false
|
|
109
|
+
this._ackedHeads = new Map()
|
|
108
110
|
this._updateLocalCore = null
|
|
109
111
|
this._host = new ApplyCalls(this)
|
|
110
112
|
this._notifyHandler = null
|
|
@@ -227,6 +229,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
227
229
|
await this.local.close()
|
|
228
230
|
await this.system.close()
|
|
229
231
|
await this._wakeup.close()
|
|
232
|
+
if (this.bootstrap && !this.bootstrap.closed) await this.bootstrap.close()
|
|
230
233
|
await this._workingBee.close()
|
|
231
234
|
await this.bee.close()
|
|
232
235
|
await this.store.close()
|
|
@@ -250,7 +253,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
250
253
|
view: info ? info.view : EMPTY_HEAD
|
|
251
254
|
}
|
|
252
255
|
|
|
253
|
-
|
|
256
|
+
// no common ancestor (null) -> flushes -1 never matches, forcing a full diff
|
|
257
|
+
const shared = (await this.system.commonAncestor(system || EMPTY_HEAD)) || {
|
|
258
|
+
flushes: -1,
|
|
259
|
+
system: EMPTY_HEAD,
|
|
260
|
+
view: EMPTY_HEAD
|
|
261
|
+
}
|
|
254
262
|
|
|
255
263
|
return UpdateChanges.from(shared, current)
|
|
256
264
|
}
|
|
@@ -374,13 +382,18 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
374
382
|
|
|
375
383
|
// Use the view position from the system info (authoritative, post-processing)
|
|
376
384
|
// rather than from the oplog (stale, captured at append time before _bump)
|
|
377
|
-
let view = this.system.view
|
|
385
|
+
let view = this.system.view
|
|
386
|
+
if (!view) {
|
|
387
|
+
const migrated = await this.local.getUserData('autobee/migrated-view')
|
|
388
|
+
view = migrated ? encoding.decodeBootRecord(migrated) : EMPTY_HEAD
|
|
389
|
+
}
|
|
378
390
|
|
|
379
391
|
// @todo migration
|
|
380
392
|
if (result.migration) {
|
|
381
393
|
if (this.handlers.migrate) {
|
|
382
394
|
view = (await this.handlers.migrate(result.migration.views)) || EMPTY_HEAD
|
|
383
395
|
this._catchupMigratedNodes = result.migration.catchup
|
|
396
|
+
await this._storeMigratedView(view)
|
|
384
397
|
}
|
|
385
398
|
|
|
386
399
|
// ff boot invalidated by migration
|
|
@@ -476,6 +489,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
476
489
|
}
|
|
477
490
|
|
|
478
491
|
async _drain() {
|
|
492
|
+
if (this._updating) await this._updating
|
|
493
|
+
|
|
479
494
|
if (this.bootFrom) {
|
|
480
495
|
await this._initFromHead(this.bootFrom)
|
|
481
496
|
this.bootFrom = null
|
|
@@ -484,6 +499,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
484
499
|
// tracked across drain iteration
|
|
485
500
|
this.system.shared = null
|
|
486
501
|
|
|
502
|
+
this._ackRequired = false
|
|
503
|
+
|
|
487
504
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
488
505
|
if (changes) changes.track()
|
|
489
506
|
|
|
@@ -503,7 +520,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
503
520
|
break // revaluate conditions...
|
|
504
521
|
}
|
|
505
522
|
|
|
506
|
-
if (
|
|
523
|
+
if (await this._bumpPendingWriters()) {
|
|
524
|
+
this._needsUpdate = true
|
|
525
|
+
continue
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (!this._appendAck()) break
|
|
507
529
|
this._needsUpdate = true
|
|
508
530
|
}
|
|
509
531
|
|
|
@@ -518,7 +540,15 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
518
540
|
if (this._interrupting) return
|
|
519
541
|
|
|
520
542
|
if (this._needsUpdate) {
|
|
521
|
-
|
|
543
|
+
const updating = rrp()
|
|
544
|
+
this._updating = updating.promise
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
await this._update(changes)
|
|
548
|
+
} finally {
|
|
549
|
+
this._updating = null
|
|
550
|
+
updating.resolve()
|
|
551
|
+
}
|
|
522
552
|
}
|
|
523
553
|
}
|
|
524
554
|
|
|
@@ -564,58 +594,74 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
564
594
|
}
|
|
565
595
|
}
|
|
566
596
|
|
|
567
|
-
async _getOplog(key, length) {
|
|
597
|
+
async _getOplog(key, length, opts = null) {
|
|
568
598
|
const core = this.openCore(key)
|
|
569
|
-
await core.ready()
|
|
570
599
|
|
|
571
|
-
|
|
572
|
-
|
|
600
|
+
try {
|
|
601
|
+
await core.ready()
|
|
573
602
|
|
|
574
|
-
|
|
575
|
-
|
|
603
|
+
const target = length >= 0 ? length : core.length
|
|
604
|
+
if (target === 0) return null
|
|
576
605
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
606
|
+
const buf = await core.get(target - 1, opts)
|
|
607
|
+
if (buf === null) return null
|
|
580
608
|
|
|
581
|
-
|
|
609
|
+
let op = encoding.decodeOplog(buf)
|
|
582
610
|
|
|
583
|
-
|
|
611
|
+
// legacy nodes always inflate, but only indexers carry views - callers
|
|
612
|
+
// that need system info must check op.views
|
|
613
|
+
if (op.version < 3) {
|
|
614
|
+
op = await this._inflateLegacyOplog(buf, core, target - 1, opts)
|
|
615
|
+
}
|
|
584
616
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
617
|
+
return {
|
|
618
|
+
key,
|
|
619
|
+
length: target,
|
|
620
|
+
op
|
|
621
|
+
}
|
|
622
|
+
} finally {
|
|
623
|
+
await core.close()
|
|
589
624
|
}
|
|
590
625
|
}
|
|
591
626
|
|
|
592
|
-
async _inflateLegacyOplog(buf, core, seq) {
|
|
627
|
+
async _inflateLegacyOplog(buf, core, seq, opts = null) {
|
|
593
628
|
const m = encoding.decodeRawOplog(buf)
|
|
629
|
+
|
|
630
|
+
const op = {
|
|
631
|
+
version: m.version,
|
|
632
|
+
timestamp: 0,
|
|
633
|
+
links: m.node.heads,
|
|
634
|
+
batch: { start: 0, end: m.node.batch - 1 },
|
|
635
|
+
views: null,
|
|
636
|
+
optimistic: !!m.optimistic,
|
|
637
|
+
value: m.node.value
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (m.digest === null || m.checkpoint === null) return op
|
|
641
|
+
|
|
594
642
|
const fetches = []
|
|
595
643
|
|
|
596
|
-
fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer) : buf)
|
|
597
|
-
fetches.push(
|
|
644
|
+
fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, opts) : buf)
|
|
645
|
+
fetches.push(
|
|
646
|
+
m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer, opts) : buf
|
|
647
|
+
)
|
|
598
648
|
|
|
599
649
|
const [digestNode, checkpointNode] = await Promise.all(fetches)
|
|
650
|
+
// a wait: false get we could not satisfy locally
|
|
651
|
+
if (digestNode === null || checkpointNode === null) return op
|
|
600
652
|
|
|
601
653
|
const { digest } = encoding.decodeRawOplog(digestNode)
|
|
602
654
|
const { checkpoint } = encoding.decodeRawOplog(checkpointNode)
|
|
603
655
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
batch: { start: 0, end: m.node.batch - 1 },
|
|
609
|
-
views: {
|
|
610
|
-
system: {
|
|
611
|
-
key: digest.key,
|
|
612
|
-
length: checkpoint.system.checkpoint.length
|
|
613
|
-
},
|
|
614
|
-
flushes: seq
|
|
656
|
+
op.views = {
|
|
657
|
+
system: {
|
|
658
|
+
key: digest.key,
|
|
659
|
+
length: checkpoint.system.checkpoint.length
|
|
615
660
|
},
|
|
616
|
-
|
|
617
|
-
value: m.node.value
|
|
661
|
+
flushes: seq
|
|
618
662
|
}
|
|
663
|
+
|
|
664
|
+
return op
|
|
619
665
|
}
|
|
620
666
|
|
|
621
667
|
async _update(changes) {
|
|
@@ -744,6 +790,39 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
744
790
|
}
|
|
745
791
|
}
|
|
746
792
|
|
|
793
|
+
// append a null value node to ack writer
|
|
794
|
+
_appendAck() {
|
|
795
|
+
if (!this._ackRequired) return false
|
|
796
|
+
if (!this.writers.writable) return false
|
|
797
|
+
|
|
798
|
+
if (this.writers.localWriter.pending !== null) return false
|
|
799
|
+
|
|
800
|
+
const links = this.system.getLinks(this.local.key)
|
|
801
|
+
const t = Math.max(this._now(), this.system.timestamp)
|
|
802
|
+
|
|
803
|
+
let unlinked = false
|
|
804
|
+
for (const { key, length } of links) {
|
|
805
|
+
const hex = b4a.toString(key, 'hex')
|
|
806
|
+
const acked = this._ackedHeads.get(hex) || 0
|
|
807
|
+
if (length > acked) {
|
|
808
|
+
unlinked = true
|
|
809
|
+
break
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (!unlinked) {
|
|
813
|
+
this._ackRequired = false
|
|
814
|
+
return false
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
for (const { key, length } of links) {
|
|
818
|
+
this._ackedHeads.set(b4a.toString(key, 'hex'), length)
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
this.writers.appendLocal(null, t, { start: 0, end: 0 }, links, false, null)
|
|
822
|
+
this._ackRequired = false
|
|
823
|
+
return true
|
|
824
|
+
}
|
|
825
|
+
|
|
747
826
|
async _bumpPendingWriters() {
|
|
748
827
|
if (this._catchupMigratedNodes !== null) {
|
|
749
828
|
await this._bumpMigratedWriters()
|
|
@@ -917,11 +996,17 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
917
996
|
const boot = this.system.bootRecord()
|
|
918
997
|
if (boot) {
|
|
919
998
|
proms.push(this.local.setUserData('autobee/head', encoding.encodeBootRecord(boot)))
|
|
999
|
+
if (this.system.view) proms.push(this._storeMigratedView(EMPTY_HEAD))
|
|
920
1000
|
}
|
|
921
1001
|
|
|
922
1002
|
return Promise.all(proms)
|
|
923
1003
|
}
|
|
924
1004
|
|
|
1005
|
+
_storeMigratedView(view) {
|
|
1006
|
+
const value = view.key ? encoding.encodeBootRecord(view) : null
|
|
1007
|
+
return this.local.setUserData('autobee/migrated-view', value)
|
|
1008
|
+
}
|
|
1009
|
+
|
|
925
1010
|
static decodeValue(buf, opts) {
|
|
926
1011
|
return encoding.decodeValue(buf, opts)
|
|
927
1012
|
}
|
|
@@ -1071,14 +1156,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1071
1156
|
|
|
1072
1157
|
async _rebootFromHead(head, trusted, { force = false, wait = true } = {}) {
|
|
1073
1158
|
const oplog = await this._getOplog(head.key, head.length)
|
|
1074
|
-
if (!oplog) return
|
|
1159
|
+
if (!oplog) return null
|
|
1075
1160
|
|
|
1076
1161
|
const verified = trusted ? await this._getOplog(trusted.key, trusted.length) : oplog
|
|
1162
|
+
if (!verified) return null
|
|
1077
1163
|
|
|
1078
|
-
|
|
1164
|
+
// legacy nodes from non-indexers have no system info to reboot from
|
|
1165
|
+
if (!oplog.op.views || !verified.op.views) return null
|
|
1079
1166
|
|
|
1080
1167
|
if (!force && verified.op.views.flushes - this.system.flushes < MIN_FF_GAP) {
|
|
1081
|
-
return
|
|
1168
|
+
return null
|
|
1082
1169
|
}
|
|
1083
1170
|
|
|
1084
1171
|
const moved = await this._moveTo(batchToHead(verified.op.views.system), {
|
|
@@ -1145,12 +1232,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1145
1232
|
this.system.bee.move(head)
|
|
1146
1233
|
await this.system.reset()
|
|
1147
1234
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1235
|
+
this.system.shared = this.rebootTo.shared || {
|
|
1236
|
+
flushes: -1,
|
|
1237
|
+
view: EMPTY_HEAD,
|
|
1238
|
+
system: EMPTY_HEAD
|
|
1239
|
+
}
|
|
1150
1240
|
|
|
1151
1241
|
// migrate is set when fast-forwarding from a legacy head
|
|
1152
1242
|
if (migrate) {
|
|
1153
1243
|
const view = (await this.handlers.migrate(migrate)) || EMPTY_HEAD
|
|
1244
|
+
await this._storeMigratedView(view)
|
|
1154
1245
|
this.bee.move(view)
|
|
1155
1246
|
this._workingBee.move(view)
|
|
1156
1247
|
} else {
|
|
@@ -1161,6 +1252,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1161
1252
|
this.rebootTo = null
|
|
1162
1253
|
await this.writers.refresh()
|
|
1163
1254
|
|
|
1255
|
+
await this._storeBoot()
|
|
1256
|
+
|
|
1164
1257
|
await this._update(changes)
|
|
1165
1258
|
|
|
1166
1259
|
this.emit('move-to', to, from)
|
package/lib/apply-calls.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const ID = require('hypercore-id-encoding')
|
|
2
|
+
const b4a = require('b4a')
|
|
2
3
|
|
|
3
4
|
class ApplyCalls {
|
|
4
5
|
constructor(auto) {
|
|
@@ -39,6 +40,10 @@ class ApplyCalls {
|
|
|
39
40
|
|
|
40
41
|
ackWriter(key) {
|
|
41
42
|
if (typeof key === 'string') key = ID.decode(key)
|
|
43
|
+
// acking an optimistic node means we owe it a linking node
|
|
44
|
+
if (this.applying && this.applying[0].optimistic && !b4a.equals(key, this.auto.local.key)) {
|
|
45
|
+
this.auto._ackRequired = true
|
|
46
|
+
}
|
|
42
47
|
return this.auto.system.ackWriter(key)
|
|
43
48
|
}
|
|
44
49
|
|
package/lib/reboot.js
CHANGED
|
@@ -86,10 +86,23 @@ module.exports = class Reboot {
|
|
|
86
86
|
await Promise.all(promises)
|
|
87
87
|
if (this.destroyed) return null
|
|
88
88
|
|
|
89
|
+
const shared = await this._commonAncestor()
|
|
90
|
+
if (this.destroyed) return null
|
|
91
|
+
|
|
89
92
|
return {
|
|
90
93
|
head: this.head,
|
|
91
94
|
tip: this.tip,
|
|
92
|
-
migrate: null
|
|
95
|
+
migrate: null,
|
|
96
|
+
shared
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async _commonAncestor() {
|
|
101
|
+
try {
|
|
102
|
+
return await this.auto.system.commonAncestor(this.head)
|
|
103
|
+
} catch (err) {
|
|
104
|
+
safetyCatch(err)
|
|
105
|
+
return null
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
|
package/lib/system.js
CHANGED
|
@@ -2,7 +2,6 @@ const Hyperbee = require('hyperbee2')
|
|
|
2
2
|
const b4a = require('b4a')
|
|
3
3
|
|
|
4
4
|
const { AUTOBEE_VERSION } = require('./constants')
|
|
5
|
-
const asserts = require('./asserts.js')
|
|
6
5
|
const encoding = require('./encoding.js')
|
|
7
6
|
const topo = require('./topo.js')
|
|
8
7
|
|
|
@@ -13,6 +12,27 @@ const INDEXER_LT = b4a.from([3])
|
|
|
13
12
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
14
13
|
const EMPTY = b4a.from([])
|
|
15
14
|
|
|
15
|
+
class SystemSnapshot {
|
|
16
|
+
constructor(bee) {
|
|
17
|
+
this.bee = bee
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async get(key, { timeout } = {}) {
|
|
21
|
+
const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
|
|
22
|
+
return node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async has(link) {
|
|
26
|
+
const node = await this.get(link.key)
|
|
27
|
+
if (!node || node.length < link.length) return false
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close() {
|
|
32
|
+
return this.bee.close()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
module.exports = class Systembee {
|
|
17
37
|
constructor(store, name, opts = {}) {
|
|
18
38
|
this.name = name // for debuggin
|
|
@@ -96,44 +116,57 @@ module.exports = class Systembee {
|
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
async commonAncestor(head) {
|
|
99
|
-
const info = await this.getInfo()
|
|
100
|
-
|
|
101
119
|
const snap = this.bee.checkout(head)
|
|
102
120
|
|
|
103
|
-
const
|
|
104
|
-
const
|
|
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
|
+
}
|
|
105
137
|
|
|
106
|
-
|
|
107
|
-
|
|
138
|
+
// different legacy cores never converge - genuinely unrelated history
|
|
139
|
+
if (!b4a.equals(l.node.head.key, r.node.head.key)) return null
|
|
108
140
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
141
|
+
if (l.node.head.length >= r.node.head.length) l = await next(ca)
|
|
142
|
+
else r = await next(cb)
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
112
145
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
}
|
|
115
152
|
|
|
116
|
-
|
|
117
|
-
if (l.flushes === r.flushes && sameHead(l.view, r.view)) {
|
|
153
|
+
if (l.info.flushes === r.info.flushes && sameHead(l.info.view, r.info.view)) {
|
|
118
154
|
return {
|
|
119
|
-
flushes: l.flushes,
|
|
120
|
-
system:
|
|
121
|
-
view: l.view || EMPTY_HEAD
|
|
155
|
+
flushes: l.info.flushes,
|
|
156
|
+
system: l.node.head,
|
|
157
|
+
view: l.info.view || EMPTY_HEAD
|
|
122
158
|
}
|
|
123
159
|
}
|
|
124
160
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (a.value) l = getInfo(a.value)
|
|
128
|
-
}
|
|
161
|
+
const lf = l.info.flushes
|
|
162
|
+
const rf = r.info.flushes
|
|
129
163
|
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
if (b.value) r = getInfo(b.value)
|
|
133
|
-
}
|
|
164
|
+
if (lf >= rf) l = await next(ca)
|
|
165
|
+
if (rf >= lf) r = await next(cb)
|
|
134
166
|
}
|
|
135
167
|
|
|
136
|
-
|
|
168
|
+
// no common ancestor
|
|
169
|
+
return null
|
|
137
170
|
}
|
|
138
171
|
|
|
139
172
|
async reset({ timeout } = {}) {
|
|
@@ -228,6 +261,14 @@ module.exports = class Systembee {
|
|
|
228
261
|
return info ? !info.isRemoved : !!optimistic
|
|
229
262
|
}
|
|
230
263
|
|
|
264
|
+
snapshot(head = null) {
|
|
265
|
+
const bee =
|
|
266
|
+
head === null
|
|
267
|
+
? this.bee.snapshot()
|
|
268
|
+
: this.bee.checkout({ length: head.length, key: head.key })
|
|
269
|
+
return new SystemSnapshot(bee)
|
|
270
|
+
}
|
|
271
|
+
|
|
231
272
|
async get(key, { unflushed = false, timeout } = {}) {
|
|
232
273
|
const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
|
|
233
274
|
const info = node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
|
|
@@ -434,6 +475,8 @@ module.exports = class Systembee {
|
|
|
434
475
|
|
|
435
476
|
w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))
|
|
436
477
|
|
|
478
|
+
this.view = info.view
|
|
479
|
+
|
|
437
480
|
await w.flush()
|
|
438
481
|
|
|
439
482
|
// Can enable if needed during debuggin
|
|
@@ -460,12 +503,16 @@ function getInfo(data) {
|
|
|
460
503
|
const prefix = k.key[0]
|
|
461
504
|
|
|
462
505
|
if (prefix === 0) {
|
|
463
|
-
|
|
506
|
+
try {
|
|
507
|
+
return encoding.decodeSystemInfo(k.value)
|
|
508
|
+
} catch {
|
|
509
|
+
return null
|
|
510
|
+
}
|
|
464
511
|
}
|
|
465
512
|
}
|
|
466
513
|
}
|
|
467
514
|
|
|
468
|
-
|
|
515
|
+
return null
|
|
469
516
|
}
|
|
470
517
|
|
|
471
518
|
function sameHead(a, b) {
|
|
@@ -473,3 +520,35 @@ function sameHead(a, b) {
|
|
|
473
520
|
if (!a.key || !b.key) return a.key === b.key
|
|
474
521
|
return b4a.equals(a.key, b.key)
|
|
475
522
|
}
|
|
523
|
+
|
|
524
|
+
async function* infoIterator(bee) {
|
|
525
|
+
try {
|
|
526
|
+
for await (const node of bee.createChangesStream()) {
|
|
527
|
+
const legacy = topo.isLegacyNode(node)
|
|
528
|
+
const info = legacy ? getLegacyInfo(node) : getInfo(node)
|
|
529
|
+
if (info) yield { node, info, legacy }
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
// fall through
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function next(it) {
|
|
537
|
+
const res = await it.next()
|
|
538
|
+
return res.done ? null : res.value
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function getLegacyInfo(data) {
|
|
542
|
+
for (const { keys } of data.batch) {
|
|
543
|
+
for (const k of keys) {
|
|
544
|
+
if (b4a.equals(k.key, INFO_LEGACY_KEY)) {
|
|
545
|
+
try {
|
|
546
|
+
return encoding.decodeSystemInfo(k.value)
|
|
547
|
+
} catch {
|
|
548
|
+
return null
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return null
|
|
554
|
+
}
|
package/lib/topo.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.getMostRecentHead = getMostRecentHead
|
|
|
13
13
|
|
|
14
14
|
exports.isLinking = isLinking
|
|
15
15
|
exports.isLinkingAll = isLinkingAll
|
|
16
|
+
exports.isLegacyNode = isLegacyNode
|
|
16
17
|
|
|
17
18
|
class Clock {
|
|
18
19
|
constructor() {
|
|
@@ -65,17 +66,66 @@ async function rollback(auto, system, verified) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// nodes we cannot resolve are returned as null rather than dropped, so callers
|
|
70
|
+
// can tell an incomplete replay from a short one
|
|
68
71
|
async function replay(auto) {
|
|
69
|
-
const ch = auto.system.bee.createChangesStream()
|
|
70
72
|
const replay = []
|
|
71
73
|
|
|
72
|
-
for await (const
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
for await (const entry of oplogIterator(auto.system.bee)) {
|
|
75
|
+
// the walk was cut short, nothing below this point is reachable
|
|
76
|
+
if (entry === null) {
|
|
77
|
+
replay.push(null)
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const ref of entry.refs) replay.push(ref)
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
// same reasoning as the iterator - never block on what we do not have
|
|
85
|
+
const inflated = await inflate(auto, replay.reverse(), { wait: false })
|
|
86
|
+
return inflated.flatMap((b) => (b === null ? [null] : b.batch))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// tolerates legacy nodes instead of asserting
|
|
90
|
+
async function* oplogIterator(bee) {
|
|
91
|
+
try {
|
|
92
|
+
for await (const node of bee.createChangesStream({ wait: false })) {
|
|
93
|
+
const legacy = isLegacyNode(node)
|
|
94
|
+
const refs = getOplogs(node)
|
|
95
|
+
if (refs.length) yield { node, refs, legacy }
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
yield null // signal data not available
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// legacy nodes carry no isOplog flag so we cannot tell which writer update is
|
|
103
|
+
// the oplog to fetch - yield every candidate until we have a better signal
|
|
104
|
+
function getOplogs(data) {
|
|
105
|
+
const undo = data.tail || EMPTY_HEAD
|
|
106
|
+
const oplogs = []
|
|
107
|
+
const candidates = []
|
|
108
|
+
|
|
109
|
+
for (const { keys } of data.batch) {
|
|
110
|
+
for (const k of keys) {
|
|
111
|
+
if (k.key[0] !== 1) continue
|
|
112
|
+
|
|
113
|
+
let value = null
|
|
114
|
+
try {
|
|
115
|
+
value = encoding.decodeSystemWriter(k.key, k.value)
|
|
116
|
+
} catch {
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// writer was just added, nothing to fetch
|
|
121
|
+
if (!value.length) continue
|
|
122
|
+
|
|
123
|
+
if (value.isOplog) oplogs.push({ undo, oplog: value })
|
|
124
|
+
else candidates.push({ undo, oplog: value })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return oplogs.length ? oplogs : candidates
|
|
79
129
|
}
|
|
80
130
|
|
|
81
131
|
async function sort(auto, batch) {
|
|
@@ -192,12 +242,19 @@ function cmp(a, b) {
|
|
|
192
242
|
return a.length < b.length ? -1 : 1
|
|
193
243
|
}
|
|
194
244
|
|
|
195
|
-
async function inflate(auto, reorder) {
|
|
245
|
+
async function inflate(auto, reorder, opts = null) {
|
|
196
246
|
const promises = []
|
|
197
247
|
const sessions = new Map()
|
|
198
248
|
|
|
199
249
|
for (let i = 0; i < reorder.length; i++) {
|
|
200
250
|
const ref = reorder[i]
|
|
251
|
+
|
|
252
|
+
// marker for a node we could not resolve, pass it through
|
|
253
|
+
if (ref === null) {
|
|
254
|
+
promises.push(null)
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
|
|
201
258
|
const id = b4a.toString(ref.oplog.key, 'hex')
|
|
202
259
|
// only seeds the node - reapplication re-resolves from the witness
|
|
203
260
|
const weight = ref.oplog.weight
|
|
@@ -214,7 +271,7 @@ async function inflate(auto, reorder) {
|
|
|
214
271
|
sessions.set(id, core)
|
|
215
272
|
}
|
|
216
273
|
|
|
217
|
-
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i))
|
|
274
|
+
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, opts))
|
|
218
275
|
}
|
|
219
276
|
|
|
220
277
|
try {
|
|
@@ -224,9 +281,10 @@ async function inflate(auto, reorder) {
|
|
|
224
281
|
}
|
|
225
282
|
}
|
|
226
283
|
|
|
227
|
-
async function getOplogBatch(undo, core, length, weight, index) {
|
|
284
|
+
async function getOplogBatch(undo, core, length, weight, index, opts = null) {
|
|
228
285
|
const seq = length - 1
|
|
229
|
-
const block = await core.get(seq)
|
|
286
|
+
const block = await core.get(seq, opts)
|
|
287
|
+
if (block === null) return null
|
|
230
288
|
const oplog = encoding.decodeOplog(block)
|
|
231
289
|
const head = createNode(core, length, weight, oplog)
|
|
232
290
|
|
|
@@ -238,12 +296,18 @@ async function getOplogBatch(undo, core, length, weight, index) {
|
|
|
238
296
|
const end = seq + head.batch.end // skip head, we have it
|
|
239
297
|
|
|
240
298
|
for (let i = start; i < end; i++) {
|
|
241
|
-
remaining.push(core.get(i))
|
|
299
|
+
remaining.push(core.get(i, opts))
|
|
242
300
|
}
|
|
243
301
|
|
|
244
302
|
if (remaining.length) {
|
|
245
303
|
const blocks = await Promise.all(remaining)
|
|
246
304
|
for (let i = 0; i < blocks.length; i++) {
|
|
305
|
+
// keep the hole so the gap in the batch stays visible
|
|
306
|
+
if (blocks[i] === null) {
|
|
307
|
+
batch.push(null)
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
|
|
247
311
|
const oplog = encoding.decodeOplog(blocks[i])
|
|
248
312
|
batch.push(createNode(core, i + start + 1, weight, oplog))
|
|
249
313
|
}
|
|
@@ -254,8 +318,12 @@ async function getOplogBatch(undo, core, length, weight, index) {
|
|
|
254
318
|
}
|
|
255
319
|
|
|
256
320
|
function getLocalBatch(undo, writer, length, weight, index) {
|
|
321
|
+
if (writer.core.closed) throw new Error('Writer core is closed')
|
|
322
|
+
|
|
257
323
|
if (length > writer.core.length) {
|
|
258
|
-
if (!writer.pending)
|
|
324
|
+
if (!writer.pending) {
|
|
325
|
+
throw new Error(`No local nodes: looking for ${length}, found ${writer.core.length}`)
|
|
326
|
+
}
|
|
259
327
|
|
|
260
328
|
let i = 0
|
|
261
329
|
while (i < writer.pending.length) {
|
package/lib/writers.js
CHANGED
|
@@ -214,7 +214,10 @@ class ActiveWriters {
|
|
|
214
214
|
return
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
const session = bootstrap.session()
|
|
218
|
+
session.setEncryption(this.auto._getEncryptionProvider())
|
|
219
|
+
|
|
220
|
+
w = new Writer(this, this.auto.system, false, session, id)
|
|
218
221
|
w.attach()
|
|
219
222
|
w.couple()
|
|
220
223
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.10",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"autobee-encryption": "^0.1.2",
|
|
27
27
|
"autobee-wakeup": "^1.1.3",
|
|
28
28
|
"b4a": "^1.8.0",
|
|
29
|
-
"compact-encoding": "^
|
|
29
|
+
"compact-encoding": "^3.3.0",
|
|
30
30
|
"hyperbee2": "^2.10.3",
|
|
31
31
|
"hypercore": "^11.33.2",
|
|
32
32
|
"hypercore-id-encoding": "^1.3.0",
|