From 759e6dc544c727c796622daac5e0259023f3b2ea Mon Sep 17 00:00:00 2001 From: pasta Date: Fri, 7 Aug 2026 16:24:51 -0500 Subject: [PATCH] feat(sdk): add transport feature to dapi-grpc for types-only consumers 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/rustls and the tokio networking stack into its dependency tree even when it never opens a connection. The wasm target already proves the crate works with codegen-only tonic. Add an opt-in 'transport' cargo feature carrying tonic's channel/transport/tls features, mirroring the client/server feature split tenderdash-proto already has. It is deliberately NOT a default feature: cargo features are not target-scoped, so a default-on transport would force tonic's transport stack onto wasm32 consumers riding defaults, where it does not build. build.rs drives tonic-build's build_transport from CARGO_FEATURE_TRANSPORT (never on wasm32). Native networked consumers enable it explicitly: rs-dapi-client (target-scoped to non-wasm), dash-sdk (default feature, so SDK users are unchanged), and drive-abci via server (which now implies transport). wasm-sdk and other wasm consumers need no changes. 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 consumption is simply the default; embedders with their own transport depend on: dapi-grpc = { default-features = false, features = ["platform", "client"] } (default-features = false remains advisable for wasm and keeps the feature set explicit.) --- .../tests-rs-nightly-long-running.yml | 3 ++- .github/workflows/tests-rs-workspace.yml | 24 +++++++++++++++++ packages/dapi-grpc/Cargo.toml | 27 +++++++++++++------ packages/dapi-grpc/build.rs | 13 +++++++-- packages/rs-dapi-client/Cargo.toml | 5 ++++ 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests-rs-nightly-long-running.yml b/.github/workflows/tests-rs-nightly-long-running.yml index 5ba1d236fe7..460ba1b8d76 100644 --- a/.github/workflows/tests-rs-nightly-long-running.yml +++ b/.github/workflows/tests-rs-nightly-long-running.yml @@ -20,7 +20,8 @@ jobs: strategy: fail-fast: false matrix: - package: [dash-sdk, rs-dapi-client, rs-dapi, dapi-grpc, dpp, drive-abci] + package: + [dash-sdk, rs-dapi-client, rs-dapi, dapi-grpc, dpp, drive-abci, drive-proof-verifier] steps: - name: Check out repo uses: actions/checkout@v4 diff --git a/.github/workflows/tests-rs-workspace.yml b/.github/workflows/tests-rs-workspace.yml index 8883e9dd3ae..6e40a7618f4 100644 --- a/.github/workflows/tests-rs-workspace.yml +++ b/.github/workflows/tests-rs-workspace.yml @@ -193,6 +193,30 @@ jobs: cargo install cargo-machete 2>/dev/null || true cargo machete + # The transport-free cut is how embedders with their own networking + # (Dash Core's platform GUI, explorers) consume verification: feature + # unification hides transport regressions in whole-workspace builds, so + # check the standalone graphs and assert the networking stack stays out + # of the proof-verification tree (native) and out of wasm builds. + - name: Check transport-free feature cut + run: | + cargo check -p dapi-grpc --no-default-features --features core,platform,client --locked + cargo check -p drive-proof-verifier --locked + for banned in hyper rustls tower; do + if cargo tree -p drive-proof-verifier -e normal -i "$banned" 2>/dev/null | grep -q .; then + echo "::error::$banned leaked into drive-proof-verifier's dependency tree" + exit 1 + fi + done + for banned in hyper rustls tower mio; do + for wasm_package in dash-sdk wasm-sdk; do + if cargo tree -p "$wasm_package" --target wasm32-unknown-unknown -e normal -i "$banned" 2>/dev/null | grep -q .; then + echo "::error::$banned leaked into $wasm_package's wasm32 dependency tree" + exit 1 + fi + done + done + - name: Detect immutable structure changes if: github.event_name == 'pull_request' run: | diff --git a/packages/dapi-grpc/Cargo.toml b/packages/dapi-grpc/Cargo.toml index 498f8584c9d..7fd569f25fd 100644 --- a/packages/dapi-grpc/Cargo.toml +++ b/packages/dapi-grpc/Cargo.toml @@ -26,12 +26,30 @@ tenderdash-proto = [] # Client support. client = ["platform"] +# Networked tonic client: `connect()` on generated clients, TLS roots. +# Deliberately NOT in the default set: cargo features are not target-scoped, +# so a default-on transport would force tonic's transport stack onto wasm32 +# consumers riding defaults, where it does not build. Without this feature +# the crate provides message types and transport-generic client stubs only. +# Tonic's codegen graph still includes tokio-stream and a minimal Tokio +# slice, but its hyper/rustls transport stack is not enabled. Native +# networked consumers enable this explicitly (rs-dapi-client does so through +# its non-wasm dependency); the wasm codegen path never emits `connect()`. +transport = [ + "tonic/channel", + "tonic/transport", + "tonic/tls-native-roots", + "tonic/tls-webpki-roots", + "tonic/tls-ring", +] + # Build tonic server code. Includes all client features and adds server-specific dependencies. server = [ "platform", "tenderdash-proto/server", "client", "drive", + "transport", "tonic/router", ] @@ -55,14 +73,7 @@ tonic = { version = "0.14.2", features = ["codegen"], default-features = false } getrandom = { version = "0.2", features = ["js"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tonic = { version = "0.14.2", features = [ - "codegen", - "channel", - "transport", - "tls-native-roots", - "tls-webpki-roots", - "tls-ring", -], default-features = false } +tonic = { version = "0.14.2", features = ["codegen"], default-features = false } [build-dependencies] tonic-prost-build = { version = "0.14.2" } diff --git a/packages/dapi-grpc/build.rs b/packages/dapi-grpc/build.rs index 50e0d57b6d7..f055da18fc2 100644 --- a/packages/dapi-grpc/build.rs +++ b/packages/dapi-grpc/build.rs @@ -69,6 +69,7 @@ fn generate_code(typ: ImplType, output_base: &Path) { println!("cargo:rerun-if-changed=./protos"); println!("cargo:rerun-if-env-changed=CARGO_FEATURE_SERDE"); + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_TRANSPORT"); println!("cargo:rerun-if-env-changed=CARGO_CFG_TARGET_ARCH"); println!("cargo:rerun-if-env-changed=DAPI_GRPC_OUT_DIR"); } @@ -416,15 +417,23 @@ enum ImplType { impl ImplType { // Configure the builder based on the implementation type. pub fn configure(&self, builder: Builder) -> Builder { + // The `transport` cargo feature controls whether generated clients get + // the `connect()` convenience impls over tonic's own channel. Without + // it, clients are still generated but stay generic over the caller's + // transport. Never enabled for wasm32, where tonic transport does not + // build. Note: cfg!(target_arch) in a build script reflects the HOST, + // so the target must be read from CARGO_CFG_TARGET_ARCH. + let transport = std::env::var("CARGO_FEATURE_TRANSPORT").is_ok() + && std::env::var("CARGO_CFG_TARGET_ARCH").map(|arch| arch != "wasm32") == Ok(true); match self { Self::Server => builder .build_client(true) .build_server(true) - .build_transport(true), + .build_transport(transport), Self::Client => builder .build_client(true) .build_server(false) - .build_transport(true), + .build_transport(transport), Self::Wasm => builder .build_client(true) .build_server(false) diff --git a/packages/rs-dapi-client/Cargo.toml b/packages/rs-dapi-client/Cargo.toml index 705f5ccec2f..a52c12400fb 100644 --- a/packages/rs-dapi-client/Cargo.toml +++ b/packages/rs-dapi-client/Cargo.toml @@ -26,6 +26,11 @@ backon = { version = "1.3", default-features = false, features = [ "tokio-sleep", ] } tokio = { version = "1.40", features = ["time"] } +# The native transport (tonic channel + TLS) comes from dapi-grpc's transport +# feature; wasm builds use tonic-web-wasm-client instead and must not pull it. +dapi-grpc = { path = "../dapi-grpc", features = [ + "transport", +], default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] gloo-timers = { version = "0.3.0", features = ["futures"] }