-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: group outputs only once, decouple it from Coin Selection #25806
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
wallet: group outputs only once, decouple it from Coin Selection #25806
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
27eac5e
to
bbac1ad
Compare
bbac1ad
to
ae41a8b
Compare
ae41a8b
to
9e97767
Compare
0bb90ca
to
ae0dd2b
Compare
Thanks @S3RK for the review. Updated per feedback (small diff), tackled the following points that made sense for me to include here:
I think that while we all agree that this work is functionality and structurally sound, we can move forward. |
And not hide it inside the `OutputGroup::Insert` method. This method does not return anything if insertion fails. We can know before calling `Insert` whether the coin will be accepted or not.
ae0dd2b
to
1cfed6c
Compare
Rebased due silent merge conflicts with #26889. |
utACK 1cfed6c (also reviewed the tests) |
The following scenarios are covered: 1) 10 UTXO with the same script: partial spends is enabled --> outputs must not be grouped. 2) 10 UTXO with the same script: partial spends disabled --> outputs must be grouped. 3) 20 UTXO, 10 one from scriptA + 10 from scriptB: a) if partial spends is enabled --> outputs must not be grouped. b) if partial spends is not enabled --> 2 output groups expected (one per script). 3) Try to add a negative output (value - fee < 0): a) if "positive_only" is enabled --> negative output must be skipped. b) if "positive_only" is disabled --> negative output must be added. 4) Try to add a non-eligible UTXO (due not fulfilling the min depth target for "not mine" UTXOs) --> it must not be added to any group 5) Try to add a non-eligible UTXO (due not fulfilling the min depth target for "mine" UTXOs) --> it must not be added to any group 6) Surpass the 'OUTPUT_GROUP_MAX_ENTRIES' size and verify that a second partial group gets created.
Initial steps towards sharing COutput instances across all possible OutputGroups (instead of copying them time after time).
…sult Another step towards the single OutputGroups calculation goal
…td::vector No functional changes. Only cosmetic changes to simplify the follow-up commit.
The 'GroupOutputs()' function performs the same calculations for only-positive and mixed groups, the only difference is that when we look for only-positive groups, we discard negative utxos. So, instead of wasting resources calling GroupOutputs() for positive-only first, then call it again to include the negative ones in the result, we can execute GroupOutputs() only once, including in the response both group types (positive-only and mixed).
Optimizes coin selection by performing the "group outputs" procedure only once, outside the "attempt selection" process. Avoiding the repeated execution of the 'GroupOutputs' operation that occurs on each coin eligibility filters (up to 8 of them); then for every coin vector type plus one for all the coins together. This also let us not perform coin selection over coin eligibility filtered groups that don't add new elements. (because, if the previous round failed, and the subsequent one has the same coins, then this new round will fail again).
1cfed6c
to
6a302d4
Compare
Updated per feedback, thanks S3RK 👌🏼. Small diff, changes are in the last test scenario and the test commit message. |
ReACK 6a302d4 |
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.
re-ACK 6a302d4 🥥
(verified via git range-diff 0bb90cab...6a302d40
that since my last ACK, most review comments by Murch and S3RK have been tackled, mostly trivial refactor + terminology/naming improvements and a minor unit test adaption)
ACK 6a302d4 |
…m Coin Selection 6a302d4 wallet: single output groups filtering and grouping process (furszy) bd91ed1 wallet: unify outputs grouping process (furszy) 5596200 test: coinselector_tests refactor, use CoinsResult instead of plain std::vector (furszy) 34f54a0 wallet: decouple outputs grouping process from each ChooseSelectionResult (furszy) 461f082 refactor: make OutputGroup::m_outputs field a vector of shared_ptr (furszy) d8e749b test: wallet, add coverage for outputs grouping process (furszy) 06ec8f9 wallet: make OutputGroup "positive_only" filter explicit (furszy) Pull request description: The idea originates from bitcoin#24845 (comment). Note: For clarity, it's recommended to start reviewing from the end result to understand the structure of the flow. #### GroupOutputs function rationale: If "Avoid Partial Spends" is enabled, the function gathers outputs with the same script together inside a container. So Coin Selection can treats them as if them were just one possible input and either select them all or not select them. #### How the Inputs Fetch + Selection process roughly works: ``` 1. Fetch user’s manually selected inputs. 2. Fetch wallet available coins (walks through the entire wallet txes map) and insert them into a set of vectors (each vector store outputs from a single type). 3. Coin Selection Process: Call `AttemptSelection` 8 times. Each of them expands the coin eligibility filter (accepting a larger subset of coins in the calculation) until it founds a solutions or completely fails if no solutions gets founds after the 8 rounds. Each `AttemptSelection` call performs the following actions: - For each output type supported by the wallet (P2SH, P2PK, P2WPKH, P2WSH and a combination of all of them): Call ‘ChooseSelectionResult’ providing the respective, filtered by type, coins vector. Which: I. Groups the outputs vector twice (one for positive only and a second one who includes the negative ones as well). - GroupOutputs walks-through the entire inputted coins vector one time at least, + more if we are avoiding partial spends, to generate a vector of OutputGroups. II. Then performs every coin selection algorithm using the recently created vector of OutputGroup: (1) BnB, (2) knapsack and (3) SRD. III. Then returns the best solution out of them. ``` We perform the general operation of gathering outputs, with the same script, into a single container inside: Each coins selection attempt (8 times —> each coin eligibility filter), for each of the outputs vector who were filtered by type (plus another one joining all the outputs as well if needed), twice (one for the positive only outputs effective value and a second one for all of them). So, in the worst case scenario where no solution is found after the 8 Coin Selection attempts, the `GroupOutputs` function is called 80 times (8 * 5 * 2). #### Improvements: This proposal streamlines the process so that the output groups, filtered by coin eligibility and type, are created in a single loop outside of the Coin Selection Process. The new process is as follows: ``` 1. Fetch user’s manually selected inputs. 2. Fetch wallet available coins. 3. Group outputs by each coin eligibility filter and each different output type found. 4. Coin Selection Process: Call AttemptSelection 8 times. Each of them expands the coin eligibility filter (accepting different output groups) until it founds a solutions or completely fails if no solutions gets founds after the 8 rounds. Each ‘AttemptSelection’ call performs the following actions: - For each output type supported by the wallet (P2SH, P2PK, P2WPKH, P2WSH and all of them): A. Call ‘ChooseSelectionResult’ providing the respective, filtered by type, output group. Which: I. Performs every coin selection algorithm using the provided vector of OutputGroup: (1) BnB, (2) knapsack and (3) SRD. II. Then returns the best solution out of them. ``` Extra Note: The next steps after this PR will be to: 1) Merge `AvailableCoins` and `GroupOutputs` processes. 2) Skip entire coin selection rounds if no new coins are added into the subsequent round. 3) Remove global feerates from the OutputGroup class. 4) Remove secondary "grouped" tx creation from `CreateTransactionInternal` by running Coin Selection results over the aps grouped outputs vs non-aps ones. ACKs for top commit: S3RK: ReACK 6a302d4 achow101: ACK 6a302d4 theStack: re-ACK 6a302d4 🥥 Tree-SHA512: dff849063be328e7d9c358ec80239a6db2cd6131963b511b83699b95b337d3106263507eaba0119eaac63e6ac21c6c42d187ae23d79d9220b90c323d44b01d24
475c20a wallet: remove coin control arg from AutomaticCoinSelection (furszy) 8a55831 wallet: remove unused methods (furszy) 8471967 wallet: GroupOutput, remove unneeded "spendable" check (furszy) a9aa041 wallet: OutputGroup, remove unused effective_feerate member (furszy) 99034b2 wallet: APS, don't create empty groups (furszy) 805f399 wallet: do not make two COutputs, use shared_ptr (furszy) Pull request description: Few small findings post-#25806 and extra cleanups, nothing biggie. ACKs for top commit: S3RK: Code review ACK 475c20a Xekyo: utACK 475c20a achow101: ACK 475c20a Tree-SHA512: df45efebd6e2e4ecac619d6ecef794979c328a2d6ef532e25124d0dc1c72b55ccf13498f61fb65958b907bfba6a72ed569bf34eb5fbe35419632fe0406e78798
475c20a wallet: remove coin control arg from AutomaticCoinSelection (furszy) 8a55831 wallet: remove unused methods (furszy) 8471967 wallet: GroupOutput, remove unneeded "spendable" check (furszy) a9aa041 wallet: OutputGroup, remove unused effective_feerate member (furszy) 99034b2 wallet: APS, don't create empty groups (furszy) 805f399 wallet: do not make two COutputs, use shared_ptr (furszy) Pull request description: Few small findings post-bitcoin#25806 and extra cleanups, nothing biggie. ACKs for top commit: S3RK: Code review ACK 475c20a Xekyo: utACK 475c20a achow101: ACK 475c20a Tree-SHA512: df45efebd6e2e4ecac619d6ecef794979c328a2d6ef532e25124d0dc1c72b55ccf13498f61fb65958b907bfba6a72ed569bf34eb5fbe35419632fe0406e78798
475c20a wallet: remove coin control arg from AutomaticCoinSelection (furszy) 8a55831 wallet: remove unused methods (furszy) 8471967 wallet: GroupOutput, remove unneeded "spendable" check (furszy) a9aa041 wallet: OutputGroup, remove unused effective_feerate member (furszy) 99034b2 wallet: APS, don't create empty groups (furszy) 805f399 wallet: do not make two COutputs, use shared_ptr (furszy) Pull request description: Few small findings post-bitcoin#25806 and extra cleanups, nothing biggie. ACKs for top commit: S3RK: Code review ACK 475c20a Xekyo: utACK 475c20a achow101: ACK 475c20a Tree-SHA512: df45efebd6e2e4ecac619d6ecef794979c328a2d6ef532e25124d0dc1c72b55ccf13498f61fb65958b907bfba6a72ed569bf34eb5fbe35419632fe0406e78798
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.
Post merge ACK 6a302d4
@@ -29,8 +29,10 @@ static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vect | |||
auto output_group = OutputGroup(coin_params); | |||
bool valid_outputgroup{false}; | |||
for (auto& coin : coins) { | |||
output_group.Insert(coin, /*ancestors=*/0, /*descendants=*/0, positive_only); | |||
// If positive_only was specified, nothing may have been inserted, leading to an empty output group | |||
if (!positive_only || (positive_only && coin.GetEffectiveValue() > 0)) { |
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.
if (!positive_only || (positive_only && coin.GetEffectiveValue() > 0)) { | |
if (!positive_only || (coin.GetEffectiveValue() > 0)) { |
The idea originates from #24845 (comment).
Note:
For clarity, it's recommended to start reviewing from the end result to understand the structure of the flow.
GroupOutputs function rationale:
If "Avoid Partial Spends" is enabled, the function gathers outputs with the same script together inside a container. So Coin Selection can treats them as if them were just one possible input and either select them all or not select them.
How the Inputs Fetch + Selection process roughly works:
We perform the general operation of gathering outputs, with the same script, into a single container inside:
Each coins selection attempt (8 times —> each coin eligibility filter), for each of the outputs vector who were filtered by type (plus another one joining all the outputs as well if needed), twice (one for the positive only outputs effective value and a second one for all of them).
So, in the worst case scenario where no solution is found after the 8 Coin Selection attempts, the
GroupOutputs
function is called 80 times (8 * 5 * 2).Improvements:
This proposal streamlines the process so that the output groups, filtered by coin eligibility and type, are created in a single loop outside of the Coin Selection Process.
The new process is as follows:
Extra Note:
The next steps after this PR will be to:
AvailableCoins
andGroupOutputs
processes.CreateTransactionInternal
by running Coin Selection results over the aps grouped outputs vs non-aps ones.