@typeberry/lib 0.8.4 → 0.9.0-c9f9e4d
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 +4 -2
- package/packages/core/utils/debug.d.ts.map +1 -1
- package/packages/core/utils/debug.js +18 -13
- 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-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/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 +16 -1
- package/packages/jam/node/main-importer.d.ts +6 -3
- package/packages/jam/node/main-importer.d.ts.map +1 -1
- package/packages/jam/node/main-importer.js +3 -2
- 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 +63 -26
- 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/workers/api-node/config.d.ts +12 -5
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +20 -17
- 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 +2 -2
- package/packages/workers/importer/importer.d.ts.map +1 -1
- package/packages/workers/importer/importer.js +5 -5
- package/packages/workers/importer/stats.d.ts +1 -3
- package/packages/workers/importer/stats.d.ts.map +1 -1
- package/packages/workers/importer/stats.js +12 -12
- 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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { tryAsEpoch } from "#@typeberry/block";
|
|
3
|
+
import { HashSet } from "#@typeberry/collections";
|
|
4
|
+
import { Logger } from "#@typeberry/logger";
|
|
5
|
+
import { measure } from "#@typeberry/utils";
|
|
6
|
+
import { TicketGeneratorPool } from "./worker-pool.js";
|
|
7
|
+
/**
|
|
8
|
+
* Extra validators to generate tickets for, beyond the minimum needed to fill the
|
|
9
|
+
* accumulator. Filling requires `epochLength` distinct valid tickets; each validator
|
|
10
|
+
* yields `ticketsPerValidator`. The margin guards against a few tickets failing to
|
|
11
|
+
* land (extra tickets are simply dropped by the accumulator).
|
|
12
|
+
*/
|
|
13
|
+
const TICKET_GENERATION_VALIDATOR_MARGIN = 8;
|
|
14
|
+
/** Leave this many cores for the main thread, importer, network and the OS. */
|
|
15
|
+
const TICKET_POOL_RESERVED_CORES = 4;
|
|
16
|
+
/** Hard cap on ticket-generation worker threads. */
|
|
17
|
+
const TICKET_POOL_MAX_WORKERS = 8;
|
|
18
|
+
const logger = Logger.new(import.meta.filename, "tickets");
|
|
19
|
+
const measureTicketGen = measure("ticket:gen");
|
|
20
|
+
/** Number of worker threads to use for parallel ticket generation. */
|
|
21
|
+
function ticketPoolWorkerCount(validators) {
|
|
22
|
+
const cores = os.availableParallelism?.() ?? os.cpus().length;
|
|
23
|
+
const availableCores = Math.min(cores - TICKET_POOL_RESERVED_CORES, TICKET_POOL_MAX_WORKERS);
|
|
24
|
+
// never reserve more cores than we have validators (makes no sense)
|
|
25
|
+
const requiredCores = Math.min(validators, availableCores);
|
|
26
|
+
return Math.max(1, requiredCores);
|
|
27
|
+
}
|
|
28
|
+
export class TicketGenerator {
|
|
29
|
+
chainSpec;
|
|
30
|
+
keys;
|
|
31
|
+
pool;
|
|
32
|
+
static async new(chainSpec, keys) {
|
|
33
|
+
const pool = await TicketGeneratorPool.create(ticketPoolWorkerCount(keys.length));
|
|
34
|
+
return new TicketGenerator(chainSpec, keys, pool);
|
|
35
|
+
}
|
|
36
|
+
constructor(chainSpec, keys, pool) {
|
|
37
|
+
this.chainSpec = chainSpec;
|
|
38
|
+
this.keys = keys;
|
|
39
|
+
this.pool = pool;
|
|
40
|
+
}
|
|
41
|
+
async generateTickets(state, isEpochStart, onTickets) {
|
|
42
|
+
// Pick the right entropy and validator set
|
|
43
|
+
const validators = isEpochStart ? state.designatedValidatorData : state.nextValidatorData;
|
|
44
|
+
const entropy = isEpochStart ? state.entropy[1] : state.entropy[2];
|
|
45
|
+
const epoch = tryAsEpoch(Math.floor(state.timeslot / this.chainSpec.epochLength));
|
|
46
|
+
const ringKeys = validators.map((d) => d.bandersnatch);
|
|
47
|
+
const nextKeySet = HashSet.from(ringKeys);
|
|
48
|
+
const validatorKeys = this.keys.filter((k) => nextKeySet.has(k.public));
|
|
49
|
+
// Generate just enough validators to fill the accumulator, plus a margin.
|
|
50
|
+
const needed = Math.ceil(this.chainSpec.epochLength / this.chainSpec.ticketsPerValidator) + TICKET_GENERATION_VALIDATOR_MARGIN;
|
|
51
|
+
const selected = validatorKeys.slice(0, Math.min(validatorKeys.length, needed));
|
|
52
|
+
const ticketGen = measureTicketGen();
|
|
53
|
+
logger.info `🎫 [E${epoch}] generating tickets for ${selected.length} validators across ${this.pool.workerCount} worker threads…`;
|
|
54
|
+
try {
|
|
55
|
+
await this.pool.generate(ringKeys, selected, entropy, this.chainSpec.ticketsPerValidator, onTickets);
|
|
56
|
+
logger.info `🎫 [E${epoch}] ${ticketGen}`;
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
logger.warn `🎫 [E${epoch}] ticket generation failed: ${e}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Transferable } from "node:worker_threads";
|
|
2
|
+
/**
|
|
3
|
+
* Parameters for a single ticket-generation shard sent to a worker thread.
|
|
4
|
+
*/
|
|
5
|
+
export declare class TicketGenShardParams {
|
|
6
|
+
/** Concatenated ring public keys (`ringSize * 32` bytes). */
|
|
7
|
+
readonly ringKeysData: Uint8Array;
|
|
8
|
+
/** Index within the ring for each validator in this shard. */
|
|
9
|
+
readonly proverKeyIndices: Uint32Array;
|
|
10
|
+
/** Concatenated validator secret seeds (`count * secretSeedDataLen` bytes). */
|
|
11
|
+
readonly secretSeedsData: Uint8Array;
|
|
12
|
+
/** Length of each secret seed in `secretSeedsData`. */
|
|
13
|
+
readonly secretSeedDataLen: number;
|
|
14
|
+
/** Concatenated VRF inputs, one per attempt. */
|
|
15
|
+
readonly inputsData: Uint8Array;
|
|
16
|
+
/** Length of each VRF input in `inputsData`. */
|
|
17
|
+
readonly vrfInputDataLen: number;
|
|
18
|
+
constructor(
|
|
19
|
+
/** Concatenated ring public keys (`ringSize * 32` bytes). */
|
|
20
|
+
ringKeysData: Uint8Array,
|
|
21
|
+
/** Index within the ring for each validator in this shard. */
|
|
22
|
+
proverKeyIndices: Uint32Array,
|
|
23
|
+
/** Concatenated validator secret seeds (`count * secretSeedDataLen` bytes). */
|
|
24
|
+
secretSeedsData: Uint8Array,
|
|
25
|
+
/** Length of each secret seed in `secretSeedsData`. */
|
|
26
|
+
secretSeedDataLen: number,
|
|
27
|
+
/** Concatenated VRF inputs, one per attempt. */
|
|
28
|
+
inputsData: Uint8Array,
|
|
29
|
+
/** Length of each VRF input in `inputsData`. */
|
|
30
|
+
vrfInputDataLen: number);
|
|
31
|
+
/**
|
|
32
|
+
* No transfers: `ringKeysData` and `inputsData` are shared across all shards,
|
|
33
|
+
* so transferring would detach them for the other shards.
|
|
34
|
+
*/
|
|
35
|
+
getTransferList(): Transferable[];
|
|
36
|
+
}
|
|
37
|
+
/** Result of a ticket-generation shard: the raw `batchGenerateRingVrfForValidators` output. */
|
|
38
|
+
export declare class TicketGenShardResult {
|
|
39
|
+
/** Raw output: validator-major, attempt-major records of `status || signature`. */
|
|
40
|
+
readonly signatures: Uint8Array;
|
|
41
|
+
constructor(
|
|
42
|
+
/** Raw output: validator-major, attempt-major records of `status || signature`. */
|
|
43
|
+
signatures: Uint8Array);
|
|
44
|
+
/**
|
|
45
|
+
* No transfers: the native binding returns a view backed by external/WASM
|
|
46
|
+
* memory that cannot be detached, so transferring it throws inside the worker.
|
|
47
|
+
*/
|
|
48
|
+
getTransferList(): Transferable[];
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../../../packages/workers/block-authorship/ticket-generator/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;GAEG;AACH,qBAAa,oBAAoB;IAE7B,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,EAAE,UAAU;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,gBAAgB,EAAE,WAAW;IACtC,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,EAAE,UAAU;IACpC,uDAAuD;IACvD,QAAQ,CAAC,iBAAiB,EAAE,MAAM;IAClC,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,UAAU;IAC/B,gDAAgD;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM;;IAXhC,6DAA6D;IACpD,YAAY,EAAE,UAAU;IACjC,8DAA8D;IACrD,gBAAgB,EAAE,WAAW;IACtC,+EAA+E;IACtE,eAAe,EAAE,UAAU;IACpC,uDAAuD;IAC9C,iBAAiB,EAAE,MAAM;IAClC,gDAAgD;IACvC,UAAU,EAAE,UAAU;IAC/B,gDAAgD;IACvC,eAAe,EAAE,MAAM;IAGlC;;;OAGG;IACH,eAAe,IAAI,YAAY,EAAE;CAGlC;AAED,+FAA+F;AAC/F,qBAAa,oBAAoB;IAE7B,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,UAAU;;IAD/B,mFAAmF;IAC1E,UAAU,EAAE,UAAU;IAGjC;;;OAGG;IACH,eAAe,IAAI,YAAY,EAAE;CAGlC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameters for a single ticket-generation shard sent to a worker thread.
|
|
3
|
+
*/
|
|
4
|
+
export class TicketGenShardParams {
|
|
5
|
+
ringKeysData;
|
|
6
|
+
proverKeyIndices;
|
|
7
|
+
secretSeedsData;
|
|
8
|
+
secretSeedDataLen;
|
|
9
|
+
inputsData;
|
|
10
|
+
vrfInputDataLen;
|
|
11
|
+
constructor(
|
|
12
|
+
/** Concatenated ring public keys (`ringSize * 32` bytes). */
|
|
13
|
+
ringKeysData,
|
|
14
|
+
/** Index within the ring for each validator in this shard. */
|
|
15
|
+
proverKeyIndices,
|
|
16
|
+
/** Concatenated validator secret seeds (`count * secretSeedDataLen` bytes). */
|
|
17
|
+
secretSeedsData,
|
|
18
|
+
/** Length of each secret seed in `secretSeedsData`. */
|
|
19
|
+
secretSeedDataLen,
|
|
20
|
+
/** Concatenated VRF inputs, one per attempt. */
|
|
21
|
+
inputsData,
|
|
22
|
+
/** Length of each VRF input in `inputsData`. */
|
|
23
|
+
vrfInputDataLen) {
|
|
24
|
+
this.ringKeysData = ringKeysData;
|
|
25
|
+
this.proverKeyIndices = proverKeyIndices;
|
|
26
|
+
this.secretSeedsData = secretSeedsData;
|
|
27
|
+
this.secretSeedDataLen = secretSeedDataLen;
|
|
28
|
+
this.inputsData = inputsData;
|
|
29
|
+
this.vrfInputDataLen = vrfInputDataLen;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* No transfers: `ringKeysData` and `inputsData` are shared across all shards,
|
|
33
|
+
* so transferring would detach them for the other shards.
|
|
34
|
+
*/
|
|
35
|
+
getTransferList() {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Result of a ticket-generation shard: the raw `batchGenerateRingVrfForValidators` output. */
|
|
40
|
+
export class TicketGenShardResult {
|
|
41
|
+
signatures;
|
|
42
|
+
constructor(
|
|
43
|
+
/** Raw output: validator-major, attempt-major records of `status || signature`. */
|
|
44
|
+
signatures) {
|
|
45
|
+
this.signatures = signatures;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* No transfers: the native binding returns a view backed by external/WASM
|
|
49
|
+
* memory that cannot be detached, so transferring it throws inside the worker.
|
|
50
|
+
*/
|
|
51
|
+
getTransferList() {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -16,6 +16,10 @@ export type ValidatorKey = {
|
|
|
16
16
|
*
|
|
17
17
|
* Each validator key produces `ticketsPerValidator` tickets using ring VRF proofs.
|
|
18
18
|
* The ring keys define the anonymous set - only members can produce valid proofs.
|
|
19
|
+
*
|
|
20
|
+
* All resolved validators are generated in a single batched native call
|
|
21
|
+
* ({@link bandersnatchVrf.generateTickets}) which reuses the ring
|
|
22
|
+
* prover setup across the batch.
|
|
19
23
|
*/
|
|
20
24
|
export declare function generateTickets(bandersnatch: BandernsatchWasm, ringKeys: BandersnatchKey[], validatorKeys: ValidatorKey[], entropy: EntropyHash, ticketsPerValidator: number): Promise<Result<SignedTicket[], TicketGeneratorError>>;
|
|
21
25
|
//# sourceMappingURL=ticket-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket-generator.d.ts","sourceRoot":"","sources":["../../../../../../packages/workers/block-authorship/ticket-generator/ticket-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI1C,oBAAY,oBAAoB;IAC9B,sBAAsB,2BAA2B;IACjD,kBAAkB,uBAAuB;CAC1C;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,sBAAsB,CAAC;IAC/B,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,gBAAgB,EAC9B,QAAQ,EAAE,eAAe,EAAE,EAC3B,aAAa,EAAE,YAAY,EAAE,EAC7B,OAAO,EAAE,WAAW,EACpB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,oBAAoB,CAAC,CAAC,CA2CvD"}
|
|
@@ -12,25 +12,35 @@ export var TicketGeneratorError;
|
|
|
12
12
|
*
|
|
13
13
|
* Each validator key produces `ticketsPerValidator` tickets using ring VRF proofs.
|
|
14
14
|
* The ring keys define the anonymous set - only members can produce valid proofs.
|
|
15
|
+
*
|
|
16
|
+
* All resolved validators are generated in a single batched native call
|
|
17
|
+
* ({@link bandersnatchVrf.generateTickets}) which reuses the ring
|
|
18
|
+
* prover setup across the batch.
|
|
15
19
|
*/
|
|
16
20
|
export async function generateTickets(bandersnatch, ringKeys, validatorKeys, entropy, ticketsPerValidator) {
|
|
17
|
-
|
|
21
|
+
// Resolve each validator's index within the ring, skipping any that are not
|
|
22
|
+
// members (only ring members can produce valid proofs).
|
|
23
|
+
const proverKeyIndices = [];
|
|
24
|
+
const secrets = [];
|
|
18
25
|
for (const validatorKey of validatorKeys) {
|
|
19
26
|
const proverIndex = ringKeys.findIndex((k) => k.isEqualTo(validatorKey.public));
|
|
20
27
|
if (proverIndex < 0) {
|
|
21
28
|
logger.warn `Validator public key not found in the ring, skipping ticket generation for this key`;
|
|
22
29
|
continue;
|
|
23
30
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else
|
|
29
|
-
|
|
31
|
+
proverKeyIndices.push(proverIndex);
|
|
32
|
+
secrets.push(validatorKey.secret);
|
|
33
|
+
}
|
|
34
|
+
if (proverKeyIndices.length === 0) {
|
|
35
|
+
// No resolvable validators: an error if some were requested, else just empty.
|
|
36
|
+
if (validatorKeys.length > 0) {
|
|
37
|
+
return Result.error(TicketGeneratorError.TicketGenerationFailed, () => "Failed to generate tickets for all validators");
|
|
30
38
|
}
|
|
39
|
+
return Result.ok([]);
|
|
31
40
|
}
|
|
32
|
-
|
|
41
|
+
const result = await bandersnatchVrf.generateTickets(bandersnatch, ringKeys, proverKeyIndices, secrets, entropy, ticketsPerValidator);
|
|
42
|
+
if (result.isError) {
|
|
33
43
|
return Result.error(TicketGeneratorError.TicketGenerationFailed, () => "Failed to generate tickets for all validators");
|
|
34
44
|
}
|
|
35
|
-
return Result.ok(
|
|
45
|
+
return Result.ok(result.ok.flat());
|
|
36
46
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket-generator.test.d.ts","sourceRoot":"","sources":["../../../../../../packages/workers/block-authorship/ticket-generator/ticket-generator.test.ts"],"names":[],"mappings":""}
|
|
@@ -21,15 +21,19 @@ function createMockValidatorKeys(count) {
|
|
|
21
21
|
describe("Ticket Generator", () => {
|
|
22
22
|
beforeEach(async () => {
|
|
23
23
|
await initWasm();
|
|
24
|
-
mock.method(bandersnatchVrf, "generateTickets", async (_bandersnatch, _ringKeys,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
tickets
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
mock.method(bandersnatchVrf, "generateTickets", async (_bandersnatch, _ringKeys, proverKeyIndices, _secrets, _entropy, ticketsPerValidator) => {
|
|
25
|
+
// One ticket list per resolved validator, in validator-major order.
|
|
26
|
+
const perValidator = proverKeyIndices.map(() => {
|
|
27
|
+
const tickets = [];
|
|
28
|
+
for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
|
|
29
|
+
tickets.push({
|
|
30
|
+
attempt: tryAsTicketAttempt(attempt),
|
|
31
|
+
signature: Bytes.zero(784).asOpaque(),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return tickets;
|
|
35
|
+
});
|
|
36
|
+
return Result.ok(perValidator);
|
|
33
37
|
});
|
|
34
38
|
});
|
|
35
39
|
afterEach(() => {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EntropyHash } from "#@typeberry/block";
|
|
2
|
+
import type { SignedTicket } from "#@typeberry/block/tickets.js";
|
|
3
|
+
import { type BandersnatchKey } from "#@typeberry/crypto";
|
|
4
|
+
import type { ValidatorKey } from "./ticket-generator.js";
|
|
5
|
+
/**
|
|
6
|
+
* A pool of worker threads that generate ring-VRF tickets in parallel.
|
|
7
|
+
*
|
|
8
|
+
* Ring-VRF proof generation is a heavy, synchronous, CPU-bound native call
|
|
9
|
+
* (~0.7s per validator with a large ring). Running it on the authoring thread
|
|
10
|
+
* blocks block production; this pool shards the work across worker threads so
|
|
11
|
+
* the main thread stays free and wall-clock time drops ~linearly with cores.
|
|
12
|
+
*
|
|
13
|
+
* Uses `Executor` from `@typeberry/concurrent`.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TicketGeneratorPool {
|
|
16
|
+
private readonly executor;
|
|
17
|
+
/** Number of worker threads in the pool. */
|
|
18
|
+
readonly workerCount: number;
|
|
19
|
+
private constructor();
|
|
20
|
+
/** Spawn `workerCount` worker threads (each initialises the native binding). */
|
|
21
|
+
static create(workerCount: number): Promise<TicketGeneratorPool>;
|
|
22
|
+
/** Terminate all worker threads. */
|
|
23
|
+
destroy(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Generate tickets for `validatorKeys`, sharded evenly across the pool.
|
|
26
|
+
*
|
|
27
|
+
* `onShardTickets` is invoked on the calling (main) thread as each shard's
|
|
28
|
+
* results arrive, so tickets can be pooled/distributed incrementally rather
|
|
29
|
+
* than waiting for the whole batch. Returns once every shard has settled.
|
|
30
|
+
*
|
|
31
|
+
* Validators whose public key is not in `ringKeys` are skipped (they cannot
|
|
32
|
+
* produce valid proofs).
|
|
33
|
+
*/
|
|
34
|
+
generate(ringKeys: BandersnatchKey[], validatorKeys: ValidatorKey[], entropy: EntropyHash, ticketsPerValidator: number, onShardTickets: (tickets: SignedTicket[]) => Promise<void>): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=worker-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../../../../../packages/workers/block-authorship/ticket-generator/worker-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,EAAE,KAAK,eAAe,EAA0C,MAAM,mBAAmB,CAAC;AAIjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAiB1D;;;;;;;;;GASG;AACH,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,4CAA4C;aAC5B,WAAW,EAAE,MAAM;IAHrC,OAAO;IAMP,gFAAgF;WACnE,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAQtE,oCAAoC;IAC9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;;;;;;;;OASG;IACG,QAAQ,CACZ,QAAQ,EAAE,eAAe,EAAE,EAC3B,aAAa,EAAE,YAAY,EAAE,EAC7B,OAAO,EAAE,WAAW,EACpB,mBAAmB,EAAE,MAAM,EAC3B,cAAc,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GACzD,OAAO,CAAC,IAAI,CAAC;CA6DjB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { BytesBlob } from "#@typeberry/bytes";
|
|
2
|
+
import { Executor } from "#@typeberry/concurrent";
|
|
3
|
+
import { SEED_SIZE } from "#@typeberry/crypto";
|
|
4
|
+
import { Logger } from "#@typeberry/logger";
|
|
5
|
+
import { buildTicketVrfInputs, parseTicketsBatchOutput } from "#@typeberry/safrole/bandersnatch-vrf.js";
|
|
6
|
+
import { TicketGenShardParams } from "./protocol.js";
|
|
7
|
+
const logger = Logger.new(import.meta.filename, "tickets-pool");
|
|
8
|
+
/** `.mjs` bootstrap that `tsImport`s the worker entry (worker threads need it under tsx). */
|
|
9
|
+
const WORKER_BOOTSTRAP = new URL("./bootstrap-ticket-generator.mjs", import.meta.url);
|
|
10
|
+
/**
|
|
11
|
+
* Validators per shard.
|
|
12
|
+
*
|
|
13
|
+
* Kept small so tickets stream into the pool incrementally (and get included in
|
|
14
|
+
* blocks throughout the contest period) rather than arriving in one lump when
|
|
15
|
+
* generation finishes. The prover setup is re-done per shard, but that cost is
|
|
16
|
+
* tiny next to the per-proof time, so finer shards add only ~1-2% overhead.
|
|
17
|
+
*/
|
|
18
|
+
const TICKET_SHARD_SIZE = 16;
|
|
19
|
+
/**
|
|
20
|
+
* A pool of worker threads that generate ring-VRF tickets in parallel.
|
|
21
|
+
*
|
|
22
|
+
* Ring-VRF proof generation is a heavy, synchronous, CPU-bound native call
|
|
23
|
+
* (~0.7s per validator with a large ring). Running it on the authoring thread
|
|
24
|
+
* blocks block production; this pool shards the work across worker threads so
|
|
25
|
+
* the main thread stays free and wall-clock time drops ~linearly with cores.
|
|
26
|
+
*
|
|
27
|
+
* Uses `Executor` from `@typeberry/concurrent`.
|
|
28
|
+
*/
|
|
29
|
+
export class TicketGeneratorPool {
|
|
30
|
+
executor;
|
|
31
|
+
workerCount;
|
|
32
|
+
constructor(executor,
|
|
33
|
+
/** Number of worker threads in the pool. */
|
|
34
|
+
workerCount) {
|
|
35
|
+
this.executor = executor;
|
|
36
|
+
this.workerCount = workerCount;
|
|
37
|
+
}
|
|
38
|
+
/** Spawn `workerCount` worker threads (each initialises the native binding). */
|
|
39
|
+
static async create(workerCount) {
|
|
40
|
+
const executor = await Executor.initialize(WORKER_BOOTSTRAP, {
|
|
41
|
+
minWorkers: workerCount,
|
|
42
|
+
maxWorkers: workerCount,
|
|
43
|
+
});
|
|
44
|
+
return new TicketGeneratorPool(executor, workerCount);
|
|
45
|
+
}
|
|
46
|
+
/** Terminate all worker threads. */
|
|
47
|
+
async destroy() {
|
|
48
|
+
await this.executor.destroy();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate tickets for `validatorKeys`, sharded evenly across the pool.
|
|
52
|
+
*
|
|
53
|
+
* `onShardTickets` is invoked on the calling (main) thread as each shard's
|
|
54
|
+
* results arrive, so tickets can be pooled/distributed incrementally rather
|
|
55
|
+
* than waiting for the whole batch. Returns once every shard has settled.
|
|
56
|
+
*
|
|
57
|
+
* Validators whose public key is not in `ringKeys` are skipped (they cannot
|
|
58
|
+
* produce valid proofs).
|
|
59
|
+
*/
|
|
60
|
+
async generate(ringKeys, validatorKeys, entropy, ticketsPerValidator, onShardTickets) {
|
|
61
|
+
// Resolve each validator's index within the ring (skip non-members).
|
|
62
|
+
const keyToIndex = new Map();
|
|
63
|
+
for (let i = 0; i < ringKeys.length; i++) {
|
|
64
|
+
keyToIndex.set(ringKeys[i].toString(), i);
|
|
65
|
+
}
|
|
66
|
+
const resolved = [];
|
|
67
|
+
for (const vk of validatorKeys) {
|
|
68
|
+
const idx = keyToIndex.get(vk.public.toString());
|
|
69
|
+
if (idx === undefined) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
resolved.push({ index: idx, secret: vk.secret });
|
|
73
|
+
}
|
|
74
|
+
if (resolved.length === 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const { inputsData, vrfInputDataLen } = buildTicketVrfInputs(entropy, ticketsPerValidator);
|
|
78
|
+
const ringKeysData = BytesBlob.blobFromParts(ringKeys.map((k) => k.raw)).raw;
|
|
79
|
+
const runShard = (shard) => {
|
|
80
|
+
const indices = Uint32Array.from(shard.map((r) => r.index));
|
|
81
|
+
const secretSeedsData = BytesBlob.blobFromParts(shard.map((r) => r.secret.raw)).raw;
|
|
82
|
+
const params = new TicketGenShardParams(ringKeysData, indices, secretSeedsData, SEED_SIZE, inputsData, vrfInputDataLen);
|
|
83
|
+
return this.executor
|
|
84
|
+
.run(params)
|
|
85
|
+
.then((result) => {
|
|
86
|
+
const parsed = parseTicketsBatchOutput(result.signatures, indices.length, ticketsPerValidator);
|
|
87
|
+
if (parsed.isError) {
|
|
88
|
+
logger.warn `A ticket-generation shard returned an invalid proof: ${parsed.error}`;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
return onShardTickets(parsed.ok.flat());
|
|
92
|
+
})
|
|
93
|
+
.catch((e) => {
|
|
94
|
+
logger.warn `A ticket-generation shard failed: ${e}`;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
// Dispatch small shards in waves of `workerCount` so every worker stays busy
|
|
98
|
+
// and tickets are delivered incrementally (one wave at a time) without
|
|
99
|
+
// over-queuing the executor.
|
|
100
|
+
const shardSize = Math.min(resolved.length, TICKET_SHARD_SIZE);
|
|
101
|
+
const waveSize = shardSize * this.workerCount;
|
|
102
|
+
for (let waveStart = 0; waveStart < resolved.length; waveStart += waveSize) {
|
|
103
|
+
const wave = [];
|
|
104
|
+
const waveEnd = Math.min(waveStart + waveSize, resolved.length);
|
|
105
|
+
for (let start = waveStart; start < waveEnd; start += shardSize) {
|
|
106
|
+
wave.push(runShard(resolved.slice(start, start + shardSize)));
|
|
107
|
+
}
|
|
108
|
+
await Promise.all(wave);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Epoch } from "#@typeberry/block";
|
|
2
|
+
import type { SignedTicket } from "#@typeberry/block/tickets.js";
|
|
3
|
+
import type { ChainSpec } from "#@typeberry/config";
|
|
4
|
+
import type { BandernsatchWasm } from "#@typeberry/safrole/bandersnatch-wasm.js";
|
|
5
|
+
import type { State } from "#@typeberry/state";
|
|
6
|
+
import { type TicketValidator, ValidationError, type VerifiedTicket } from "#@typeberry/ticket-pool";
|
|
7
|
+
import { Result } from "#@typeberry/utils";
|
|
8
|
+
/**
|
|
9
|
+
* {@link TicketValidator} implementation that verifies a ticket against the ring
|
|
10
|
+
* commitment and current epoch entropy using bandersnatch.
|
|
11
|
+
*
|
|
12
|
+
* `getState` is a thunk because state advances continuously while validation is in
|
|
13
|
+
* flight; we want the latest available state for each call.
|
|
14
|
+
*/
|
|
15
|
+
export declare class BandersnatchTicketValidator implements TicketValidator {
|
|
16
|
+
private readonly chainSpec;
|
|
17
|
+
private readonly bandersnatch;
|
|
18
|
+
private readonly getState;
|
|
19
|
+
static new(chainSpec: ChainSpec, bandersnatch: BandernsatchWasm, getState: () => State): BandersnatchTicketValidator;
|
|
20
|
+
private constructor();
|
|
21
|
+
validate(epochIndex: Epoch, inTickets: SignedTicket[]): Promise<Result<VerifiedTicket[], ValidationError>>;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the correct tickets entropy for verification given the current state.
|
|
24
|
+
*
|
|
25
|
+
* When `state` is from epoch E-1 (i.e. we haven't produced epoch E's first block yet),
|
|
26
|
+
* the ticket entropy for epoch E is at index 1 (not yet shifted). After the epoch
|
|
27
|
+
* transition it moves to index 2.
|
|
28
|
+
*/
|
|
29
|
+
private getTicketEntropy;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=ticket-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket-validator.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/block-authorship/ticket-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,KAAK,eAAe,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;GAMG;AACH,qBAAa,2BAA4B,YAAW,eAAe;IAM/D,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAP3B,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,KAAK;IAItF,OAAO;IAMD,QAAQ,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,eAAe,CAAC,CAAC;IAwChH;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;CAIzB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import bandersnatchVrf from "#@typeberry/safrole/bandersnatch-vrf.js";
|
|
2
|
+
import { ValidationError } from "#@typeberry/ticket-pool";
|
|
3
|
+
import { Result } from "#@typeberry/utils";
|
|
4
|
+
/**
|
|
5
|
+
* {@link TicketValidator} implementation that verifies a ticket against the ring
|
|
6
|
+
* commitment and current epoch entropy using bandersnatch.
|
|
7
|
+
*
|
|
8
|
+
* `getState` is a thunk because state advances continuously while validation is in
|
|
9
|
+
* flight; we want the latest available state for each call.
|
|
10
|
+
*/
|
|
11
|
+
export class BandersnatchTicketValidator {
|
|
12
|
+
chainSpec;
|
|
13
|
+
bandersnatch;
|
|
14
|
+
getState;
|
|
15
|
+
static new(chainSpec, bandersnatch, getState) {
|
|
16
|
+
return new BandersnatchTicketValidator(chainSpec, bandersnatch, getState);
|
|
17
|
+
}
|
|
18
|
+
constructor(chainSpec, bandersnatch, getState) {
|
|
19
|
+
this.chainSpec = chainSpec;
|
|
20
|
+
this.bandersnatch = bandersnatch;
|
|
21
|
+
this.getState = getState;
|
|
22
|
+
}
|
|
23
|
+
async validate(epochIndex, inTickets) {
|
|
24
|
+
const state = this.getState();
|
|
25
|
+
// Reject obviously invalid epochs up front: we only have the entropy to verify
|
|
26
|
+
// the current epoch (tickets being submitted now) and the next one (tickets at
|
|
27
|
+
// an epoch boundary). Anything else would only fail after the expensive proof
|
|
28
|
+
// check, so a peer could otherwise burn CPU with far-past/future batches.
|
|
29
|
+
const stateEpoch = Math.floor(state.timeslot / this.chainSpec.epochLength);
|
|
30
|
+
if (epochIndex < stateEpoch || epochIndex > stateEpoch + 1) {
|
|
31
|
+
return Result.error(ValidationError.InvalidProof, () => `ticket batch targets an invalid epoch ${epochIndex}`);
|
|
32
|
+
}
|
|
33
|
+
const entropy = this.getTicketEntropy(epochIndex, state);
|
|
34
|
+
// Batch verifier: a single `isValid` covers the whole batch
|
|
35
|
+
// and `tickets` holds the computed id per input ticket.
|
|
36
|
+
const { isValid, tickets } = await bandersnatchVrf.verifyTickets(this.bandersnatch, state.designatedValidatorData.length, state.epochRoot, inTickets, entropy);
|
|
37
|
+
if (tickets.length !== inTickets.length) {
|
|
38
|
+
return Result.error(ValidationError.ValidatorUnavailable, () => `io size mismatch got: ${tickets.length}, expected ${inTickets.length}`);
|
|
39
|
+
}
|
|
40
|
+
if (!isValid) {
|
|
41
|
+
return Result.error(ValidationError.InvalidProof, () => "bandersnatch proof rejected");
|
|
42
|
+
}
|
|
43
|
+
return Result.ok(inTickets.map((ticket, index) => {
|
|
44
|
+
const id = tickets[index];
|
|
45
|
+
return { ticket, id };
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns the correct tickets entropy for verification given the current state.
|
|
50
|
+
*
|
|
51
|
+
* When `state` is from epoch E-1 (i.e. we haven't produced epoch E's first block yet),
|
|
52
|
+
* the ticket entropy for epoch E is at index 1 (not yet shifted). After the epoch
|
|
53
|
+
* transition it moves to index 2.
|
|
54
|
+
*/
|
|
55
|
+
getTicketEntropy(epochIndex, state) {
|
|
56
|
+
const stateEpoch = Math.floor(state.timeslot / this.chainSpec.epochLength);
|
|
57
|
+
return epochIndex > stateEpoch ? state.entropy[1] : state.entropy[2];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Api, type Internal } from "#@typeberry/workers-api";
|
|
2
|
-
import {
|
|
2
|
+
import { TicketsMessage } from "./tickets-message.js";
|
|
3
3
|
/**
|
|
4
4
|
* Port name for authorship-network direct communication.
|
|
5
5
|
* Used when spawning jam-network worker to pass the port for receiving tickets.
|
|
@@ -21,14 +21,24 @@ export declare const protocol: import("@typeberry/workers-api").LousyProtocol<{
|
|
|
21
21
|
}>>;
|
|
22
22
|
response: import("@typeberry/codec").Descriptor<void, void>;
|
|
23
23
|
};
|
|
24
|
+
replaceTicketPool: {
|
|
25
|
+
request: import("@typeberry/codec").Descriptor<TicketsMessage, import("@typeberry/codec").ViewOf<TicketsMessage, {
|
|
26
|
+
epochIndex: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<4> & import("@typeberry/utils").WithOpaque<"Epoch">, import("@typeberry/bytes").Bytes<4>>;
|
|
27
|
+
tickets: import("@typeberry/codec").Descriptor<import("@typeberry/block").SignedTicket[], import("@typeberry/codec").SequenceView<import("@typeberry/block").SignedTicket, import("@typeberry/codec").ViewOf<import("@typeberry/block").SignedTicket, {
|
|
28
|
+
attempt: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<1> & import("@typeberry/utils").WithOpaque<"TicketAttempt[u8]">, import("@typeberry/numbers").U32>;
|
|
29
|
+
signature: import("@typeberry/codec").Descriptor<import("@typeberry/bytes").Bytes<784> & import("@typeberry/utils").WithOpaque<"BandersnatchRingSignature">, import("@typeberry/bytes").Bytes<784>>;
|
|
30
|
+
}>>>;
|
|
31
|
+
}>>;
|
|
32
|
+
response: import("@typeberry/codec").Descriptor<void, void>;
|
|
33
|
+
};
|
|
24
34
|
}, {
|
|
25
35
|
receivedTickets: {
|
|
26
|
-
request: import("@typeberry/codec").Descriptor<
|
|
36
|
+
request: import("@typeberry/codec").Descriptor<TicketsMessage, import("@typeberry/codec").ViewOf<TicketsMessage, {
|
|
27
37
|
epochIndex: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<4> & import("@typeberry/utils").WithOpaque<"Epoch">, import("@typeberry/bytes").Bytes<4>>;
|
|
28
|
-
|
|
38
|
+
tickets: import("@typeberry/codec").Descriptor<import("@typeberry/block").SignedTicket[], import("@typeberry/codec").SequenceView<import("@typeberry/block").SignedTicket, import("@typeberry/codec").ViewOf<import("@typeberry/block").SignedTicket, {
|
|
29
39
|
attempt: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<1> & import("@typeberry/utils").WithOpaque<"TicketAttempt[u8]">, import("@typeberry/numbers").U32>;
|
|
30
40
|
signature: import("@typeberry/codec").Descriptor<import("@typeberry/bytes").Bytes<784> & import("@typeberry/utils").WithOpaque<"BandersnatchRingSignature">, import("@typeberry/bytes").Bytes<784>>;
|
|
31
|
-
}
|
|
41
|
+
}>>>;
|
|
32
42
|
}>>;
|
|
33
43
|
response: import("@typeberry/codec").Descriptor<boolean, boolean>;
|
|
34
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,uBAAuB,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBnB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC;AACnD,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { codec } from "#@typeberry/codec";
|
|
2
2
|
import { createProtocol } from "#@typeberry/workers-api";
|
|
3
|
-
import {
|
|
3
|
+
import { TicketsMessage } from "./tickets-message.js";
|
|
4
4
|
/**
|
|
5
5
|
* Port name for authorship-network direct communication.
|
|
6
6
|
* Used when spawning jam-network worker to pass the port for receiving tickets.
|
|
@@ -12,19 +12,25 @@ export const AUTHORSHIP_NETWORK_PORT = "authorship-network";
|
|
|
12
12
|
* This bypasses the main thread for ticket distribution, reducing latency.
|
|
13
13
|
*/
|
|
14
14
|
export const protocol = createProtocol("authorship-network", {
|
|
15
|
-
// Messages from block-authorship to jam-network
|
|
15
|
+
// Messages from block-authorship to jam-network.
|
|
16
16
|
toWorker: {
|
|
17
|
+
// Newly generated own tickets; networking should add them to its redistribution pool.
|
|
17
18
|
tickets: {
|
|
18
19
|
request: TicketsMessage.Codec,
|
|
19
20
|
response: codec.nothing,
|
|
20
21
|
},
|
|
22
|
+
// Authoritative pool snapshot for the given epoch; networking replaces its local
|
|
23
|
+
// pool with these tickets (one-way, source of truth lives in block-authorship).
|
|
24
|
+
replaceTicketPool: {
|
|
25
|
+
request: TicketsMessage.Codec,
|
|
26
|
+
response: codec.nothing,
|
|
27
|
+
},
|
|
21
28
|
},
|
|
22
|
-
// Messages from jam-network to block-authorship
|
|
23
|
-
// Response indicates whether
|
|
24
|
-
// to decide whether to redistribute the ticket to other peers.
|
|
29
|
+
// Messages from jam-network to block-authorship
|
|
30
|
+
// Response indicates whether all tickets in batch were valid (no per-ticket validity!)
|
|
25
31
|
fromWorker: {
|
|
26
32
|
receivedTickets: {
|
|
27
|
-
request:
|
|
33
|
+
request: TicketsMessage.Codec,
|
|
28
34
|
response: codec.bool,
|
|
29
35
|
},
|
|
30
36
|
},
|
|
@@ -15,18 +15,4 @@ export declare class TicketsMessage extends WithDebug {
|
|
|
15
15
|
static create({ epochIndex, tickets }: CodecRecord<TicketsMessage>): TicketsMessage;
|
|
16
16
|
private constructor();
|
|
17
17
|
}
|
|
18
|
-
/** Single-ticket message sent from jam-network to block-authorship (one ticket per peer relay). */
|
|
19
|
-
export declare class ReceivedTicketMessage extends WithDebug {
|
|
20
|
-
readonly epochIndex: Epoch;
|
|
21
|
-
readonly ticket: SignedTicket;
|
|
22
|
-
static Codec: import("@typeberry/codec").Descriptor<ReceivedTicketMessage, import("@typeberry/codec").ViewOf<ReceivedTicketMessage, {
|
|
23
|
-
epochIndex: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<4> & import("@typeberry/utils").WithOpaque<"Epoch">, import("@typeberry/bytes").Bytes<4>>;
|
|
24
|
-
ticket: import("@typeberry/codec").Descriptor<SignedTicket, import("@typeberry/codec").ViewOf<SignedTicket, {
|
|
25
|
-
attempt: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<1> & import("@typeberry/utils").WithOpaque<"TicketAttempt[u8]">, import("@typeberry/numbers").U32>;
|
|
26
|
-
signature: import("@typeberry/codec").Descriptor<import("@typeberry/bytes").Bytes<784> & import("@typeberry/utils").WithOpaque<"BandersnatchRingSignature">, import("@typeberry/bytes").Bytes<784>>;
|
|
27
|
-
}>>;
|
|
28
|
-
}>>;
|
|
29
|
-
static create({ epochIndex, ticket }: CodecRecord<ReceivedTicketMessage>): ReceivedTicketMessage;
|
|
30
|
-
private constructor();
|
|
31
|
-
}
|
|
32
18
|
//# sourceMappingURL=tickets-message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tickets-message.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/tickets-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,qBAAa,cAAe,SAAQ,SAAS;aAWzB,UAAU,EAAE,KAAK;aACjB,OAAO,EAAE,YAAY,EAAE;IAXzC,MAAM,CAAC,KAAK;;;;;;QAGT;IAEH,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,WAAW,CAAC,cAAc,CAAC;IAIlE,OAAO;CAMR
|
|
1
|
+
{"version":3,"file":"tickets-message.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/tickets-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,qBAAa,cAAe,SAAQ,SAAS;aAWzB,UAAU,EAAE,KAAK;aACjB,OAAO,EAAE,YAAY,EAAE;IAXzC,MAAM,CAAC,KAAK;;;;;;QAGT;IAEH,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,WAAW,CAAC,cAAc,CAAC;IAIlE,OAAO;CAMR"}
|