Skip to content

feat(sdk): add dash-platform-cxx, a thin CXX shell over dash-sdk for C++ embedders - #4633

Draft
PastaPastaPasta wants to merge 5 commits into
feat/dapi-client-socks5-proxyfrom
feat/platform-sdk-cxx
Draft

PastaPastaPasta wants to merge 5 commits into
feat/dapi-client-socks5-proxyfrom
feat/platform-sdk-cxx

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Dash Core's DashPay GUI (dash-qt, tracked in dashpay/dash#7512) needs Dash Platform from C++, on Core's own terms. Quorum keys come from its LLMQ store, evonode endpoints from its masternode list, and signatures from its wallet. No key and no trust decision leaves the node. rs-sdk-ffi is shaped for mobile wallets that fetch quorum keys from a trusted HTTP service and keep private keys in-process, so it does not fit.

Earlier revisions of this PR were stacked on #4632 and used document builders added to dash-platform-queries. Following review, that stack is gone. This PR is a thin cxx shell over dash-sdk with no protocol logic of its own. The SDK builds the queries, owns the transport, retries and address banning, verifies the proofs and tracks the protocol version. Core supplies only what it alone knows. The PR does not depend on #4632.

Stacked on #5160 (feat(sdk): route DAPI connections through a SOCKS5 proxy, branch feat/dapi-client-socks5-proxy), and this PR's base is that branch. Dash Core users route their traffic through -proxy (usually Tor), and DashPay must follow the same settings instead of refusing to run under a proxy. That PR adds SOCKS5 support to rs-dapi-client and SdkBuilder::with_proxy, and fixes a panic on IPv6 endpoints. This PR passes the embedder's proxy through. The diff here is only this PR's own commits. Once #5160 merges, this PR is retargeted to v5.0-dev. Core pins a commit of this branch, so it does not wait for either merge. Core currently pins 02b1749cb6; the review fixes in 100556a767 change a few C++ signatures (see below), so the next re-pin needs matching Core changes.

What was done?

New workspace member packages/rs-platform-cxx (dash-platform-cxx). Its only Platform dependencies are dash-sdk (default features off; dpns-contract, dashpay-contract, core_key_wallet) and platform-encryption.

  • Push-model quorum provider (provider.rs). The embedder pushes the active Platform quorum keys, hashes in Core's byte order, and its best ChainLock height. A proof naming any LLMQ type other than the network's Platform type is refused before a key is looked up. No proved read is dispatched until a ChainLock height has been pushed. A proof whose core-chain-locked height trails that anchor by more than 288 blocks is refused as stale. There is no upper bound, since a node one ChainLock ahead is honest.
  • Height watermark in the shell (ops.rs). After the SDK has verified the quorum signature and its signed-time window, the shell applies its own monotonic Platform-height watermark (tolerance 3 blocks) and then records the verified protocol version, so a stale response moves nothing the shell decides on. The SDK's own watermark is off. Both the watermark and the verified version survive SDK rebuilds.
  • No expected chain id. The shell does not compare the signed Tenderdash chain_id with a configured value, for the reasons that closed feat(sdk): reject response metadata from an unexpected chain id #4963. The id is part of the message the quorum signs, so a relabelled id fails verification. The signing quorum must be a Platform quorum the embedder pushed from its own Core chain, so a response from another network does not verify. A stale node after a testnet reset is caught by the SDK's signed-time window and the 288-block ChainLock lag floor.
  • Proved reads, one SDK request per call, each returning a typed status (Ok, ProvenAbsent, Rejected, Unavailable, UnsupportedProtocolVersion, …) plus the verified metadata. The reads cover identity by id and by key hash, identity-contract nonce, DPNS resolve, search and names-of-identity (paged), DashPay profile, contact requests, and contested vote state. Broadcasts are classified the same way, including consensus error codes.
  • Signer contract (include/dash/platform/signer.h). SignForKey(key_id, signable) receives the full signable preimage, so the wallet hashes and checks what it signs and answers with a 65-byte compact recoverable signature. SignAssetLockSighash is the one digest path, and the public key is recovered from its signature. Private keys never cross the FFI. The signer must be callable from any thread, although builders only call it on the calling thread.
  • Builders return the serialized transition, its hash and the new object's id, all signed through that contract. Identity create goes through dpp's try_from_identity_with_signers. DPNS preorder/domain and profile create/replace are assembled over dpp's batch transitions. Contact requests are minted by Sdk::create_contact_request with the embedder's ECDH secret. Builders use the protocol version a verified read has shown, and refuse to run before one exists or after an unknown version has been seen.
  • Document ids at PV13 (D1). Up to protocol version 13, dpp sends a create's document id as given, and Drive refuses a mismatch. Every create now gets the id put_to_platform would derive, from the entropy alone at PV13 and from the entropy and nonce from PV14. A property the network's contract version does not have yet, such as DashPay v2 payment addresses before PV14, is refused before signing.
  • SOCKS5 proxy passthrough. Config carries proxy: Proxy{kind, address, isolate}: kind 0 is none, 1 is a numeric TCP ip:port, and 2 is a Unix socket path. isolate sends fresh random SOCKS5 credentials on every connection, so Tor builds a separate circuit for each, matching Core's -proxyrandomize.
    • The proxy is fixed for the client's lifetime, and there is no setter: Core's proxy settings need a restart to change, so no SDK ever runs a request without it. Every SDK the client builds gets it through SdkBuilder::with_proxy, with a 15 s connect budget instead of 5 s.
    • Anything the shell cannot use (an unknown kind, a host name instead of an address, an empty path) makes new_platform_client fail, so there is never a direct fallback.
    • set_endpoints accepts only IP literals, or .onion names when a proxy is set, so no endpoint is ever resolved locally and an onion name never reaches the system resolver.
    • A proxy failure is classified Unavailable, not Rejected. The SDK neither retries it nor bans the evonode.
  • Dropping the SDK closes its connections (D4). The DAPI client keeps a pooled connection to every evonode it has used, and removing an address does not close it. An empty endpoint set therefore drops the SDK, so no Platform socket stays open while the embedder has networking off. A set that removes endpoints rebuilds the SDK over the updated list, and a set that only adds updates the list in place.
  • Every bridge entry point runs under catch_unwind, and the crate refuses panic = "abort". build.rs stages dash/platform/{ffi.h,signer.h} and rust/cxx.h under target/<profile>/include/.
  • CI runs the crate's tests under nextest and links tests/cxx_smoke.cc from the staged headers and archive. It also asserts the trust closure: no rs-sdk-trusted-context-provider, reqwest, openssl-sys or native-tls in the tree, and no seed-list or unproved entry points in the sources. The README documents the trust model, API and threading contract.

