refactor(sdk): extract fetch helpers to fix HRTB Send inference#3376
refactor(sdk): extract fetch helpers to fix HRTB Send inference#3376
Conversation
📝 WalkthroughWalkthroughThree files in the platform module extract private async helper functions to encapsulate retry logic and request execution. These refactoring changes improve code organization and address Send/lifetime constraints without modifying public API signatures. Changes
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/rs-sdk/src/platform/fetch_unproved.rs (1)
145-166:⚠️ Potential issue | 🟡 MinorRemove unnecessary
#[async_trait]annotation.The
fn maybe_from_unproved_with_metadatamethod is synchronous and returnsResult<...>directly, not aFuture. The#[async_trait]attribute is used to convert async methods into boxed futures and is unnecessary here. This is inconsistent with theCurrentQuorumsInfoimplementation of the same trait, which correctly omits the annotation. Remove the annotation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rs-sdk/src/platform/fetch_unproved.rs` around lines 145 - 166, The impl block for FromUnproved<EvoNode> incorrectly includes the #[async_trait::async_trait] attribute even though maybe_from_unproved_with_metadata is synchronous; remove the #[async_trait::async_trait] annotation from the impl so the impl block for FromUnproved<EvoNode> (and its method maybe_from_unproved_with_metadata) matches the synchronous pattern used by CurrentQuorumsInfo and does not attempt to treat the method as async. Ensure the impl compiles without the attribute.
🧹 Nitpick comments (1)
packages/rs-sdk/src/platform/fetch.rs (1)
257-260: Minor: Redundant match arm conversion.The match block converts
Some(item)toSome(item.into()), but sinceitemis already of typeS(the same type expected in the output), the.into()call appears to be a no-op identity conversion. This is harmless but slightly redundant.♻️ Optional simplification
match object { - Some(item) => Ok((item.into(), response_metadata, proof)), + Some(item) => Ok((Some(item), response_metadata, proof)), None => Ok((None, response_metadata, proof)), }Or more concisely:
- match object { - Some(item) => Ok((item.into(), response_metadata, proof)), - None => Ok((None, response_metadata, proof)), - } + Ok((object, response_metadata, proof))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/rs-sdk/src/platform/fetch.rs` around lines 257 - 260, The match on `object` is doing an unnecessary identity conversion for `Some(item)` by calling `item.into()`; replace the match with a direct mapped value (e.g., transform `object` to the desired output once using `.map(Into::into)` or equivalent) and return Ok((mapped_object, response_metadata, proof)) instead—update the code around the `object`, `response_metadata`, and `proof` return to use the simplified expression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/rs-sdk/src/platform/fetch_unproved.rs`:
- Around line 145-166: The impl block for FromUnproved<EvoNode> incorrectly
includes the #[async_trait::async_trait] attribute even though
maybe_from_unproved_with_metadata is synchronous; remove the
#[async_trait::async_trait] annotation from the impl so the impl block for
FromUnproved<EvoNode> (and its method maybe_from_unproved_with_metadata) matches
the synchronous pattern used by CurrentQuorumsInfo and does not attempt to treat
the method as async. Ensure the impl compiles without the attribute.
---
Nitpick comments:
In `@packages/rs-sdk/src/platform/fetch.rs`:
- Around line 257-260: The match on `object` is doing an unnecessary identity
conversion for `Some(item)` by calling `item.into()`; replace the match with a
direct mapped value (e.g., transform `object` to the desired output once using
`.map(Into::into)` or equivalent) and return Ok((mapped_object,
response_metadata, proof)) instead—update the code around the `object`,
`response_metadata`, and `proof` return to use the simplified expression.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ceb6761b-a8b8-4357-989f-06bf1aab57c3
📒 Files selected for processing (3)
packages/rs-sdk/src/platform/fetch.rspackages/rs-sdk/src/platform/fetch_many.rspackages/rs-sdk/src/platform/fetch_unproved.rs
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Clean, well-executed RPITIT migration that correctly replaces #[async_trait] with native return-position impl Trait on Fetch, FetchMany, and FetchUnproved. The transformation is semantically equivalent — the explicit Self: Send bound was already implicitly enforced by #[async_trait] (without ?Send). The UFCS changes in wasm-sdk are mechanically necessary due to a known Rust inference limitation with RPITIT. No blocking issues found.
Reviewed commit: 6555aea
🟡 2 suggestion(s) | 💬 3 nitpick(s)
🤖 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-sdk/src/platform/fetch.rs`:
- [SUGGESTION] lines 95-167: Add a compile-time regression test proving fetch futures are Send
This PR's stated goal is to fix Send inference (issue #3361) so fetch futures can be spawned. While the `+ Send` bound on the return type is a compile-time assertion, a dedicated regression test would guard against future regressions more explicitly. A simple `fn assert_send<T: Send>(_: &T) {}` or `tokio::spawn(Identity::fetch(&sdk, id))` in a test module would catch any re-introduction of non-Send captures.
In `packages/wasm-sdk/src/queries/identity.rs`:
- [SUGGESTION] lines 673-678: UFCS required even for single-impl types due to Query<T> ambiguity, not trait ambiguity
Types likeIdentityBalance have only one FetchMany impl, yet UFCS is required. The root cause is that `Vec<Identifier>` implements `Query<T>` for multiple `T` values, and RPITIT's opaque return type blocks the inference path that async_trait's boxed future allowed. This is a known Rust limitation (rust-lang/rust#113495). Not actionable now, but worth noting in a code comment for future maintainers who may wonder why UFCS is needed for seemingly unambiguous cases.
Addressed review commentsCompile-time regression test for Send — Good suggestion. The UFCS line length (token.rs lines 689/736) — These are auto-formatted by Redundant match/into in fetch_impl — Good catch, this is pre-existing. The UFCS required for single-impl types — Correct analysis. This is a known Rust limitation (rust-lang/rust#113495) where RPITIT's opaque return type blocks Query inference. Added a comment in the UFCS call sites explaining why. Remove stale #[async_trait] from fetch_unproved.rs — Done in latest push. The rs-sdk-ffi type inference — Fixed in latest push. Added UFCS turbofish to all 8 affected files in rs-sdk-ffi. Second address.clone() — Pre-existing minor optimization. Will address in a follow-up cleanup. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v3.1-dev #3376 +/- ##
=========================================
Coverage 79.91% 79.92%
=========================================
Files 2861 2861
Lines 279648 279700 +52
=========================================
+ Hits 223493 223541 +48
- Misses 56155 56159 +4
🚀 New features to boost your workflow:
|
|
✅ DashSDKFFI.xcframework built for this PR.
SwiftPM (host the zip at a stable URL, then use): .binaryTarget(
name: "DashSDKFFI",
url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
checksum: "1b14c8ca460ebe66e255e62ac5f36ad9456650be8af8570a3e880e3e0d97e5b4"
)Xcode manual integration:
|
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Clean incremental push completing the RPITIT migration. The three follow-up commits add necessary UFCS disambiguation across rs-sdk-ffi (7 files), wasm-sdk (3 files), and SDK tests (3 files), remove a stale #[async_trait] attribute, and fix an unused variable warning. All six review agents (general, rust-quality, ffi-engineer × Claude/Codex) confirmed correctness with no blocking issues. The UFCS type parameters correctly match the intended FetchMany<K, O> implementations.
Reviewed commit: 63c716a
…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>
63c716a to
28d088e
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/rs-sdk/src/platform/fetch_many.rs`:
- Around line 210-211: The FetchMany trait still uses
#[async_trait::async_trait] and async fn methods; remove the attribute and
rewrite each async method signature (e.g., fetch_many_with_metadata_and_proof,
along with other async methods declared on FetchMany) to return a native future
type like -> impl std::future::Future<Output = Result<...>> + Send, update all
implementors to return the appropriate async block or future (instead of relying
on async_trait), and adjust helper functions such as
fetch_many_with_metadata_and_proof_impl and the call site that delegates via let
request = query.query(sdk.prove())?;
fetch_many_with_metadata_and_proof_impl(sdk, request, settings).await so they
match the new return types (i.e., return the future rather than an async fn),
ensuring all trait method signatures and implementations use impl Future + Send
consistently.
In `@packages/rs-sdk/src/platform/fetch.rs`:
- Around line 161-162: Remove the #[async_trait] usage on the Fetch trait and
convert it to RPITIT by adding an associated generic Future type and changing
the async method into a generic-returning fn: add type Future<'a>: Future<Output
= Result<...>> + Send + 'a; change async fn fetch(...) -> Result<...> to fn
fetch<'a>(&'a self, sdk: &'a Sdk, query: Query, settings: Settings) ->
Self::Future<'a>; then update all impls of Fetch (the concrete impl struct(s)
that define fetch) to define the associated type and return their async work as
a boxed/pinned future (e.g., Box::pin(async move { ... })) or another concrete
Future type; also update callers like fetch_with_metadata_and_proof_impl and
places that call fetch (the code building request via query.query(sdk.prove())
and awaiting fetch) to accept the new Future-returning signature (no
#[async_trait] wrapping).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: af2a6e18-cd79-4e2a-801b-5c3b8c43fc82
📒 Files selected for processing (3)
packages/rs-sdk/src/platform/fetch.rspackages/rs-sdk/src/platform/fetch_many.rspackages/rs-sdk/src/platform/fetch_unproved.rs
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Clean mechanical refactoring that extracts retry+closure logic from three #[async_trait] trait default methods into standalone async fn helpers, fixing HRTB Send inference failures with tokio::spawn. The code is unchanged — just moved from inline to free functions. All trait bounds correctly mirror their originals. The stale #[async_trait] removal on ContenderWithSerializedDocument is correct. No new findings on this push.
Reviewed commit: 28d088e
🟡 1 suggestion(s)
🤖 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-sdk/src/platform/fetch.rs`:
- [SUGGESTION] lines 218-272: No compile-time regression test for Send on fetched futures
The entire purpose of this refactoring is to ensure futures from `Fetch::fetch`, `FetchMany::fetch_many`, and `FetchUnproved::fetch_unproved` are `Send` for `tokio::spawn`. However, there's no compile-time assertion to catch a regression if someone later moves the closure back inline or adds a non-Send capture.
A zero-cost compile-time test would make the invariant explicit:
```rust
#[allow(dead_code)]
fn assert_send<T: Send>(_: T) {}
#[allow(dead_code)]
fn fetch_future_is_send(sdk: &Sdk) {
assert_send(Identity::fetch(sdk, Identifier::new([0u8; 32])));
}
</details>
Downstream consumer report: Send bounds still broken for
|
thepastaclaw
left a comment
There was a problem hiding this comment.
Incremental Review (push 3286fcfc — merge of v3.1-dev, no PR-file changes)
Pure merge of v3.1-dev into the PR branch. The three modified SDK files (fetch.rs, fetch_many.rs, fetch_unproved.rs) are unchanged from the prior approved review at 28d088e0.
Prior APPROVE stands — the helper extraction approach correctly resolves the HRTB Send inference issue.
lklimek
left a comment
There was a problem hiding this comment.
Dash Evo tool doesn't build with this pr, see dashpay/dash-evo-tool#777
* feat: integrate platform mempool support Bump dash-sdk to platform commit 6d9efa97b which includes rust-dashcore mempool support (MempoolManager with bloom filter monitoring) and SDK RPITIT refactor. Key changes: - Configure SPV client with BloomFilter mempool strategy - Update process_mempool_transaction() call signature - Adapt to Network::Dash -> Network::Mainnet rename - Add DB migration 29 to update stored "dash" network strings - Add Send-asserting wrappers for backend task futures to work around RPITIT HRTB Send inference limitations in the SDK Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix formatting in settings.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: remove unsafe assert_send workaround Rebase platform dependency onto aa86b74f (before PR #3376 which paradoxically broke Send inference) so the compiler can natively prove the backend task future is Send. Changes: - Update dash-sdk rev to 2414799b7 (mempool bump on clean base) - Remove assert_send() transmute and all _send() wrapper methods - Revert app.rs to call run_backend_task() directly This commit is self-contained — revert it to restore the workaround if a future platform update reintroduces the HRTB regression. See: dashpay/platform#3376 See: rust-lang/rust#100013 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add trace-level timing instrumentation to E2E harness Add structured tracing to wait helpers and create_funded_test_wallet() for diagnosing IS lock timing issues. All new logs are trace-level (enable with RUST_LOG=backend_e2e=trace) except the final setup summary which logs at info. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump platform rev for bloom filter rebuild API Update dash-sdk to platform commit d9de0fd2 which includes rust-dashcore c451a1c6 adding notify_wallet_addresses_changed() for triggering mempool bloom filter rebuilds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: trigger bloom filter rebuild on wallet/address changes Call notify_wallet_addresses_changed() after loading a new wallet into SPV and after registering DashPay contact addresses. This ensures the mempool bloom filter includes newly added addresses so incoming transactions are detected immediately rather than waiting for the next block or SPV reconnect. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): let RUST_LOG override default trace level in E2E harness The hardcoded .add_directive("backend_e2e=info") was overriding RUST_LOG=backend_e2e=trace. Change to use RUST_LOG as primary source with info as fallback when the env var is not set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * revert: use original rust-dashcore 1af96dd without filter rebuild additions Reverts bloom filter rebuild API additions (notify_wallet_addresses_changed) that were added in commit eb948ae. The upstream MempoolManager has bugs (masternode sync cascade blocking activation) that make these calls ineffective. Failing tests proving these bugs are tracked in dashpay/rust-dashcore#567. Platform rev pinned to 2af8cac6 which uses rust-dashcore 1af96dd (PR #558). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update Cargo.lock for platform rev 2af8cac6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump platform rev to be2082b3c (rust-dashcore b03252af) Updates platform to be2082b3c which pins rust-dashcore b03252af, bringing bloom filter staleness detection via monitor_revision polling. Adapts imports for key-wallet-manager → key-wallet crate merge (upstream rust-dashcore #503). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): wait for SPV sync completion before broadcasting in E2E harness The E2E harness was broadcasting funding transactions before the MempoolManager's bloom filter was loaded to peers. Peers never relayed IS locks for those txs because the filter didn't exist yet. - Add wait_for_spv_running() that polls ConnectionStatus::spv_status() until SpvStatus::Running (fires after SyncComplete + bloom filter built) - Call it in BackendTestContext::init() after spendable balance is confirmed - Add 200ms sleep in create_funded_test_wallet() after wallet appears in SPV to let the mempool manager tick rebuild the bloom filter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): use SpvManager status directly instead of ConnectionStatus ConnectionStatus.spv_status is only updated from the UI frame loop, which doesn't run in the E2E test harness. Read from SpvManager's internal RwLock instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump platform rev to 93556aa4b (rust-dashcore 450f72f) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: push SPV status to ConnectionStatus from event handlers Eliminate polling of SpvManager.status() from the UI frame loop. SpvManager event handlers now push status, peer count, and error updates directly to ConnectionStatus via new setter methods, making SPV status visible to headless/test code without a UI polling cycle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: document ConnectionStatus as single source of truth for connection health Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): replace wallet_addresses with identity_token_balances in migration 29 wallet_addresses table has no `network` column, causing migration 29 (rename "dash" → "mainnet") to fail with "no such column: network". Replace with identity_token_balances which does have the column. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): include table name in migration 29 error messages If a table update fails during the dash→mainnet rename, the error now identifies which table caused the failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(spv): push Starting/Stopping status to ConnectionStatus immediately write_status() updated the internal RwLock but did not push to ConnectionStatus. The UI reads ConnectionStatus.spv_status() to show the SPV Sync Status section, so it stayed hidden until spawn_progress_watcher fired its first async tick. Now start() and stop() push status to ConnectionStatus immediately, matching the pattern used in all other status transitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(spv): centralize ConnectionStatus push in write_status() Move the ConnectionStatus push into write_status() so every status transition automatically propagates to the UI. Remove redundant manual pushes from start(), stop(), run_spv_loop(), and run_client(). Async event handlers (spawn_progress_watcher, spawn_sync_event_handler) still push directly because they write to the RwLock without going through write_status() (they hold Arc<RwLock>, not &self). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address remaining PR review comments - fix(ui): testnet MNListDiff fallback now uses Network::Testnet (was incorrectly Network::Mainnet in file-not-found path) - fix(spv): progress watcher no longer clears last_error when a second error arrives with error_msg=None (preserves first error) - fix(spv): clear spv_no_peers_since when SPV transitions to non-active state, preventing stale "no peers" tooltip warnings - docs: clarify ConnectionStatus scope — health is push-based, detailed sync progress may still be read from SpvManager Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add key_wallet and mempool_filter to default tracing filter These crates were renamed/added upstream in rust-dashcore 450f72f and were missing from the default EnvFilter, hiding debug logs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): show actionable SPV error details in banner and Settings - Error banner now says "Go to Settings" and attaches the actual error message via with_details() for the collapsible details panel - Settings SPV status label shows the error message instead of just "Error" - Add public spv_last_error() getter on ConnectionStatus (DRY) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): deduplicate wallet transactions by txid before insert With mempool support enabled, upstream wallet_transaction_history() can return the same txid twice — as both a mempool entry (no height) and a confirmed entry (with height). This caused a UNIQUE constraint violation on (seed_hash, txid, network). Deduplicate in replace_wallet_transactions(), preferring the confirmed version over unconfirmed when both exist for the same txid. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(wallet): add TransactionStatus enum for transaction lifecycle tracking Add TransactionStatus (Unconfirmed/InstantSendLocked/Confirmed/ChainLocked) to WalletTransaction model and database schema. - New TransactionStatus enum with from_height() heuristic for inferring status from block height (confirmed vs unconfirmed) - Migration 30: adds `status` column to wallet_transactions table (default 2=Confirmed for existing rows) - INSERT OR REPLACE handles duplicate txids from mempool + block - UI shows status labels: "Pending", "⚡ InstantSend", "Confirmed @n", "🔒 ChainLocked @n" InstantSendLocked and ChainLocked require upstream support (rust-dashcore#569) — currently only Unconfirmed/Confirmed are inferred. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(db): consolidate migrations 29+30 into single migration 29 Both migrations were introduced in this PR — no need for users to run them separately. Migration 29 now does both: rename network "dash" to "mainnet" AND add the transaction status column. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ui): throttle system theme detection to prevent white flash during sync When using the "System" color theme, dark_light::detect() was called every frame. During SPV sync (high repaint frequency), transient wrong values caused brief theme flips visible as white flashes. Changes: - Cache the resolved theme in AppState and only re-poll the OS every 2s - Guard font initialization with AtomicBool so set_fonts runs once - Immediately resolve and apply theme on preference change Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Revert "fix(ui): throttle system theme detection to prevent white flash during sync" This reverts commit 7f822bb. * fix(db): default transaction status to Unconfirmed for new inserts The table-creation DEFAULT was Confirmed (2), which is wrong — transactions should be unconfirmed until proven otherwise. The migration ALTER TABLE correctly keeps DEFAULT 2 for pre-existing rows (all from confirmed RPC). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments (migration idempotency, status field, poison handling) - Guard ALTER TABLE in migration 30 with pragma_table_info check to prevent duplicate column crash on DBs upgrading from version <14 - Make is_confirmed() authoritative via TransactionStatus instead of height - Consistent recover-from-poison in ConnectionStatus setters - Add TODO for write_last_error() ConnectionStatus push consolidation - Add maintenance comments for progress watcher write_status bypass - Add tracing::debug for unknown Network variant fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Issue being fixed or feature implemented
The default implementations of
Fetch::fetch_with_metadata_and_proof,FetchMany::fetch_many_with_metadata_and_proof, andFetchUnproved::fetch_unproved_with_settingsuse#[async_trait]which desugars toPin<Box<dyn Future + Send + 'life>>. When downstream code calls these methods insidetokio::spawn, the compiler must prove that the resulting future isSendfor all lifetimes (higher-ranked trait bound).It fails because the default method body contains an inlined retry closure capturing
&Sdk, creating a complex async state machine whoseSendimpl the compiler cannot prove universally.This was previously masked because
Documenthad a customfetch_manyoverride with a simpler body. When that override was removed in #3179 (it referenced the deletedparse_proofmethod),Document::fetch_manyfell through to the default trait implementation, exposing the HRTB failure for any caller usingtokio::spawn.More #3361
What was done?
Extracted the retry+closure logic from the three core default trait methods into standalone
async fnhelpers. The default methods become thin wrappers that callquery.query()and delegate to the helper.This breaks the HRTB Send inference chain because the standalone helper is a concrete
async fn(not a trait default method), so the compiler proves Send at the helper's definition site where all types areconcrete.
For each trait:
Fetch::fetch_with_metadata_and_proof→ delegates tofetch_with_metadata_and_proof_impl()FetchMany::fetch_many_with_metadata_and_proof→ delegates tofetch_many_with_metadata_and_proof_impl()FetchUnproved::fetch_unproved_with_settings→ delegates tofetch_unproved_with_settings_impl()Also removed stale
#[async_trait::async_trait]fromimpl FetchMany<Identifier, Contenders> for ContenderWithSerializedDocument(bare impl with no async methods).No call sites changed. No turbofish/UFCS needed.
#[async_trait]stays on all traits.How Has This Been Tested?
cargo check -p dash-sdk— passescargo check -p dash-sdk --tests— passes (all test files compile without changes)cargo clippy -p dash-sdk -- -D warnings— zero warningscargo fmt --check --all— passesBreaking Changes
None. The trait signatures are unchanged. The extracted helpers are private to the module.
Checklist:
For repository code-owners and collaborators only
Summary by CodeRabbit