Skip to content

TicketAttempt validation and encoding fix#913

Merged
tomusdrw merged 3 commits intomainfrom
td-fix-enc
Feb 19, 2026
Merged

TicketAttempt validation and encoding fix#913
tomusdrw merged 3 commits intomainfrom
td-fix-enc

Conversation

@tomusdrw
Copy link
Copy Markdown
Member

TicketAttempt was missing proper validation during decoding (same as tryAsTicketAttempt conversion).
This PR adds the validation and additionally changes the encoding of ticket attempt from fixed-size U8 to variable-size,
which is what GP specifies.
Note that for valid TicketAttempt values there is no difference, since both fixed-size and variable-size encoding will just result in a single
byte value, however it does affect some degenerate cases.

Additional context:
davxy/jam-conformance#66

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Summary by CodeRabbit

  • Refactor
    • Parsing and deserialization are now chain-spec aware, improving correctness for spec-specific data.
    • Test runners and state dumps have been updated to run with explicit chain-spec parameters.
    • Ticket generation, validation, and network ticket distribution now operate with an explicit chain-spec, enforcing bounds and consistent encoding.

Walkthrough

This change threads ChainSpec through JSON deserializers and ticket-related functions: many formerly static FromJson/exported parsers (e.g., headerFromJson, ticketsExtrinsicFromJson, ticketFromJson, StateTransitionGenesis.fromJson, TicketsOrKeys.fromJson, SafroleTest.fromJson) were converted to functions that accept a ChainSpec and call nested spec-aware constructors. Ticket attempt validation and codecs (tryAsTicketAttempt, ticketAttemptCodec) and generateTickets were updated to require a ChainSpec. Call sites across tests, runners, workers, and state dumping were updated to pass the spec.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • skoszuta
  • mateuszsikora
  • DrEverr

Poem

🐰 I hopped through parsers, light and spry,
Tucked ChainSpec in each packet as I fly.
Tickets now check bounds where numbers play,
FromJsons wear specs to lead the way.
Thump-thump — the build hops on, hooray! 🎩✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately reflects the main change: adding validation and fixing the encoding of TicketAttempt from fixed-size U8 to variable-size, which aligns with the PR objectives and the comprehensive changes across multiple files.
Description check ✅ Passed The description clearly explains the purpose of the changes: adding TicketAttempt validation during decoding and changing encoding from fixed-size to variable-size per GP spec, which directly relates to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch td-fix-enc

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/jam/safrole/bandersnatch-vrf.ts (1)

209-255: ⚠️ Potential issue | 🟠 Major

Guard ticketsPerValidator to avoid thrown errors.
With the parameter now a plain number, an oversized value will cause tryAsTicketAttempt to throw and bypass the Result error path. Add an upfront bounds check and return a Result.error instead.

