From 1d1383be9afc80a14b3e9c97f006ad5dc2096795 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:45:18 +0100 Subject: [PATCH] =?UTF-8?q?refactor(db):=20schema=20v28=20=E2=80=94=20drop?= =?UTF-8?q?=20obsolete=20sync=20columns=20and=20clean=20up=20DB=20interfac?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump DEFAULT_DB_VERSION 27 → 28 - Drop last_terminal_block from wallet table (unused after sync simplification) - Drop last_full_sync_balance from platform_address_balances table - Rename last_platform_sync_checkpoint → last_platform_sync_height - Add runtime SQLite ≥3.35 check (required for DROP COLUMN) - Idempotent migration: checks column existence before each ALTER - Remove unused _is_sync_operation param from set_platform_address_info() - Remove set_platform_address_info_from_sync() wrapper - Fix redundant let-mut rebinding in fetch_platform_address_balances Co-Authored-By: Claude Opus 4.6 --- .../wallet/fetch_platform_address_balances.rs | 4 +- src/context/wallet_lifecycle.rs | 1 - src/database/initialization.rs | 89 ++++++++++++++++++- src/database/wallet.rs | 13 ++- src/model/wallet/mod.rs | 22 +---- 5 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/backend_task/wallet/fetch_platform_address_balances.rs b/src/backend_task/wallet/fetch_platform_address_balances.rs index a3031f810..219752505 100644 --- a/src/backend_task/wallet/fetch_platform_address_balances.rs +++ b/src/backend_task/wallet/fetch_platform_address_balances.rs @@ -32,7 +32,7 @@ impl AppContext { self.db.get_platform_sync_info(&seed_hash).unwrap_or((0, 0)); // Create provider (requires wallet to be open for address derivation) - let provider = { + let mut provider = { let wallet = wallet_arc.read().map_err(|e| e.to_string())?; match WalletAddressProvider::new(&wallet, self.network) { Ok(provider) => provider.with_stored_state(&wallet, self.network, last_sync_height), @@ -42,7 +42,6 @@ impl AppContext { Err(e) => return Err(e), } }; - let mut provider = provider; // Sync using SDK's privacy-preserving method (handles both full and incremental) let sdk = self.sdk.load().as_ref().clone(); @@ -148,7 +147,6 @@ impl AppContext { funds.balance, funds.nonce, &self.network, - true, ) { tracing::warn!("Failed to persist Platform address info: {}", e); } diff --git a/src/context/wallet_lifecycle.rs b/src/context/wallet_lifecycle.rs index 2ddb07fb4..f75428f1a 100644 --- a/src/context/wallet_lifecycle.rs +++ b/src/context/wallet_lifecycle.rs @@ -251,7 +251,6 @@ impl AppContext { info.balance, info.nonce, &self.network, - false, // Not a sync operation ) { tracing::warn!("Failed to store Platform address info in database: {}", e); } diff --git a/src/database/initialization.rs b/src/database/initialization.rs index 95be50b61..defe91c19 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -4,7 +4,7 @@ use rusqlite::{Connection, params}; use std::fs; use std::path::Path; -pub const DEFAULT_DB_VERSION: u16 = 27; +pub const DEFAULT_DB_VERSION: u16 = 28; pub const DEFAULT_NETWORK: &str = "dash"; @@ -51,6 +51,9 @@ impl Database { fn apply_version_changes(&self, version: u16, tx: &Connection) -> rusqlite::Result<()> { match version { + 28 => { + self.drop_obsolete_sync_columns_and_rename_checkpoint(tx)?; + } 27 => { self.add_network_indexes(tx)?; } @@ -315,8 +318,7 @@ impl Database { unconfirmed_balance INTEGER DEFAULT 0, total_balance INTEGER DEFAULT 0, last_platform_full_sync INTEGER DEFAULT 0, - last_platform_sync_checkpoint INTEGER DEFAULT 0, - last_terminal_block INTEGER DEFAULT 0 + last_platform_sync_height INTEGER DEFAULT 0 )", [], )?; @@ -355,7 +357,6 @@ impl Database { nonce INTEGER NOT NULL DEFAULT 0, network TEXT NOT NULL, updated_at INTEGER NOT NULL DEFAULT 0, - last_full_sync_balance INTEGER DEFAULT NULL, PRIMARY KEY (seed_hash, address, network), FOREIGN KEY (seed_hash) REFERENCES wallet(seed_hash) ON DELETE CASCADE )", @@ -553,6 +554,7 @@ impl Database { /// Migration: Add platform sync columns to wallet table (version 20). /// - last_platform_full_sync: Unix timestamp of last full platform address sync /// - last_platform_sync_checkpoint: Block height checkpoint from last full sync + /// (renamed to `last_platform_sync_height` in version 28) fn add_platform_sync_columns(&self, conn: &Connection) -> rusqlite::Result<()> { conn.execute( "ALTER TABLE wallet ADD COLUMN last_platform_full_sync INTEGER DEFAULT 0", @@ -838,6 +840,85 @@ impl Database { Ok(()) } + /// Migration: Drop obsolete sync columns and rename checkpoint column (version 28). + /// + /// - Drops `last_terminal_block` from `wallet` (added in v23, no longer read/written) + /// - Drops `last_full_sync_balance` from `platform_address_balances` (added in v26, no longer read/written) + /// - Renames `last_platform_sync_checkpoint` → `last_platform_sync_height` in `wallet` + /// to reflect its new semantics (SDK sync height, not a legacy checkpoint) + /// + /// Requires SQLite ≥ 3.35.0 for DROP COLUMN support. + fn drop_obsolete_sync_columns_and_rename_checkpoint( + &self, + conn: &Connection, + ) -> rusqlite::Result<()> { + // Verify SQLite version supports DROP COLUMN (3.35.0+) + let version_str: String = + conn.query_row("SELECT sqlite_version()", [], |row| row.get(0))?; + let parts: Vec = version_str + .split('.') + .filter_map(|s| s.parse().ok()) + .collect(); + let (major, minor, _patch) = ( + parts.first().copied().unwrap_or(0), + parts.get(1).copied().unwrap_or(0), + parts.get(2).copied().unwrap_or(0), + ); + if major < 3 || (major == 3 && minor < 35) { + return Err(rusqlite::Error::SqliteFailure( + rusqlite::ffi::Error::new(1), // SQLITE_ERROR + Some(format!( + "SQLite {} is too old for DROP COLUMN (need ≥ 3.35.0). \ + Please upgrade your system SQLite library.", + version_str + )), + )); + } + + // Drop obsolete columns (check existence first for idempotency) + let has_terminal_block: bool = conn + .query_row( + "SELECT COUNT(*) FROM pragma_table_info('wallet') WHERE name='last_terminal_block'", + [], + |row| row.get::<_, i32>(0).map(|count| count > 0), + ) + .unwrap_or(false); + if has_terminal_block { + conn.execute("ALTER TABLE wallet DROP COLUMN last_terminal_block", [])?; + } + + let has_full_sync_balance: bool = conn + .query_row( + "SELECT COUNT(*) FROM pragma_table_info('platform_address_balances') WHERE name='last_full_sync_balance'", + [], + |row| row.get::<_, i32>(0).map(|count| count > 0), + ) + .unwrap_or(false); + if has_full_sync_balance { + conn.execute( + "ALTER TABLE platform_address_balances DROP COLUMN last_full_sync_balance", + [], + )?; + } + + // Rename checkpoint → sync_height to match new semantics (check if rename is needed) + let has_checkpoint: bool = conn + .query_row( + "SELECT COUNT(*) FROM pragma_table_info('wallet') WHERE name='last_platform_sync_checkpoint'", + [], + |row| row.get::<_, i32>(0).map(|count| count > 0), + ) + .unwrap_or(false); + if has_checkpoint { + conn.execute( + "ALTER TABLE wallet RENAME COLUMN last_platform_sync_checkpoint TO last_platform_sync_height", + [], + )?; + } + + Ok(()) + } + /// Migration: Add network indexes to high-traffic tables (version 27). /// These tables are frequently queried with WHERE network = ? but lacked indexes. fn add_network_indexes(&self, conn: &Connection) -> rusqlite::Result<()> { diff --git a/src/database/wallet.rs b/src/database/wallet.rs index cd4760cda..fd26d0e58 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -976,7 +976,6 @@ impl Database { balance: u64, nonce: u32, network: &Network, - _is_sync_operation: bool, ) -> rusqlite::Result<()> { let network_str = network.to_string(); let canonical_address = Wallet::canonical_address(address, *network); @@ -1128,7 +1127,7 @@ impl Database { pub fn get_platform_sync_info(&self, seed_hash: &[u8; 32]) -> rusqlite::Result<(u64, u64)> { let conn = self.conn.lock().unwrap(); conn.query_row( - "SELECT last_platform_full_sync, last_platform_sync_checkpoint FROM wallet WHERE seed_hash = ?", + "SELECT last_platform_full_sync, last_platform_sync_height FROM wallet WHERE seed_hash = ?", params![seed_hash], |row| { let last_sync: i64 = row.get(0)?; @@ -1146,7 +1145,7 @@ impl Database { sync_height: u64, ) -> rusqlite::Result<()> { self.execute( - "UPDATE wallet SET last_platform_full_sync = ?, last_platform_sync_checkpoint = ? WHERE seed_hash = ?", + "UPDATE wallet SET last_platform_full_sync = ?, last_platform_sync_height = ? WHERE seed_hash = ?", params![last_sync_timestamp as i64, sync_height as i64, seed_hash], )?; Ok(()) @@ -1420,7 +1419,7 @@ mod tests { assert!(info.is_none()); // Set platform address info - db.set_platform_address_info(&seed_hash, &address, 10_000_000, 5, &network, true) + db.set_platform_address_info(&seed_hash, &address, 10_000_000, 5, &network) .expect("Failed to set platform address info"); // Retrieve it @@ -1433,7 +1432,7 @@ mod tests { assert_eq!(info.1, 5); // nonce // Update it - db.set_platform_address_info(&seed_hash, &address, 20_000_000, 10, &network, true) + db.set_platform_address_info(&seed_hash, &address, 20_000_000, 10, &network) .expect("Failed to update platform address info"); let info = db @@ -1531,7 +1530,7 @@ mod tests { // Add a single valid platform address using the helper function let address = create_test_address(network); - db.set_platform_address_info(&seed_hash, &address, 5_000_000, 3, &network, true) + db.set_platform_address_info(&seed_hash, &address, 5_000_000, 3, &network) .expect("Failed to set platform address info"); // Get all addresses @@ -1569,7 +1568,7 @@ mod tests { } // Set platform address info - db.set_platform_address_info(&seed_hash, &address, 10_000_000, 5, &network, true) + db.set_platform_address_info(&seed_hash, &address, 10_000_000, 5, &network) .expect("Failed to set platform address info"); // Verify it exists diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index 78593a75f..71b126b47 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -1989,16 +1989,6 @@ impl Wallet { .insert(address, PlatformAddressInfo { balance, nonce }); } - /// Set platform address info from a sync operation (same as `set_platform_address_info`). - pub fn set_platform_address_info_from_sync( - &mut self, - address: Address, - balance: Credits, - nonce: AddressNonce, - ) { - self.set_platform_address_info(address, balance, nonce); - } - /// Get the private key for a Platform address #[allow(clippy::result_large_err)] pub fn get_platform_address_private_key( @@ -2273,11 +2263,7 @@ impl WalletAddressProvider { let canonical_address = Wallet::canonical_address(address, self.network); // Update wallet with synced balances - wallet.set_platform_address_info_from_sync( - canonical_address.clone(), - funds.balance, - funds.nonce, - ); + wallet.set_platform_address_info(canonical_address.clone(), funds.balance, funds.nonce); // Also register in known_addresses and watched_addresses if not already present if !wallet.known_addresses.contains_key(&canonical_address) @@ -2859,11 +2845,11 @@ mod tests { } #[test] - fn test_set_platform_address_info_from_sync() { + fn test_set_platform_address_info() { let mut wallet = test_wallet(); let addr = test_address(1); - wallet.set_platform_address_info_from_sync(addr.clone(), 500_000, 3); + wallet.set_platform_address_info(addr.clone(), 500_000, 3); let info = wallet.platform_address_info.get(&addr).unwrap(); assert_eq!(info.balance, 500_000); @@ -2875,7 +2861,7 @@ mod tests { let mut wallet = test_wallet(); let addr = test_address(1); - wallet.set_platform_address_info_from_sync(addr.clone(), 500_000, 3); + wallet.set_platform_address_info(addr.clone(), 500_000, 3); wallet.set_platform_address_info(addr.clone(), 600_000, 4);