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

Reduce overestimation of minimum UTxO values for Shelley-era addresses. #3416

Merged
merged 22 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
626204c
Miscellaneous formatting fixes.
jonathanknowles Jul 26, 2022
676f31d
Move `ProtocolMagic` type and functions to separate module.
jonathanknowles Jul 26, 2022
19c6d99
Move `dummyAddress` from `SelectionContext` to `SelectionConstraints`.
jonathanknowles Jul 21, 2022
521f06f
Rename `dummyAddress` to `maximumLengthChangeAddress`.
jonathanknowles Jul 29, 2022
b22d9c0
Add `BoundedAddressLength` type class and instances.
jonathanknowles Jul 26, 2022
2421a73
Add `BoundedAddressLength` constraint to functions in `Wallet`.
jonathanknowles Jul 26, 2022
cf15978
Reduce overestimation of minimum UTxO values for Shelley-era addresses.
jonathanknowles Jul 19, 2022
daeeb88
Replace `genCoinRange` with `chooseCoin` in `Migration.SelectionSpec`.
jonathanknowles Jul 29, 2022
7509c9c
Use a minimum-length address for the dummy output in module `Balance`.
jonathanknowles Jul 29, 2022
aa78b31
Raise `UnableToConstructChange.shortfall` values for `balanceTx` gold…
Anviking Jul 25, 2022
a049485
Raise cost of joining and quitting in `STAKE_POOLS_{JOIN,QUIT}_01x`.
jonathanknowles Jul 29, 2022
a813ed5
Use `errMsg403MinUTxOValue` instead of magic constants in integration…
jonathanknowles Aug 2, 2022
eab4ba9
Add `SelectionOutputCoinInsufficient` constructor to `SelectionOutput…
jonathanknowles Aug 1, 2022
c1f2c9a
Verify ada quantities of user-specified outputs in `CoinSelection.Int…
jonathanknowles Aug 1, 2022
7381e2f
Remove coverage check for `SelectionBalanceError.InsufficientMinCoinV…
jonathanknowles Aug 1, 2022
ac4ae3d
Verify values of type `SelectionOutputCoinInsufficientError`.
jonathanknowles Aug 1, 2022
87bc3fa
Adjust `TransactionSpec` to detect `SelectionOutputCoinInsufficientEr…
jonathanknowles Aug 1, 2022
4de24a4
Remove the now-unused `InsufficientMinCoinValues` error.
jonathanknowles Aug 1, 2022
10f52c9
Use a null address and minimal coin value within `performSelectionEmp…
jonathanknowles Aug 1, 2022
6a8d81d
Lower `UnableToConstructChange.shortfall` values for `balanceTx` gold…
jonathanknowles Aug 1, 2022
06b4fe0
Lower cost of joining and quitting in `STAKE_POOLS_{JOIN,QUIT}_01x`.
jonathanknowles Aug 1, 2022
cbc2c20
Use `SelectionContext` parameter consistently in `SelectionOutputError`.
jonathanknowles Aug 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions lib/core-integration/src/Test/Integration/Framework/DSL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -674,9 +674,20 @@ walletId =
-- | Min UTxO parameter for the test cluster.
minUTxOValue :: ApiEra -> Natural
minUTxOValue e
| e >= ApiBabbage = 1_107_670 -- needs to be overestimated for the sake of
-- long byron addresses
| e >= ApiAlonzo = 999_978 -- From 34482 lovelace per word
| e >= ApiBabbage = 995_610
-- This value is a slight overestimation for outputs with Shelley
-- addresses and no tokens.
--
-- However, it would be incorrect for outputs with Byron addresses,
-- where the lower bound would be greater by the following amount:
--
-- 4310 lovelace/byte * (86 - 57) byte ≈ 0.125 ada
--
-- However, this value appears to be fine for the purposes of
-- integration tests.
--
| e >= ApiAlonzo = 999_978
-- From 34482 lovelace/word.
| otherwise = 1_000_000

-- | Parameter in test cluster shelley genesis.
Expand Down
1 change: 1 addition & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ library
Cardano.Wallet.Primitive.Passphrase.Types
Cardano.Wallet.Primitive.Types
Cardano.Wallet.Primitive.Types.Address
Cardano.Wallet.Primitive.Types.Address.Constants
Cardano.Wallet.Primitive.Types.Coin
Cardano.Wallet.Primitive.Types.Hash
Cardano.Wallet.Primitive.Types.MinimumUTxO
Expand Down
38 changes: 21 additions & 17 deletions lib/core/src/Cardano/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,6 @@ import Cardano.Wallet.Primitive.Types.RewardAccount
( RewardAccount (..) )
import Cardano.Wallet.Primitive.Types.TokenBundle
( TokenBundle (..) )
import Cardano.Wallet.Primitive.Types.TokenMap
( TokenMap )
import Cardano.Wallet.Primitive.Types.TokenPolicy
( TokenName (UnsafeTokenName), TokenPolicyId (UnsafeTokenPolicyId) )
import Cardano.Wallet.Primitive.Types.TokenQuantity
Expand Down Expand Up @@ -474,7 +472,7 @@ import Cardano.Wallet.Transaction
import Control.Applicative
( (<|>) )
import Control.Arrow
( left )
( first, left )
import Control.DeepSeq
( NFData )
import Control.Monad
Expand Down Expand Up @@ -547,7 +545,7 @@ import Data.Map.Strict
import Data.Maybe
( fromMaybe, isJust, mapMaybe )
import Data.Proxy
( Proxy )
( Proxy (..) )
import Data.Quantity
( Quantity (..) )
import Data.Set
Expand Down Expand Up @@ -923,26 +921,28 @@ getWalletUtxoSnapshot ctx wid = do
(wallet, _, pending) <- withExceptT id (readWallet @ctx @s @k ctx wid)
pp <- liftIO $ currentProtocolParameters nl
era <- liftIO $ currentNodeEra nl
let bundles = availableUTxO @s pending wallet
let txOuts = availableUTxO @s pending wallet
& unUTxO
& F.toList
& fmap (view #tokens)
pure $ pairBundleWithMinAdaQuantity era pp <$> bundles
pure $ first (view #tokens) . pairTxOutWithMinAdaQuantity era pp <$> txOuts
where
nl = ctx ^. networkLayer
tl = ctx ^. transactionLayer @k

pairBundleWithMinAdaQuantity
pairTxOutWithMinAdaQuantity
:: Cardano.AnyCardanoEra
-> ProtocolParameters
-> TokenBundle
-> (TokenBundle, Coin)
pairBundleWithMinAdaQuantity era pp bundle =
(bundle, computeMinAdaQuantity $ view #tokens bundle)
-> TxOut
-> (TxOut, Coin)
pairTxOutWithMinAdaQuantity era pp out =
(out, computeMinAdaQuantity out)
where
computeMinAdaQuantity :: TokenMap -> Coin
computeMinAdaQuantity =
view #txOutputMinimumAdaQuantity (constraints tl era pp)
computeMinAdaQuantity :: TxOut -> Coin
computeMinAdaQuantity (TxOut addr bundle) =
view #txOutputMinimumAdaQuantity
(constraints tl era pp)
(addr)
(view #tokens bundle)

-- | List the wallet's UTxO statistics.
listUtxoStatistics
Expand Down Expand Up @@ -1988,6 +1988,8 @@ balanceTransactionWithSelectionStrategy
intCast @Word16 @Int $ view #maximumCollateralInputCount pp
, minimumCollateralPercentage =
view #minimumCollateralPercentage pp
, maximumLengthChangeAddress =
maxLengthAddressFor $ Proxy @k
}

selectionParams = SelectionParams
Expand Down Expand Up @@ -2174,8 +2176,8 @@ calcMinimumCoinValues
calcMinimumCoinValues ctx era outs = do
pp <- currentProtocolParameters nl
pure
$ view #txOutputMinimumAdaQuantity (constraints tl era pp)
. view (#tokens . #tokens) <$> outs
$ uncurry (view #txOutputMinimumAdaQuantity (constraints tl era pp))
. (\o -> (view #address o, view (#tokens . #tokens) o)) <$> outs
where
nl = ctx ^. networkLayer
tl = ctx ^. transactionLayer @k
Expand Down Expand Up @@ -2251,6 +2253,8 @@ selectAssets ctx era pp params transform = do
intCast @Word16 @Int $ view #maximumCollateralInputCount pp
, minimumCollateralPercentage =
view #minimumCollateralPercentage pp
, maximumLengthChangeAddress =
maxLengthAddressFor $ Proxy @k
}
let selectionParams = SelectionParams
{ assetsToMint =
Expand Down
29 changes: 28 additions & 1 deletion lib/core/src/Cardano/Wallet/Api/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ import Cardano.Wallet.Primitive.AddressDerivation
)
import Cardano.Wallet.Primitive.AddressDerivation.SharedKey
( purposeCIP1854 )
import Cardano.Wallet.Primitive.AddressDerivation.Shelley
( ShelleyKey )
import Cardano.Wallet.Primitive.AddressDiscovery.Random
( RndState )
import Cardano.Wallet.Primitive.AddressDiscovery.Sequential
Expand Down Expand Up @@ -1134,7 +1136,32 @@ toApiNetworkParameters (NetworkParameters gp sp pp) txConstraints toEpochInfo =
$ view #decentralizationLevel pp
, desiredPoolNumber = view #desiredNumberOfStakePools pp
, minimumUtxoValue = toApiCoin $
txOutputMinimumAdaQuantity txConstraints TokenMap.empty
-- NOTE:
--
-- In eras prior to Babbage, the ledger minimum UTxO function was
-- independent of the length of an address.
--
-- However, from the Babbage era onwards, the ledger minimum UTxO
-- function is *dependent* on the length of an address: longer
-- addresses give rise to greater minimum UTxO values.
--
-- Since address lengths are variable, there is no single ideal
-- constant that we can return here.
--
-- Therefore, we return a "minimum" UTxO quantity that is likely to
-- be more useful than others: the quantity required to send a
-- 9-byte ada quantity (and no non-ada tokens) to the longest
-- possible Shelley address.
--
-- We should consider deprecating this parameter, and replacing it
-- with era-specific protocol parameters such as:
--
-- - lovelacePerUTxOWord (Alonzo)
-- - lovelacePerUTxOByte (Babbage)
--
txOutputMinimumAdaQuantity txConstraints
(AD.maxLengthAddressFor $ Proxy @ShelleyKey)
TokenMap.empty
, eras = apiEras
, maximumCollateralInputCount =
view #maximumCollateralInputCount pp
Expand Down
8 changes: 3 additions & 5 deletions lib/core/src/Cardano/Wallet/CoinSelection.hs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ data SelectionConstraints = SelectionConstraints
-- ^ Amount that should be taken from/returned back to the wallet for
-- each stake key registration/de-registration in the transaction.
, computeMinimumAdaQuantity
:: TokenMap -> Coin
:: Address -> TokenMap -> Coin
-- ^ Computes the minimum ada quantity required for a given output.
, computeMinimumCost
:: SelectionSkeleton -> Coin
Expand All @@ -239,6 +239,8 @@ data SelectionConstraints = SelectionConstraints
:: Natural
-- ^ Specifies the minimum required amount of collateral as a
-- percentage of the total transaction fee.
, maximumLengthChangeAddress
:: Address
}
deriving Generic

Expand All @@ -255,10 +257,6 @@ toInternalSelectionConstraints SelectionConstraints {..} =
txOutMaxCoin
, maximumOutputTokenQuantity =
txOutMaxTokenQuantity
, maximumLengthChangeAddress =
-- TODO:
-- Specify a real address of the maximum length here.
Address ""
, ..
}

Expand Down
27 changes: 14 additions & 13 deletions lib/core/src/Cardano/Wallet/CoinSelection/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Copyright: © 2021 IOHK
Expand Down Expand Up @@ -159,7 +158,7 @@ data SelectionConstraints ctx = SelectionConstraints
-- ^ Amount that should be taken from/returned back to the wallet for
-- each stake key registration/de-registration in the transaction.
, computeMinimumAdaQuantity
:: TokenMap -> Coin
:: Address ctx -> TokenMap -> Coin
-- ^ Computes the minimum ada quantity required for a given output.
, computeMinimumCost
:: SelectionSkeleton ctx -> Coin
Expand Down Expand Up @@ -382,7 +381,8 @@ performSelectionCollateral balanceResult cs ps
-- | Returns a selection's ordinary outputs and change outputs in a single list.
--
-- Since change outputs do not have addresses at the point of generation,
-- this function assigns all change outputs with a dummy change address.
-- this function assigns all change outputs with a dummy change address
-- of the maximum possible length.
--
selectionAllOutputs
:: SelectionConstraints ctx
Expand Down Expand Up @@ -827,6 +827,7 @@ verifySelectionOutputCoinsSufficient cs _ps selection =
minimumExpectedCoin :: Coin
minimumExpectedCoin =
(cs ^. #computeMinimumAdaQuantity)
(fst output)
(snd output ^. #tokens)

--------------------------------------------------------------------------------
Expand Down Expand Up @@ -971,6 +972,7 @@ verifyInsufficientMinCoinValueError cs _ps e =
reportedMinCoinValue = e ^. #expectedMinCoinValue
verifiedMinCoinValue =
(cs ^. #computeMinimumAdaQuantity)
(fst reportedOutput)
(snd reportedOutput ^. #tokens)

--------------------------------------------------------------------------------
Expand Down Expand Up @@ -1111,7 +1113,7 @@ verifyUnableToConstructChangeError cs ps errorOriginal =
-- A modified set of constraints that should always allow the
-- successful creation of a selection:
cs' = cs
{ computeMinimumAdaQuantity = const $ Coin 0
{ computeMinimumAdaQuantity = const $ const $ Coin 0
, computeMinimumCost = const $ Coin 0
, computeSelectionLimit = const Balance.NoLimit
}
Expand Down Expand Up @@ -1434,19 +1436,18 @@ prepareOutputsInternal constraints outputsUnprepared
-- quantity required to make a particular output valid.
--
prepareOutputsWith
:: Functor f
=> (TokenMap -> Coin)
:: forall f address. Functor f
=> (address -> TokenMap -> Coin)
-> f (address, TokenBundle)
-> f (address, TokenBundle)
prepareOutputsWith minCoinValueFor =
fmap $ fmap augmentBundle
fmap augmentBundle
where
augmentBundle :: TokenBundle -> TokenBundle
augmentBundle bundle
| TokenBundle.getCoin bundle == Coin 0 =
bundle & set #coin (minCoinValueFor (view #tokens bundle))
| otherwise =
bundle
augmentBundle :: (address, TokenBundle) -> (address, TokenBundle)
augmentBundle (addr, bundle) = (addr,) $
if TokenBundle.getCoin bundle == Coin 0
then bundle & set #coin (minCoinValueFor addr (view #tokens bundle))
else bundle

-- | Indicates a problem when preparing outputs for a coin selection.
--
Expand Down
26 changes: 14 additions & 12 deletions lib/core/src/Cardano/Wallet/CoinSelection/Internal/Balance.hs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ data SelectionConstraints ctx = SelectionConstraints
-- the 'TokenBundleSizeAssessor' type to learn about the expected
-- properties of this field.
, computeMinimumAdaQuantity
:: TokenMap -> Coin
:: Address ctx -> TokenMap -> Coin
-- ^ Computes the minimum ada quantity required for a given output.
, computeMinimumCost
:: SelectionSkeleton ctx -> Coin
Expand Down Expand Up @@ -851,11 +851,11 @@ performSelectionEmpty performSelectionFn constraints params =
transform :: a -> (NonEmpty (Address ctx, TokenBundle) -> a) -> a
transform x y = maybe x y $ NE.nonEmpty $ view #outputsToCover params

dummyAddress :: Address ctx
dummyAddress = maximumLengthChangeAddress constraints

dummyOutput :: (Address ctx, TokenBundle)
dummyOutput =
( maximumLengthChangeAddress constraints
, TokenBundle.fromCoin minCoin
)
dummyOutput = (dummyAddress, TokenBundle.fromCoin minCoin)

-- The 'performSelectionNonEmpty' function imposes a precondition that all
-- outputs must have at least the minimum ada quantity. Therefore, the
Expand All @@ -872,7 +872,8 @@ performSelectionEmpty performSelectionFn constraints params =
minCoin :: Coin
minCoin = max
(Coin 1)
(view #computeMinimumAdaQuantity constraints TokenMap.empty)
(view #computeMinimumAdaQuantity constraints dummyAddress TokenMap.empty
)

performSelectionNonEmpty
:: forall m ctx. (HasCallStack, MonadRandom m, SelectionContext ctx)
Expand Down Expand Up @@ -917,6 +918,7 @@ performSelectionNonEmpty constraints params
, computeSelectionLimit
, maximumOutputAdaQuantity
, maximumOutputTokenQuantity
, maximumLengthChangeAddress
} = constraints
SelectionParams
{ outputsToCover
Expand Down Expand Up @@ -956,17 +958,17 @@ performSelectionNonEmpty constraints params
mkInsufficientMinCoinValueError
:: (Address ctx, TokenBundle)
-> Maybe (InsufficientMinCoinValueError ctx)
mkInsufficientMinCoinValueError o
| view #coin (snd o) >= expectedMinCoinValue =
mkInsufficientMinCoinValueError (addr, bundle)
| view #coin bundle >= expectedMinCoinValue =
Nothing
| otherwise =
Just $ InsufficientMinCoinValueError
{ expectedMinCoinValue
, outputWithInsufficientAda = o
, outputWithInsufficientAda = (addr, bundle)
}
where
expectedMinCoinValue = computeMinimumAdaQuantity
(view #tokens $ snd o)
expectedMinCoinValue = computeMinimumAdaQuantity addr
(view #tokens bundle)

-- Given a UTxO index that corresponds to a valid selection covering
-- 'outputsToCover', 'predictChange' yields a non-empty list of assets
Expand Down Expand Up @@ -1084,7 +1086,7 @@ performSelectionNonEmpty constraints params
where
mChangeGenerated :: Either UnableToConstructChangeError [TokenBundle]
mChangeGenerated = makeChange MakeChangeCriteria
{ minCoinFor = computeMinimumAdaQuantity
{ minCoinFor = computeMinimumAdaQuantity maximumLengthChangeAddress
, bundleSizeAssessor = TokenBundleSizeAssessor assessTokenBundleSize
, requiredCost
, extraCoinSource
Expand Down
17 changes: 14 additions & 3 deletions lib/core/src/Cardano/Wallet/Primitive/Migration/Selection.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ module Cardano.Wallet.Primitive.Migration.Selection

import Prelude

import Cardano.Wallet.Primitive.Types.Address.Constants
( maxLengthAddress )
import Cardano.Wallet.Primitive.Types.Coin
( Coin (..) )
import Cardano.Wallet.Primitive.Types.TokenBundle
Expand Down Expand Up @@ -274,7 +276,15 @@ assignMinimumAdaQuantity :: TxConstraints -> TokenMap -> TokenBundle
assignMinimumAdaQuantity constraints m =
TokenBundle c m
where
c = txOutputMinimumAdaQuantity constraints m
-- Using @maxLengthAddressFor $ Proxy @k@ via @constraints@ would not help
-- here, as outputs created by the migration algorithm are assigned with
-- user-defined addresses.
--
-- Something we /could/ do would be to pass in the actual user-defined
-- addresses here, since they are available in the 'createMigrationPlan'
-- server handler.
--
c = txOutputMinimumAdaQuantity constraints maxLengthAddress m

--------------------------------------------------------------------------------
-- Adding value to outputs
Expand Down Expand Up @@ -835,8 +845,9 @@ checkOutputMinimumAdaQuantities constraints selection =
, expectedMinimumAdaQuantity
}
where
expectedMinimumAdaQuantity =
txOutputMinimumAdaQuantity constraints (view #tokens outputBundle)
expectedMinimumAdaQuantity = txOutputMinimumAdaQuantity constraints
maxLengthAddress
(view #tokens outputBundle)

--------------------------------------------------------------------------------
-- Selection correctness: output sizes
Expand Down
Loading