Context
PR #3432 / #3497 introduced the workspace-canonical async-sync bridge dash_async::block_on (crate packages/rs-dash-async), which is runtime-aware and safely handles the no-runtime, current-thread, and multi-thread tokio flavors — avoiding the deadlocks that motivated that fix.
PR #3492 (async Signer trait) migrated exactly one call site — packages/rs-sdk-ffi/src/signer_simple.rs::dash_sdk_signer_sign — to use dash_async::block_on. However, the workspace still contains roughly 30 pre-existing sync-to-async bridging sites that spin up an ad-hoc tokio runtime inline (tokio::runtime::Builder::new_current_thread() or Runtime::new() followed by .block_on(...), or wrapper.runtime.block_on(async { ... })).
Affected sites
A non-exhaustive grep turned up these modules using the legacy pattern:
packages/rs-dash-platform-macros/src/lib.rs (CMT-002, marked with TODO(CMT-002, issue #3535)): the #[stack_size] proc-macro expands async functions into an inline Builder::new_current_thread().build().block_on(async move #block) on a spawned thread. Used by many test bodies; same deadlock class PR #3490 / #3497 addressed.
packages/rs-sdk-ffi FFI entry points:
packages/rs-sdk-ffi/src/sdk.rs
packages/rs-sdk-ffi/src/dashpay/contact_request.rs
packages/rs-sdk-ffi/src/address_sync/mod.rs
packages/rs-sdk-ffi/src/shielded/transitions/*.rs (shield, unshield, shielded_transfer, shielded_withdrawal, shield_from_asset_lock, broadcast, builders)
packages/rs-sdk-ffi/src/shielded/queries/*.rs (anchors, nullifiers, pool_state, most_recent_anchor, encrypted_notes)
packages/rs-sdk-ffi/src/shielded/pool_client/sync.rs
packages/rs-sdk-ffi/src/data_contract/queries/fetch_json.rs
Motivation
Consistency. Any FFI entry point or proc-macro-expanded test that is re-entered while another tokio runtime is already active on the calling thread currently risks a block_in_place-style panic or deadlock (the exact class of bug that dash-async was built to fix). Migrating all sites puts every bridging call through the same vetted code path.
Scope
- Replace
tokio::runtime::Builder::new_current_thread()...build().block_on(...) and Runtime::new().block_on(...) patterns with dash_async::block_on(...).
- For sites using
wrapper.runtime.block_on(...) (where wrapper.runtime is a long-lived runtime handle), evaluate per call whether the long-lived runtime is still needed or whether dash_async::block_on suffices. If the runtime is only used for this single block_on, prefer dash_async.
- Adjust error handling to distinguish bridging failures (
DashSDKErrorCode::InternalError) from domain failures.
- Where the future borrows locals, own them up front (
Vec::from / clone) to satisfy Send + 'static.
- For
rs-dash-platform-macros: add a dash-async dep and switch the generated block_on(async move #block) to dash_async::block_on(async move #block).expect(...).
Non-goals
- No behavioral change for callers.
- No changes to public FFI signatures.
Out of scope for this issue
wasm-sdk code — dash_async::block_on is a WASM stub that returns an error.
🤖 Co-authored by Claudius the Magnificent AI Agent
Context
PR #3432 / #3497 introduced the workspace-canonical async-sync bridge
dash_async::block_on(cratepackages/rs-dash-async), which is runtime-aware and safely handles the no-runtime, current-thread, and multi-thread tokio flavors — avoiding the deadlocks that motivated that fix.PR #3492 (async Signer trait) migrated exactly one call site —
packages/rs-sdk-ffi/src/signer_simple.rs::dash_sdk_signer_sign— to usedash_async::block_on. However, the workspace still contains roughly 30 pre-existing sync-to-async bridging sites that spin up an ad-hoc tokio runtime inline (tokio::runtime::Builder::new_current_thread()orRuntime::new()followed by.block_on(...), orwrapper.runtime.block_on(async { ... })).Affected sites
A non-exhaustive grep turned up these modules using the legacy pattern:
packages/rs-dash-platform-macros/src/lib.rs(CMT-002, marked withTODO(CMT-002, issue #3535)): the#[stack_size]proc-macro expands async functions into an inlineBuilder::new_current_thread().build().block_on(async move #block)on a spawned thread. Used by many test bodies; same deadlock class PR #3490 / #3497 addressed.packages/rs-sdk-ffiFFI entry points:packages/rs-sdk-ffi/src/sdk.rspackages/rs-sdk-ffi/src/dashpay/contact_request.rspackages/rs-sdk-ffi/src/address_sync/mod.rspackages/rs-sdk-ffi/src/shielded/transitions/*.rs(shield, unshield, shielded_transfer, shielded_withdrawal, shield_from_asset_lock, broadcast, builders)packages/rs-sdk-ffi/src/shielded/queries/*.rs(anchors, nullifiers, pool_state, most_recent_anchor, encrypted_notes)packages/rs-sdk-ffi/src/shielded/pool_client/sync.rspackages/rs-sdk-ffi/src/data_contract/queries/fetch_json.rsMotivation
Consistency. Any FFI entry point or proc-macro-expanded test that is re-entered while another tokio runtime is already active on the calling thread currently risks a
block_in_place-style panic or deadlock (the exact class of bug thatdash-asyncwas built to fix). Migrating all sites puts every bridging call through the same vetted code path.Scope
tokio::runtime::Builder::new_current_thread()...build().block_on(...)andRuntime::new().block_on(...)patterns withdash_async::block_on(...).wrapper.runtime.block_on(...)(wherewrapper.runtimeis a long-lived runtime handle), evaluate per call whether the long-lived runtime is still needed or whetherdash_async::block_onsuffices. If the runtime is only used for this single block_on, preferdash_async.DashSDKErrorCode::InternalError) from domain failures.Vec::from/ clone) to satisfySend + 'static.rs-dash-platform-macros: add adash-asyncdep and switch the generatedblock_on(async move #block)todash_async::block_on(async move #block).expect(...).Non-goals
Out of scope for this issue
wasm-sdkcode —dash_async::block_onis a WASM stub that returns an error.🤖 Co-authored by Claudius the Magnificent AI Agent