fix(kotlin-sdk): unmanaged-identity reads return absence + typed SigningKeyUnavailable (split from #4183)#4191
Conversation
…otFound Split out of dashpay#4183 (port of dashpay#4060) per review: the unmanaged-identity read fixes are conflict-free on v4.1-dev and should not be blocked on the Keystore rework. The FFI's blanket Option -> result conversion reports an identity the wallet does not manage as PlatformWalletFFIResultCode::NotFound (98), so the Kotlin callers' zero-handle checks were dead and every local read over an unmanaged identity threw instead of returning absence. - Dashpay: route getManagedIdentity through translateManagedIdentityNotFoundToZero so contacts()/syncState()/ payments()/sendContactRequest() treat "not managed" as null/empty/ false; ErrorInvalidHandle still propagates. - Sweep the remaining dead `== 0L` sites: ManagedPlatformWallet .inMemoryIdentityStates (one unmanaged/just-removed id no longer throws through the whole listing) and IdentityRegistration .contestedDpnsNames (the intended "identity is not managed by this wallet" NotFound now actually surfaces). - platform-wallet-ffi: platform_wallet_get_managed_identity keeps the three outcomes distinct (classify_managed_identity_outcome) so a stale/removed wallet surfaces ErrorInvalidHandle and never masquerades as an unmanaged identity; unit tests pin both arms. - DashSdkError: name the code (PLATFORM_WALLET_NOT_FOUND_CODE = 98); the 98 -> DashSdkError.NotFound mapping is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split out of dashpay#4183 (port of dashpay#4060) per review: the typed signing error is conflict-free on v4.1-dev and should not be blocked on the Keystore rework. The KeystoreSigner "missing key" completion error travels as free text through Rust and used to come back as an opaque PlatformWallet.Generic (or WalletOperation) failure. It is now built from a shared MESSAGE_MARKER constant and recognized on the Kotlin boundary as the typed DashSdkError.PlatformWallet.SigningKeyUnavailable, so hosts can route users to key repair instead of showing a generic error. The marker is only consulted on the catch-all codes (6 / else), so the dedicated retry-semantics types are never overridden. Known limitation (kept as-is from dashpay#4183 by request): the discriminator is message-text-based (message.contains on the marker); a structured error code across the FFI boundary is follow-up work in the parent PR line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 14 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🔍 Review in progress — actively reviewing now (commit ce3a17d) |
…the signer wire Replace end-to-end message sniffing for the signer's "missing key" failure with a typed discriminator (dashpay#4060 finding 7): - rs-sdk-ffi: DashSDKSignerErrorCode { Generic = 0, SigningKeyUnavailable = 1, AuthenticationFailed = 2 (reserved) }; SignCompletionCallback and dash_sdk_sign_async_completion gain error_code: i32 (before error_message). SignResult stays Result<Vec<u8>, ProtocolError> (a new rs-dpp ProtocolError variant would carry serialization blast radius), so code 1 rides the single Rust-owned machine prefix DASH_SDK_SIGNER_ERR_KEY_UNAVAILABLE_PREFIX through ProtocolError::Generic — typed at both ABI edges, one constant bridging the string segment. This is an internal coordinated ABI change: every piece versions together in this monorepo. - rs-platform-wallet-ffi: PlatformWalletFFIResultCode:: ErrorSigningKeyUnavailable = 31 (codes 26-28 are reserved for dashpay#4185's reservation-token errors and 29/30 for dashpay#4184's asset-lock errors on sibling branches — documented in the enum as dashpay#4184 does). The From<dpp::ProtocolError> conversion restores the typed code from the prefix FIRST (before the loose keyword sniffs), and the From<PlatformWalletError> blanket impl restores it on the catch-all only (dedicated retry-semantics codes are never overridden) — covering the Sdk(dash_sdk::Error::Protocol(..)) wrapping path. - JNI/Kotlin: SignerNative.completeSign(token, signature, errorCode, errorMessage); KeystoreSigner passes SIGNER_ERROR_CODE_KEY_UNAVAILABLE on the null-key branch (keeping the MESSAGE_MARKER text for the transition window) and Generic everywhere else. DashSdkError maps 31 → PlatformWallet.SigningKeyUnavailable; the dashpay#4191 marker sniff on the catch-all codes remains as a deprecated old-native fallback with a removal note tied to the next minor release. - Swift: KeychainSigner trampolines forward the code (missing-row / missing-scalar outcomes classify as 1); PlatformWalletResultCode gains errorSigningKeyUnavailable = 31 → PlatformWalletError .signingKeyUnavailable (Kotlin parity). - Tests: rs-sdk-ffi completion-code tests (prefix present for code 1, absent for generic), platform-wallet-ffi prefix→31 tests on both conversion points, Kotlin code-31 + fallback-marker tests, Swift mapping and trampoline-classifier tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g-2 TOCTOU window Round-2 nits (dashpay#4060): - The MESSAGE_MARKER fallback's stated rationale — "old native library + new Kotlin partial builds" — was not viable: the sign-completion JNI arity change (3→4 args) makes that pairing a type-confused native call on every completion, so mixed artifacts are unsupported outright. The fallback's real purpose is the dashpay#4191 merge-order transition (whose marker-based classification predates the typed code 31) plus defense in depth for conversion paths that lose the machine prefix. KDoc/comments in DashSdkError, KeystoreSigner, DashSdkErrorTest, and the parity/leftovers docs now say so; removal horizon unchanged (next minor release). - retrievePrivateKey rung 2 gains a comment naming the accepted fingerprint-read→decrypt TOCTOU window (pre-existing dashpay#4172 parity): a concurrent rotation fails as a wrong-key crypto error into the recovery ladder — never stale plaintext. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed the split (two independent passes): exactly the two conflict-free slices, semantically unchanged from the earlier review, with all five |
…the signer wire Replace end-to-end message sniffing for the signer's "missing key" failure with a typed discriminator (dashpay#4060 finding 7): - rs-sdk-ffi: DashSDKSignerErrorCode { Generic = 0, SigningKeyUnavailable = 1, AuthenticationFailed = 2 (reserved) }; SignCompletionCallback and dash_sdk_sign_async_completion gain error_code: i32 (before error_message). SignResult stays Result<Vec<u8>, ProtocolError> (a new rs-dpp ProtocolError variant would carry serialization blast radius), so code 1 rides the single Rust-owned machine prefix DASH_SDK_SIGNER_ERR_KEY_UNAVAILABLE_PREFIX through ProtocolError::Generic — typed at both ABI edges, one constant bridging the string segment. This is an internal coordinated ABI change: every piece versions together in this monorepo. - rs-platform-wallet-ffi: PlatformWalletFFIResultCode:: ErrorSigningKeyUnavailable = 31 (codes 26-28 are reserved for dashpay#4185's reservation-token errors and 29/30 for dashpay#4184's asset-lock errors on sibling branches — documented in the enum as dashpay#4184 does). The From<dpp::ProtocolError> conversion restores the typed code from the prefix FIRST (before the loose keyword sniffs), and the From<PlatformWalletError> blanket impl restores it on the catch-all only (dedicated retry-semantics codes are never overridden) — covering the Sdk(dash_sdk::Error::Protocol(..)) wrapping path. - JNI/Kotlin: SignerNative.completeSign(token, signature, errorCode, errorMessage); KeystoreSigner passes SIGNER_ERROR_CODE_KEY_UNAVAILABLE on the null-key branch (keeping the MESSAGE_MARKER text for the transition window) and Generic everywhere else. DashSdkError maps 31 → PlatformWallet.SigningKeyUnavailable; the dashpay#4191 marker sniff on the catch-all codes remains as a deprecated old-native fallback with a removal note tied to the next minor release. - Swift: KeychainSigner trampolines forward the code (missing-row / missing-scalar outcomes classify as 1); PlatformWalletResultCode gains errorSigningKeyUnavailable = 31 → PlatformWalletError .signingKeyUnavailable (Kotlin parity). - Tests: rs-sdk-ffi completion-code tests (prefix present for code 1, absent for generic), platform-wallet-ffi prefix→31 tests on both conversion points, Kotlin code-31 + fallback-marker tests, Swift mapping and trampoline-classifier tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g-2 TOCTOU window Round-2 nits (dashpay#4060): - The MESSAGE_MARKER fallback's stated rationale — "old native library + new Kotlin partial builds" — was not viable: the sign-completion JNI arity change (3→4 args) makes that pairing a type-confused native call on every completion, so mixed artifacts are unsupported outright. The fallback's real purpose is the dashpay#4191 merge-order transition (whose marker-based classification predates the typed code 31) plus defense in depth for conversion paths that lose the machine prefix. KDoc/comments in DashSdkError, KeystoreSigner, DashSdkErrorTest, and the parity/leftovers docs now say so; removal horizon unchanged (next minor release). - retrievePrivateKey rung 2 gains a comment naming the accepted fingerprint-read→decrypt TOCTOU window (pre-existing dashpay#4172 parity): a concurrent rotation fails as a wrong-key crypto error into the recovery ladder — never stale plaintext. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Splits the two conflict-free, review-approved slices out of #4183 so they aren't blocked on the
Keystore rework, as suggested by @shumkov in review.
What's included
Unmanaged-identity local reads return absence, not an exception. The FFI's blanket
Option → resultconversion reports an identity the wallet does not manage asPlatformWalletFFIResultCode::NotFound(98), so the Kotlin callers' zero-handle checks were dead andany local read over an unmanaged identity (e.g.
Dashpay.syncStateon a contact's identity) threwinstead of returning null/empty.
DashpayroutesgetManagedIdentitythrough atranslateManagedIdentityNotFoundToZerohelper:contacts()/syncState()/payments()/sendContactRequest()treat "not managed" asnull / empty / false.
ErrorInvalidHandlestill propagates — a stale wallet handle nevermasquerades as an unmanaged identity.
== 0Lsites flagged in review:ManagedPlatformWallet.inMemoryIdentityStates()(one unmanaged id no longer throws through thewhole listing) and
IdentityRegistration.contestedDpnsNames()(the intended "identity is notmanaged by this wallet" error now actually surfaces).
platform-wallet-ffi:platform_wallet_get_managed_identitykeeps the three lookup outcomesdistinct (handle absent / wallet removed from the manager map →
ErrorInvalidHandle; valid walletnot managing the id →
NotFound), with unit tests pinning both error arms.Typed
SigningKeyUnavailable. TheKeystoreSigner"missing key" completion error travels asfree text through Rust and came back as an opaque
Generic/WalletOperationfailure. It is nowbuilt from a shared
MESSAGE_MARKERconstant and recognized at the Kotlin boundary asDashSdkError.PlatformWallet.SigningKeyUnavailable, so hosts can route users to key repair. Themarker is only consulted on the catch-all codes, so dedicated retry-semantics types are never
overridden.
Known limitation, kept deliberately: the discriminator is still message-text-based
(
message.contains); moving to a structured code across the FFI boundary is follow-up work in theparent PR line.
What's NOT included (stays in #4183)
KeySecurityPolicy(AUTH_GATED/DEVICE_BOUND), the keys-alias split andlegacy-blob migration matrix, decrypt-probing, pending-identity-key state, and their tests.
PlatformWallet.NotFound(this PR keeps theexisting 98 →
DashSdkError.NotFoundmapping, only naming the code viaPLATFORM_WALLET_NOT_FOUND_CODE).Testing
:sdk:compileDebugKotlinand:sdk:testDebugUnitTest(new:ManagedIdentityNotFoundTranslationTest,two
SigningKeyUnavailablemapping tests; existing 98-mapping tests unchanged and passing).cargo test -p platform-wallet-ffidashpay unit tests, including the two new outcome-classificationtests.
🤖 Generated with Claude Code