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 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 2 so always one byte
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.end++ // max flag is 64 so always one byte
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 new ApplyView(this.bee.checkout({ key: v.key, length: v.start + v.length }), this)
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
- // if (topo.isLinkingAll(node, this.system.heads)) {
1195
- // return { undo: null, view: null, tip: [batch] }
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
  }
@@ -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(value, timestamp, batch, links, optimistic, witness, approvals)
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
- if (this.localWriter && !this.localWriter.isClosed) {
138
- await this.updateLocalState()
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.5.5",
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",