Skip to content

Commit 0373efc

Browse files
shumkovclaude
andcommitted
refactor(sdk): replace #[async_trait] with explicit BoxFuture on SDK traits
Replace #[async_trait] with explicit Pin<Box<dyn Future + Send + 'a>> returns on Fetch, FetchMany, FetchUnproved, BroadcastStateTransition, and all 21 transition traits in the SDK. ## Problem The Rust compiler has a known HRTB Send inference bug (rust-lang/rust#96865) that prevents it from proving async futures are Send when the number of generic trait implementations exceeds a threshold. With 28 Fetch impl types (after adding shielded pool support), any consumer using tokio::spawn with SDK operations gets: error: implementation of Send is not general enough This forced consumers (e.g. dash-evo-tool) to use spawn_blocking + block_on as a workaround, wasting threads and blocking the async executor. ## Solution By returning BoxFuture explicitly instead of relying on #[async_trait], the compiler sees Box<dyn Future + Send> and stops analyzing the inner state machine. Send is proven by construction via the boxed trait object. This is the same boxing that #[async_trait] performed — we just make it explicit so the compiler's inference engine is not involved. ## Impact - ALL SDK consumers can use tokio::spawn directly - No spawn_blocking workaround needed - No unsafe AssertSend wrapper needed - No performance regression (same boxing as #[async_trait]) - WASM builds unaffected (verified) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 94cefb3 commit 0373efc

25 files changed

Lines changed: 1660 additions & 1527 deletions

packages/rs-sdk/src/platform/fetch.rs

Lines changed: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use drive_proof_verifier::FromProof;
2323
use rs_dapi_client::{transport::TransportRequest, DapiRequest, RequestSettings};
2424
use rs_dapi_client::{ExecutionError, ExecutionResponse, InnerInto, IntoInner};
2525
use std::fmt::Debug;
26+
use std::future::Future;
27+
use std::pin::Pin;
2628

2729
use super::types::identity::IdentityRequest;
2830
use super::DocumentQuery;
@@ -52,10 +54,10 @@ use super::DocumentQuery;
5254
///
5355
/// let identity = Identity::fetch(&sdk, query);
5456
/// ```
55-
#[async_trait::async_trait]
5657
pub trait Fetch
5758
where
5859
Self: Sized
60+
+ Send
5961
+ Debug
6062
+ MockResponse
6163
+ FromProof<
@@ -91,11 +93,13 @@ where
9193
/// ## Error Handling
9294
///
9395
/// Any errors encountered during the execution are returned as [Error] instances.
94-
async fn fetch<Q: Query<<Self as Fetch>::Request>>(
95-
sdk: &Sdk,
96+
fn fetch<'a, Q: Query<<Self as Fetch>::Request> + Send + 'a>(
97+
sdk: &'a Sdk,
9698
query: Q,
97-
) -> Result<Option<Self>, Error> {
98-
Self::fetch_with_settings(sdk, query, RequestSettings::default()).await
99+
) -> Pin<Box<dyn Future<Output = Result<Option<Self>, Error>> + Send + 'a>> {
100+
Box::pin(async move {
101+
Self::fetch_with_settings(sdk, query, RequestSettings::default()).await
102+
})
99103
}
100104

101105
/// Fetch single object from Platform with metadata.
@@ -119,14 +123,17 @@ where
119123
/// ## Error Handling
120124
///
121125
/// Any errors encountered during the execution are returned as [Error] instances.
122-
async fn fetch_with_metadata<Q: Query<<Self as Fetch>::Request>>(
123-
sdk: &Sdk,
126+
fn fetch_with_metadata<'a, Q: Query<<Self as Fetch>::Request> + Send + 'a>(
127+
sdk: &'a Sdk,
124128
query: Q,
125129
settings: Option<RequestSettings>,
126-
) -> Result<(Option<Self>, ResponseMetadata), Error> {
127-
Self::fetch_with_metadata_and_proof(sdk, query, settings)
128-
.await
129-
.map(|(object, metadata, _)| (object, metadata))
130+
) -> Pin<Box<dyn Future<Output = Result<(Option<Self>, ResponseMetadata), Error>> + Send + 'a>>
131+
{
132+
Box::pin(async move {
133+
Self::fetch_with_metadata_and_proof(sdk, query, settings)
134+
.await
135+
.map(|(object, metadata, _)| (object, metadata))
136+
})
130137
}
131138

132139
/// Fetch single object from Platform with metadata and underlying proof.
@@ -153,52 +160,56 @@ where
153160
/// ## Error Handling
154161
///
155162
/// Any errors encountered during the execution are returned as [Error] instances.
156-
async fn fetch_with_metadata_and_proof<Q: Query<<Self as Fetch>::Request>>(
157-
sdk: &Sdk,
163+
fn fetch_with_metadata_and_proof<'a, Q: Query<<Self as Fetch>::Request> + Send + 'a>(
164+
sdk: &'a Sdk,
158165
query: Q,
159166
settings: Option<RequestSettings>,
160-
) -> Result<(Option<Self>, ResponseMetadata, Proof), Error> {
161-
let request: &<Self as Fetch>::Request = &query.query(sdk.prove())?;
162-
163-
let fut = |settings: RequestSettings| async move {
164-
let ExecutionResponse {
165-
address,
166-
retries,
167-
inner: response,
168-
} = request
169-
.clone()
170-
.execute(sdk, settings)
171-
.await
172-
.map_err(|execution_error| execution_error.inner_into())?;
167+
) -> Pin<
168+
Box<dyn Future<Output = Result<(Option<Self>, ResponseMetadata, Proof), Error>> + Send + 'a>,
169+
> {
170+
Box::pin(async move {
171+
let request: &<Self as Fetch>::Request = &query.query(sdk.prove())?;
172+
173+
let fut = |settings: RequestSettings| async move {
174+
let ExecutionResponse {
175+
address,
176+
retries,
177+
inner: response,
178+
} = request
179+
.clone()
180+
.execute(sdk, settings)
181+
.await
182+
.map_err(|execution_error| execution_error.inner_into())?;
183+
184+
let object_type = std::any::type_name::<Self>().to_string();
185+
tracing::trace!(request = ?request, response = ?response, ?address, retries, object_type, "fetched object from platform");
186+
187+
let (object, response_metadata, proof): (Option<Self>, ResponseMetadata, Proof) =
188+
sdk.parse_proof_with_metadata_and_proof(request.clone(), response)
189+
.await
190+
.map_err(|e| ExecutionError {
191+
inner: e,
192+
address: Some(address.clone()),
193+
retries,
194+
})?;
195+
196+
match object {
197+
Some(item) => Ok((item.into(), response_metadata, proof)),
198+
None => Ok((None, response_metadata, proof)),
199+
}
200+
.map(|x| ExecutionResponse {
201+
inner: x,
202+
address,
203+
retries,
204+
})
205+
};
173206

174-
let object_type = std::any::type_name::<Self>().to_string();
175-
tracing::trace!(request = ?request, response = ?response, ?address, retries, object_type, "fetched object from platform");
207+
let settings = sdk
208+
.dapi_client_settings
209+
.override_by(settings.unwrap_or_default());
176210

177-
let (object, response_metadata, proof): (Option<Self>, ResponseMetadata, Proof) = sdk
178-
.parse_proof_with_metadata_and_proof(request.clone(), response)
179-
.await
180-
.map_err(|e| ExecutionError {
181-
inner: e,
182-
address: Some(address.clone()),
183-
retries,
184-
})?;
185-
186-
match object {
187-
Some(item) => Ok((item.into(), response_metadata, proof)),
188-
None => Ok((None, response_metadata, proof)),
189-
}
190-
.map(|x| ExecutionResponse {
191-
inner: x,
192-
address,
193-
retries,
194-
})
195-
};
196-
197-
let settings = sdk
198-
.dapi_client_settings
199-
.override_by(settings.unwrap_or_default());
200-
201-
retry(sdk.address_list(), settings, fut).await.into_inner()
211+
retry(sdk.address_list(), settings, fut).await.into_inner()
212+
})
202213
}
203214

204215
/// Fetch single object from Platform.
@@ -222,13 +233,15 @@ where
222233
/// ## Error Handling
223234
///
224235
/// Any errors encountered during the execution are returned as [Error] instances.
225-
async fn fetch_with_settings<Q: Query<<Self as Fetch>::Request>>(
226-
sdk: &Sdk,
236+
fn fetch_with_settings<'a, Q: Query<<Self as Fetch>::Request> + Send + 'a>(
237+
sdk: &'a Sdk,
227238
query: Q,
228239
settings: RequestSettings,
229-
) -> Result<Option<Self>, Error> {
230-
let (object, _) = Self::fetch_with_metadata(sdk, query, Some(settings)).await?;
231-
Ok(object)
240+
) -> Pin<Box<dyn Future<Output = Result<Option<Self>, Error>> + Send + 'a>> {
241+
Box::pin(async move {
242+
let (object, _) = Self::fetch_with_metadata(sdk, query, Some(settings)).await?;
243+
Ok(object)
244+
})
232245
}
233246

234247
/// Fetch single object from Platform by identifier.
@@ -241,11 +254,14 @@ where
241254
///
242255
/// - `sdk`: An instance of [Sdk].
243256
/// - `id`: An [Identifier] of the object to be fetched.
244-
async fn fetch_by_identifier(sdk: &Sdk, id: Identifier) -> Result<Option<Self>, Error>
257+
fn fetch_by_identifier<'a>(
258+
sdk: &'a Sdk,
259+
id: Identifier,
260+
) -> Pin<Box<dyn Future<Output = Result<Option<Self>, Error>> + Send + 'a>>
245261
where
246262
Identifier: Query<<Self as Fetch>::Request>,
247263
{
248-
Self::fetch(sdk, id).await
264+
Box::pin(async move { Self::fetch(sdk, id).await })
249265
}
250266
}
251267

0 commit comments

Comments
 (0)