Skip to content

Commit 0f01edd

Browse files
lklimekclaude
andauthored
feat(spv): enable mempool transaction detection via bloom filters (#776)
* 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>
1 parent 2b9bda7 commit 0f01edd

38 files changed

Lines changed: 654 additions & 231 deletions

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ Screens hold `Arc<AppContext>` and manage their own UI state.
165165
- `connection_status`, `developer_mode`, `fee_multiplier_permille`
166166
- Per-network instances (mainnet always present, others created on demand)
167167

168+
### ConnectionStatus (single source of truth for connection health)
169+
170+
`ConnectionStatus` (`src/context/connection_status.rs`) is the **single source of truth** for all high-level connection health state — RPC, ZMQ, SPV, and DAPI. For connection health (status, peer counts, errors, overall state), always read from `ConnectionStatus`, not directly from `SpvManager` or other subsystems.
171+
172+
SPV status is **push-based**: `SpvManager` event handlers write directly to `ConnectionStatus` atomics (status, peer count, errors) as events arrive. The UI frame loop calls `refresh_state()` to recompute `overall_state` from these atomics, but does not poll SPV for health. This means `ConnectionStatus` is up-to-date in both GUI and headless/test contexts. Detailed SPV sync progress (heights, phase summaries used by tooltips) may still be read directly from `SpvManager.status()` until that progress reporting is migrated into `ConnectionStatus`.
173+
168174
## UI Component Pattern
169175

170176
Components follow a lazy initialization pattern (see `docs/COMPONENT_DESIGN_PATTERN.md`):

0 commit comments

Comments
 (0)