feat(rs-sdk): own the masternode voting-key facts - #4340
Conversation
Casting a contested-resource vote needs three things that are properties of
Platform, not of any binding: which key a voter identity holds, how to
recognise that Platform rejected that key, and what the rejection meant.
All three lived in `rs-sdk-ffi`, so Swift and Kotlin callers got them by
routing through the FFI while Rust callers of `PutVote` did not — and had
to rediscover them, including the byte-order rule that makes a vote address
an identity that has never existed.
Adds `platform::transition::masternode_vote_keys`:
* `voter_identity_id` takes a typed `ProTxHash` rather than bytes. The
derivation is orientation-sensitive and `Txid` bytes for the same
transaction are its exact reverse, so passing the wrong one silently
yields a nonexistent identity. The type is the guard; a doc comment was
not enough to stop it happening once already.
* `voter_identity_voting_key` builds the key Platform holds, so a caller
can broadcast without first fetching the identity.
* `select_voting_key` picks the usable key off a fetched identity by its
own data, not by position — position cannot tell a live key from one a
rotation disabled.
* `is_voting_key_failure` gates diagnosis on the typed consensus error.
Without it, diagnosing every failure lets an absent voter identity
masquerade as the cause of a closed poll or a transport error.
* `diagnose_voting_key_failure` returns a typed `VotingKeyProblem` rather
than a formatted string: rs-sdk reports the fact, bindings phrase it.
Signing stays with the caller. Producing a `Signer` from a raw key is a
binding concern, and pulling `simple-signer` into the public SDK to do it
here would be the wrong trade.
No behaviour change on its own — `rs-sdk-ffi` moves onto these separately,
so this can be reviewed as the API it is.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe Rust SDK adds a public transition module with utilities for voter identity derivation, voting-key construction and selection, consensus failure classification, and asynchronous diagnosis. Tests cover byte ordering, key attributes, disabled or mismatched keys, and supported failures. ChangesVoting key utilities
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant diagnose_voting_key_failure
participant Sdk
participant Platform
Caller->>diagnose_voting_key_failure: provide ProTxHash and voting address
diagnose_voting_key_failure->>Sdk: fetch voter identity
Sdk->>Platform: request identity by derived Identifier
Platform-->>Sdk: return identity or fetch error
Sdk-->>diagnose_voting_key_failure: return identity result
diagnose_voting_key_failure->>diagnose_voting_key_failure: select_voting_key
diagnose_voting_key_failure-->>Caller: return VotingKeyProblem or None
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
🕓 Ready for review — 1 ahead in queue (commit e1465ed) |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/rs-sdk/src/platform/transition/masternode_vote_keys.rs (1)
254-256: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the dead reversal block.
bytesandreverseddo not affect the assertions.asymmetricalready tests orientation behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-sdk/src/platform/transition/masternode_vote_keys.rs` around lines 254 - 256, Remove the unused bytes and reversed initialization/reversal block from the affected test, leaving the existing assertions and asymmetric orientation coverage unchanged.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/rs-sdk/src/platform/transition/masternode_vote_keys.rs`:
- Around line 254-256: Remove the unused bytes and reversed
initialization/reversal block from the affected test, leaving the existing
assertions and asymmetric orientation coverage unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 468d71ef-eea9-422f-bdb5-20ebc2b115b5
📒 Files selected for processing (2)
packages/rs-sdk/src/platform/transition.rspackages/rs-sdk/src/platform/transition/masternode_vote_keys.rs
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v4.2-dev #4340 +/- ##
============================================
- Coverage 87.62% 87.62% -0.01%
============================================
Files 2704 2704
Lines 345206 345211 +5
============================================
Hits 302474 302474
- Misses 42732 42737 +5
🚀 New features to boost your workflow:
|
Follow-up to #4333, which fixed a masternode voting bug — and in doing so put a pile of Platform domain knowledge in the FFI shim.
The problem
Casting a contested-resource vote needs three things that are properties of Platform, not of any binding:
All three live in
rs-sdk-ffi. So Swift and Kotlin callers get them — they route through the FFI — while a Rust caller ofPutVotedoes not, and has to rediscover them.That is not hypothetical.
get_voting_identity_idderives the voter identity fromvoter_pro_tx_hash.as_bytes()verbatim, and until #4333 nothing said which orientation that had to be. A caller holding wire/Txidbytes addresses an identity that has never existed and gets "no voter identity" back. That cost real debugging time, and the FFI's.reverse()only protects callers who go through the FFI.What this adds
platform::transition::masternode_vote_keys:voter_identity_idProTxHash, not bytesvoter_identity_voting_keyselect_voting_keyis_voting_key_failurediagnose_voting_key_failureVotingKeyProblemTwo choices worth calling out:
The type is the guard, not the doc.
voter_identity_idtakesProTxHashbecause the derivation is orientation-sensitive andTxidbytes for the same transaction are its exact reverse. A doc comment already proved insufficient — this bug happened once with the contract undocumented, and documenting it (#4333) helps only readers. The type helps everyone.diagnose_voting_key_failurereturns a fact, not a sentence.VotingKeyProblemis an enum; bindings phrase it for their users. Formatting messages in the SDK would make every caller inherit English strings they cannot localise.Signing stays with the caller. Producing a
Signerfrom a raw key is a binding concern, and pullingsimple-signerinto the public SDK to do it here would be the wrong trade — so this covers the key facts, not a fullcast_vote.Testing
10 tests, all in-process:
create_voter_identity_v0assigns (id 0, VOTING, ECDSA_HASH160, enabled)ProTxHashchanges it, which is the property that made votes failScope
No behaviour change on its own — this is the API. Moving
rs-sdk-ffionto it is a separate PR so this can be reviewed as the surface it is, and so #4333 (already several rounds in) is not reopened.🤖 Generated with Claude Code
Summary by CodeRabbit