Skip to content

Conversation

kwvg
Copy link
Collaborator

@kwvg kwvg commented May 4, 2025

Additional Information

  • While bitcoin#17526 introduces Single Random Draw (SRD) as a new coin-selection algorithm, it remains disabled (like BnB) as it is unaware of mixed funds.

    • UTXO (un)locking as introduced in df765a4 for rpc_fundrawtransaction.py has been omitted in the test_fee_p2pkh() sub-test as we do not have any other outputs except for p2pkh. Failing to do so results in test failure (see below).

      Test error:
      dash@507c3774b68c:/src/dash$ ./test/functional/rpc_fundrawtransaction.py
      2025-05-06T06:48:01.101000Z TestFramework (INFO): PRNG seed is: 4612096820677160739
      2025-05-06T06:48:01.101000Z TestFramework (INFO): Initializing test directory    /tmp/dash_func_test_n3i0k9yc
      2025-05-06T06:48:04.177000Z TestFramework (INFO): Connect nodes, set fees, generate blocks, and sync
      [...]
      2025-05-06T06:48:09.249000Z TestFramework (INFO): Test fundrawtxn p2pkh fee
      2025-05-06T06:48:09.298000Z TestFramework (ERROR): JSONRPC error
      Traceback (most recent call last):
        File "/src/dash/test/functional/test_framework/test_framework.py", line 162, in main
          self.run_test()
        File "/src/dash/./test/functional/rpc_fundrawtransaction.py", line 115, in run_test
          self.test_fee_p2pkh()
        File "/src/dash/./test/functional/rpc_fundrawtransaction.py", line 398, in test_fee_p2pkh
          fundedTx = self.nodes[0].fundrawtransaction(rawtx)
        File "/src/dash/test/functional/test_framework/coverage.py", line 49, in __call__
          return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
        File "/src/dash/test/functional/test_framework/authproxy.py", line 143, in __call__
          raise JSONRPCException(response['error'], status)
      test_framework.authproxy.JSONRPCException: Insufficient funds. (-4)
      2025-05-06T06:48:09.802000Z TestFramework (INFO): Stopping nodes
      
  • In bitcoin#22019 we don't use SelectionResult::GetShuffledInputVector() in CreateTransactionInternal() because it interferes with our BIP69 sorting implementation that sorts vins instead of CRecipients.

How Has This Been Tested?

A full CoinJoin session run on 0e33db2

CoinJoin session run on build 0e33db23

Breaking Changes

None expected.

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation (note: N/A)
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@kwvg kwvg added this to the 23 milestone May 4, 2025
PastaPastaPasta added a commit that referenced this pull request May 7, 2025
2b3d4a5 fix: use `GetTxDepthInMainChain` for CoinJoin depth checks (Kittywhiskers Van Gogh)

