-
Notifications
You must be signed in to change notification settings - Fork 56
fix(platform-wallet): wait for SPV transport before resuming asset locks that need broadcast #4355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4.2-dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,10 @@ const SPV_CLIENT_STOP_BUDGET: Duration = Duration::from_secs(15); | |
| /// graceful timeout above was meant to escape. | ||
| const SPV_ABORT_GRACE: Duration = Duration::from_secs(2); | ||
|
|
||
| /// How often [`SpvRuntime::wait_until_ready`] re-checks for a started client | ||
| /// with connected peers. | ||
| const SPV_READINESS_POLL_INTERVAL: Duration = Duration::from_millis(250); | ||
|
|
||
| /// Join a stopped SPV runner, escalating to cancellation after `timeout`. | ||
| /// | ||
| /// Returns `None` once Tokio has confirmed the task terminated. Returns | ||
|
|
@@ -196,6 +200,46 @@ impl SpvRuntime { | |
| self.client.try_read().map(|c| c.is_some()).unwrap_or(false) | ||
| } | ||
|
|
||
| /// Whether a broadcast issued right now could reach the network: the | ||
| /// client is started *and* at least one peer is connected. | ||
| /// | ||
| /// Both halves are required because both are pre-send rejections in | ||
| /// [`broadcast_transaction_and_wait`](Self::broadcast_transaction_and_wait) | ||
| /// — an unstarted client and dash-spv's zero-connected-peers check. | ||
| async fn is_broadcast_ready(&self) -> bool { | ||
| self.client.read().await.is_some() && !self.peer_tracker.snapshot().is_empty() | ||
| } | ||
|
|
||
| /// Resolve once a broadcast could actually reach the network, or when | ||
| /// `timeout` elapses. `None` waits indefinitely. | ||
| /// | ||
| /// Returns whether readiness was reached. This closes the launch race | ||
| /// where work resumed at app start (asset-lock catch-up in particular) | ||
| /// broadcasts into a client that has not finished starting, takes the | ||
| /// definitive `Rejected { "client not started" }` verdict, and — having | ||
| /// no retry — strands the transaction for the whole session. | ||
| /// | ||
| /// Readiness is polled rather than pushed: "started" is a `client` | ||
| /// transition and "has peers" arrives as a dash-spv `PeersUpdated` | ||
| /// event, with no combined signal to subscribe to. The poll interval is | ||
| /// irrelevant next to the network latency being waited on. | ||
| pub async fn wait_until_ready(&self, timeout: Option<Duration>) -> bool { | ||
| let deadline = timeout.map(|t| tokio::time::Instant::now() + t); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: An overflowing FFI timeout can panic across the C boundary
source: ['codex'] |
||
| loop { | ||
| if self.is_broadcast_ready().await { | ||
| return true; | ||
| } | ||
| let now = tokio::time::Instant::now(); | ||
| match deadline { | ||
| None => tokio::time::sleep(SPV_READINESS_POLL_INTERVAL).await, | ||
| Some(deadline) if now < deadline => { | ||
| tokio::time::sleep_until(deadline.min(now + SPV_READINESS_POLL_INTERVAL)).await | ||
| } | ||
| Some(_) => return false, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Broadcast a transaction through SPV peers and wait for dash-spv's | ||
| /// network-acceptance verdict. | ||
| /// | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: Regression tests bypass the production SPV readiness path
The recovery tests use
StartingUpBroadcaster, which supplies its own readiness loop and a single synthetic flag. They prove thatAssetLockManagerinvokes the trait method, but they do not exerciseSpvBroadcasterdelegation or the productionSpvRuntimepredicate requiring both a started client and at least one connected peer. Removing this override or regressing either half of the production predicate would leave all new tests green. Add production-boundary coverage through a recordingSpvChannel, plus runtime coverage for the no-client and no-peer states.source: ['codex']