Skip to content
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

Allow custom fallback algorithm for bnb #1581

Conversation

evanlinjin
Copy link
Member

@evanlinjin evanlinjin commented Aug 28, 2024

Description

This allows the caller to set a custom fallback algorithm when using BranchAndBoundCoinSelection. Previously, you were forced into using SingleRandomDraw.

Signature of CoinSelectionAlgorithm::coin_select has been changed to take in a &mut RangCore. This allows us to pass the random number generator directly to the cs algorithm.

Single random draw is now it's own type SingleRandomDraw and impls CoinSelectionAlgorithm.

BranchAndBoundCoinSelection now handles it's own fallback algorithm internally, and a generic type parameter is added to specify the fallback algorithm.

coin_selection::Error is renamed to InsufficientFunds and the BnB error variants are removed. The BnB error variants are no longer needed since those cases are handled internally by BranchAndBoundCoinSelection (via calling the fallback algorithm).

Notes to the reviewers

This is breaking change. Not sure how useful this is for our users. If it's deemed useful, consider including in beta.

Changelog notice

  • Changed CoinSelectionAlgorithm::coin_select to take in an additional &mut RangCore variable. This allows us to pass a random number generator directly to the cs algorithm.
  • Added SingeRandomDraw type which impls CoinSelectAlgorithm.
  • Changed BranchAndBoundCoinSelection to call the fallback internally. An additional generic parameter is added set this.
  • Changed coin_selection::Error to coin_selection::InsufficientFunds (which is now a struct) and therefore removing bnb error variants.

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

@evanlinjin evanlinjin force-pushed the custom_fallback_algo_wallet_coin_select branch from 3a7c3c2 to 36f7d39 Compare August 28, 2024 10:08
@evanlinjin evanlinjin marked this pull request as ready for review August 28, 2024 10:19
@evanlinjin evanlinjin self-assigned this Aug 28, 2024
@evanlinjin evanlinjin added new feature New feature or request discussion There's still a discussion ongoing api A breaking API change labels Aug 28, 2024
@evanlinjin evanlinjin added this to the 1.0.0-beta milestone Aug 28, 2024
@ValuedMammal
Copy link
Contributor

Concept ACK. I want to take a closer look at it. It would also be nice to try and fix the ignored tests in coin_selection module

@ValuedMammal
Copy link
Contributor

Simple test suggestion

    #[test]
    fn test_bnb_fallback_algorithm() {
        // utxo value
        // 120k + 80k + 300k
        let optional_utxos = get_oldest_first_test_utxos();
        let feerate = FeeRate::BROADCAST_MIN;
        let target_amount = 190_000;
        let drain_script = ScriptBuf::new();
        // bnb won't find exact match and should select oldest first
        let res = BranchAndBoundCoinSelection::<OldestFirstCoinSelection>::default()
            .coin_select(
                vec![],
                optional_utxos,
                feerate,
                target_amount,
                &drain_script,
                &mut thread_rng(),
            )
            .unwrap();
        assert_eq!(res.selected_amount(), 200_000);
    }

@evanlinjin
Copy link
Member Author

Thanks @ValuedMammal feel free to push a commit to introduce the test (if you wish).

Copy link
Contributor

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

Concept ACK 36f7d39
Overall looks good! It's getting late, I'll do another review and test it tomorrow :)

@ValuedMammal
Copy link
Contributor

My main gripe is making the RNG required in order to implement CoinSelectionAlgorithm when previously it wasn't and technically none of our other algos require randomness. But there may be a way to keep the new struct SingleRandomDraw and implement the trait feature-gated behind "std" so that we can use thread_rng. Then BnB can fallback to single random draw if "std" feature is enabled, otherwise a user-provided algorithm. I haven't fully fleshed it out in code yet.

@oleonardolima
Copy link
Contributor

oleonardolima commented Sep 10, 2024

[...] It would also be nice to try and fix the ignored tests in coin_selection module

AFAICT it only needs to removes the #[ignore = ...] from lines 1148 and 1193, as SRD is the fallback algorithm now. At least those two worked just fine testing locally.

Maybe renaming the test fn to mention it relies on fallback is good too.

edit: I still haven't figured out the problem with the other one from line 1236, as it's been ignored for a while.

