-
Notifications
You must be signed in to change notification settings - Fork 338
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
Refactor/use iterators to preselect utxos #1798
base: master
Are you sure you want to change the base?
Refactor/use iterators to preselect utxos #1798
Conversation
Hey @nymius, does this actually fix something or is this purely a refactor? Note that we want to redo the tx building / creating logic to use |
Hi @evanlinjin. Yes, maybe I went to far with the changes, still, it fixes the duplicate issue I open in #1794 by using a Then, about the refactor: The research of the code push me to isolate the pre selection steps as items on a checklist and that's why It ended up as a refactor. My idea is to further isolate each filter in their own iterator adaptor (a separated function for each one) that consumes The heavy use of iterators came for trying to avoid the allocation of helpers, like I envisioned something like this: let optional_utxos = self
.list_unspent()
.check_are_not_already_manually_selected()
.check_are_not_unspendable()
.check_confirmed_only_if_RBF()
.check_is_local_utxo()
.check_is_mature_if_coinbase();
// then
let (required, optional) = optional_utxos.chain(required_utxos.iter().clone())
.get_weighted_utxos()
.chain(foreign_utxos.iter().clone())
.apply_custom_validation_for_all_tx_inputs()
.split_utxos_in_required_and_optional() Discussing this today with @ValuedMammal, I decided to do the following changes:
If we don't agree on the above, I propose the following alternatives:
|
05ac09c
to
e42b5aa
Compare
05d94f3
to
388b7cc
Compare
Rebased |
📌 I don't think we've considered what would happen if a third party provides us with a "foreign" utxo that actually duplicates a utxo owned by the wallet, potentially causing us to sign something we didn't agree to. It might need more research so I'll open an issue. #1819 |
388b7cc
to
e8e21e1
Compare
I created a separated draft PR #1823 to address the issue. |
review club notes 02/06/2025
Thanks to @ValuedMammal, @oleonardolima, @LagginTimes and @evanlinjin for participating! |
Next steps based on review club:
|
e8e21e1
to
905c8dd
Compare
Now that foreign UTxOs are separated from manually selected UTxOs there are new cases for duplicity:
TODO:
|
7cb0243
to
49867ad
Compare
@@ -125,7 +125,8 @@ pub(crate) struct TxParams { | |||
pub(crate) fee_policy: Option<FeePolicy>, | |||
pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, | |||
pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>, | |||
pub(crate) utxos: Vec<WeightedUtxo>, | |||
pub(crate) utxos: HashSet<LocalOutput>, | |||
pub(crate) foreign_utxos: HashSet<WeightedUtxo>, |
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.
I'm worried that this won't be sufficient to check that the outpoint is unique. For instance, one could change the satisfaction weight and get a different WeightedUtxo
while using the same outpoint.
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.
Good point, will think more about this. Will reproduce your case in a test and use a HashMap
with outpoint as key in the meantime.
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.
I modified the test slightly to cover that case. I didn't add a different one because it is not modifying the logic being tested, but just being more precise with the assertions.
I used HashMap
instead of BTreeMap
because we don't need the ordering features, however it can be argued that BTreeMap
is already use it in crates/wallet/src/wallet/tx_builder.rs
and we are adding a new import.
I prefered the former over the later.
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.
To avoid the duplication concern we had for #1582, we can implement Hash
manually for WeightedUTxO
and make it use the Hash
implementation of Outpoint
.
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.
I realized this won't be enough, as HashSet
requires the following for keys k1
, k2
:
But this should enforced by us. If we implement Hash
as I said above and don't also modify PartialEq
implementation to be compatible with this, the case where outpoint is the same but weight is different will introduce two different utxos in foreign utxos set.
I wouldn't modify PartialEq
because is probably going to break other logic depending of the usual way of comparing two elements (i.e. considering all its fields).
HashMap
is still the best solution so far.
49867ad
to
1b90b92
Compare
…nal UTxOs 03b7eca fix(wallet): off-by-one error checking coinbase maturity in optional UTxOs (nymius) Pull request description: ### Description As I was developing the changes in #1798 I discover issue #1810. So I introduced the fixes in that PR but later I split them in two to ease the review by suggestion of @oleonardolima . The `preselect_utxos` method has an off-by-one error that is making the selection of optional UTxOs too restrictive, by requiring the coinbase outputs to surpass or equal coinbase maturity time at the current height of the selection, and not in the block in which the transaction may be included in the blockchain. The changes in this commit fix it by considering the maturity of the coinbase output at the spending height and not the transaction creation height, this means, a +1 at the considered height at the moment of building the transaction. Fixes #1810. ### Notes to the reviewers Tests for issue #1810 have not been explicitly added, as there already was a `text_spend_coinbase` test which was corrected to ensure coinbase maturation is considered in alignment with the new logic. Changes are not breaking but I'm modifying slightly the documentation for the public method `TxBuilder::current_height` to adjust to the fixed code. Does this merit an entry in the CHANGELOG? ### Changelog notice `Wallet` now considers a utxo originated from a coinbase transaction (`coinbase utxo`) as available for selection if it will mature in the next block after the height provided to the selection, the current height by default. The previous behavior allowed selecting a `coinbase utxo` only when the height at the moment of selection was equal to maturity height or greater. ### Checklists * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing * [x] I've updated existing tests to match the fix * [x] I've updated docs to match the fix logic * [x] This pull request DOES NOT break the existing API * [x] I'm linking the issue being fixed by this PR ACKs for top commit: LagginTimes: ACK 03b7eca evanlinjin: ACK 03b7eca Tree-SHA512: f270b73963bd6f141c8a3e759bc9b9bf75de7c52f37fff93f0a6b8b996b449d98c58e5eeb2b56f0ee236222f0807da5c8201ade7462813743e0c4d255313e2b5
There were multiple calls for de-duplication of selected UTxOs. As the test `test_filter_duplicates` shows, there are four possible cases for duplication of UTxOs while feeding the coin selection algorithms. 1. no duplication: out of concern 2. duplication in the required utxos only: covered by the source of `required_utxos`, `Wallet::list_unspent`, which roots back the provided `UTxOs` to a `HashMap` which should avoid any duplication by definition 3. duplication in the optional utxos only: is the only one possible as optional `UTxOs` are stored in a `Vec` and no checks are performed about the duplicity of their members. 4. duplication across the required and optional utxos: is already covered by `Wallet::preselect_utxos`, which avoid the processing of required UTxOs while listing the unspent available UTxOs in the wallet. This refactor changes: - `TxParams::utxos` type to be `HashSet<LocalOutput>` avoiding the duplication case 3, and allowing a query closer to O(1) on avg. to cover duplication case 4 (before was O(n) where n is the size of required utxos). - Moves the computation of the `WeightedUtxos` to the last part of UTxO filtering, allowing the unification of the computation for local outputs. - Removes some extra allocations done for helpers structures or intermediate results while filtering UTxOs. - Allows for future integration of UTxO filtering methods for other utilities. - Adds more comments for each filtering step. With these changes all four cases would be covered, and `coin_selection::filter_duplicates` would be no longer needed.
…nal utxos This test replaces the one used to test `coin_selection::filter_duplicates` introduced in 5299db3. As the code changed and there is not a single point to verificate the following properties: - there are no duplicates in required utxos - there are no duplicates in optional utxos - there are no duplicates across optional and required utxos anymore, test have been prefixed with `not_duplicated_utxos*` to allow its joint execution by using the following command: cargo test -- not_duplicated_utxos
1b90b92
to
ebecafc
Compare
Description
There were multiple calls for de-duplication of selected UTxOs in
Wallet::create_tx
: (1) and (2).As the test
test_filter_duplicates
shows, there are four possible cases for duplication of UTxOs while feeding the coin selection algorithms.required_utxos
,Wallet::list_unspent
, which roots back the providedUTxOs
to aHashMap
which should avoid any duplication by definitionUTxOs
are stored in aVec
and no checks are performed about the duplicity of their members.Wallet::preselect_utxos
, which avoid the processing of required UTxOs while listing the unspent available UTxOs in the wallet.This refactor does the following:
TxParams::utxos
type to beHashSet<LocalOutput>
avoiding the duplication case 3required_utxos
,Wallet::list_unspent
comes from aHashMap
which should avoid duplication by definition.WeightedUtxos
to the last part of UTxO filtering, allowing the unification of the computation for local outputs.foreign_utxos
, which should include a provided satisfation weight to use them effectively, andutxos
, manually selected UTxOs for which the wallet can compute their satisfaction weight without external resources.With these changes all four cases would be covered, and
coin_selection::filter_duplicates
is no longer needed.Fixes #1794.
Notes to the reviewers
I added three test to cover the interesting cases for duplication:
- there are no duplicates in required utxos
- there are no duplicates in optional utxos
- there are no duplicates across optional and required utxos
the three of them have been prefixed with
not_duplicated_utxos*
to allow its joint execution under the command:cargo test -- not_duplicated_utxos
because the guarantees for the three conditions above are spread in different parts of the code.
Changelog notice
No changes to public APIs.
Checklists
cargo fmt
andcargo clippy
before committing