How Has This Been Tested?

  • tests/replay.rs (70 cases). Proofs are generated from a real Drive state and signed by a test BLS quorum, then replayed through dash-sdk's mock transport, so only the socket is mocked. The cases cover the freshness matrix (tampered proof or signature, unknown quorum, wrong LLMQ type, chain id relabelled after signing, a chain id signed by a pushed quorum accepted, no anchor, ChainLock lag 288/289, watermark H-3 accepted and H-4 rejected, unsupported protocol version), proven absence, paging, every read, and every broadcast status.
  • tests/builders.rs (21 cases). Every builder is compared byte for byte with dpp's in-process private-key constructions and the SDK's put_to_platform / create_contact_request paths. The suite recomputes each document id the way Drive does, validates the data against the system contract, and checks signer refusals and the no-reactor invariant.
  • Both suites run every case at PV13 (what testnet and mainnet run) and at PV14, with the Drive fixture generated at each version. There are also 37 unit tests. They include the proxy passthrough:
    • proxy config parsing and fail-closed refusals;
    • endpoint hosts (IP literals always, onion names only with a proxy, host names never);
    • the proxied connect budget on every built SDK;
    • a proxy that hangs up, reported as Unavailable with the evonode not banned.
  • At head 100556a767 (review fixes): cargo test -p dash-platform-cxx 137 passed (40 unit, 23 builders, 74 replay); cargo clippy -p dash-platform-cxx -- -D warnings clean, with and without --all-targets; cargo fmt --all --check clean; scripts/cxx-smoke.sh links and runs on macOS. The fixes:
    • A get_contact_requests continuation below PV14 is Unavailable. PV13 Drive skips the requests that share the cursor's $createdAt; the replay suite reproduces it, 102 of 104, and shows the since_ms re-read that recovers them.
    • get_contested_vote_state(label, start_after) pages contenders, and VerifiedContested gains page.
    • build_dpns_domain(.., salt, contenders, key, signer) states the fund to join a contest of that size (PV14 doubles it past 250 contenders). contest_fund_to_join(contenders) exposes the amount.
    • build_contact_request refuses a to_user_id other than the recipient and no longer needs endpoints.
    • build_profile refuses revision u64::MAX.
    • dip15_account_reference_from_mac throws on a version above 15 or an index above 28 bits.
  • At head 02b1749cb6 (chain-id check removed): cargo test -p dash-platform-cxx 128 passed (37 unit, 21 builders, 70 replay); cargo clippy -p dash-platform-cxx --all-targets -- -D warnings clean with and without --features mocks; cargo fmt --check clean; scripts/cxx-smoke.sh links and runs on macOS.
  • cargo test -p dash-platform-cxx --features mocks: 128 passed (37 unit, 21 builders, 70 replay), on top of the proxy branch rebased onto v5.0-dev (then v4.2-dev) at 99bc968a5c. The rebase only needed the builders suite to pass the new DocumentSystemValues argument to validate_document_properties. Also clean: cargo clippy -p dash-platform-cxx --all-targets -- -D warnings with and without --features mocks, cargo fmt --check and cargo machete. scripts/cxx-smoke.sh links and runs from C++ on macOS, including construction with a proxy and the refusal of a proxy host name.
  • Live testnet E2E from dash-qt on the real PV13 testnet, with dash-qt statically linked against a release build of this crate. The earlier rounds used the pre-rebase tip b40f44c12e. The final round on 2026-09-28 used fd356b5c9f, this branch before the rebase onto the current v4.2-dev. Its crate sources match this head apart from the one-line test fix. It also covered interop with the DashPay iOS app. Results:
    • Registration: a new encrypted wallet went from opt-in to a registered username, with identity create, preorder and domain all returning code=0. The name resolves on Platform to the new identity, which confirms the PV13 document-id fix. A later round registered two more names on encrypted wallets under a single passphrase prompt.
    • Profile: created natively. The profile document on Platform has the entered display name and message at revision 1.
    • Contacts: a request was sent and accepted, and the sender picked up the acceptance without pressing Accept or signing anything new. Ignore and restore also worked, and contacts were recovered on relaunch.
    • Payments: paying by DashPay username and through the contact picker resolved a fresh address from the contact request each time. Receipts were InstantSend-locked and labelled with the payer's username.
    • Network off: Platform sockets dropped to 0 within 5 s.
    • iOS interop (final round): contact requests in both directions between dash-qt and the DashPay iOS app, a DIP-15 re-send picked up as the newest request, and payments both ways.
    • The remaining findings are GUI-side (Core) issues.

Breaking Changes

