-
Notifications
You must be signed in to change notification settings - Fork 631
[CSL-2526] update changes after covering remaining fees #3815
Conversation
-> Value utxo | ||
-> [CoinSelResult utxo] | ||
returnAdditioncalChange [] _ = error "found empty CoinSelResult" | ||
returnAdditioncalChange (cs : restcs) additionalChange = |
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.
typo here I believe returnAdditioncalChange
-> returnAdditionalChange
(css', ) <$> coverRemainingFee pickUtxo remainingFee | ||
(additionalUtxo, additionalChange) <- coverRemainingFee pickUtxo remainingFee | ||
let css'' = returnAdditioncalChange css' additionalChange | ||
return (css'', additionalUtxo) |
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 be honest, I'd rather add that logic to runCoinSelT
from Cardano.Wallet.Kernel.CoinSelection.FromGeneric
.
When we construct the final result:
let css = map (coinSelRemoveDust (csoDustThreshold opts)) cssWithDust
inps = concatMap selectedEntries
(additionalUtxo : map coinSelInputs css)
outs = map coinSelOutput css
let allInps = case inps of
[] -> error "runCoinSelT: empty list of inputs"
i:is -> i :| is
originalOuts = case outs of
[] -> error "runCoinSelT: empty list of outputs"
o:os -> o :| os
return . Right $ CoinSelFinalResult allInps
originalOuts
(concatMap coinSelChange css)
This would avoid fiddling with CoinSelResult
here and "artificially" add the change. To my understanding, we've separated additional inputs yielded by the fee policy for a reason; We have conflated them with the CoinSelResult
inputs already but we didn't. Instead, we reconcialte the CoinSelFinalResult
later and therefore, I see this as a similar case.
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.
Should I create an additional change output just for these additional UTXO's instead of just adding it to the existing one? In this case should I also check if this new change is dust? Not sure 🤔
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.
Cf @edsko inputs I believe. Let's distribute the extra change across all changes.
The change looks good; I simply think it's not applied to the right place. |
We need a fix on |
Are we planning to add a unit or property tests to exercise this bug and prove that it's fixed? I can't really see from the code changes here what the specific logic error was or that it has been fixed. |
Yes we are. And this is why this PR is still blocked and under review.
We aren't going to ship something to fix it again 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.
LGTM, nice catch, and I like the logic of the solution. One concern: by modifying a single change output with the remaining change, if that remaining change is very large, it becomes easily identifiable as the change output. Might wish to consider divvy
to distribute it evenly over all change outputs.
Very minor concern: possibility of overflow when adding change to existing outputs. |
fafdf6b
to
f684d15
Compare
Sanity check breaks unit tests :/ |
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.
Some minor comments and some concerns about the hardcoded min fee.
This is a chain parameter that can in theory, change with chain updates. The value should come from the chain as much as possible.
let fees = computeFeesOfUnsignedTx tx | ||
if isSane options fees | ||
then return (snapshot, tx, fees, availableUtxo) | ||
else throwError $ NewTransactionErrorCoinSelectionFailed CoinSelHardErrCannotCoverFee |
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.
The error would be a bit misleading here and I wouldn't define any particular new user-facing error either; this is an invariant violation. If we reach that case, it means we fucked up hard and everything should blow up!
@@ -229,7 +229,12 @@ newUnsignedTransaction ActiveWallet{..} options accountId payees = runExceptT $ | |||
-- that it may change in the future. | |||
let attributes = def :: TxAttributes | |||
let tx = UnsignedTx inputs outputs attributes coins | |||
return (snapshot, tx, availableUtxo) | |||
|
|||
-- STEP 3: sanity test. |
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.
Worth extending the comment here explaining what that check is all about.
-- this is a bit ad-hoc, but the policy is taken from the node. | ||
-- another solution is to use the policy from CoinSelectionOptions | ||
-- and call it with 0 ins and outs, which should give the same number. | ||
let minFees = Core.mkCoin 155381 |
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 can (ought to) be extracted from the NodeStateAdaptor:
-- | Get fee policy
, getFeePolicy :: m TxFeePolicy
ad1371c
to
e1b1cfa
Compare
e1b1cfa
to
a762072
Compare
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.
LGTM 👍
[CSL-2526] update changes after covering remaining fees
…hk/kderme/CSL-2526 [CSL-2526] update changes after covering remaining fees
Description
This pr tries to fix a bug on coin and fee selection, which was reported by QA.
The bug was caused because in some cases some changes were not included in the transaction, resulting in huge fees.
The CoinSelection works in many different stages:
The problem is that these additional inputs (last step) which cover the fees, may not be entirely consumed for the fees: we need to return what's left as change.
TODO:
UPDATE: we split additional changes to existing change outputs. We do this in FromGeneric module (instead of the Generic or Fees) for 2 reason: in this module change outputs are flatten to a list (instead of list of lists) and also it is less polymorphic, which means we can directly use divide function from Core.
round
vsceiling
in the use ofcalculateTxSizeLinear
.Linked issue
https://iohk.myjetbrains.com/youtrack/issue/CSL-2526
QA Steps
Here are some steps to reproduce the bug with tome good probabilities:
After trying the last step with different amounts and addresses, at some point the bug will appear and fees will be > 1 ADA.
The bug appears in these cases because the inputs in [100..1000] are enough to cover the <100 tx, but not enough to cover the fees. So an additional input needs to be selected. If we are "lucky" this input will be the > 1 ADA in which case we will have huge fees (without the fix).
Type of change
Screenshots (if available)
before
after