Skip to content

fix(sdk-coin-polyx): forward caller material into v8 batch staking builder on rebuild [SI-1034]#9290

Merged
abhijit0943 merged 1 commit into
masterfrom
SI-1034
Jul 20, 2026
Merged

fix(sdk-coin-polyx): forward caller material into v8 batch staking builder on rebuild [SI-1034]#9290
abhijit0943 merged 1 commit into
masterfrom
SI-1034

Conversation

@abhijit0943

Copy link
Copy Markdown
Contributor

Summary

Custodial Polymesh v8 stake fails at Trust approval with InvalidSignature: invalid key signature in TPOLYX.getSignedTx, while custodial transfers on the same wallet succeed.

Root cause (proven from staging Loki + payload decode): the tx is signed by the HSM against live chain material (specVersion 8000010) but re-verified against stale static material (getV8Material(), specVersion 8000000). In TransactionBuilderFactory.getBuilder(), the v8 batchAll(bond,nominate) decode-success path returned getV8BatchStakingBuilder() without forwarding this._material — unlike every v7 getter. So factory.material(liveMaterial).from(serializedTxHex) (what wallet-platform's getSignedTx does) silently dropped the live material; the rebuilt signable payload used a stale specVersion and no longer matched the signed bytes.

Transfers work because their getters (getTransferBuilder/getHexTransferBuilder) already forward this._material.

Change

One line, on the decode-success path only:

-            return this.getV8BatchStakingBuilder();
+            return this.getV8BatchStakingBuilder().material(this._material);

The v8 getters and the tryGetV8Builder() decode-failure catch path are intentionally left untouched — that path relies on the static v8 default when this._material cannot decode the tx.

Test

New unit test in v8BatchStakingBuilder.ts: bumps specVersion by 10 on a copy of testnetV8Material (simulating the real 8000000 → 8000010 chain upgrade), builds → serializes → rebuilds via factory.material(liveMaterial).from(...), and asserts the rebuilt signablePayload equals the original.

  • Without the fix: fails with the exact production byte diff — …00127a00… (static 8000000) vs …0a127a00… (live 8000010), rest of payload byte-identical.
  • With the fix: 29 passing in the v8 batch staking suite.

Notes

  • Draft. Depends on the SI-981 routing commit (c22ca5c633), which is included in this branch's diff until SI-981 lands on master.
  • Must deploy to staging and retest custodial stake (1-validator and multi-validator) before merging.
  • Follow-up (separate): refresh the stale static testnetV8Material/mainnetV8Material specVersion so the decode-failure catch path stays valid across future runtime upgrades.

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

SI-1034

…ilder on decode-success path

getBuilder() routed the v8 batchAll(bond,nominate) decode-success path to
getV8BatchStakingBuilder() without forwarding this._material, unlike every
other getter. When wallet-platform's getSignedTx re-verifies a custodial v8
stake via factory.material(liveMaterial).from(serializedTxHex), the live
material was silently dropped in favor of the builder's static
getV8Material(), so the rebuilt signable payload used a stale specVersion and
no longer matched the HSM-signed bytes, causing
InvalidSignature: invalid key signature at Trust approval. SI-1034.

TICKET: SI-1034
@abhijit0943

Copy link
Copy Markdown
Contributor Author

@claude review

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @abhijit0943's task —— View job


Code Review Complete ✅

PR Summary: This PR fixes a critical issue in Polymesh v8 staking where custodial transactions fail with InvalidSignature due to material version mismatch between signing and verification.

Fix Implementation Review

Problem Analysis:

  • Root Cause: In transactionBuilderFactory.ts:238, the v8 batch staking decode-success path returned getV8BatchStakingBuilder() without forwarding this._material
  • Impact: HSM signs against live chain material (e.g., specVersion: 8000010) but verification uses stale static material (specVersion: 8000000)
  • Result: Signable payload mismatch → InvalidSignature: invalid key signature

Solution Quality: ⭐⭐⭐⭐⭐

// Before (line 230)
return this.getV8BatchStakingBuilder();

// After (line 238) 
return this.getV8BatchStakingBuilder().material(this._material);

Strengths:

  1. Surgical Fix: Minimal, targeted change that addresses root cause precisely
  2. Consistent Pattern: Matches how all v7 getters forward this._material (lines 53-101)
  3. Excellent Documentation: Comprehensive comment explaining the issue and solution context
  4. Selective Application: Only affects decode-success path; intentionally preserves catch path behavior for static v8 fallback

Test Implementation Review

Test Quality: ⭐⭐⭐⭐⭐

The new test in v8BatchStakingBuilder.ts (lines 175-217) is exceptionally well-designed:

  1. Realistic Scenario: Simulates actual production issue by bumping specVersion by 10 (8000000 → 8000010)
  2. Complete Workflow: Build → serialize → rebuild via factory.material(liveMaterial).from(...)
  3. Precise Validation: Compares signablePayload hex strings to verify material forwarding
  4. Clear Documentation: Excellent comments explaining the custodial staking workflow and failure mode

Test Coverage: Covers the exact production scenario where wallet-platform calls getSignedTxfactory.material(liveMaterial).from(serializedTxHex).

Architecture & Code Quality

Design Consistency: ⭐⭐⭐⭐⭐

  • Aligns perfectly with existing v7 builder patterns (all forward this._material)
  • Maintains backward compatibility on decode-failure catch path
  • No breaking changes to public API

Code Safety: ⭐⭐⭐⭐⭐

  • Zero risk of regression - only adds missing material forwarding
  • Preserves existing error handling paths
  • Well-scoped change with clear boundaries

Security & Performance

Security: ✅ No security concerns

  • Fix prevents signature verification failures (improves security posture)
  • No credential handling or sensitive data exposure

Performance: ✅ Zero performance impact

  • Single method call addition
  • No computational overhead

🎯 Recommendations

  1. Immediate: Ready to merge - this is a high-quality, well-tested fix for a critical production issue
  2. Follow-up: As noted in PR description, consider refreshing static testnetV8Material/mainnetV8Material to keep decode-failure catch path valid
  3. Testing: Strongly recommend staging deployment validation as mentioned in PR notes

📝 Minor Observations

  • Import addition of Interface is correctly scoped to test file only
  • Comment formatting and technical accuracy are excellent
  • Test method name clearly identifies the ticket (SI-1034) for traceability

Overall Assessment

Rating: ⭐⭐⭐⭐⭐ Excellent

This is a production-critical fix implemented with exceptional care:

  • Precise problem diagnosis and surgical solution
  • Comprehensive test coverage that reproduces the exact production failure
  • Clear documentation of complex chain material versioning concepts
  • Zero risk of regression with significant upside

Recommendation: Approve and prioritize for deployment


Job Run | Branch: SI-1034

@abhijit0943
abhijit0943 marked this pull request as ready for review July 20, 2026 10:15
@abhijit0943
abhijit0943 requested a review from a team as a code owner July 20, 2026 10:15
@abhijit0943
abhijit0943 merged commit 051870d into master Jul 20, 2026
25 checks passed
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