-
Notifications
You must be signed in to change notification settings - Fork 629
[RCD-45] & [RCD-44] Review fee calculation entirely #3861
[RCD-45] & [RCD-44] Review fee calculation entirely #3861
Conversation
1e88b75
to
dc35732
Compare
dc35732
to
02bd85a
Compare
02bd85a
to
2dbcd7a
Compare
So, basically, by conflating a bit the selected entries and changes, a lot of things become easier. At the price of one thing: fairness. The previous code was splitting fee across change proportionally to inputs. So here, I just split the fee across all changes, regardless of the input. So everyone's got to pay the same part of for the transaction. One could see it as another type of fairness 🙃 ... But that's also a lot simpler to handle, because we can just manipulate all inputs and all changes directly and compute fee for those directly.
2dbcd7a
to
01c16c7
Compare
changes = changesRemoveDust (csoDustThreshold opts) changesWithDust | ||
return . Right $ CoinSelFinalResult allInps | ||
originalOuts | ||
changes |
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.
Checked moved inside adjustForFees
newRemaining = unsafeValueSub remaining piece -- unsafe because of div. | ||
in case valueAdd piece a of | ||
Just newChange -> newChange : go newRemaining as | ||
Nothing -> a : go remaining as |
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.
Moved in Generic/Fee
and is now part of the senderPaysFee
algorithm.
@@ -70,6 +70,7 @@ instance IsValue Core.Coin where | |||
else a `Core.unsafeSubCoin` b | |||
valueRatio = \ a b -> coinToDouble a / coinToDouble b | |||
valueAdjust = \r d a -> coinFromDouble r (d * coinToDouble a) | |||
valueDiv = divCoin |
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.
splitChange
ranked higher up in the abstraction, so we need a way to divide Value dom
into k
pieces.
|
||
type PickUtxo m utxo | ||
= Value (Dom utxo) | ||
-> CoinSelT utxo CoinSelHardErr m (Maybe (UtxoEntry (Dom utxo))) |
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.
Just a type-alias
, csrOutputs :: NonEmpty Core.TxOutAux | ||
-- ^ Picked outputs | ||
, csrChange :: [Core.Coin] | ||
} |
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.
Moved one level higher in the abstraction as CoinSelFinalResult dom
-- 2/ | ||
-- We try to cover fee with the available change by substracting equally | ||
-- across all inputs. There's no fairness in that in the case of a | ||
-- multi-account transaction. Everyone pays the same part. |
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.
TODO: Amend this comment. Not really on the spot. We still divvy fee proportionally across all changes. Thus, the output:change ratio is more-or-less preserved. In the end, it may be less accurate but fairness still applies here.
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, just some requests for some comments clarifying a few points.
senderPaysFee pickUtxo upperBound css | ||
where | ||
upperBound = feeUpperBound feeOptions css | ||
let inps = concatMap (selectedEntries . coinSelInputs) css |
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.
Suggested comment: We lose the relationship between the transaction outputs and their corresponding inputs/change outputs here. This is a decision we may wish to revisit later. For now however note that since (a) coin selection tries to establish a particular ratio between payment outputs and change outputs (currently it aims for an average of 1:1), and moreover (b) coin selection currently only generates a single change output per payment output, distributing the fee proportionally across all change outputs is roughly equivalent to distributing it proportionally over the payment outputs (roughly, not exactly, because the 1:1 proportion is best effort only, and may in some cases be wildly different). Note that for (a) we don't need the ratio to be 1:1, the above reasoning will remain true for any proportion 1:n
. For (b) however, if coin selection starts creating multiple outputs, and this number may vary, then losing the connection between outputs and change outputs will mean that that some outputs may pay a larger percentage of the fee (depending on how many change outputs the algorithm happened to choose).
-- 2/ | ||
-- We try to cover fee with the available change by substracting equally | ||
-- across all inputs. There's no fairness in that in the case of a | ||
-- multi-account transaction. Everyone pays the same part. |
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 "There's no fairness.." comment is not quite right, see my suggested comment above. Might want to refer back to that here.
let (change', fee') = reduceChangeOutputs fee (coinSelChange cs) | ||
in (cs { coinSelChange = change' }, fee') | ||
|
||
-- we should have (x + (sum ls) = sum result), but this check could overflow. |
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.
splitChange
needs a comment what it does. Note that it makes no attempt to divvy the new output proportionally over the change outputs. This means that if we happen to pick a very large UTxO entry, adding this evenly rather than proportionally might skew the payment:change ratio a lot. Could consider defining this in terms of divvy
instead (but if you do, we need to be wary of lower bounds/upper bounds in the division calculation, since the requirements may not be the same for subtracting stuff and adding stuff.). Can be reconsidered later.
= round $ calculateTxSizeLinear linearFeePolicy | ||
$ hi $ estimateSize boundAddrAttrSize boundTxAttrSize ins outs | ||
= ceiling $ calculateTxSizeLinear linearFeePolicy | ||
$ hi $ estimateSize boundAddrAttrSize boundTxAttrSize ins outs |
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.
Core nodes use ceiling
in this computation and not round
.
The 'estimateCardanoFee' was using 'round' but as pointed out by @duncan, core nodes use 'ceiling' which may cause some divergence in the fee computation.
4a32438
to
b6c7208
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.
Simple dust filtering may fix the following issue: I had a wallet with 2 utxos: 1Ada and 10Lovelace. I tried to empty the wallet and the tx failed (not accepted on the network). This is the tx generated (note the output with amount = 0)
{"data":[
{
"id":"37bdc959ff2e35a85dd6abdb196b3431d46a5fb67ff2e3d1e197692188204661",
"confirmations":0,
"amount":1000010,"inputs":[{"address":"DdzFFzCqrhsnFXjLjbgFbZHmnwvexoQLXZkFQ9RQeGuNWsLEkUDFvyYZy1bBzfguya752AT9TbzXYXiVrYnU5CdXDzptuj3RA236SLY4","amount":10},{"address":"DdzFFzCqrhsnFXjLjbgFbZHmnwvexoQLXZkFQ9RQeGuNWsLEkUDFvyYZy1bBzfguya752AT9TbzXYXiVrYnU5CdXDzptuj3RA236SLY4","amount":1000000}],"outputs":[{"address":"DdzFFzCqrht7ZQTTrMXbvdQFt9k4FYJEwNdoRhucjiBCCQn2fGvt4Heu9CJCURPbD6c6ACKvTzw74o4uo2U1Wg2o785DoKuSPEArzLwv","amount":0},{"address":"DdzFFzCqrhsnryDPMxqHDS8zRe8Lrw1AtsXyJh25iZvt2tRzgtBKCdfBe2WUNCyUacuqFEykXKpALCY6eR3ENhxozg2mrE68iRUWXDTB","amount":819800}],"type":"foreign","direction":"outgoing","creationTime":"2018-11-19T10:21:55.06173","status":{"tag":"applying","data":{}}}
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! I manually tested cases where multiple inputs are needed, cases where the wallet empties and combination of these cases. It always works ok.
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! I manually tested cases where multiple inputs are needed, cases where the wallet empties and combination of these cases. It always works ok.
Merging this into |
Description
So, basically, by conflating a bit the selected entries and changes, a
lot of things become easier. At the price of one thing: fairness.
The previous code was splitting fee across change proportionally to
inputs. So here, I just split the fee across all changes, regardless of
the input.
One could see it as another type of fairness 🙃 ... But
that's also a lot simpler to handle, because we can just manipulate all
inputs and all changes directly and compute fee for those directly.
Linked issue
RCD-45
RCD-44
Type of change
Developer checklist
Testing checklist
QA Steps
Screenshots (if available)
How to merge
Send the message
bors r+
to merge this PR. For more information, seedocs/how-to/bors.md
.