None. This adds a new crate, and nothing existing changes apart from workspace membership, the Rust CI package filters and the workflow. (The base PR's AppliedRequestSettings.proxy field is described there.)

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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Consumer. The dash-qt stack tracked in dashpay/dash#7512 is the only consumer:

Core pins this crate by dashpay/platform commit. A Core-side bundle script (contrib/devtools/platform-bundle.sh) produces a reproducible, sha256-pinned vendored crate bundle for that commit, and depends builds the static library from it. The bundle gains one crate, tokio-socks (MIT), from the base PR. No other rs-sdk change is required.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PastaPastaPasta
PastaPastaPasta force-pushed the feat/shared-dpns-dashpay-builders branch from 0631c07 to 5279114 Compare September 8, 2026 21:16
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 8, 2026
…crate

dashpay/platform#4633 rebuilds the Platform CXX bindings as a thin bridge over dash-sdk: the SDK owns DAPI transport, retries and proof verification, and Core supplies endpoints, quorum keys, its ChainLock height and wallet signatures. The crate is an ordinary workspace member now, so the package vendors from the workspace root (the lockfile made vendorable by dashpay/platform#4631), builds with -p dash-platform-cxx, and installs the header tree the crate's build.rs stages plus the static archive; the nested standalone manifest and install.sh are gone with the old design.

mbedtls leaves depends: the SDK carries its own TLS stack (rustls with the system trust store), so Core no longer links a TLS library for Platform. The vendoring config gains the workspace's git sources.

Validated on aarch64-apple-darwin: make -C depends PLATFORM_GUI=1 platform_cxx vendors 840 crates (150 MB archive) and builds the crate offline in 3 minutes; the staged prefix carries include/dash/platform/{ffi.h,signer.h}, include/rust/cxx.h and lib/libdash_platform_cxx.a. The knob-off package set is unchanged.
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 8, 2026
…ble-platform-gui

The Qt-free client library dash-qt drives for DashPay: per-network parameters and system contract ids, the PlatformClient interface, DPP decoding and state-transition adapters, and the wallet record formats. Where the earlier revision (dashpay#7626) carried its own gRPC-Web/TLS transport, hand-written protobuf and CBOR encoders, per-endpoint retry and freshness tracking, and handed request/response byte pairs to a transport-free verifier, this one is a thin consumer of the Dash Platform SDK through dash-platform-cxx (dashpay/platform#4633). The SDK owns query construction, DAPI transport, retries with address banning, proof verification (GroveDB replay plus the Tenderdash quorum signature against the keys this node pushes from its LLMQ store), protocol-version tracking and the chain-id and ChainLock freshness checks; the node supplies evonode endpoints from its deterministic masternode list, the Platform quorum keys, its best ChainLock height and wallet signatures through a digest callback, so private keys never leave the wallet.

The PlatformClient interface the GUI programs against is unchanged apart from gaining an sdk() accessor; the production implementation keeps its single worker thread and callback marshalling and forwards each query to the SDK handle. Absence stays proven, never inferred: an empty result only reaches a callback after the SDK verified a proof of it. The DPP decoders and state-transition builders take the SDK handle so they build under the protocol version the SDK has seen the network run, ratcheted up from the per-network floor in params.cpp. The C++ transport, protobuf, CBOR, retry and freshness code and their unit tests are gone with the design; the DPP byte-exactness suite and the wallet key tests stay, and the fuzz harness keeps the decoder targets (proof verification is fuzzed upstream).

Validated on aarch64-apple-darwin against a depends prefix carrying the SDK-backed archive: configure detects the bindings, libdash_platform.a and test_dash build, platform_dpp_tests and platformkeys_tests pass.
@PastaPastaPasta
PastaPastaPasta force-pushed the feat/shared-dpns-dashpay-builders branch from 5279114 to 47b05e3 Compare September 9, 2026 21:46
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 9, 2026
…crate

dashpay/platform#4633 rebuilds the Platform CXX bindings as a thin bridge over dash-sdk: the SDK owns DAPI transport, retries and proof verification, and Core supplies endpoints, quorum keys, its ChainLock height and wallet signatures. The crate is an ordinary workspace member now, so the package vendors from the workspace root (the lockfile made vendorable by dashpay/platform#4631), builds with -p dash-platform-cxx, and installs the header tree the crate's build.rs stages plus the static archive; the nested standalone manifest and install.sh are gone with the old design.

mbedtls leaves depends: the SDK carries its own TLS stack (rustls with the system trust store), so Core no longer links a TLS library for Platform. The vendoring config gains the workspace's git sources.

Validated on aarch64-apple-darwin: make -C depends PLATFORM_GUI=1 platform_cxx vendors 840 crates (150 MB archive) and builds the crate offline in 3 minutes; the staged prefix carries include/dash/platform/{ffi.h,signer.h}, include/rust/cxx.h and lib/libdash_platform_cxx.a. The knob-off package set is unchanged.
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 9, 2026
…ble-platform-gui

The Qt-free client library dash-qt drives for DashPay: per-network parameters and system contract ids, the PlatformClient interface, DPP decoding and state-transition adapters, and the wallet record formats. Where the earlier revision (dashpay#7626) carried its own gRPC-Web/TLS transport, hand-written protobuf and CBOR encoders, per-endpoint retry and freshness tracking, and handed request/response byte pairs to a transport-free verifier, this one is a thin consumer of the Dash Platform SDK through dash-platform-cxx (dashpay/platform#4633). The SDK owns query construction, DAPI transport, retries with address banning, proof verification (GroveDB replay plus the Tenderdash quorum signature against the keys this node pushes from its LLMQ store), protocol-version tracking and the chain-id and ChainLock freshness checks; the node supplies evonode endpoints from its deterministic masternode list, the Platform quorum keys, its best ChainLock height and wallet signatures through a digest callback, so private keys never leave the wallet.

The PlatformClient interface the GUI programs against is unchanged apart from gaining an sdk() accessor; the production implementation keeps its single worker thread and callback marshalling and forwards each query to the SDK handle. Absence stays proven, never inferred: an empty result only reaches a callback after the SDK verified a proof of it. The DPP decoders and state-transition builders take the SDK handle so they build under the protocol version the SDK has seen the network run, ratcheted up from the per-network floor in params.cpp. The C++ transport, protobuf, CBOR, retry and freshness code and their unit tests are gone with the design; the DPP byte-exactness suite and the wallet key tests stay, and the fuzz harness keeps the decoder targets (proof verification is fuzzed upstream).

Validated on aarch64-apple-darwin against a depends prefix carrying the SDK-backed archive: configure detects the bindings, libdash_platform.a and test_dash build, platform_dpp_tests and platformkeys_tests pass.
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 9, 2026
…crate

dashpay/platform#4633 rebuilds the Platform CXX bindings as a thin bridge over dash-sdk: the SDK owns DAPI transport, retries and proof verification, and Core supplies endpoints, quorum keys, its ChainLock height and wallet signatures. The crate is an ordinary workspace member now, so the package vendors from the workspace root (the lockfile made vendorable by dashpay/platform#4631), builds with -p dash-platform-cxx, and installs the header tree the crate's build.rs stages plus the static archive; the nested standalone manifest and install.sh are gone with the old design.

mbedtls leaves depends: the SDK carries its own TLS stack (rustls with the system trust store), so Core no longer links a TLS library for Platform. The vendoring config gains the workspace's git sources.

Validated on aarch64-apple-darwin: make -C depends PLATFORM_GUI=1 platform_cxx vendors 840 crates (150 MB archive) and builds the crate offline in 3 minutes; the staged prefix carries include/dash/platform/{ffi.h,signer.h}, include/rust/cxx.h and lib/libdash_platform_cxx.a. The knob-off package set is unchanged.
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 9, 2026
…ble-platform-gui

The Qt-free client library dash-qt drives for DashPay: per-network parameters and system contract ids, the PlatformClient interface, DPP decoding and state-transition adapters, and the wallet record formats. Where the earlier revision (dashpay#7626) carried its own gRPC-Web/TLS transport, hand-written protobuf and CBOR encoders, per-endpoint retry and freshness tracking, and handed request/response byte pairs to a transport-free verifier, this one is a thin consumer of the Dash Platform SDK through dash-platform-cxx (dashpay/platform#4633). The SDK owns query construction, DAPI transport, retries with address banning, proof verification (GroveDB replay plus the Tenderdash quorum signature against the keys this node pushes from its LLMQ store), protocol-version tracking and the chain-id and ChainLock freshness checks; the node supplies evonode endpoints from its deterministic masternode list, the Platform quorum keys, its best ChainLock height and wallet signatures through a digest callback, so private keys never leave the wallet.

The PlatformClient interface the GUI programs against is unchanged apart from gaining an sdk() accessor; the production implementation keeps its single worker thread and callback marshalling and forwards each query to the SDK handle. Absence stays proven, never inferred: an empty result only reaches a callback after the SDK verified a proof of it. The DPP decoders and state-transition builders take the SDK handle so they build under the protocol version the SDK has seen the network run, ratcheted up from the per-network floor in params.cpp. The C++ transport, protobuf, CBOR, retry and freshness code and their unit tests are gone with the design; the DPP byte-exactness suite and the wallet key tests stay, and the fuzz harness keeps the decoder targets (proof verification is fuzzed upstream).

Validated on aarch64-apple-darwin against a depends prefix carrying the SDK-backed archive: configure detects the bindings, libdash_platform.a and test_dash build, platform_dpp_tests and platformkeys_tests pass.
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 9, 2026
…crate

dashpay/platform#4633 rebuilds the Platform CXX bindings as a thin bridge over dash-sdk: the SDK owns DAPI transport, retries and proof verification, and Core supplies endpoints, quorum keys, its ChainLock height and wallet signatures. The crate is an ordinary workspace member now, so the package vendors from the workspace root (the lockfile made vendorable by dashpay/platform#4631), builds with -p dash-platform-cxx, and installs the header tree the crate's build.rs stages plus the static archive; the nested standalone manifest and install.sh are gone with the old design.

mbedtls leaves depends: the SDK carries its own TLS stack (rustls with the system trust store), so Core no longer links a TLS library for Platform. The vendoring config gains the workspace's git sources.

Validated on aarch64-apple-darwin: make -C depends PLATFORM_GUI=1 platform_cxx vendors 840 crates (150 MB archive) and builds the crate offline in 3 minutes; the staged prefix carries include/dash/platform/{ffi.h,signer.h}, include/rust/cxx.h and lib/libdash_platform_cxx.a. The knob-off package set is unchanged.
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 9, 2026
…ble-platform-gui

The Qt-free client library dash-qt drives for DashPay: per-network parameters and system contract ids, the PlatformClient interface, DPP decoding and state-transition adapters, and the wallet record formats. Where the earlier revision (dashpay#7626) carried its own gRPC-Web/TLS transport, hand-written protobuf and CBOR encoders, per-endpoint retry and freshness tracking, and handed request/response byte pairs to a transport-free verifier, this one is a thin consumer of the Dash Platform SDK through dash-platform-cxx (dashpay/platform#4633). The SDK owns query construction, DAPI transport, retries with address banning, proof verification (GroveDB replay plus the Tenderdash quorum signature against the keys this node pushes from its LLMQ store), protocol-version tracking and the chain-id and ChainLock freshness checks; the node supplies evonode endpoints from its deterministic masternode list, the Platform quorum keys, its best ChainLock height and wallet signatures through a digest callback, so private keys never leave the wallet.

The PlatformClient interface the GUI programs against is unchanged apart from gaining an sdk() accessor; the production implementation keeps its single worker thread and callback marshalling and forwards each query to the SDK handle. Absence stays proven, never inferred: an empty result only reaches a callback after the SDK verified a proof of it. The DPP decoders and state-transition builders take the SDK handle so they build under the protocol version the SDK has seen the network run, ratcheted up from the per-network floor in params.cpp. The C++ transport, protobuf, CBOR, retry and freshness code and their unit tests are gone with the design; the DPP byte-exactness suite and the wallet key tests stay, and the fuzz harness keeps the decoder targets (proof verification is fuzzed upstream).

Validated on aarch64-apple-darwin against a depends prefix carrying the SDK-backed archive: configure detects the bindings, libdash_platform.a and test_dash build, platform_dpp_tests and platformkeys_tests pass.
@PastaPastaPasta
PastaPastaPasta force-pushed the feat/shared-dpns-dashpay-builders branch 2 times, most recently from cfb93ac to edb833b Compare September 10, 2026 20:18
@thepastaclaw

thepastaclaw commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Review not started yet because this PR is a draft.

  • Request normal review — click when the PR is ready for review.
  • Request priority review — click to move this review to the front of the queue.

Commit fb52ec0. Normal review starts when eligible; priority review starts as soon as a slot is available.

@PastaPastaPasta
PastaPastaPasta force-pushed the feat/platform-sdk-cxx branch 2 times, most recently from 25fd33c to b40f44c Compare September 25, 2026 09:20
@PastaPastaPasta
PastaPastaPasta changed the base branch from feat/shared-dpns-dashpay-builders to v4.2-dev September 25, 2026 21:38
@PastaPastaPasta PastaPastaPasta changed the title feat(sdk): add CXX bindings over dash-sdk for C++ embedders feat(sdk): add dash-platform-cxx, a thin CXX shell over dash-sdk for C++ embedders Sep 25, 2026
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 25, 2026

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Phase 1 + Phase 2

The CXX shell has four blocking correctness defects: PV13 pagination can silently omit contact requests, contested vote state is truncated at 100 contenders without continuation, rejected foreign-chain responses can poison the SDK's protocol-version state, and document builders omit the state-dependent contest funding required for existing contests. Several additional API, input-validation, and test-quality issues should also be addressed before relying on the new interface.

🔴 4 blocking | 🟡 6 suggestion(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: ffi-engineer); reviewer 4: muse-spark-1.3-contributor (agent: phase1-reviewer, role: platform-versioning); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 6: muse-spark-1.3-contributor (agent: phase1-reviewer, role: security-auditor); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6-astra (agent: phase2-reviewer, role: ffi-engineer); reviewer 10: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: critical by gpt-6-astra (effort low) — This large, intricate new FFI layer directly introduces signature and key-handling logic in packages/rs-platform-cxx/src/signer.rs, including wallet signing callbacks and AssetLockSigner::sign_ecdsa compact-signature parsing and public-key recovery, alongside signed state-transition assembly in builders.rs.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — architecture-layering (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — ffi-engineer (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — platform-versioning (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — security-auditor (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 13% left, 5h 100% left), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-cxx/src/ops.rs`:
- [BLOCKING] packages/rs-platform-cxx/src/ops.rs:644-670: Guard contact-request pagination against PV13 timestamp-boundary skips
  This query orders by `$createdAt` and uses the generic document-ID cursor, but protocol versions below 14 can return a proven empty continuation when a page boundary splits contact requests sharing the same `$createdAt`. The first page can therefore contain 100 requests and report `has_more`, while the next call returns zero items and `has_more = false`, silently omitting requests. Requests created in the same Platform block can share a timestamp, and the behavior affects both incoming and outgoing queries. Use a safe continuation strategy or return `Unavailable` for unsupported PV13 continuations, as `names_of_identity` already does, and add same-timestamp paging coverage.
- [BLOCKING] packages/rs-platform-cxx/src/ops.rs:724-739: Expose continuation for contested states exceeding 100 contenders
  The request always uses `limit: Some(CONTESTED_VOTE_COUNT)` with `CONTESTED_VOTE_COUNT = 100` and `start_at: None`. `VerifiedContested` exposes neither a cursor nor an incomplete-result indicator. Contests can legally contain more than 100 contenders; the current protocol limits permit up to 1,000. The API therefore returns a successful, proved first page as though it were the complete contested state, so an embedder cannot reliably find a later contender or display all tallies. Add pagination/completeness information while preserving the one-request-per-call contract.
- [BLOCKING] packages/rs-platform-cxx/src/ops.rs:169-227: Apply chain acceptance before the SDK mutates verification state
  `fetch` calls `client.run`, which performs proof verification inside the SDK, before calling the shell's `accept` function. Proof verification invokes `Sdk::verify_response_metadata`, which ratchets the shared SDK protocol version before `accept` checks the signed chain ID. A correctly signed response from a foreign chain can therefore advance the SDK to a newer protocol version, be rejected as `ChainIdMismatch`, and leave a later valid legacy response unable to parse under the mutated version. The shell's own watermark remains unchanged, but the shared SDK state is still poisoned. Add an SDK acceptance hook that checks the embedder's chain policy after proof/signature verification and before `verify_response_metadata` commits the version, or otherwise prevent rejected metadata from mutating SDK state.

In `packages/rs-platform-cxx/src/builders.rs`:
- [BLOCKING] packages/rs-platform-cxx/src/builders.rs:213-228: Preserve state-dependent contest-fund preparation in the document builder
  The create path passes `None` for `StateTransitionCreationOptions`, so it bypasses `rs-sdk`'s `with_contest_fund_to_join` preparation. That preparation reads the existing contender count and increases the prefund when joining an existing contest. At the first funding-doubling threshold, the transition built here carries only the base fund while Drive requires the larger amount and rejects it with `DocumentContestNotPaidForError`. The CXX API exposes no contest maximum or creation-options parameter that lets callers supply the required amount. Share the SDK's canonical preparation/build path or expose an explicit, state-aware maximum and test a domain joining an existing contest.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:256-314: Keep protocol-owned document and DIP-15 encoding logic in lower layers
  The CXX shell duplicates protocol semantics that already belong to lower layers: DPNS preorder hashing and document properties, DashPay profile create/replace assembly, and the DIP-15 ASK28/account-reference bit layout in `helpers.rs:71-83`. The implementations match today, but future system-contract or DIP-15 corrections can update the SDK or `platform-encryption` while leaving C++ callers on stale behavior. Add pure, parameterized builders and MAC-based helpers to the owning crates, then have this adapter perform only FFI conversion and signing integration.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:455-466: Reject contradictory recipient IDs before building a contact request
  `ContactRequestInput` carries `to_user_id`, but the builder never validates or uses it. The SDK derives the document's `toUserId` from `recipient.id()` instead. If the two FFI inputs disagree, the builder successfully signs a request addressed to a different identity than the caller supplied, making contradictory embedder state silently actionable. Validate equality before invoking the SDK or remove the redundant field, and add a mismatch test that confirms no signer callback is made.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:418-423: Check the profile revision increment before building a replacement
  `existing.revision + 1` is computed from embedder-supplied data without overflow checking. At `u64::MAX`, debug builds panic and release builds wrap to zero, producing an invalid replacement revision rather than rejecting the input before signing.

In `packages/rs-platform-cxx/tests/cxx_smoke.cc`:
- [SUGGESTION] packages/rs-platform-cxx/tests/cxx_smoke.cc:159-169: Value-initialize the C++ identity key before passing it to Rust
  The generated C++ `ContractBounds` contains a `std::array<uint8_t, 32> contract_id` without a default member initializer. `platform_ffi::IdentityKey key;` therefore leaves that array indeterminate; assigning only `bounds.kind` does not initialize it. Rust unconditionally copies `bounds.contract_id` before matching `NoBounds`, so this smoke test reads uninitialized bytes across the CXX boundary even though the field is unused for this variant.

In `packages/rs-platform-cxx/src/helpers.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/helpers.rs:71-84: Reject or explicitly constrain DIP-15 versions above four bits
  The public helper accepts `version: u32` but stores it in the top four bits using `(version << 28)`. Values above `0x0f` silently lose their high bits; for example, version 16 produces version bits zero and cannot round-trip through the inverse helper. Validate the range at the FFI boundary or change the API to return an error instead of silently truncating caller input.

In `packages/rs-platform-cxx/src/lib.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/lib.rs:873-885: Keep the network-free contact builder independent of endpoint readiness
  Unlike the other builders, `build_contact_request` calls `self.0.sdk()?` before invoking the SDK's local contact-document assembly. With no endpoints pushed, this returns `no evonode endpoints` even when a verified protocol version and all contact inputs are available. The API documents builders as requiring no network request, and the other builders work after verification without an endpoint set. Provide an endpoint-independent SDK/context handle for this local operation or otherwise make the readiness precondition consistent across builders.

Comment thread packages/rs-platform-cxx/src/ops.rs Outdated
Comment thread packages/rs-platform-cxx/src/ops.rs Outdated
Comment thread packages/rs-platform-cxx/src/ops.rs Outdated
Comment thread packages/rs-platform-cxx/src/builders.rs Outdated
Comment thread packages/rs-platform-cxx/src/builders.rs
Comment thread packages/rs-platform-cxx/src/builders.rs
Comment thread packages/rs-platform-cxx/tests/cxx_smoke.cc Outdated
Comment thread packages/rs-platform-cxx/src/helpers.rs
Comment thread packages/rs-platform-cxx/src/builders.rs
Comment thread packages/rs-platform-cxx/src/lib.rs
@PastaPastaPasta
PastaPastaPasta force-pushed the feat/dapi-client-socks5-proxy branch from 0f8fc29 to 324dbc9 Compare September 29, 2026 05:01
@PastaPastaPasta
PastaPastaPasta force-pushed the feat/platform-sdk-cxx branch 2 times, most recently from c5752ab to 35eac29 Compare September 29, 2026 05:09

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — Preliminary review — Phase 1 blocker gate

The CXX shell preserves the intended trust and version-gating boundaries, but several previously identified correctness issues remain. In particular, contested vote results are silently truncated, contact-request pagination can skip results on PV13, and foreign-chain proof verification can mutate SDK protocol-version state before the shell rejects the response.

Validated blockers were found by the Phase-1 review and confirmed by a fresh verifier. Phase 2 is deferred until a fresh same-head revalidation clears the blocker gate.

🔴 4 blocking | 🟡 6 suggestion(s)

10 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: ffi-engineer); reviewer 4: muse-spark-1.3-contributor (agent: phase1-reviewer, role: platform-versioning); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 6: muse-spark-1.3-contributor (agent: phase1-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-gate-verifier, role: verifier)

  • Triage: critical by gpt-6-astra (effort low) — This large, intricate addition directly implements signature and key-handling boundaries in packages/rs-platform-cxx/src/signer.rs (including AssetLockSigner::sign_ecdsa signature parsing and public-key recovery) and quorum trust-anchor enforcement in src/provider.rs, meeting both the complexity and critical-surface criteria.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — architecture-layering (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — ffi-engineer (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — platform-versioning (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — security-auditor (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 13% left, 5h 100% left), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6-astra — verifier; agent astra-gate-verifier
  • Phase 2 reviewers: not run (deferred by blocker gate)
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-cxx/src/lib.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/lib.rs:873-885: Keep the network-free contact builder independent of endpoint readiness
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701915)
  The contact builder only assembles and signs a request using the local context provider; it does not perform network I/O. However, this entry point requires `self.0.sdk()`, which returns an error until endpoints have been installed and also fails after endpoints are cleared. This unnecessarily couples offline transition construction to transport readiness. Retain a provider-backed SDK or otherwise separate builder SDK acquisition from the live network SDK.

In `packages/rs-platform-cxx/src/builders.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:418-423: Check the profile revision increment before building a replacement
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701907)
  The replacement revision is derived from embedder-supplied data with plain `existing.revision + 1`. At `u64::MAX` this panics in debug builds or wraps in release builds, rather than returning a controlled error before signing. Use checked arithmetic at the boundary.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:455-468: Reject contradictory recipient IDs before building a contact request
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701877)
  The bridge accepts both a structured recipient identity and `input.to_user_id`, but the builder never compares them. The SDK request is constructed from the structured recipient, so a caller can provide a different `to_user_id` and receive a successfully built request addressed to a different identity than the input claims. Validate the two IDs before deriving the shared secret and signing.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:256-314: Keep protocol-owned document and DIP-15 encoding logic in lower layers
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701869)
  The shell duplicates protocol-owned DPNS commitment hashing and preorder/domain document construction that already has a canonical SDK/DPP path. The PR describes this crate as a thin shell, but these copies can drift when the SDK or system-contract behavior changes, causing transitions to be rejected only after signing. Move the canonical constructors or encodings into the owning layer and keep this crate focused on FFI translation.
- [BLOCKING] packages/rs-platform-cxx/src/builders.rs:213-228: Preserve state-dependent contest-fund preparation in the document builder
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701856)
  The SDK's normal document-create path calls `with_contest_fund_to_join`, which reads the current contest state and supplies the maximum fund required to join, including the increased amount for crowded contests. This CXX builder calls the lower-level DPP constructor directly with `None` for `StateTransitionCreationOptions` and performs no equivalent state read. DPP only applies a contest fund when one is supplied; it does not perform the SDK's live contest lookup here. Consequently, a contested domain can be built without the required join fund and be rejected after signing and nonce/fee processing. The builder must obtain and pass the state-dependent contest fund, or expose a state-aware SDK helper for this path.

In `packages/rs-platform-cxx/src/helpers.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/helpers.rs:71-84: Reject or explicitly constrain DIP-15 versions above four bits
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701903)
  The account-reference format reserves only the top four bits for the rotation version, but this function accepts an unrestricted `u32` and shifts it into those bits. Values above 15 silently alias or corrupt the encoded reference. Reject versions greater than 15, or explicitly mask them if wrapping is the intended contract and document that behavior.

In `packages/rs-platform-cxx/tests/cxx_smoke.cc`:
- [SUGGESTION] packages/rs-platform-cxx/tests/cxx_smoke.cc:159-169: Value-initialize the C++ identity key before passing it to Rust
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701882)
  The smoke test default-declares `platform_ffi::IdentityKey key;` and initializes only selected fields. The bounds structure includes an array field that is not explicitly initialized, so this test can pass indeterminate bytes across the CXX boundary. Use value-initialization to make the bridge input deterministic.

In `packages/rs-platform-cxx/src/ops.rs`:
- [BLOCKING] packages/rs-platform-cxx/src/ops.rs:169-227: Apply chain acceptance before the SDK mutates verification state
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701850)
  The shell checks `metadata.chain_id` only after `client.run(op)` has completed SDK proof verification. `Client::platform_version` documents that the SDK ratchets its internal protocol version during verification, before this chain-id check. A validly signed response from another chain can therefore mutate the shared SDK's version before being rejected by the shell. That state is later used by SDK operations such as contact-request creation, so the foreign-chain response can influence subsequent local behavior. Move chain acceptance ahead of the ratchet or isolate and roll back the SDK state when the chain check fails.
- [BLOCKING] packages/rs-platform-cxx/src/ops.rs:724-755: Expose continuation for contested states exceeding 100 contenders
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701841)
  The contested vote query sets `limit: Some(CONTESTED_VOTE_COUNT)` with a limit of 100 and no `start_at`, offset, cursor, or `has_more` result. Contests can contain more than 100 contenders, so the FFI returns an apparently successful but incomplete tally. Either page through all contenders internally or expose a continuation mechanism and make truncation explicit.
- [BLOCKING] packages/rs-platform-cxx/src/ops.rs:644-677: Guard contact-request pagination against PV13 timestamp-boundary skips
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701828)
  Contact requests are ordered by `$createdAt` but continuation uses only a document-id `StartAfter` cursor while the optional filter uses `$createdAt > since_ms`. The neighboring `names_of_identity` implementation explicitly rejects PV13 continuation because this class of index/cursor behavior can return an empty page while entries remain. Contact-request pagination has no equivalent guard, overlap, or compound timestamp-plus-ID cursor, so a multi-page PV13 inbox can silently skip requests at the page boundary. Apply the same PV13 guard or implement a boundary-safe cursor.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review — Final validation — Phase 1 + Phase 2

The exact head preserves the SDK proof-verification boundary and does not modify shipped consensus behavior. Eight client/API and FFI-fixture issues remain; under the supplied severity policy, these are suggestions rather than consensus blockers. This verification was static only: no builds or tests were run, and the supplied CI snapshot contains no Rust/CXX validation result.

🟡 8 suggestion(s)

8 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 8: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 13: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 14: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This large new integration directly implements signature handling and asset-lock public-key recovery in packages/rs-platform-cxx/src/signer.rs and quorum-key trust and proof freshness checks in provider.rs, meeting both the intricacy and critical-surface criteria.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 15% left, 5h 100% left), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-cxx/src/builders.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:217-228: Preserve state-dependent contest-fund preparation in the document builder
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701856)
  The final None leaves creation options at their default, so DPP attaches only the base contested-document fund. At PV14, required_vote_resolution_fund_to_join requires twice that amount when 250 contenders already exist, with further increases every 50 contenders. Document-create state_v2 rejects an insufficient stated maximum during paid validation. The regular SDK create path prepares this through with_contest_fund_to_join, but build_dpns_domain exposes no funding override, so callers cannot construct a valid join for these populated contests through the bridge. Preserve network-free construction by accepting an explicit contest-fund maximum and forwarding it through StateTransitionCreationOptions.contest_fund, with state-dependent preparation delegated to the SDK. Add a populated-contest regression; comparisons against another DPP construction using None reproduce the omission.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:455-466: Reject contradictory recipient IDs before building a contact request
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701877)
  ContactRequestInput exposes to_user_id, but this conversion never reads or validates it. The SDK constructs toUserId from the separately supplied recipient identity, so contradictory bridge inputs can successfully sign a request addressed to a different identity from the one named in input.to_user_id. The public-key check validates the encryption key selected from that recipient identity; it does not establish agreement between the two destination IDs. Reject a mismatch before minting or signing, or remove the redundant field so the bridge has one authoritative destination. Add a negative case asserting that the signer is not called.
- [SUGGESTION] packages/rs-platform-cxx/src/builders.rs:418-424: Check the profile revision increment before building a replacement
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701907)
  The bridge revision is an unrestricted u64, and the preceding validation rejects only values below INITIAL_REVISION. An input of u64::MAX reaches existing.revision + 1: overflow-checked builds panic, while ordinary release builds wrap to zero. The replacement constructor checks that a revision exists but does not reject zero before signing, so release mode can return a signed invalid replacement. Use checked_add, or construct the document at the existing revision and delegate to DPP's checked increment_revision method. Return a descriptive local error and test that the maximum revision is refused before the signer callback.

In `packages/rs-platform-cxx/src/ops.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/ops.rs:723-727: Expose continuation for contested states exceeding 100 contenders
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701841)
  This query always requests the first 100 contenders. The bridge accepts no continuation, and VerifiedContested exposes neither page metadata nor a completeness indicator. Valid contests can exceed this limit; PV14 permits 1,000 contenders. A contest containing 101 contenders therefore returns Ok while omitting an identity and its tally, and the caller cannot retrieve that omitted entry through this API. The proof authenticates the bounded query, not a complete contender list. Expose the existing query's start_at capability and page metadata, preserving one SDK request per bridge call, or return an explicit incomplete outcome when completeness cannot be established. Cover a contest exceeding 100 contenders in the replay suite.
- [SUGGESTION] packages/rs-platform-cxx/src/ops.rs:642-664: Guard contact-request pagination against PV13 timestamp-boundary skips
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701828)
  StartAfter is unsafe here at PV13 when a page boundary splits requests sharing $createdAt. With a positive since_ms, the frozen v0 lowering passes the exclusive cursor to WhereClause::to_path_query, which starts strictly after the cursor's timestamp key; the equality-only route also excludes that terminal timestamp key. Remaining document IDs at that timestamp are skipped. Both prover and verifier use this lowering, so the next page can verify successfully and return Ok with has_more=false despite omitted contacts. PV14's cursor-padding path is version-gated and does not protect PV13. Add a fail-closed legacy continuation guard, analogous to names_of_identity, or a demonstrably complete legacy paging strategy. Test a page boundary splitting equal timestamps at PV13 and PV14 without changing shipped Drive v0.

In `packages/rs-platform-cxx/tests/cxx_smoke.cc`:
- [SUGGESTION] packages/rs-platform-cxx/tests/cxx_smoke.cc:157: Value-initialize the C++ identity key before passing it to Rust
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701882)
  The pinned CXX 1.0.198 generator supplies default member initializers for primitive scalar fields, but not std::array members. IdentityKey key therefore leaves bounds.contract_id indeterminate. Assigning NoBounds does not initialize that array, and builders::contract_bounds reads it into an Identifier before matching the bounds kind. The devnet builder path consequently reads uninitialized input across the FFI boundary. Value-initialize the key. Apply the same initialization discipline to the devnet ContactRequestInput at line 204: recipient_pubkey is never assigned, yet build_contact_request reads it through PublicKey::from_slice before producing the expected error. Rust Default and catch_unwind do not make these C++ inputs initialized.

In `packages/rs-platform-cxx/src/lib.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/lib.rs:876-881: Keep the network-free contact builder independent of endpoint readiness
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701915)
  This local builder requires self.0.sdk(), which fails when the endpoint set is empty. After an accepted read establishes the protocol version, set_endpoints([]) deliberately drops that SDK while preserving the verified version. Other builders remain usable, but contact construction fails with no evonode endpoints even though both identities, encryption inputs, nonce and signing callback are supplied locally and the contract comes from the local provider. Separate local minting capability from transport readiness rather than retaining the connection-owning SDK solely for this builder. Add coverage for accepting a read, clearing endpoints, and then constructing a contact request.

In `packages/rs-platform-cxx/src/helpers.rs`:
- [SUGGESTION] packages/rs-platform-cxx/src/helpers.rs:78-84: Reject or explicitly constrain DIP-15 versions above four bits
  (existing thread: https://github.com/dashpay/platform/pull/4633#discussion_r4129701903)
  The bridge accepts a u32 version but encodes only its low four bits through version << 28. Version 16 therefore encodes identically to version 0, and the advertised inverse cannot recover the supplied version. This matches the existing platform-encryption formula, so it is not an encoding divergence, but the new API does not explicitly state a supported input range or rollover policy. Document and test a 0..=15 precondition, explicitly define modulo-16 behavior, or make the operation fallible for larger versions. This avoids callers treating an unrestricted rotation counter as a lossless input.

@PastaPastaPasta
PastaPastaPasta marked this pull request as draft October 5, 2026 17:41
PastaPastaPasta and others added 5 commits October 6, 2026 13:11
packages/rs-platform-cxx rebuilt on v4.2-dev with dash-sdk (default-features off: dpns-contract, dashpay-contract, core_key_wallet) and platform-encryption as its only Platform dependencies; the direct dpp/drive/platform-version/context-provider/dash-platform-queries dependencies, the #4632 builders and the decode/st/queries modules are gone.

Bridge (namespace platform_ffi): Config{network, tenderdash_chain_id, platform_llmq_type, proxy}; nine-kind Status; one Verified* struct per read with typed ProvenAbsent (also under an unsupported protocol version, which meta then shows; the default value could not tell absence apart); one page per bridge call with a StartAfter cursor, has_more computed against the query's own limit (at PV13 Drive answers a names_of_identity continuation with an empty page); typed BroadcastResult; builders returning Built{bytes, hash, object_id}; pure DPNS/DIP-15 helpers. Every entry, including new_platform_client and Drop, runs under catch_unwind, and the crate refuses panic=abort at compile time as well as in build.rs.

provider.rs is the push-model ContextProvider: LLMQ-type gate, quorum-hash normalization from Core's internal uint256 order, refusal of every proof until a local ChainLock height is pushed, a 288-block ChainLock-lag floor with no ceiling, and the compiled-in DPNS/DashPay contracts cached per protocol version with negative entries. Provider errors map to Status by variant, also when raised inside proof verification.

client.rs builds the SDK lazily from the https endpoint set the embedder pushes (any other scheme is refused: the address list would dial http in the clear; the host must be an IP address, or an onion name when a proxy is set, so nothing is resolved locally). Config.proxy{kind, address, isolate} is the embedder's SOCKS5 proxy (none, a numeric TCP address or a Unix socket; isolate = fresh random credentials per connection, one Tor circuit each), fixed for the client's lifetime and passed to every SDK it builds through SdkBuilder::with_proxy, with a 15 s connect budget instead of 5 s; a proxy the shell cannot use fails new_platform_client, so there is never a direct fallback. A proxy failure is Unavailable (the SDK neither retries nor bans for it), not Rejected. The DAPI client keeps a pooled channel, and so an open connection, per evonode it has talked to, and removing an address from the list does not touch the pool, which is private to the client: an empty set therefore drops the SDK (no Platform socket stays open while the embedder has disabled networking), a set that removes endpoints rebuilds the SDK over the same, updated AddressList (retained entries keep their ban state), and a set that only adds updates the list in place. The provider, the height watermark and the verified protocol version belong to the client and survive every rebuild; a rebuilt SDK is seeded at the verified version. Proved reads are not dispatched before a ChainLock anchor is pushed or after shutdown. The SDK watermark is off; ops.rs checks the chain id, keeps a tolerance-3 Platform-height watermark keyed after it, records the protocol version of every accepted response, and signals UnsupportedProtocolVersion while still returning the value. Builders and contested_vote_fund_credits take that verified version (transition rules and the contested prefund changed across versions), so they fail before the first accepted read, except on a devnet whose floor is the latest version, and once a version this build does not know was seen. Document queries load the compiled-in contracts at this build's latest version, since a network SDK seeded at the PV13 floor could not decode a DashPay v2 profile. A node's definitive (non-retryable) gRPC refusal of a read or a broadcast, including tonic's answer to a response above the 4 MiB decoding bound, is Rejected; no answer is Unavailable. search_names refuses an empty or over-long prefix before dispatch, which Drive would refuse anyway. runtime.rs owns the tokio runtime and aborts the in-flight task on shutdown.

signer.rs: BytesSigner hands the full signable preimage to WalletSigner::SignForKey; AssetLockSigner is the single digest path and recovers the public key from the compact signature (compressed-key header only, as Core funds P2PKH of the compressed key), so PublicKeyForKey is gone. The adapters take a SignerCallbacks trait whose Send + Sync impls sit on the extern type under the any-thread contract signer.h states. builders.rs: identity create through dpp try_from_identity_with_signers, DPNS preorder/domain and the profile create assembled inline until rs-sdk exports them, each create given the document id put_to_platform derives from its entropy and nonce at the verified version (dpp derives it itself only from PV14; up to PV13 it sends the document's id as is, and a zero id is refused by Drive with InvalidDocumentTransitionIdError), a property the network's contract does not have yet refused before signing, contact requests through Sdk::create_contact_request with client-side ECDH under the verified version. build_profile replaces the Profile a get_profile read at its revision + 1 and carries the fields the embedder does not edit (avatar, DashPay v2 payment addresses) into the replacement, since a replace transition carries the whole document and would otherwise erase what another wallet set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d PV14

Every test that depends on the protocol version runs at PV13 (what testnet and mainnet run, the SDK's floor for them) and at this build's latest, parametrized with test_case::test_matrix; the Drive fixture is generated at each version, signed by one test quorum.

tests/replay.rs replays the proved reads and the broadcast through the same client code an embedder runs, against dash-sdk's mock transport fed with proofs generated from a real Drive state (tests/common/mod.rs): GroveDB proofs from the state and a Tenderdash StateId/CanonicalVote signed by a test BLS quorum, so the SDK runs the full proof replay and quorum-signature check and only the socket is mocked. It covers the design's freshness matrix (proofless, tampered proof, tampered signature, unknown quorum, quorum hash in Core order, wrong LLMQ type, foreign chain id with no shell state moved while the SDK's ratchet does, no anchor, ChainLock lag 288/289 and one ahead, height watermark H/H-2/H-3 accepted and H-4/H-5 rejected, stale signed time recording no version, PV15 signalled with the value and closing the builders, a proven absence under PV15 still ProvenAbsent), identity by id and by key hash, nonce masking and the proven-absent nonce of an unused contract, resolve/search/names-of-identity, search prefix guards and a definitive gRPC refusal read as Rejected while an outage stays Unavailable, 101-name pages with has_more against the query's limit and a cursor continuation for both paged name reads (a names_of_identity continuation is an empty page at PV13, pinned), a profile with a DashPay v2 payment address where the contract has one and the profile at the network floor, contact requests, contested tallies and absence, every broadcast Status kind, and the lifecycle.

tests/builders.rs checks every builder byte for byte against dpp's in-process private-key constructions (identity create against try_from_identity_with_signer_and_private_key for instant and chain proofs, DPNS preorder/domain and profile create against put_to_platform's create path including its id derivation, the profile replace against its replacement path, the contact request against Sdk::create_contact_request and decryption with the same secret), every document's id recomputed as Drive's advanced-structure check does and its data validated against the system contract at that version, the profile replace carrying avatar and payment-address fields it does not edit, refusing another identity's profile and refusing at PV13 a payment address DashPay v1 lacks, the contested prefund, signer and bad-input refusals, public-key recovery for every compressed header, the no-reactor invariant, and test_data/state_transition_first_byte.json (Batch = 2, IdentityCreate = 3), checked against the built transitions and rewritten only under UPDATE_TEST_VECTORS=1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The README states the trust model (pushed quorum keys and ChainLock anchor, chain-id check, shell watermark, protocol-version signal, proven absence, refusal versus outage, panic containment), summarizes the bridge API by group, describes the SOCKS5 proxy passthrough (fixed Config.proxy, no direct fallback, per-connection isolation, proxy failures Unavailable without banning, the 15 s proxied connect budget), and fixes the threading and signing contract (blocking reads on the embedder's worker, builders on the calling thread, SignForKey over the signable preimage with the first-byte variant index, SignAssetLockSighash as the one digest path).

CI runs the crate's tests with nextest, links tests/cxx_smoke.cc from the staged headers and archive, and asserts the embedder's trust closure: no trusted-context-provider, reqwest, openssl-sys or native-tls in the dependency tree and no seed-list or unproved entry points in the sources.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The shell compared each verified response's signed chain_id with a
configured Config.tenderdash_chain_id. That check adds nothing: chain_id
is part of the message the Platform quorum signs, so a relabelled id
fails verification, and the signing quorum must be one the embedder
pushed from its own Core chain, so a response from another network does
not verify at all. The stale-node case after a testnet reset is covered
by the SDK's signed-time window and the 288-block ChainLock lag floor.
This matches the reasoning that closed #4963.

Removed from the bridge: Config.tenderdash_chain_id, Meta.chain_id and
StatusKind::ChainIdMismatch; UnsupportedProtocolVersion and Internal are
renumbered to 6 and 7. Client::tenderdash_chain_id() and the empty-id
refusal in Client::new are gone. accept() now runs the height watermark,
then records the verified protocol version, then raises the
unsupported-version signal.

The replay suite replaces the two foreign-chain-id cases with one that
relabels the chain id after signing (Rejected, no shell state moved) and
one that accepts a proof any pushed Platform quorum signed for another
chain id.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- get_contact_requests: below protocol version 14 Drive continues the
  (field, $createdAt) index after the cursor's creation time and skips the
  requests sharing it; a continuation there is Unavailable instead of a
  silently short list (as names_of_identity already does). The replay
  suite pages across a shared $createdAt at PV13 and PV14 and shows the
  since_ms re-read that recovers every request.
- get_contested_vote_state takes a start_after cursor and returns a Page,
  so contests above 100 contenders are reachable; the first page carries
  the tallies, an empty continuation is the end, not an absence.
- build_dpns_domain takes the contender count and states the fund to join
  that contest (PV14 doubles it past 250 contenders), as put_to_platform
  does; contest_fund_to_join exposes the amount.
- build_contact_request refuses a to_user_id that is not the recipient
  before signing and no longer needs endpoints (local_sdk).
- build_profile refuses a replace at revision u64::MAX instead of wrapping.
- dip15_account_reference_from_mac refuses a version above 15 or an
  account index above 28 bits instead of truncating.
- cxx_smoke.cc value-initializes every bridge struct.

C++ API changes: build_dpns_domain(.., salt, contenders, key, signer),
get_contested_vote_state(label, start_after), VerifiedContested.page,
contest_fund_to_join(contenders), and dip15_account_reference_from_mac
now throws rust::Error on out-of-range input.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@PastaPastaPasta
PastaPastaPasta force-pushed the feat/dapi-client-socks5-proxy branch from 2bb7671 to 6109d6e Compare October 6, 2026 18:15

This branch has not been deployed

No deployments
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.

2 participants