-
Notifications
You must be signed in to change notification settings - Fork 1.2k
backport: merge bitcoin#17526, #22928, #22019, #23762, #24067, #24091, #24560, partial bitcoin#17211 (wallet backports: part 4) #6654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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  ## 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
WalkthroughThis 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 Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🔭 Outside diff range comments (1)
src/wallet/spend.cpp (1)
370-401
: 🛠️ Refactor suggestionUnnecessary 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 theif
condition is alwaysfalse
, 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(...)) { ... } #endifor 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 implieswtx.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 thelistunspent
loop
scriptPubKey
is recreated for every iteration even whenavoid_reuse
isfalse
.
This is tiny, but you could scope it inside theif (avoid_reuse …)
block to save a hash lookup.
IsSpentKey()
is evaluated only ifavoid_reuse
istrue
(short-circuit), good.
However, a single call per UTXO is still expensive when thousands of outputs are returned.
Consider caching the result peroutpoint
if this becomes a hotspot.The push-order of JSON keys is inconsistent with earlier versions (
spendable/solvable
came beforeamount/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.If
ancestorcount
is fetched,ancestorsize
andancestorfees
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 bestd::atomic
(or at leaststatic thread_local
)
The benchmark uses a file-scope‐staticint nextLockTime
that is incremented every timeaddCoin()
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 bodyRename 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 outliverand
, 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 inAttemptSelection
coins
is passed by value, so every call copies the fullstd::vector<COutput>
.
AttemptSelection
never mutates the argument—it only forwards it—so aconst&
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
📒 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.pyThe 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 parameterThis 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 simplifiedThe code correctly removes the transaction finality check logic that previously set statuses to
OpenUntilBlock
orOpenUntilDate
. 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 vecInputCoinsThe change properly updates the input selection loop to iterate over
tallyItem.outpoints
instead of the previoustallyItem.vecInputCoins
. This aligns with the codebase-wide refactoring whereCompactTallyItem
now usesoutpoints
(vector of COutPoint) for more consistent output referencing.src/wallet/test/spend_tests.cpp (1)
19-19
: Update to add ArgsManager parameterThe change correctly adds the
m_args
parameter to theCreateSyncedWallet
function call, aligning with the updated wallet construction pattern that now requires anArgsManager
reference.src/qt/test/addressbooktests.cpp (1)
67-67
: Updated wallet construction to include ArgsManagerThe 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 parameterThe
CWallet
constructor call now correctly includesgArgs
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 handlingAdded 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 discoveryThe 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:
- Added synchronization of mempools to ensure transaction visibility across nodes
- Used
find_vout_for_address
to explicitly find the output index- 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
andout.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 without.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 parameterThis 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 constructorThis 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 parameterThe 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 signatureThese 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 goodThe 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 functionImporting
find_vout_for_address
allows more precise identification of outputs for spending.
35-40
: Well-implemented transaction helperThe
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 lockingThe output is now explicitly locked before spending, providing better control over UTXO selection and reducing ambiguity in test behavior.
66-67
: Improved transaction construction approachSwitching 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 correctlyThe
MakeWallet
function now accepts the ArgsManager parameter, consistent with other wallet-related functions.
70-70
: Wallet constructor updated properlyThe 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 constructorThis 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 codeUsing 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 explicitspend_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 theCWallet
constructor, aligning with the broader change to includeArgsManager
in wallet instantiation across the codebase.
203-203
: Changed loop counter type for improved type safety.The loop index was changed from
size_t
touint32_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 withvecInputCoins
. This aligns with the broader refactoring to useCOutPoint
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()
matchesvecAmounts.size()
, reflecting the change fromvecInputCoins
tooutpoints
as the container for transaction outputs.src/wallet/wallet.h (3)
131-131
: Refactored CompactTallyItem to use COutPoint directly.Changed
std::vector<CInputCoin> vecInputCoins
tostd::vector<COutPoint> outpoints
, simplifying the wallet's internal representation of outputs. This change is part of the broader refactoring to replace the legacyCInputCoin
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 newm_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 fromBitcoinTestFramework
and implements the required methods:
set_test_params
configures a single node for the testskip_test_if_missing_module
ensures the test is skipped if wallet support is not available
17-32
: Effective timelock transaction setup.The test:
- Gets the current block's median time past (MTP)
- Creates a new address with a Unicode label
- Sends funds with a locktime just before the current MTP
- 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 useoutpoints
instead ofvecInputCoins
This change is part of a broader refactoring where
CompactTallyItem
now usesoutpoints
instead ofvecInputCoins
to track inputs, maintaining the same logic for checking if this is a single input coin.
1565-1565
: Code simplification: Direct access to txout propertyThis change simplifies the code by directly accessing the output's
txout
property instead of using the nested pointer access patternoutput.tx->tx->vout[output.i]
, making the code more readable and less error-prone.
1568-1568
: Code simplification: Direct access to outpoint propertiesThis change simplifies the code by directly accessing the output's
outpoint
properties instead of usingoutput.tx->GetHash()
andoutput.i
, aligning with the broader refactoring to use the richerCOutput
class.
1639-1640
: Refactor: Updated to useoutpoints
instead ofvecInputCoins
Similar to previous changes, this replaces the check on
vecInputCoins.size()
withoutpoints.size()
to maintain compatibility with the refactoredCompactTallyItem
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 previousvecInputCoins
implementation, maintaining the same functionality but using the modernized data structure.
194-194
: Consistent data structure update.Using
outpoints
vector instead of the previousvecInputCoins
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 globalgArgs
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 globalgArgs
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 globalgArgs
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 withnextLockTime
nextLockTime
is a non-atomic signedint
.
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 monotonicstd::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 forsubtract_fee_outputs
selectionsWhen
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, whiletarget
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 usingselected_real_value
when!use_effective_value
.src/wallet/test/wallet_tests.cpp (3)
116-116
: Constructor parameter update looks goodAll
CWallet
constructors now properly include them_args
parameter, which is consistent with the wallet refactoring to explicitly passArgsManager
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 usageReplaced construction of new
COutPoint
objects with direct usage of existingcoin.outpoint
members. This is more efficient and aligns with the wallet refactoring that favors directCOutPoint
usage.Also applies to: 1297-1298, 1355-1355
1423-1423
: Updated member variable referenceThe member
vecInputCoins
inCompactTallyItem
was properly renamed tooutpoints
to reflect the shift from coin objects to outpoints, matching changes in related wallet code.Also applies to: 1466-1467
if outputtype in ["legacy", "p2pkh", "pkh"]: | ||
prefixes = ["pkh(", "sh(multi("] | ||
else: | ||
assert False, f"Unknown output type {outputtype}" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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)
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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) |
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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:
- The function is not thread-safe: parallel test cases or future multi-threaded callers will race on the shared buffer.
- Nested/ chained calls overwrite the same buffer, leading to subtle aliasing bugs (e.g.
SelectCoinsBnB(GroupCoins(...), ...)
inside anotherGroupCoins()
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.
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 | |
} |
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) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) | ||
{ | ||
AssertLockHeld(wallet.cs_wallet); | ||
return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 0e33db2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…ection algorithm UTXO (un)locking has been omitted from `test_fee_p2pkh` sub-test
…dpsbt to take external inputs includes: - a00eb38
Required to simplify the diff in the next commit
…in selection solution
`CInputCoin` will be rid of in the next commit and `COutput`, its replacement is too heavy for what is being done with `vecInputCoins`, since we ultimately only care about the `COutPoint`, let's just store that instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (5)
test/functional/rpc_fundrawtransaction.py (2)
58-61
:⚠️ Potential issueFix 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), raiseAssertionError()
Replace
assert False
(B011)
75-80
: 🛠️ Refactor suggestionGuard against empty
to_keep
listCalling
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 suggestionAvoid static return buffer in
GroupCoins()
GroupCoins()
re-uses a staticstd::vector<OutputGroup>
as its return value.
This is fragile for two reasons:
- The function is not thread-safe: parallel test cases or future multi-threaded callers will race on the shared buffer.
- 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
: Uncheckednullptr
dereference risk is still presentThe 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 whilepreset_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 variableThe 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 bodyRename unused
i
to_i
(B007)
src/wallet/spend.cpp (1)
366-378
: Pass UTXO list by const-reference & avoid no-op solver calls
coins
is copied on every call – the container can be several KB.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
📒 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: Newlock_outputs_type
helper functionThis 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), raiseAssertionError()
Replace
assert False
(B011)
71-74
: LGTM: Newunlock_utxos
helper functionThis helper function centralizes the unlocking logic, making it consistent across the test cases while preserving watchonly UTXOs.
82-84
: LGTM: Initialize watchonly tracking variablesGood initialization of tracking variables for watchonly outputs at the start of the test.
410-411
: LGTM: Proper UTXO locking and unlocking in fee testThe 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 testAppropriately using the new helpers for coin selection restriction.
Also applies to: 458-459
463-464
: LGTM: Proper UTXO locking and unlocking in 4of5 fee testAppropriately using the helpers for coin selection control.
Also applies to: 502-503
570-574
: LGTM: Improved input selection in locked wallet testThe 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 testingBetter approach to creating and tracking unsafe inputs:
- Creates two 5 DASH inputs in a loop
- Finds the correct output index for each input
- Stores inputs in an array for later verification
🧰 Tools
🪛 Ruff (0.8.2)
950-950: Loop control variable
i
not used within loop bodyRename unused
i
to_i
(B007)
957-958
: LGTM: Better verification of unsafe inputs in transactionsThe 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 inputsThe 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 COutputProperly updated the typedef to use
COutput
instead ofCInputCoin
, in line with the API modernization.
37-38
: LGTM: Added nextLockTime to ensure unique transaction hashesGood 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 COutputThe function now properly constructs COutput objects instead of CInputCoin, consistent with the updated coin selection API.
48-58
: LGTM: New add_coin overload for SelectionResultThis helper function makes it easy to add coins directly to a SelectionResult, improving test code organization.
60-71
: LGTM: Updated add_coin for CoinSetProperly updated to use COutput instead of CInputCoin with correct initialization of fee fields.
73-93
: LGTM: Updated add_coin for wallet testsNow correctly uses the new COutput constructor with additional metadata when adding coins to the wallet.
95-112
: LGTM: New EquivalentResult functionGood 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 functionGood addition of a helper function to check if SelectionResult objects contain exactly the same inputs, useful for testing determinism.
147-150
: LGTM: Updated KnapsackSolver wrapperProperly updated to return std::optional and take a FastRandomContext for randomness.
152-168
: LGTM: Updated KnapsackGroupOutputsNow correctly creates and passes a CoinSelectionParams object with the required FastRandomContext.
172-219
: LGTM: Updated BnB tests to use optional return typeTests 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 ArgsManagerThe test now properly passes m_args to the CWallet constructor to match the updated wallet API.
332-351
: LGTM: Updated coin control testsThe wallet creation and testing code now properly uses the ArgsManager parameter and the updated SelectCoins API.
353-663
: LGTM: Comprehensive update of Knapsack testsAll the tests have been properly updated to use std::optional results and verify selection properties through the new interfaces.
665-688
: LGTM: Updated ApproximateBestSubset testThe test now properly uses FastRandomContext and verifies the optional result.
687-738
: LGTM: Updated SelectCoins testThe test now uses the ArgsManager and updated coin selection parameters with FastRandomContext.
src/wallet/coinselection.h (6)
21-86
: LGTM: Rich COutput class replacing CInputCoinExcellent 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 randomnessGood improvement to pass FastRandomContext explicitly, ensuring deterministic behavior in tests while allowing genuine randomness in production.
216-255
: LGTM: New SelectionResult class with comprehensive APIExcellent 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 flagThe
m_use_effective
flag could be inconsistent when groups with differentm_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 algorithmGood 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 signatureProperly updated to return std::optional for consistent error handling across all selection algorithms.
src/wallet/coinselection.cpp (8)
63-170
: LGTM: Updated SelectCoinsBnB implementationThe function has been properly refactored to:
- Return std::optional instead of modifying output parameters
- Use the new SelectionResult class to track selections
- Return std::nullopt on failure cases
- Directly return the result on success
This creates a more robust and easier to use API.
172-194
: LGTM: New SelectCoinsSRD implementationClean implementation of the Single Random Draw algorithm that:
- Shuffles indexes to avoid bias
- Adds inputs until target is reached
- Returns std::nullopt if insufficient funds
- Uses Assume() for checks that should always succeed
This provides a simple alternative selection algorithm.
196-197
: LGTM: Updated ApproximateBestSubset signatureProperly updated to take a FastRandomContext reference for randomness.
267-377
: LGTM: Updated KnapsackSolver implementationThe function has been properly refactored to:
- Return std::optional instead of modifying output parameters
- Use the SelectionResult class to track selections
- Return early with the result when a solution is found
- Return std::nullopt on failure cases
This creates a more robust API.
385-415
: LGTM: Updated OutputGroup::Insert implementationThe function now properly works with COutput objects and correctly:
- Calculates effective values
- Updates group properties
- Handles fee calculations
This aligns with the updated data structure.
429-454
: LGTM: Updated GetSelectionWaste functionThe function now operates on std::set instead of CInputCoin and correctly calculates waste based on effective values and fees.
456-501
: LGTM: SelectionResult method implementationsGood implementation of all SelectionResult methods with proper:
- Waste calculation
- Input management
- Value aggregation
- Comparison operations
- Input shuffling
This completes the API needed for comprehensive coin selection.
503-506
: LGTM: COutput::ToString implementationGood addition of a debug formatting method for COutput objects.
void SelectionResult::AddInput(const OutputGroup& group) | ||
{ | ||
util::insert(m_selected_inputs, group.m_outputs); | ||
m_use_effective = !group.m_subtract_fee_outputs; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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;
+ }
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); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, utACK e901183
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK e901183
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 thetest_fee_p2pkh()
sub-test as we do not have any other outputs except forp2pkh
. Failing to do so results in test failure (see below).Test error:
In bitcoin#22019 we don't use
SelectionResult::GetShuffledInputVector()
inCreateTransactionInternal()
because it interferes with our BIP69 sorting implementation that sortsvin
s instead ofCRecipient
s.How Has This Been Tested?
A full CoinJoin session run on 0e33db2
Breaking Changes
None expected.
Checklist