-
Notifications
You must be signed in to change notification settings - Fork 12
fix(spv): wait for wallet bootstrap before filter scan begins #833
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
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -69,6 +69,15 @@ impl AppContext { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .count() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback: if no open wallets in memory yet but the database has | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // wallets for this network, expect at least 1. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // bootstrap_loaded_wallets will load them shortly and the SPV wait | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // loop will block until load completes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let expected_wallets = if expected_wallets == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.db.wallet_count_for_network(&self.network)?.min(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expected_wallets | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+76
to
+80
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. 🟡 Suggestion: DB error now aborts This commit replaced 💡 Suggested change
Suggested change
source: ['claude'] 🤖 Fix this with AI agents
Comment on lines
+72
to
+80
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. 🟡 Suggestion:
💡 Suggested change
Suggested change
source: ['claude', 'codex'] 🤖 Fix this with AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Register reconcile channel BEFORE starting SPV so the event handlers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (spawned inside run_spv_loop) always capture a valid sender. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.spv_setup_reconcile_listener(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -92,6 +92,16 @@ impl Database { | |||||||||||||||||||||||||||||||||||||||
| tx.commit() | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /// Count how many wallets are stored in the database for a given network. | ||||||||||||||||||||||||||||||||||||||||
| pub fn wallet_count_for_network(&self, network: &Network) -> rusqlite::Result<usize> { | ||||||||||||||||||||||||||||||||||||||||
| let conn = self.conn.lock().unwrap(); | ||||||||||||||||||||||||||||||||||||||||
| conn.query_row( | ||||||||||||||||||||||||||||||||||||||||
| "SELECT COUNT(*) FROM wallet WHERE network = ?", | ||||||||||||||||||||||||||||||||||||||||
| [network.to_string()], | ||||||||||||||||||||||||||||||||||||||||
| |row| row.get(0), | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+95
to
+103
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: DB fallback counts password-locked wallets and forces a 30s startup stall on password-only profiles
💡 Suggested change
Suggested change
source: ['claude', 'codex'] 🤖 Fix this with AI agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /// Update the Dash Core wallet name for an HD wallet. | ||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||
| /// Returns `Ok(true)` if exactly one row was updated, `Ok(false)` if no | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1884,4 +1894,45 @@ mod tests { | |||||||||||||||||||||||||||||||||||||||
| .expect("Failed to query total_received"); | ||||||||||||||||||||||||||||||||||||||||
| assert_eq!(total_received, 10_000_000); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| fn insert_test_wallet(db: &Database, seed_hash: &[u8; 32], network: Network) { | ||||||||||||||||||||||||||||||||||||||||
| let network_str = network.to_string(); | ||||||||||||||||||||||||||||||||||||||||
| let conn = db.conn.lock().unwrap(); | ||||||||||||||||||||||||||||||||||||||||
| conn.execute( | ||||||||||||||||||||||||||||||||||||||||
| "INSERT INTO wallet (seed_hash, encrypted_seed, salt, nonce, master_ecdsa_bip44_account_0_epk, uses_password, network) | ||||||||||||||||||||||||||||||||||||||||
| VALUES (?, ?, ?, ?, ?, 0, ?)", | ||||||||||||||||||||||||||||||||||||||||
| rusqlite::params![ | ||||||||||||||||||||||||||||||||||||||||
| seed_hash.as_slice(), | ||||||||||||||||||||||||||||||||||||||||
| vec![0u8; 64], | ||||||||||||||||||||||||||||||||||||||||
| vec![0u8; 16], | ||||||||||||||||||||||||||||||||||||||||
| vec![0u8; 12], | ||||||||||||||||||||||||||||||||||||||||
| vec![0u8; 78], | ||||||||||||||||||||||||||||||||||||||||
| network_str, | ||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||
| .expect("Failed to insert test wallet"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1898
to
+1914
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. 💬 Nitpick: The new test helper covers only part of the source: ['claude'] |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||
| fn test_wallet_count_for_network_counts_per_network() { | ||||||||||||||||||||||||||||||||||||||||
| let db = create_test_database().expect("create db"); | ||||||||||||||||||||||||||||||||||||||||
| let mut seed_a = create_test_seed_hash(); | ||||||||||||||||||||||||||||||||||||||||
| let mut seed_b = create_test_seed_hash(); | ||||||||||||||||||||||||||||||||||||||||
| seed_a[0] = 0xA0; | ||||||||||||||||||||||||||||||||||||||||
| seed_b[0] = 0xB0; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| insert_test_wallet(&db, &seed_a, Network::Testnet); | ||||||||||||||||||||||||||||||||||||||||
| insert_test_wallet(&db, &seed_b, Network::Testnet); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||||||||||
| db.wallet_count_for_network(&Network::Testnet) | ||||||||||||||||||||||||||||||||||||||||
| .expect("count"), | ||||||||||||||||||||||||||||||||||||||||
| 2 | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| assert_eq!( | ||||||||||||||||||||||||||||||||||||||||
| db.wallet_count_for_network(&Network::Mainnet) | ||||||||||||||||||||||||||||||||||||||||
| .expect("count"), | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1898
to
+1934
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. 🟡 Suggestion: Signaling fix has no automated coverage — only the DB helper is tested
source: ['claude'] 🤖 Fix this with AI agents |
||||||||||||||||||||||||||||||||||||||||
| 0 | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1898
to
+1937
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. 💬 Nitpick: No test exercises the start_spv() fallback branch
source: ['claude'] |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
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.
🔴 Blocking:
.min(1)fallback undercounts wallets and counts password-protected ones that cannot auto-unlockrun_spv_loopwaits untilwm.wallet_count() >= expected_wallet_countbefore building the client and starting the initial filter scan (src/spv/manager.rs:1045-1075). The DB fallback here has two problems that both cause the race this PR is meant to fix to persist:Multi-wallet undercount. Clamping the DB count with
.min(1)means that when the database contains multiple wallets for this network, SPV only waits for the first one to load. As soon as wallet chore: impl ContextProvider #1 is imported into theWalletManager,wallet_count() >= 1passes and the compact-filter scan starts, but wallets Fix matrix strategy for ARM and AMD Builds #2..N are still being loaded asynchronously bybootstrap_loaded_wallets, so their addresses are absent frommonitored_addresses()when the scan begins. Their historical transactions are skipped for the session, reproducing the original bug for every wallet past the first.Password-protected wallets are counted but will never load. The in-memory count above filters by
is_open() && seed_bytes().is_ok(), meaning only wallets with decrypted seeds. The DB fallback is unfiltered, so password-protected wallets (stillWalletSeed::Closedafter bootstrap) raise the expected count buthandle_wallet_unlocked→wallet_seed_snapshotreturnsNonefor them, soqueue_spv_wallet_loadis never called.wallet_count()stays at 0 andrun_spv_loopburns its full 30-second deadline on every startup for users whose wallets are all password-protected.Both issues share the same root cause: the fallback should count the full set of wallets that can actually auto-load into SPV, not collapse everything to 1 and not include locked wallets that can never satisfy the wait condition.
💡 Suggested change
source: ['claude', 'codex']
🤖 Fix this with AI agents