Skip to content

fix: duplicate tx when importing completed tx#7064

Merged
SWvheerden merged 5 commits intotari-project:developmentfrom
mmrrnn:mp_import_completed_tx_duplicate
May 20, 2025
Merged

fix: duplicate tx when importing completed tx#7064
SWvheerden merged 5 commits intotari-project:developmentfrom
mmrrnn:mp_import_completed_tx_duplicate

Conversation

@mmrrnn
Copy link
Copy Markdown
Contributor

@mmrrnn mmrrnn commented May 18, 2025

Description

When importing a completed transaction (not yet broadcasted), the wallet creates a duplicate of this transaction while scanning UTXOs.

Motivation and Context

Check if the corresponding transaction hasn’t been imported. Use existing tx's tx_id instead of creating a duplicate record.

How Has This Been Tested?

Manually

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

  1. Send one-sided tx via non-existing base node
  2. Export tx
  3. Imported tx to another wallet instance
  4. Run this wallet instance and wait until tx is mined.

Breaking Changes

  • None
  • Requires data directory on base node to be deleted
  • Requires hard fork
  • Other - Please specify

Summary by CodeRabbit

  • New Features

    • Enhanced wallet output recovery by linking recovered outputs to existing transactions using address-based transaction queries.
    • Added the ability to filter completed transactions by source and destination addresses in transaction history queries.
  • Tests

    • Introduced new tests for querying completed transactions filtered by addresses.
    • Updated test setups to include the transaction service handle for improved output recovery support.

@mmrrnn mmrrnn requested a review from a team as a code owner May 18, 2025 21:02
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 18, 2025

Walkthrough

The changes introduce the TransactionServiceHandle as a new dependency to the OutputManagerService and its related components. Constructors and resource structs are updated to accept and store this handle, enabling the output manager to access transaction service data, particularly during output recovery. The recovery logic is enhanced to query the transaction service for matching transactions before generating random transaction IDs. The transaction service is extended to support querying completed transactions filtered by source and destination addresses. Associated test setups and new tests are added accordingly.

Changes

File(s) Change Summary
base_layer/wallet/src/output_manager_service/mod.rs Imports TransactionServiceHandle and updates the initialize method to retrieve and pass this handle to OutputManagerService::new. No other logic is changed.
base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs Adds transaction_service_handle field to StandardUtxoRecoverer and updates its constructor. Modifies scan_and_recover_outputs to query the transaction service for completed transactions to find matching outputs before generating a random TxId.
base_layer/wallet/src/output_manager_service/resources.rs Adds a new transaction_service_handle field to OutputManagerResources and imports the handle type. No other logic is affected.
base_layer/wallet/src/output_manager_service/service.rs Adds transaction_service_handle to the OutputManagerService and its resources, updating the constructor and request handling to use the handle, especially when instantiating StandardUtxoRecoverer.
base_layer/wallet/tests/output_manager_service_tests/service.rs Updates test setup functions to pass a cloned TransactionServiceHandle to OutputManagerService::new. No other changes made.
base_layer/wallet/tests/transaction_service_tests/service.rs Adds helper function to create mock completed transactions and a new async test for querying completed transactions by source and destination addresses. Adds transaction_service_handle.clone() argument to OutputManagerService::new in test setup.
base_layer/wallet/src/transaction_service/handle.rs Adds new GetCompletedTransactionsByAddresses variant to TransactionServiceRequest enum and implements get_completed_transactions_by_addresses async method in TransactionServiceHandle to query transactions filtered by optional source and destination addresses.
base_layer/wallet/src/transaction_service/service.rs Adds handling of GetCompletedTransactionsByAddresses request in TransactionService to return completed transactions filtered by source and destination addresses from the database.
base_layer/wallet/src/transaction_service/storage/database.rs Adds find_completed_transactions_filter_addresses method to TransactionBackend trait and get_completed_transactions_by_addresses method to TransactionDatabase struct to support filtering completed transactions by source and destination addresses.
base_layer/wallet/src/transaction_service/storage/sqlite_db.rs Implements find_completed_transactions_filter_addresses in TransactionServiceSqliteDatabase to query completed transactions filtered by optional source and destination addresses using Diesel ORM, decrypting and converting results before returning.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant OutputManagerService
    participant TransactionServiceHandle
    participant StandardUtxoRecoverer

    App->>OutputManagerService: initialize (with TransactionServiceHandle)
    OutputManagerService->>TransactionServiceHandle: store handle for later use

    App->>OutputManagerService: ScanForRecoverableOutputs
    OutputManagerService->>StandardUtxoRecoverer: new(..., TransactionServiceHandle)
    StandardUtxoRecoverer->>TransactionServiceHandle: get_completed_transactions_by_addresses (filter by block/height)
    TransactionServiceHandle-->>StandardUtxoRecoverer: matching transactions or none
    StandardUtxoRecoverer->>OutputManagerService: return recovered outputs
