feat(sdk): propertyConstraints rules and pre-check in the Kotlin SDK and Android example app - #5066
Conversation
…nd iOS example app rs-sdk-ffi gains dash_sdk_data_contract_get_property_constraints (a document type's rules as a JSON array, in name order, with what each reads and whether it reads $ownerId) and dash_sdk_data_contract_check_property_constraints (the first rule a document to create breaks, or JSON null). Both take the contract as its platform serialization, read at the SDK's protocol version, and match wasm-dpp2's JSON shapes. The check builds the document the way dash_sdk_document_create does (its parsing and building now shared as two helpers) and judges it with DPP's validate_property_constraints. The Swift SDK decodes both into DocumentPropertyConstraint and PropertyConstraintViolation behind thin SDK wrappers, with PersistentDocumentType helpers reading the stored contract bytes. The example app lists the rules on the document type screen and refuses to broadcast a document that breaks one, naming the rule and the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and Android example app rs-unified-sdk-jni gains two QueriesNative exports, dataContractGetPropertyConstraints and dataContractCheckPropertyConstraints, each a thin marshaler over the rs-sdk-ffi function of the same purpose (dash_sdk_data_contract_get_property_constraints and dash_sdk_data_contract_check_property_constraints): the contract's platform serialization, the document type, the properties JSON and the 32-byte owner go in, the JSON string comes back, and FFI errors keep their codes. The Kotlin SDK decodes both into DocumentPropertyConstraint, PropertyConstraintRead and PropertyConstraintViolation (unknown read kinds and violation names kept as Other(name)) behind sdk.contracts.propertyConstraints and sdk.contracts.checkPropertyConstraints. No rule is evaluated in Kotlin. The example app lists the rules on the document type screen and refuses to broadcast a document that breaks one, naming the rule, the violation and the reason. A pre-check that cannot run (no SDK, no stored contract bytes, a native library without the exports) is logged and the create goes ahead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (10)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe Kotlin SDK adds APIs to retrieve and check document property constraints through JNI. The Kotlin example app displays available constraints and checks them before document creation. A reported violation blocks creation; when the check cannot run, the app logs the reason and continues. ChangesDocument Property Constraints
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant CreateDocumentScreen
participant Contracts
participant QueriesNative
participant JNI
participant RustFFI
CreateDocumentScreen->>Contracts: checkPropertyConstraints
Contracts->>QueriesNative: dataContractCheckPropertyConstraints
QueriesNative->>JNI: Send contract, properties, and owner ID
JNI->>RustFFI: Forward constraint check
RustFFI-->>CreateDocumentScreen: Return violation or no violation
Merge Risk: ⚪ Minimal · up to No confirmed issue currently prevents merging. Native cross-compilation and device testing remain unrun. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The new check can prevent some fee-bearing rejected submissions, while document creation remains subject to the existing network validation. The added native input path and behavior when the check is unavailable warrant review, but no introduced security bypass was established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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 5a56624) · triage: normal |
Basic explanation
What this does: The Kotlin SDK and the Android example app can now list a document type's
propertyConstraintsrules and check a new document against them before sending it. Two new JNI functions pass the contract bytes, the properties and the owner to the two C functions #5064 added tors-sdk-ffi; Rust does all the reading and judging, and Kotlin only decodes the JSON that comes back.Value: The Android app now matches the iOS app: it shows each rule on the document type screen, and it refuses to broadcast a document that breaks one, naming the rule and the reason. Without this, the user learns about the broken rule only after paying for a transition that consensus refuses with error 10422. All three SDKs (JS in #5051, Swift in #5064, Kotlin here) now report rules and violations in the same shape.
Risks: Low. Everything is additive: two new JNI exports, two new SDK methods, new data classes, one new screen section and one extra step before a create. Nothing changes in consensus, Drive, DPP or
rs-sdk-ffi. If the check cannot run (no SDK, the contract was stored without its bytes, or the check fails), the app logs it and sends the document as before, so the new step never blocks a valid create. The Android native library must be rebuilt (./build_android.sh) for the app to use the new exports. With an older.sothe app does not crash: the details screen says the library predates property constraints, and creates go ahead without the pre-check. Like the Swift side, the rules are read at the SDK's current protocol version, so before the app learns that the network is at 14 it sees no rules: the pre-check can then miss a broken rule (consensus still refuses it), but it never refuses a valid document.Issue being fixed or feature implemented
The
propertyConstraintsseries (#4962, then #5036 through #5048) gave consensus a rule language for document properties, but the Kotlin SDK only surfaced the 10422 error after the fact. This is the last of three SDK PRs:The two C functions it calls came with #5064, now merged into
v4.2-dev, which this PR targets.What was done?
rs-unified-sdk-jni (
src/queries.rs)Two exports on
QueriesNative, each a thin marshaler over oners-sdk-ffifunction, with the crate's usualguard,unwrap_string(throwsDashSDKExceptionand frees the error, or copies and frees the string) andjlonghandle cast:The contract bytes are copied into a buffer that outlives the call; the owner id must be exactly 32 bytes (the C function reads 32 bytes behind the pointer), otherwise
InvalidParameteris thrown before the call. Error codes pass through unchanged, so Kotlin seesDashSdkError.NotFoundfor an unknown document type,SerializationErrorfor bytes that are not a contract andInvalidParameterfor empty bytes or properties that are not a JSON object.Kotlin SDK
ffi/QueriesNative.kt: the twoexternal fundeclarations.queries/PlatformQueries.kt:sdk.contracts.propertyConstraints(serializedContract, documentType): List<DocumentPropertyConstraint>andsdk.contracts.checkPropertyConstraints(serializedContract, documentType, propertiesJson, ownerId): PropertyConstraintViolation?. Both run under the SDK's query fence (queryGate) like every otherContractscall, since they read the SDK handle for its protocol version, and map native errors toDashSdkError.queries/DocumentPropertyConstraints.kt: the data classes, decoded with kotlinx.serialization and matching the Swift types field for field:DocumentPropertyConstraint(name, ruleJson, reads, readsOwner), withruleJsonkept as the declared rule (compact, sorted keys) andprettyRuleJsonfor display;PropertyConstraintRead(path, kind),kindone ofValue,Presence,Text,IdentifierorOther(name);PropertyConstraintViolation(rule, violation, message),violationone ofNotMet,Overflow,DivisionByZero,NegativeExponent,NotAnIntegerorOther(name), with a one-linedescription.A name a later version adds decodes to
Other(name)instead of failing the list. Malformed JSON (a missing field, a number or a string where a boolean belongs) throwsDashSdkError.SerializationError. No rule is evaluated in Kotlin.Example:
KotlinExampleApp (
ui/contracts/)PropertyConstraints.kt: the screen glue, kept out of the composables so it can be unit tested: whether a schema declares the keyword, what the details section shows (Hidden,Rules,NotEnforced,Unavailable(reason)), and the create pre-check outcome (Passed,Broken(violation),Skipped(reason)).Document type details (
DocumentTypeDetailsScreen.kt). Before: the screen listed settings, indices and properties; a type's rules were visible only in the raw contract JSON. After: a "Property Constraints (N)" section, placed after the settings as on iOS, lists each rule:The section is re-read when the SDK, its protocol version or the stored contract changes. A schema declaring rules that the current protocol version does not enforce says so; with no SDK, no stored contract bytes or an older native library, the section says why the rules cannot be read. Rows carry the iOS accessibility identifiers as test tags (
documentType.propertyConstraint.<name>).Create document (
CreateDocumentScreen.kt). Before: an offer with{"price": 100, "fee": 0}was broadcast, and consensus refused it withDocumentPropertyConstraintViolatedError(10422) after charging the fee. After: "Create / Broadcast" runs the pre-check first and nothing is sent:A document meeting every rule, or of a type declaring none, is broadcast as before. When the pre-check cannot run (no SDK, no stored contract bytes, the check itself fails, or the native library predates the exports), it logs a warning and the create proceeds, since consensus judges the document either way. As on iOS, only the create flow is pre-checked; replace and transfer (
DocumentActionsScreen.kt) are unchanged, because the C function judges a document to create.How Has This Been Tested?
sdk/src/test/.../queries/DocumentPropertyConstraintsTest.kt(13 JVM tests, no native library): the rules decode in order with their reads andreadsOwner; each rule is kept as its declared JSON (compact, sorted keys, operand order untouched) and pretty-printed to the same JSON;[]decodes to an empty list; unknown read kinds and violation names are kept asOther(name); every known name round trips; a violation decodes with itsdescription;nullmeans every rule holds; malformed payloads (not JSON, not an array, a missing field,readsOwneras a number or a string, a non-string name) throwSerializationError. Same cases as the Swift SDK'sDocumentPropertyConstraintsTests.app/src/test/.../ui/contracts/PropertyConstraintsTest.kt(13 JVM tests): the keyword check; the details section (hidden without asking Rust when no rules are declared, the rules read from the stored bytes, "not enforced" when Rust reads none, and why nothing could be read with no SDK, no bytes or a failed read); the create pre-check (passes without asking Rust when no rules are declared, stops a broken document, passes a valid one, lets the create proceed with no SDK, no bytes or a failed check, and rethrows a cancellation); a native library without the exports neither crashes nor blocks; the reads line and the alert text.sdk/src/androidTest/.../PropertyConstraintsFfiTest.kt(3 tests, run by CI'sconnectedDebugAndroidTest): both exports round trip on a trusted testnet SDK handle with the Swift test's contract fixture ([]andnull), the error codes come through (NotFound7,SerializationError4,InvalidParameter1), and an owner id of 20 bytes is refused. Compiled locally, not run (no emulator, and the.sowas not rebuilt).cargo check -p rs-unified-sdk-jni: clean.cargo clippy -p rs-unified-sdk-jni --all-targets -- -D warnings: clean.cargo test -p rs-unified-sdk-jni: 43 passed../gradlew :sdk:compileDebugKotlin :app:compileDebugKotlin :sdk:compileDebugAndroidTestKotlin: succeeded (JDK: Android Studio's bundled JBR)../gradlew :sdk:testDebugUnitTest :app:testDebugUnitTest: 501 SDK tests and 232 app tests passed (the new ones included), 0 failures.build_android.sh(the native cross-compile), so the app was not run on an emulator against the new exports; the rules section and the refusal alert were not exercised on a device.Breaking Changes
None. New JNI exports, new Kotlin API and new app UI; existing calls are unchanged. The native library has to be rebuilt to carry the new exports (see Risks).
Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
5a56624/skip-botsproceeds without the ones not yet reported/self-reviewedonce the bots are donekotlin-sdk(packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/contracts/CreateDocumentScreen.kt,packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/contracts/DocumentTypeDetailsScreen.kt,packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/contracts/PropertyConstraints.ktand 6 more) — HashEngineeringWhen every box is checked the
PR Hygienecheck passes and this can merge.Summary by CodeRabbit