🔧 Suggested fix
 async function generateTickets(
   bandersnatch: BandernsatchWasm,
   ringKeys: BandersnatchKey[],
   proverKeyIndex: number,
   key: BandersnatchSecretSeed,
   entropy: EntropyHash,
   ticketsPerValidator: number,
   chainSpec: ChainSpec,
 ): Promise<Result<SignedTicket[], null>> {
+  if (ticketsPerValidator < 0 || ticketsPerValidator > chainSpec.ticketsPerValidator) {
+    return Result.error(
+      null,
+      () =>
+        `ticketsPerValidator ${ticketsPerValidator} is out of bounds [0, ${chainSpec.ticketsPerValidator}]`,
+    );
+  }
   // Build VRF inputs: JAM_TICKET_SEAL || entropy || attempt_byte for each attempt
   const vrfInputParts: Uint8Array[] = [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/jam/safrole/bandersnatch-vrf.ts` around lines 209 - 255, Add an
upfront guard in generateTickets to validate ticketsPerValidator before
generating VRF inputs: call tryAsTicketAttempt(ticketsPerValidator - 1,
chainSpec) wrapped in a try/catch (or otherwise validate the max allowed
attempt) and if it throws/invalid return Result.error(null, () => `Invalid
ticketsPerValidator: ${ticketsPerValidator}`); this prevents tryAsTicketAttempt
from throwing inside the loop and ensures failures use the Result.error path
instead of throwing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/jam/safrole/bandersnatch-vrf.ts`:
- Around line 209-255: Add an upfront guard in generateTickets to validate
ticketsPerValidator before generating VRF inputs: call
tryAsTicketAttempt(ticketsPerValidator - 1, chainSpec) wrapped in a try/catch
(or otherwise validate the max allowed attempt) and if it throws/invalid return
Result.error(null, () => `Invalid ticketsPerValidator: ${ticketsPerValidator}`);
this prevents tryAsTicketAttempt from throwing inside the loop and ensures
failures use the Result.error path instead of throwing.

@github-actions
Copy link
Copy Markdown

View all
File Benchmark Ops
bytes/hex-from.ts[0] parse hex using Number with NaN checking 48431.54 ±0.7% 89.25% slower
bytes/hex-from.ts[1] parse hex from char codes 450643.69 ±0.6% fastest ✅
bytes/hex-from.ts[2] parse hex from string nibbles 246889.08 ±0.45% 45.21% slower
math/mul_overflow.ts[0] multiply and bring back to u32 92773687.59 ±5.55% 3.24% slower
math/mul_overflow.ts[1] multiply and take modulus 95883753.33 ±5.45% fastest ✅
math/switch.ts[0] switch 94532069.9 ±5.38% 1.86% slower
math/switch.ts[1] if 96327603.63 ±4.21% fastest ✅
math/add_one_overflow.ts[0] add and take modulus 101742300.66 ±3.99% fastest ✅
math/add_one_overflow.ts[1] condition before calculation 97634732.98 ±4.84% 4.04% slower
math/count-bits-u32.ts[0] standard method 49368320.5 ±2.31% 46.35% slower
math/count-bits-u32.ts[1] magic 92012088.31 ±3.54% fastest ✅
math/count-bits-u64.ts[0] standard method 5327663.19 ±0.53% 78.13% slower
math/count-bits-u64.ts[1] magic 24359977.16 ±0.87% fastest ✅
collections/map-set.ts[0] 2 gets + conditional set 85544.33 ±0.21% fastest ✅
collections/map-set.ts[1] 1 get 1 set 48770.49 ±0.11% 42.99% slower
hash/index.ts[0] hash with numeric representation 70.91 ±0.2% 28.65% slower
hash/index.ts[1] hash with string representation 37.61 ±0.49% 62.16% slower
hash/index.ts[2] hash with symbol representation 68.9 ±0.27% 30.67% slower
hash/index.ts[3] hash with uint8 representation 68.02 ±0.32% 31.56% slower
hash/index.ts[4] hash with packed representation 99.38 ±0.31% fastest ✅
hash/index.ts[5] hash with bigint representation 71.02 ±2.59% 28.54% slower
hash/index.ts[6] hash with uint32 representation 81.75 ±0.87% 17.74% slower
logger/index.ts[0] console.log with string concat 5087900.33 ±26.96% fastest ✅
logger/index.ts[1] console.log with args 1256143.59 ±70.29% 75.31% slower
bytes/hex-to.ts[0] number toString + padding 81747.96 ±0.5% fastest ✅
bytes/hex-to.ts[1] manual 6655.19 ±0.54% 91.86% slower
codec/bigint.compare.ts[0] compare custom 95507302.39 ±4.15% fastest ✅
codec/bigint.compare.ts[1] compare bigint 87041298.66 ±5.45% 8.86% slower
codec/bigint.decode.ts[0] decode custom 70447913.82 ±3.43% fastest ✅
codec/bigint.decode.ts[1] decode bigint 37353630.25 ±1.26% 46.98% slower
codec/decoding.ts[0] manual decode 7332628.7 ±0.65% 88.82% slower
codec/decoding.ts[1] int32array decode 64317614.04 ±3.69% 1.96% slower
codec/decoding.ts[2] dataview decode 65605744.87 ±2.86% fastest ✅
codec/encoding.ts[0] manual encode 891065.07 ±0.54% 14.9% slower
codec/encoding.ts[1] int32array encode 1027697.74 ±0.62% 1.85% slower
codec/encoding.ts[2] dataview encode 1047081.5 ±0.54% fastest ✅
codec/view_vs_collection.ts[0] Get first element from Decoded 11761.95 ±0.52% 57.11% slower
codec/view_vs_collection.ts[1] Get first element from View 27421.74 ±0.49% fastest ✅
codec/view_vs_collection.ts[2] Get 50th element from Decoded 11695.1 ±0.35% 57.35% slower
codec/view_vs_collection.ts[3] Get 50th element from View 13907.45 ±0.71% 49.28% slower
codec/view_vs_collection.ts[4] Get last element from Decoded 11360.65 ±0.83% 58.57% slower
codec/view_vs_collection.ts[5] Get last element from View 9646.35 ±0.45% 64.82% slower
codec/view_vs_object.ts[0] Get the first field from Decoded 175733.79 ±0.59% 0.81% slower
codec/view_vs_object.ts[1] Get the first field from View 44919.52 ±0.51% 74.65% slower
codec/view_vs_object.ts[2] Get the first field as view from View 44392.63 ±0.61% 74.94% slower
codec/view_vs_object.ts[3] Get two fields from Decoded 177165.48 ±0.5% fastest ✅
codec/view_vs_object.ts[4] Get two fields from View 35004.87 ±0.64% 80.24% slower
codec/view_vs_object.ts[5] Get two fields from materialized from View 70188.05 ±0.9% 60.38% slower
codec/view_vs_object.ts[6] Get two fields as views from View 35302.24 ±0.55% 80.07% slower
codec/view_vs_object.ts[7] Get only third field from Decoded 111376.6 ±72.89% 37.13% slower
codec/view_vs_object.ts[8] Get only third field from View 42550.24 ±1.79% 75.98% slower
codec/view_vs_object.ts[9] Get only third field as view from View 43954.16 ±0.57% 75.19% slower
collections/hash-dict-vs-blob-dict_set.ts[0] StringHashDictionary 1513.84 ±1.9% 2.07% slower
collections/hash-dict-vs-blob-dict_set.ts[1] BlobDictionary(1) 1545.86 ±0.79% fastest ✅
collections/hash-dict-vs-blob-dict_set.ts[2] BlobDictionary(2) 1505.1 ±0.72% 2.64% slower
collections/hash-dict-vs-blob-dict_set.ts[3] BlobDictionary(3) 1535.98 ±0.65% 0.64% slower
collections/hash-dict-vs-blob-dict_set.ts[4] BlobDictionary(4) 1527.19 ±0.78% 1.21% slower
collections/hash-dict-vs-blob-dict_set.ts[5] BlobDictionary(5) 1513.71 ±0.26% 2.08% slower
collections/hash-dict-vs-blob-dict_delete.ts[0] StringHashDictionary 2016.43 ±0.89% 3.43% slower
collections/hash-dict-vs-blob-dict_delete.ts[1] BlobDictionary(1) 2069.16 ±0.82% 0.9% slower
collections/hash-dict-vs-blob-dict_delete.ts[2] BlobDictionary(2) 2072.64 ±0.56% 0.73% slower
collections/hash-dict-vs-blob-dict_delete.ts[3] BlobDictionary(3) 2082.39 ±0.6% 0.27% slower
collections/hash-dict-vs-blob-dict_delete.ts[4] BlobDictionary(4) 2087.97 ±0.48% fastest ✅
collections/hash-dict-vs-blob-dict_delete.ts[5] BlobDictionary(5) 2071.86 ±0.52% 0.77% slower
collections/hash-dict-vs-blob-dict_get.ts[0] StringHashDictionary 2125.6 ±0.33% fastest ✅
collections/hash-dict-vs-blob-dict_get.ts[1] BlobDictionary(1) 2114.82 ±0.61% 0.51% slower
collections/hash-dict-vs-blob-dict_get.ts[2] BlobDictionary(2) 2104.38 ±0.41% 1% slower
collections/hash-dict-vs-blob-dict_get.ts[3] BlobDictionary(3) 2089.31 ±0.52% 1.71% slower
collections/hash-dict-vs-blob-dict_get.ts[4] BlobDictionary(4) 2099.05 ±0.46% 1.25% slower
collections/hash-dict-vs-blob-dict_get.ts[5] BlobDictionary(5) 2097.34 ±0.54% 1.33% slower
collections/map_vs_sorted.ts[0] Map 126304.49 ±0.12% fastest ✅
collections/map_vs_sorted.ts[1] Map-array 44421.55 ±0.14% 64.83% slower
collections/map_vs_sorted.ts[2] Array 25984.68 ±3.89% 79.43% slower
collections/map_vs_sorted.ts[3] SortedArray 79317.03 ±2.32% 37.2% slower
bytes/bytes-to-number.ts[0] Conversion with bitops 3480.32 ±5.93% 4.35% slower
bytes/bytes-to-number.ts[1] Conversion without bitops 3638.76 ±5.86% fastest ✅
bytes/compare.ts[0] Comparing Uint32 bytes 10274.17 ±0.23% 3.54% slower
bytes/compare.ts[1] Comparing raw bytes 10651.77 ±0.47% fastest ✅
hash/blake2b.ts[0] our hasher 1.13 ±0.26% fastest ✅
hash/blake2b.ts[1] blake2b js 0.03 ±0.32% 97.35% slower
crypto/ed25519.ts[0] native crypto 3.35 ±22.15% fastest ✅
crypto/ed25519.ts[1] wasm lib 2.11 ±0.55% 37.01% slower
crypto/ed25519.ts[2] wasm lib batch 2.09 ±1.62% 37.61% slower

Benchmarks summary: 83/83 OK ✅

@tomusdrw tomusdrw added this pull request to the merge queue Feb 19, 2026
Merged via the queue into main with commit f0cc874 Feb 19, 2026
14 checks passed
@tomusdrw tomusdrw deleted the td-fix-enc branch February 19, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants