autobee 2.0.0-rc.9 → 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/legacy.js +30 -5
- package/encoding/spec/autobee/index.js +289 -10
- package/encoding/spec/autobee/schema.json +217 -1
- package/index.js +548 -321
- 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 +83 -88
- 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 +4 -3
- 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,34 +38,14 @@ 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
|
-
}
|
|
43
|
-
|
|
44
|
-
async function rollback(auto, system, verified) {
|
|
45
|
-
const tip = []
|
|
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
|
-
}
|
|
41
|
+
const refs = getOplogCompat(data)
|
|
42
|
+
if (!refs.length) continue
|
|
54
43
|
|
|
55
|
-
|
|
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
|
|
|
69
51
|
// nodes we cannot resolve are returned as null rather than dropped, so callers
|
|
@@ -86,48 +68,6 @@ async function replay(auto) {
|
|
|
86
68
|
return inflated.flatMap((b) => (b === null ? [null] : b.batch))
|
|
87
69
|
}
|
|
88
70
|
|
|
89
|
-
// tolerates legacy nodes instead of asserting
|
|
90
|
-
async function* oplogIterator(bee) {
|
|
91
|
-
try {
|
|
92
|
-
for await (const node of bee.createChangesStream({ wait: false })) {
|
|
93
|
-
const legacy = isLegacyNode(node)
|
|
94
|
-
const refs = getOplogs(node)
|
|
95
|
-
if (refs.length) yield { node, refs, legacy }
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
yield null // signal data not available
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// legacy nodes carry no isOplog flag so we cannot tell which writer update is
|
|
103
|
-
// the oplog to fetch - yield every candidate until we have a better signal
|
|
104
|
-
function getOplogs(data) {
|
|
105
|
-
const undo = data.tail || EMPTY_HEAD
|
|
106
|
-
const oplogs = []
|
|
107
|
-
const candidates = []
|
|
108
|
-
|
|
109
|
-
for (const { keys } of data.batch) {
|
|
110
|
-
for (const k of keys) {
|
|
111
|
-
if (k.key[0] !== 1) continue
|
|
112
|
-
|
|
113
|
-
let value = null
|
|
114
|
-
try {
|
|
115
|
-
value = encoding.decodeSystemWriter(k.key, k.value)
|
|
116
|
-
} catch {
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// writer was just added, nothing to fetch
|
|
121
|
-
if (!value.length) continue
|
|
122
|
-
|
|
123
|
-
if (value.isOplog) oplogs.push({ undo, oplog: value })
|
|
124
|
-
else candidates.push({ undo, oplog: value })
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return oplogs.length ? oplogs : candidates
|
|
129
|
-
}
|
|
130
|
-
|
|
131
71
|
async function sort(auto, batch) {
|
|
132
72
|
const node = batch[0]
|
|
133
73
|
const ch = auto.system.bee.createChangesStream()
|
|
@@ -136,7 +76,7 @@ async function sort(auto, batch) {
|
|
|
136
76
|
for await (const data of ch) {
|
|
137
77
|
if (isLegacyNode(data)) break
|
|
138
78
|
|
|
139
|
-
const { ref
|
|
79
|
+
const { ref } = getOplog(data)
|
|
140
80
|
const link = {
|
|
141
81
|
key: ref.oplog.key,
|
|
142
82
|
length: ref.oplog.length,
|
|
@@ -178,8 +118,7 @@ function addSorted(list, entry) {
|
|
|
178
118
|
clock.add(link)
|
|
179
119
|
}
|
|
180
120
|
|
|
181
|
-
|
|
182
|
-
if (node.witness) clock.add(node.witness.backer)
|
|
121
|
+
if (node.witness) clock.add(node.witness.link)
|
|
183
122
|
|
|
184
123
|
const tip = []
|
|
185
124
|
const linked = []
|
|
@@ -242,7 +181,9 @@ function cmp(a, b) {
|
|
|
242
181
|
return a.length < b.length ? -1 : 1
|
|
243
182
|
}
|
|
244
183
|
|
|
245
|
-
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
|
+
|
|
246
187
|
const promises = []
|
|
247
188
|
const sessions = new Map()
|
|
248
189
|
|
|
@@ -271,7 +212,7 @@ async function inflate(auto, reorder, opts = null) {
|
|
|
271
212
|
sessions.set(id, core)
|
|
272
213
|
}
|
|
273
214
|
|
|
274
|
-
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i,
|
|
215
|
+
promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, readOpts))
|
|
275
216
|
}
|
|
276
217
|
|
|
277
218
|
try {
|
|
@@ -281,12 +222,27 @@ async function inflate(auto, reorder, opts = null) {
|
|
|
281
222
|
}
|
|
282
223
|
}
|
|
283
224
|
|
|
284
|
-
async function getOplogBatch(undo, core, length, weight, index,
|
|
225
|
+
async function getOplogBatch(undo, core, length, weight, index, readOpts = null) {
|
|
285
226
|
const seq = length - 1
|
|
286
|
-
|
|
227
|
+
|
|
228
|
+
const block = await core.get(seq, readOpts)
|
|
287
229
|
if (block === null) return null
|
|
230
|
+
|
|
288
231
|
const oplog = encoding.decodeOplog(block)
|
|
289
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
|
+
}
|
|
290
246
|
|
|
291
247
|
const batch = []
|
|
292
248
|
const result = { undo, batch, index }
|
|
@@ -296,7 +252,7 @@ async function getOplogBatch(undo, core, length, weight, index, opts = null) {
|
|
|
296
252
|
const end = seq + head.batch.end // skip head, we have it
|
|
297
253
|
|
|
298
254
|
for (let i = start; i < end; i++) {
|
|
299
|
-
remaining.push(core.get(i,
|
|
255
|
+
remaining.push(core.get(i, readOpts))
|
|
300
256
|
}
|
|
301
257
|
|
|
302
258
|
if (remaining.length) {
|
|
@@ -314,6 +270,8 @@ async function getOplogBatch(undo, core, length, weight, index, opts = null) {
|
|
|
314
270
|
}
|
|
315
271
|
|
|
316
272
|
batch.push(head)
|
|
273
|
+
finalizeNodeBatch(batch)
|
|
274
|
+
|
|
317
275
|
return result
|
|
318
276
|
}
|
|
319
277
|
|
|
@@ -322,7 +280,9 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
322
280
|
|
|
323
281
|
if (length > writer.core.length) {
|
|
324
282
|
if (!writer.pending) {
|
|
325
|
-
throw new Error(
|
|
283
|
+
throw new Error(
|
|
284
|
+
`No local nodes: looking for ${length}, found ${writer.core.length}@${writer.core.id}`
|
|
285
|
+
)
|
|
326
286
|
}
|
|
327
287
|
|
|
328
288
|
let i = 0
|
|
@@ -332,9 +292,10 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
332
292
|
}
|
|
333
293
|
|
|
334
294
|
const head = writer.pending[i]
|
|
295
|
+
const headBatch = head.batch || { start: 0, end: 0 }
|
|
335
296
|
|
|
336
|
-
const start = i -
|
|
337
|
-
const end = i +
|
|
297
|
+
const start = i - headBatch.start
|
|
298
|
+
const end = i + headBatch.end + 1 // include head
|
|
338
299
|
|
|
339
300
|
const batch = writer.pending.slice(start, end)
|
|
340
301
|
return { undo, batch, index }
|
|
@@ -343,6 +304,19 @@ function getLocalBatch(undo, writer, length, weight, index) {
|
|
|
343
304
|
return getOplogBatch(undo, writer.core, length, weight, index)
|
|
344
305
|
}
|
|
345
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
|
+
|
|
346
320
|
function getOplog(data) {
|
|
347
321
|
const result = { ref: null, info: null }
|
|
348
322
|
for (const { keys } of data.batch) {
|
|
@@ -368,6 +342,35 @@ function getOplog(data) {
|
|
|
368
342
|
asserts.bail('Bad system node')
|
|
369
343
|
}
|
|
370
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
|
+
|
|
371
374
|
function isLinking(node, link) {
|
|
372
375
|
if (node.length >= link.length && b4a.equals(node.key, link.key)) {
|
|
373
376
|
return true
|
|
@@ -398,11 +401,3 @@ function isLegacyNode(data) {
|
|
|
398
401
|
}
|
|
399
402
|
return false
|
|
400
403
|
}
|
|
401
|
-
|
|
402
|
-
function sameNode(a, b) {
|
|
403
|
-
return b4a.equals(a.key, b.key) && a.length === b.length
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function getBatch(b) {
|
|
407
|
-
return b.batch
|
|
408
|
-
}
|
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
|
}
|