autobee 2.0.0-rc.8 → 2.0.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 +48 -1
- package/encoding/compat.js +1 -1
- package/encoding/legacy.js +33 -8
- package/encoding/spec/autobee/index.js +312 -25
- package/encoding/spec/autobee/schema.json +228 -2
- package/index.js +555 -310
- package/lib/apply-calls.js +5 -6
- package/lib/apply-view.js +25 -0
- package/lib/boot.js +14 -182
- package/lib/constants.js +3 -6
- package/lib/encoding.js +72 -3
- package/lib/fast-forward.js +346 -0
- package/lib/migrations.js +457 -0
- package/lib/promotions.js +164 -0
- package/lib/system.js +97 -179
- package/lib/topo.js +110 -52
- package/lib/trusted.js +44 -0
- package/lib/updates.js +1 -38
- package/lib/witness.js +34 -65
- package/lib/writers.js +180 -116
- package/package.json +5 -4
- package/lib/reboot.js +0 -238
package/lib/topo.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const b4a = require('b4a')
|
|
2
2
|
const encoding = require('./encoding.js')
|
|
3
|
-
const { createNode } = require('./writers.js')
|
|
3
|
+
const { createNode, finalizeNodeBatch } = require('./writers.js')
|
|
4
4
|
const asserts = require('./asserts.js')
|
|
5
|
+
const { LEGACY_OPLOG_VERSION } = require('./constants.js')
|
|
5
6
|
|
|
6
7
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
7
8
|
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
|
|
8
9
|
|
|
10
|
+
const DEFAULT_OP_TIMEOUT = 5000
|
|
11
|
+
|
|
9
12
|
exports.sort = sort
|
|
10
|
-
exports.rollback = rollback
|
|
11
13
|
exports.replay = replay
|
|
12
14
|
exports.getMostRecentHead = getMostRecentHead
|
|
13
15
|
|
|
@@ -36,47 +38,34 @@ class Clock {
|
|
|
36
38
|
async function getMostRecentHead(auto, system) {
|
|
37
39
|
const ch = system.createChangesStream()
|
|
38
40
|
for await (const data of ch) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
}
|
|
41
|
+
const refs = getOplogCompat(data)
|
|
42
|
+
if (!refs.length) continue
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const ch = system.createChangesStream()
|
|
47
|
-
|
|
48
|
-
for await (const data of ch) {
|
|
49
|
-
const { ref, info } = getOplog(data)
|
|
50
|
-
if (sameNode(ref.oplog, verified.op)) {
|
|
51
|
-
asserts.assert(info.flushes === verified.flushes, 'Expect tip to be continuous')
|
|
52
|
-
break
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
tip.push(ref)
|
|
44
|
+
const { oplog } = refs[0]
|
|
45
|
+
return { key: oplog.key, length: oplog.length }
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// @todo: verify sort
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
undo: null,
|
|
64
|
-
view: null,
|
|
65
|
-
tip: inflated.map(getBatch)
|
|
66
|
-
}
|
|
48
|
+
return null
|
|
67
49
|
}
|
|
68
50
|
|
|
51
|
+
// nodes we cannot resolve are returned as null rather than dropped, so callers
|
|
52
|
+
// can tell an incomplete replay from a short one
|
|
69
53
|
async function replay(auto) {
|
|
70
|
-
const ch = auto.system.bee.createChangesStream()
|
|
71
54
|
const replay = []
|
|
72
55
|
|
|
73
|
-
for await (const
|
|
74
|
-
|
|
75
|
-
|
|
56
|
+
for await (const entry of oplogIterator(auto.system.bee)) {
|
|
57
|
+
// the walk was cut short, nothing below this point is reachable
|
|
58
|
+
if (entry === null) {
|
|
59
|
+
replay.push(null)
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const ref of entry.refs) replay.push(ref)
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
66
|
+
// same reasoning as the iterator - never block on what we do not have
|
|
67
|
+
const inflated = await inflate(auto, replay.reverse(), { wait: false })
|
|
68
|
+
return inflated.flatMap((b) => (b === null ? [null] : b.batch))
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
async function sort(auto, batch) {
|
|
@@ -87,7 +76,7 @@ async function sort(auto, batch) {
|
|
|
87
76
|
for await (const data of ch) {
|
|
88
77
|
if (isLegacyNode(data)) break
|
|
89
78
|
|
|
90
|
-
const { ref
|
|
79
|
+
const { ref } = getOplog(data)
|
|
91
80
|
const link = {
|
|
92
81
|
key: ref.oplog.key,
|
|
93
82
|
length: ref.oplog.length,
|
|
@@ -129,8 +118,7 @@ function addSorted(list, entry) {
|
|
|
129
118
|
clock.add(link)
|
|
130
119
|
}
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
if (node.witness) clock.add(node.witness.backer)
|
|
121
|
+
if (node.witness) clock.add(node.witness.link)
|
|
134
122
|
|
|
135
123
|
const tip = []
|
|
136
124
|
const linked = []
|
|
@@ -193,12 +181,21 @@ function cmp(a, b) {
|
|
|
193
181
|
return a.length < b.length ? -1 : 1
|
|
194
182
|
}
|
|
195
183
|
|
|
196
|
-
async function inflate(auto, reorder) {
|
|
184
|
+
async function inflate(auto, reorder, { wait = true, timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
185
|
+
const readOpts = wait ? { timeout } : { wait: false }
|
|
186
|
+
|
|
197
187
|
const promises = []
|
|
198
188
|
const sessions = new Map()
|
|
199
189
|
|
|
200
190
|
for (let i = 0; i < reorder.length; i++) {
|
|
201
191
|
const ref = reorder[i]
|
|
192
|
+
|
|
193
|
+
// marker for a node we could not resolve, pass it through
|
|
194
|
+
if (ref === null) {
|
|
195
|
+
promises.push(null)
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
202
199
|
const id = b4a.toString(ref.oplog.key, 'hex')
|
|
203
200
|
// only seeds the node - reapplication re-resolves from the witness
|
|
204
201
|
const weight = ref.oplog.weight
|
|
@@ -215,7 +212,7 @@ async function inflate(auto, reorder) {
|
|
|
215
212
|
sessions.set(id, core)
|
|
216
213
|
}
|
|
217
214
|
|
|
218
|
-
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i))
|
|
215
|
+
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, readOpts))
|
|
219
216
|
}
|
|
220
217
|
|
|
221
218
|
try {
|
|
@@ -225,11 +222,27 @@ async function inflate(auto, reorder) {
|
|
|
225
222
|
}
|
|
226
223
|
}
|
|
227
224
|
|
|
228
|
-
async function getOplogBatch(undo, core, length, weight, index) {
|
|
225
|
+
async function getOplogBatch(undo, core, length, weight, index, readOpts = null) {
|
|
229
226
|
const seq = length - 1
|
|
230
|
-
|
|
227
|
+
|
|
228
|
+
const block = await core.get(seq, readOpts)
|
|
229
|
+
if (block === null) return null
|
|
230
|
+
|
|
231
231
|
const oplog = encoding.decodeOplog(block)
|
|
232
232
|
const head = createNode(core, length, weight, oplog)
|
|
233
|
+
const legacy = oplog.version <= LEGACY_OPLOG_VERSION
|
|
234
|
+
|
|
235
|
+
// if this is legacy the start pointer is always 0, fix
|
|
236
|
+
if (legacy) {
|
|
237
|
+
for (let i = length - 1; i > 0; i--) {
|
|
238
|
+
const block = await core.get(i - 1, readOpts)
|
|
239
|
+
if (block === null) return null // wait: false in replay helper
|
|
240
|
+
|
|
241
|
+
const oplog = encoding.decodeOplog(block)
|
|
242
|
+
if (oplog.batch.end === 0) break
|
|
243
|
+
head.batch.start++
|
|
244
|
+
}
|
|
245
|
+
}
|
|
233
246
|
|
|
234
247
|
const batch = []
|
|
235
248
|
const result = { undo, batch, index }
|
|
@@ -239,18 +252,26 @@ async function getOplogBatch(undo, core, length, weight, index) {
|
|
|
239
252
|
const end = seq + head.batch.end // skip head, we have it
|
|
240
253
|
|
|
241
254
|
for (let i = start; i < end; i++) {
|
|
242
|
-
remaining.push(core.get(i))
|
|
255
|
+
remaining.push(core.get(i, readOpts))
|
|
243
256
|
}
|
|
244
257
|
|
|
245
258
|
if (remaining.length) {
|
|
246
259
|
const blocks = await Promise.all(remaining)
|
|
247
260
|
for (let i = 0; i < blocks.length; i++) {
|
|
261
|
+
// keep the hole so the gap in the batch stays visible
|
|
262
|
+
if (blocks[i] === null) {
|
|
263
|
+
batch.push(null)
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
|
|
248
267
|
const oplog = encoding.decodeOplog(blocks[i])
|
|
249
268
|
batch.push(createNode(core, i + start + 1, weight, oplog))
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
271
|
|
|
253
272
|
batch.push(head)
|
|
273
|
+
finalizeNodeBatch(batch)
|
|
274
|
+
|
|
254
275
|
return result
|
|
255
276
|
}
|
|
256
277
|
|
|
@@ -259,7 +280,9 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
259
280
|
|
|
260
281
|
if (length > writer.core.length) {
|
|
261
282
|
if (!writer.pending) {
|
|
262
|
-
throw new Error(
|
|
283
|
+
throw new Error(
|
|
284
|
+
`No local nodes: looking for ${length}, found ${writer.core.length}@${writer.core.id}`
|
|
285
|
+
)
|
|
263
286
|
}
|
|
264
287
|
|
|
265
288
|
let i = 0
|
|
@@ -269,9 +292,10 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
269
292
|
}
|
|
270
293
|
|
|
271
294
|
const head = writer.pending[i]
|
|
295
|
+
const headBatch = head.batch || { start: 0, end: 0 }
|
|
272
296
|
|
|
273
|
-
const start = i -
|
|
274
|
-
const end = i +
|
|
297
|
+
const start = i - headBatch.start
|
|
298
|
+
const end = i + headBatch.end + 1 // include head
|
|
275
299
|
|
|
276
300
|
const batch = writer.pending.slice(start, end)
|
|
277
301
|
return { undo, batch, index }
|
|
@@ -280,6 +304,19 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
280
304
|
return getOplogBatch(undo, writer.core, length, weight, index)
|
|
281
305
|
}
|
|
282
306
|
|
|
307
|
+
// tolerates legacy nodes instead of asserting
|
|
308
|
+
async function* oplogIterator(bee) {
|
|
309
|
+
try {
|
|
310
|
+
for await (const node of bee.createChangesStream({ wait: false })) {
|
|
311
|
+
const legacy = isLegacyNode(node)
|
|
312
|
+
const refs = getOplogCompat(node)
|
|
313
|
+
if (refs.length) yield { node, refs, legacy }
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
yield null // signal data not available
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
283
320
|
function getOplog(data) {
|
|
284
321
|
const result = { ref: null, info: null }
|
|
285
322
|
for (const { keys } of data.batch) {
|
|
@@ -305,6 +342,35 @@ function getOplog(data) {
|
|
|
305
342
|
asserts.bail('Bad system node')
|
|
306
343
|
}
|
|
307
344
|
|
|
345
|
+
// legacy nodes carry no isOplog flag so we cannot tell which writer update is
|
|
346
|
+
// the oplog to fetch - yield every candidate until we have a better signal
|
|
347
|
+
function getOplogCompat(data) {
|
|
348
|
+
const undo = data.tail || EMPTY_HEAD
|
|
349
|
+
const oplogs = []
|
|
350
|
+
const candidates = []
|
|
351
|
+
|
|
352
|
+
for (const { keys } of data.batch) {
|
|
353
|
+
for (const k of keys) {
|
|
354
|
+
if (k.key[0] !== 1) continue
|
|
355
|
+
|
|
356
|
+
let value = null
|
|
357
|
+
try {
|
|
358
|
+
value = encoding.decodeSystemWriter(k.key, k.value)
|
|
359
|
+
} catch {
|
|
360
|
+
continue
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// writer was just added, nothing to fetch
|
|
364
|
+
if (!value.length) continue
|
|
365
|
+
|
|
366
|
+
if (value.isOplog) oplogs.push({ undo, oplog: value })
|
|
367
|
+
else candidates.push({ undo, oplog: value })
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return oplogs.length ? oplogs : candidates
|
|
372
|
+
}
|
|
373
|
+
|
|
308
374
|
function isLinking(node, link) {
|
|
309
375
|
if (node.length >= link.length && b4a.equals(node.key, link.key)) {
|
|
310
376
|
return true
|
|
@@ -335,11 +401,3 @@ function isLegacyNode(data) {
|
|
|
335
401
|
}
|
|
336
402
|
return false
|
|
337
403
|
}
|
|
338
|
-
|
|
339
|
-
function sameNode(a, b) {
|
|
340
|
-
return b4a.equals(a.key, b.key) && a.length === b.length
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function getBatch(b) {
|
|
344
|
-
return b.batch
|
|
345
|
-
}
|
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/witness.js
CHANGED
|
@@ -1,94 +1,63 @@
|
|
|
1
1
|
const b4a = require('b4a')
|
|
2
|
-
const
|
|
3
|
-
const crypto = require('hypercore-crypto')
|
|
4
|
-
const Hypercore = require('hypercore')
|
|
5
|
-
const { NS_WITNESS } = require('./constants.js')
|
|
2
|
+
const encoding = require('./encoding.js')
|
|
6
3
|
|
|
7
4
|
exports.currentWeight = currentWeight
|
|
8
5
|
exports.resolveWeight = resolveWeight
|
|
9
|
-
exports.
|
|
10
|
-
exports.attestationMessage = attestationMessage
|
|
11
|
-
exports.signAttestation = signAttestation
|
|
6
|
+
exports.verifyClaim = verifyClaim
|
|
12
7
|
|
|
13
|
-
// resolved current weight of a writer
|
|
14
8
|
function currentWeight(rec) {
|
|
15
9
|
if (!rec) return 0
|
|
16
10
|
if (rec.maxWeight > 0 && rec.isGenesis) return rec.maxWeight
|
|
17
11
|
return rec.weight
|
|
18
12
|
}
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
function seedWeight(auto, node) {
|
|
15
|
+
if (node.length === 1 && b4a.equals(node.key, auto.key)) return auto.bootstrapWeight
|
|
16
|
+
return 0
|
|
17
|
+
}
|
|
18
|
+
|
|
21
19
|
async function resolveWeight(auto, node) {
|
|
22
20
|
const rec = await auto.system.get(node.key)
|
|
23
|
-
const prev = currentWeight(rec)
|
|
21
|
+
const prev = rec ? currentWeight(rec) : seedWeight(auto, node)
|
|
24
22
|
|
|
25
23
|
const witness = node.witness
|
|
26
|
-
if (!witness
|
|
24
|
+
if (!witness) return prev
|
|
27
25
|
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
26
|
+
const { weight, link } = witness
|
|
27
|
+
if (weight <= prev) return prev
|
|
28
|
+
if (!(await verifyClaim(auto, node.key, weight, link))) return prev
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
return weight
|
|
31
|
+
}
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
if (!
|
|
33
|
+
async function verifyClaim(auto, key, weight, link) {
|
|
34
|
+
if (!link || !link.length) return false
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
const anchor = await readOplogNode(auto, link.key, link.length)
|
|
37
|
+
const entry = anchor.approvals ? anchor.approvals.find((a) => b4a.equals(a.key, key)) : null
|
|
38
|
+
if (!entry || entry.weight < weight) return false
|
|
38
39
|
|
|
39
|
-
return
|
|
40
|
+
return approverQualified(auto, link.key, entry.weight)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// signer public key read out of it, and the signature checked against that
|
|
48
|
-
function verifyWitness(node) {
|
|
49
|
-
const witness = node.witness
|
|
50
|
-
if (!witness || !witness.backer || witness.weight === 0) return false
|
|
51
|
-
if (b4a.equals(witness.backer.key, node.key)) return false
|
|
52
|
-
|
|
53
|
-
const publicKey = signerFromRawManifest(witness.backer.key, witness.backer.manifest)
|
|
54
|
-
if (!publicKey) return false
|
|
43
|
+
// the approval sits in the causal past of any citation of it, so the approver's
|
|
44
|
+
// record is lower-bounded by the standing it held when it approved - no need to
|
|
45
|
+
// walk its own witness chain back to genesis
|
|
46
|
+
async function approverQualified(auto, key, weight) {
|
|
47
|
+
if (b4a.equals(key, auto.key)) return auto.bootstrapWeight >= weight
|
|
55
48
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
node.key,
|
|
59
|
-
witness.backer.length,
|
|
60
|
-
witness.weight
|
|
61
|
-
)
|
|
62
|
-
return crypto.verify(message, witness.backer.signature, publicKey)
|
|
49
|
+
const rec = await auto.system.get(key)
|
|
50
|
+
return currentWeight(rec) >= weight
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
function
|
|
66
|
-
|
|
53
|
+
async function readOplogNode(auto, key, length) {
|
|
54
|
+
const core = auto.openCore(key)
|
|
67
55
|
|
|
68
|
-
let manifest
|
|
69
56
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
await core.ready()
|
|
58
|
+
const block = await core.get(length - 1)
|
|
59
|
+
return encoding.decodeOplog(block)
|
|
60
|
+
} finally {
|
|
61
|
+
await core.close()
|
|
74
62
|
}
|
|
75
|
-
|
|
76
|
-
// single-signer backers only: an attestation is one writer's testimony
|
|
77
|
-
if (!manifest || !manifest.signers || manifest.signers.length !== 1) return null
|
|
78
|
-
|
|
79
|
-
return manifest.signers[0].publicKey
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function attestationMessage(backerKey, key, length, weight) {
|
|
83
|
-
return b4a.concat([
|
|
84
|
-
NS_WITNESS,
|
|
85
|
-
backerKey,
|
|
86
|
-
key,
|
|
87
|
-
c.encode(c.uint, length),
|
|
88
|
-
c.encode(c.uint, weight)
|
|
89
|
-
])
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function signAttestation(secretKey, backerKey, key, length, weight) {
|
|
93
|
-
return crypto.sign(attestationMessage(backerKey, key, length, weight), secretKey)
|
|
94
63
|
}
|