@typeberry/lib 0.8.4 → 0.9.0-3f2c45b

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.
Files changed (129) hide show
  1. package/package.json +6 -4
  2. package/packages/configs/index.d.ts +30 -1
  3. package/packages/configs/index.d.ts.map +1 -1
  4. package/packages/configs/index.js +4 -2
  5. package/packages/configs/typeberry-dev-full.json +29 -0
  6. package/packages/core/bytes/bytes.d.ts +1 -0
  7. package/packages/core/bytes/bytes.d.ts.map +1 -1
  8. package/packages/core/bytes/bytes.js +8 -0
  9. package/packages/core/utils/debug.d.ts +4 -2
  10. package/packages/core/utils/debug.d.ts.map +1 -1
  11. package/packages/core/utils/debug.js +18 -13
  12. package/packages/core/utils/debug.test.js +12 -6
  13. package/packages/jam/config-node/node-config.d.ts +2 -1
  14. package/packages/jam/config-node/node-config.d.ts.map +1 -1
  15. package/packages/jam/config-node/node-config.js +8 -3
  16. package/packages/jam/config-node/node-config.test.js +3 -3
  17. package/packages/jam/database-fjall/hybrid-states.d.ts +82 -0
  18. package/packages/jam/database-fjall/hybrid-states.d.ts.map +1 -0
  19. package/packages/jam/database-fjall/hybrid-states.js +169 -0
  20. package/packages/jam/database-fjall/hybrid-states.test.d.ts +2 -0
  21. package/packages/jam/database-fjall/hybrid-states.test.d.ts.map +1 -0
  22. package/packages/jam/database-fjall/hybrid-states.test.js +113 -0
  23. package/packages/jam/database-fjall/index.d.ts +3 -0
  24. package/packages/jam/database-fjall/index.d.ts.map +1 -0
  25. package/packages/jam/database-fjall/index.js +2 -0
  26. package/packages/jam/database-fjall/root.d.ts +58 -0
  27. package/packages/jam/database-fjall/root.d.ts.map +1 -0
  28. package/packages/jam/database-fjall/root.js +89 -0
  29. package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts +18 -10
  30. package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts.map +1 -1
  31. package/packages/jam/jamnp-s/tasks/ticket-distribution.js +44 -68
  32. package/packages/jam/jamnp-s/tasks/ticket-distribution.test.js +30 -8
  33. package/packages/jam/node/main-fuzz.d.ts.map +1 -1
  34. package/packages/jam/node/main-fuzz.js +69 -11
  35. package/packages/jam/node/main-importer.d.ts +13 -3
  36. package/packages/jam/node/main-importer.d.ts.map +1 -1
  37. package/packages/jam/node/main-importer.js +5 -3
  38. package/packages/jam/safrole/bandersnatch-vrf.d.ts +24 -4
  39. package/packages/jam/safrole/bandersnatch-vrf.d.ts.map +1 -1
  40. package/packages/jam/safrole/bandersnatch-vrf.js +63 -26
  41. package/packages/jam/safrole/bandersnatch-vrf.test.js +12 -9
  42. package/packages/jam/safrole/bandersnatch-wasm.d.ts +10 -0
  43. package/packages/jam/safrole/bandersnatch-wasm.d.ts.map +1 -1
  44. package/packages/jam/safrole/bandersnatch-wasm.js +12 -0
  45. package/packages/jam/safrole/safrole.js +5 -5
  46. package/packages/jam/safrole/safrole.test.js +13 -13
  47. package/packages/jam/ticket-pool/index.d.ts +4 -0
  48. package/packages/jam/ticket-pool/index.d.ts.map +1 -0
  49. package/packages/jam/ticket-pool/index.js +3 -0
  50. package/packages/jam/ticket-pool/pending-ticket-pool.d.ts +30 -0
  51. package/packages/jam/ticket-pool/pending-ticket-pool.d.ts.map +1 -0
  52. package/packages/jam/ticket-pool/pending-ticket-pool.js +56 -0
  53. package/packages/jam/ticket-pool/pending-ticket-pool.test.d.ts +2 -0
  54. package/packages/jam/ticket-pool/pending-ticket-pool.test.d.ts.map +1 -0
  55. package/packages/jam/ticket-pool/pending-ticket-pool.test.js +67 -0
  56. package/packages/jam/ticket-pool/ticket-validator.d.ts +47 -0
  57. package/packages/jam/ticket-pool/ticket-validator.d.ts.map +1 -0
  58. package/packages/jam/ticket-pool/ticket-validator.js +34 -0
  59. package/packages/jam/ticket-pool/ticket-validator.test.d.ts +2 -0
  60. package/packages/jam/ticket-pool/ticket-validator.test.d.ts.map +1 -0
  61. package/packages/jam/ticket-pool/ticket-validator.test.js +35 -0
  62. package/packages/jam/ticket-pool/verified-ticket-pool.d.ts +26 -0
  63. package/packages/jam/ticket-pool/verified-ticket-pool.d.ts.map +1 -0
  64. package/packages/jam/ticket-pool/verified-ticket-pool.js +41 -0
  65. package/packages/jam/ticket-pool/verified-ticket-pool.test.d.ts +2 -0
  66. package/packages/jam/ticket-pool/verified-ticket-pool.test.d.ts.map +1 -0
  67. package/packages/jam/ticket-pool/verified-ticket-pool.test.js +54 -0
  68. package/packages/workers/api-node/config.d.ts +21 -6
  69. package/packages/workers/api-node/config.d.ts.map +1 -1
  70. package/packages/workers/api-node/config.js +26 -19
  71. package/packages/workers/api-node/config.test.js +38 -1
  72. package/packages/workers/block-authorship/{generator.d.ts → block-generator.d.ts} +5 -5
  73. package/packages/workers/block-authorship/block-generator.d.ts.map +1 -0
  74. package/packages/workers/block-authorship/{generator.js → block-generator.js} +3 -3
  75. package/packages/workers/block-authorship/block-generator.test.d.ts +2 -0
  76. package/packages/workers/block-authorship/block-generator.test.d.ts.map +1 -0
  77. package/packages/workers/block-authorship/{generator.test.js → block-generator.test.js} +8 -8
  78. package/packages/workers/block-authorship/epoch-authoring-slots.d.ts +35 -0
  79. package/packages/workers/block-authorship/epoch-authoring-slots.d.ts.map +1 -0
  80. package/packages/workers/block-authorship/epoch-authoring-slots.js +86 -0
  81. package/packages/workers/block-authorship/epoch-tracker.d.ts +29 -0
  82. package/packages/workers/block-authorship/epoch-tracker.d.ts.map +1 -0
  83. package/packages/workers/block-authorship/epoch-tracker.js +80 -0
  84. package/packages/workers/block-authorship/index.d.ts.map +1 -1
  85. package/packages/workers/block-authorship/index.js +1 -1
  86. package/packages/workers/block-authorship/main.d.ts +3 -0
  87. package/packages/workers/block-authorship/main.d.ts.map +1 -1
  88. package/packages/workers/block-authorship/main.js +197 -315
  89. package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts +2 -0
  90. package/packages/workers/block-authorship/ticket-generator/bootstrap-main.d.ts.map +1 -0
  91. package/packages/workers/block-authorship/ticket-generator/bootstrap-main.js +23 -0
  92. package/packages/workers/block-authorship/ticket-generator/index.d.ts +16 -0
  93. package/packages/workers/block-authorship/ticket-generator/index.d.ts.map +1 -0
  94. package/packages/workers/block-authorship/ticket-generator/index.js +62 -0
  95. package/packages/workers/block-authorship/ticket-generator/protocol.d.ts +50 -0
  96. package/packages/workers/block-authorship/ticket-generator/protocol.d.ts.map +1 -0
  97. package/packages/workers/block-authorship/ticket-generator/protocol.js +54 -0
  98. package/packages/workers/block-authorship/{ticket-generator.d.ts → ticket-generator/ticket-generator.d.ts} +4 -0
  99. package/packages/workers/block-authorship/ticket-generator/ticket-generator.d.ts.map +1 -0
  100. package/packages/workers/block-authorship/{ticket-generator.js → ticket-generator/ticket-generator.js} +19 -9
  101. package/packages/workers/block-authorship/ticket-generator/ticket-generator.test.d.ts.map +1 -0
  102. package/packages/workers/block-authorship/{ticket-generator.test.js → ticket-generator/ticket-generator.test.js} +13 -9
  103. package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts +36 -0
  104. package/packages/workers/block-authorship/ticket-generator/worker-pool.d.ts.map +1 -0
  105. package/packages/workers/block-authorship/ticket-generator/worker-pool.js +111 -0
  106. package/packages/workers/block-authorship/ticket-validator.d.ts +31 -0
  107. package/packages/workers/block-authorship/ticket-validator.d.ts.map +1 -0
  108. package/packages/workers/block-authorship/ticket-validator.js +59 -0
  109. package/packages/workers/comms-authorship-network/protocol.d.ts +14 -4
  110. package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -1
  111. package/packages/workers/comms-authorship-network/protocol.js +12 -6
  112. package/packages/workers/comms-authorship-network/tickets-message.d.ts +0 -14
  113. package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -1
  114. package/packages/workers/comms-authorship-network/tickets-message.js +0 -17
  115. package/packages/workers/importer/importer.d.ts +2 -2
  116. package/packages/workers/importer/importer.d.ts.map +1 -1
  117. package/packages/workers/importer/importer.js +5 -5
  118. package/packages/workers/importer/stats.d.ts +1 -3
  119. package/packages/workers/importer/stats.d.ts.map +1 -1
  120. package/packages/workers/importer/stats.js +12 -12
  121. package/packages/workers/jam-network/main.d.ts.map +1 -1
  122. package/packages/workers/jam-network/main.js +25 -4
  123. package/packages/workers/block-authorship/generator.d.ts.map +0 -1
  124. package/packages/workers/block-authorship/generator.test.d.ts +0 -2
  125. package/packages/workers/block-authorship/generator.test.d.ts.map +0 -1
  126. package/packages/workers/block-authorship/ticket-generator.d.ts.map +0 -1
  127. package/packages/workers/block-authorship/ticket-generator.test.d.ts.map +0 -1
  128. /package/packages/configs/{typeberry-dev.json → typeberry-dev-tiny.json} +0 -0
  129. /package/packages/workers/block-authorship/{ticket-generator.test.d.ts → ticket-generator/ticket-generator.test.d.ts} +0 -0
@@ -6,7 +6,7 @@ import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
6
6
  import { createImporter, ImporterConfig } from "#@typeberry/importer";
7
7
  import { tryAsU16 } from "#@typeberry/numbers";
8
8
  import { CURRENT_SUITE, CURRENT_VERSION, Result, resultToString, version } from "#@typeberry/utils";
9
- import { HybridWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig } from "#@typeberry/workers-api-node";
9
+ import { HybridWorkerConfig, InMemWorkerConfig, LmdbWorkerConfig, } from "#@typeberry/workers-api-node";
10
10
  import { getChainSpec, getDatabasePath, initializeDatabase, logger } from "./common.js";
11
11
  const zeroHash = Bytes.zero(HASH_SIZE).asOpaque();
12
12
  export async function mainImporter(config, withRelPath, options = {}) {
@@ -39,8 +39,8 @@ export async function mainImporter(config, withRelPath, options = {}) {
39
39
  blake2b,
40
40
  workerParams,
41
41
  })
42
- : dbBackend === "hybrid"
43
- ? HybridWorkerConfig.new({
42
+ : dbBackend === "lmdb-hybrid" || dbBackend === "fjall-hybrid"
43
+ ? await HybridWorkerConfig.new({
44
44
  nodeName,
45
45
  chainSpec,
46
46
  blake2b,
@@ -48,6 +48,8 @@ export async function mainImporter(config, withRelPath, options = {}) {
48
48
  workerParams,
49
49
  ephemeral,
50
50
  compression,
51
+ backend: dbBackend === "lmdb-hybrid" ? "lmdb" : "fjall",
52
+ sharedFjallSession: options.sharedFjallSession,
51
53
  })
52
54
  : LmdbWorkerConfig.new({
53
55
  nodeName,
@@ -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
- entropyHash: EntropyHash;
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
- * Generates signed tickets for all attempts at once using batch ring VRF.
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[], proverKeyIndex: number, key: BandersnatchSecretSeed, entropy: EntropyHash, ticketsPerValidator: number): Promise<Result<SignedTicket[], null>>;
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;AACjF,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC9B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAwC/D,QAAA,MAAM,SAAS;;;;;;;;CAQd,CAAC;AAKF,eAAe,SAAS,CAAC;AAIzB,iBAAe,iBAAiB,CAC9B,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,eAAe,EAC1B,SAAS,EAAE,wBAAwB,EACnC,OAAO,EAAE,SAAS,EAClB,qBAAqB,EAAE,SAAS,EAChC,gBAAgB,EAAE,wBAAwB,EAC1C,oBAAoB,EAAE,SAAS,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC,CAkBnD;AAED,iBAAe,UAAU,CACvB,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,eAAe,EAC1B,SAAS,EAAE,wBAAwB,EACnC,OAAO,EAAE,SAAS,EAClB,qBAAqB,EAAE,SAAS,GAC/B,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAapC;AAED,iBAAS,iBAAiB,CACxB,YAAY,EAAE,gBAAgB,EAC9B,UAAU,EAAE,eAAe,EAAE,GAC5B,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC,CAe7C;AAmBD,iBAAe,aAAa,CAC1B,YAAY,EAAE,gBAAgB,EAC9B,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,oBAAoB,EAC/B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,WAAW,CAAA;CAAE,EAAE,CAAC,CAqB3D;AAGD,iBAAe,YAAY,CACzB,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,sBAAsB,EACjC,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,MAAM,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC,CAQjD;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;AAGlE,iBAAe,gBAAgB,CAC7B,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,sBAAsB,EACjC,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAQtC;AAKD;;GAEG;AACH,iBAAe,eAAe,CAC5B,YAAY,EAAE,gBAAgB,EAC9B,QAAQ,EAAE,eAAe,EAAE,EAC3B,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,WAAW,EACpB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,IAAI,CAAC,CAAC,CA0CvC"}
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";
@@ -33,6 +34,10 @@ const ringCommitmentCache = [
33
34
  keys: BytesBlob.empty(),
34
35
  value: Promise.resolve(Result.error(null, () => "")),
35
36
  },
37
+ {
38
+ keys: BytesBlob.empty(),
39
+ value: Promise.resolve(Result.error(null, () => "")),
40
+ },
36
41
  ];
37
42
  const FUNCTIONS = {
38
43
  verifySeal,
@@ -88,17 +93,20 @@ async function getRingCommitmentNoCache(bandersnatch, keys) {
88
93
  }
89
94
  return Result.ok(Bytes.fromBlob(commitmentResult.subarray(1), BANDERSNATCH_RING_ROOT_BYTES).asOpaque());
90
95
  }
91
- // One byte for result discriminator (`ResultValues`) and the rest is entropy hash.
92
- const TICKET_RESULT_LENGTH = 1 + HASH_SIZE;
93
96
  async function verifyTickets(bandersnatch, numberOfValidators, epochRoot, tickets, entropy) {
94
97
  const contextLength = entropy.length + JAM_TICKET_SEAL.length + 1;
95
98
  const ticketsData = BytesBlob.blobFromParts(tickets.map((ticket) => BytesBlob.blobFromParts([ticket.signature.raw, JAM_TICKET_SEAL, entropy.raw, Uint8Array.of(ticket.attempt)])
96
99
  .raw)).raw;
97
100
  const verificationResult = await bandersnatch.batchVerifyTicket(numberOfValidators, epochRoot.raw, ticketsData, contextLength);
98
- return Array.from(BytesBlob.blobFrom(verificationResult).chunks(TICKET_RESULT_LENGTH)).map((result) => ({
99
- isValid: result.raw[RESULT_INDEX] === ResultValues.Ok,
100
- entropyHash: Bytes.fromBlob(result.raw.subarray(1, TICKET_RESULT_LENGTH), HASH_SIZE).asOpaque(),
101
- }));
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 };
102
110
  }
103
111
  const SEAL_FAILED_ERROR = () => "Seal generation failed";
104
112
  async function generateSeal(bandersnatch, authorKey, input, auxData) {
@@ -119,31 +127,60 @@ async function getVrfOutputHash(bandersnatch, authorKey, input) {
119
127
  // One byte for result discriminator and the rest is the ring VRF signature.
120
128
  const GENERATE_RESULT_ENTRY_LENGTH = 1 + BANDERSNATCH_PROOF_BYTES;
121
129
  /**
122
- * Generates signed tickets for all attempts at once using batch ring VRF.
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`.
133
+ */
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.
123
153
  */
124
- async function generateTickets(bandersnatch, ringKeys, proverKeyIndex, key, entropy, ticketsPerValidator) {
125
- // Build VRF inputs: JAM_TICKET_SEAL || entropy || attempt_byte for each attempt
154
+ export function buildTicketVrfInputs(entropy, ticketsPerValidator) {
126
155
  const vrfInputParts = [];
127
156
  for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
128
157
  vrfInputParts.push(BytesBlob.blobFromParts([JAM_TICKET_SEAL, entropy.raw, Uint8Array.of(attempt)]).raw);
129
158
  }
130
- const attemptLength = 1;
131
- const vrfInputDataLen = JAM_TICKET_SEAL.length + entropy.length + attemptLength;
132
- const inputsData = BytesBlob.blobFromParts(vrfInputParts).raw;
133
- const ringKeysData = BytesBlob.blobFromParts(ringKeys.map((k) => k.raw)).raw;
134
- const result = await bandersnatch.batchGenerateRingVrf(ringKeysData, proverKeyIndex, key.raw, inputsData, vrfInputDataLen);
135
- const tickets = [];
136
- for (let attempt = 0; attempt < ticketsPerValidator; attempt++) {
137
- const offset = attempt * GENERATE_RESULT_ENTRY_LENGTH;
138
- const resultByte = result[offset];
139
- if (resultByte === ResultValues.Error) {
140
- return Result.error(null, () => `Ring VRF proof generation failed for attempt ${attempt}`);
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;
141
182
  }
142
- const signature = Bytes.fromBlob(new Uint8Array(result.subarray(offset + 1, offset + GENERATE_RESULT_ENTRY_LENGTH)), BANDERSNATCH_PROOF_BYTES).asOpaque();
143
- tickets.push(SignedTicket.create({
144
- attempt: tryAsTicketAttempt(attempt),
145
- signature,
146
- }));
183
+ perValidator.push(tickets);
147
184
  }
148
- return Result.ok(tickets);
185
+ return Result.ok(perValidator);
149
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.every((x) => x.isValid), true);
75
- assert.deepStrictEqual(result.map((x) => x.entropyHash.toString()), expectedIds.map((x) => x.toString()));
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
- "0x13fecb426e0a73b84b58b9a0832b11582dc971e79c5399e69f0baf1a244c7787",
96
- "0x3a5d10abc80dda33fe3f40b3bb2e3eefd3e97dda3d617a860c9d94eb70b832ad",
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.deepStrictEqual(result.map((x) => x.isValid), [false, true, true]);
100
- assert.deepStrictEqual(result.map((x) => x.entropyHash.toString()), expectedIds.map((x) => x.toString()));
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
- const verifyResult = await bandersnatchVrf.verifyTickets(await bandersnatchWasm, ringKeys.length, commitment.ok, genResult.ok, entropy);
171
- assert.ok(verifyResult.every((r) => r.isValid), "Generated tickets should pass verification");
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;CAI1B"}
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].entropyHash,
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
- { isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
72
- { isValid: true, entropyHash: Bytes.fill(HASH_SIZE, 1) },
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([{ isValid: false, entropyHash: Bytes.zero(HASH_SIZE) }]));
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
- { isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
193
- { isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
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
- { isValid: true, entropyHash: Bytes.fill(HASH_SIZE, 1) },
242
- { isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
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,4 @@
1
+ export * from "./pending-ticket-pool.js";
2
+ export * from "./ticket-validator.js";
3
+ export * from "./verified-ticket-pool.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export * from "./pending-ticket-pool.js";
2
+ export * from "./ticket-validator.js";
3
+ export * from "./verified-ticket-pool.js";
@@ -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"}
@@ -0,0 +1,56 @@
1
+ import { Logger } from "#@typeberry/logger";
2
+ const logger = Logger.new(import.meta.filename, "pending-pool");
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 class PendingTicketPool {
12
+ tickets = [];
13
+ currentEpochValue = null;
14
+ /** Epoch the pool is currently holding tickets for, or `null` if empty. */
15
+ get currentEpoch() {
16
+ return this.currentEpochValue;
17
+ }
18
+ /** Returns the ordered tickets currently in the pool. Caller must not mutate the array. */
19
+ getTickets() {
20
+ return this.tickets;
21
+ }
22
+ /** Returns true if the ticket was added, false if it was a duplicate or dropped (old epoch). */
23
+ addTicket(epochIndex, ticket) {
24
+ if (this.currentEpochValue !== null && epochIndex < this.currentEpochValue) {
25
+ return false;
26
+ }
27
+ if (this.currentEpochValue !== null && epochIndex > this.currentEpochValue) {
28
+ logger.log `Epoch changed from ${this.currentEpochValue} to ${epochIndex}, clearing ${this.tickets.length} old tickets`;
29
+ this.tickets = [];
30
+ }
31
+ this.currentEpochValue = epochIndex;
32
+ const isDuplicate = this.tickets.some((pending) => pending.epochIndex === epochIndex && pending.ticket.signature.isEqualTo(ticket.signature));
33
+ if (isDuplicate) {
34
+ return false;
35
+ }
36
+ this.tickets.push({ epochIndex, ticket });
37
+ logger.info `[addTicket] Added ticket for epoch ${epochIndex}, total: ${this.tickets.length}`;
38
+ return true;
39
+ }
40
+ /**
41
+ * Replace the pool contents for the given epoch with the supplied tickets. Used when the
42
+ * authorship worker pushes an authoritative pool dump on an epoch boundary; any tickets
43
+ * that aren't in the dump are dropped, and dedup runs over the new set.
44
+ */
45
+ replace(epochIndex, tickets) {
46
+ this.tickets = [];
47
+ this.currentEpochValue = epochIndex;
48
+ for (const ticket of tickets) {
49
+ const isDuplicate = this.tickets.some((pending) => pending.ticket.signature.isEqualTo(ticket.signature));
50
+ if (!isDuplicate) {
51
+ this.tickets.push({ epochIndex, ticket });
52
+ }
53
+ }
54
+ logger.log `Pool replaced for epoch ${epochIndex} with ${this.tickets.length} tickets`;
55
+ }
56
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pending-ticket-pool.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-ticket-pool.test.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/pending-ticket-pool.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import assert from "node:assert";
2
+ import { describe, it } from "node:test";
3
+ import { tryAsEpoch } from "#@typeberry/block";
4
+ import { SignedTicket, tryAsTicketAttempt } from "#@typeberry/block/tickets.js";
5
+ import { Bytes } from "#@typeberry/bytes";
6
+ import { BANDERSNATCH_PROOF_BYTES } from "#@typeberry/crypto";
7
+ import { PendingTicketPool } from "./pending-ticket-pool.js";
8
+ const E1 = tryAsEpoch(1);
9
+ const E2 = tryAsEpoch(2);
10
+ function makeTicket(seed, attempt = 0) {
11
+ const sig = Bytes.zero(BANDERSNATCH_PROOF_BYTES);
12
+ sig.raw[0] = seed;
13
+ return SignedTicket.create({
14
+ attempt: tryAsTicketAttempt(attempt),
15
+ signature: sig.asOpaque(),
16
+ });
17
+ }
18
+ describe("PendingTicketPool", () => {
19
+ it("starts empty with no current epoch", () => {
20
+ const pool = new PendingTicketPool();
21
+ assert.strictEqual(pool.currentEpoch, null);
22
+ assert.deepStrictEqual(pool.getTickets(), []);
23
+ });
24
+ it("adds a ticket and tracks the epoch", () => {
25
+ const pool = new PendingTicketPool();
26
+ const t = makeTicket(1);
27
+ assert.strictEqual(pool.addTicket(E1, t), true);
28
+ assert.strictEqual(pool.currentEpoch, E1);
29
+ assert.strictEqual(pool.getTickets().length, 1);
30
+ });
31
+ it("dedups by signature within an epoch", () => {
32
+ const pool = new PendingTicketPool();
33
+ const t = makeTicket(1);
34
+ pool.addTicket(E1, t);
35
+ assert.strictEqual(pool.addTicket(E1, t), false);
36
+ assert.strictEqual(pool.getTickets().length, 1);
37
+ });
38
+ it("clears tickets when a newer epoch arrives", () => {
39
+ const pool = new PendingTicketPool();
40
+ pool.addTicket(E1, makeTicket(1));
41
+ pool.addTicket(E1, makeTicket(2));
42
+ pool.addTicket(E2, makeTicket(3));
43
+ const tickets = pool.getTickets();
44
+ assert.strictEqual(tickets.length, 1);
45
+ assert.strictEqual(tickets[0].epochIndex, E2);
46
+ assert.strictEqual(pool.currentEpoch, E2);
47
+ });
48
+ it("drops late tickets for older epochs", () => {
49
+ const pool = new PendingTicketPool();
50
+ pool.addTicket(E2, makeTicket(1));
51
+ assert.strictEqual(pool.addTicket(E1, makeTicket(2)), false);
52
+ assert.strictEqual(pool.getTickets().length, 1);
53
+ assert.strictEqual(pool.currentEpoch, E2);
54
+ });
55
+ it("replace clears existing tickets and dedups the new set", () => {
56
+ const pool = new PendingTicketPool();
57
+ pool.addTicket(E1, makeTicket(1));
58
+ pool.addTicket(E1, makeTicket(2));
59
+ const dump = [makeTicket(3), makeTicket(4), makeTicket(3)];
60
+ pool.replace(E2, dump);
61
+ const tickets = pool.getTickets();
62
+ assert.strictEqual(tickets.length, 2);
63
+ assert.strictEqual(pool.currentEpoch, E2);
64
+ assert.strictEqual(tickets[0].ticket.signature.raw[0], 3);
65
+ assert.strictEqual(tickets[1].ticket.signature.raw[0], 4);
66
+ });
67
+ });
@@ -0,0 +1,47 @@
1
+ import type { EntropyHash, Epoch } from "#@typeberry/block";
2
+ import type { SignedTicket } from "#@typeberry/block/tickets.js";
3
+ import { Result } from "#@typeberry/utils";
4
+ /**
5
+ * Outcome of a successful validation.
6
+ *
7
+ * `id` is the entropy hash the validator computed for this ticket. It is `null` when the
8
+ * concrete validator doesn't actually verify (e.g. {@link AcceptTicketsValidator}) or when
9
+ * it delegates to another process that doesn't bother to send the id back over the wire.
10
+ */
11
+ export type ValidatedTicket = {
12
+ ticket: SignedTicket;
13
+ id: EntropyHash;
14
+ };
15
+ /** Reasons a ticket may fail validation. */
16
+ export declare enum ValidationError {
17
+ /** Verifier rejected the signature / proof. */
18
+ InvalidProof = "invalid_proof",
19
+ /** Validator could not run (e.g. state unavailable, transient internal failure). */
20
+ ValidatorUnavailable = "validator_unavailable",
21
+ /** Ticket is for an epoch outside the validator's window of interest. */
22
+ WrongEpoch = "wrong_epoch"
23
+ }
24
+ /**
25
+ * Strategy for verifying tickets arriving from peers.
26
+ *
27
+ * The concrete implementation may call into the bandersnatch verifier, defer to another
28
+ * worker via IPC, or short-circuit (Accept/Deny defaults for tests).
29
+ */
30
+ export interface TicketValidator {
31
+ validate(epochIndex: Epoch, tickets: SignedTicket[]): Promise<Result<ValidatedTicket[], ValidationError>>;
32
+ }
33
+ /**
34
+ * Accepts every ticket without inspection. Useful for unit tests where the validator
35
+ * isn't the subject under test. Must never be used in production.
36
+ */
37
+ export declare class AcceptTicketsValidator implements TicketValidator {
38
+ validate(_epochIndex: Epoch, ticket: SignedTicket[]): Promise<Result<ValidatedTicket[], ValidationError>>;
39
+ }
40
+ /**
41
+ * Rejects every ticket. Used as the default for any task that needs an explicit, real
42
+ * validator wired in before it will accept anything from the network.
43
+ */
44
+ export declare class DenyTicketsValidator implements TicketValidator {
45
+ validate(_epochIndex: Epoch, _tickets: SignedTicket[]): Promise<Result<ValidatedTicket[], ValidationError>>;
46
+ }
47
+ //# sourceMappingURL=ticket-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket-validator.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/ticket-pool/ticket-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,WAAW,CAAC;CACjB,CAAC;AAEF,4CAA4C;AAC5C,oBAAY,eAAe;IACzB,+CAA+C;IAC/C,YAAY,kBAAkB;IAC9B,oFAAoF;IACpF,oBAAoB,0BAA0B;IAC9C,yEAAyE;IACzE,UAAU,gBAAgB;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;CAC3G;AAED;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IACtD,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;CAQhH;AAED;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IACpD,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,eAAe,CAAC,CAAC;CAGlH"}