autobee 2.0.0-rc.2 → 2.0.0-rc.21
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 +48 -1
- package/encoding/compat.js +1 -1
- package/encoding/legacy.js +3 -3
- package/encoding/spec/autobee/index.js +104 -22
- package/encoding/spec/autobee/schema.json +63 -1
- package/index.js +392 -197
- package/lib/apply-calls.js +5 -0
- package/lib/apply-view.js +25 -0
- package/lib/boot.js +6 -1
- package/lib/encoding.js +12 -0
- package/lib/{reboot.js → fast-forward.js} +213 -19
- package/lib/system.js +38 -62
- package/lib/topo.js +121 -18
- package/lib/trusted.js +44 -0
- package/lib/updates.js +1 -38
- package/lib/writers.js +90 -15
- package/package.json +2 -2
package/lib/topo.js
CHANGED
|
@@ -6,6 +6,8 @@ const asserts = require('./asserts.js')
|
|
|
6
6
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
7
7
|
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
|
|
8
8
|
|
|
9
|
+
const DEFAULT_OP_TIMEOUT = 5000
|
|
10
|
+
|
|
9
11
|
exports.sort = sort
|
|
10
12
|
exports.rollback = rollback
|
|
11
13
|
exports.replay = replay
|
|
@@ -13,6 +15,7 @@ exports.getMostRecentHead = getMostRecentHead
|
|
|
13
15
|
|
|
14
16
|
exports.isLinking = isLinking
|
|
15
17
|
exports.isLinkingAll = isLinkingAll
|
|
18
|
+
exports.isLegacyNode = isLegacyNode
|
|
16
19
|
|
|
17
20
|
class Clock {
|
|
18
21
|
constructor() {
|
|
@@ -35,26 +38,46 @@ class Clock {
|
|
|
35
38
|
async function getMostRecentHead(auto, system) {
|
|
36
39
|
const ch = system.createChangesStream()
|
|
37
40
|
for await (const data of ch) {
|
|
38
|
-
const
|
|
39
|
-
|
|
41
|
+
const refs = getOplogCompat(data)
|
|
42
|
+
if (!refs.length) continue
|
|
43
|
+
|
|
44
|
+
const { oplog } = refs[0]
|
|
45
|
+
return { key: oplog.key, length: oplog.length }
|
|
40
46
|
}
|
|
47
|
+
|
|
48
|
+
return null
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
async function rollback(auto, system, verified) {
|
|
52
|
+
if (!(await isApplied(system, verified.op))) return null
|
|
53
|
+
|
|
44
54
|
const tip = []
|
|
45
55
|
const ch = system.createChangesStream()
|
|
46
56
|
|
|
57
|
+
let found = false
|
|
58
|
+
|
|
47
59
|
for await (const data of ch) {
|
|
48
60
|
const { ref, info } = getOplog(data)
|
|
61
|
+
|
|
49
62
|
if (sameNode(ref.oplog, verified.op)) {
|
|
50
|
-
|
|
63
|
+
// stale views.flushes - the verified head describes a prefix this system
|
|
64
|
+
// does not share, so nothing above it is ours to reapply
|
|
65
|
+
if (info.flushes === verified.flushes) found = true
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// walking back, a writer's lengths only decrease - once this writer is
|
|
70
|
+
// behind the verified head the marker cannot be further down
|
|
71
|
+
if (b4a.equals(ref.oplog.key, verified.op.key) && ref.oplog.length < verified.op.length) {
|
|
51
72
|
break
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
tip.push(ref)
|
|
55
76
|
}
|
|
56
77
|
|
|
57
|
-
|
|
78
|
+
if (!found) return null
|
|
79
|
+
|
|
80
|
+
const inflated = await inflate(auto, tip.reverse())
|
|
58
81
|
|
|
59
82
|
// @todo: verify sort
|
|
60
83
|
|
|
@@ -65,17 +88,32 @@ async function rollback(auto, system, verified) {
|
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
|
|
91
|
+
// nodes we cannot resolve are returned as null rather than dropped, so callers
|
|
92
|
+
// can tell an incomplete replay from a short one
|
|
68
93
|
async function replay(auto) {
|
|
69
|
-
const ch = auto.system.bee.createChangesStream()
|
|
70
94
|
const replay = []
|
|
71
95
|
|
|
72
|
-
for await (const
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
for await (const entry of oplogIterator(auto.system.bee)) {
|
|
97
|
+
// the walk was cut short, nothing below this point is reachable
|
|
98
|
+
if (entry === null) {
|
|
99
|
+
replay.push(null)
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const ref of entry.refs) replay.push(ref)
|
|
75
104
|
}
|
|
76
105
|
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
// same reasoning as the iterator - never block on what we do not have
|
|
107
|
+
const inflated = await inflate(auto, replay.reverse(), { wait: false })
|
|
108
|
+
return inflated.flatMap((b) => (b === null ? [null] : b.batch))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function isApplied(system, node) {
|
|
112
|
+
const entry = await system.get(encoding.encodeSystemWriterKey(node.key))
|
|
113
|
+
if (entry === null) return false
|
|
114
|
+
|
|
115
|
+
const info = encoding.decodeSystemWriter(entry.key, entry.value)
|
|
116
|
+
return info.length >= node.length
|
|
79
117
|
}
|
|
80
118
|
|
|
81
119
|
async function sort(auto, batch) {
|
|
@@ -192,12 +230,21 @@ function cmp(a, b) {
|
|
|
192
230
|
return a.length < b.length ? -1 : 1
|
|
193
231
|
}
|
|
194
232
|
|
|
195
|
-
async function inflate(auto, reorder) {
|
|
233
|
+
async function inflate(auto, reorder, opts = null) {
|
|
234
|
+
const readOpts = opts === null ? { timeout: DEFAULT_OP_TIMEOUT } : opts
|
|
235
|
+
|
|
196
236
|
const promises = []
|
|
197
237
|
const sessions = new Map()
|
|
198
238
|
|
|
199
239
|
for (let i = 0; i < reorder.length; i++) {
|
|
200
240
|
const ref = reorder[i]
|
|
241
|
+
|
|
242
|
+
// marker for a node we could not resolve, pass it through
|
|
243
|
+
if (ref === null) {
|
|
244
|
+
promises.push(null)
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
201
248
|
const id = b4a.toString(ref.oplog.key, 'hex')
|
|
202
249
|
// only seeds the node - reapplication re-resolves from the witness
|
|
203
250
|
const weight = ref.oplog.weight
|
|
@@ -214,7 +261,7 @@ async function inflate(auto, reorder) {
|
|
|
214
261
|
sessions.set(id, core)
|
|
215
262
|
}
|
|
216
263
|
|
|
217
|
-
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i))
|
|
264
|
+
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, readOpts))
|
|
218
265
|
}
|
|
219
266
|
|
|
220
267
|
try {
|
|
@@ -224,9 +271,10 @@ async function inflate(auto, reorder) {
|
|
|
224
271
|
}
|
|
225
272
|
}
|
|
226
273
|
|
|
227
|
-
async function getOplogBatch(undo, core, length, weight, index) {
|
|
274
|
+
async function getOplogBatch(undo, core, length, weight, index, opts = null) {
|
|
228
275
|
const seq = length - 1
|
|
229
|
-
const block = await core.get(seq)
|
|
276
|
+
const block = await core.get(seq, opts)
|
|
277
|
+
if (block === null) return null
|
|
230
278
|
const oplog = encoding.decodeOplog(block)
|
|
231
279
|
const head = createNode(core, length, weight, oplog)
|
|
232
280
|
|
|
@@ -238,12 +286,18 @@ async function getOplogBatch(undo, core, length, weight, index) {
|
|
|
238
286
|
const end = seq + head.batch.end // skip head, we have it
|
|
239
287
|
|
|
240
288
|
for (let i = start; i < end; i++) {
|
|
241
|
-
remaining.push(core.get(i))
|
|
289
|
+
remaining.push(core.get(i, opts))
|
|
242
290
|
}
|
|
243
291
|
|
|
244
292
|
if (remaining.length) {
|
|
245
293
|
const blocks = await Promise.all(remaining)
|
|
246
294
|
for (let i = 0; i < blocks.length; i++) {
|
|
295
|
+
// keep the hole so the gap in the batch stays visible
|
|
296
|
+
if (blocks[i] === null) {
|
|
297
|
+
batch.push(null)
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
|
|
247
301
|
const oplog = encoding.decodeOplog(blocks[i])
|
|
248
302
|
batch.push(createNode(core, i + start + 1, weight, oplog))
|
|
249
303
|
}
|
|
@@ -254,8 +308,14 @@ async function getOplogBatch(undo, core, length, weight, index) {
|
|
|
254
308
|
}
|
|
255
309
|
|
|
256
310
|
function getLocalBatch(undo, writer, length, weight, index) {
|
|
311
|
+
if (writer.core.closed) throw new Error('Writer core is closed')
|
|
312
|
+
|
|
257
313
|
if (length > writer.core.length) {
|
|
258
|
-
if (!writer.pending)
|
|
314
|
+
if (!writer.pending) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`No local nodes: looking for ${length}, found ${writer.core.length}@${writer.core.id}`
|
|
317
|
+
)
|
|
318
|
+
}
|
|
259
319
|
|
|
260
320
|
let i = 0
|
|
261
321
|
while (i < writer.pending.length) {
|
|
@@ -264,9 +324,10 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
264
324
|
}
|
|
265
325
|
|
|
266
326
|
const head = writer.pending[i]
|
|
327
|
+
const headBatch = head.batch || { start: 0, end: 0 }
|
|
267
328
|
|
|
268
|
-
const start = i -
|
|
269
|
-
const end = i +
|
|
329
|
+
const start = i - headBatch.start
|
|
330
|
+
const end = i + headBatch.end + 1 // include head
|
|
270
331
|
|
|
271
332
|
const batch = writer.pending.slice(start, end)
|
|
272
333
|
return { undo, batch, index }
|
|
@@ -275,6 +336,19 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
275
336
|
return getOplogBatch(undo, writer.core, length, weight, index)
|
|
276
337
|
}
|
|
277
338
|
|
|
339
|
+
// tolerates legacy nodes instead of asserting
|
|
340
|
+
async function* oplogIterator(bee) {
|
|
341
|
+
try {
|
|
342
|
+
for await (const node of bee.createChangesStream({ wait: false })) {
|
|
343
|
+
const legacy = isLegacyNode(node)
|
|
344
|
+
const refs = getOplogCompat(node)
|
|
345
|
+
if (refs.length) yield { node, refs, legacy }
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
yield null // signal data not available
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
278
352
|
function getOplog(data) {
|
|
279
353
|
const result = { ref: null, info: null }
|
|
280
354
|
for (const { keys } of data.batch) {
|
|
@@ -300,6 +374,35 @@ function getOplog(data) {
|
|
|
300
374
|
asserts.bail('Bad system node')
|
|
301
375
|
}
|
|
302
376
|
|
|
377
|
+
// legacy nodes carry no isOplog flag so we cannot tell which writer update is
|
|
378
|
+
// the oplog to fetch - yield every candidate until we have a better signal
|
|
379
|
+
function getOplogCompat(data) {
|
|
380
|
+
const undo = data.tail || EMPTY_HEAD
|
|
381
|
+
const oplogs = []
|
|
382
|
+
const candidates = []
|
|
383
|
+
|
|
384
|
+
for (const { keys } of data.batch) {
|
|
385
|
+
for (const k of keys) {
|
|
386
|
+
if (k.key[0] !== 1) continue
|
|
387
|
+
|
|
388
|
+
let value = null
|
|
389
|
+
try {
|
|
390
|
+
value = encoding.decodeSystemWriter(k.key, k.value)
|
|
391
|
+
} catch {
|
|
392
|
+
continue
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// writer was just added, nothing to fetch
|
|
396
|
+
if (!value.length) continue
|
|
397
|
+
|
|
398
|
+
if (value.isOplog) oplogs.push({ undo, oplog: value })
|
|
399
|
+
else candidates.push({ undo, oplog: value })
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return oplogs.length ? oplogs : candidates
|
|
404
|
+
}
|
|
405
|
+
|
|
303
406
|
function isLinking(node, link) {
|
|
304
407
|
if (node.length >= link.length && b4a.equals(node.key, link.key)) {
|
|
305
408
|
return true
|
package/lib/trusted.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
2
|
+
|
|
3
|
+
class TrustedPeers {
|
|
4
|
+
constructor(hooks) {
|
|
5
|
+
this.trusted = new Map()
|
|
6
|
+
this.pending = new Map()
|
|
7
|
+
this.hooks = hooks
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async isTrusted(key, reference) {
|
|
11
|
+
const hex = b4a.toString(key, 'hex')
|
|
12
|
+
if (this.trusted.get(hex)) return true
|
|
13
|
+
|
|
14
|
+
let promise = this.pending.get(hex)
|
|
15
|
+
if (promise) return promise
|
|
16
|
+
|
|
17
|
+
promise = this.hooks.isTrusted
|
|
18
|
+
? this.hooks.isTrusted(key, reference)
|
|
19
|
+
: Promise.resolve(!this.hooks.mostRecentTrusted)
|
|
20
|
+
this.pending.set(hex, promise)
|
|
21
|
+
|
|
22
|
+
const trusted = await promise
|
|
23
|
+
if (trusted) this.trusted.set(hex, trusted)
|
|
24
|
+
|
|
25
|
+
if (this.pending.get(hex) === promise) this.pending.delete(hex)
|
|
26
|
+
return trusted
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
read(trusted) {
|
|
30
|
+
return trusted || null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
mostRecentTrusted(target, reference) {
|
|
34
|
+
if (!this.hooks.mostRecentTrusted) return Promise.resolve(null)
|
|
35
|
+
return this.hooks.mostRecentTrusted(target, reference)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
clear() {
|
|
39
|
+
this.pending.clear()
|
|
40
|
+
this.trusted.clear()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = TrustedPeers
|
package/lib/updates.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const b4a = require('b4a')
|
|
2
|
-
|
|
3
1
|
module.exports = class UpdateChanges {
|
|
4
2
|
constructor(auto) {
|
|
5
3
|
this.auto = auto
|
|
@@ -21,57 +19,22 @@ module.exports = class UpdateChanges {
|
|
|
21
19
|
flushes: this.auto.system.flushes,
|
|
22
20
|
from: this.tracking[k].head(),
|
|
23
21
|
to: null,
|
|
24
|
-
|
|
22
|
+
incremental: false
|
|
25
23
|
})
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
finalise() {
|
|
30
|
-
const floor = this.auto.system.shared
|
|
31
|
-
|
|
32
28
|
// flush count at `to` - safe to read live, state is frozen for the hook
|
|
33
29
|
const flushes = this.auto.system.flushes
|
|
34
30
|
|
|
35
31
|
for (const k in this.tracking) {
|
|
36
32
|
const tracked = this.byName.get(k)
|
|
37
|
-
const startFlushes = tracked.flushes
|
|
38
33
|
|
|
39
34
|
tracked.to = this.tracking[k].head()
|
|
40
35
|
tracked.flushes = flushes
|
|
41
|
-
|
|
42
|
-
if (floor && floor.flushes < startFlushes) {
|
|
43
|
-
tracked.shared = k === 'view' ? floor.view : floor.system
|
|
44
|
-
} else {
|
|
45
|
-
tracked.shared = tracked.from
|
|
46
|
-
}
|
|
47
36
|
}
|
|
48
37
|
|
|
49
38
|
return this.byName
|
|
50
39
|
}
|
|
51
|
-
|
|
52
|
-
static from(shared, current) {
|
|
53
|
-
if (
|
|
54
|
-
shared.flushes === current.flushes &&
|
|
55
|
-
shared.system.length === current.system.length &&
|
|
56
|
-
(shared.system.key === current.system.key ||
|
|
57
|
-
(shared.system.key &&
|
|
58
|
-
current.system.key &&
|
|
59
|
-
b4a.equals(shared.system.key, current.system.key)))
|
|
60
|
-
) {
|
|
61
|
-
return null
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const byName = new Map()
|
|
65
|
-
const flushes = current.flushes
|
|
66
|
-
|
|
67
|
-
byName.set('view', { from: shared.view, shared: shared.view, to: current.view, flushes })
|
|
68
|
-
byName.set('system', {
|
|
69
|
-
from: shared.system,
|
|
70
|
-
shared: shared.system,
|
|
71
|
-
to: current.system,
|
|
72
|
-
flushes
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
return byName
|
|
76
|
-
}
|
|
77
40
|
}
|
package/lib/writers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const b4a = require('b4a')
|
|
2
2
|
const encoding = require('./encoding.js')
|
|
3
3
|
const Triggers = require('./triggers.js')
|
|
4
|
-
const { signAttestation } = require('./witness.js')
|
|
4
|
+
const { signAttestation, resolveWeight } = require('./witness.js')
|
|
5
5
|
const { WriterEncryption } = require('autobee-encryption')
|
|
6
6
|
|
|
7
7
|
const WRITER_PREFETCH = 10
|
|
@@ -25,6 +25,8 @@ class ActiveWriters {
|
|
|
25
25
|
this.witnesses = []
|
|
26
26
|
this.attestations = []
|
|
27
27
|
|
|
28
|
+
this._pendingBuffer = new Map()
|
|
29
|
+
|
|
28
30
|
this.localWriter.attach()
|
|
29
31
|
|
|
30
32
|
this._bootstrap()
|
|
@@ -62,9 +64,14 @@ class ActiveWriters {
|
|
|
62
64
|
const info = await this.auto.system.get(key, { unflushed: true })
|
|
63
65
|
if (info && info.isAnchor) return
|
|
64
66
|
w = await this.add(key)
|
|
67
|
+
if (!w) return
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
//
|
|
70
|
+
// remember the announced length so gc doesn't drop a writer we haven't caught up on
|
|
71
|
+
w.hint(length)
|
|
72
|
+
|
|
73
|
+
// prefetch the oplog head for the fast-forward that runs right after us
|
|
74
|
+
if (w && length > 0) w.core.get(length - 1).catch(noop)
|
|
68
75
|
|
|
69
76
|
this.bump()
|
|
70
77
|
}
|
|
@@ -95,19 +102,24 @@ class ActiveWriters {
|
|
|
95
102
|
return this.localWriter.clear()
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
flushLocal(views) {
|
|
105
|
+
async flushLocal(views) {
|
|
99
106
|
const w = this.localWriter
|
|
100
107
|
|
|
101
108
|
// only consume the queue when a flush will actually write
|
|
102
|
-
if (w.pending === null || w.processed === 0) return
|
|
109
|
+
if (w.pending === null || w.processed === 0) return
|
|
103
110
|
|
|
104
111
|
const queued = this.attestations
|
|
105
112
|
this.attestations = []
|
|
106
113
|
|
|
107
|
-
|
|
114
|
+
const trusted = await this.auto.trusted.mostRecentTrusted(this.auto._workingView.view, null)
|
|
115
|
+
|
|
116
|
+
return w.flush(views, queued, trusted)
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
async refresh() {
|
|
120
|
+
// peeked batches were read against the pre-reboot system, drop them
|
|
121
|
+
this._pendingBuffer.clear()
|
|
122
|
+
|
|
111
123
|
if (this.localWriter && !this.localWriter.isClosed) {
|
|
112
124
|
await this.updateLocalState()
|
|
113
125
|
}
|
|
@@ -122,6 +134,33 @@ class ActiveWriters {
|
|
|
122
134
|
await this.localWriter.update()
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
async nextPendingNode() {
|
|
138
|
+
for (const w of this.pending.slice()) {
|
|
139
|
+
if (this._pendingBuffer.has(w) || w.isFrozen) continue
|
|
140
|
+
|
|
141
|
+
const batch = await w.next()
|
|
142
|
+
if (batch !== null) this._pendingBuffer.set(w, batch)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this._pendingBuffer.size === 0) return null
|
|
146
|
+
|
|
147
|
+
let writer = null
|
|
148
|
+
let best = null
|
|
149
|
+
|
|
150
|
+
for (const [w, batch] of this._pendingBuffer) {
|
|
151
|
+
const head = batch[0]
|
|
152
|
+
// -1 marks a node next() has not resolved a sort weight for yet
|
|
153
|
+
if (head.weight < 0) head.weight = await resolveWeight(this.auto, head)
|
|
154
|
+
if (best === null || sortsBefore(head, best[0])) {
|
|
155
|
+
writer = w
|
|
156
|
+
best = batch
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this._pendingBuffer.delete(writer)
|
|
161
|
+
return { writer, batch: best }
|
|
162
|
+
}
|
|
163
|
+
|
|
125
164
|
_updateLocalState() {
|
|
126
165
|
const w = this.localWriter
|
|
127
166
|
|
|
@@ -210,11 +249,16 @@ class ActiveWriters {
|
|
|
210
249
|
const id = b4a.toString(bootstrap.key, 'hex')
|
|
211
250
|
let w = this.active.get(id)
|
|
212
251
|
if (w) {
|
|
252
|
+
w.isBootstrap = true
|
|
213
253
|
w.couple() // couple if we weren't ready first time
|
|
214
254
|
return
|
|
215
255
|
}
|
|
216
256
|
|
|
217
|
-
|
|
257
|
+
const session = bootstrap.session()
|
|
258
|
+
session.setEncryption(this.auto._getEncryptionProvider())
|
|
259
|
+
|
|
260
|
+
w = new Writer(this, this.auto.system, false, session, id)
|
|
261
|
+
w.isBootstrap = true // the root of the autobase, never gc'd
|
|
218
262
|
w.attach()
|
|
219
263
|
w.couple()
|
|
220
264
|
}
|
|
@@ -245,6 +289,7 @@ class Writer {
|
|
|
245
289
|
this.appendLength = this.core.length + 1
|
|
246
290
|
this.waiting = null
|
|
247
291
|
this.download = null
|
|
292
|
+
this.hintedLength = 0
|
|
248
293
|
|
|
249
294
|
this.isPending = false
|
|
250
295
|
this.isClosed = false
|
|
@@ -253,6 +298,7 @@ class Writer {
|
|
|
253
298
|
this.isRemoved = false
|
|
254
299
|
this.isCoupled = false
|
|
255
300
|
this.isFrozen = false
|
|
301
|
+
this.isBootstrap = false
|
|
256
302
|
|
|
257
303
|
this._onchangeBound = this._onchange.bind(this)
|
|
258
304
|
|
|
@@ -300,6 +346,8 @@ class Writer {
|
|
|
300
346
|
if (!this.isPending) return
|
|
301
347
|
this.isPending = false
|
|
302
348
|
|
|
349
|
+
this.writers._pendingBuffer.delete(this)
|
|
350
|
+
|
|
303
351
|
const head = this.writers.pending.pop()
|
|
304
352
|
if (head !== this) {
|
|
305
353
|
this.writers.pending[this.index] = head
|
|
@@ -307,6 +355,11 @@ class Writer {
|
|
|
307
355
|
}
|
|
308
356
|
}
|
|
309
357
|
|
|
358
|
+
// a peer announced this length for us, so we're not caught up until we've seen it
|
|
359
|
+
hint(length) {
|
|
360
|
+
if (length > this.hintedLength) this.hintedLength = length
|
|
361
|
+
}
|
|
362
|
+
|
|
310
363
|
_onchange() {
|
|
311
364
|
if (this.waiting === null) this.bump()
|
|
312
365
|
}
|
|
@@ -344,10 +397,10 @@ class Writer {
|
|
|
344
397
|
|
|
345
398
|
const length = await this.update()
|
|
346
399
|
|
|
347
|
-
// don't drop any local nodes left in buffer
|
|
348
|
-
if (length < this.core.length || this.appending) {
|
|
400
|
+
// don't drop any local nodes left in buffer, or work a peer told us about
|
|
401
|
+
if (length < this.core.length || length < this.hintedLength || this.appending) {
|
|
349
402
|
this.addPending()
|
|
350
|
-
} else if (this !== this.writers.localWriter && !this.isIndexer) {
|
|
403
|
+
} else if (this !== this.writers.localWriter && !this.isIndexer && !this.isBootstrap) {
|
|
351
404
|
await this.detachAndClose()
|
|
352
405
|
}
|
|
353
406
|
}
|
|
@@ -367,6 +420,7 @@ class Writer {
|
|
|
367
420
|
|
|
368
421
|
detachAndClose() {
|
|
369
422
|
this.detach()
|
|
423
|
+
this.decouple()
|
|
370
424
|
return this.close()
|
|
371
425
|
}
|
|
372
426
|
|
|
@@ -620,7 +674,8 @@ class Writer {
|
|
|
620
674
|
batch,
|
|
621
675
|
views: null,
|
|
622
676
|
optimistic: optimistic && !this.writers.writable,
|
|
623
|
-
value
|
|
677
|
+
value,
|
|
678
|
+
trusted: null
|
|
624
679
|
}
|
|
625
680
|
|
|
626
681
|
const node = createNode(this.core, this.appendLength++, -1, oplog)
|
|
@@ -643,12 +698,16 @@ class Writer {
|
|
|
643
698
|
this.processed = 0
|
|
644
699
|
}
|
|
645
700
|
|
|
646
|
-
flush(views, queued) {
|
|
647
|
-
if (this.pending === null || this.processed === 0) return
|
|
701
|
+
async flush(views, queued, trusted) {
|
|
702
|
+
if (this.pending === null || this.processed === 0) return
|
|
648
703
|
|
|
649
704
|
const head = this.pending[this.processed - 1]
|
|
650
705
|
head.views = views
|
|
651
706
|
|
|
707
|
+
head.trusted = trusted
|
|
708
|
+
? [{ key: trusted.key, length: trusted.length, flushes: views.flushes }]
|
|
709
|
+
: null
|
|
710
|
+
|
|
652
711
|
if (queued && queued.length) {
|
|
653
712
|
const attestations = []
|
|
654
713
|
for (const { key, weight } of queued) {
|
|
@@ -668,11 +727,26 @@ class Writer {
|
|
|
668
727
|
for (let i = 0; i < this.processed; i++) {
|
|
669
728
|
buffers.push(encoding.encodeOplog(this.pending[i]))
|
|
670
729
|
}
|
|
671
|
-
|
|
672
|
-
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
await this.core.append(buffers)
|
|
733
|
+
} finally {
|
|
734
|
+
this.clear()
|
|
735
|
+
}
|
|
673
736
|
}
|
|
674
737
|
}
|
|
675
738
|
|
|
739
|
+
// mirrors topo.cmp - kept local because topo.js requires this module
|
|
740
|
+
function sortsBefore(a, b) {
|
|
741
|
+
if (a.weight !== b.weight) return a.weight > b.weight
|
|
742
|
+
if (a.timestamp !== b.timestamp) return a.timestamp < b.timestamp
|
|
743
|
+
|
|
744
|
+
const c = b4a.compare(a.key, b.key)
|
|
745
|
+
if (c !== 0) return c < 0
|
|
746
|
+
|
|
747
|
+
return a.length < b.length
|
|
748
|
+
}
|
|
749
|
+
|
|
676
750
|
exports.ActiveWriters = ActiveWriters
|
|
677
751
|
exports.Writer = Writer
|
|
678
752
|
exports.createNode = createNode
|
|
@@ -692,7 +766,8 @@ function createNode(core, length, weight, oplog) {
|
|
|
692
766
|
batch: oplog.batch,
|
|
693
767
|
views: oplog.views,
|
|
694
768
|
optimistic: oplog.optimistic,
|
|
695
|
-
value: oplog.value
|
|
769
|
+
value: oplog.value,
|
|
770
|
+
trusted: oplog.trusted || []
|
|
696
771
|
}
|
|
697
772
|
}
|
|
698
773
|
|
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.21",
|
|
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",
|