Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 01c16c7

Browse files
committed
[RCD-45] & [RCD-44] Review fee calculation
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.
1 parent b49b9de commit 01c16c7

File tree

5 files changed

+171
-136
lines changed

5 files changed

+171
-136
lines changed

wallet-new/src/Cardano/Wallet/Kernel/CoinSelection/FromGeneric.hs

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ instance IsValue Core.Coin where
7070
else a `Core.unsafeSubCoin` b
7171
valueRatio = \ a b -> coinToDouble a / coinToDouble b
7272
valueAdjust = \r d a -> coinFromDouble r (d * coinToDouble a)
73+
valueDiv = divCoin
7374

7475
instance CoinSelDom Cardano where
7576
type Input Cardano = Core.TxIn
@@ -191,14 +192,6 @@ feeOptions CoinSelectionOptions{..} = FeeOptions{
191192
type PickUtxo m = forall e. Core.Coin -- ^ Fee to still cover
192193
-> CoinSelT Core.Utxo e m (Maybe (Core.TxIn, Core.TxOutAux))
193194

194-
data CoinSelFinalResult = CoinSelFinalResult {
195-
csrInputs :: NonEmpty (Core.TxIn, Core.TxOutAux)
196-
-- ^ Picked inputs
197-
, csrOutputs :: NonEmpty Core.TxOutAux
198-
-- ^ Picked outputs
199-
, csrChange :: [Core.Coin]
200-
}
201-
202195
-- | Run coin selection
203196
--
204197
-- NOTE: Final UTxO is /not/ returned: coin selection runs /outside/ any wallet
@@ -215,8 +208,8 @@ runCoinSelT :: forall m. Monad m
215208
-> (forall utxo. PickFromUtxo utxo
216209
=> NonEmpty (Output (Dom utxo))
217210
-> CoinSelT utxo CoinSelHardErr m [CoinSelResult (Dom utxo)])
218-
-> CoinSelPolicy Core.Utxo m CoinSelFinalResult
219-
runCoinSelT opts pickUtxo policy (NE.sortBy (flip (comparing outVal)) -> request) utxo = do
211+
-> CoinSelPolicy Core.Utxo m (CoinSelFinalResult Cardano)
212+
runCoinSelT opts pickUtxo policy (NE.sortBy (flip (comparing outVal)) -> request) =
220213
-- NOTE: we sort the payees by output value, to maximise our chances of succees.
221214
-- In particular, let's consider a scenario where:
222215
--
@@ -233,44 +226,9 @@ runCoinSelT opts pickUtxo policy (NE.sortBy (flip (comparing outVal)) -> request
233226
--
234227
-- Therefore, just always considering them in order from large to small
235228
-- is probably a good idea.
236-
mSelection <- unwrapCoinSelT policy' utxo
237-
case mSelection of
238-
Left err -> return (Left err)
239-
Right ((css, additionalUtxo, additionalChange), _utxo') -> do
240-
let inps = concatMap selectedEntries
241-
(additionalUtxo : map coinSelInputs css)
242-
outs = map coinSelOutput css
243-
changesWithDust = splitChange additionalChange $ concatMap coinSelChange css
244-
let allInps = case inps of
245-
[] -> error "runCoinSelT: empty list of inputs"
246-
i:is -> i :| is
247-
originalOuts = case outs of
248-
[] -> error "runCoinSelT: empty list of outputs"
249-
o:os -> o :| os
250-
changes = changesRemoveDust (csoDustThreshold opts) changesWithDust
251-
return . Right $ CoinSelFinalResult allInps
252-
originalOuts
253-
changes
229+
evalCoinSelT policy'
254230
where
255-
-- we should have (x + (sum ls) = sum result), but this check could overflow.
256-
splitChange :: Value Cardano -> [Value Cardano] -> [Value Cardano]
257-
splitChange = go
258-
where
259-
go remaining [] = [remaining]
260-
-- we only create new change if for whatever reason there is none already
261-
-- or if is some overflow happens when we try to add.
262-
go remaining [a] = case valueAdd remaining a of
263-
Just newChange -> [newChange]
264-
Nothing -> [a, remaining]
265-
go remaining ls@(a : as) =
266-
let piece = divCoin remaining (length ls)
267-
newRemaining = unsafeValueSub remaining piece -- unsafe because of div.
268-
in case valueAdd piece a of
269-
Just newChange -> newChange : go newRemaining as
270-
Nothing -> a : go remaining as
271-
272-
policy' :: CoinSelT Core.Utxo CoinSelHardErr m
273-
([CoinSelResult Cardano], SelectedUtxo Cardano, Value Cardano)
231+
policy' :: CoinSelT Core.Utxo CoinSelHardErr m (CoinSelFinalResult Cardano)
274232
policy' = do
275233
mapM_ validateOutput request
276234
css <- intInputGrouping (csoInputGrouping opts)
@@ -346,7 +304,7 @@ validateOutput out =
346304
random :: forall m. MonadRandom m
347305
=> CoinSelectionOptions
348306
-> Word64 -- ^ Maximum number of inputs
349-
-> CoinSelPolicy Core.Utxo m CoinSelFinalResult
307+
-> CoinSelPolicy Core.Utxo m (CoinSelFinalResult Cardano)
350308
random opts maxInps =
351309
runCoinSelT opts pickUtxo
352310
$ Random.random Random.PrivacyModeOn maxInps . NE.toList
@@ -361,7 +319,7 @@ random opts maxInps =
361319
largestFirst :: forall m. Monad m
362320
=> CoinSelectionOptions
363321
-> Word64
364-
-> CoinSelPolicy Core.Utxo m CoinSelFinalResult
322+
-> CoinSelPolicy Core.Utxo m (CoinSelFinalResult Cardano)
365323
largestFirst opts maxInps =
366324
runCoinSelT opts pickUtxo
367325
$ LargestFirst.largestFirst maxInps . NE.toList

wallet-new/src/Cardano/Wallet/Kernel/CoinSelection/Generic.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module Cardano.Wallet.Kernel.CoinSelection.Generic (
2828
, mapCoinSelErr
2929
, mapCoinSelUtxo
3030
, unwrapCoinSelT
31+
, evalCoinSelT
3132
, wrapCoinSelT
3233
-- * Errors
3334
, CoinSelHardErr(..)
@@ -91,6 +92,7 @@ class Ord v => IsValue v where
9192
valueDist :: v -> v -> v -- ^ @|a - b|@
9293
valueRatio :: v -> v -> Double -- ^ @a / b@
9394
valueAdjust :: Rounding -> Double -> v -> Maybe v -- ^ @a * b@
95+
valueDiv :: v -> Int -> v -- ^ @a / k@
9496

9597
class ( Ord (Input dom)
9698
, IsValue (Value dom)
@@ -246,6 +248,10 @@ mapCoinSelUtxo inj proj act = wrapCoinSelT $ \st ->
246248
unwrapCoinSelT :: CoinSelT utxo e m a -> utxo -> m (Either e (a, utxo))
247249
unwrapCoinSelT act = runExceptT . runStrictStateT (unCoinSelT act)
248250

251+
-- | Unwrap the 'CoinSelT' stack, only getting the resulting selection
252+
evalCoinSelT :: Monad m => CoinSelT utxo e m a -> utxo -> m (Either e a)
253+
evalCoinSelT act = runExceptT . evalStrictStateT (unCoinSelT act)
254+
249255
-- | Inverse of 'unwrapCoinSelT'
250256
wrapCoinSelT :: Monad m
251257
=> (utxo -> m (Either e (a, utxo))) -> CoinSelT utxo e m a

0 commit comments

Comments
 (0)