@notmandatory
Copy link
Member

My main gripe is making the RNG required in order to implement CoinSelectionAlgorithm when previously it wasn't and technically none of our other algos require randomness. But there may be a way to keep the new struct SingleRandomDraw and implement the trait feature-gated behind "std" so that we can use thread_rng. Then BnB can fallback to single random draw if "std" feature is enabled, otherwise a user-provided algorithm. I haven't fully fleshed it out in code yet.

I also took a stab at encapsulating the RNG for SingleRandomDraw and it's cleaner in some ways but breaks the ability to use DefaultCoinSelectionAlgorithm in the no_std mode. So for now I think the way it is here is best.

@notmandatory
Copy link
Member

notmandatory commented Sep 10, 2024

@evanlinjin feel free to squash 89e619e which has @ValuedMammal's suggestions in it and I think this one also needs a rebase.

Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

ACK 89e619e

Signature of `CoinSelectionAlgorithm::coin_select` has been changed to
take in a `&mut RangCore`. This allows us to pass the random number
generator directly to the cs algorithm.

Single random draw is now it's own type `SingleRandomDraw` and impls
`CoinSelectionAlgorithm`.

`BranchAndBoundCoinSelection` now handles it's own fallback algorithm
internally, and a generic type parameter is added to specify the
fallback algorithm.

`coin_selection::Error` is renamed to `InsufficientFunds` and the BnB
error variants are removed. The BnB error variants are no longer needed
since those cases are handled internally by
`BranchAndBoundCoinSelection` (via calling the fallback algorithm).

Add test_bnb_fallback_algorithm test and docs cleanup suggested by @ValuedMammal.
evanlinjin

This comment was marked as outdated.

@notmandatory notmandatory force-pushed the custom_fallback_algo_wallet_coin_select branch from 89e619e to c18204d Compare September 10, 2024 15:53
@notmandatory
Copy link
Member

I've rebased this PR and incorporated @ValuedMammal 's docs and new bnb fallback test.

@notmandatory
Copy link
Member

I think this PR still requires a few changes. If someone can take over this PR (and review and come to consensus on the following suggestions), that will be absolutely wonderful.

1. We currently use `.sort_unstable_by_key` to sort UTXOs for our coin selection algorithm impls. This is fine, but to make things deterministic (i.e. equal-key elements are ordered the same each time), we should add something like `OutPoint` at the end of the key used to sort.

I added a test to check if deterministic coin selection algos were always returning the same utxos and even without changing .sort_unstable_by_key to .sort_by_key I'm getting deterministic results. I still changed to using .sort_by_key just to be safe.

2. In bnb, we use `.reverse`. However, to make things consistent with the way we use `OutPoint` as part of the sort-key, consider using [`Reverse(effective_value)`](https://doc.rust-lang.org/std/cmp/struct.Reverse.html) as the first tuple element of the sort-key.

I didn't have to change .reverse in bnb to get deterministic results.

I also did some test cleanup and added back ignored tests that work as long as we calculate the correct target amount that bnb needs to select the expected utxos.

@notmandatory notmandatory force-pushed the custom_fallback_algo_wallet_coin_select branch 2 times, most recently from 9e1310d to 61fbe30 Compare September 11, 2024 03:02
@ValuedMammal
Copy link
Contributor

Regarding utxo sorting I think we're also saved by the fact that list_unspent calls index.outpoints() that returns an ordered set of ((K, u32), OutPoint) - it seems like we can rely on this sorting. Also isn't it true that no two WeightedUtxos should be the same because it already contains the outpoint?

@evanlinjin
Copy link
Member Author

evanlinjin commented Sep 11, 2024

I think I pushed everyone onto the wrong rabbit-hole with regards to .sort_unstable_by_key. The "unstable" part only means that equal elements may be reordered. There is no randomness here however, so with the same UTXO set, the sorted order will still be the same (as you probably found in your tests @notmandatory).

I think we should revert back to using .sort_unstable_by_key as it is more performant and there is no benefit for keeping equal elements ordered the same.

Added back ignored branch and bounnd tests and cleaned up calculation for target amounts.
@notmandatory notmandatory force-pushed the custom_fallback_algo_wallet_coin_select branch from 61fbe30 to 65be4ea Compare September 11, 2024 16:46
@notmandatory
Copy link
Member

I think we should revert back to using .sort_unstable_by_key as it is more performant and there is no benefit for keeping equal elements ordered the same.

Makes sense, reverted.

Copy link
Contributor

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

utACK 65be4ea

Cool, the new deterministic UTXO selection tests look like a great addition!

Copy link
Member Author

@evanlinjin evanlinjin left a comment

Choose a reason for hiding this comment

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

self-ACK 65be4ea

@evanlinjin evanlinjin merged commit 1b50d88 into bitcoindevkit:master Sep 12, 2024
21 checks passed
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -402,7 +402,7 @@ pub struct BranchAndBoundCoinSelection<Cs = SingleRandomDraw> {
     fallback_algorithm: Cs,
 }
 
-/// Error returned by branch and bond coin selection.
+/// Error returned by branch and bound coin selection.
 #[derive(Debug)]
 enum BnbError {
     /// Branch and bound coin selection tries to avoid needing a change by finding the right inputs for
@@ -521,8 +521,8 @@ impl<Cs: CoinSelectionAlgorithm> CoinSelectionAlgorithm for BranchAndBoundCoinSe
         }
 
         match self.bnb(
-            required_ogs.clone(),
-            optional_ogs.clone(),
+            required_ogs,
+            optional_ogs,
             curr_value,
             curr_available_value,
             signed_target_amount,
@@ -1383,7 +1383,7 @@ mod test {
 
         let drain_script = ScriptBuf::default();
         let target_amount = 20_000 + FEE_AMOUNT;
-        BranchAndBoundCoinSelection::<SingleRandomDraw>::default()
+        BranchAndBoundCoinSelection::new(size_of_change, SingleRandomDraw)
             .bnb(
                 vec![],
                 utxos,
@@ -1414,7 +1414,7 @@ mod test {
 
         let drain_script = ScriptBuf::default();
 
-        BranchAndBoundCoinSelection::<SingleRandomDraw>::default()
+        BranchAndBoundCoinSelection::new(size_of_change, SingleRandomDraw)
             .bnb(
                 vec![],
                 utxos,
@@ -1450,7 +1450,7 @@ mod test {
 
         let drain_script = ScriptBuf::default();
 
-        let result = BranchAndBoundCoinSelection::<SingleRandomDraw>::default()
+        let result = BranchAndBoundCoinSelection::new(size_of_change, SingleRandomDraw)
             .bnb(
                 vec![],
                 utxos,

Copy link
Member

@notmandatory notmandatory Sep 12, 2024

Choose a reason for hiding this comment

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

I'm working on a follow-up PR. I also think the tests need to be improved and there's a bigger issue with SingleRandomDraw in that if used on its own it doesn't fail when there's not enough sats to meet the required amount.

Copy link
Member

Choose a reason for hiding this comment

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

Fixed in #1605

notmandatory added a commit that referenced this pull request Sep 12, 2024
…nt funds

22a2f83 fix(wallet): fix SingleRandomDraw to throw an error if insufficient funds (Steve Myers)

Pull request description:

  ### Description

  * fix SingleRandomDraw to error if insufficient funds
  * fixed spelling and clippy errors (see: #1581 (comment))
  * updated tests to check for error variant instead of a panic

  ### Notes to the reviewers

  Since the single random draw algo can be used on its own it needs to be able to return an insufficient funds error. I think the reason we didn't catch this before is that single random draw already check for sufficient required + optional utxo amounts and returns the insufficient funds error.

  ### Changelog notice

  * fix SingleRandomDraw coin selection to error if there are insufficient funds for a requested payment amount.

  ### Checklists

  #### All Submissions:

  * [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

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [x] I've added tests to reproduce the issue which are now passing
  * [ ] I'm linking the issue being fixed by this PR

ACKs for top commit:
  ValuedMammal:
    ACK 22a2f83

Tree-SHA512: c434003ea6cec1423960a0c7d2f830324227f9f99d9d8f72bd7785368cf51c867036b80c300a97177a10998830ef4df924bdcad408730f9e5dddc92cda75dceb
This was referenced Sep 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api A breaking API change discussion There's still a discussion ongoing new feature New feature or request
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants