Skip to content

fix(wallet)!: support multiple vault locks, handle partial commit case - #1611

Merged
sdbondi merged 1 commit into
tari-project:developmentfrom
sdbondi:wallet-sdk-fixes
Oct 23, 2025
Merged

fix(wallet)!: support multiple vault locks, handle partial commit case#1611
sdbondi merged 1 commit into
tari-project:developmentfrom
sdbondi:wallet-sdk-fixes

Conversation

@sdbondi

@sdbondi sdbondi commented Oct 23, 2025

Copy link
Copy Markdown
Member

Description

fix(wallet)!: support multiple vault locks, handle partial commit case

Motivation and Context

The payment processor implementation highlighted some issues when performing transfers concurrently. This PR addresses those issues.

This PR allows multiple vault locks to be acquired to allow concurrent transfers of vault funds.

There was a TODO in the code to handle the case where the main transaction intent was rejected, but the fee intent accepted. This could result in incorrect fund tracking (output and vault) in this case. Specifically, it was observed that the wallet would attempt to spend outputs that were never created on-chain in the payment processor.

How Has This Been Tested?

Manually (payment processor)

What process can a PR reviewer use to test or verify this change?

Breaking Changes

  • None
  • Requires data directory to be deleted
  • Other - Please specify

Summary by CodeRabbit

  • New Features

    • Added UTXO type filter to Substates view.
    • Enhanced multi-signature support for stealth transfer transactions.
  • Bug Fixes

    • Improved transaction signing flow and error handling.
    • Enhanced UTXO recovery to continue syncing when indexer returns empty results.
    • Updated error messages for better clarity on vault fund constraints.
  • UI Updates

    • Removed "On Chain" column from Stealth UTXO list for improved clarity.
  • Chores

    • Updated logging indicators across account monitoring services.
    • Refined database schema for improved lock and vault management.

@coderabbitai

coderabbitai Bot commented Oct 23, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Comprehensive wallet SDK storage trait restructuring alongside stealth transfer signing flow refactoring, introducing read/write storage trait split with CommitableStore, enhanced lock lifecycle management via vault_locks table, branch tracking in key records, and multiple signing/commitment patterns across transaction handling and context management.

Changes

