feat(sdk): transport-free verification and query core for embedders - #4335
Draft
PastaPastaPasta wants to merge 8 commits into
Draft
feat(sdk): transport-free verification and query core for embedders#4335PastaPastaPasta wants to merge 8 commits into
PastaPastaPasta wants to merge 8 commits into
Conversation
dapi-grpc unconditionally built tonic with its native transport stack
(channel + TLS roots) on non-wasm targets, so any consumer of the message
types or proof-verification layers (drive-proof-verifier) dragged
hyper/tokio/rustls into its dependency tree even when it never opens a
connection. The wasm target already proves the crate works with codegen-only
tonic.
Add a default-on 'transport' cargo feature carrying tonic's
channel/transport/tls features, mirroring the client/server feature split
tenderdash-proto already has. build.rs drives tonic-build's build_transport
from CARGO_FEATURE_TRANSPORT (never on wasm32, where the transport stack does
not build). Consumers that need the native transport are wired explicitly:
rs-dapi-client (target-scoped to non-wasm), dash-sdk (default feature), and
drive-abci via server (which now implies transport). wasm-sdk switches to
default-features = false like rs-dapi-client already does, since the
default-on feature would otherwise request tonic's transport on wasm.
drive-proof-verifier needs no changes and its standalone tree drops from 407
to 339 crates: hyper, h2, rustls, ring, tower and the rest of the transport
stack disappear; what remains of tonic's codegen core is a sync-only tokio
slice via tokio-stream.
Types-only consumers build with:
dapi-grpc = { default-features = false, features = ["platform", "client"] }
…ueries Split rs-sdk per the maintainer guidance to refactor rather than duplicate: the query-building, wire-encoding, and proof-decoding core that a transport-free embedder needs now lives in a new packages/dash-platform-queries crate, and rs-sdk depends on it and re-exports every moved item at its old path, so no rs-sdk consumer changes imports. Moved out of rs-sdk: DocumentQuery and its wire encoders (document_query.rs), the count/sum/average/ranked proof helpers and their FromProof aggregate views (DocumentCount, DocumentSum, DocumentAverage, DocumentSplitCounts, DocumentSplitSums, DocumentSplitAverages, DocumentRankedEntries), DocumentHistoryQuery, block_info_from_metadata, QuerySettings, FinalizedEpochQuery, ensure_valid_state_transition_structure, and the DPNS username helpers (convert_to_homograph_safe_chars, is_valid_username, is_contested_username). Sdk-bound pieces stay behind: the contract-fetching DocumentQuery constructor (now the DocumentQuerySdk extension trait), the Query<GetDocumentsRequest> encoder impl, the Fetch bindings for the aggregate views, and the Query impls for FinalizedEpochQuery. QuerySettings loses its request_settings field: it was documented dead weight (not consulted by any encoder) and was the only rs-dapi-client tie in the moved struct. Sdk::query_settings and the few test construction sites were updated accordingly. The new crate has its own small thiserror enum (Config/Drive/Protocol); rs-sdk converts it via From, so existing ? call sites keep compiling. wasm-sdk gains the matching From impl for WasmSdkError, routed through SdkError so the mapping is unchanged. Coherence fallout: with DocumentQuery now foreign to rs-sdk, the blanket 'impl Query<T> for T where T: TransportRequest' would conflict with the explicit identity impl for DocumentQuery. The blanket is now additionally bounded by a local, explicitly-implemented WireQuery marker covering every wire request proto (list mirrors rs-dapi-client's TransportRequest impls); rustc can then prove the impl sets disjoint. The new crate's dependency tree is transport-free: no rs-dapi-client, hyper, rustls, or tonic transport.
…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.
drive-proof-verifier had no integration tests at all: its verification pipeline (grovedb proof replay + tenderdash quorum signature check) was exercised only indirectly through rs-sdk's mock replay. Add a standalone corpus of 16 fixture cases under tests/vectors/, generated from a real Drive state (platform v4.0.0 fixtures, protocol version 12): identity balance/nonce/contract-nonce/keys, DPNS exact and prefix document queries, DashPay profile and contact requests, contested vote state (active/finished/absent), and quorum-signature positive and negative cases. Each case is a self-contained directory with an explicit manifest.json (request parameters, block metadata, expected outcome, pinned root hash) plus proof/signature/quorum-key blobs. The loader synthesizes the DAPI response protobuf from components and drives the real FromProof entry points with a ContextProvider serving the per-case quorum key, so every positive case runs the full pipeline including a genuine BLS check - all 16 drive proofs commit to the same root hash, which is exactly the app hash the quorum signature signs. Negative cases pin clean failures: a corrupted proof fails as a GroveDB error, a tampered signature at point decompression, a wrong quorum key or block-id hash at signature verification - never a panic. The corpus doubles as a cross-implementation anchor: the same fixtures are replayed byte-exact by Dash Core's platform GUI implementation, so drift between what Drive proves and what any client verifies fails loudly here.
Feature unification hides transport-stack regressions in whole-workspace builds, so add a PR-time step checking the standalone graphs (types-only dapi-grpc, drive-proof-verifier, dash-platform-queries) and failing if hyper, rustls, or tower leaks into drive-proof-verifier's tree. Add both verification crates to the nightly per-feature check matrix and to the check-features tool's crate list.
dapi-grpc gets a crate-level feature table (including the new transport feature and the types-only build recipe), dash-platform-queries gets a README describing who the crate is for and what lives in it, and rs-sdk's README points transport-free embedders at the split crate.
…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 rejects aggregate projections (COUNT/SUM/AVG) up front with a pointer to the aggregate proof helpers, instead of surfacing an opaque low-level proof error; try_from_request documents that it mirrors the server's wire-shape decode, not validate_and_route business rules. - 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.
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 |
This was referenced Aug 8, 2026
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
Embedders that bring their own transport and trust context — Dash Core's platform GUI (PastaPastaPasta/dash#67), block explorers, Electrum-style servers, hardware-wallet tooling — currently cannot consume Platform's verification layer without dragging in the full networked SDK stack, and end up hand-rolling query construction and proof verification against Drive internals that can silently drift.
Concretely, before this PR:
dapi-grpcunconditionally built tonic's native transport on non-wasm targets, so evendrive-proof-verifier(which never opens a connection) pulled hyper/tokio/rustls/ring/tower into its tree — measured at Dash Core: the transport stack alone is +123 crates of vendored, supply-chain-reviewed source per release.dash-sdk, inseparable fromrs-dapi-client(a hard, non-optional dependency), so "use just the verification layer" was not expressible.DriveDocumentQueryshapes by hand from Drive internals — ~1.5k lines of drift-prone reimplementation per embedder.Discussed with QuantumExplorer (Slack, 2026-08-07): the agreed direction was "make an SDK cut small enough to match / split rs-sdk; refactor instead of duplicating." This PR is that refactor. It follows the same pattern
tenderdash-protoalready uses (client/server feature split) and the precedent ofwasm-drive-verify(an in-tree transport-freedrive/verifyconsumer).What was done?
Eight commits, each independently reviewable and revertible (happy to split into separate PRs if preferred):
feat(sdk): add transport feature to dapi-grpc— default-ontransportfeature carrying tonic's channel/TLS features;build_transportdriven by the feature (never on wasm32). Types-only consumers build withdefault-features = false, features = ["platform", "client"].drive-proof-verifier's standalone tree drops 407 → 339 crates; hyper, h2, rustls, ring, tower disappear. Zero changes todrive-proof-verifieritself.refactor(sdk): extract transport-free query core into dash-platform-queries— new crate carryingDocumentQuery+ wire encoding, aggregate proof helpers, DPNS free functions, transition validation;dash-sdkdepends on it and re-exports everything at historical paths. Pure moves, no logic changes (one disclosed API cleanup: the documented-deadQuerySettings.request_settingsfield is removed;DocumentQuery::new_with_data_contract_idmoves to an extension trait).refactor(sdk): decode document queries from the wire request in shared client code— the drive-abci v1 proto→clause decoders move to the shared crate (error strings byte-identical; drive-abci keeps a thin mapping shim), enablingDocumentQuery::try_from_requestand the embedder entry pointsverify_documents_response/verify_documents_response_with_provider_contract: request-driven, typed, proof-verified document queries where client and server decode the same bytes with the same code.refactor(sdk): extract pure DPNS and DashPay document builders—build_dpns_preorder_and_domain_documentsandbuild_contact_request_documentas pure functions (crypto material supplied by the caller; keys never enter the crate);register_dpns_name/create_contact_requestnow call them.test(sdk): add proof-vector regression corpus for drive-proof-verifier— the crate's first integration tests: 16 cases from a real Drive state driving the publicFromProofentry points, including genuine tenderdash BLS verification with real fixture key material, and negative cases pinning clean failures. Seetests/vectors/README.mdfor the exact per-family coverage matrix (the four documents-family cases stop at document decode, before the BLS step — the commit message's blanket claim is corrected by the README and commit 8). The same fixtures are replayed byte-exact by Dash Core's implementation, making this a cross-implementation anchor.ci: cover the transport-free feature cuts— PR-time standalone-graph checks + a tree guard failing if hyper/rustls/tower leaks intodrive-proof-verifier(native) orwasm-sdk(wasm32); both verification crates added to the nightly per-feature matrix andcheck-features.docs(sdk): document the transport-free consumption path— dapi-grpc feature table,dash-platform-queriesREADME, rs-sdk README pointer.fix(sdk): align DPNS builder validation with consensus, harden embedder seams— post-review fixes: the DPNS builder validates against the exact contract schema pattern (consensus accepts consecutive hyphens; the stricteris_valid_usernameremains as documented client-side policy for the existing FFI/wasm gates),verify_documents_responsefail-fasts on aggregate projections, wasm-target leak guard, corpus coverage README.Known wording nits in already-made commit messages (kept rather than rewriting history mid-review): commit 2 says "no rs-sdk consumer changes imports" — two in-repo test files needed a trait import; commit 5's "full pipeline" claim is scoped down by the coverage matrix in
tests/vectors/README.md.Why not "just use the SDK"?
For a phone with no node, the SDK owning transport, trust bootstrapping (
TrustedHttpContextProvider→quorums.<network>.networks.dash.org), and key custody is the right design. A validating node inverts all three: it has the masternode list and LLMQ quorum keys from consensus (a trusted HTTP endpoint would be a trust downgrade), its keys live in its own wallet, and its endpoint selection is consensus-derived. It needs the layer underneath the SDK — which already existed inside these crates; this PR exposes it rather than forking it.How Has This Been Tested?
cargo fmt --check --all,cargo clippy --workspace --all-targets --all-features --locked -- --no-deps -D warnings,cargo machete, wallet-closure check — all clean.cargo test -p dash-platform-queries(36 + 9 cases),cargo test -p drive-proof-verifier --features mocks(261 existing + 16 new vector cases),cargo test -p dash-sdk --no-default-features --features mocks,offline-testing,...(164 + offline vector suite), drive-abcidocument_queryunit tests unchanged (76 cases).dapi-grpc,drive-proof-verifier,dash-platform-queriesall build--locked;cargo treeconfirms no hyper/rustls/tower;wasm-sdkbuilds for wasm32-unknown-unknown.rs-sdk-ffi,platform-wallet,rs-unified-sdk-ffi,wasm-sdk.platform-gui-rustbranch (feat(qt): back Dash Platform GUI internals with real grovedb/drive/dpp crates PastaPastaPasta/dash#67) is being reworked to pin this branch and drive its DashPay GUI throughverify_documents_response/FromProof— byte-exact against the same fixture corpus.Breaking Changes
None for
dash-sdkconsumers via crates: everything moved is re-exported at its historical path. Two narrow source-level changes inside the workspace, both disclosed in commit 2's message:QuerySettings.request_settings(documented as unused) is removed, andDocumentQuery::new_with_data_contract_idrequires importing theDocumentQuerySdkextension trait.dapi-grpc'stransportfeature is default-on, so existing native consumers are unchanged; wasm consumers already usedefault-features = false.Checklist:
🤖 Generated with Claude Code