Loading

Poem

In the warren of code, a handle appears,
Linking outputs to transactions, reducing our fears.
Recovery’s smarter, with connections anew,
The tests hop along, as rabbits will do.
With handles in paws, and logic refined,
The garden of code grows more entwined!
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 84a219d and dcce63a.

📒 Files selected for processing (1)
  • base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: test (mainnet, stagenet)
  • GitHub Check: test (testnet, esmeralda)
  • GitHub Check: cargo check with stable
  • GitHub Check: test (nextnet, nextnet)
  • GitHub Check: ci
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

⚠️ This PR contains unsigned commits. To get your PR merged, please sign those commits (git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}) and force push them to this branch (git push --force-with-lease).

If you're new to commit signing, there are different ways to set it up:

Sign commits with gpg

Follow the steps below to set up commit signing with gpg:

  1. Generate a GPG key
  2. Add the GPG key to your GitHub account
  3. Configure git to use your GPG key for commit signing
Sign commits with ssh-agent

Follow the steps below to set up commit signing with ssh-agent:

  1. Generate an SSH key and add it to ssh-agent
  2. Add the SSH key to your GitHub account
  3. Configure git to use your SSH key for commit signing
Sign commits with 1Password

You can also sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.

Learn how to use 1Password to sign your commits.

Watch the demo

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 8901bcb and f2f8cee.

📒 Files selected for processing (6)
  • base_layer/wallet/src/output_manager_service/mod.rs (3 hunks)
  • base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs (2 hunks)
  • base_layer/wallet/src/output_manager_service/resources.rs (2 hunks)
  • base_layer/wallet/src/output_manager_service/service.rs (4 hunks)
  • base_layer/wallet/tests/output_manager_service_tests/service.rs (2 hunks)
  • base_layer/wallet/tests/transaction_service_tests/service.rs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Cucumber tests / Base Layer
  • GitHub Check: test (nextnet, nextnet)
  • GitHub Check: Cucumber tests / FFI
  • GitHub Check: test (testnet, esmeralda)
  • GitHub Check: cargo check with stable
  • GitHub Check: test (mainnet, stagenet)
  • GitHub Check: ci
🔇 Additional comments (15)
base_layer/wallet/tests/output_manager_service_tests/service.rs (2)

185-185: Align test setup with updated OutputManagerService constructor
Added ts_handle.clone() to the OutputManagerService::new call, ensuring the service is initialized with the required TransactionServiceHandle.


255-255: Align secondary test setup with updated constructor
Similarly added ts_handle.clone() in setup_oms_with_bn_state, keeping both test helpers consistent with the new service signature.

base_layer/wallet/tests/transaction_service_tests/service.rs (1)

410-424: Test setup updated to match implementation changes

The change correctly passes a cloned transaction_service_handle to the OutputManagerService::new constructor, aligning with the implementation changes that introduced the handle as a new dependency for the output manager. This enables the output manager to access transaction service data, which is essential for the fix that prevents duplicate transactions when importing completed transactions.

base_layer/wallet/src/output_manager_service/resources.rs (2)

33-33: LGTM: Added TransactionServiceHandle import.

The import of TransactionServiceHandle is necessary for the fix to prevent duplicate transactions when importing completed transactions.


51-51: LGTM: Added TransactionServiceHandle as a resource dependency.

Adding the transaction service handle to OutputManagerResources enables the output manager to check if transactions already exist before creating duplicates during UTXO scanning. This addition follows the existing pattern of dependency injection in the codebase and directly addresses the PR objective of preventing duplicate transactions when importing completed transactions.

base_layer/wallet/src/output_manager_service/mod.rs (3)

65-65: LGTM: Added TransactionServiceHandle import.

The import is necessary for retrieving and using the transaction service handle in the initialization process.


130-130: LGTM: Added retrieval of TransactionServiceHandle.

Retrieving the TransactionServiceHandle from the available service handles is necessary to pass it to the OutputManagerService constructor.


145-145: LGTM: Added TransactionServiceHandle to the constructor.

Passing the transaction service handle to the OutputManagerService constructor completes the dependency injection pattern and provides the service with access to transaction data. This enables checking for existing transactions before creating duplicates during UTXO scanning, directly addressing the PR objective of preventing duplicate transactions when importing completed transactions.

base_layer/wallet/src/output_manager_service/service.rs (4)

118-118: Added new import for TransactionServiceHandle.

This import is required for the new functionality that checks for existing transactions during output recovery.


160-160: Added TransactionServiceHandle parameter to service constructor.

This parameter allows the OutputManagerService to access transaction data during output recovery operations.


191-191: Added transaction_service_handle to the OutputManagerResources struct.

The handle is properly stored in the resources to be accessible by the service operations.


492-499: Added transaction_service_handle to StandardUtxoRecoverer.

This change properly passes the transaction service handle to the StandardUtxoRecoverer, enabling it to check for existing transactions during output recovery, which addresses the duplicate transaction issue mentioned in the PR description.

base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs (3)

45-56: Appropriate import update for new dependency.

The imports have been properly updated to include the TransactionServiceHandle which is needed for the new functionality to prevent duplicate transactions.


63-63: Good addition of TransactionServiceHandle field.

Adding this field to the StandardUtxoRecoverer struct is necessary to access transaction history during output recovery, which aligns with the PR objective of preventing duplicate transactions.


71-80: Constructor properly updated to include the new dependency.

The constructor has been correctly modified to accept and store the TransactionServiceHandle parameter, maintaining proper initialization of the struct.

Comment on lines +160 to +175
None => {
let last_block_txs = self
.transaction_service_handle
.get_completed_transactions(None, db_output.mined_in_block, db_output.mined_height)
.await
.unwrap_or_default();
let tx_id = last_block_txs.iter().find_map(|tx| {
tx.transaction
.body
.outputs()
.iter()
.find(|tx| tx.commitment == db_output.commitment)
.map(|_| tx.tx_id)
});
tx_id.unwrap_or_else(TxId::new_random)
},
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.

💡 Verification agent

🧩 Analysis chain

Well-implemented fix for duplicate transaction issue.

This change directly addresses the PR objective by checking for existing transactions with matching outputs before generating a new random TxId. When importing completed transactions, it will now reuse the original transaction ID instead of creating duplicates.

The implementation appropriately:

  1. Queries completed transactions from the block where the output was mined
  2. Searches for transactions containing the same output commitment
  3. Falls back to the original random ID generation only when necessary

Let's verify this implementation with a simple test case:


🏁 Script executed:

#!/bin/bash
# Find test code that validates this behavior

echo "Searching for test cases that verify the duplicate transaction fix:"
rg -A 5 "StandardUtxoRecoverer.*new.*transaction_service_handle" --type rust
echo "Searching for test cases involving transaction import/export:"
rg -A 5 "import.*transaction|export.*transaction" --type rust

Length of output: 47879


Add missing tests for TxId reuse in StandardOutputsRecoverer

There are currently no tests covering the new “reuse existing TxId” logic in standard_outputs_recoverer.rs. Please add unit or integration tests to verify both outcomes:

  • When a matching output commitment is found in get_completed_transactions, the original tx_id is returned.
  • When no matching commitment exists, a new random TxId is generated.

Suggested locations for new tests:

  • base_layer/wallet/tests/ (unit tests for the recoverer)
  • integration_tests/tests/ (end-to-end test that imports outputs and checks the returned TxId)

Target file and lines needing coverage:

  • base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs:160–175
🤖 Prompt for AI Agents
In
base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs
around lines 160 to 175, add unit or integration tests to cover the new logic
that reuses existing TxId when a matching output commitment is found in
get_completed_transactions, and generates a new random TxId when no match
exists. Create tests that mock or simulate get_completed_transactions returning
transactions with and without matching commitments, then assert that the
returned TxId matches the expected behavior. Place these tests in
base_layer/wallet/tests/ for unit tests or integration_tests/tests/ for
end-to-end coverage.

@github-actions
Copy link
Copy Markdown

github-actions bot commented May 18, 2025

Test Results (CI)

    2 files     60 suites   22m 26s ⏱️
1 063 tests 1 061 ✅ 0 💤 2 ❌
2 125 runs  2 121 ✅ 0 💤 4 ❌

For more details on these failures, see this check.

Results for commit dcce63a.

♻️ This comment has been updated with latest results.

@mmrrnn mmrrnn force-pushed the mp_import_completed_tx_duplicate branch from f2f8cee to d968129 Compare May 18, 2025 21:19
@github-actions
Copy link
Copy Markdown

github-actions bot commented May 18, 2025

Test Results (Integration tests)

 2 files  + 2  11 suites  +11   1h 29m 54s ⏱️ + 1h 29m 54s
36 tests +36  31 ✅ +31  0 💤 ±0   5 ❌ + 5 
45 runs  +45  32 ✅ +32  0 💤 ±0  13 ❌ +13 

For more details on these failures, see this check.

Results for commit 54e3785. ± Comparison against base commit f593638.

♻️ This comment has been updated with latest results.

SWvheerden
SWvheerden previously approved these changes May 20, 2025
@SWvheerden SWvheerden merged commit 0c9d7f6 into tari-project:development May 20, 2025
8 of 13 checks passed
@coderabbitai coderabbitai bot mentioned this pull request May 22, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants