@typeberry/lib 0.8.3 → 0.8.4-70b1490
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/package.json +6 -4
- package/packages/configs/index.d.ts +30 -1
- package/packages/configs/index.d.ts.map +1 -1
- package/packages/configs/index.js +4 -2
- package/packages/configs/typeberry-dev-full.json +29 -0
- package/packages/core/bytes/bytes.d.ts +1 -0
- package/packages/core/bytes/bytes.d.ts.map +1 -1
- package/packages/core/bytes/bytes.js +8 -0
- package/packages/core/utils/debug.d.ts +16 -4
- package/packages/core/utils/debug.d.ts.map +1 -1
- package/packages/core/utils/debug.js +39 -17
- package/packages/core/utils/debug.test.js +12 -6
- package/packages/jam/config-node/node-config.d.ts +2 -1
- package/packages/jam/config-node/node-config.d.ts.map +1 -1
- package/packages/jam/config-node/node-config.js +8 -3
- package/packages/jam/config-node/node-config.test.js +3 -3
- package/packages/jam/database/states.d.ts +7 -0
- package/packages/jam/database/states.d.ts.map +1 -1
- package/packages/jam/database-fjall/hybrid-states.d.ts +45 -0
- package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -0
- package/packages/jam/database-fjall/hybrid-states.js +113 -0
- package/packages/jam/database-fjall/hybrid-states.test.d.ts +2 -0
- package/packages/jam/database-fjall/hybrid-states.test.d.ts.map +1 -0
- package/packages/jam/database-fjall/hybrid-states.test.js +83 -0
- package/packages/jam/database-fjall/index.d.ts +3 -0
- package/packages/jam/database-fjall/index.d.ts.map +1 -0
- package/packages/jam/database-fjall/index.js +2 -0
- package/packages/jam/database-fjall/root.d.ts +52 -0
- package/packages/jam/database-fjall/root.d.ts.map +1 -0
- package/packages/jam/database-fjall/root.js +85 -0
- package/packages/jam/database-lmdb/hybrid-states.d.ts +3 -1
- package/packages/jam/database-lmdb/hybrid-states.d.ts.map +1 -1
- package/packages/jam/database-lmdb/hybrid-states.js +5 -2
- package/packages/jam/database-lmdb/root.d.ts +14 -1
- package/packages/jam/database-lmdb/root.d.ts.map +1 -1
- package/packages/jam/database-lmdb/root.js +25 -5
- package/packages/jam/database-lmdb/states.d.ts +1 -0
- package/packages/jam/database-lmdb/states.d.ts.map +1 -1
- package/packages/jam/database-lmdb/states.js +3 -0
- package/packages/jam/database-lmdb/states.test.js +4 -4
- package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts +18 -10
- package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts.map +1 -1
- package/packages/jam/jamnp-s/tasks/ticket-distribution.js +44 -68
- package/packages/jam/jamnp-s/tasks/ticket-distribution.test.js +30 -8
- package/packages/jam/node/main-fuzz.d.ts.map +1 -1
- package/packages/jam/node/main-fuzz.js +21 -4
- package/packages/jam/node/main-importer.d.ts +7 -4
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +10 -4
- package/packages/jam/safrole/bandersnatch-vrf.d.ts +24 -4
- package/packages/jam/safrole/bandersnatch-vrf.d.ts.map +1 -1
- package/packages/jam/safrole/bandersnatch-vrf.js +92 -40
- package/packages/jam/safrole/bandersnatch-vrf.test.js +12 -9
- package/packages/jam/safrole/bandersnatch-wasm.d.ts +10 -0
- package/packages/jam/safrole/bandersnatch-wasm.d.ts.map +1 -1
- package/packages/jam/safrole/bandersnatch-wasm.js +12 -0
- package/packages/jam/safrole/safrole.js +5 -5
- package/packages/jam/safrole/safrole.test.js +13 -13
- package/packages/jam/ticket-pool/index.d.ts +4 -0
- package/packages/jam/ticket-pool/index.d.ts.map +1 -0
- package/packages/jam/ticket-pool/index.js +3 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.d.ts +30 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.d.ts.map +1 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.js +56 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.test.d.ts +2 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.test.d.ts.map +1 -0
- package/packages/jam/ticket-pool/pending-ticket-pool.test.js +67 -0
- package/packages/jam/ticket-pool/ticket-validator.d.ts +47 -0
- package/packages/jam/ticket-pool/ticket-validator.d.ts.map +1 -0
- package/packages/jam/ticket-pool/ticket-validator.js +34 -0
- package/packages/jam/ticket-pool/ticket-validator.test.d.ts +2 -0
- package/packages/jam/ticket-pool/ticket-validator.test.d.ts.map +1 -0
- package/packages/jam/ticket-pool/ticket-validator.test.js +35 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.d.ts +26 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.d.ts.map +1 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.js +41 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.d.ts +2 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.d.ts.map +1 -0
- package/packages/jam/ticket-pool/verified-ticket-pool.test.js +54 -0
- package/packages/jam/transition/chain-stf.d.ts +2 -1
- package/packages/jam/transition/chain-stf.d.ts.map +1 -1
- package/packages/jam/transition/chain-stf.js +15 -3
- package/packages/workers/api-node/config.d.ts +14 -5
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +29 -20
- package/packages/workers/api-node/config.test.js +38 -1
- package/packages/workers/block-authorship/{generator.d.ts → block-generator.d.ts} +5 -5
- package/packages/workers/block-authorship/block-generator.d.ts.map +1 -0
- package/packages/workers/block-authorship/{generator.js → block-generator.js} +3 -3
- package/packages/workers/block-authorship/block-generator.test.d.ts +2 -0
- package/packages/workers/block-authorship/block-generator.test.d.ts.map +1 -0
- package/packages/workers/block-authorship/{generator.test.js → block-generator.test.js} +8 -8
- package/packages/workers/block-authorship/epoch-authoring-slots.d.ts +35 -0
- package/packages/workers/block-authorship/epoch-authoring-slots.d.ts.map +1 -0
- package/packages/workers/block-authorship/epoch-authoring-slots.js +86 -0
- package/packages/workers/block-authorship/epoch-tracker.d.ts +29 -0
- package/packages/workers/block-authorship/epoch-tracker.d.ts.map +1 -0
- package/packages/workers/block-authorship/epoch-tracker.js +80 -0
- package/packages/workers/block-authorship/index.d.ts.map +1 -1
- package/packages/workers/block-authorship/index.js +1 -1
- package/packages/workers/block-authorship/main.d.ts +3 -0
- package/packages/workers/block-authorship/main.d.ts.map +1 -1
- package/packages/workers/block-authorship/main.js +197 -315
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts +2 -0
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/bootstrap-main.js +23 -0
- package/packages/workers/block-authorship/ticket-generator/index.d.ts +16 -0
- package/packages/workers/block-authorship/ticket-generator/index.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/index.js +62 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.d.ts +50 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/protocol.js +54 -0
- package/packages/workers/block-authorship/{ticket-generator.d.ts → ticket-generator/ticket-generator.d.ts} +4 -0
- package/packages/workers/block-authorship/ticket-generator/ticket-generator.d.ts.map +1 -0
- package/packages/workers/block-authorship/{ticket-generator.js → ticket-generator/ticket-generator.js} +19 -9
- package/packages/workers/block-authorship/ticket-generator/ticket-generator.test.d.ts.map +1 -0
- package/packages/workers/block-authorship/{ticket-generator.test.js → ticket-generator/ticket-generator.test.js} +13 -9
- package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts +36 -0
- package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-generator/worker-pool.js +111 -0
- package/packages/workers/block-authorship/ticket-validator.d.ts +31 -0
- package/packages/workers/block-authorship/ticket-validator.d.ts.map +1 -0
- package/packages/workers/block-authorship/ticket-validator.js +59 -0
- package/packages/workers/comms-authorship-network/protocol.d.ts +14 -4
- package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/protocol.js +12 -6
- package/packages/workers/comms-authorship-network/tickets-message.d.ts +0 -14
- package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/tickets-message.js +0 -17
- package/packages/workers/importer/importer.d.ts +5 -3
- package/packages/workers/importer/importer.d.ts.map +1 -1
- package/packages/workers/importer/importer.js +43 -35
- package/packages/workers/importer/stats.d.ts +36 -0
- package/packages/workers/importer/stats.d.ts.map +1 -0
- package/packages/workers/importer/stats.js +69 -0
- package/packages/workers/jam-network/main.d.ts.map +1 -1
- package/packages/workers/jam-network/main.js +25 -4
- package/packages/workers/block-authorship/generator.d.ts.map +0 -1
- package/packages/workers/block-authorship/generator.test.d.ts +0 -2
- package/packages/workers/block-authorship/generator.test.d.ts.map +0 -1
- package/packages/workers/block-authorship/ticket-generator.d.ts.map +0 -1
- package/packages/workers/block-authorship/ticket-generator.test.d.ts.map +0 -1
- /package/packages/configs/{typeberry-dev.json → typeberry-dev-tiny.json} +0 -0
- /package/packages/workers/block-authorship/{ticket-generator.test.d.ts → ticket-generator/ticket-generator.test.d.ts} +0 -0
|
@@ -14,6 +14,9 @@ import { mainImporter } from "./main-importer.js";
|
|
|
14
14
|
const logger = Logger.new(import.meta.filename, "fuzztarget");
|
|
15
15
|
/** Dedicated subdirectory under the configured base path that the fuzzer owns and wipes. */
|
|
16
16
|
const FUZZ_DB_SUBDIR = "typeberry-fuzz-db";
|
|
17
|
+
const FUZZ_DB_FJALL = "fjall-hybrid";
|
|
18
|
+
const FUZZ_DB_LMDB = "lmdb-hybrid";
|
|
19
|
+
const FUZZ_DB_OPTIONS = [FUZZ_DB_FJALL, FUZZ_DB_LMDB];
|
|
17
20
|
/**
|
|
18
21
|
* Resolve the directory the fuzzer should use for its on-disk database, or
|
|
19
22
|
* `undefined` for an in-memory database. The dedicated `FUZZ_DB_SUBDIR` is
|
|
@@ -50,6 +53,15 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
|
|
|
50
53
|
logHostEnvironment(logger);
|
|
51
54
|
const { jamNodeConfig: config } = fuzzConfig;
|
|
52
55
|
const fuzzDbBase = resolveFuzzDbBase(config.node.databaseBasePath);
|
|
56
|
+
const rawFuzzDb = process.env.JAM_FUZZ_DB?.trim() ?? "";
|
|
57
|
+
// Using experimental fjall-hybrid by default, with an option to test lmdb as well.
|
|
58
|
+
const hybridStateBackend = rawFuzzDb === "" ? FUZZ_DB_FJALL : rawFuzzDb;
|
|
59
|
+
if (!isValidStateBackend(hybridStateBackend)) {
|
|
60
|
+
throw new Error(`JAM_FUZZ_DB must be one of: ${FUZZ_DB_OPTIONS} (got: "${rawFuzzDb}").`);
|
|
61
|
+
}
|
|
62
|
+
if (fuzzDbBase !== undefined) {
|
|
63
|
+
logger.info `🗄️ Fuzz persistent backend: ${hybridStateBackend}.`;
|
|
64
|
+
}
|
|
53
65
|
let runningNode = null;
|
|
54
66
|
const chainSpec = getChainSpec(config.node.flavor);
|
|
55
67
|
const closeFuzzTarget = startFuzzTarget(fuzzConfig.version, fuzzConfig.socket, {
|
|
@@ -101,10 +113,12 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
|
|
|
101
113
|
// like the in-memory backend; only the large values live on disk.
|
|
102
114
|
dummyFinalityDepth: 20,
|
|
103
115
|
pruneBlocks: true,
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
// Long full-spec sessions accumulate a large, never-pruned values db.
|
|
117
|
+
// Syncing lets the OS reclaim dirty mmap pages, and compression (full
|
|
118
|
+
// spec only, where values are big) bounds its on-disk/page-cache size.
|
|
119
|
+
// Tiny stays uncompressed since its db is small and speed matters more.
|
|
120
|
+
ephemeral: isPersistent,
|
|
121
|
+
stateBackend: isPersistent ? hybridStateBackend : "lmdb",
|
|
108
122
|
});
|
|
109
123
|
};
|
|
110
124
|
if (fuzzDbBase !== undefined) {
|
|
@@ -134,3 +148,6 @@ export async function mainFuzz(fuzzConfig, withRelPath) {
|
|
|
134
148
|
}
|
|
135
149
|
};
|
|
136
150
|
}
|
|
151
|
+
function isValidStateBackend(val) {
|
|
152
|
+
return FUZZ_DB_OPTIONS.indexOf(val) !== -1;
|
|
153
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { JamConfig } from "./jam-config.js";
|
|
2
2
|
import type { NodeApi } from "./main.js";
|
|
3
|
+
export type StateBackend = "lmdb" | "lmdb-hybrid" | "fjall-hybrid";
|
|
3
4
|
export type ImporterOptions = {
|
|
4
5
|
initGenesisFromAncestry?: boolean;
|
|
5
6
|
dummyFinalityDepth?: number;
|
|
6
7
|
pruneBlocks?: boolean;
|
|
7
|
-
/** Open the
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
|
|
8
|
+
/** Open the database without fsync/compression. Only safe for throwaway dbs (e.g. fuzzing). */
|
|
9
|
+
ephemeral?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Persistent backend to use when `databaseBasePath` is set. Defaults to full LMDB.
|
|
12
|
+
*/
|
|
13
|
+
stateBackend?: StateBackend;
|
|
11
14
|
};
|
|
12
15
|
export declare function mainImporter(config: JamConfig, withRelPath: (v: string) => string, options?: ImporterOptions): Promise<NodeApi>;
|
|
13
16
|
//# sourceMappingURL=main-importer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-importer.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-importer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main-importer.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main-importer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,cAAc,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAEF,wBAAsB,YAAY,CAChC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAqGlB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Bytes } from "#@typeberry/bytes";
|
|
2
2
|
import { PvmBackend } from "#@typeberry/config";
|
|
3
|
+
import { KnownChainSpec } from "#@typeberry/config-node";
|
|
3
4
|
import { bandersnatch, initWasm } from "#@typeberry/crypto";
|
|
4
5
|
import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
|
|
5
6
|
import { createImporter, ImporterConfig } from "#@typeberry/importer";
|
|
@@ -28,6 +29,9 @@ export async function mainImporter(config, withRelPath, options = {}) {
|
|
|
28
29
|
dummyFinalityDepth: tryAsU16(options.dummyFinalityDepth ?? 0),
|
|
29
30
|
pruneBlocks: options.pruneBlocks ?? false,
|
|
30
31
|
});
|
|
32
|
+
const ephemeral = options.ephemeral ?? false;
|
|
33
|
+
// enable compression when running full test suite
|
|
34
|
+
const compression = ephemeral && config.node.flavor === KnownChainSpec.Full;
|
|
31
35
|
const workerConfig = dbBackend === "in-memory"
|
|
32
36
|
? InMemWorkerConfig.new({
|
|
33
37
|
nodeName,
|
|
@@ -35,14 +39,16 @@ export async function mainImporter(config, withRelPath, options = {}) {
|
|
|
35
39
|
blake2b,
|
|
36
40
|
workerParams,
|
|
37
41
|
})
|
|
38
|
-
: dbBackend === "hybrid"
|
|
39
|
-
? HybridWorkerConfig.new({
|
|
42
|
+
: dbBackend === "lmdb-hybrid" || dbBackend === "fjall-hybrid"
|
|
43
|
+
? await HybridWorkerConfig.new({
|
|
40
44
|
nodeName,
|
|
41
45
|
chainSpec,
|
|
42
46
|
blake2b,
|
|
43
47
|
dbPath,
|
|
44
48
|
workerParams,
|
|
45
|
-
ephemeral
|
|
49
|
+
ephemeral,
|
|
50
|
+
compression,
|
|
51
|
+
backend: dbBackend === "lmdb-hybrid" ? "lmdb" : "fjall",
|
|
46
52
|
})
|
|
47
53
|
: LmdbWorkerConfig.new({
|
|
48
54
|
nodeName,
|
|
@@ -50,7 +56,7 @@ export async function mainImporter(config, withRelPath, options = {}) {
|
|
|
50
56
|
blake2b,
|
|
51
57
|
dbPath,
|
|
52
58
|
workerParams,
|
|
53
|
-
ephemeral
|
|
59
|
+
ephemeral,
|
|
54
60
|
});
|
|
55
61
|
// Initialize the database with genesis state and block if there isn't one.
|
|
56
62
|
logger.info `🛢️ Opening database at ${dbPath}`;
|
|
@@ -21,13 +21,33 @@ declare function verifySeal(bandersnatch: BandernsatchWasm, authorKey: Bandersna
|
|
|
21
21
|
declare function getRingCommitment(bandersnatch: BandernsatchWasm, validators: BandersnatchKey[]): Promise<Result<BandersnatchRingRoot, null>>;
|
|
22
22
|
declare function verifyTickets(bandersnatch: BandernsatchWasm, numberOfValidators: number, epochRoot: BandersnatchRingRoot, tickets: readonly SignedTicket[], entropy: EntropyHash): Promise<{
|
|
23
23
|
isValid: boolean;
|
|
24
|
-
|
|
25
|
-
}
|
|
24
|
+
tickets: EntropyHash[];
|
|
25
|
+
}>;
|
|
26
26
|
declare function generateSeal(bandersnatch: BandernsatchWasm, authorKey: BandersnatchSecretSeed, input: BytesBlob, auxData: BytesBlob): Promise<Result<BandersnatchVrfSignature, null>>;
|
|
27
27
|
export type VrfOutputHash = Opaque<OpaqueHash, "VRF Output Hash">;
|
|
28
28
|
declare function getVrfOutputHash(bandersnatch: BandernsatchWasm, authorKey: BandersnatchSecretSeed, input: BytesBlob): Promise<Result<VrfOutputHash, null>>;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Batch-generate signed tickets for multiple validators in a single native call,
|
|
31
|
+
* reusing the ring prover setup across all of them. Returns one ticket list per
|
|
32
|
+
* validator, in the same order as `proverKeyIndices`/`secrets`.
|
|
31
33
|
*/
|
|
32
|
-
declare function generateTickets(bandersnatch: BandernsatchWasm, ringKeys: BandersnatchKey[],
|
|
34
|
+
declare function generateTickets(bandersnatch: BandernsatchWasm, ringKeys: BandersnatchKey[], proverKeyIndices: readonly number[], secrets: readonly BandersnatchSecretSeed[], entropy: EntropyHash, ticketsPerValidator: number): Promise<Result<SignedTicket[][], null>>;
|
|
35
|
+
/**
|
|
36
|
+
* Build the concatenated ring-VRF inputs for ticket generation: one
|
|
37
|
+
* `JAM_TICKET_SEAL || entropy || attempt_byte` input per attempt.
|
|
38
|
+
*
|
|
39
|
+
* Exposed so the worker-pool path can build the same inputs to hand off to a
|
|
40
|
+
* worker thread without re-deriving the layout.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildTicketVrfInputs(entropy: EntropyHash, ticketsPerValidator: number): {
|
|
43
|
+
inputsData: Uint8Array;
|
|
44
|
+
vrfInputDataLen: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Parse the raw output of `batchGenerateRingVrfForValidators` into per-validator
|
|
48
|
+
* ticket lists. Records are ordered validator-major, then attempt-major; each
|
|
49
|
+
* record is `status byte || signature`. A malformed batch yields a single error
|
|
50
|
+
* byte. Exposed so the worker-pool path can parse a worker's raw result.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseTicketsBatchOutput(result: Uint8Array, numValidators: number, ticketsPerValidator: number): Result<SignedTicket[][], null>;
|
|
33
53
|
//# sourceMappingURL=bandersnatch-vrf.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bandersnatch-vrf.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-vrf.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAsB,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAS,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"bandersnatch-vrf.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-vrf.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SignedTicket, tryAsTicketAttempt } from "#@typeberry/block/tickets.js";
|
|
2
2
|
import { Bytes, BytesBlob } from "#@typeberry/bytes";
|
|
3
|
+
import { SEED_SIZE } from "#@typeberry/crypto";
|
|
3
4
|
import { BANDERSNATCH_PROOF_BYTES, BANDERSNATCH_RING_ROOT_BYTES, BANDERSNATCH_VRF_SIGNATURE_BYTES, } from "#@typeberry/crypto/bandersnatch.js";
|
|
4
5
|
import { HASH_SIZE } from "#@typeberry/hash";
|
|
5
6
|
import { Result } from "#@typeberry/utils";
|
|
@@ -17,8 +18,27 @@ var ResultValues;
|
|
|
17
18
|
* to overcome that we cache the results of getting ring commitment.
|
|
18
19
|
* Note we can also tentatively populate this cache, before we even
|
|
19
20
|
* reach the epoch change block.
|
|
21
|
+
*
|
|
22
|
+
* Keep number of entries low here, since matching is done by fully
|
|
23
|
+
* comparing the keys.
|
|
24
|
+
* To avoid array re-allocation we keep it's size constant and use
|
|
25
|
+
* index.
|
|
20
26
|
*/
|
|
21
|
-
|
|
27
|
+
let ringCommitmentIndex = 0;
|
|
28
|
+
const ringCommitmentCache = [
|
|
29
|
+
{
|
|
30
|
+
keys: BytesBlob.empty(),
|
|
31
|
+
value: Promise.resolve(Result.error(null, () => "")),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
keys: BytesBlob.empty(),
|
|
35
|
+
value: Promise.resolve(Result.error(null, () => "")),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
keys: BytesBlob.empty(),
|
|
39
|
+
value: Promise.resolve(Result.error(null, () => "")),
|
|
40
|
+
},
|
|
41
|
+
];
|
|
22
42
|
const FUNCTIONS = {
|
|
23
43
|
verifySeal,
|
|
24
44
|
verifyHeaderSeals,
|
|
@@ -32,10 +52,11 @@ const FUNCTIONS = {
|
|
|
32
52
|
// Ideally we would just export functions and figure out how to mock
|
|
33
53
|
// properly in ESM.
|
|
34
54
|
export default FUNCTIONS;
|
|
55
|
+
const VRF_SEAL_VERIFICATION_FAILED = () => "Bandersnatch VRF seal verification failed";
|
|
35
56
|
async function verifyHeaderSeals(bandersnatch, authorKey, signature, payload, encodedUnsealedHeader, entropySignature, entropyPayloadPrefix) {
|
|
36
57
|
const sealResult = await bandersnatch.verifyHeaderSeals(authorKey.raw, signature.raw, payload.raw, encodedUnsealedHeader.raw, entropySignature.raw, entropyPayloadPrefix.raw);
|
|
37
58
|
if (sealResult[RESULT_INDEX] === ResultValues.Error) {
|
|
38
|
-
return Result.error(null,
|
|
59
|
+
return Result.error(null, VRF_SEAL_VERIFICATION_FAILED);
|
|
39
60
|
}
|
|
40
61
|
return Result.ok([
|
|
41
62
|
Bytes.fromBlob(sealResult.subarray(1, 33), HASH_SIZE).asOpaque(),
|
|
@@ -45,90 +66,121 @@ async function verifyHeaderSeals(bandersnatch, authorKey, signature, payload, en
|
|
|
45
66
|
async function verifySeal(bandersnatch, authorKey, signature, payload, encodedUnsealedHeader) {
|
|
46
67
|
const sealResult = await bandersnatch.verifySeal(authorKey.raw, signature.raw, payload.raw, encodedUnsealedHeader.raw);
|
|
47
68
|
if (sealResult[RESULT_INDEX] === ResultValues.Error) {
|
|
48
|
-
return Result.error(null,
|
|
69
|
+
return Result.error(null, VRF_SEAL_VERIFICATION_FAILED);
|
|
49
70
|
}
|
|
50
71
|
return Result.ok(Bytes.fromBlob(sealResult.subarray(1), HASH_SIZE).asOpaque());
|
|
51
72
|
}
|
|
52
73
|
function getRingCommitment(bandersnatch, validators) {
|
|
53
74
|
const keys = BytesBlob.blobFromParts(validators.map((x) => x.raw));
|
|
54
|
-
// We currently compare the large bytes blob, but the number of entries in the cache
|
|
55
|
-
// must be low. If the cache ever grows larger, we should rather consider hashing the keys.
|
|
56
|
-
const MAX_CACHE_ENTRIES = 3;
|
|
57
75
|
const cacheEntry = ringCommitmentCache.find((v) => v.keys.isEqualTo(keys));
|
|
58
76
|
if (cacheEntry !== undefined) {
|
|
59
77
|
return cacheEntry.value;
|
|
60
78
|
}
|
|
61
79
|
const value = getRingCommitmentNoCache(bandersnatch, keys);
|
|
62
|
-
ringCommitmentCache
|
|
80
|
+
ringCommitmentCache[ringCommitmentIndex] = {
|
|
63
81
|
keys,
|
|
64
82
|
value,
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
83
|
+
};
|
|
84
|
+
// move the index to point at next entry to override.
|
|
85
|
+
ringCommitmentIndex = (ringCommitmentIndex + 1) % ringCommitmentCache.length;
|
|
69
86
|
return value;
|
|
70
87
|
}
|
|
88
|
+
const RING_COMMITMENT_FAILED = () => "Bandersnatch ring commitment calculation failed";
|
|
71
89
|
async function getRingCommitmentNoCache(bandersnatch, keys) {
|
|
72
90
|
const commitmentResult = await bandersnatch.getRingCommitment(keys.raw);
|
|
73
91
|
if (commitmentResult[RESULT_INDEX] === ResultValues.Error) {
|
|
74
|
-
return Result.error(null,
|
|
92
|
+
return Result.error(null, RING_COMMITMENT_FAILED);
|
|
75
93
|
}
|
|
76
94
|
return Result.ok(Bytes.fromBlob(commitmentResult.subarray(1), BANDERSNATCH_RING_ROOT_BYTES).asOpaque());
|
|
77
95
|
}
|
|
78
|
-
// One byte for result discriminator (`ResultValues`) and the rest is entropy hash.
|
|
79
|
-
const TICKET_RESULT_LENGTH = 1 + HASH_SIZE;
|
|
80
96
|
async function verifyTickets(bandersnatch, numberOfValidators, epochRoot, tickets, entropy) {
|
|
81
97
|
const contextLength = entropy.length + JAM_TICKET_SEAL.length + 1;
|
|
82
98
|
const ticketsData = BytesBlob.blobFromParts(tickets.map((ticket) => BytesBlob.blobFromParts([ticket.signature.raw, JAM_TICKET_SEAL, entropy.raw, Uint8Array.of(ticket.attempt)])
|
|
83
99
|
.raw)).raw;
|
|
84
100
|
const verificationResult = await bandersnatch.batchVerifyTicket(numberOfValidators, epochRoot.raw, ticketsData, contextLength);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
const isValid = verificationResult[RESULT_INDEX] === ResultValues.Ok;
|
|
102
|
+
// NOTE: in case of failure, the hashes will be all zeros, but we can safely
|
|
103
|
+
// keep the same code path.
|
|
104
|
+
const chunks = BytesBlob.blobFrom(verificationResult.subarray(1)).chunks(HASH_SIZE);
|
|
105
|
+
const results = [];
|
|
106
|
+
for (const entropyHash of chunks) {
|
|
107
|
+
results.push(Bytes.fromBlob(entropyHash.raw, HASH_SIZE).asOpaque());
|
|
108
|
+
}
|
|
109
|
+
return { isValid, tickets: results };
|
|
89
110
|
}
|
|
111
|
+
const SEAL_FAILED_ERROR = () => "Seal generation failed";
|
|
90
112
|
async function generateSeal(bandersnatch, authorKey, input, auxData) {
|
|
91
113
|
const result = await bandersnatch.generateSeal(authorKey.raw, input.raw, auxData.raw);
|
|
92
114
|
if (result[RESULT_INDEX] === ResultValues.Error) {
|
|
93
|
-
return Result.error(null,
|
|
115
|
+
return Result.error(null, SEAL_FAILED_ERROR);
|
|
94
116
|
}
|
|
95
117
|
return Result.ok(Bytes.fromBlob(result.subarray(1), BANDERSNATCH_VRF_SIGNATURE_BYTES).asOpaque());
|
|
96
118
|
}
|
|
119
|
+
const VRF_OUTPUT_FAILED = () => "VRF output hash generation failed";
|
|
97
120
|
async function getVrfOutputHash(bandersnatch, authorKey, input) {
|
|
98
121
|
const result = await bandersnatch.getVrfOutputHash(authorKey.raw, input.raw);
|
|
99
122
|
if (result[RESULT_INDEX] === ResultValues.Error) {
|
|
100
|
-
return Result.error(null,
|
|
123
|
+
return Result.error(null, VRF_OUTPUT_FAILED);
|
|
101
124
|
}
|
|
102
125
|
return Result.ok(Bytes.fromBlob(result.subarray(1), HASH_SIZE).asOpaque());
|
|
103
126
|
}
|
|
104
127
|
// One byte for result discriminator and the rest is the ring VRF signature.
|
|
105
128
|
const GENERATE_RESULT_ENTRY_LENGTH = 1 + BANDERSNATCH_PROOF_BYTES;
|
|
106
129
|
/**
|
|
107
|
-
*
|
|
130
|
+
* Batch-generate signed tickets for multiple validators in a single native call,
|
|
131
|
+
* reusing the ring prover setup across all of them. Returns one ticket list per
|
|
132
|
+
* validator, in the same order as `proverKeyIndices`/`secrets`.
|
|
108
133
|
*/
|
|
109
|
-
async function generateTickets(bandersnatch, ringKeys,
|
|
110
|
-
|
|
134
|
+
async function generateTickets(bandersnatch, ringKeys, proverKeyIndices, secrets, entropy, ticketsPerValidator) {
|
|
135
|
+
if (proverKeyIndices.length !== secrets.length) {
|
|
136
|
+
return Result.error(null, () => "proverKeyIndices and secrets must have the same length");
|
|
137
|
+
}
|
|
138
|
+
if (proverKeyIndices.length === 0) {
|
|
139
|
+
return Result.ok([]);
|
|
140
|
+
}
|
|
141
|
+
const { inputsData, vrfInputDataLen } = buildTicketVrfInputs(entropy, ticketsPerValidator);
|
|
142
|
+
const ringKeysData = BytesBlob.blobFromParts(ringKeys.map((k) => k.raw)).raw;
|
|
143
|
+
const secretSeedsData = BytesBlob.blobFromParts(secrets.map((s) => s.raw)).raw;
|
|
144
|
+
const result = await bandersnatch.batchGenerateRingVrfForValidators(ringKeysData, Uint32Array.from(proverKeyIndices), secretSeedsData, SEED_SIZE, inputsData, vrfInputDataLen);
|
|
145
|
+
return parseTicketsBatchOutput(result, proverKeyIndices.length, ticketsPerValidator);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build the concatenated ring-VRF inputs for ticket generation: one
|
|
149
|
+
* `JAM_TICKET_SEAL || entropy || attempt_byte` input per attempt.
|
|
150
|
+
*
|
|
151
|
+
* Exposed so the worker-pool path can build the same inputs to hand off to a
|
|
152
|
+
* worker thread without re-deriving the layout.
|
|
153
|
+
*/
|
|
154
|
+
export function buildTicketVrfInputs(entropy, ticketsPerValidator) {
|
|
111
155
|
const vrfInputParts = [];
|
|
112
156
|
for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
|
|
113
157
|
vrfInputParts.push(BytesBlob.blobFromParts([JAM_TICKET_SEAL, entropy.raw, Uint8Array.of(attempt)]).raw);
|
|
114
158
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
159
|
+
return {
|
|
160
|
+
inputsData: BytesBlob.blobFromParts(vrfInputParts).raw,
|
|
161
|
+
vrfInputDataLen: JAM_TICKET_SEAL.length + entropy.length + 1,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Parse the raw output of `batchGenerateRingVrfForValidators` into per-validator
|
|
166
|
+
* ticket lists. Records are ordered validator-major, then attempt-major; each
|
|
167
|
+
* record is `status byte || signature`. A malformed batch yields a single error
|
|
168
|
+
* byte. Exposed so the worker-pool path can parse a worker's raw result.
|
|
169
|
+
*/
|
|
170
|
+
export function parseTicketsBatchOutput(result, numValidators, ticketsPerValidator) {
|
|
171
|
+
const perValidator = [];
|
|
172
|
+
let offset = 0;
|
|
173
|
+
for (let v = 0; v < numValidators; v++) {
|
|
174
|
+
const tickets = [];
|
|
175
|
+
for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
|
|
176
|
+
if (result[offset] === ResultValues.Error) {
|
|
177
|
+
return Result.error(null, () => `Ring VRF proof generation failed for validator ${v}, attempt ${attempt}`);
|
|
178
|
+
}
|
|
179
|
+
const signature = Bytes.fromBlob(result.subarray(offset + 1, offset + GENERATE_RESULT_ENTRY_LENGTH), BANDERSNATCH_PROOF_BYTES).asOpaque();
|
|
180
|
+
tickets.push(SignedTicket.create({ attempt: tryAsTicketAttempt(attempt), signature }));
|
|
181
|
+
offset += GENERATE_RESULT_ENTRY_LENGTH;
|
|
126
182
|
}
|
|
127
|
-
|
|
128
|
-
tickets.push(SignedTicket.create({
|
|
129
|
-
attempt: tryAsTicketAttempt(attempt),
|
|
130
|
-
signature,
|
|
131
|
-
}));
|
|
183
|
+
perValidator.push(tickets);
|
|
132
184
|
}
|
|
133
|
-
return Result.ok(
|
|
185
|
+
return Result.ok(perValidator);
|
|
134
186
|
}
|
|
@@ -71,8 +71,8 @@ describe("Bandersnatch verification", () => {
|
|
|
71
71
|
"0x3a5d10abc80dda33fe3f40b3bb2e3eefd3e97dda3d617a860c9d94eb70b832ad",
|
|
72
72
|
].map((x) => Bytes.parseBytes(x, HASH_SIZE));
|
|
73
73
|
const result = await bandersnatchVrf.verifyTickets(await bandersnatchWasm, bandersnatchKeys.length, commitment, tickets, entropy);
|
|
74
|
-
assert.strictEqual(result.
|
|
75
|
-
assert.deepStrictEqual(result.map((x) => x.
|
|
74
|
+
assert.strictEqual(result.isValid, true);
|
|
75
|
+
assert.deepStrictEqual(result.tickets.map((x) => x.toString()), expectedIds.map((x) => x.toString()));
|
|
76
76
|
});
|
|
77
77
|
it("should detect that one signature is incorrect", async () => {
|
|
78
78
|
const tickets = [
|
|
@@ -90,14 +90,16 @@ describe("Bandersnatch verification", () => {
|
|
|
90
90
|
},
|
|
91
91
|
];
|
|
92
92
|
const entropy = Bytes.parseBytes("0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa", HASH_SIZE).asOpaque();
|
|
93
|
+
// Batch verification fails as a whole when any signature is invalid,
|
|
94
|
+
// and in that case all returned entropy hashes are zeroed out.
|
|
93
95
|
const expectedIds = [
|
|
94
96
|
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
98
|
+
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
97
99
|
].map((x) => Bytes.parseBytes(x, HASH_SIZE));
|
|
98
100
|
const result = await bandersnatchVrf.verifyTickets(await bandersnatchWasm, bandersnatchKeys.length, commitment, tickets, entropy);
|
|
99
|
-
assert.
|
|
100
|
-
assert.deepStrictEqual(result.map((x) => x.
|
|
101
|
+
assert.strictEqual(result.isValid, false);
|
|
102
|
+
assert.deepStrictEqual(result.tickets.map((x) => x.toString()), expectedIds.map((x) => x.toString()));
|
|
101
103
|
});
|
|
102
104
|
});
|
|
103
105
|
describe("verifySeal", () => {
|
|
@@ -163,12 +165,13 @@ describe("Bandersnatch verification", () => {
|
|
|
163
165
|
const ringKeys = secrets.map((secret) => deriveBandersnatchPublicKey(secret));
|
|
164
166
|
const proverIndex = 0;
|
|
165
167
|
const entropy = Bytes.fill(HASH_SIZE, 123).asOpaque();
|
|
166
|
-
const genResult = await bandersnatchVrf.generateTickets(await bandersnatchWasm, ringKeys, proverIndex, secrets[proverIndex], entropy, 2);
|
|
168
|
+
const genResult = await bandersnatchVrf.generateTickets(await bandersnatchWasm, ringKeys, [proverIndex], [secrets[proverIndex]], entropy, 2);
|
|
167
169
|
assert.ok(genResult.isOk);
|
|
168
170
|
const commitment = await bandersnatchVrf.getRingCommitment(await bandersnatchWasm, ringKeys);
|
|
169
171
|
assert.ok(commitment.isOk);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
assert.strictEqual(genResult.ok.length, 1);
|
|
173
|
+
const verifyResult = await bandersnatchVrf.verifyTickets(await bandersnatchWasm, ringKeys.length, commitment.ok, genResult.ok[0], entropy);
|
|
174
|
+
assert.ok(verifyResult.isValid, "Generated tickets should pass verification");
|
|
172
175
|
});
|
|
173
176
|
});
|
|
174
177
|
});
|
|
@@ -8,5 +8,15 @@ export declare class BandernsatchWasm {
|
|
|
8
8
|
generateSeal(authorKey: Uint8Array, input: Uint8Array, auxData: Uint8Array): Promise<Uint8Array<ArrayBufferLike>>;
|
|
9
9
|
getVrfOutputHash(authorKey: Uint8Array, input: Uint8Array): Promise<Uint8Array<ArrayBufferLike>>;
|
|
10
10
|
batchGenerateRingVrf(ringKeys: Uint8Array, proverKeyIndex: number, secretSeed: Uint8Array, inputsData: Uint8Array, vrfInputDataLen: number): Promise<Uint8Array<ArrayBufferLike>>;
|
|
11
|
+
/**
|
|
12
|
+
* Batch-generate ring VRF tickets for multiple validators in a single call,
|
|
13
|
+
* reusing the ring prover setup across all of them.
|
|
14
|
+
*
|
|
15
|
+
* `secretSeedsData` is the fixed-width concatenation of the validators' secret
|
|
16
|
+
* seeds (each `secretSeedDataLen` bytes); `proverKeyIndices` are their indices
|
|
17
|
+
* within the ring and must have the same count. Output records are ordered
|
|
18
|
+
* validator-major then input-major, each `status byte || signature`.
|
|
19
|
+
*/
|
|
20
|
+
batchGenerateRingVrfForValidators(ringKeys: Uint8Array, proverKeyIndices: Uint32Array, secretSeedsData: Uint8Array, secretSeedDataLen: number, inputsData: Uint8Array, vrfInputDataLen: number): Promise<Uint8Array<ArrayBufferLike>>;
|
|
11
21
|
}
|
|
12
22
|
//# sourceMappingURL=bandersnatch-wasm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bandersnatch-wasm.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-wasm.ts"],"names":[],"mappings":"AAEA,qBAAa,gBAAgB;IAC3B,OAAO;WAEM,GAAG;IAKV,UAAU,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAIjG,iBAAiB,CACrB,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,UAAU,EAC7B,cAAc,EAAE,UAAU,EAC1B,WAAW,EAAE,UAAU,EACvB,oBAAoB,EAAE,UAAU;IAY5B,iBAAiB,CAAC,IAAI,EAAE,UAAU;IAIlC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM;IAI1G,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAI1E,gBAAgB,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;IAIzD,oBAAoB,CACxB,QAAQ,EAAE,UAAU,EACpB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"bandersnatch-wasm.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/safrole/bandersnatch-wasm.ts"],"names":[],"mappings":"AAEA,qBAAa,gBAAgB;IAC3B,OAAO;WAEM,GAAG;IAKV,UAAU,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAIjG,iBAAiB,CACrB,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,UAAU,EAC7B,cAAc,EAAE,UAAU,EAC1B,WAAW,EAAE,UAAU,EACvB,oBAAoB,EAAE,UAAU;IAY5B,iBAAiB,CAAC,IAAI,EAAE,UAAU;IAIlC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM;IAI1G,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU;IAI1E,gBAAgB,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;IAIzD,oBAAoB,CACxB,QAAQ,EAAE,UAAU,EACpB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM;IAKzB;;;;;;;;OAQG;IACG,iCAAiC,CACrC,QAAQ,EAAE,UAAU,EACpB,gBAAgB,EAAE,WAAW,EAC7B,eAAe,EAAE,UAAU,EAC3B,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,MAAM;CAW1B"}
|
|
@@ -26,4 +26,16 @@ export class BandernsatchWasm {
|
|
|
26
26
|
async batchGenerateRingVrf(ringKeys, proverKeyIndex, secretSeed, inputsData, vrfInputDataLen) {
|
|
27
27
|
return bandersnatchWasm.batchGenerateRingVrf(ringKeys, proverKeyIndex, secretSeed, inputsData, vrfInputDataLen);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Batch-generate ring VRF tickets for multiple validators in a single call,
|
|
31
|
+
* reusing the ring prover setup across all of them.
|
|
32
|
+
*
|
|
33
|
+
* `secretSeedsData` is the fixed-width concatenation of the validators' secret
|
|
34
|
+
* seeds (each `secretSeedDataLen` bytes); `proverKeyIndices` are their indices
|
|
35
|
+
* within the ring and must have the same count. Output records are ordered
|
|
36
|
+
* validator-major then input-major, each `status byte || signature`.
|
|
37
|
+
*/
|
|
38
|
+
async batchGenerateRingVrfForValidators(ringKeys, proverKeyIndices, secretSeedsData, secretSeedDataLen, inputsData, vrfInputDataLen) {
|
|
39
|
+
return bandersnatchWasm.batchGenerateRingVrfForValidators(ringKeys, proverKeyIndices, secretSeedsData, secretSeedDataLen, inputsData, vrfInputDataLen);
|
|
40
|
+
}
|
|
29
41
|
}
|
|
@@ -260,15 +260,15 @@ export class Safrole {
|
|
|
260
260
|
*/
|
|
261
261
|
// TODO [ToDr] Verify that ticket attempt is in correct range.
|
|
262
262
|
const verificationResult = extrinsic.length === 0
|
|
263
|
-
? []
|
|
263
|
+
? { isValid: true, tickets: [] }
|
|
264
264
|
: await bandersnatchVrf.verifyTickets(await this.bandersnatch, validators.length, epochRoot, extrinsic, entropy);
|
|
265
|
+
if (!verificationResult.isValid) {
|
|
266
|
+
return Result.error(SafroleErrorCode.BadTicketProof, () => "Safrole: invalid ticket proof in extrinsic");
|
|
267
|
+
}
|
|
265
268
|
const tickets = extrinsic.map((ticket, i) => ({
|
|
266
|
-
id: verificationResult[i]
|
|
269
|
+
id: verificationResult.tickets[i],
|
|
267
270
|
attempt: ticket.attempt,
|
|
268
271
|
}));
|
|
269
|
-
if (!verificationResult.every((x) => x.isValid)) {
|
|
270
|
-
return Result.error(SafroleErrorCode.BadTicketProof, () => "Safrole: invalid ticket proof in extrinsic");
|
|
271
|
-
}
|
|
272
272
|
/**
|
|
273
273
|
* Verify if tickets are sorted and unique
|
|
274
274
|
*
|
|
@@ -67,10 +67,10 @@ const fakeSealingKeys = {
|
|
|
67
67
|
describe("Safrole", () => {
|
|
68
68
|
let blake2b;
|
|
69
69
|
beforeEach(async () => {
|
|
70
|
-
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve({
|
|
71
|
+
isValid: true,
|
|
72
|
+
tickets: [Bytes.zero(HASH_SIZE), Bytes.fill(HASH_SIZE, 1)],
|
|
73
|
+
}));
|
|
74
74
|
blake2b = await Blake2b.createHasher();
|
|
75
75
|
});
|
|
76
76
|
afterEach(() => {
|
|
@@ -146,7 +146,7 @@ describe("Safrole", () => {
|
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
it("should return bad ticket proof error", async () => {
|
|
149
|
-
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve(
|
|
149
|
+
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve({ isValid: false, tickets: [Bytes.zero(HASH_SIZE)] }));
|
|
150
150
|
const punishSet = SortedSet.fromArray(hashComparator);
|
|
151
151
|
const state = {
|
|
152
152
|
timeslot: tryAsTimeSlot(1),
|
|
@@ -188,10 +188,10 @@ describe("Safrole", () => {
|
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
190
|
it("should return duplicated ticket error", async () => {
|
|
191
|
-
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve({
|
|
192
|
+
isValid: true,
|
|
193
|
+
tickets: [Bytes.zero(HASH_SIZE), Bytes.zero(HASH_SIZE)],
|
|
194
|
+
}));
|
|
195
195
|
const punishSet = SortedSet.fromArray(hashComparator);
|
|
196
196
|
const state = {
|
|
197
197
|
timeslot: tryAsTimeSlot(1),
|
|
@@ -237,10 +237,10 @@ describe("Safrole", () => {
|
|
|
237
237
|
}
|
|
238
238
|
});
|
|
239
239
|
it("should return bad ticket order error", async () => {
|
|
240
|
-
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
mock.method(bandersnatchVrf, "verifyTickets", () => Promise.resolve({
|
|
241
|
+
isValid: true,
|
|
242
|
+
tickets: [Bytes.fill(HASH_SIZE, 1), Bytes.zero(HASH_SIZE)],
|
|
243
|
+
}));
|
|
244
244
|
const punishSet = SortedSet.fromArray(hashComparator);
|
|
245
245
|
const state = {
|
|
246
246
|
timeslot: tryAsTimeSlot(1),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Epoch } from "#@typeberry/block";
|
|
2
|
+
import type { SignedTicket } from "#@typeberry/block/tickets.js";
|
|
3
|
+
/**
|
|
4
|
+
* An ordered, signature-deduplicated pool of tickets waiting to be redistributed to peers.
|
|
5
|
+
*
|
|
6
|
+
* Used on the networking side. Indices are stable within an epoch so callers can track
|
|
7
|
+
* per-peer "sent" sets by index. The pool is cleared whenever a new epoch is observed,
|
|
8
|
+
* and tickets for older epochs are dropped (can happen when an async validation completes
|
|
9
|
+
* after the epoch already advanced).
|
|
10
|
+
*/
|
|
11
|
+
export declare class PendingTicketPool {
|
|
12
|
+
private tickets;
|
|
13
|
+
private currentEpochValue;
|
|
14
|
+
/** Epoch the pool is currently holding tickets for, or `null` if empty. */
|
|
15
|
+
get currentEpoch(): Epoch | null;
|
|
16
|
+
/** Returns the ordered tickets currently in the pool. Caller must not mutate the array. */
|
|
17
|
+
getTickets(): readonly {
|
|
18
|
+
epochIndex: Epoch;
|
|
19
|
+
ticket: SignedTicket;
|
|
20
|
+
}[];
|
|
21
|
+
/** Returns true if the ticket was added, false if it was a duplicate or dropped (old epoch). */
|
|
22
|
+
addTicket(epochIndex: Epoch, ticket: SignedTicket): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Replace the pool contents for the given epoch with the supplied tickets. Used when the
|
|
25
|
+
* authorship worker pushes an authoritative pool dump on an epoch boundary; any tickets
|
|
26
|
+
* that aren't in the dump are dropped, and dedup runs over the new set.
|
|
27
|
+
*/
|
|
28
|
+
replace(epochIndex: Epoch, tickets: readonly SignedTicket[]): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=pending-ticket-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-ticket-pool.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/pending-ticket-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE;;;;;;;GAOG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,iBAAiB,CAAsB;IAE/C,2EAA2E;IAC3E,IAAI,YAAY,IAAI,KAAK,GAAG,IAAI,CAE/B;IAED,2FAA2F;IAC3F,UAAU,IAAI,SAAS;QAAE,UAAU,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,YAAY,CAAA;KAAE,EAAE;IAIpE,gGAAgG;IAChG,SAAS,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO;IAyB3D;;;;OAIG;IACH,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI;CAWnE"}
|