autobee 2.5.5 → 2.7.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/README.md +9 -0
- package/encoding/spec/autobee/index.js +23 -6
- package/encoding/spec/autobee/schema.json +10 -0
- package/index.js +17 -5
- package/lib/fast-forward.js +31 -0
- package/lib/system.js +33 -1
- package/lib/writers.js +28 -7
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -81,6 +81,7 @@ Options:
|
|
|
81
81
|
optimistic: true, // allow optimistic writes from unknown writers
|
|
82
82
|
isTrusted (key, reference) {}, // do we trust this writer, see Fast-forward
|
|
83
83
|
mostRecentTrusted (target, reference) {}, // the head we vouch for, see Fast-forward
|
|
84
|
+
warmup (view) {}, // prepare a candidate view, see Fast-forward
|
|
84
85
|
ackThreshold: 32, // flushes we may fall behind before acking, see Acking
|
|
85
86
|
fastForward: {} // see Fast-forward
|
|
86
87
|
}
|
|
@@ -255,6 +256,14 @@ Return the oplog head you most recently vouched for, given the `target` view bei
|
|
|
255
256
|
|
|
256
257
|
Called at flush time to stamp your own oplog (with your view as `target` and a `null` reference), and again per candidate during discovery.
|
|
257
258
|
|
|
259
|
+
#### `warmup(view)`
|
|
260
|
+
|
|
261
|
+
Prepare a candidate `view` before the fast-forward onto it is applied.
|
|
262
|
+
|
|
263
|
+
Called as soon as the candidate's view head is known, and runs alongside the block downloads the fast-forward is already doing rather than after them. Throw or reject to reject the candidate: the fast-forward is abandoned and the usual apply path catches up instead.
|
|
264
|
+
|
|
265
|
+
`view` is opened and closed through the `open` and `close` handlers, so don't build or close a db of your own.
|
|
266
|
+
|
|
258
267
|
#### `fastForward.boot`
|
|
259
268
|
|
|
260
269
|
```js
|
|
@@ -494,13 +494,14 @@ const encoding18 = {
|
|
|
494
494
|
c.uint.preencode(state, m.flushes)
|
|
495
495
|
encoding22.preencode(state, m.view)
|
|
496
496
|
encoding18_3.preencode(state, m.heads)
|
|
497
|
-
state.end++ // max flag is
|
|
497
|
+
state.end++ // max flag is 4 so always one byte
|
|
498
498
|
|
|
499
499
|
if (m.indexers) encoding18_4.preencode(state, m.indexers)
|
|
500
500
|
if (m.pending) encoding18_5.preencode(state, m.pending)
|
|
501
|
+
if (m.hash) c.fixed8.preencode(state, m.hash)
|
|
501
502
|
},
|
|
502
503
|
encode(state, m) {
|
|
503
|
-
const flags = (m.indexers ? 1 : 0) | (m.pending ? 2 : 0)
|
|
504
|
+
const flags = (m.indexers ? 1 : 0) | (m.pending ? 2 : 0) | (m.hash ? 4 : 0)
|
|
504
505
|
|
|
505
506
|
c.uint.encode(state, m.timestamp)
|
|
506
507
|
c.uint.encode(state, m.flushes)
|
|
@@ -510,6 +511,7 @@ const encoding18 = {
|
|
|
510
511
|
|
|
511
512
|
if (m.indexers) encoding18_4.encode(state, m.indexers)
|
|
512
513
|
if (m.pending) encoding18_5.encode(state, m.pending)
|
|
514
|
+
if (m.hash) c.fixed8.encode(state, m.hash)
|
|
513
515
|
},
|
|
514
516
|
decode(state) {
|
|
515
517
|
const v = c.uint.decode(state)
|
|
@@ -526,7 +528,8 @@ const encoding18 = {
|
|
|
526
528
|
view: r2,
|
|
527
529
|
heads: r3,
|
|
528
530
|
indexers: (flags & 1) !== 0 ? encoding18_4.decode(state) : null,
|
|
529
|
-
pending: (flags & 2) !== 0 ? encoding18_5.decode(state) : null
|
|
531
|
+
pending: (flags & 2) !== 0 ? encoding18_5.decode(state) : null,
|
|
532
|
+
hash: (flags & 4) !== 0 ? c.fixed8.decode(state) : null
|
|
530
533
|
}
|
|
531
534
|
}
|
|
532
535
|
}
|
|
@@ -1215,9 +1218,19 @@ const encoding37_6 = c.array(encoding36)
|
|
|
1215
1218
|
// @autobee/oplog-message-v4
|
|
1216
1219
|
const encoding37 = {
|
|
1217
1220
|
preencode(state, m) {
|
|
1221
|
+
const flags =
|
|
1222
|
+
(m.batch ? 1 : 0) |
|
|
1223
|
+
(m.views ? 2 : 0) |
|
|
1224
|
+
(m.trusted ? 4 : 0) |
|
|
1225
|
+
(m.witness ? 8 : 0) |
|
|
1226
|
+
(m.approvals ? 16 : 0) |
|
|
1227
|
+
(m.optimistic ? 32 : 0) |
|
|
1228
|
+
(m.value ? 64 : 0) |
|
|
1229
|
+
(m.hash ? 128 : 0)
|
|
1230
|
+
|
|
1218
1231
|
c.uint.preencode(state, m.timestamp)
|
|
1219
1232
|
encoding37_1.preencode(state, m.links)
|
|
1220
|
-
state
|
|
1233
|
+
c.uint.preencode(state, flags)
|
|
1221
1234
|
|
|
1222
1235
|
if (m.batch) encoding24.preencode(state, m.batch)
|
|
1223
1236
|
if (m.views) encoding25.preencode(state, m.views)
|
|
@@ -1225,6 +1238,7 @@ const encoding37 = {
|
|
|
1225
1238
|
if (m.witness) encoding35.preencode(state, m.witness)
|
|
1226
1239
|
if (m.approvals) encoding37_6.preencode(state, m.approvals)
|
|
1227
1240
|
if (m.value) c.buffer.preencode(state, m.value)
|
|
1241
|
+
if (m.hash) c.fixed8.preencode(state, m.hash)
|
|
1228
1242
|
},
|
|
1229
1243
|
encode(state, m) {
|
|
1230
1244
|
const flags =
|
|
@@ -1234,7 +1248,8 @@ const encoding37 = {
|
|
|
1234
1248
|
(m.witness ? 8 : 0) |
|
|
1235
1249
|
(m.approvals ? 16 : 0) |
|
|
1236
1250
|
(m.optimistic ? 32 : 0) |
|
|
1237
|
-
(m.value ? 64 : 0)
|
|
1251
|
+
(m.value ? 64 : 0) |
|
|
1252
|
+
(m.hash ? 128 : 0)
|
|
1238
1253
|
|
|
1239
1254
|
c.uint.encode(state, m.timestamp)
|
|
1240
1255
|
encoding37_1.encode(state, m.links)
|
|
@@ -1246,6 +1261,7 @@ const encoding37 = {
|
|
|
1246
1261
|
if (m.witness) encoding35.encode(state, m.witness)
|
|
1247
1262
|
if (m.approvals) encoding37_6.encode(state, m.approvals)
|
|
1248
1263
|
if (m.value) c.buffer.encode(state, m.value)
|
|
1264
|
+
if (m.hash) c.fixed8.encode(state, m.hash)
|
|
1249
1265
|
},
|
|
1250
1266
|
decode(state) {
|
|
1251
1267
|
const v = c.uint.decode(state)
|
|
@@ -1263,7 +1279,8 @@ const encoding37 = {
|
|
|
1263
1279
|
witness: (flags & 8) !== 0 ? encoding35.decode(state) : null,
|
|
1264
1280
|
approvals: (flags & 16) !== 0 ? encoding37_6.decode(state) : null,
|
|
1265
1281
|
optimistic: (flags & 32) !== 0,
|
|
1266
|
-
value: (flags & 64) !== 0 ? c.buffer.decode(state) : null
|
|
1282
|
+
value: (flags & 64) !== 0 ? c.buffer.decode(state) : null,
|
|
1283
|
+
hash: (flags & 128) !== 0 ? c.fixed8.decode(state) : null
|
|
1267
1284
|
}
|
|
1268
1285
|
}
|
|
1269
1286
|
}
|
|
@@ -434,6 +434,11 @@
|
|
|
434
434
|
"array": true,
|
|
435
435
|
"type": "bool",
|
|
436
436
|
"version": 1
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"name": "hash",
|
|
440
|
+
"type": "fixed8",
|
|
441
|
+
"version": 1
|
|
437
442
|
}
|
|
438
443
|
]
|
|
439
444
|
},
|
|
@@ -1018,6 +1023,11 @@
|
|
|
1018
1023
|
"name": "value",
|
|
1019
1024
|
"type": "buffer",
|
|
1020
1025
|
"version": 1
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
"name": "hash",
|
|
1029
|
+
"type": "fixed8",
|
|
1030
|
+
"version": 1
|
|
1021
1031
|
}
|
|
1022
1032
|
]
|
|
1023
1033
|
}
|
package/index.js
CHANGED
|
@@ -134,6 +134,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
134
134
|
this._now = handlers.now || Date.now // overridable for clock-drift tests
|
|
135
135
|
this._preapply = handlers.preapply || null
|
|
136
136
|
this._preApplied = false
|
|
137
|
+
this._warmup = handlers.warmup || null
|
|
137
138
|
this._hasApply = !!handlers.apply
|
|
138
139
|
this._hasUpdate = !!handlers.update
|
|
139
140
|
this._needsUpdate = false
|
|
@@ -440,7 +441,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
440
441
|
const v = oplog.op.views.view
|
|
441
442
|
if (!v) return null
|
|
442
443
|
|
|
443
|
-
return
|
|
444
|
+
return this.openView({ key: v.key, length: v.start + v.length })
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
openView(head) {
|
|
448
|
+
return new ApplyView(this.bee.checkout({ key: head.key, length: head.length }), this)
|
|
444
449
|
}
|
|
445
450
|
|
|
446
451
|
openCore(key) {
|
|
@@ -1191,9 +1196,14 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1191
1196
|
node.weight = await resolveWeight(this, node)
|
|
1192
1197
|
for (const n of batch) n.weight = node.weight
|
|
1193
1198
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1199
|
+
if (
|
|
1200
|
+
node.hash &&
|
|
1201
|
+
this.system.hash &&
|
|
1202
|
+
b4a.equals(node.hash, this.system.hash) &&
|
|
1203
|
+
topo.isLinkingAll(node, this.system.heads)
|
|
1204
|
+
) {
|
|
1205
|
+
return { undo: null, view: null, tip: [batch] }
|
|
1206
|
+
}
|
|
1197
1207
|
|
|
1198
1208
|
const t = await topo.sort(this, batch)
|
|
1199
1209
|
|
|
@@ -1347,6 +1357,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1347
1357
|
await this.local.ready()
|
|
1348
1358
|
|
|
1349
1359
|
const links = this.system.getLinks(this.local.key)
|
|
1360
|
+
const hash = this.system.hash
|
|
1350
1361
|
|
|
1351
1362
|
// never stamp before anything we link
|
|
1352
1363
|
const t = Math.max(this._now(), this.system.timestamp)
|
|
@@ -1374,7 +1385,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1374
1385
|
lnk,
|
|
1375
1386
|
optimistic,
|
|
1376
1387
|
i === 0 ? witness : null,
|
|
1377
|
-
i === 0 ? approvals : null
|
|
1388
|
+
i === 0 ? approvals : null,
|
|
1389
|
+
i === 0 ? hash : null
|
|
1378
1390
|
)
|
|
1379
1391
|
batch.push(node)
|
|
1380
1392
|
}
|
package/lib/fast-forward.js
CHANGED
|
@@ -30,6 +30,8 @@ class FastForward {
|
|
|
30
30
|
this.running = null
|
|
31
31
|
this.failed = false
|
|
32
32
|
this.cores = []
|
|
33
|
+
this.warmup = null
|
|
34
|
+
this.warmupView = null
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT
|
|
@@ -170,6 +172,8 @@ class FastForward {
|
|
|
170
172
|
|
|
171
173
|
const promises = []
|
|
172
174
|
|
|
175
|
+
if (this._startWarmup()) promises.push(this.warmup)
|
|
176
|
+
|
|
173
177
|
// ensure local key is locally available always
|
|
174
178
|
promises.push(this.system.get(this.auto.local.key, { timeout: this.timeout }))
|
|
175
179
|
|
|
@@ -199,6 +203,17 @@ class FastForward {
|
|
|
199
203
|
}
|
|
200
204
|
}
|
|
201
205
|
|
|
206
|
+
_startWarmup() {
|
|
207
|
+
if (!this.auto._warmup || !this.system.view.length) return false
|
|
208
|
+
|
|
209
|
+
this.warmupView = this.auto.openView(this.system.view)
|
|
210
|
+
|
|
211
|
+
// ensure thenable
|
|
212
|
+
this.warmup = Promise.resolve(this.auto._warmup(this.warmupView.view))
|
|
213
|
+
|
|
214
|
+
return true
|
|
215
|
+
}
|
|
216
|
+
|
|
202
217
|
async _resolveLength() {
|
|
203
218
|
const core = this.auto.store.get({ key: this.head.key })
|
|
204
219
|
this.cores.push(core)
|
|
@@ -278,6 +293,22 @@ class FastForward {
|
|
|
278
293
|
async close() {
|
|
279
294
|
this.destroyed = true
|
|
280
295
|
this.clearRequests()
|
|
296
|
+
|
|
297
|
+
// close view to cancel inflight reads
|
|
298
|
+
if (this.warmupView) {
|
|
299
|
+
const view = this.warmupView
|
|
300
|
+
this.warmupView = null
|
|
301
|
+
await view.close()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this.warmup) {
|
|
305
|
+
try {
|
|
306
|
+
await this.warmup
|
|
307
|
+
} catch (err) {
|
|
308
|
+
safetyCatch(err)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
281
312
|
if (this.system) await this.system.close()
|
|
282
313
|
for (const core of this.cores) await core.close()
|
|
283
314
|
}
|
package/lib/system.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const Hyperbee = require('hyperbee2')
|
|
2
2
|
const Promotions = require('./promotions.js')
|
|
3
|
+
const sodium = require('sodium-native')
|
|
4
|
+
const c = require('compact-encoding')
|
|
3
5
|
const b4a = require('b4a')
|
|
4
6
|
|
|
5
7
|
const { getViewMap, getCoreManifest } = require('./migrations.js')
|
|
@@ -13,6 +15,10 @@ const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
|
|
|
13
15
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
14
16
|
const EMPTY = b4a.from([])
|
|
15
17
|
|
|
18
|
+
const HASH = c.fixed(sodium.crypto_shorthash_BYTES)
|
|
19
|
+
const HASH_KEY = b4a.from('autobee-orderkey') // crypto_shorthash_KEYBYTES
|
|
20
|
+
const HASH_SEED = b4a.alloc(sodium.crypto_shorthash_BYTES)
|
|
21
|
+
|
|
16
22
|
class SystemSnapshot {
|
|
17
23
|
constructor(bee) {
|
|
18
24
|
this.bee = bee
|
|
@@ -41,6 +47,7 @@ module.exports = class Systembee {
|
|
|
41
47
|
this.bee = new Hyperbee(store, opts)
|
|
42
48
|
this.view = null
|
|
43
49
|
this.heads = []
|
|
50
|
+
this.hash = null
|
|
44
51
|
this.migration = null
|
|
45
52
|
this.version = 0
|
|
46
53
|
this.timestamp = 0
|
|
@@ -142,6 +149,7 @@ module.exports = class Systembee {
|
|
|
142
149
|
this.view = info ? info.view : EMPTY_HEAD
|
|
143
150
|
this.migration = null
|
|
144
151
|
this.heads = info ? info.heads : []
|
|
152
|
+
this.hash = info ? info.hash : null
|
|
145
153
|
this.timestamp = info ? info.timestamp : 0
|
|
146
154
|
this.flushes = info ? info.flushes : 0
|
|
147
155
|
|
|
@@ -224,6 +232,8 @@ module.exports = class Systembee {
|
|
|
224
232
|
this.heads.push({ key: node.key, length: node.length })
|
|
225
233
|
if (node.timestamp > this.timestamp) this.timestamp = node.timestamp // TODO: support smoothing
|
|
226
234
|
|
|
235
|
+
this.hash = advanceHash(this.hash, node)
|
|
236
|
+
|
|
227
237
|
// resolved sort weight, re-stamped on every reapplication - this is what
|
|
228
238
|
// topo reads back out of the changes stream
|
|
229
239
|
this.update(node.key, node.length, node.weight, -1, false, false, false, node.timestamp, null)
|
|
@@ -443,7 +453,8 @@ module.exports = class Systembee {
|
|
|
443
453
|
timestamp: this.timestamp,
|
|
444
454
|
flushes: this.flushes,
|
|
445
455
|
indexers: null, // legacy
|
|
446
|
-
pending: this.promotions.digest
|
|
456
|
+
pending: this.promotions.digest,
|
|
457
|
+
hash: this.hash
|
|
447
458
|
}
|
|
448
459
|
|
|
449
460
|
w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))
|
|
@@ -488,3 +499,24 @@ module.exports = class Systembee {
|
|
|
488
499
|
return this.promotions.hint(key, opts)
|
|
489
500
|
}
|
|
490
501
|
}
|
|
502
|
+
|
|
503
|
+
function advanceHash(prev, node) {
|
|
504
|
+
const state = { start: 0, end: 0, buffer: null }
|
|
505
|
+
|
|
506
|
+
HASH.preencode(state, prev || HASH_SEED)
|
|
507
|
+
c.fixed32.preencode(state, node.key)
|
|
508
|
+
c.uint.preencode(state, node.length)
|
|
509
|
+
c.uint.preencode(state, node.weight)
|
|
510
|
+
|
|
511
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
512
|
+
|
|
513
|
+
HASH.encode(state, prev || HASH_SEED)
|
|
514
|
+
c.fixed32.encode(state, node.key)
|
|
515
|
+
c.uint.encode(state, node.length)
|
|
516
|
+
c.uint.encode(state, node.weight)
|
|
517
|
+
|
|
518
|
+
const hash = b4a.allocUnsafe(sodium.crypto_shorthash_BYTES)
|
|
519
|
+
sodium.crypto_shorthash(hash, state.buffer, HASH_KEY)
|
|
520
|
+
|
|
521
|
+
return hash
|
|
522
|
+
}
|
package/lib/writers.js
CHANGED
|
@@ -86,8 +86,17 @@ class ActiveWriters {
|
|
|
86
86
|
return this.localWriter.getLatest()
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
appendLocal(value, timestamp, batch, links, optimistic, witness, approvals = null) {
|
|
90
|
-
return this.localWriter.append(
|
|
89
|
+
appendLocal(value, timestamp, batch, links, optimistic, witness, approvals = null, hash = null) {
|
|
90
|
+
return this.localWriter.append(
|
|
91
|
+
value,
|
|
92
|
+
timestamp,
|
|
93
|
+
batch,
|
|
94
|
+
links,
|
|
95
|
+
optimistic,
|
|
96
|
+
witness,
|
|
97
|
+
approvals,
|
|
98
|
+
hash
|
|
99
|
+
)
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
async rotateLocalWriter(core) {
|
|
@@ -134,10 +143,20 @@ class ActiveWriters {
|
|
|
134
143
|
// peeked batches were read against the pre-reboot system, drop them
|
|
135
144
|
this._pendingBuffer.clear()
|
|
136
145
|
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
const local = this.localWriter && !this.localWriter.isClosed ? this.localWriter : null
|
|
147
|
+
|
|
148
|
+
const info = []
|
|
149
|
+
for (const w of this.active.values()) {
|
|
150
|
+
if (w.isFrozen && w !== this.localWriter) continue
|
|
151
|
+
info.push(w.system.get(w.core.key))
|
|
139
152
|
}
|
|
140
153
|
|
|
154
|
+
if (local) info.push(local.system.get(local.core.key))
|
|
155
|
+
|
|
156
|
+
await Promise.all(info)
|
|
157
|
+
|
|
158
|
+
if (local) await this.updateLocalState()
|
|
159
|
+
|
|
141
160
|
for (const writer of this.active.values()) {
|
|
142
161
|
if (writer.isFrozen && writer !== this.localWriter) await writer.detachAndClose()
|
|
143
162
|
else await writer.reset()
|
|
@@ -683,7 +702,7 @@ class Writer {
|
|
|
683
702
|
return this.pending !== null
|
|
684
703
|
}
|
|
685
704
|
|
|
686
|
-
append(value, timestamp, batch, links, optimistic, witness, approvals = null) {
|
|
705
|
+
append(value, timestamp, batch, links, optimistic, witness, approvals = null, hash = null) {
|
|
687
706
|
if (this.pending === null) this.pending = []
|
|
688
707
|
|
|
689
708
|
const oplog = {
|
|
@@ -696,7 +715,8 @@ class Writer {
|
|
|
696
715
|
witness: witness || null,
|
|
697
716
|
approvals,
|
|
698
717
|
optimistic: optimistic && !this.writers.writable,
|
|
699
|
-
value
|
|
718
|
+
value,
|
|
719
|
+
hash
|
|
700
720
|
}
|
|
701
721
|
|
|
702
722
|
const node = createNode(this.core, this.appendLength++, -1, oplog)
|
|
@@ -773,7 +793,8 @@ function createNode(core, length, weight, oplog) {
|
|
|
773
793
|
witness: oplog.witness || null,
|
|
774
794
|
approvals: oplog.approvals || null,
|
|
775
795
|
optimistic: oplog.optimistic,
|
|
776
|
-
value: oplog.value
|
|
796
|
+
value: oplog.value,
|
|
797
|
+
hash: oplog.hash || null
|
|
777
798
|
}
|
|
778
799
|
}
|
|
779
800
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"ready-guard": "^1.0.0",
|
|
36
36
|
"ready-resource": "^1.2.0",
|
|
37
37
|
"resolve-reject-promise": "^1.1.0",
|
|
38
|
-
"safety-catch": "^1.0.3"
|
|
38
|
+
"safety-catch": "^1.0.3",
|
|
39
|
+
"sodium-native": "^5.1.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"brittle": "^4.1.0",
|