Skip to content

refactor(drive-abci): extract v1 document-query wire decoders into platform-query-wire - #4464

Draft
PastaPastaPasta wants to merge 1 commit into
v4.2-devfrom
refactor/extract-query-wire-decoders
Draft

refactor(drive-abci): extract v1 document-query wire decoders into platform-query-wire#4464
PastaPastaPasta wants to merge 1 commit into
v4.2-devfrom
refactor/extract-query-wire-decoders

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Aug 23, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

The problem this solves is not an SDK problem — it's a duplication problem at a security boundary.

Why doesn't the existing SDK already need this? Because the SDK never decodes a request. It builds the rich DocumentQuery object in-process, encodes it to wire bytes on the way out, and verifies the returned proof against the same in-memory object it still holds (FromProof<DocumentQuery> takes the rich query, not bytes). One process, one object — the bytes→query direction never runs client-side. It has only ever existed in the server, the one party that receives requests as bytes: rs-drive-abci/src/query/document_query/v1/conversions.rs (~380 lines), unreachable by any client.

What changes now: Dash Core's DashPay GUI (#4389) is the first client that holds its own request only as wire bytes — its transport lives in C++, and bytes are what cross the FFI. Its verification entry point therefore has to reconstruct the query from those bytes, running the server's decode direction client-side for the first time. (Binding verification to the bytes actually sent, rather than to a parallel in-memory object, is also the point — the proof then attests to exactly what went on the wire.) The same will hold for any future embedder with its own transport: block explorers, wallet tooling.

And that reconstruction must match the server's interpretation exactly — a proof only protects the client if the client rebuilds the query precisely as an honest server would have executed it. Since the decode lives inside drive-abci, unreachable by clients, #4389 is forced to carry a byte-for-byte copy of those 380 lines with a comment saying "keep this in lockstep with the server" — because there is currently nowhere shared to put the code.

Hand-maintained copies of security-critical logic rot in a specific, nasty way here: if the server's decode gains a new operator, clause type, or changed edge-case handling and the client copy isn't updated in the same breath, the two sides now interpret the same request bytes differently. The failure isn't a crash — it's a client whose "verified" answer corresponds to a subtly different query than the one the server answered. Drift is silent until it's exploitable or produces wrong data.

Why a new crate? Every existing home is wrong for one side or the other:

Hence this micro-crate: platform-query-wire, containing only the wire→drive decode, with dependencies that are a strict subset of what drive-abci already builds (dapi-grpc, dpp, drive, thiserror — no proof verifier, no context provider, no async runtime, no transport). The server consumes it through a thin error-mapping adapter with zero behavior change; a small follow-up deletes #4389's client-side copy and points it here. From then on, server and verifiers compile against the same functions and this class of drift is structurally impossible instead of a standing review obligation.

This PR is fully independent and standalone — it does not stack on or depend on #4389, and nothing regresses if it merges before or after it. It exists now, rather than later, because the first client copy is being introduced right now; landing the shared home early keeps that copy's lifetime short.

What was done?

  • New micro-crate packages/rs-platform-query-wire (crate name platform-query-wire, matching the packages/rs-platform-*platform-* convention). Scope: decode only — no transport, no proof verification, no async runtime.
    • Dependencies: dapi-grpc (default-features = false, platform + clientclient = ["platform"] adds no dependencies and is required because the generated message types are only emitted under the client/server cfgs on native targets; the transport feature stays off), dpp (default-features = false), drive (default-features = false, verify), thiserror. drive-abci already enables dapi-grpc's server feature (a superset of client) and a superset of the dpp/drive features, so the server's build graph gains no new dapi-grpc features and no new third-party dependencies.
  • Moved all 14 decode functions from drive-abci's v1/conversions.rs into platform_query_wire::proto_conversions verbatim (byte-identical bodies including every error message string), behind a neutral DecodeError { InvalidArgument(String), Unsupported(String) }.
  • drive-abci's v1/conversions.rs is now a thin adapter mapping DecodeError back onto its QueryError surface with identical message strings: InvalidArgument(msg)QueryError::InvalidArgument(msg), and the aggregate ORDER BY arm Unsupported(msg)QueryError::Query(QuerySyntaxError::Unsupported(msg)). No server behavior change.
  • Registered the crate at every workspace touchpoint: root Cargo.toml members, Cargo.lock, the four Dockerfile workspace COPY lists, and the three .github/package-filters files (with the new crate added to drive-abci's filter, since it is now a drive-abci dependency).
    • Deliberately not added to check-features/the nightly per-feature matrix (the crate has no features; the check-features harness panics on crates without a [features] table) nor to the transport-free CI assertion step (that step is specific to dash-platform-queries/drive-proof-verifier).

How Has This Been Tested?

  • Move purity: diffed the old drive-abci conversions.rs (at v4.2-dev) against the new crate's proto_conversions.rs with comments stripped and the declared transforms normalized (error-type rename, the aggregate ORDER BY arm neutralization, visibility qualifiers, #[allow(dead_code)] removal, rustfmt re-wrapping of two signatures). Result: normalized bodies are identical — every function body and error message string is unchanged.
  • cargo check -p platform-query-wire -p drive-abci --locked — passes, no new warnings (the only warning is the pre-existing workspace-wide dashcore default-features manifest note).
  • cargo test -p drive-abci --locked document_query93 passed, 0 failed (1 ignored), covering the v1 decode paths end to end including the error-message assertions in v1/tests.rs.
  • cargo test -p platform-query-wire --locked — passes (the moved code's tests live in drive-abci and exercise the shared decoders through the adapter).
  • cargo clippy -p platform-query-wire -p drive-abci --all-targets --locked — no new warnings.
  • cargo fmt -p platform-query-wire -p drive-abci -- --check — clean.
  • Package-filter YAML re-parsed programmatically (anchors resolve; platform-query-wire entry defined before its use in drive-abci's list).

Breaking Changes

None. The server's request decoding, error variants, and error message strings are byte-for-byte unchanged. The new crate is additive.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

…atform-query-wire

Pure move: the wire-proto -> drive-type decoders for the v1 getDocuments surface now live in a new packages/rs-platform-query-wire micro-crate (crate name platform-query-wire) behind a neutral DecodeError { InvalidArgument, Unsupported }, and drive-abci's v1/conversions.rs becomes a thin adapter mapping DecodeError onto its QueryError surface with the exact same message strings (including the aggregate ORDER BY arm, which maps Unsupported onto QuerySyntaxError::Unsupported). No behavior change to server request decoding.

The decode of a wire request into a rich query is an equivalence contract at a trust boundary: a client-side proof verifier must interpret a request exactly as the server does, or a proof could verify against a different query than the server answered. Hosting the shared decoders in a neutral crate lets both rs-drive-abci and (in a follow-up) the client-side SDK decoders run the same functions, without making the consensus server depend on SDK-branded code: the new crate's dependencies (dapi-grpc platform+client without transport, dpp, drive/verify, thiserror) are a strict subset of what drive-abci already carries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant