-
Notifications
You must be signed in to change notification settings - Fork 310
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
Add process_and_select_coins
function
#727
Add process_and_select_coins
function
#727
Conversation
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 not sure how BDK prefers to structure its commits, but on Bitcoin Core, we like to separate renaming of things and relocating without functional changes from functional changes to separate commits, i.e. it might be easier to review if the relocation of SRD were a separate commit.
I thought that squashing things was the way to proceed but I can split this in two commits or more. |
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 gave a first quick pass, I'm not done reviewing :) (but I chose to publish the review so you can start working on these fixes!)
.max_satisfaction_weight() | ||
.unwrap() | ||
} else { | ||
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.
This is wrong, you should use miniscript to get the max satisfaction weight here as well
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.
Didn't we consider the drain_to
script pubkey as something external? the satisfaction weight for the wallet is zero because it won't spend the created output.
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.
Didn't we consider the drain_to script pubkey as something external?
Yeah
the satisfaction weight for the wallet is zero because it won't spend the created output.
I admit I'm a bit undecided on this... But I think we should still consider the satisfaction weight here. Even tho it's not us spending the output, someone still will have to, so I think it's fair to try to reduce their waste.
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.
Ok, I've to find a way to compute the satisfaction_weight
for an arbitrary Script
, I've tried before without success.
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.
How is this going? Do you need help?
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 not advancing in figuring out how to solve this. I've tried to implement a solution using miniscript functions without success. Another idea, which I don't like, is to pass the foreign weight as a parameter of the transaction.
Also, I don't want to implement something to parse the script and compute the weight. It would be pointless because miniscript implements those functions. But there isn't an implementation for an arbitrary scriptpubkey.
src/wallet/coin_selection.rs
Outdated
@@ -116,6 +87,9 @@ use std::convert::TryInto; | |||
pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection; | |||
#[cfg(test)] | |||
pub type DefaultCoinSelectionAlgorithm = LargestFirstCoinSelection; // make the tests more predictable | |||
/// Algorithm to use in case of an error in the chosen coin selection algorithm | |||
// If everything fails, simple random selection should work | |||
pub type FallbackCoinSelectionAlgorithm = SingleRandomDrawCoinSelection; |
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.
First, I agree with Murch that it would have been better to have this split in a different commit - but I understand it's not easy to see when to squash and when not to squash :) If you can, review would be much easier with two commits!
Second, this is a bit suboptimal: not every CS algorithm has a fallback algorithm! Instead, me and @afilini came up with this idea of having a FallbackWrapper
algorithm struct (you should be able to copy-paste this code and it should already work):
diff --git a/src/wallet/coin_selection.rs b/src/wallet/coin_selection.rs
index 49bafa9..ef6139b 100644
--- a/src/wallet/coin_selection.rs
+++ b/src/wallet/coin_selection.rs
@@ -84,12 +84,10 @@ use std::convert::TryInto;
/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
/// overridden
#[cfg(not(test))]
-pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
+pub type DefaultCoinSelectionAlgorithm =
+ FallbackWrapper<BranchAndBoundCoinSelection, SingleRandomDrawCoinSelection>;
#[cfg(test)]
pub type DefaultCoinSelectionAlgorithm = LargestFirstCoinSelection; // make the tests more predictable
-/// Algorithm to use in case of an error in the chosen coin selection algorithm
-// If everything fails, simple random selection should work
-pub type FallbackCoinSelectionAlgorithm = SingleRandomDrawCoinSelection;
// Base weight of a Txin, not counting the weight needed for satisfying it.
// prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes)
@@ -250,25 +248,13 @@ pub fn get_selection<D: Database, Cs: CoinSelectionAlgorithm<D>>(
} else {
// from now on, target_amount can only be positive
let target_amount = (target_amount_i64 - required_effective_value) as u64;
- let (mut selected_utxos, selected_fees) = match algorithm.coin_select(
+ let (mut selected_utxos, selected_fees) = algorithm.coin_select(
database,
optional_utxos.clone(),
fee_rate,
target_amount,
optional_effective_value,
- ) {
- Ok(selection) => selection,
- Err(_err) => {
- let fallback_algorithm = FallbackCoinSelectionAlgorithm {};
- fallback_algorithm.coin_select(
- database,
- optional_utxos,
- fee_rate,
- target_amount,
- optional_effective_value,
- )?
- }
- };
+ )?;
let selected_effective_value: i64 =
selected_utxos.iter().map(|og| og.effective_value).sum();
selected_utxos.append(&mut required_utxos);
@@ -344,6 +330,59 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug {
) -> Result<(Vec<OutputGroup>, u64), Error>;
}
+#[derive(Debug)]
+pub struct FallbackWrapper<M, F> {
+ main: M,
+ fallback: F,
+}
+
+impl<M, F> Default for FallbackWrapper<M, F>
+where
+ M: Default,
+ F: Default,
+{
+ fn default() -> Self {
+ FallbackWrapper {
+ main: M::default(),
+ fallback: F::default(),
+ }
+ }
+}
+
+impl<M, F, D> CoinSelectionAlgorithm<D> for FallbackWrapper<M, F>
+where
+ M: CoinSelectionAlgorithm<D>,
+ F: CoinSelectionAlgorithm<D>,
+ D: Database,
+{
+ fn coin_select(
+ &self,
+ database: &D,
+ optional_utxos: Vec<OutputGroup>,
+ fee_rate: FeeRate,
+ target_amount: u64,
+ available_value: i64,
+ ) -> Result<(Vec<OutputGroup>, u64), Error> {
+ self.main
+ .coin_select(
+ database,
+ optional_utxos.clone(),
+ fee_rate,
+ target_amount,
+ available_value,
+ )
+ })
+ }
+}
+
/// Simple and dumb coin selection
///
/// This coin selection algorithm sorts the available UTXOs by value and then picks them starting
This is basically introducing a FallbackWrapper
, which you can build around your favorite CS algorithms. We implemented CoinSelectionAlgorithm on the wrapper, so that you just need to call coin_select
, and we automatically take care of calling the fallback in case of errors.
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 just noticed that calling BranchAndBounCoinSelection::default()
is suboptimal (it's using a default size_of_change
)... I'm working on finding a solution...
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 may be missing something, but shouldn't be a call to self.callback.coin_select
inside the impl of coin_select
for FallbackWrapper
?
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.
Sorry, I probably copied it wrong 🤦🏻♀️ Yeah, it should be:
+impl<M, F, D> CoinSelectionAlgorithm<D> for FallbackWrapper<M, F>
+where
+ M: CoinSelectionAlgorithm<D>,
+ F: CoinSelectionAlgorithm<D>,
+ D: Database,
+{
+ fn coin_select(
+ &self,
+ database: &D,
+ optional_utxos: Vec<OutputGroup>,
+ fee_rate: FeeRate,
+ target_amount: u64,
+ available_value: i64,
+ ) -> Result<(Vec<OutputGroup>, u64), Error> {
+ self.main
+ .coin_select(
+ database,
+ optional_utxos.clone(),
+ fee_rate,
+ target_amount,
+ available_value,
+ )
+ .or_else(|_| {
+ self.fallback.coin_select(
+ database,
+ optional_utxos.clone(),
+ fee_rate,
+ target_amount,
+ available_value,
+ )
+ })
+ }
+}
+
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 just noticed that calling
BranchAndBounCoinSelection::default()
is suboptimal (it's using a defaultsize_of_change
)... I'm working on finding a solution...
But the user can still pass algorithm
as:
DefaultCoinSelectionAlgorithm {
main: BranchAndBoundCoinCoinSelection::new(<user_set_size_of_change>),
fallback: SingleRandomDrawCoinSelection::default()
}
This default is not giving up the flexibility above.
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'll continue working with this in another PR.
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.
Concept ACK..
I made a first shallow pass and for now just have few nit comments and name changing requests.. I will go through the full thing again once more..
src/wallet/mod.rs
Outdated
use coin_selection::DefaultCoinSelectionAlgorithm; | ||
use coin_selection::{ | ||
get_selection, DefaultCoinSelectionAlgorithm, | ||
Excess::{Change, NoChange}, |
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.
Naming request: A name like HandleExcess
could be more clearer here.. Its a simple enum, that specifies how to handle the change if any.
Not a major issue, a general rust street rule is not to import enum variants directly. Call them with the enum name in code like Excess::Change
etc. The name of the enum gives useful context at the usage location. And its always just two item long.
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.
Naming request: A name like
HandleExcess
could be more clearer here.. Its a simple enum, that specifies how to handle the change if any.
Well, I thought about that before and didn't like it because Excess
is just a "thing" and not an action per se. The logic inside create_tx
is the one that handles the Excess
. That was my reasoning. If I didn't convince you, let me know and I will change it.
Not a major issue, a general rust street rule is not to import enum variants directly. Call them with the enum name in code like
Excess::Change
etc. The name of the enum gives useful context at the usage location. And its always just two item long.
Good rule! I'll apply it.
Thanks, @xekyo, @danielabrozzoni, @rajarshimaitra, and @afilini for taking the time to review this. I've several changes ahead. Here is a list to keep you updated:
|
f584370
to
144807b
Compare
get_selection
functionprocess_and_select_coins
function
.max_satisfaction_weight() | ||
.unwrap() | ||
} else { | ||
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.
How is this going? Do you need help?
There were some common tasks identified in the construction of a coin selection result that the different algorithms performed separately. Those tasks were blended inside the algorithms or separated into functions, but the underlying actions were the same: - Do some preprocessing to the inputs. - Select utxos based on the algorithm criterium. - Decide change. - Build the coin selection result. This commit intends to create a single function that consumes different algorithms and perform all the mentioned tasks as a pipeline taking utxos as input and producing coin selection results at the end. The new function reduces the work needed to implement other coin selection algorithms, modularizes the code not related directly to the selection process and reduces the number of lines to review and understand its logic.
144807b
to
20ad2a7
Compare
I don't think this will get into the milestone 0.23, I'm sorry :( I need some more time to review it and think about the API changes. This will introduce another API break in the |
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.
This is an excellent undertaking of trying to streamline coin selection in BDK.
However, there seems to be an attempt of "not changing too much" (which is understandable), but the transaction building model in BDK is inconvenient (and potentially wrong) and should be reworked from scratch (in my opinion).
Let's discuss tomorrow over the call and come up with a "grand plan". Your insights will be very appreciated (based on my judgement of your work).
NACK (for now) 😢
After discussing this with the team, we came to the conclusion that we have to close this PR, as the Just a reminder: even though this exact code won't be used in bdk, the ideas discovered while writing/reviewing it surely will. Thanks for your work, Cesar :) |
Description
There were some common tasks identified in the construction of a coin selection
result that the different algorithms performed separately. Those tasks were
blended inside the algorithms or separated into functions, but the underlying
actions were the same:
This commit intends to create a single function that consumes different
algorithms and perform all the mentioned tasks as a pipeline taking utxos as
input and producing coin selection results at the end.
The new function reduces the work needed to implement other coin selection
algorithms, modularizes the code not related directly to the selection process
and reduces the number of lines to review and understand its logic.
As a side effect of the implementation, the
SingleRandomDraw
algorithm wasextracted from its host algorithm,
BranchAndBound
, and introduced as aFallBack
algorithm. But I consider this a feature.Notes to the reviewers
I'm not totally satisfied with some parts of the code.
For example, I don't like to have "ad-hoc" parameters in the current signature
of
coin_select
just for the sake ofBranchAndBound
compatibility. I thoughtof some solutions to this, like the addition of a
cost_of_change
field into thestruct or the recomputation of the available value inside
BranchAndBound::coin_select
(duplicating the amount of work), but I wouldlike to hear other opinions.
Another thing that bothers me is the conversion of
target_amount
from u64 toi64 and the other way back. I must be obfuscated with it because I can't see
another way to do it.
This PR will set up the structure needed to integrate the Waste metric (#558)
with the coin selection module.
BREAKING CHANGES: This PR modifies the API of
coin_select
and introduce a newfunction,
get_selection
, that supersedes its functionality.Changelog notice
process_and_select_coins()
function.OutputGroup
s as a public structure that holds theWeightedUtxo
, itseffective value, and its fees.
CoinSelectionAlgorithm::coin_select()
signature. The scope of theselection was reduced to select utxos only from
optional_utxos
. It onlyreturns the selected
OutputGroups
from now on.WeightedScriptPubkey
struct.Checklists
All Submissions:
cargo fmt
andcargo clippy
before committingNew Features:
addedupdated the tests for the new featureCHANGELOG.md