Pull request description:

  ## Motivation

  When testing CoinJoin on [dash#6654](#6654), it was found that CoinJoin on `develop` (fc96190) had stopped working as expected. The regression was traced back to 9a5dc62 in [dash#6633](#6633).

  Specifically, erroneous substitution of `GetDepthInMainChain()` calls with `GetTxBlocksToMaturity()`. This has been resolved by changing them to the intended substitution, `GetTxDepthInMainChain()` and now CoinJoin should work as intended.

  ## How Has This Been Tested?

  A full CoinJoin session run on 2b3d4a5

  ![CoinJoin session run on build 2b3d4a5](https://github.com/user-attachments/assets/69e5950f-3ff4-46b6-bb84-ab10410c7f93)

  ## Breaking Changes

  None expected.

  ## Checklist

  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)**
  - [x] I have added or updated relevant unit/integration/functional/e2e tests **(note: N/A)**
  - [x] I have made corresponding changes to the documentation **(note: N/A)**
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 2b3d4a5
  UdjinM6:
    utACK 2b3d4a5

Tree-SHA512: f4cd134cc5104397e137d80406f3b8afe4c8268a33926186ec441a4e1030c8b57eae5796c5d9dac09c9fc02a7fdd3254d2910d18c75f44723a1d986918ff59c2
@kwvg kwvg marked this pull request as ready for review May 7, 2025 20:58
@kwvg kwvg requested review from knst, UdjinM6 and PastaPastaPasta May 7, 2025 21:00
Copy link

coderabbitai bot commented May 7, 2025

Walkthrough

This set of changes modernizes and refactors wallet coin selection, output handling, and transaction status logic across the codebase. The primary updates include replacing the legacy CInputCoin class with a new, richer COutput class that encapsulates more metadata about wallet outputs. Coin selection algorithms (SelectCoinsBnB, KnapsackSolver, etc.) now return a std::optional<SelectionResult> object instead of using output parameters, encapsulating selection results and error states more cleanly. The CWallet class and related test fixtures were updated to require an ArgsManager reference (gArgs or m_args) during construction, localizing argument access. Transaction finality checks and related status codes were removed from wallet and GUI logic, simplifying transaction status reporting. Functional tests and utilities were updated to use new output handling patterns, with new helpers for UTXO locking and explicit raw transaction spending. Public and internal APIs were updated to reflect these structural changes.

Tip

⚡️ 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.

✨ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

Copy link

@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: 7

🔭 Outside diff range comments (1)
src/wallet/spend.cpp (1)

370-401: 🛠️ Refactor suggestion

Unnecessary work: disabled algorithms are still executed

The SRD/BnB blocks are guarded by
if (auto bnb_result{SelectCoinsBnB(...)}; /* DISABLES CODE */ (false))
and similarly for SRD.
Although the if condition is always false, the initializer (SelectCoinsBnB / SelectCoinsSRD) is still evaluated, performing a complete coin-selection run only to discard the result—wasting CPU on every transaction.

Refactor to avoid the call altogether, e.g.

#ifdef ENABLE_BNB            // or runtime flag
if (auto bnb_result = SelectCoinsBnB(...)) {
    ...
}
#endif

or use a short-circuit expression:

if (false && SelectCoinsBnB(...)) { /* never reached, no call */ }

Eliminating the redundant invocation will noticeably speed up heavy wallet workloads.
[performance]

♻️ Duplicate comments (1)
src/wallet/rpcwallet.cpp (1)

1006-1008: Same readability issue as in GetReceived()

The ListReceived() branch repeats the overlapping checks noted above.
Please apply the same simplification here for consistency.

🧹 Nitpick comments (8)
src/wallet/rpcwallet.cpp (2)

589-593: Condition can be simplified to avoid double-checking immature coinbase state

wallet.IsTxImmatureCoinBase(wtx) already implies wtx.IsCoinBase(), so the first half of the
OR (wtx.IsCoinBase() && depth < 1 …) partially overlaps with the second half.
Although functionally correct, this duplication makes the intent harder to read and increases the
risk of future maintenance bugs (e.g. if the “< 1 confirmation” rule changes).

-        if (// Coinbase with less than 1 confirmation is no longer in the main chain
-            (wtx.IsCoinBase() && (depth < 1 || !include_coinbase))
-            || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
+        // Skip coinbase if it must be excluded, or it is still immature and the caller didn't ask
+        // for immature results.
+        if ((wtx.IsCoinBase() && !include_coinbase) ||
+            (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))

This conveys the same logic with fewer moving parts.
No change in behaviour – just readability & maintainability.


3250-3308: Several nit-level observations in the listunspent loop

  1. scriptPubKey is recreated for every iteration even when avoid_reuse is false.
    This is tiny, but you could scope it inside the if (avoid_reuse …) block to save a hash lookup.

  2. IsSpentKey() is evaluated only if avoid_reuse is true (short-circuit), good.
    However, a single call per UTXO is still expensive when thousands of outputs are returned.
    Consider caching the result per outpoint if this becomes a hotspot.

  3. The push-order of JSON keys is inconsistent with earlier versions (spendable/solvable
    came before amount/confirmations). This breaks strict consumers that relied on the order
    (yes, JSON objects are unordered, but several downstream scripts sort-compare the raw string).
    Re-ordering might save a headache for integrators.

  4. If ancestorcount is fetched, ancestorsize and ancestorfees are always pushed,
    even when their value is zero. That is fine, but the RPC help string says they are optional.
    Either update the help or skip zero-value fields to match the documentation.

Nothing is blocking, just minor polish.

src/bench/coin_selection.cpp (1)

16-21: nextLockTime should be std::atomic (or at least static thread_local)
The benchmark uses a file-scope‐static int nextLockTime that is incremented every time addCoin() is called.
If the benchmark is ever executed concurrently (either by the Google-bench harness or by multiple threads you add later), the increment becomes a data race and results in undefined behaviour.

-static int nextLockTime = 0;
+static std::atomic<int> nextLockTime{0};

A cheaper alternative is static thread_local int nextLockTime, which preserves determinism while remaining race-free when the benchmark is sharded.

test/functional/rpc_fundrawtransaction.py (1)

950-955: Unused loop variable – rename to “_” for clarity
i is never referenced inside the body, triggering Ruff’s B007 warning.

-        for i in range(0, 2):
+        for _ in range(2):
🧰 Tools
🪛 Ruff (0.8.2)

950-950: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

src/wallet/spend.h (1)

49-60: Minor doc-comment formatting
The “param@[…]” tokens are non-standard and break Doxygen parsing.
Consider replacing with the usual @param wallet / @param nTargetValue etc., or simple sentence descriptions, so generated documentation remains clean.

src/wallet/coinselection.h (1)

90-99: CoinSelectionParams stores a reference that can easily dangle

rng_fast is stored by reference. When callers pass a local
FastRandomContext rand{} directly into the constructor (as done in the
tests), CoinSelectionParams may outlive rand, leaving a dangling
reference.

Two minimal-impact fixes:

-struct CoinSelectionParams {
-    FastRandomContext& rng_fast;
+struct CoinSelectionParams {
+    FastRandomContext* rng_fast;

and dereference via *rng_fast, or accept the RNG only as a regular
parameter to the functions that actually need it.

Either approach avoids lifetime pitfalls without changing behaviour.

src/wallet/coinselection.cpp (1)

482-487: Use caller-supplied RNG when shuffling inputs

SelectionResult::GetShuffledInputVector() seeds a new default
FastRandomContext, discarding the RNG already available to the caller.
That:

  • breaks determinism in unit tests that pass a seeded RNG, and
  • duplicates entropy gathering.

Passing the existing RNG (or a reference) avoids both issues:

-std::vector<COutput> SelectionResult::GetShuffledInputVector() const
-{
-    std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
-    Shuffle(coins.begin(), coins.end(), FastRandomContext());
+std::vector<COutput> SelectionResult::GetShuffledInputVector(FastRandomContext& rng) const
+{
+    std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
+    Shuffle(coins.begin(), coins.end(), rng);

(This requires adjusting call-sites.)

src/wallet/spend.cpp (1)

366-368: Avoid needless vector copies in AttemptSelection

coins is passed by value, so every call copies the full std::vector<COutput>.
AttemptSelection never mutates the argument—it only forwards it—so a const& is sufficient and avoids potentially large heap copies when many UTXOs are available.

-std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue,
-                                                const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
+std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue,
+                                                const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& coins,

Remember to update the call sites accordingly.
[performance]

🧰 Tools
🪛 Cppcheck (2.10-2)

[performance] 366-366: Function parameter 'coins' should be passed by const reference.

(passedByValue)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e14a8bd and 0e33db2.

📒 Files selected for processing (43)
  • src/bench/coin_selection.cpp (3 hunks)
  • src/bench/wallet_balance.cpp (1 hunks)
  • src/coinjoin/client.cpp (3 hunks)
  • src/coinjoin/util.cpp (1 hunks)
  • src/interfaces/chain.h (0 hunks)
  • src/interfaces/wallet.h (0 hunks)
  • src/node/interfaces.cpp (0 hunks)
  • src/qt/test/addressbooktests.cpp (1 hunks)
  • src/qt/test/wallettests.cpp (1 hunks)
  • src/qt/transactiondesc.cpp (0 hunks)
  • src/qt/transactionrecord.cpp (1 hunks)
  • src/qt/transactionrecord.h (0 hunks)
  • src/qt/transactiontablemodel.cpp (0 hunks)
  • src/rpc/evo.cpp (1 hunks)
  • src/rpc/masternode.cpp (1 hunks)
  • src/wallet/coinjoin.cpp (5 hunks)
  • src/wallet/coinselection.cpp (13 hunks)
  • src/wallet/coinselection.h (6 hunks)
  • src/wallet/dump.cpp (1 hunks)
  • src/wallet/interfaces.cpp (2 hunks)
  • src/wallet/receive.cpp (0 hunks)
  • src/wallet/rpcwallet.cpp (4 hunks)
  • src/wallet/salvage.cpp (1 hunks)
  • src/wallet/spend.cpp (20 hunks)
  • src/wallet/spend.h (3 hunks)
  • src/wallet/test/coinjoin_tests.cpp (2 hunks)
  • src/wallet/test/coinselector_tests.cpp (20 hunks)
  • src/wallet/test/ismine_tests.cpp (10 hunks)
  • src/wallet/test/scriptpubkeyman_tests.cpp (1 hunks)
  • src/wallet/test/spend_tests.cpp (1 hunks)
  • src/wallet/test/util.cpp (1 hunks)
  • src/wallet/test/util.h (2 hunks)
  • src/wallet/test/wallet_test_fixture.cpp (1 hunks)
  • src/wallet/test/wallet_tests.cpp (18 hunks)
  • src/wallet/wallet.cpp (5 hunks)
  • src/wallet/wallet.h (3 hunks)
  • src/wallet/wallettool.cpp (5 hunks)
  • test/functional/rpc_fundrawtransaction.py (7 hunks)
  • test/functional/test_runner.py (1 hunks)
  • test/functional/wallet_basic.py (2 hunks)
  • test/functional/wallet_timelock.py (1 hunks)
  • test/functional/wallet_txn_clone.py (4 hunks)
  • test/functional/wallet_txn_doublespend.py (4 hunks)
💤 Files with no reviewable changes (7)
  • src/interfaces/wallet.h
  • src/interfaces/chain.h
  • src/qt/transactionrecord.h
  • src/qt/transactiontablemodel.cpp
  • src/qt/transactiondesc.cpp
  • src/node/interfaces.cpp
  • src/wallet/receive.cpp
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/qt/test/addressbooktests.cpp (6)
src/wallet/wallet.h (1)
  • wallet (402-402)
src/coinjoin/interfaces.cpp (2)
  • wallet (98-102)
  • wallet (98-98)
src/dummywallet.cpp (2)
  • node (29-29)
  • node (29-29)
src/walletinitinterface.h (1)
  • node (26-26)
src/wallet/init.cpp (1)
  • node (47-47)
src/wallet/walletdb.cpp (2)
  • CreateMockWalletDatabase (1187-1194)
  • CreateMockWalletDatabase (1187-1187)
src/bench/wallet_balance.cpp (3)
src/wallet/wallet.h (1)
  • wallet (402-402)
src/bitcoin-cli.cpp (1)
  • gArgs (441-447)
src/wallet/walletdb.cpp (2)
  • CreateMockWalletDatabase (1187-1194)
  • CreateMockWalletDatabase (1187-1187)
src/wallet/dump.cpp (2)
src/wallet/wallet.h (1)
  • wallet (402-402)
src/wallet/wallettool.cpp (2)
  • WalletToolReleaseWallet (24-29)
  • WalletToolReleaseWallet (24-24)
src/wallet/wallettool.cpp (1)
src/wallet/dump.cpp (2)
  • WalletToolReleaseWallet (109-114)
  • WalletToolReleaseWallet (109-109)
test/functional/wallet_timelock.py (3)
test/functional/test_framework/test_framework.py (2)
  • BitcoinTestFramework (102-1134)
  • skip_if_no_wallet (1057-1065)
test/functional/test_framework/util.py (1)
  • assert_equal (64-69)
src/wallet/rpcwallet.cpp (16)
  • getnewaddress (142-182)
  • getnewaddress (142-142)
  • send (4081-4271)
  • send (4081-4081)
  • getreceivedbyaddress (609-648)
  • getreceivedbyaddress (609-609)
  • getreceivedbylabel (651-690)
  • getreceivedbylabel (651-651)
  • listreceivedbyaddress (1102-1157)
  • listreceivedbyaddress (1102-1102)
  • listreceivedbylabel (1159-1204)
  • listreceivedbylabel (1159-1159)
  • getbalances (2389-2456)
  • getbalances (2389-2389)
  • listunspent (3082-3314)
  • listunspent (3082-3082)
test/functional/wallet_basic.py (3)
test/functional/test_framework/util.py (1)
  • find_vout_for_address (672-681)
test/functional/test_framework/test_framework.py (1)
  • sync_mempools (842-868)
src/wallet/rpcwallet.cpp (2)
  • lockunspent (2098-2236)
  • lockunspent (2098-2098)
test/functional/wallet_txn_clone.py (2)
test/functional/test_framework/util.py (1)
  • find_vout_for_address (672-681)
test/functional/wallet_txn_doublespend.py (1)
  • spend_txid (33-38)
src/wallet/test/wallet_tests.cpp (4)
src/wallet/wallet.h (1)
  • wallet (402-402)
src/wallet/walletdb.cpp (4)
  • CreateDummyWalletDatabase (1181-1184)
  • CreateDummyWalletDatabase (1181-1181)
  • CreateMockWalletDatabase (1187-1194)
  • CreateMockWalletDatabase (1187-1187)
src/wallet/test/util.cpp (2)
  • CreateSyncedWallet (18-46)
  • CreateSyncedWallet (18-18)
src/wallet/test/util.h (1)
  • CreateSyncedWallet (21-21)
🪛 Ruff (0.8.2)
test/functional/rpc_fundrawtransaction.py

61-61: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


950-950: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

🪛 Cppcheck (2.10-2)
src/wallet/spend.cpp

[performance] 366-366: Function parameter 'coins' should be passed by const reference.

(passedByValue)

🔇 Additional comments (71)
test/functional/test_runner.py (1)

376-376: Test suite expanded with wallet_timelock.py

The addition of 'wallet_timelock.py' to BASE_SCRIPTS ensures the new wallet timelock functionality is properly tested as part of the regular test suite, which is good for maintaining code quality and preventing regressions.

src/wallet/test/scriptpubkeyman_tests.cpp (1)

22-22: CWallet constructor updated with m_args parameter

This change updates the wallet construction to match the new constructor signature that takes an ArgsManager reference. This is part of a larger refactoring effort to reduce global state (gArgs) in favor of explicit dependency injection through constructor parameters, which improves testability and encapsulation.

src/qt/transactionrecord.cpp (1)

281-321: Transaction status handling simplified

The code correctly removes the transaction finality check logic that previously set statuses to OpenUntilBlock or OpenUntilDate. This simplification of the transaction status model makes the user experience more straightforward while maintaining all necessary status states for generated and regular transactions.

src/coinjoin/util.cpp (1)

127-130: Updated to use outpoints instead of vecInputCoins

The change properly updates the input selection loop to iterate over tallyItem.outpoints instead of the previous tallyItem.vecInputCoins. This aligns with the codebase-wide refactoring where CompactTallyItem now uses outpoints (vector of COutPoint) for more consistent output referencing.

src/wallet/test/spend_tests.cpp (1)

19-19: Update to add ArgsManager parameter

The change correctly adds the m_args parameter to the CreateSyncedWallet function call, aligning with the updated wallet construction pattern that now requires an ArgsManager reference.

src/qt/test/addressbooktests.cpp (1)

67-67: Updated wallet construction to include ArgsManager

The wallet constructor call now correctly includes gArgs as an additional parameter, consistent with the wallet construction updates throughout the codebase.

src/bench/wallet_balance.cpp (1)

22-22: Wallet constructor updated to include ArgsManager parameter

The CWallet constructor call now correctly includes gArgs as an additional parameter, matching the wallet construction updates across the codebase.

test/functional/wallet_basic.py (2)

18-18: Added utility function import for UTXO handling

Added import for find_vout_for_address function which will be used to improve the precision of UTXO handling in tests.


489-491: Improved UTXO locking with explicit vout discovery

The code now explicitly identifies the correct output index (vout) for the transaction before locking the unspent output, improving precision in test transaction handling.

The changes:

  1. Added synchronization of mempools to ensure transaction visibility across nodes
  2. Used find_vout_for_address to explicitly find the output index
  3. Locked the unspent output using the specific txid and vout
src/rpc/evo.cpp (1)

264-265: Improved code clarity with direct property access.

The code now uses direct property access (out.txout.scriptPubKey and out.outpoint) instead of navigating through nested pointers (out.tx->tx->vout[out.i].scriptPubKey and creating a new COutPoint). This simplification is part of the wallet output handling refactoring and makes the code more readable and less prone to errors.

src/wallet/test/ismine_tests.cpp (10)

38-38: Updated CWallet constructor to include ArgsManager parameter.

The CWallet constructor now requires a reference to ArgsManager (m_args) as part of a broader refactor to localize argument access in the wallet code.


55-55: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


72-72: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


89-89: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


106-106: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


130-130: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


148-148: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


179-179: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


200-200: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.


214-214: Updated CWallet constructor to include ArgsManager parameter.

Same change as above - providing required ArgsManager reference to CWallet constructor.

src/wallet/dump.cpp (1)

202-202: Updated CWallet constructor to include ArgsManager parameter.

The constructor call now includes gArgs to match the updated CWallet constructor signature, which now requires an ArgsManager reference. This is part of a broader refactor to localize argument access in the wallet code.

src/rpc/masternode.cpp (1)

143-143: Simplified transaction output access.

The code now directly uses out.outpoint instead of going through an intermediary method with out.GetInputCoin().outpoint. This change is part of the broader refactoring where wallet transaction outputs use the COutput class members directly, making the code cleaner and more efficient.

src/wallet/salvage.cpp (1)

136-136: Updated CWallet constructor call to include the gArgs parameter

This change correctly implements the wallet constructor signature update as part of the backporting effort, ensuring the wallet has explicit access to the global arguments.

src/wallet/test/wallet_test_fixture.cpp (1)

13-13: LGTM: Added m_args parameter to wallet constructor

This change correctly updates the test fixture to use the updated CWallet constructor signature, maintaining consistency with the refactored wallet code.

src/wallet/test/util.cpp (1)

18-18: Properly updated CreateSyncedWallet to use ArgsManager parameter

The function signature and implementation have been correctly modified to include and pass the ArgsManager reference to the wallet constructor, consistent with the broader refactoring pattern.

Also applies to: 20-20

src/wallet/test/util.h (1)

10-10: Added ArgsManager forward declaration and updated function signature

These changes correctly maintain consistency between the header and implementation by adding the necessary forward declaration and updating the function signature to match the implementation in util.cpp.

Also applies to: 21-21

src/qt/test/wallettests.cpp (1)

113-113: Constructor update looks good

The wallet constructor now receives gArgs as an additional parameter, aligning with the broader refactor to ensure wallet instances have access to argument management.

test/functional/wallet_txn_clone.py (4)

10-10: Appropriately importing helper function

Importing find_vout_for_address allows more precise identification of outputs for spending.


35-40: Well-implemented transaction helper

The spend_txid helper function provides explicit control over spending specific outputs, which aligns with the enhanced output management pattern being introduced in the PR.


53-53: Good use of explicit UTXO locking

The output is now explicitly locked before spending, providing better control over UTXO selection and reducing ambiguity in test behavior.


66-67: Improved transaction construction approach

Switching from simple wallet sends to explicitly spending identified outputs provides better test control and more accurately tests the transaction cloning behavior.

src/wallet/wallettool.cpp (2)

59-59: Function signature updated correctly

The MakeWallet function now accepts the ArgsManager parameter, consistent with other wallet-related functions.


70-70: Wallet constructor updated properly

The constructor call now passes the ArgsManager argument, ensuring wallet instances have access to configuration parameters.

src/wallet/interfaces.cpp (2)

132-141: Good addition of COutput-based constructor

This new overloaded function elegantly constructs a WalletTxOut directly from a COutput object, simplifying code and improving encapsulation by leveraging the richer COutput class.


472-473: Simplified coin listing code

Using the new MakeWalletTxOut overload with COutput directly streamlines the code, removing the need to extract components from the output.

test/functional/wallet_txn_doublespend.py (4)

12-12: Imported new utility function for improved UTXO handling.

The addition of find_vout_for_address import enhances the test's capability to locate specific outputs by address, which is needed for the new explicit UTXO handling approach.


33-38: Well-structured helper method for explicit UTXO spending.

This new spend_txid method implements a clear pattern for creating, funding, signing, and sending raw transactions that spend specific outputs. This improves test clarity and maintainability by encapsulating the raw transaction creation workflow.


58-58: Good use of explicit UTXO locking.

Explicitly locking the unspent output with lockunspent(False, ...) ensures deterministic UTXO selection. This change aligns with the modernized approach to wallet UTXO handling in the updated codebase.


89-90: Improved transaction control using explicit output selection.

Replacing wallet-level sendtoaddress calls with explicit spend_txid calls provides more precise control over which UTXOs are spent, making the test's double-spend scenario more deterministic and reliable.

src/wallet/test/coinjoin_tests.cpp (4)

133-134: Updated CWallet constructor call to include ArgsManager reference.

The constructor now passes m_args to the CWallet constructor, aligning with the broader change to include ArgsManager in wallet instantiation across the codebase.


203-203: Changed loop counter type for improved type safety.

The loop index was changed from size_t to uint32_t to better match the data type of transaction output indices, which are typically 32-bit unsigned integers.


208-208: Refactored to use COutPoint for output representation.

The code now uses outpoints.emplace_back(COutPoint{tx->GetHash(), n}) instead of the previous approach with vecInputCoins. This aligns with the broader refactoring to use COutPoint objects for representing transaction outputs throughout the wallet code.


212-212: Updated assertion to check outpoints size.

The assertion was updated to verify tallyItem.outpoints.size() matches vecAmounts.size(), reflecting the change from vecInputCoins to outpoints as the container for transaction outputs.

src/wallet/wallet.h (3)

131-131: Refactored CompactTallyItem to use COutPoint directly.

Changed std::vector<CInputCoin> vecInputCoins to std::vector<COutPoint> outpoints, simplifying the wallet's internal representation of outputs. This change is part of the broader refactoring to replace the legacy CInputCoin class with more focused data structures.


348-350: Added ArgsManager reference to CWallet class.

Added const ArgsManager& m_args as a private member to provide localized access to application arguments. This improves encapsulation by removing direct dependencies on global argument variables.


442-444: Updated CWallet constructor to accept ArgsManager.

Modified the constructor signature to include const ArgsManager& args parameter and initialize the new m_args member. This enables better testing and dependency injection by explicitly passing the arguments rather than relying on globals.

test/functional/wallet_timelock.py (5)

1-8: Well-structured test file with proper licensing and imports.

The file has appropriate licensing headers and imports only the necessary components from the test framework. This follows the established pattern for Bitcoin Core functional tests.


10-16: Clear test class definition with proper setup.

The WalletLocktimeTest class correctly inherits from BitcoinTestFramework and implements the required methods:

  • set_test_params configures a single node for the test
  • skip_test_if_missing_module ensures the test is skipped if wallet support is not available

17-32: Effective timelock transaction setup.

The test:

  1. Gets the current block's median time past (MTP)
  2. Creates a new address with a Unicode label
  3. Sends funds with a locktime just before the current MTP
  4. Confirms the transaction in a new block

This creates the necessary conditions to test locktime handling in the wallet.


33-46: Comprehensive verification of timelock behavior.

The test verifies that changing the node's mocktime to before the transaction's locktime does not affect the wallet's view of confirmed transactions. It checks multiple wallet functions:

  • getreceivedbyaddress
  • getreceivedbylabel
  • listreceivedbyaddress
  • listreceivedbylabel
  • getbalances
  • listunspent

This thoroughly validates that transaction finality in the wallet is properly based on confirmation status, not on the transaction's locktime relative to the current time.


49-51: Standard test execution boilerplate.

The file properly implements the standard Python pattern for allowing the test to be run directly or as part of the test suite.

src/coinjoin/client.cpp (4)

1471-1472: Refactor: Updated to use outpoints instead of vecInputCoins

This change is part of a broader refactoring where CompactTallyItem now uses outpoints instead of vecInputCoins to track inputs, maintaining the same logic for checking if this is a single input coin.


1565-1565: Code simplification: Direct access to txout property

This change simplifies the code by directly accessing the output's txout property instead of using the nested pointer access pattern output.tx->tx->vout[output.i], making the code more readable and less error-prone.


1568-1568: Code simplification: Direct access to outpoint properties

This change simplifies the code by directly accessing the output's outpoint properties instead of using output.tx->GetHash() and output.i, aligning with the broader refactoring to use the richer COutput class.


1639-1640: Refactor: Updated to use outpoints instead of vecInputCoins

Similar to previous changes, this replaces the check on vecInputCoins.size() with outpoints.size() to maintain compatibility with the refactored CompactTallyItem structure.

src/wallet/coinjoin.cpp (6)

71-72: Refactoring to direct field access looks cleaner.

The code now directly accesses the COutPoint and CTxOut fields instead of using nested accessors, making it more readable and maintainable.


77-78: Clean field access improvement.

Using direct field references from the COutput structure improves code clarity.


98-98: Good modernization using effective_value.

The comparison now uses the effective_value property of COutput objects which aligns with the refactoring from CInputCoin to COutput across the codebase.


117-117: Consistent direct field access pattern.

This follows the pattern established throughout the PR of directly accessing txout's nValue property.


172-172: Updated interface using outpoints collection.

The code now checks against outpoints.size() instead of the previous vecInputCoins implementation, maintaining the same functionality but using the modernized data structure.


194-194: Consistent data structure update.

Using outpoints vector instead of the previous vecInputCoins collection, which aligns with the broader refactoring of wallet output handling.

src/wallet/wallet.cpp (5)

953-953: Replaced global argument access with instance member.

Now using m_args instance member instead of global gArgs for accessing command-line arguments, improving encapsulation.


2721-2721: Updated wallet instantiation to use ArgsManager parameter.

The wallet constructor now accepts an ArgsManager reference which gets stored as a member variable, enabling the removal of global gArgs references throughout the code.


2957-2961: Consistent local args parameter usage.

Using the local args parameter instead of global gArgs for accessing the consolidate fee rate setting, following the pattern established throughout the PR.


3374-3374: Localized instantsendnotify argument access.

Replaced global argument access with wallet instance member m_args, consistent with other similar changes in this PR.


3551-3551: Improved wallet backups configuration access.

Using the instance member m_args instead of global gArgs for accessing the wallet backup configuration setting, completing the consistent pattern of localized argument access.

src/wallet/test/coinselector_tests.cpp (1)

32-38: Potential overflow / concurrency issue with nextLockTime

nextLockTime is a non-atomic signed int.
After ~2 billion coins have been added across all tests the counter will wrap, re-creating txids.
While that seems far-fetched, switching to an unsigned 64-bit (or even a monotonic std::atomic_uint64_t) costs nothing and removes the edge-case entirely.

-static int nextLockTime = 0;
+static std::atomic<uint64_t> nextLockTime{0};

(You may need to #include <atomic>.)

src/wallet/coinselection.cpp (1)

424-445: Assertion may trigger for subtract_fee_outputs selections

When change_cost == 0 the function asserts
selected_effective_value >= target.

If any input came from a group with m_subtract_fee_outputs==true, its
effective_value equals the real value, while target still embeds
fees. In that borderline case the inequality can be violated, firing
the assert in valid scenarios.

Consider replacing the assert with a runtime check (and early return of
MAX_MONEY) or using selected_real_value when !use_effective_value.

src/wallet/test/wallet_tests.cpp (3)

116-116: Constructor parameter update looks good

All CWallet constructors now properly include the m_args parameter, which is consistent with the wallet refactoring to explicitly pass ArgsManager instances rather than relying on global arguments.

Also applies to: 137-137, 166-166, 193-193, 232-232, 298-298, 320-320, 353-353, 528-528, 630-630, 640-640, 892-892, 989-989


607-607: Improved COutPoint usage

Replaced construction of new COutPoint objects with direct usage of existing coin.outpoint members. This is more efficient and aligns with the wallet refactoring that favors direct COutPoint usage.

Also applies to: 1297-1298, 1355-1355


1423-1423: Updated member variable reference

The member vecInputCoins in CompactTallyItem was properly renamed to outpoints to reflect the shift from coin objects to outpoints, matching changes in related wallet code.

Also applies to: 1466-1467

Comment on lines +58 to +62
if outputtype in ["legacy", "p2pkh", "pkh"]:
prefixes = ["pkh(", "sh(multi("]
else:
assert False, f"Unknown output type {outputtype}"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace assert False with a proper exception
assert statements are stripped when the interpreter is started with the -O flag, potentially turning this branch into a no-op.
Use an explicit exception to guarantee the error is raised in every run:

-        else:
-            assert False, f"Unknown output type {outputtype}"
+        else:
+            raise ValueError(f"Unknown output type {outputtype}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if outputtype in ["legacy", "p2pkh", "pkh"]:
prefixes = ["pkh(", "sh(multi("]
else:
assert False, f"Unknown output type {outputtype}"
if outputtype in ["legacy", "p2pkh", "pkh"]:
prefixes = ["pkh(", "sh(multi("]
else:
raise ValueError(f"Unknown output type {outputtype}")
🧰 Tools
🪛 Ruff (0.8.2)

61-61: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)

Comment on lines +75 to +80
to_keep = []
if self.watchonly_txid is not None and self.watchonly_vout is not None:
to_keep.append({"txid": self.watchonly_txid, "vout": self.watchonly_vout})
wallet.lockunspent(True)
wallet.lockunspent(False, to_keep)

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid unnecessary RPC when no UTXOs must stay locked
Calling lockunspent(False, []) fails with “Invalid parameter” on some daemon versions.
Guard the second call:

-        wallet.lockunspent(True)
-        wallet.lockunspent(False, to_keep)
+        wallet.lockunspent(True)
+        if to_keep:
+            wallet.lockunspent(False, to_keep)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
to_keep = []
if self.watchonly_txid is not None and self.watchonly_vout is not None:
to_keep.append({"txid": self.watchonly_txid, "vout": self.watchonly_vout})
wallet.lockunspent(True)
wallet.lockunspent(False, to_keep)
to_keep = []
if self.watchonly_txid is not None and self.watchonly_vout is not None:
to_keep.append({"txid": self.watchonly_txid, "vout": self.watchonly_vout})
wallet.lockunspent(True)
if to_keep:
wallet.lockunspent(False, to_keep)

Comment on lines 136 to 145
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
for (auto& coin : coins) {
static_groups.emplace_back();
static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
return static_groups;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid static return buffer in GroupCoins()

GroupCoins() re-uses a static std::vector<OutputGroup> as its return value.
This is fragile for two reasons:

  1. The function is not thread-safe: parallel test cases or future multi-threaded callers will race on the shared buffer.
  2. Nested/ chained calls overwrite the same buffer, leading to subtle aliasing bugs (e.g. SelectCoinsBnB(GroupCoins(...), ...) inside another GroupCoins() scope).

Returning a fresh vector (move-constructed) is cheap and eliminates the risk.

-inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
+inline std::vector<OutputGroup> GroupCoins(const std::vector<COutput>& coins)
 {
-    static std::vector<OutputGroup> static_groups;
-    static_groups.clear();
-    ...
-    return static_groups;
+    std::vector<OutputGroup> groups;
+    groups.reserve(coins.size());
+    for (const auto& coin : coins) {
+        groups.emplace_back();
+        groups.back().Insert(coin, /*ancestors=*/0, /*descendants=*/0,
+                             /*positive_only=*/false);
+    }
+    return groups;        // NRVO / move-elision
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
for (auto& coin : coins) {
static_groups.emplace_back();
static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
return static_groups;
}
inline std::vector<OutputGroup> GroupCoins(const std::vector<COutput>& coins)
{
std::vector<OutputGroup> groups;
groups.reserve(coins.size());
for (const auto& coin : coins) {
groups.emplace_back();
groups.back().Insert(coin, /*ancestors=*/0, /*descendants=*/0,
/*positive_only=*/false);
}
return groups; // NRVO / move-elision
}

Comment on lines +224 to +232
const CAmount m_target;
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
bool m_use_effective{false};
/** The computed waste */
std::optional<CAmount> m_waste;

public:
explicit SelectionResult(const CAmount target)
: m_target(target) {}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

m_use_effective may be wrong when groups with mixed m_subtract_fee_outputs flags are combined

SelectionResult::AddInput() (see cpp file) blindly sets

m_use_effective = !group.m_subtract_fee_outputs;

for every added group, so the last inserted group dictates whether
effective or real values are used in later waste calculations. If any
previous group had a differing flag the result becomes inconsistent.

A safer approach is to disable effective-value mode as soon as a single
group requires real values:

-    m_use_effective = !group.m_subtract_fee_outputs;
+    if (group.m_subtract_fee_outputs) {
+        m_use_effective = false;
+    }

(or initialise m_use_effective = true once in the constructor and turn
it off cumulatively).

Failing to fix this could lead to incorrect fee/waste computation when
coin control mixes outputs with and without “subtract-fee-from-output”.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +227 to +231
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint)
{
AssertLockHeld(wallet.cs_wallet);
return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Null-pointer dereference risk in new FindNonChangeParentOutput overload

wallet.GetWalletTx(outpoint.hash) can legally return nullptr (e.g. if the outpoint
belongs to an external wallet but was manually locked).
Dereferencing immediately with ->tx results in undefined behaviour.

Add an explicit check (or assert) before dereferencing:

const CWalletTx* pwtx = wallet.GetWalletTx(outpoint.hash);
if (pwtx == nullptr) {
    throw std::runtime_error("Unknown outpoint in FindNonChangeParentOutput");
}
return FindNonChangeParentOutput(wallet, *pwtx->tx, outpoint.n);

This prevents a potential crash when the wallet state is inconsistent.
[error_handling]

PastaPastaPasta
PastaPastaPasta previously approved these changes May 9, 2025
Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

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

utACK 0e33db2

Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

There is a regression in AttemptSelection(), pls consider e34b8f5 + 877c690 (or maybe there is a simpler way, feel free to suggest it).

Copy link

@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: 2

♻️ Duplicate comments (5)
test/functional/rpc_fundrawtransaction.py (2)

58-61: ⚠️ Potential issue

Fix the assert False pattern

Replace assert False with a proper exception to ensure error handling works even when Python's optimization flag -O is used (which removes assert statements).

-        else:
-            assert False, f"Unknown output type {outputtype}"
+        else:
+            raise ValueError(f"Unknown output type {outputtype}")
🧰 Tools
🪛 Ruff (0.8.2)

61-61: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


75-80: 🛠️ Refactor suggestion

Guard against empty to_keep list

Calling lockunspent(False, []) fails with "Invalid parameter" on some daemon versions. Protect the call with a check:

-        wallet.lockunspent(True)
-        wallet.lockunspent(False, to_keep)
+        wallet.lockunspent(True)
+        if to_keep:
+            wallet.lockunspent(False, to_keep)
src/wallet/test/coinselector_tests.cpp (1)

136-145: 🛠️ Refactor suggestion

Avoid static return buffer in GroupCoins()

GroupCoins() re-uses a static std::vector<OutputGroup> as its return value.
This is fragile for two reasons:

  1. The function is not thread-safe: parallel test cases or future multi-threaded callers will race on the shared buffer.
  2. Nested/chained calls overwrite the same buffer, leading to subtle aliasing bugs.
-inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
+inline std::vector<OutputGroup> GroupCoins(const std::vector<COutput>& coins)
 {
-    static std::vector<OutputGroup> static_groups;
-    static_groups.clear();
+    std::vector<OutputGroup> groups;
+    groups.reserve(coins.size());
     for (auto& coin : coins) {
-        static_groups.emplace_back();
-        static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
+        groups.emplace_back();
+        groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
     }
-    return static_groups;
+    return groups;
 }
src/wallet/spend.cpp (2)

227-231: Unchecked nullptr dereference risk is still present

The overload continues to dereference wallet.GetWalletTx(outpoint.hash) without verifying the pointer.
The issue was raised in a previous review and remains unresolved – the wallet can legitimately not know about the referenced tx, resulting in undefined behaviour and a crash.

 const CWalletTx* pwtx = wallet.GetWalletTx(outpoint.hash);
-if (pwtx == nullptr) {
-    throw std::runtime_error("Unknown outpoint in FindNonChangeParentOutput");
-}
-return FindNonChangeParentOutput(wallet, *pwtx->tx, outpoint.n);
+if (pwtx == nullptr) {
+    throw std::runtime_error("Unknown outpoint in FindNonChangeParentOutput");
+}
+return FindNonChangeParentOutput(wallet, *pwtx->tx, outpoint.n);

421-436: Preset-input duplication bug persists

result.AddInput(preset_inputs) is executed inside the loop while preset_inputs
grows, so the first input is added N times, the second N-1, etc., leading to duplicate
vin entries and invalid transactions. Move the call after the loop – once.

 for (const COutput& out : vCoins) {
     if (!out.spendable) continue;
     preset_inputs.Insert(out, 0, 0, false);
-    result.AddInput(preset_inputs);
     if (!coin_control.fRequireAllInputs && result.GetSelectedValue() >= nTargetValue) {
-        return result;
+        break;
     }
 }
+result.AddInput(preset_inputs);
 if (result.GetSelectedValue() < nTargetValue) return std::nullopt;
 return result;
🧹 Nitpick comments (2)
test/functional/rpc_fundrawtransaction.py (1)

950-950: Remove unused loop variable

The loop variable i is not used inside the loop body. Rename it to indicate it's unused.

-for i in range(0, 2):
+for _ in range(0, 2):
🧰 Tools
🪛 Ruff (0.8.2)

950-950: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

src/wallet/spend.cpp (1)

366-378: Pass UTXO list by const-reference & avoid no-op solver calls

  1. coins is copied on every call – the container can be several KB.
  2. positive_groups is deliberately cleared, yet BnB/SRD are still invoked with an empty vector – redundant work and confusing intent.
-std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue,
-                                                const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
+std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue,
+                                                const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& coins,
                                                 const CoinSelectionParams& coin_selection_params, CoinType nCoinType)
 {
-    // Vector of results. We will choose the best one based on waste.
+    // Vector of results. We will choose the best one based on waste.
     std::vector<SelectionResult> results;
 
-    std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
-    positive_groups.clear(); // Cleared to skip BnB and SRD as they're unaware of mixed coins
-    if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
+    // Dash currently disables BnB/SRD for mixed coins.  Skip building the groups
+    // and the subsequent solver calls until mixed-coin support is available.
+    std::vector<OutputGroup> positive_groups; // left empty intentionally
+    if (!positive_groups.empty() &&
+        (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}))
+    {
         bnb_result->ComputeAndSetWaste(CAmount(0));
         results.push_back(*bnb_result);
     }

This eliminates an unnecessary copy and clarifies that BnB/SRD are intentionally disabled.

🧰 Tools
🪛 Cppcheck (2.10-2)

[performance] 366-366: Function parameter 'coins' should be passed by const reference.

(passedByValue)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e33db2 and e901183.

📒 Files selected for processing (43)
  • src/bench/coin_selection.cpp (3 hunks)
  • src/bench/wallet_balance.cpp (1 hunks)
  • src/coinjoin/client.cpp (3 hunks)
  • src/coinjoin/util.cpp (1 hunks)
  • src/interfaces/chain.h (0 hunks)
  • src/interfaces/wallet.h (0 hunks)
  • src/node/interfaces.cpp (0 hunks)
  • src/qt/test/addressbooktests.cpp (1 hunks)
  • src/qt/test/wallettests.cpp (1 hunks)
  • src/qt/transactiondesc.cpp (0 hunks)
  • src/qt/transactionrecord.cpp (1 hunks)
  • src/qt/transactionrecord.h (0 hunks)
  • src/qt/transactiontablemodel.cpp (0 hunks)
  • src/rpc/evo.cpp (1 hunks)
  • src/rpc/masternode.cpp (1 hunks)
  • src/wallet/coinjoin.cpp (5 hunks)
  • src/wallet/coinselection.cpp (13 hunks)
  • src/wallet/coinselection.h (6 hunks)
  • src/wallet/dump.cpp (1 hunks)
  • src/wallet/interfaces.cpp (2 hunks)
  • src/wallet/receive.cpp (0 hunks)
  • src/wallet/rpcwallet.cpp (4 hunks)
  • src/wallet/salvage.cpp (1 hunks)
  • src/wallet/spend.cpp (20 hunks)
  • src/wallet/spend.h (3 hunks)
  • src/wallet/test/coinjoin_tests.cpp (2 hunks)
  • src/wallet/test/coinselector_tests.cpp (20 hunks)
  • src/wallet/test/ismine_tests.cpp (10 hunks)
  • src/wallet/test/scriptpubkeyman_tests.cpp (1 hunks)
  • src/wallet/test/spend_tests.cpp (1 hunks)
  • src/wallet/test/util.cpp (1 hunks)
  • src/wallet/test/util.h (2 hunks)
  • src/wallet/test/wallet_test_fixture.cpp (1 hunks)
  • src/wallet/test/wallet_tests.cpp (18 hunks)
  • src/wallet/wallet.cpp (5 hunks)
  • src/wallet/wallet.h (3 hunks)
  • src/wallet/wallettool.cpp (5 hunks)
  • test/functional/rpc_fundrawtransaction.py (7 hunks)
  • test/functional/test_runner.py (1 hunks)
  • test/functional/wallet_basic.py (2 hunks)
  • test/functional/wallet_timelock.py (1 hunks)
  • test/functional/wallet_txn_clone.py (4 hunks)
  • test/functional/wallet_txn_doublespend.py (4 hunks)
💤 Files with no reviewable changes (7)
  • src/node/interfaces.cpp
  • src/interfaces/wallet.h
  • src/interfaces/chain.h
  • src/qt/transactionrecord.h
  • src/wallet/receive.cpp
  • src/qt/transactiondesc.cpp
  • src/qt/transactiontablemodel.cpp
✅ Files skipped from review due to trivial changes (1)
  • test/functional/test_runner.py
🚧 Files skipped from review as they are similar to previous changes (30)
  • src/rpc/masternode.cpp
  • src/wallet/dump.cpp
  • src/wallet/test/ismine_tests.cpp
  • src/wallet/test/spend_tests.cpp
  • src/wallet/test/scriptpubkeyman_tests.cpp
  • src/bench/wallet_balance.cpp
  • src/wallet/test/util.cpp
  • src/wallet/salvage.cpp
  • src/qt/transactionrecord.cpp
  • src/qt/test/wallettests.cpp
  • src/coinjoin/util.cpp
  • src/wallet/test/wallet_test_fixture.cpp
  • test/functional/wallet_basic.py
  • src/wallet/wallettool.cpp
  • src/qt/test/addressbooktests.cpp
  • src/wallet/interfaces.cpp
  • test/functional/wallet_txn_doublespend.py
  • test/functional/wallet_timelock.py
  • src/rpc/evo.cpp
  • test/functional/wallet_txn_clone.py
  • src/wallet/test/coinjoin_tests.cpp
  • src/wallet/test/util.h
  • src/bench/coin_selection.cpp
  • src/wallet/wallet.h
  • src/wallet/coinjoin.cpp
  • src/wallet/rpcwallet.cpp
  • src/wallet/spend.h
  • src/coinjoin/client.cpp
  • src/wallet/wallet.cpp
  • src/wallet/test/wallet_tests.cpp
🧰 Additional context used
🪛 Ruff (0.8.2)
test/functional/rpc_fundrawtransaction.py

61-61: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


950-950: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

🪛 Cppcheck (2.10-2)
src/wallet/spend.cpp

[performance] 366-366: Function parameter 'coins' should be passed by const reference.

(passedByValue)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: linux64_multiprocess-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
🔇 Additional comments (40)
test/functional/rpc_fundrawtransaction.py (10)

54-70: LGTM: New lock_outputs_type helper function

This helper function improves code organization by centralizing the logic for locking specific output types, helping to restrict coin selection to certain UTXO types during fee tests.

🧰 Tools
🪛 Ruff (0.8.2)

61-61: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)


71-74: LGTM: New unlock_utxos helper function

This helper function centralizes the unlocking logic, making it consistent across the test cases while preserving watchonly UTXOs.


82-84: LGTM: Initialize watchonly tracking variables

Good initialization of tracking variables for watchonly outputs at the start of the test.


410-411: LGTM: Proper UTXO locking and unlocking in fee test

The test now appropriately uses the new helper functions to restrict coin selection to specific UTXO types, improving test isolation and preventing interference between test cases.

Also applies to: 431-432


435-436: LGTM: Proper UTXO locking and unlocking in p2sh fee test

Appropriately using the new helpers for coin selection restriction.

Also applies to: 458-459


463-464: LGTM: Proper UTXO locking and unlocking in 4of5 fee test

Appropriately using the helpers for coin selection control.

Also applies to: 502-503


570-574: LGTM: Improved input selection in locked wallet test

The changes improve how inputs and total value are calculated, providing a more robust approach by using all available inputs and calculating the total value dynamically.


949-955: LGTM: Improved unsafe input creation and testing

Better approach to creating and tracking unsafe inputs:

  1. Creates two 5 DASH inputs in a loop
  2. Finds the correct output index for each input
  3. Stores inputs in an array for later verification
🧰 Tools
🪛 Ruff (0.8.2)

950-950: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


957-958: LGTM: Better verification of unsafe inputs in transactions

The test now properly verifies that specific inputs are included in the transaction and uses testmempoolaccept to check validity without broadcasting, which is more efficient.

Also applies to: 963-966


969-974: LGTM: Improved post-confirmation test for previously unsafe inputs

The test now properly checks that previously unsafe inputs are included in the transaction after confirmation and verifies acceptance with testmempoolaccept.

src/wallet/test/coinselector_tests.cpp (16)

32-33: LGTM: Updated CoinSet typedef to use COutput

Properly updated the typedef to use COutput instead of CInputCoin, in line with the API modernization.


37-38: LGTM: Added nextLockTime to ensure unique transaction hashes

Good addition of a static variable to ensure each test transaction gets a unique hash, preventing collisions during testing.


39-46: LGTM: Updated add_coin function to use COutput

The function now properly constructs COutput objects instead of CInputCoin, consistent with the updated coin selection API.


48-58: LGTM: New add_coin overload for SelectionResult

This helper function makes it easy to add coins directly to a SelectionResult, improving test code organization.


60-71: LGTM: Updated add_coin for CoinSet

Properly updated to use COutput instead of CInputCoin with correct initialization of fee fields.


73-93: LGTM: Updated add_coin for wallet tests

Now correctly uses the new COutput constructor with additional metadata when adding coins to the wallet.


95-112: LGTM: New EquivalentResult function

Good addition of a helper function to check if SelectionResult objects contain inputs with equivalent values, which is useful for testing selection algorithms.


114-122: LGTM: New EqualResult function

Good addition of a helper function to check if SelectionResult objects contain exactly the same inputs, useful for testing determinism.


147-150: LGTM: Updated KnapsackSolver wrapper

Properly updated to return std::optional and take a FastRandomContext for randomness.


152-168: LGTM: Updated KnapsackGroupOutputs

Now correctly creates and passes a CoinSelectionParams object with the required FastRandomContext.


172-219: LGTM: Updated BnB tests to use optional return type

Tests now properly check for the presence of a result and verify selection properties through the SelectionResult interface.


309-330: LGTM: Updated effective value tests to use ArgsManager

The test now properly passes m_args to the CWallet constructor to match the updated wallet API.


332-351: LGTM: Updated coin control tests

The wallet creation and testing code now properly uses the ArgsManager parameter and the updated SelectCoins API.


353-663: LGTM: Comprehensive update of Knapsack tests

All the tests have been properly updated to use std::optional results and verify selection properties through the new interfaces.


665-688: LGTM: Updated ApproximateBestSubset test

The test now properly uses FastRandomContext and verifies the optional result.


687-738: LGTM: Updated SelectCoins test

The test now uses the ArgsManager and updated coin selection parameters with FastRandomContext.

src/wallet/coinselection.h (6)

21-86: LGTM: Rich COutput class replacing CInputCoin

Excellent refactoring to replace the limited CInputCoin with a more comprehensive COutput class that includes:

  • Transaction metadata (depth, safety, etc.)
  • Spendability and solvability flags
  • Ownership information
  • Fee calculation fields

This provides a more complete picture of each output for better coin selection decisions.


89-129: LGTM: Updated CoinSelectionParams with explicit randomness

Good improvement to pass FastRandomContext explicitly, ensuring deterministic behavior in tests while allowing genuine randomness in production.


216-255: LGTM: New SelectionResult class with comprehensive API

Excellent addition of a dedicated class to encapsulate selection results, providing methods to:

  • Track and retrieve selected inputs
  • Calculate and retrieve waste
  • Get shuffled inputs for transaction creation
  • Support comparison operations

This improves encapsulation and code organization.


226-227: Potential issue with m_use_effective flag

The m_use_effective flag could be inconsistent when groups with different m_subtract_fee_outputs flags are combined. In the corresponding implementation (SelectionResult::AddInput), this flag is blindly overwritten by each group added, so the last group's setting prevails.

Consider updating the logic to only allow disabling effective value mode, not re-enabling it:

// In cpp file:
-    m_use_effective = !group.m_subtract_fee_outputs;
+    if (group.m_subtract_fee_outputs) {
+        m_use_effective = false;
+    }

Can you verify whether the implementation of AddInput in the cpp file has already addressed this concern?


256-265: LGTM: New SelectCoinsSRD algorithm

Good addition of the Single Random Draw coin selection algorithm, which provides a simple alternative that might be useful in specific cases.


267-268: LGTM: Updated KnapsackSolver signature

Properly updated to return std::optional for consistent error handling across all selection algorithms.

src/wallet/coinselection.cpp (8)

63-170: LGTM: Updated SelectCoinsBnB implementation

The function has been properly refactored to:

  1. Return std::optional instead of modifying output parameters
  2. Use the new SelectionResult class to track selections
  3. Return std::nullopt on failure cases
  4. Directly return the result on success

This creates a more robust and easier to use API.


172-194: LGTM: New SelectCoinsSRD implementation

Clean implementation of the Single Random Draw algorithm that:

  1. Shuffles indexes to avoid bias
  2. Adds inputs until target is reached
  3. Returns std::nullopt if insufficient funds
  4. Uses Assume() for checks that should always succeed

This provides a simple alternative selection algorithm.


196-197: LGTM: Updated ApproximateBestSubset signature

Properly updated to take a FastRandomContext reference for randomness.


267-377: LGTM: Updated KnapsackSolver implementation

The function has been properly refactored to:

  1. Return std::optional instead of modifying output parameters
  2. Use the SelectionResult class to track selections
  3. Return early with the result when a solution is found
  4. Return std::nullopt on failure cases

This creates a more robust API.


385-415: LGTM: Updated OutputGroup::Insert implementation

The function now properly works with COutput objects and correctly:

  1. Calculates effective values
  2. Updates group properties
  3. Handles fee calculations

This aligns with the updated data structure.


429-454: LGTM: Updated GetSelectionWaste function

The function now operates on std::set instead of CInputCoin and correctly calculates waste based on effective values and fees.


456-501: LGTM: SelectionResult method implementations

Good implementation of all SelectionResult methods with proper:

  1. Waste calculation
  2. Input management
  3. Value aggregation
  4. Comparison operations
  5. Input shuffling

This completes the API needed for comprehensive coin selection.


503-506: LGTM: COutput::ToString implementation

Good addition of a debug formatting method for COutput objects.

Comment on lines +477 to +481
void SelectionResult::AddInput(const OutputGroup& group)
{
util::insert(m_selected_inputs, group.m_outputs);
m_use_effective = !group.m_subtract_fee_outputs;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Verify m_use_effective flag handling

In the AddInput method, m_use_effective is overwritten by each group:

m_use_effective = !group.m_subtract_fee_outputs;

This means the last group added determines whether effective values are used, potentially causing inconsistency if groups with different fee subtraction settings are combined.

Consider only allowing disabling effective value mode:

-    m_use_effective = !group.m_subtract_fee_outputs;
+    if (group.m_subtract_fee_outputs) {
+        m_use_effective = false;
+    }

Comment on lines 171 to 176
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));

vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Skip outputs whose spend-size cannot be estimated

GetTxSpendSize returns -1 when the wallet cannot sign the given script type.
Passing that sentinel value into COutput yields a negative input_bytes, corrupts the effective-value calculation, and may later crash on size under-flow.

 int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
-if (input_bytes <= 0) {
-    continue;           // skip unsignable or unknown scripts
-}
 vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i),
                     nDepth, input_bytes,
                     spendable, solvable, safeTx,
                     wtx.GetTxTime(), tx_from_me);

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

LGTM, utACK e901183

Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

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

utACK e901183

@PastaPastaPasta PastaPastaPasta merged commit 06de2b0 into dashpay:develop May 12, 2025
63 of 64 checks passed
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.

3 participants