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

withdrawals as part of the coin selection #1865

Merged
merged 8 commits into from
Jul 8, 2020

Conversation

KtorZ
Copy link
Member

@KtorZ KtorZ commented Jul 6, 2020

Issue Number

#1861

Overview

  • 12b28f7
    📍 take withdrawals into account, one level earlier, during the coin selection
    Still to be done:

    • Make sure it's correctly done in the largest-first algorithm
    • Add some test scenario that show the influence of the withdrawal
  • 547b130
    📍 unit-test the newly introduced 'proportionallyTo'
    Getting this one wrong would be quite bad :s

  • 975f726
    📍 add unit tests showing how withdrawal impacts the random coin selection

💡 NOTE

I've chosen not to treat the withdrawal as a single input, but more as a "money pot" that is proportionally distributed amongst change output, so that it contributes to every output, based on their size. This is to avoid having a small output consuming the entire withdrawal for itself. Note sure if I'll keep the approach in the end, I'll have the night to think about it.

Comments

  • TODO: take into account the withdrawal when doing largest-first (and testing it)
  • TODO: echo the error message change on "ErrInputsDepleted" to integration tests relying on that message.

@KtorZ KtorZ requested review from paweljakubas and piotr-iohk July 6, 2020 15:43
@KtorZ KtorZ self-assigned this Jul 6, 2020
, "Try sending a smaller amount."
[ "I cannot select enough UTxO from your wallet to construct "
, "an adequate transaction. Try sending a smaller amount or "
, "increasing the number of available UTxO."
Copy link
Member Author

Choose a reason for hiding this comment

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

ErrInputsDepleted is now used also when trying to select coins on a null UTxO, despite enough balance in the reward account.

(\acc out ->
let
withdraw = totalWithdraw
`proportionallyTo` (getCoin (coin out) % totalOut)
Copy link
Member Author

Choose a reason for hiding this comment

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

So here, we are guaranteed that the ratio given as second argument is always <= 1

Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

real question, maybe trivial... but why do we need to use proportionallyTo at all? why this rule to transfer upon withdrawing proportionally to relative weight in total outs? Why not to transfer randomly (adding up to withdrawal)? Or almost everything to one plus 1 + 1 + ... + 1? Or evenly? Or create one change?

guard sel $> (sel, remUtxo)
-- NOTE re-assigning total withdrawal to cope with potential
-- rounding issues.
guard sel $> (sel { withdrawal = totalWithdraw }, remUtxo)
Copy link
Member Author

Choose a reason for hiding this comment

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

sel here is constructed by appending multiple selection together from left to right, using the Monoid instance on CoinSelection. So in the end, the total withdrawal that we get is the sum of all withdrawals that were assigned to each output, but because proportionallyTo rounds down, the final sum may end up being slightly less than the total (an error of 1 per output at most) yet, we need it to be precisely equal to the reward's balance without what the Ledger will reject the withdrawal.

Thus, this is reassigned here, and the possible exceeding will be balanced out during fee balancing anyway.

Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

if we transfer withdraw to one change we would not have this problem;-) But, when used proportionallyTo indeed we need to take care of this... So what happens in detail when proportionallyTo rounding does consume not all withdraw? To which changes this additional 1s go ? To those that correspond to outputs that were affected by this rounding, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

To which changes this additional 1s go

Most likely, they'll be used to partially cover fee in the fee balancing algorithm here. What will happen in case of rounding issues is that the transaction will be slightly unbalanced (positively) and have a few lovelace extra on the input side assigned to "nothing". Following the fee balancing algorithm, it'll start by consuming that delta, and then, starts to deplete (proportionally again) change outputs. So in the end, the little exceeding isn't lost but goes directly for fee; which is roughly similar to assigning it to any of the change output, since they'll be depleted for fees regardless.

@@ -117,12 +123,35 @@ changeBalance = foldl' addCoin 0 . change
feeBalance :: CoinSelection -> Word64
feeBalance sel = inputBalance sel - outputBalance sel - changeBalance sel

-- | Total UTxO balance + withdrawal.
totalBalance :: Quantity "lovelace" Word64 -> [(TxIn, TxOut)] -> Word64
totalBalance (Quantity withdraw) inps = balance' inps + withdraw
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

--
-- >>> 10 `proportionallyTo` 1%3
-- 3
proportionallyTo :: Integral a => a -> Ratio a -> a
Copy link
Contributor

Choose a reason for hiding this comment

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

any chance to have unit test for this or something that uses this inside?

Copy link
Contributor

Choose a reason for hiding this comment

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

it is checked below

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed!

@@ -59,11 +69,15 @@ largestFirst opt outs utxo = do
guard s $> (s, UTxO $ Map.fromList utxo')
Nothing -> do
let moneyRequested = sum $ (getCoin . coin) <$> (descending outs)
let utxoBalance = fromIntegral $ balance utxo
let utxoList = Map.toList $ getUTxO utxo
let total = totalBalance withdraw utxoList
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

coinSelectionUnitTest random "withdrawal simple"
(Right $ CoinSelectionResult
{ rsInputs = [1]
, rsChange = []
Copy link
Contributor

Choose a reason for hiding this comment

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

why it is not [1] ?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, ok. we have input 1 plus withdraw 1 to cover 2

coinSelectionUnitTest random "withdrawal multi-output"
(Right $ CoinSelectionResult
{ rsInputs = [1,1]
, rsChange = []
Copy link
Contributor

Choose a reason for hiding this comment

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

why it is not [1,1] ?

Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

inps + withdrawals : [1,1,2] to cover [2,2] gives [] change

Copy link
Member Author

Choose a reason for hiding this comment

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

These examples are indeed a bit tricky to follow, I tried to left some note above each test case to make it a bit easier.

, totalWithdrawal = 20
})

coinSelectionUnitTest random "withdrawal requires at least one input"
Copy link
Contributor

Choose a reason for hiding this comment

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

this one I understand 👍

-- - 2 Ada goes to the other output ( 4/14 * 10 ~= 2)
(Right $ CoinSelectionResult
{ rsInputs = [5,5]
, rsChange = [2,3]
Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

and here we see that in spite of missing 1 by proportionallyTo the money is counting right

{ maxNumOfInputs = 100
, validateSelection = noValidation
, utxoInputs = [1]
, txOutputs = 10 :| [10]
Copy link
Contributor

Choose a reason for hiding this comment

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

in old days it would trigger not enough fragmentation if the number of inputs does not match number of outputs. now I see withdrawals help in this sense 👍 wonder if there are some integration tests checking this edge conditions that needs to be updated

Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

what about this example :

            (CoinSelectionFixture
                { maxNumOfInputs = 100
                , validateSelection = noValidation
                , utxoInputs = [1]
                , txOutputs = 1 :| [1,1,1,1,1]
                , totalWithdrawal = 5
                })

?

Copy link
Member Author

Choose a reason for hiding this comment

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

what about this example

Following the current logic, each output would receive 0 withdrawal bonus (1/6 * 5, rounded down) and therefore, this would fail on the second output because there wouldn't be enough inputs to select. Although indeed, if instead of using the withdrawal proportionally, we were using them fully to cover "just" the right amount, it'd give better result.

@@ -99,6 +109,16 @@ spec = do
prop "All inputs are used" prop_allInputsAreUsed
prop "All inputs are used per transaction" prop_allInputsAreUsedPerTx
prop "Addresses are recycled fairly" prop_fairAddressesRecycled

describe "proportionallyTo" $ do
Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

ah, this one I was asking above - persuasive 💯

@@ -707,10 +709,10 @@ genSelectionFor :: NonEmpty TxOut -> Gen CoinSelection
genSelectionFor outs = do
let opts = CS.CoinSelectionOptions (const 100) (const $ pure ())
utxo <- vector (NE.length outs * 3) >>= genUTxO
case runIdentity $ runExceptT $ largestFirst opts outs utxo of
withdrawal_ <- genWithdrawal
case runIdentity $ runExceptT $ largestFirst opts outs (Quantity withdrawal_) utxo of
Copy link
Contributor

@paweljakubas paweljakubas Jul 6, 2020

Choose a reason for hiding this comment

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

Dusting is going to affect this somehow ? Or we are confident it is going to be fine?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure to follow your concern on this one 🤔 ?

@KtorZ KtorZ force-pushed the KtorZ/1861/withdrawals-during-selection branch 3 times, most recently from ef04981 to 588a68c Compare July 7, 2020 15:36
@KtorZ
Copy link
Member Author

KtorZ commented Jul 7, 2020

bors try

iohk-bors bot added a commit that referenced this pull request Jul 7, 2020
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 7, 2020

try

Build failed

Copy link
Contributor

@paweljakubas paweljakubas left a comment

Choose a reason for hiding this comment

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

LGTM!

@KtorZ
Copy link
Member Author

KtorZ commented Jul 8, 2020

bors r+

iohk-bors bot added a commit that referenced this pull request Jul 8, 2020
1865: withdrawals as part of the coin selection r=KtorZ a=KtorZ

# Issue Number

<!-- Put here a reference to the issue this PR relates to and which requirements it tackles -->

#1861 

# Overview

<!-- Detail in a few bullet points the work accomplished in this PR -->

- 12b28f7
  📍 **take withdrawals into account, one level earlier, during the coin selection**
  Still to be done:

    - Make sure it's correctly done in the largest-first algorithm
    - Add some test scenario that show the influence of the withdrawal

- 547b130
  📍 **unit-test the newly introduced 'proportionallyTo'**
  Getting this one wrong would be quite bad :s

- 975f726
  📍 **add unit tests showing how withdrawal impacts the random coin selection**


> 💡 NOTE
>
> I've chosen not to treat the withdrawal as a single _input_, but more as a "money pot" that is proportionally distributed amongst change output, so that it contributes to every output, based on their size. This is to avoid having a small output consuming the entire withdrawal for itself. Note sure if I'll keep the approach in the end, I'll have the night to think about it.

# Comments

<!-- Additional comments or screenshots to attach if any -->

- [ ] TODO: take into account the withdrawal when doing largest-first (and testing it)
- [ ] TODO: echo the error message change on "ErrInputsDepleted" to integration tests relying on that message.

<!-- 
Don't forget to:

 ✓ Self-review your changes to make sure nothing unexpected slipped through
 ✓ Assign yourself to the PR
 ✓ Assign one or several reviewer(s)
 ✓ Once created, link this PR to its corresponding ticket
 ✓ Assign the PR to a corresponding milestone
 ✓ Acknowledge any changes required to the Wiki
-->


Co-authored-by: KtorZ <[email protected]>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

Build failed

KtorZ added 4 commits July 8, 2020 16:55
…ection

Still to be done:

- Make sure it's correctly done in the largest-first algorithm
- Add some test scenario that show the influence of the withdrawal
Getting this one wrong would be quite bad :s
It is a bit more work to get this right, but in the end, it solves the same problem (being able to use the withdrawal on several outputs) with more elegance and efficiency.
Now the withdrawal is used gradually, and only what's needed at every step so in that sense, it is optimal.
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

26 similar comments
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

This PR was included in a batch with a merge conflict, it will be automatically retried

@cleverca22
Copy link
Contributor

bors r-

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Jul 8, 2020

Canceled

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants