Skip to content

Commit 28d088e

Browse files
shumkovclaude
andcommitted
refactor(sdk): extract retry logic from trait default methods to fix HRTB Send issue
Move the retry + closure logic from the default methods of Fetch, FetchMany, and FetchUnproved traits into standalone async fn helpers. This breaks the HRTB Send inference chain that prevents tokio::spawn from proving the resulting future is Send, without changing any call sites or removing #[async_trait]. Also remove stale #[async_trait::async_trait] from the bare FetchMany impl for ContenderWithSerializedDocument. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 95bdf2c commit 28d088e

3 files changed

Lines changed: 181 additions & 123 deletions

File tree

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

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -158,47 +158,8 @@ where
158158
query: Q,
159159
settings: Option<RequestSettings>,
160160
) -> 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())?;
173-
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");
176-
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()
161+
let request = query.query(sdk.prove())?;
162+
fetch_with_metadata_and_proof_impl(sdk, request, settings).await
202163
}
203164

204165
/// Fetch single object from Platform.
@@ -249,6 +210,67 @@ where
249210
}
250211
}
251212

213+
/// Standalone helper for the default `Fetch::fetch_with_metadata_and_proof`.
214+
///
215+
/// Extracting the retry + closure logic into a free `async fn` breaks the
216+
/// HRTB Send inference chain that prevents `tokio::spawn` from proving the
217+
/// resulting future is Send.
218+
async fn fetch_with_metadata_and_proof_impl<T, R>(
219+
sdk: &Sdk,
220+
request: R,
221+
settings: Option<RequestSettings>,
222+
) -> Result<(Option<T>, ResponseMetadata, Proof), Error>
223+
where
224+
T: Sized
225+
+ Debug
226+
+ MockResponse
227+
+ Send
228+
+ FromProof<R, Request = R, Response = <R as DapiRequest>::Response>,
229+
R: TransportRequest + Into<<T as FromProof<R>>::Request>,
230+
{
231+
let request = &request;
232+
233+
let fut = |settings: RequestSettings| async move {
234+
let ExecutionResponse {
235+
address,
236+
retries,
237+
inner: response,
238+
} = request
239+
.clone()
240+
.execute(sdk, settings)
241+
.await
242+
.map_err(|execution_error| execution_error.inner_into())?;
243+
244+
let object_type = std::any::type_name::<T>().to_string();
245+
tracing::trace!(request = ?request, response = ?response, ?address, retries, object_type, "fetched object from platform");
246+
247+
let (object, response_metadata, proof): (Option<T>, ResponseMetadata, Proof) = sdk
248+
.parse_proof_with_metadata_and_proof(request.clone(), response)
249+
.await
250+
.map_err(|e| ExecutionError {
251+
inner: e,
252+
address: Some(address.clone()),
253+
retries,
254+
})?;
255+
256+
match object {
257+
Some(item) => Ok((item.into(), response_metadata, proof)),
258+
None => Ok((None, response_metadata, proof)),
259+
}
260+
.map(|x| ExecutionResponse {
261+
inner: x,
262+
address,
263+
retries,
264+
})
265+
};
266+
267+
let settings = sdk
268+
.dapi_client_settings
269+
.override_by(settings.unwrap_or_default());
270+
271+
retry(sdk.address_list(), settings, fut).await.into_inner()
272+
}
273+
252274
impl Fetch for Identity {
253275
type Request = IdentityRequest;
254276
}

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

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -207,51 +207,8 @@ where
207207
query: Q,
208208
settings: Option<RequestSettings>,
209209
) -> Result<(O, ResponseMetadata, Proof), Error> {
210-
let request = &query.query(sdk.prove())?;
211-
212-
let fut = |settings: RequestSettings| async move {
213-
let ExecutionResponse {
214-
address,
215-
retries,
216-
inner: response,
217-
} = request
218-
.clone()
219-
.execute(sdk, settings)
220-
.await
221-
.map_err(|e| e.inner_into())?;
222-
223-
let object_type = std::any::type_name::<Self>().to_string();
224-
tracing::trace!(
225-
request = ?request,
226-
response = ?response,
227-
?address,
228-
retries,
229-
object_type,
230-
"fetched objects from platform"
231-
);
232-
233-
sdk.parse_proof_with_metadata_and_proof::<<Self as FetchMany<K, O>>::Request, O>(
234-
request.clone(),
235-
response,
236-
)
237-
.await
238-
.map_err(|e| ExecutionError {
239-
inner: e,
240-
address: Some(address.clone()),
241-
retries,
242-
})
243-
.map(|(o, metadata, proof)| ExecutionResponse {
244-
inner: (o.unwrap_or_default(), metadata, proof),
245-
retries,
246-
address: address.clone(),
247-
})
248-
};
249-
250-
let settings = sdk
251-
.dapi_client_settings
252-
.override_by(settings.unwrap_or_default());
253-
254-
retry(sdk.address_list(), settings, fut).await.into_inner()
210+
let request = query.query(sdk.prove())?;
211+
fetch_many_with_metadata_and_proof_impl(sdk, request, settings).await
255212
}
256213

257214
/// Fetch multiple objects from Platform by their identifiers.
@@ -306,6 +263,67 @@ where
306263
}
307264
}
308265

266+
/// Standalone helper for the default `FetchMany::fetch_many_with_metadata_and_proof`.
267+
///
268+
/// Extracting the retry + closure logic into a free `async fn` breaks the
269+
/// HRTB Send inference chain that prevents `tokio::spawn` from proving the
270+
/// resulting future is Send.
271+
async fn fetch_many_with_metadata_and_proof_impl<R, O>(
272+
sdk: &Sdk,
273+
request: R,
274+
settings: Option<RequestSettings>,
275+
) -> Result<(O, ResponseMetadata, Proof), Error>
276+
where
277+
R: TransportRequest + Into<<O as FromProof<R>>::Request>,
278+
O: FromProof<R, Request = R, Response = <R as TransportRequest>::Response>
279+
+ MockResponse
280+
+ Send
281+
+ Default,
282+
{
283+
let request = &request;
284+
285+
let fut = |settings: RequestSettings| async move {
286+
let ExecutionResponse {
287+
address,
288+
retries,
289+
inner: response,
290+
} = request
291+
.clone()
292+
.execute(sdk, settings)
293+
.await
294+
.map_err(|e| e.inner_into())?;
295+
296+
let object_type = std::any::type_name::<O>().to_string();
297+
tracing::trace!(
298+
request = ?request,
299+
response = ?response,
300+
?address,
301+
retries,
302+
object_type,
303+
"fetched objects from platform"
304+
);
305+
306+
sdk.parse_proof_with_metadata_and_proof::<R, O>(request.clone(), response)
307+
.await
308+
.map_err(|e| ExecutionError {
309+
inner: e,
310+
address: Some(address.clone()),
311+
retries,
312+
})
313+
.map(|(o, metadata, proof)| ExecutionResponse {
314+
inner: (o.unwrap_or_default(), metadata, proof),
315+
retries,
316+
address: address.clone(),
317+
})
318+
};
319+
320+
let settings = sdk
321+
.dapi_client_settings
322+
.override_by(settings.unwrap_or_default());
323+
324+
retry(sdk.address_list(), settings, fut).await.into_inner()
325+
}
326+
309327
/// Fetch documents from Platform.
310328
///
311329
/// Returns [Documents](dpp::document::Document) indexed by their [Identifier](dpp::prelude::Identifier).
@@ -462,7 +480,6 @@ impl FetchMany<Identifier, ContestedResources> for ContestedResource {
462480
/// ## Supported query types
463481
///
464482
/// * [`ContestedDocumentVotePollDriveQuery`](drive::query::vote_poll_vote_state_query::ContestedDocumentVotePollDriveQuery)
465-
#[async_trait::async_trait]
466483
impl FetchMany<Identifier, Contenders> for ContenderWithSerializedDocument {
467484
type Request = GetContestedResourceVoteStateRequest;
468485
}

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

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -70,46 +70,65 @@ where
7070
Response = <<Self as FetchUnproved>::Request as TransportRequest>::Response,
7171
>,
7272
{
73-
// Default implementation
74-
let request: &<Self as FetchUnproved>::Request = &query.query(false)?;
75-
let closure = move |local_settings: RequestSettings| async move {
76-
// Execute the request using the Sdk instance
77-
let ExecutionResponse {
78-
inner: response,
79-
address,
80-
retries,
81-
} = request
82-
.clone()
83-
.execute(sdk, local_settings)
84-
.await
85-
.map_err(|e| e.inner_into())?;
73+
let request = query.query(false)?;
74+
fetch_unproved_with_settings_impl(sdk, request, settings).await
75+
}
76+
}
77+
78+
/// Standalone helper for the default `FetchUnproved::fetch_unproved_with_settings`.
79+
///
80+
/// Extracting the retry + closure logic into a free `async fn` breaks the
81+
/// HRTB Send inference chain that prevents `tokio::spawn` from proving the
82+
/// resulting future is Send.
83+
async fn fetch_unproved_with_settings_impl<T, R>(
84+
sdk: &Sdk,
85+
request: R,
86+
settings: RequestSettings,
87+
) -> Result<(Option<T>, ResponseMetadata), Error>
88+
where
89+
T: Sized
90+
+ Debug
91+
+ Send
92+
+ FromUnproved<R, Request = R, Response = <R as TransportRequest>::Response>,
93+
R: TransportRequest,
94+
{
95+
let request = &request;
8696

87-
// Parse the response into the appropriate type along with metadata
88-
let (object, metadata): (Option<Self>, platform_proto::ResponseMetadata) =
89-
Self::maybe_from_unproved_with_metadata(
90-
request.clone(),
91-
response,
92-
sdk.network,
93-
sdk.version(),
94-
)
95-
.map_err(|e| ExecutionError {
96-
inner: e.into(),
97-
address: Some(address.clone()),
98-
retries,
99-
})?;
97+
let closure = move |local_settings: RequestSettings| async move {
98+
let ExecutionResponse {
99+
inner: response,
100+
address,
101+
retries,
102+
} = request
103+
.clone()
104+
.execute(sdk, local_settings)
105+
.await
106+
.map_err(|e| e.inner_into())?;
100107

101-
Ok(ExecutionResponse {
102-
inner: (object, metadata),
103-
address,
108+
let (object, metadata): (Option<T>, platform_proto::ResponseMetadata) =
109+
T::maybe_from_unproved_with_metadata(
110+
request.clone(),
111+
response,
112+
sdk.network,
113+
sdk.version(),
114+
)
115+
.map_err(|e| ExecutionError {
116+
inner: e.into(),
117+
address: Some(address.clone()),
104118
retries,
105-
})
106-
};
119+
})?;
107120

108-
let settings = sdk.dapi_client_settings.override_by(settings);
109-
retry(sdk.address_list(), settings, closure)
110-
.await
111-
.into_inner()
112-
}
121+
Ok(ExecutionResponse {
122+
inner: (object, metadata),
123+
address,
124+
retries,
125+
})
126+
};
127+
128+
let settings = sdk.dapi_client_settings.override_by(settings);
129+
retry(sdk.address_list(), settings, closure)
130+
.await
131+
.into_inner()
113132
}
114133

115134
impl FetchUnproved for drive_proof_verifier::types::CurrentQuorumsInfo {

0 commit comments

Comments
 (0)