Cohort / File(s) Summary
Storage trait abstraction
crates/wallet/sdk/src/storage.rs, crates/wallet/storage_sqlite/src/lib.rs
Replaces monolithic WalletStore with ReadableWalletStore and WriteableWalletStore traits; introduces CommitableStore trait for commit/rollback semantics; updates WalletStoreReader to include exclude_locked parameter in stealth_outputs_get_unspent_by_account and change locks_get_by_transaction_id to return single WalletLockId instead of Vec.
Context management module
crates/wallet/sdk/src/apis/context.rs
New file introducing SdkReadContext, SdkWriteContext, and WithContext wrapper types; implements Deref/DerefMut and CommitableStore with lazy initialization via OnceLock and auto-commit-on-drop semantics.
Storage import consolidations across tests
crates/wallet/storage_sqlite/tests/accounts.rs, config.rs, key_manager_state.rs, substates.rs, transaction.rs, crates/wallet/sdk/tests/confidential_output_api.rs
Updates public imports to replace WalletStore with CommitableStore, ReadableWalletStore, WriteableWalletStore.
Vault and lock schema refactoring
crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql, crates/wallet/storage_sqlite/src/schema.rs, crates/wallet/storage_sqlite/src/models/vault.rs
Introduces new vault_locks table with vault/lock/amount columns; removes locked_revealed_balance and locked_by from vaults table; adds unique index on (vault_id, lock_id) and transaction-id index on locks.
Vault model and reader updates
crates/wallet/storage_sqlite/src/reader.rs
Renames commit/rollback to internal variants; refactors vault retrieval to compute locked_revealed_balance from vault_locks table and pass to vault construction; updates stealth_outputs_get_unspent_by_account signature to accept exclude_locked: bool; changes locks_get_by_transaction_id to return single WalletLockId with NotFound error if absent.
Lock and vault writer operations
crates/wallet/storage_sqlite/src/writer.rs
Implements CommitableStore on WriteTransaction; adds locks_unlock_finalized(lock_id, diff) and locks_release(lock_id) methods; refactors vault locking logic to use vault_locks table instead of direct vaults columns; introduces finalize/release helpers for stealth and confidential outputs.
Stealth transfer signing refactor
crates/wallet/sdk/src/apis/stealth_transfer.rs
Replaces signing_key_branch/signing_key_id with additional_signer (optional WalletPublicKey) and main_signer (WalletPublicKey); adds concurrency semaphore to StealthTransferApi; makes unlock_on_failure public; introduces lock_fee_inputs helper; updates signing flow to compute fee and main signers separately.
Transaction signing and finalization
crates/wallet/sdk/src/apis/transaction.rs
Reworks error handling in submit_transaction to map error status to InvalidTransaction; adds optional diff derived from execution_result.finalize.result.any_accept() for finalization; introduces locks_set_transaction_id(lock_id, transaction_id) public API; refactors lock management from per-lock iteration to single-diff-driven path.
Stealth outputs API updates
crates/wallet/sdk/src/apis/stealth_outputs.rs
Updates finalize_lock signature to require diff: &SubstateDiff; changes get_unspent_outputs_by_account to accept exclude_locked: bool; removes locks_set_transaction_id and finalize_outputs methods; simplifies release_lock to single transaction path; adjusts upsert_utxo logic for status preservation.
Key manager branch tracking
crates/wallet/sdk/src/models/key.rs, crates/wallet/sdk/src/apis/key_manager.rs
Adds branch: KeyBranch field to WalletKeyRecord, DerivedWalletKey, and WalletPublicKey; updates From<DerivedWalletKey> for WalletPublicKey to propagate branch; removes From<DerivedKey> for DerivedWalletKey conversion.
Transaction service lock integration
crates/wallet/sdk_services/src/transaction_service/handle.rs, service.rs
Adds lock_id: Option<WalletLockId> field to TransactionServiceRequest::SubmitTransaction; extends submit_transaction_with_opts signature; calls locks_set_transaction_id(lock_id, transaction_id) when lock provided.
Wallet daemon account handlers
applications/tari_walletd/src/handlers/accounts.rs
Invokes get_unspent_outputs_by_account with extra false parameter; adds None argument to transaction submission; major rewrite of handle_stealth_transfer signing flow with additional_signer support, new unlock_on_failure pattern, and enhanced error handling via context propagation.
Walletd import updates
applications/tari_walletd/src/handlers/auth/jwt.rs, src/services/webauthn.rs
Adds CommitableStore to storage imports.
Wallet daemon UI updates
applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx, applications/tari_indexer/web_ui/src/routes/Substates/Substates.tsx
Removes "On Chain" column from stealth UTXO table and adjusts column widths; adds "Utxo" to substate types filter list.
Logging and emoji updates
crates/wallet/sdk_services/src/account_monitor/monitor.rs, scanner.rs, applications/tari_indexer/src/lib.rs, applications/tari_indexer/src/network_state_sync/worker.rs
Replaces emoji prefixes with 🏦 in account monitor logs; converts dbg! to structured debug! log in indexer; adds warning/debug logs for unexpected substate values in network sync.
Wallet SDK and service imports
crates/wallet/sdk/src/apis/accounts.rs, confidential_outputs.rs, config.rs, non_fungible_tokens.rs, crates/wallet/sdk/Cargo.toml
Adds CommitableStore to storage imports across modules; adds tokio = { workspace = true, default-features = false, features = ["sync"] } dependency.
Account scanner refactor
crates/wallet/sdk_services/src/account_monitor/scanner.rs
Derives Debug and Clone on AccountScanner; changes process_result signature from &mut self to &self.
UTXO recovery and validation
crates/wallet/sdk_services/src/utxo_scanner/utxo_recovery.rs, scanner_round.rs
Adds WalletStorageError to storage imports; introduces pre-query cleanup and continues on empty responses instead of erroring; adds #[allow(clippy::too_many_lines)] to validation queue processor; downgrades progress commit log from info! to debug!.
Key manager memo API
crates/wallet/crypto/src/memo.rs
Removes pub fn as_bytes(&self) -> &[u8] accessor method from Memo impl.
Engine types accessors
crates/engine_types/src/commit_result.rs, resource_container.rs
Adds reject() accessor method to FinalizeResult and TransactionResult returning Option<&RejectReason>; updates error message in ResourceContainer::withdraw to include "or vault" text.
Indexer logging and updates
applications/tari_indexer/src/network_state_sync/worker.rs
Adds warning for unexpected substate values in Create branch; adds debug logs for UTXO-address creation with no value; adds warning if ID is Template/TransactionReceipt with no value.
Web server routing
utilities/db_inspector/src/webserver/server.rs
Updates route path parameters from :db_name to {db_name} syntax across /databases/{db_name}/... routes.
Lint configuration
lints.toml
Re-enables clippy::dbg_macro lint by uncommenting the entry.
Wallet SDK placeholder
crates/wallet/sdk/src/sdk.rs
Adds commented-out helper methods for creating/using read and write contexts (non-functional).

Sequence Diagram

sequenceDiagram
    participant User
    participant StealthTransfer as StealthTransferApi
    participant Signer as Signer
    participant Lock as LockManager
    participant TxSubmit as TransactionSubmit
    participant Storage as Storage

    rect rgb(240, 248, 255)
    note over StealthTransfer: New Signing Flow
    User->>StealthTransfer: transfer(amount, recipient)
    StealthTransfer->>StealthTransfer: compute fee_signer & main_signer
    alt has additional_signer
        StealthTransfer->>Signer: sign with additional_signer
    end
    StealthTransfer->>Signer: sign final with main_signer
    end

    rect rgb(255, 245, 238)
    note over Lock: Lock Lifecycle
    StealthTransfer->>Lock: lock_fee_inputs()
    StealthTransfer->>TxSubmit: submit_transaction_with_opts(lock_id)
    TxSubmit->>Storage: locks_set_transaction_id(lock_id)
    end

    rect rgb(240, 255, 240)
    note over Storage: Finalization with Diff
    alt execution succeeds & has diff
        TxSubmit->>Storage: locks_unlock_finalized(lock_id, diff)
    else execution fails or no diff
        TxSubmit->>Storage: locks_release(lock_id)
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Rationale: 60+ files modified across multiple interconnected subsystems. While ~40% of changes are homogeneous import consolidations (reducing complexity), the remaining changes involve: (1) foundational trait redesign splitting monolithic WalletStore; (2) dense schema and reader/writer refactoring for vault-lock separation; (3) intricate stealth transfer signing flow with new signer patterns and context management; (4) finalization logic restructuring with diff-driven patterns. Logic density and cross-domain coupling require careful review of each subsystem's coherence.

Possibly related PRs

Suggested labels

P-acks_required, P-reviews_required

Poem

🏦 The rabbit refactored with care,
Splitting stores for read and write,
Branch by branch, key by key,
Locks now dance with vaults in sight,
Signers sign the transfer right—
A wallet reborn, strong and fair! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.82% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix(wallet)!: support multiple vault locks, handle partial commit case" accurately captures the two primary objectives of this changeset. The schema restructuring (introducing a separate vault_locks table and removing lock tracking from the vaults table) directly enables the stated goal of supporting multiple vault locks per vault. The transaction API changes, particularly the reworked finalize/lock logic and error handling in submit_transaction, specifically address the partial commit scenario where a fee transaction succeeds while the main transaction fails. The title is concise, specific, and clearly communicates the main changes without being vague or overly broad, making it easy for team members scanning the commit history to understand the core purpose of the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/engine_types/src/resource_container.rs (1)

619-623: Bug: Stealth::lock_all returns a Fungible container.

This breaks type invariants and will cause unlock to fail with ResourceTypeMismatch. It should return Self::stealth(...), not Self::fungible(...).

Apply this fix:

             // Sets to zero and returns the amount
             let newly_locked_amount = mem::take(revealed_amount);
             *locked_amount += newly_locked_amount;
-            Ok(Self::fungible(resource_address, newly_locked_amount))
+            Ok(Self::stealth(resource_address, newly_locked_amount))
crates/wallet/storage_sqlite/src/writer.rs (1)

901-939: Bug: finalizing revealed funds handles only one vault row and subtracts once

Selecting first (vault_id, amount) loses other rows for the same lock_id, then deletes all rows. Result: balances for other vaults aren’t decremented.

Apply this fix to finalize all rows atomically:

-    // Fetch the vault locked by this lock_id
-    let (vault_id, amount) = vault_locks::table
-        .select((vault_locks::vault_id, vault_locks::amount))
-        .filter(vault_locks::lock_id.eq(lock_id))
-        .first::<(i32, i64)>(self.connection())
-        .optional()
-        .map_err(|e| WalletStorageError::general(OPERATION, e))?
-        .ok_or_else(|| WalletStorageError::NotFound {
-            operation: OPERATION,
-            entity: "vault lock".to_string(),
-            key: lock_id.to_string(),
-        })?;
-
-    // Delete the lock record
-    diesel::delete(vault_locks::table)
-        .filter(vault_locks::lock_id.eq(lock_id))
-        .execute(self.connection())
-        .map_err(|e| WalletStorageError::general(OPERATION, e))?;
-
-    let num_rows = diesel::update(vaults::table)
-        .set(vaults::revealed_balance.eq(vaults::revealed_balance.sub(amount)))
-        .filter(vaults::id.eq(vault_id))
-        .execute(self.connection())
-        .map_err(|e| WalletStorageError::general(OPERATION, e))?;
+    // Fetch all vault rows locked by this lock_id
+    let rows = vault_locks::table
+        .select((vault_locks::vault_id, vault_locks::amount))
+        .filter(vault_locks::lock_id.eq(lock_id))
+        .load::<(i32, i64)>(self.connection())
+        .map_err(|e| WalletStorageError::general(OPERATION, e))?;
+    if rows.is_empty() {
+        return Err(WalletStorageError::NotFound {
+            operation: OPERATION,
+            entity: "vault lock".to_string(),
+            key: lock_id.to_string(),
+        });
+    }
+
+    // Apply decrements per vault
+    for (vault_id, amount) in rows {
+        let affected = diesel::update(vaults::table)
+            .set(vaults::revealed_balance.eq(vaults::revealed_balance.sub(amount)))
+            .filter(vaults::id.eq(vault_id))
+            .execute(self.connection())
+            .map_err(|e| WalletStorageError::general(OPERATION, e))?;
+        if affected == 0 {
+            return Err(WalletStorageError::NotFound {
+                operation: OPERATION,
+                entity: "lock on vault".to_string(),
+                key: format!("{lock_id} (vault_id={vault_id})"),
+            });
+        }
+    }
+
+    // Delete all lock records
+    diesel::delete(vault_locks::table.filter(vault_locks::lock_id.eq(lock_id)))
+        .execute(self.connection())
+        .map_err(|e| WalletStorageError::general(OPERATION, e))?;
🧹 Nitpick comments (14)
crates/wallet/sdk_services/src/indexer_rest_api.rs (1)

255-256: TODO comment clearly documents a potential optimization.

The comment articulates a concrete performance concern—inefficiency of JSON + hex encoding for large protobuf payloads—and proposes a reasonable mitigation (supporting application/x-protobuf in the indexer REST API). Well-written and actionable for future work.

However, consider whether this optimization is part of the core objectives of this PR (vault locks and partial commit handling), or if it should be tracked as a separate GitHub issue to avoid scope creep and keep the PR focused on the intended changes.

crates/wallet/sdk_services/src/utxo_scanner/utxo_recovery.rs (2)

93-93: Consider refactoring to reduce function complexity.

Suppressing clippy::too_many_lines indicates the function handles multiple concerns. Consider extracting the UTXO filtering/cleanup logic (lines 146-166) into a separate method to improve maintainability.


134-136: Address the TODO and previous bug mentioned.

The TODO references a previous bug where "spent [UTXOs] were marked as unspent." While this PR adds logic to remove missing UTXOs from the queue, the underlying issue of checking local UTXO state before querying the indexer remains unaddressed. This optimization would improve performance and prevent redundant processing.

Do you want me to help implement this optimization or open a new issue to track it?

crates/wallet/sdk/src/apis/non_fungible_tokens.rs (1)

15-16: Remove unused CommitableStore import or use it in the bound.

CommitableStore is imported but not referenced; this may fail under deny(warnings). Either drop it or change the bound to where TStore: WalletStore + CommitableStore.

Apply this if not needed:

