autobee 2.6.0 → 2.7.1

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
@@ -256,13 +256,17 @@ Return the oplog head you most recently vouched for, given the `target` view bei
256
256
 
257
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.
258
258
 
259
+ During discovery `target` is a view opened for the candidate, and reads through it carry the fast-forward's timeout. Anything else the hook awaits is unbounded, so if it reaches beyond that view, put a timeout on it: the candidate waits on the hook, and so does closing it.
260
+
259
261
  #### `warmup(view)`
260
262
 
261
263
  Prepare a candidate `view` before the fast-forward onto it is applied.
262
264
 
263
265
  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
266
 
265
- `view` is opened and closed through the `open` and `close` handlers, so don't build or close a db of your own.
267
+ `view` is opened and closed through the `open` and `close` handlers, so don't build or close a db of your own. Reads through it carry the fast-forward's timeout, so a warmup that needs blocks nobody serves fails the candidate instead of hanging.
268
+
269
+ Only reads through `view` are bounded. Anything else the handler awaits, such as other cores or your own network calls, is up to you to time out: the fast-forward waits on the handler, and an abandoned candidate cannot finish closing until it returns.
266
270
 
267
271
  #### `fastForward.boot`
268
272
 
@@ -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
@@ -437,15 +437,17 @@ module.exports = class Autobee extends ReadyResource {
437
437
  }
438
438
 
439
439
  // the view an oplog head points at, opened and closed through the handlers
440
- openViewAt(oplog) {
440
+ openViewAt(oplog, { timeout } = {}) {
441
441
  const v = oplog.op.views.view
442
442
  if (!v) return null
443
443
 
444
- return this.openView({ key: v.key, length: v.start + v.length })
444
+ return this.openView({ key: v.key, length: v.start + v.length }, { timeout })
445
445
  }
446
446
 
447
- openView(head) {
448
- return new ApplyView(this.bee.checkout({ key: head.key, length: head.length }), this)
447
+ // timeout bounds every hypercore read done through the view, defaults to the main bee's
448
+ openView(head, { timeout } = {}) {
449
+ const bee = this.bee.checkout({ key: head.key, length: head.length, timeout })
450
+ return new ApplyView(bee, this)
449
451
  }
450
452
 
451
453
  openCore(key) {
@@ -1196,9 +1198,14 @@ module.exports = class Autobee extends ReadyResource {
1196
1198
  node.weight = await resolveWeight(this, node)
1197
1199
  for (const n of batch) n.weight = node.weight
1198
1200
 
1199
- // if (topo.isLinkingAll(node, this.system.heads)) {
1200
- // return { undo: null, view: null, tip: [batch] }
1201
- // }
1201
+ if (
1202
+ node.hash &&
1203
+ this.system.hash &&
1204
+ b4a.equals(node.hash, this.system.hash) &&
1205
+ topo.isLinkingAll(node, this.system.heads)
1206
+ ) {
1207
+ return { undo: null, view: null, tip: [batch] }
1208
+ }
1202
1209
 
1203
1210
  const t = await topo.sort(this, batch)
1204
1211
 
@@ -1352,6 +1359,7 @@ module.exports = class Autobee extends ReadyResource {
1352
1359
  await this.local.ready()
1353
1360
 
1354
1361
  const links = this.system.getLinks(this.local.key)
1362
+ const hash = this.system.hash
1355
1363
 
1356
1364
  // never stamp before anything we link
1357
1365
  const t = Math.max(this._now(), this.system.timestamp)
@@ -1379,7 +1387,8 @@ module.exports = class Autobee extends ReadyResource {
1379
1387
  lnk,
1380
1388
  optimistic,
1381
1389
  i === 0 ? witness : null,
1382
- i === 0 ? approvals : null
1390
+ i === 0 ? approvals : null,
1391
+ i === 0 ? hash : null
1383
1392
  )
1384
1393
  batch.push(node)
1385
1394
  }
@@ -56,7 +56,9 @@ class FastForward {
56
56
  return null
57
57
  }
58
58
 
59
- return new FastForward(auto, batchToHead(verified.op.views.system))
59
+ return new FastForward(auto, batchToHead(verified.op.views.system), {
60
+ timeout: timeout || DEFAULT_OP_TIMEOUT
61
+ })
60
62
  }
61
63
 
62
64
  static async fromHeads(auto, heads, { force = false, timeout = 0 } = {}) {
@@ -107,7 +109,7 @@ class FastForward {
107
109
  candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)
108
110
 
109
111
  for (const res of candidates) {
110
- const trusted = await FastForward.mostRecentTrusted(auto, res, reference)
112
+ const trusted = await FastForward.mostRecentTrusted(auto, res, reference, timeout)
111
113
  if (trusted === null) continue
112
114
  if (auto.fastForwarding || auto.fastForwardTo) return null
113
115
 
@@ -130,8 +132,8 @@ class FastForward {
130
132
  }
131
133
  }
132
134
 
133
- static async mostRecentTrusted(auto, head, reference) {
134
- const opened = auto.openViewAt(head)
135
+ static async mostRecentTrusted(auto, head, reference, timeout) {
136
+ const opened = auto.openViewAt(head, { timeout })
135
137
  if (opened === null) return null
136
138
 
137
139
  try {
@@ -206,7 +208,7 @@ class FastForward {
206
208
  _startWarmup() {
207
209
  if (!this.auto._warmup || !this.system.view.length) return false
208
210
 
209
- this.warmupView = this.auto.openView(this.system.view)
211
+ this.warmupView = this.auto.openView(this.system.view, { timeout: this.timeout })
210
212
 
211
213
  // ensure thenable
212
214
  this.warmup = Promise.resolve(this.auto._warmup(this.warmupView.view))
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) {
@@ -693,7 +702,7 @@ class Writer {
693
702
  return this.pending !== null
694
703
  }
695
704
 
696
- append(value, timestamp, batch, links, optimistic, witness, approvals = null) {
705
+ append(value, timestamp, batch, links, optimistic, witness, approvals = null, hash = null) {
697
706
  if (this.pending === null) this.pending = []
698
707
 
699
708
  const oplog = {
@@ -706,7 +715,8 @@ class Writer {
706
715
  witness: witness || null,
707
716
  approvals,
708
717
  optimistic: optimistic && !this.writers.writable,
709
- value
718
+ value,
719
+ hash
710
720
  }
711
721
 
712
722
  const node = createNode(this.core, this.appendLength++, -1, oplog)
@@ -783,7 +793,8 @@ function createNode(core, length, weight, oplog) {
783
793
  witness: oplog.witness || null,
784
794
  approvals: oplog.approvals || null,
785
795
  optimistic: oplog.optimistic,
786
- value: oplog.value
796
+ value: oplog.value,
797
+ hash: oplog.hash || null
787
798
  }
788
799
  }
789
800
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.6.0",
3
+ "version": "2.7.1",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",
@@ -27,7 +27,7 @@
27
27
  "autobee-wakeup": "^1.1.3",
28
28
  "b4a": "^1.8.0",
29
29
  "compact-encoding": "^3.3.0",
30
- "hyperbee2": "^2.10.3",
30
+ "hyperbee2": "^2.16.0",
31
31
  "hypercore": "^11.33.2",
32
32
  "hypercore-id-encoding": "^1.3.0",
33
33
  "hyperschema": "^1.20.1",
@@ -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",