feat(sdk): propertyConstraints rules and pre-check in the Swift SDK and iOS example app - #5064
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>
|
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. 📝 WalkthroughWalkthroughAdds protocol-version-aware property-constraint retrieval and checking to the Rust FFI and Swift SDK. Persisted document types expose the operations. The example app displays declared constraints and checks proposed document properties before broadcasting. ChangesDocument property constraints
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~60 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant CreateDocumentView
participant PersistentDocumentType
participant SDK
participant RustFFI
participant DocumentBroadcaster
CreateDocumentView->>PersistentDocumentType: check proposed properties and owner ID
PersistentDocumentType->>SDK: checkDocumentPropertyConstraints
SDK->>RustFFI: dash_sdk_data_contract_check_property_constraints
RustFFI-->>SDK: violation or JSON null
SDK-->>PersistentDocumentType: decoded violation or nil
PersistentDocumentType-->>CreateDocumentView: violation or nil
CreateDocumentView->>DocumentBroadcaster: broadcast when no violation blocks submission
Suggested reviewers: Merge Risk: ⚪ Minimal · up to No confirmed issue prevents merging after normal checks. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to The pre-check can miss a rule when contract data or the protocol version is stale, or when the check fails. A document may then be sent and rejected at a cost. Network validation still decides whether the document is accepted. 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 a5fac13) · triage: normal |
The workspace build compiles dpp's JSON schema validation in, and the protocol version 13 meta-schema refuses the propertyConstraints keyword, so the fixture could not be created at 13. The test now reads the bytes a network at 14 returns with an SDK still at 13, the case it is meant to cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Basic explanation
What this does: The Swift SDK and the iOS example app can now list a document type's
propertyConstraintsrules and check a new document against them before sending it. Two new C functions inrs-sdk-ffido all the work in Rust, with the same check consensus runs; Swift only passes the contract bytes, the properties and the owner in, and decodes the JSON that comes back.Value: The example app shows each rule on the document type screen, and refuses to broadcast a document that breaks one, naming the rule and the reason. Without this, the user finds out only after paying for a transition that consensus refuses with error 10422. The same two C functions will back the Kotlin SDK next, so every SDK reports rules and violations in the same shape as the JS SDK (#5051).
Risks: Low. Everything is additive except one small refactor:
dash_sdk_document_createnow shares its properties parsing and document building with the pre-check through two helpers, with the same steps and the same error messages. Nothing changes in consensus, Drive or DPP. The rules are read at the SDK's current protocol version, so an SDK that has not yet learned the network is at 14 reports no rules: the pre-check can then miss a broken rule (consensus still refuses it), but it never blocks a valid document.Issue being fixed or feature implemented
The
propertyConstraintsseries (#4962, then #5036 through #5048) gave consensus a rich rule language, but the Swift SDK only surfaced the 10422 error after the fact. This is the second of three SDK PRs:What was done?
rs-sdk-ffi: two C functions (
src/data_contract/property_constraints.rs)Both return a
DashSDKResultwhosedatais a C string (free withdash_sdk_string_free), or anerror(free withdash_sdk_error_free):NotFoundfor an unknown document type,SerializationErrorfor bytes that are not a contract,InvalidParameterfor null or empty arguments and for properties that are not a JSON object. The JSON matches #5051 key for key:[ { "name": "perUnitFee", "rule": { "greaterThanOrEqual": [{ "divide": ["price", "fee"] }, 1] }, "reads": [{ "path": "price", "kind": "value" }, { "path": "fee", "kind": "value" }], "readsOwner": false }, { "name": "sellerIsOwner", "rule": { "anyOf": [{ "absent": "sellerId" }, { "equal": ["sellerId", "$ownerId"] }] }, "reads": [{ "path": "sellerId", "kind": "presence" }, { "path": "sellerId", "kind": "identifier" }], "readsOwner": true } ]{ "rule": "perUnitFee", "violation": "DivisionByZero", "message": "it divides by zero" }readsOwnercome from DPP's parsed rules (property_constraints(),property_reads(),reads_owner());ruleis the declaration as the document type's schema holds it.sanitize_document_propertiesandcreate_document_from_dataofdash_sdk_document_create, now shared asparse_document_properties_jsonandbuild_document_from_properties), so integers, identifiers and nested identifiers are typed as they would be sent. The document is then judged by DPP'svalidate_property_constraints, the method consensus calls on a create: every rule in name order throughPropertyConstraint::violation, with the owner for$ownerId. No rule logic lives in the FFI or in Swift. Only the rules are checked, not the JSON schema.platform_wallet_create_document_with_signer), and the only handle constructors fetch from the network. Both example apps already persist the contract's platform serialization next to its JSON (PersistentDataContract.binarySerializationon iOS), and the crate already reads those bytes elsewhere (dash_sdk_add_known_contracts, the token transitions). The functions read them at the SDK's protocol version at call time. Looking the contract up in the trusted context provider instead would reuse a parse made at SDK start: the app loads its known contracts while the protocol version refresh is still in flight, so on testnet they are parsed at the version 13 floor and would show no rules.Swift SDK
Core/Utils/DocumentPropertyConstraints.swift:DocumentPropertyConstraint(name,ruleJSONkept as the declared JSON,prettyRuleJSON,reads,readsOwner),PropertyConstraintRead(path,kind),PropertyConstraintViolation(rule,violation,message, aLocalizedError), and two thin wrappers onSDK:documentPropertyConstraints(serializedContract:documentType:)andcheckDocumentPropertyConstraints(serializedContract:documentType:propertiesJSON:ownerId:). A read kind or violation name a later protocol version adds decodes as.other(name)instead of failing.PersistentDocumentType:propertyConstraints(using:)andpropertyConstraintViolation(propertiesJSON:ownerId:using:)pass the parent contract's storedbinarySerialization, anddeclaresPropertyConstraintsreads the keyword off the persisted schema. Nothing new is stored, so no entity hash or schema version moves.SDK.processStringResultis nowinternal(wasprivate) so the new wrappers reuse it.SwiftExampleApp
Document type details. Before: the screen listed settings, indices and properties; a type's rules were visible only by reading the raw contract JSON. After: a "Property Constraints (N)" section lists each rule:
The section reloads when the app learns the network's protocol version; a schema declaring rules that the current version does not enforce says so instead.
Create document. Before: an offer with
{"price": 100, "fee": 0}was broadcast, and consensus refused it withDocumentPropertyConstraintViolatedError(10422) after charging the fee. After: tapping "Create / Broadcast" runs the pre-check first and nothing is sent:A document meeting every rule is broadcast as before. When the pre-check cannot run (no SDK, no stored contract bytes), it logs and lets the create proceed, since consensus judges the document either way.
How Has This Been Tested?
data_contract/property_constraints.rs, calling the C functions end to end on a contract whoseoffertype declares five rules (a stringconstwithpresent, an integer comparison, adivide,$ownerIdwithabsent, andin) beside aplaintype declaring none:readsOwner;[]for a type with none;NotFoundfor an unknown type;nullfor a valid document (an identifier sent as base58 is typed as the create path types it, so it equals the owner); the first broken rule in name order (NotMetfor the string, integer andinrules,DivisionByZeroforfee: 0);$ownerIdread from the owner passed in;cargo test -p rs-sdk-ffi --lib: 340 passed, 1 ignored (the new 9 included; the existingdash_sdk_document_createtests cover the shared parsing and its messages).cargo clippy -p rs-sdk-ffi --all-targets -- -D warnings: clean. The two functions appear in the generatedrs-sdk-ffi.hand in the xcframework headers.SwiftTests/SwiftDashSDKTests/DocumentPropertyConstraintsTests.swift(16 tests): decoding of the rules and violations (order, reads,readsOwner, the rule kept as declared JSON, pretty printing, unknown names kept as.other, malformed JSON refused), round trips of both wrappers through the FFI on the mock SDK with a protocol version 13 contract fixture, error codes kept (notFound,serializationError,invalidParameter), the 32-byte owner guard, and thePersistentDocumentTypehelpers reading the stored contract bytes (or refusing without them)../build_ios.sh --target sim(release; includes the SwiftExampleApp build with warnings as errors): succeeded.xcodebuild -project SwiftExampleApp/SwiftExampleApp.xcodeproj -scheme SwiftExampleApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17,arch=arm64' -quiet clean build: succeeded (iPhone 17, as no iPhone 16 simulator is installed here).SKIP_EXAMPLE_APP_BUILD=1 ./build_ios.sh --target tests(adds the macOS slice), thenswift test --filter DocumentPropertyConstraintsTests: 16 passed;swift test --filter "DataContractParser|DocumentTypeImmutabilityTests|DocumentTypedArray|SDKMethodTests|DashModelMigrationTests|DashReleasedSchemaTests": 111 passed, 3 skipped.Breaking Changes
None. New C functions and Swift API;
dash_sdk_document_createkeeps its behaviour and error messages.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 ·
a5fac13/skip-botsproceeds without the ones not yet reported/self-reviewedonce the bots are donerust-sdk-ffi(packages/rs-sdk-ffi/src/data_contract/mod.rs,packages/rs-sdk-ffi/src/data_contract/property_constraints.rs,packages/rs-sdk-ffi/src/document/create.rsand 2 more) — lklimek or shumkovswift-sdk(packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DocumentPropertyConstraints.swift,packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDocumentType.swift,packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swiftand 2 more) — llbartekll or romchornyiWhen every box is checked the
PR Hygienecheck passes and this can merge.Summary by CodeRabbit