-    storage::{CommitableStore, WalletStorageError, WalletStore, WalletStoreReader, WalletStoreWriter},
+    storage::{WalletStorageError, WalletStore, WalletStoreReader, WalletStoreWriter},
applications/tari_indexer/web_ui/src/routes/Substates/Substates.tsx (1)

62-63: Verify type literal matches backend (“Utxo”).

Confirm the API expects exactly "Utxo" (case/spelling). If the server uses a canonical enum/string, prefer importing it from a shared package to avoid drift.

crates/wallet/sdk/src/models/key.rs (1)

56-63: Propagate and persist branch consistently; verify migration/serialization.

  • Field additions look correct and mapped in From<DerivedWalletKey>.
  • Please confirm DB migrations (or the documented data-dir reset) initialize WalletKeyRecord.branch for existing rows.
  • Validate any API payloads that include WalletPublicKey (now with branch) remain compatible or are versioned.

Also applies to: 78-81, 106-111, 123-128, 140-147

crates/wallet/sdk/src/sdk.rs (1)

98-135: Consider removing commented-out context management code.

These commented-out helper methods for read/write context management appear to be unused scaffolding, especially since the AI summary indicates that context management is implemented in crates/wallet/sdk/src/apis/context.rs. Removing dead code helps maintain codebase clarity.

If these methods are intended for future use or documentation, consider adding a comment explaining their purpose or moving them to a design document.

crates/wallet/sdk_services/src/transaction_service/handle.rs (1)

14-19: Plumbing lock_id into SubmitTransaction looks correct; consider an options struct to avoid positional None,None.

Two Option parameters are easy to swap. A small Options struct (e.g., SubmitOpts { new_account_info, lock_id }) or builder would reduce call-site errors.

crates/wallet/sdk_services/src/transaction_service/service.rs (1)

152-164: Linking lock_id to transaction_id occurs in a separate DB tx; consider making it atomic.

If locks_set_transaction_id() fails, the transaction remains inserted but unlinked. Prefer a single store write that inserts the tx and links the lock (or rolls back both) to avoid partial state.

Would you like a follow-up API on TransactionApi like insert_new_transaction_and_link(transaction, new_account_info, lock_id) -> tx_id?

applications/tari_walletd/src/handlers/accounts.rs (1)

286-289: Boolean parameter readability and semantics.

The added false is unclear at call sites. Confirm it excludes locked outputs (intended?), and consider replacing the bool with a descriptive enum or a builder for clarity.

What does the new boolean flag control exactly here?

crates/wallet/sdk/src/apis/stealth_outputs.rs (1)

385-457: Avoid per-UTXO write transactions inside the scan loop

Creating and committing a write tx per output is expensive and increases contention. Batch within a single write tx (or small chunks) for this loop.

Example:

