feat(sdk): add transport-free CXX bindings - #4416
Draft
PastaPastaPasta wants to merge 11 commits into
Draft
Conversation
…d client code The proto-to-domain decoding for document queries existed only server-side (rs-drive-abci's v1 conversions), so a client verifying a documents proof had to reconstruct the query shape by hand and could silently drift from what the server actually proves. Move the decode logic into dash-platform-queries::documents::proto_conversions with a neutral error type; drive-abci's conversions module becomes a thin mapping onto its QueryError surface with identical error message strings. On top of the shared decoder, DocumentQuery::try_from_request(request, contract) reconstructs the rich query from the wire request (both request versions), and verify_documents_response(...) / verify_documents_response_with_provider_contract(...) give embedders a request-driven verification entry point that delegates to the existing FromProof machinery, resolving the contract explicitly or via ContextProvider::get_data_contract. Round-trip tests cover encode-decode equality for representative queries in both wire versions plus malformed-clause rejection; drive-abci's document_query unit tests pass unchanged (76 cases).
Move the document *content* assembly of rs-sdk's networked DPNS and DashPay flows into transport-free functions in dash-platform-queries, so offline/embedder consumers and rs-sdk share one implementation: - build_dpns_preorder_and_domain_documents assembles the preorder and domain documents exactly as register_dpns_name did: both ids from the same entropy via generate_document_id_v0, saltedDomainHash = sha256d(salt || normalized_label + ".dash"), and the full domain property map (parentDomainName/normalizedParentDomainName, label, normalizedLabel, preorderSalt, records.identity, subdomainRules.allowSubdomains=false). It additionally rejects labels failing is_valid_username up front - previously only enforced by rs-sdk-ffi and platform consensus - so register_dpns_name now fails locally on an invalid label instead of after a network round-trip. - build_contact_request_document assembles the DIP-15 contactRequest id and property map from already-derived crypto material (encrypted xpub/label bytes, key indices, entropy). ECDH, encryption, the 69-byte compact-xpub check, key purpose checks, and recipient fetching stay in rs-sdk; the ciphertext size validations (96-byte xpub, 48-80-byte label, 38-102-byte autoAcceptProof) moved into the builder, with validate_auto_accept_proof also called early in create_contact_request to keep the pre-fetch fail-fast. - ensure_entropy_matches_document_id and prepare_document_for_transition moved from put_document.rs into dash_platform_queries::transition::put_document; rs-sdk re-exports and keeps calling them. Entropy/salt generation and all networking remain in rs-sdk. Builder validation errors surface through the new dash_platform_queries::Error::InvalidInput variant, which rs-sdk maps back to Error::Generic with the exact pre-move messages. New unit tests in dash-platform-queries pin a DPNS known vector (document ids and property maps for fixed label/entropy/salt), mirror the entropy-derives-id relation for contact requests, and cover the negative validation paths.
…er seams Review follow-ups on the transport-free series: - The DPNS document builder validated labels with is_valid_username, whose consecutive-hyphen rejection is stricter than the DPNS contract's schema pattern - consensus accepts names like ab--cd. Split the check: new is_consensus_valid_label matches the contract pattern exactly and gates the builder (so dash-sdk's register_dpns_name no longer refuses consensus-valid labels), while is_valid_username keeps the stricter policy for its existing FFI/wasm gates and now documents the difference. - verify_documents_response now binds the proof to the whole wire request, not just its SELECT projection. GroveDB and Tenderdash proofs authenticate the state and the resolved DriveDocumentQuery, and the DocumentQuery -> DriveDocumentQuery lowering drops group_by, having, offset and prove - so an untrusted transport could otherwise pair a request the real server would have refused (SELECT DOCUMENTS ... GROUP BY age) with a genuine proof for the narrower query it lowers to, and verification would accept it. Every dropped field is now rejected up front, mirroring rs-drive-abci's validate_and_route (non-empty HAVING for a non-aggregate SELECT, GROUP BY under SELECT DOCUMENTS) and reject_offset_off_the_ranked_path (OFFSET off the ranked surface); prove=false is rejected because an honest server answers such a request without a proof at all. The pre-existing aggregate- projection rejection (COUNT/SUM/AVG, which use a different proof shape) moves into the same gate. Five tests cover the rejections with a ContextProvider that panics if reached, pinning that they fire before any proof machinery runs. - TryFrom<&DocumentQuery> for DriveDocumentQuery converts the limit with a checked u16::try_from instead of an `as` cast. Drive's limit is a u16 and the server refuses anything larger with InvalidLimit, so the cast turned a request for 65537 documents into a 1-document query - and a proof for that query then verified. This matches the offset conversion right below it, which already refused rather than truncated. - try_from_request documents that it mirrors the server's wire-shape decode, not validate_and_route business rules, and points at the entry point that closes the gap. - The CI transport-leak guard also asserts wasm-sdk's wasm32 tree stays free of the native transport stack. - The proof-vector corpus gains a README with an explicit coverage matrix: the four documents-family cases pin query shape and clean decode failure but stop before the BLS check (placeholder payloads in the fixture state); identity, contested, and quorum-sig families run the full pipeline. This corrects the corpus commit's broader claim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ions and limits Two trust-boundary gaps let an untrusted transport pair a valid proof with a request the supplied platform version's server would have refused. First, the wire version oneof (V0/V1) was never checked against platform_version.drive_abci.query.document_query bounds, so a V1 request could be verified under a platform version whose server only serves V0. Both verify entry points now run the same check_version gate the server's query_documents dispatch runs, before any decoding, contract lookup, or proof machinery. Second, the DocumentQuery -> DriveDocumentQuery lowering only guarded the u16 cast, so limits 101..=65535 - which the server refuses with InvalidLimit via DriveDocumentQuery::from_typed_clauses' default_query_limit cap (100) - reached a raw DriveDocumentQuery and could verify a proof no honest server would produce. The lowering now mirrors from_typed_clauses exactly: 0 stays the unset-default sentinel, 1..=100 passes, anything above is refused with the server's own QuerySyntaxError::InvalidLimit. Tests reuse the panicking ContextProvider to pin that both rejections happen before any proof machinery runs, and cover the provider-resolved entry point rejecting before its contract lookup.
The COUNT/SUM/AVG proof verifiers each duplicated a u16::try_from-only limit conversion, so server-invalid limits in 101..=65535 - which drive's aggregate dispatchers refuse with InvalidLimit against max_query_limit before producing proof bytes - still reached the proof primitives. Same vulnerability class as the documents-path limit fix: an untrusted transport could pair such a request with a genuine proof for a different, server-permitted query. Centralize the semantics in a shared aggregate_limit module: a single cap check (DEFAULT_QUERY_LIMIT, the compile-time twin of the config default the SDK cannot see per-operator) runs at the top of each verify helper before any proof or provider machinery, and the two 0-sentinel translations (distinct walk: 0 -> DEFAULT_QUERY_LIMIT; carrier walk: 0 -> None, unbounded outer walk) replace the six hand-rolled copies, mirroring the server dispatchers exactly. The new test drives the COUNT verify path through FromProof with the panicking ContextProvider, pinning that over-cap limits are rejected before proof machinery runs.
…fault The aggregate dispatchers server-side compare against drive_config.max_query_limit, so the verifier's compile-time mirror is DEFAULT_MAX_QUERY_LIMIT, not DEFAULT_QUERY_LIMIT (default_query_limit's twin, which remains correct for the documents path's from_typed_clauses comparison). Both are 100 today with no compile-time link; a const assert pins the distinct-walk fallback within the cap so a future divergence becomes a build error instead of a silent parity break.
The transport-free preorder/domain builder takes the salt as an argument, so the front-running protection the preorder commitment provides now rests on the embedder: a fresh CSPRNG 32-byte salt per registration attempt, and salt/label/domain-document secrecy until the preorder create transition is confirmed. Spell out both obligations - and that the networked SDK's register_dpns_name (StdRng::from_entropy, submit-and-wait before broadcasting the domain document) is the reference behavior - on the builder's docs.
The extraction from drive-abci widened where_operator_from_proto, value_from_proto, where_clause_from_proto, order_clause_from_proto and having_clause_from_proto to pub, but the cross-crate consumers (drive-abci's v1 conversions, the SDK decode path) only use DecodeError, the plural request-level decoders and select_from_proto. Narrow the singular helpers to pub(crate) so the shared decode surface stays as small as its actual contract.
Drive the over-cap rejection through DocumentSum and DocumentAverage as well as DocumentCount, so removing the shared cap gate from any one verify helper fails the test, and add unit tests pinning the cap boundaries (0/1/100 accepted, 101 rejected) and the 0-sentinel translations (distinct walk -> DEFAULT_QUERY_LIMIT, carrier walk -> None).
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
PastaPastaPasta
force-pushed
the
feat/platform-cxx-bindings
branch
from
August 18, 2026 19:10
a1cca2d to
df4fdb6
Compare
This was referenced Aug 18, 2026
PastaPastaPasta
force-pushed
the
refactor/document-query-decode-builders
branch
from
August 20, 2026 14:23
b6ea184 to
5136419
Compare
This was referenced Aug 20, 2026
PastaPastaPasta
force-pushed
the
refactor/document-query-decode-builders
branch
2 times, most recently
from
August 20, 2026 15:48
c0ccb87 to
b3e9de1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue being fixed or feature implemented
Dash Core's Platform GUI currently carries a private copy of its Rust/CXX bridge and pins individual Platform crates. This makes the embedding ABI, proof-verification logic, decoders, state-transition builders, and dependency closure owned by the consumer instead of Platform.
This stacked PR gives Platform ownership of that transport-free C++ embedding surface. It is based on #4389 and assumes #4388 and #4389 merge before this PR is retargeted to
v4.2-dev.What was done?
dash-platform-cxxworkspace package with the existingplatform_ffiABI for proof verification, DPP decoding, state-transition construction, quorum context, and callback-based wallet signing.After this lands, PastaPastaPasta/dash#67 will consume the installed archive and headers through Dash Core's
dependssystem and remove its private Rust/CXX implementation.How Has This Been Tested?
cargo test -p dash-platform-cxx --locked(31 tests)cargo clippy -p dash-platform-cxx --all-targets --locked -- -D warningscargo check --manifest-path packages/rs-platform-cxx/standalone/Cargo.toml --lockedrs-dapi-client, Hyper, Rustls, Tower, and Reqwestcargo machetecargo fmt --all -- --checkshellcheck packages/rs-platform-cxx/install.sh packages/rs-platform-cxx/test-cxx-link.shgit diff --checkBreaking Changes
None. The existing
platform_ffinamespace and bridge ABI from the downstream integration are preserved.Checklist:
For repository code-owners and collaborators only
This pull request was created by Codex.