-        for (addr, utxo) in outputs {
-            let mut tx = self.store.create_write_tx()?;
+        let mut tx = self.store.create_write_tx()?;
+        for (addr, utxo) in outputs {
             match tx
                 .stealth_outputs_get_by_commitment(resource_address, &commitment)
                 .optional()?
             {
                 // ...
             }
-            tx.commit()?;
         }
+        tx.commit()?;
crates/wallet/sdk/src/apis/stealth_transfer.rs (2)

602-631: Change output indexing guarded by filter is safe, but brittle

Index math depends on which outputs are positive after filtering. Current guard is fine, but consider selecting by tag/owner or passing back the created change commitment to avoid positional coupling.


796-798: Naming nit: main_signer vs additional_signer may confuse integrators

Here main_signer holds the fee signer and additional_signer may hold the transfer signer. Consider renaming to fee_signer and transfer_signer.

crates/wallet/sdk/src/apis/context.rs (1)

144-152: Safer Drop: rollback on panic, otherwise commit.

Commit during unwinding can persist inconsistent state. Prefer rollback when panicking.

 impl<TCtx: CommitableStore> Drop for WithContext<TCtx> {
     fn drop(&mut self) {
         if let Some(mut ctx) = self.ctx.take() {
-            if let Err(err) = ctx.commit() {
-                log::error!("Failed to commit context on drop: {}", err);
-            }
+            if std::thread::panicking() {
+                if let Err(err) = ctx.rollback() {
+                    log::error!("Failed to rollback context during panic: {}", err);
+                }
+            } else if let Err(err) = ctx.commit() {
+                log::error!("Failed to commit context on drop: {}", err);
+            }
         }
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cff16c and 14d0d3c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (44)
  • applications/tari_indexer/src/lib.rs (1 hunks)
  • applications/tari_indexer/src/network_state_sync/worker.rs (1 hunks)
  • applications/tari_indexer/web_ui/src/routes/Substates/Substates.tsx (2 hunks)
  • applications/tari_walletd/src/handlers/accounts.rs (6 hunks)
  • applications/tari_walletd/src/handlers/auth/jwt.rs (1 hunks)
  • applications/tari_walletd/src/services/webauthn.rs (1 hunks)
  • applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx (1 hunks)
  • crates/engine_types/src/commit_result.rs (2 hunks)
  • crates/engine_types/src/resource_container.rs (2 hunks)
  • crates/wallet/crypto/src/memo.rs (0 hunks)
  • crates/wallet/sdk/Cargo.toml (1 hunks)
  • crates/wallet/sdk/src/apis/accounts.rs (1 hunks)
  • crates/wallet/sdk/src/apis/confidential_outputs.rs (1 hunks)
  • crates/wallet/sdk/src/apis/config.rs (1 hunks)
  • crates/wallet/sdk/src/apis/context.rs (1 hunks)
  • crates/wallet/sdk/src/apis/key_manager.rs (5 hunks)
  • crates/wallet/sdk/src/apis/non_fungible_tokens.rs (1 hunks)
  • crates/wallet/sdk/src/apis/stealth_outputs.rs (5 hunks)
  • crates/wallet/sdk/src/apis/stealth_transfer.rs (13 hunks)
  • crates/wallet/sdk/src/apis/transaction.rs (5 hunks)
  • crates/wallet/sdk/src/models/key.rs (5 hunks)
  • crates/wallet/sdk/src/sdk.rs (1 hunks)
  • crates/wallet/sdk/src/storage.rs (7 hunks)
  • crates/wallet/sdk/tests/confidential_output_api.rs (1 hunks)
  • crates/wallet/sdk_services/src/account_monitor/monitor.rs (5 hunks)
  • crates/wallet/sdk_services/src/account_monitor/scanner.rs (12 hunks)
  • crates/wallet/sdk_services/src/indexer_rest_api.rs (1 hunks)
  • crates/wallet/sdk_services/src/transaction_service/handle.rs (4 hunks)
  • crates/wallet/sdk_services/src/transaction_service/service.rs (3 hunks)
  • crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs (1 hunks)
  • crates/wallet/sdk_services/src/utxo_scanner/utxo_recovery.rs (4 hunks)
  • crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql (2 hunks)
  • crates/wallet/storage_sqlite/src/lib.rs (3 hunks)
  • crates/wallet/storage_sqlite/src/models/vault.rs (3 hunks)
  • crates/wallet/storage_sqlite/src/reader.rs (12 hunks)
  • crates/wallet/storage_sqlite/src/schema.rs (3 hunks)
  • crates/wallet/storage_sqlite/src/writer.rs (13 hunks)
  • crates/wallet/storage_sqlite/tests/accounts.rs (1 hunks)
  • crates/wallet/storage_sqlite/tests/config.rs (1 hunks)
  • crates/wallet/storage_sqlite/tests/key_manager_state.rs (1 hunks)
  • crates/wallet/storage_sqlite/tests/substates.rs (1 hunks)
  • crates/wallet/storage_sqlite/tests/transaction.rs (1 hunks)
  • lints.toml (1 hunks)
  • utilities/db_inspector/src/webserver/server.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • crates/wallet/crypto/src/memo.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: check stable
  • GitHub Check: check nightly
  • GitHub Check: machete
  • GitHub Check: clippy
  • GitHub Check: test
  • GitHub Check: fmt
🔇 Additional comments (68)
crates/wallet/sdk/Cargo.toml (1)

38-38: LGTM! Tokio sync dependency appropriately added.

The addition of tokio with the sync feature and default-features = false follows best practices: it's workspace-pinned for consistency, minimal features reduce the attack surface and binary size, and the synchronization primitives (e.g., Mutex, RwLock) align well with the PR's goal of managing concurrent vault locks. The placement in core dependencies (not dev-only) is correct since vault locking is fundamental SDK functionality.

lints.toml (1)

25-25: Re-enabling dbg_macro lint aligns with logging improvements.

Uncommenting this lint is a good practice to prevent debug macros in production code and aligns with the PR's logging/formatting refinements mentioned in the summary.

Verify that no remaining dbg!() calls exist in the codebase that would now be flagged as errors.

crates/wallet/sdk_services/src/utxo_scanner/scanner_round.rs (1)

151-157: LGTM! Appropriate log level adjustment.

Reducing this intermediate shard completion message from info! to debug! improves signal-to-noise ratio in logs. The important final scan completion message at line 210 remains at info level, ensuring users still get visibility into overall progress.

utilities/db_inspector/src/webserver/server.rs (2)

22-29: LGTM! Correct path parameter syntax in macro.

The route parameter syntax has been correctly updated to use Axum's standard brace notation {db_name}. The double braces {{db_name}} in the format! macro (line 25) properly escape to produce {db_name} in the resulting route string.


39-65: Route parameter syntax correctly aligned with Axum standards.

All route definitions have been systematically updated to use brace-based path parameters {db_name}, which is the standard syntax for Axum. The changes are consistent across all affected routes.

Optionally verify that the route handlers correctly extract the db_name parameter using Axum's Path extractor with matching field names:

crates/wallet/sdk_services/src/account_monitor/monitor.rs (1)

98-98: LGTM: Consistent logging style update.

The emoji prefix changes standardize logging across the account monitor subsystem.

Also applies to: 105-105, 128-128, 192-192, 197-197, 222-222, 227-227, 241-241

crates/wallet/sdk_services/src/account_monitor/scanner.rs (3)

38-38: LGTM: Derive additions enable useful traits.

Adding Debug and Clone to AccountScanner expands the public API surface in a backward-compatible way and aligns with the broader refactoring to support concurrent operations.


57-57: LGTM: Consistent logging style update.

The emoji prefix changes standardize logging across the account scanner, matching the updates in monitor.rs.

Also applies to: 432-432, 447-447, 472-472, 525-525, 533-533, 539-539, 553-553, 599-599, 611-611, 659-659, 674-674


414-414: Verify interior mutability in storage layer.

Changing process_result from &mut self to &self relaxes mutability requirements, enabling concurrent access patterns. This aligns with the PR's goal of supporting multiple vault locks. However, ensure that the storage layer (WalletStore and related traits) properly handles interior mutability for all mutation operations called within this method (e.g., update_account, add_vault, save_nft, etc.).

Run the following to confirm the storage trait refactoring provides sound interior mutability:

crates/wallet/sdk_services/src/utxo_scanner/utxo_recovery.rs (2)

15-15: LGTM!

The WalletStorageError import is necessary for the new transaction handling logic.


168-172: Verify empty response handling aligns with error handling strategy.

The change from error to continue when no UTXOs are returned is more resilient. Combined with the pre-query cleanup (lines 146-166), this makes sense. However, ensure this doesn't mask legitimate indexer connectivity issues that should surface as errors in the outer error handling (lines 59-77).

applications/tari_indexer/src/lib.rs (1)

192-195: LGTM: switch to structured debug! logging.

Consistent with the module’s LOG_TARGET; no behavior change.

applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx (1)

67-73: LGTM: column widths align with 6-column layout.

Totals = 100%, headers and colSpan remain consistent after removing the extra column.

applications/tari_indexer/web_ui/src/routes/Substates/Substates.tsx (1)

94-95: Nit: dependency array change is fine.

Trailing comma is a no-op; readability OK.

crates/engine_types/src/resource_container.rs (1)

333-336: LGTM: clearer error messages.

Updated wording (“Bucket or vault…”) improves diagnostics; no behavior change.

Also applies to: 345-348

crates/engine_types/src/commit_result.rs (1)

200-203: LGTM: add reject() accessors.

Simple, ergonomic access to rejection reasons; aligns with existing any_* APIs.

Also applies to: 295-301

crates/wallet/sdk/src/apis/accounts.rs (1)

42-42: LGTM: Import addition aligns with storage trait refactoring.

The addition of CommitableStore to the imports is consistent with the broader PR objective to introduce granular storage traits.

applications/tari_walletd/src/services/webauthn.rs (1)

6-12: LGTM: Storage trait import update.

The addition of CommitableStore to the import list aligns with the PR's storage trait refactoring. The import formatting is clear and consistent.

crates/wallet/sdk/src/apis/config.rs (1)

9-9: LGTM: Import addition supports storage trait refactoring.

The CommitableStore import aligns with the broader storage interface restructuring in this PR.

crates/wallet/sdk/src/apis/confidential_outputs.rs (1)

19-19: LGTM: Storage trait import update.

The addition of CommitableStore is consistent with the storage interface restructuring across the PR.

crates/wallet/sdk/tests/confidential_output_api.rs (1)

10-10: LGTM: Test imports updated for new storage traits.

The change from WalletStore to ReadableWalletStore correctly reflects the storage trait refactoring and is appropriate for this test's usage patterns.

applications/tari_walletd/src/handlers/auth/jwt.rs (1)

10-16: LGTM: Storage import update for trait refactoring.

The addition of CommitableStore to the import list is consistent with the PR's storage trait restructuring. The multi-line import formatting enhances readability.

crates/wallet/storage_sqlite/tests/accounts.rs (1)

8-8: LGTM: Test imports updated for granular storage traits.

The import changes correctly reflect the storage trait split into ReadableWalletStore, WriteableWalletStore, and CommitableStore, replacing the monolithic WalletStore trait. This aligns with the PR's architectural improvements.

crates/wallet/storage_sqlite/tests/transaction.rs (1)

8-8: Import update aligns with storage trait refactor.

The addition of CommitableStore, ReadableWalletStore, and WriteableWalletStore to the import surface is consistent with the broader PR refactoring that splits storage capabilities into granular traits.

crates/wallet/storage_sqlite/migrations/2023-02-08-122514_initial/up.sql (3)

104-114: Vaults table correctly updated for multi-lock architecture.

The removal of locked_revealed_balance and _locked_by columns (per the AI summary) and the retention of core vault fields aligns with the shift to the vault_locks junction table for managing multiple locks per vault.


119-129: Vault locks junction table properly designed.

The vault_locks table correctly implements a many-to-many relationship between vaults and locks with:

  • Appropriate foreign keys with CASCADE delete for referential integrity
  • Unique constraint on (vault_id, lock_id) preventing duplicates
  • amount field to track the locked amount per lock

This enables the concurrent transfer capability mentioned in the PR objectives.


186-186: Partial unique index correctly handles NULL transaction IDs.

The filtered unique index on transaction_id allows multiple locks without assigned transactions (NULL values) while enforcing uniqueness for locks with transaction IDs. This is the correct approach for optional transaction association.

crates/wallet/storage_sqlite/tests/key_manager_state.rs (1)

5-11: Import surface updated consistently.

The expanded imports align with the storage trait split introduced across the PR.

crates/wallet/storage_sqlite/tests/substates.rs (1)

8-14: Import surface updated consistently.

The expanded imports align with the storage trait split introduced across the PR.

crates/wallet/sdk/src/apis/key_manager.rs (2)

37-37: Import updated for new storage trait.

The addition of CommitableStore aligns with the storage API refactoring across the PR.


79-79: Branch field added consistently to key records.

The branch field has been added to all key-related struct constructions (WalletKeyRecord, WalletPublicKey, DerivedWalletKey), ensuring branch context is preserved throughout the key lifecycle. The changes are consistent across all construction sites.

Also applies to: 141-141, 149-149, 174-174, 261-261

crates/wallet/storage_sqlite/tests/config.rs (1)

5-11: Import surface updated consistently.

The expanded imports align with the storage trait split introduced across the PR.

crates/wallet/storage_sqlite/src/lib.rs (3)

21-21: Import updated for storage trait split.

The updated imports reflect the split of storage capabilities into ReadableWalletStore and WriteableWalletStore traits.


57-67: ReadableWalletStore implementation is correct.

The implementation properly provides read-only transaction capabilities with appropriate transaction lifecycle management.


69-79: WriteableWalletStore implementation is correct.

The implementation properly provides write transaction capabilities, completing the separation of read and write storage concerns.

crates/wallet/storage_sqlite/src/schema.rs (3)

218-226: Vault locks table schema correctly defined.

The Diesel schema declaration matches the migration SQL and properly defines the junction table for vault-lock relationships.


228-242: Vaults table schema updated correctly.

The schema reflects the removal of locking-related columns, consistent with the migration to the vault_locks table.


270-271: Table relationships properly declared.

The joinable! declarations and inclusion in allow_tables_to_appear_in_same_query! correctly establish the relationships between vault_locks, locks, and vaults for Diesel ORM queries.

Also applies to: 291-291

crates/wallet/sdk_services/src/transaction_service/handle.rs (2)

39-41: LGTM on delegating with defaults.

Forwarding None for new_account_info and lock_id is clear and preserves prior behavior.


58-75: submit_transaction_with_opts correctly threads lock_id; please verify all call-sites updated.

New param surfaces widely; ensure no stale usages.

Run to find and review all call sites:

crates/wallet/sdk_services/src/transaction_service/service.rs (1)

109-121: Request handling forwards lock_id correctly.

Match arm captures lock_id and passes it through as intended.

applications/tari_walletd/src/handlers/accounts.rs (3)

679-689: Submit with extended opts (None lock_id) looks fine.

Matches new signature; no lock to associate in this flow.


1013-1030: Dry‑run path properly releases the lock before submission.

Looks correct; aligns with TODO to avoid locking on dry runs at the source.


1032-1047: unlock_on_failure usage—confirm signature and intent.

You pass the awaited Result into unlock_on_failure(). If the API expects a closure/future to run and auto‑release on error, consider passing a closure to also cover pre‑submission errors. Otherwise, confirm it’s defined to accept a Result and release accordingly.

Please confirm unlock_on_failure(lock_id, result) semantics.

crates/wallet/sdk/src/apis/transaction.rs (5)

92-107: Good: map immediate submission rejection to InvalidTransaction and return Ok(false).

Clearer caller contract and preserves error semantics for non‑rejection statuses.


260-279: Finalize/release logic aligns with diff‑driven commit; ensure 1‑tx ↔ 1‑lock invariant.

This now unlocks a single linked lock when a diff exists, else releases all. If multiple locks can be tied to a single tx, some may leak. Please confirm the invariant that exactly one WalletLockId is linked per tx.


295-303: locks_set_transaction_id API is a useful addition.

Simple, single-purpose linking method—good.


355-357: Equality fix in commit_diff is correct.

Comparing by value avoids missed child attachment.


305-316: Release path targets a single lock; verify this matches schema/usage.

If the store can hold multiple locks per tx, iterate and release all; otherwise this is correct.

Run to find the linking/getter API and check its cardinality:

crates/wallet/storage_sqlite/src/models/vault.rs (1)

62-67: Do not expect on BigDecimal conversion; propagate an error instead. Potential runtime panic.

BigDecimal::to_u128() returns None on overflow or non-integer scale; this will panic. Storage code must not panic.

Apply a safe conversion and error mapping:

-            locked_revealed_balance: Amount::from(
-                locked_revealed_balance
-                    .to_u128()
-                    // Should be impossible because sqlite is limited to i64
-                    .expect("locked more than u128::MAX funds"),
-            ),
+            locked_revealed_balance: {
+                use bigdecimal::ToPrimitive;
+                let v = locked_revealed_balance
+                    .to_i64()
+                    .ok_or_else(|| WalletStorageError::DecodingError {
+                        operation: "try_into_vault",
+                        item: "vault.locked_revealed_balance",
+                        details: format!("non-integer or out of i64 range: {locked_revealed_balance}"),
+                    })?;
+                if v < 0 {
+                    return Err(WalletStorageError::DecodingError {
+                        operation: "try_into_vault",
+                        item: "vault.locked_revealed_balance",
+                        details: "negative locked balance".to_string(),
+                    });
+                }
+                Amount::from(v)
+            },

Additionally, please confirm upstream that locked_revealed_balance is stored in base units (no fractional scale).

crates/wallet/sdk/src/apis/stealth_outputs.rs (4)

188-190: Bold move to centralize release via tx.locks_release

This simplifies lock teardown into a single atomic op. LGTM.


192-196: Finalize method returns the wrong API error type for this module

This is StealthOutputsApi, but the method returns ConfidentialOutputsApiError. Align the error type to avoid surprising call sites and unnecessary coupling.
[ suggest_recommended_refactor ]
Apply this diff:

-    pub fn finalize_lock(&self, lock_id: WalletLockId, diff: &SubstateDiff) -> Result<(), ConfidentialOutputsApiError> {
+    pub fn finalize_lock(&self, lock_id: WalletLockId, diff: &SubstateDiff) -> Result<(), StealthOutputsApiError> {
         self.store
-            .with_write_tx(|tx| tx.locks_unlock_finalized(lock_id, diff))?;
+            .with_write_tx(|tx| tx.locks_unlock_finalized(lock_id, diff))?;
         Ok(())
     }

If other modules depend on the previous signature, add a thin wrapper in their APIs instead of reusing this method with a mismatched error.


261-267: New exclude_locked toggle is a good addition

Forwarding exclude_locked down to storage keeps the API precise and avoids client-side filtering. LGTM.


293-305: upsert_utxo preserves non-Unspent status — sensible guardrail

Only allowing status changes from Unspent prevents rescans from reviving spent/invalid outputs. LGTM.

To confirm no unintended status flips elsewhere, search for direct updates bypassing this method:

crates/wallet/sdk/src/apis/stealth_transfer.rs (3)

481-489: Good: explicit logging when adding unconfirmed fee change output

Improves traceability during concurrent transfers. LGTM.


677-687: unlock_on_failure made public — good ergonomics

Centralizes lock cleanup on error paths. LGTM.


235-241: Critical: lock_id is re-created and shadowed in PreferConfidential, leaking the caller’s lock

This branch creates a new lock_id instead of using the provided one, so the returned InputsToSpend cannot be finalized/released with the outer lock. Locks will leak and outputs remain stuck.

Apply this fix:

-                let lock_id = self.outputs_api.create_lock()?;
-                let (inputs, blinded_amount_locked) = self.outputs_api.lock_outputs_until_partial_amount(
+                let (inputs, blinded_amount_locked) = self.outputs_api.lock_outputs_until_partial_amount(
                     owner_account_component_address,
                     &resource_address,
                     spend_amount,
-                    lock_id,
+                    lock_id,
                 )?;

Add a test to assert all locked outputs for a transfer share the same lock_id.

crates/wallet/storage_sqlite/src/writer.rs (2)

1188-1215: Allowing re-lock of same-lock_id unconfirmed outputs is correct for spending fee change

The added OR branch enabling LockedUnconfirmed for the same lock avoids races when spending change in a single flow. LGTM.


1642-1643: Drop now calls rollback_internal — good safety net

Prevents dangling write transactions. LGTM.

crates/wallet/sdk/src/storage.rs (4)

79-95: with_write_tx handles commit/rollback internally

Clearer ergonomics and fewer footguns for callers. LGTM.


253-258: Reader now supports exclude_locked for stealth outputs

Good capability uplift with explicit param. LGTM.


277-281: locks_get_by_transaction_id returns a single id

Makes sense for 1:1 lock/tx association. LGTM.


478-485: Lock lifecycle APIs (release/unlock_finalized) are well‑documented

Interfaces align with new diff-driven finalization model. LGTM.

crates/wallet/storage_sqlite/src/reader.rs (4)

534-548: vaults_get includes locked_revealed_balance via vault_locks sum

Accurate balance view without overloading the main vault row. LGTM.


568-603: vaults_get_by_resource computes locked_revealed_balance correctly

Consistent with vaults_get; improves accuracy under concurrent locks. LGTM.


928-953: exclude_locked flag plumbed through for stealth outputs

Optional lock filtering at the DB is the right place. LGTM.


1076-1092: locks_get_by_transaction_id returns NotFound for missing — appropriate

Behavior matches new trait signature. LGTM.

crates/wallet/sdk/src/apis/context.rs (1)

18-21: Verify Clone on SdkReadContext is valid.

Deriving Clone requires TStore::ReadTransaction<'ctx>: Clone. If it isn’t, this will not compile. Either remove Clone or add a manual impl with the appropriate bound.

Possible quick fix (remove Clone):

-#[derive(Debug, Clone)]
+#[derive(Debug)]
 pub struct SdkReadContext<'ctx, TStore: ReadableWalletStore> {
     reader: TStore::ReadTransaction<'ctx>,
 }

Or implement Clone conditionally:

impl<'ctx, TStore> Clone for SdkReadContext<'ctx, TStore>
where
    TStore: ReadableWalletStore,
    TStore::ReadTransaction<'ctx>: Clone,
{
    fn clone(&self) -> Self {
        Self { reader: self.reader.clone() }
    }
}

Comment thread applications/tari_indexer/src/network_state_sync/worker.rs
Comment thread applications/tari_walletd/src/handlers/accounts.rs
Comment thread crates/wallet/sdk_services/src/utxo_scanner/utxo_recovery.rs
Comment thread crates/wallet/sdk/src/apis/context.rs Outdated
Comment thread crates/wallet/sdk/src/apis/context.rs Outdated
Comment thread crates/wallet/sdk/src/apis/context.rs Outdated
Comment thread crates/wallet/sdk/src/apis/context.rs Outdated
Comment thread crates/wallet/sdk/src/apis/stealth_transfer.rs
Comment thread crates/wallet/storage_sqlite/src/reader.rs
Comment thread crates/wallet/storage_sqlite/src/writer.rs
@github-actions

Copy link
Copy Markdown

Test Results (CI)

338 tests   - 113   334 ✅  - 117   1h 8m 50s ⏱️ + 17m 47s
 48 suites  -  15     0 💤 ±  0 
  1 files   ±  0     4 ❌ +  4 

For more details on these failures, see this check.

Results for commit 14d0d3c. ± Comparison against base commit 2cff16c.

This pull request removes 113 tests.
tari_ootle_wallet_storage_sqlite::transaction ‑ get_and_insert_transaction
tari_rpc_framework ‑ body::test::single_body
tari_rpc_framework ‑ body::test::streaming_body
tari_rpc_framework ‑ bounded_executor::test::spawn
tari_rpc_framework ‑ status::test::rpc_status_code_conversions
tari_rpc_framework ‑ status::test::rpc_status_or_optional
tari_state_store_rocksdb ‑ codecs::small_bytes::tests::from_slices
tari_state_store_rocksdb ‑ codecs::small_bytes::tests::heap_vs_stack
tari_state_store_rocksdb ‑ codecs::state_tree::tests::encode_decode
tari_state_store_rocksdb ‑ codecs::state_tree::tests::smoke
…

@sdbondi
sdbondi force-pushed the wallet-sdk-fixes branch 2 times, most recently from 4271c81 to 858fe67 Compare October 23, 2025 08:53
@sdbondi
sdbondi merged commit 4981b9b into tari-project:development Oct 23, 2025
10 of 11 checks passed
@sdbondi
sdbondi deleted the wallet-sdk-fixes branch October 23, 2025 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants