Skip to content

Commit

Permalink
Try #2552:
Browse files Browse the repository at this point in the history
  • Loading branch information
iohk-bors[bot] authored Mar 5, 2021
2 parents f4d697a + 7e29b19 commit b650847
Show file tree
Hide file tree
Showing 20 changed files with 1,251 additions and 565 deletions.
1 change: 1 addition & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ test-suite unit
, cardano-addresses
, cardano-api
, cardano-crypto
, cardano-numeric
, cardano-wallet-core
, cardano-wallet-launcher
, cardano-wallet-test-utils
Expand Down
1 change: 1 addition & 0 deletions lib/core/src/Cardano/Wallet.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ selectAssets ctx (utxo, cp, pending) tx outs transform = do
sel <- performSelection
(calcMinimumCoinValue tl pp)
(calcMinimumCost tl pp tx)
(assessTokenBundleSize tl)
(initSelectionCriteria tl pp tx utxo outs)
liftIO $ traceWith tr $ MsgSelectionDone sel
withExceptT ErrSelectAssetsSelectionError $ except $
Expand Down
342 changes: 129 additions & 213 deletions lib/core/src/Cardano/Wallet/Primitive/CoinSelection/MA/RoundRobin.hs

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion lib/core/src/Cardano/Wallet/Primitive/Types/Coin.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ module Cardano.Wallet.Primitive.Types.Coin
, subtractCoin
, sumCoins
, distance
, equipartition

) where

import Prelude

import Cardano.Numeric.Util
( equipartitionNatural )
import Control.DeepSeq
( NFData (..) )
import Control.Monad
Expand All @@ -38,6 +41,8 @@ import Data.Foldable
( foldl' )
import Data.Hashable
( Hashable )
import Data.List.NonEmpty
( NonEmpty (..) )
import Data.Quantity
( Quantity (..) )
import Data.Text.Class
Expand Down Expand Up @@ -111,6 +116,9 @@ coinToInteger = fromIntegral . unCoin
coinToNatural :: Coin -> Natural
coinToNatural = fromIntegral . unCoin

unsafeNaturalToCoin :: Natural -> Coin
unsafeNaturalToCoin = Coin . fromIntegral

{-------------------------------------------------------------------------------
Checks
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -146,7 +154,26 @@ addCoin (Coin a) (Coin b) = Coin (a + b)
sumCoins :: Foldable t => t Coin -> Coin
sumCoins = foldl' addCoin (Coin 0)


-- | Absolute difference between two coin amounts. The result is never negative.
distance :: Coin -> Coin -> Coin
distance (Coin a) (Coin b) = if a < b then Coin (b - a) else Coin (a - b)

-- | Computes the equipartition of a coin into 'n' smaller coins.
--
-- An /equipartition/ of a coin is a /partition/ of that coin into 'n' smaller
-- coins whose values differ by no more than 1.
--
-- The resultant list is sorted in ascending order.
--
equipartition
:: Coin
-- ^ The coin to be partitioned.
-> NonEmpty a
-- ^ Represents the number of portions in which to partition the coin.
-> NonEmpty Coin
-- ^ The partitioned coins.
equipartition c =
-- Note: the natural-to-coin conversion is safe, as equipartitioning always
-- guarantees to produce values that are less than or equal to the original
-- value.
fmap unsafeNaturalToCoin . equipartitionNatural (coinToNatural c)
53 changes: 53 additions & 0 deletions lib/core/src/Cardano/Wallet/Primitive/Types/TokenBundle.hs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ module Cardano.Wallet.Primitive.Types.TokenBundle
, adjustQuantity
, removeQuantity

-- * Partitioning
, equipartitionAssets
, equipartitionQuantitiesWithUpperBound

-- * Policies
, hasPolicy

Expand Down Expand Up @@ -110,6 +114,7 @@ import GHC.TypeLits

import qualified Cardano.Wallet.Primitive.Types.Coin as Coin
import qualified Cardano.Wallet.Primitive.Types.TokenMap as TokenMap
import qualified Data.List.NonEmpty as NE

--------------------------------------------------------------------------------
-- Types
Expand Down Expand Up @@ -365,6 +370,54 @@ adjustQuantity b a f = b { tokens = TokenMap.adjustQuantity (tokens b) a f }
removeQuantity :: TokenBundle -> AssetId -> TokenBundle
removeQuantity b a = b { tokens = TokenMap.removeQuantity (tokens b) a }

--------------------------------------------------------------------------------
-- Partitioning
--------------------------------------------------------------------------------

-- | Partitions a token bundle into 'n' smaller bundles, where the asset sets
-- of the resultant bundles are disjoint.
--
-- In the resultant bundles, the smallest asset set size and largest asset set
-- size will differ by no more than 1.
--
-- The ada 'Coin' quantity is equipartitioned across the resulting bundles.
--
-- The quantities of each non-ada asset are unchanged.
--
equipartitionAssets
:: TokenBundle
-- ^ The token bundle to be partitioned.
-> NonEmpty a
-- ^ Represents the number of portions in which to partition the bundle.
-> NonEmpty TokenBundle
-- ^ The partitioned bundles.
equipartitionAssets (TokenBundle c m) count =
NE.zipWith TokenBundle cs ms
where
cs = Coin.equipartition c count
ms = TokenMap.equipartitionAssets m count

-- | Partitions a token bundle into 'n' smaller bundles, where the quantity of
-- each token is equipartitioned across the resultant bundles, with the goal
-- that no token quantity in any of the resultant bundles exceeds the given
-- upper bound.
--
-- The value 'n' is computed automatically, and is the minimum value required
-- to achieve the goal that no token quantity in any of the resulting bundles
-- exceeds the maximum allowable token quantity.
--
equipartitionQuantitiesWithUpperBound
:: TokenBundle
-> TokenQuantity
-- ^ Maximum allowable token quantity.
-> NonEmpty TokenBundle
-- ^ The partitioned bundles.
equipartitionQuantitiesWithUpperBound (TokenBundle c m) maxQuantity =
NE.zipWith TokenBundle cs ms
where
cs = Coin.equipartition c ms
ms = TokenMap.equipartitionQuantitiesWithUpperBound m maxQuantity

--------------------------------------------------------------------------------
-- Policies
--------------------------------------------------------------------------------
Expand Down
109 changes: 109 additions & 0 deletions lib/core/src/Cardano/Wallet/Primitive/Types/TokenMap.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ module Cardano.Wallet.Primitive.Types.TokenMap
, removeQuantity
, maximumQuantity

-- * Partitioning
, equipartitionAssets
, equipartitionQuantities
, equipartitionQuantitiesWithUpperBound

-- * Policies
, hasPolicy

Expand All @@ -91,6 +96,8 @@ import Prelude hiding

import Algebra.PartialOrd
( PartialOrd (..) )
import Cardano.Numeric.Util
( equipartitionNatural )
import Cardano.Wallet.Primitive.Types.TokenPolicy
( TokenName, TokenPolicyId )
import Cardano.Wallet.Primitive.Types.TokenQuantity
Expand All @@ -117,6 +124,8 @@ import Data.Map.Strict.NonEmptyMap
( NonEmptyMap )
import Data.Maybe
( fromMaybe, isJust )
import Data.Ratio
( (%) )
import Data.Set
( Set )
import Data.Text.Class
Expand All @@ -127,6 +136,8 @@ import GHC.Generics
( Generic )
import GHC.TypeLits
( ErrorMessage (..), TypeError )
import Numeric.Natural
( Natural )
import Quiet
( Quiet (..) )

Expand Down Expand Up @@ -640,6 +651,104 @@ maximumQuantity =
| otherwise =
champion

--------------------------------------------------------------------------------
-- Partitioning
--------------------------------------------------------------------------------

-- | Partitions a token map into 'n' smaller maps, where the asset sets of the
-- resultant maps are disjoint.
--
-- In the resultant maps, the smallest asset set size and largest asset set
-- size will differ by no more than 1.
--
-- The quantities of each asset are unchanged.
--
equipartitionAssets
:: TokenMap
-- ^ The token map to be partitioned.
-> NonEmpty a
-- ^ Represents the number of portions in which to partition the token map.
-> NonEmpty TokenMap
-- ^ The partitioned maps.
equipartitionAssets m mapCount =
fromFlatList <$> NE.unfoldr generateChunk (assetCounts, toFlatList m)
where
-- The total number of assets.
assetCount :: Int
assetCount = Set.size $ getAssets m

-- How many asset quantities to include in each chunk.
assetCounts :: NonEmpty Int
assetCounts = fromIntegral @Natural @Int <$>
equipartitionNatural (fromIntegral @Int @Natural assetCount) mapCount

-- Generates a single chunk of asset quantities.
generateChunk :: (NonEmpty Int, [aq]) -> ([aq], Maybe (NonEmpty Int, [aq]))
generateChunk (c :| mcs, aqs) = case NE.nonEmpty mcs of
Just cs -> (prefix, Just (cs, suffix))
Nothing -> (aqs, Nothing)
where
(prefix, suffix) = L.splitAt c aqs

-- | Partitions a token map into 'n' smaller maps, where the quantity of each
-- token is equipartitioned across the resultant maps.
--
-- In the resultant maps, the smallest quantity and largest quantity of a given
-- token will differ by no more than 1.
--
-- The resultant list is sorted into ascending order when maps are compared
-- with the 'leq' function.
--
equipartitionQuantities
:: TokenMap
-- ^ The map to be partitioned.
-> NonEmpty a
-- ^ Represents the number of portions in which to partition the map.
-> NonEmpty TokenMap
-- ^ The partitioned maps.
equipartitionQuantities m count =
F.foldl' accumulate (empty <$ count) (toFlatList m)
where
accumulate
:: NonEmpty TokenMap
-> (AssetId, TokenQuantity)
-> NonEmpty TokenMap
accumulate maps (asset, quantity) = NE.zipWith (<>) maps $
singleton asset <$>
TokenQuantity.equipartition quantity count

-- | Partitions a token map into 'n' smaller maps, where the quantity of each
-- token is equipartitioned across the resultant maps, with the goal that no
-- token quantity in any of the resultant maps exceeds the given upper bound.
--
-- The value 'n' is computed automatically, and is the minimum value required
-- to achieve the goal that no token quantity in any of the resulting maps
-- exceeds the maximum allowable token quantity.
--
equipartitionQuantitiesWithUpperBound
:: TokenMap
-> TokenQuantity
-- ^ Maximum allowable token quantity.
-> NonEmpty TokenMap
-- ^ The partitioned maps.
equipartitionQuantitiesWithUpperBound m (TokenQuantity maxQuantity)
| maxQuantity == 0 =
maxQuantityZeroError
| currentMaxQuantity <= maxQuantity =
m :| []
| otherwise =
equipartitionQuantities m (() :| replicate extraPartCount ())
where
TokenQuantity currentMaxQuantity = maximumQuantity m

extraPartCount :: Int
extraPartCount = floor $ pred currentMaxQuantity % maxQuantity

maxQuantityZeroError = error $ unwords
[ "equipartitionQuantitiesWithUpperBound:"
, "the maximum allowable token quantity cannot be zero."
]

--------------------------------------------------------------------------------
-- Policies
--------------------------------------------------------------------------------
Expand Down
22 changes: 22 additions & 0 deletions lib/core/src/Cardano/Wallet/Primitive/Types/TokenQuantity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module Cardano.Wallet.Primitive.Types.TokenQuantity
, subtract
, pred
, succ
, equipartition

-- * Tests
, isNonZero
Expand All @@ -29,6 +30,8 @@ module Cardano.Wallet.Primitive.Types.TokenQuantity
import Prelude hiding
( pred, subtract, succ )

import Cardano.Numeric.Util
( equipartitionNatural )
import Control.DeepSeq
( NFData (..) )
import Control.Monad
Expand All @@ -39,6 +42,8 @@ import Data.Functor
( ($>) )
import Data.Hashable
( Hashable )
import Data.List.NonEmpty
( NonEmpty (..) )
import Data.Text.Class
( FromText (..), ToText (..) )
import Fmt
Expand Down Expand Up @@ -122,6 +127,23 @@ pred (TokenQuantity q) = TokenQuantity $ Prelude.pred q
succ :: TokenQuantity -> TokenQuantity
succ (TokenQuantity q) = TokenQuantity $ Prelude.succ q

-- | Computes the equipartition of a token quantity into 'n' smaller quantities.
--
-- An /equipartition/ of a token quantity is a /partition/ of that quantity
-- into 'n' smaller quantities whose values differ by no more than 1.
--
-- The resultant list is sorted in ascending order.
--
equipartition
:: TokenQuantity
-- ^ The token quantity to be partitioned.
-> NonEmpty a
-- ^ Represents the number of portions in which to partition the quantity.
-> NonEmpty TokenQuantity
-- ^ The partitioned quantities.
equipartition q =
fmap TokenQuantity . equipartitionNatural (unTokenQuantity q)

--------------------------------------------------------------------------------
-- Tests
--------------------------------------------------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions lib/core/src/Cardano/Wallet/Primitive/Types/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Cardano.Wallet.Primitive.Types.Tx
, UnsignedTx (..)
, TransactionInfo (..)
, Direction (..)
, TokenBundleSizeAssessment (..)

-- * Functions
, fromTransactionInfo
Expand Down Expand Up @@ -430,3 +431,15 @@ txMetadataIsNull (TxMetadata md) = Map.null md
toTxHistory :: TransactionInfo -> (Tx, TxMeta)
toTxHistory info =
(fromTransactionInfo info, txInfoMeta info)

-- | Indicates the size of a token bundle relative to the upper limit of what
-- can be included in a single transaction output, defined by the protocol.
--
data TokenBundleSizeAssessment
= TokenBundleSizeWithinLimit
-- ^ Indicates that the size of a token bundle does not exceed the maximum
-- size that can be included in a transaction output.
| TokenBundleSizeExceedsLimit
-- ^ Indicates that the size of a token bundle exceeds the maximum size
-- that can be included in a transaction output.
deriving (Eq, Generic, Show)
Loading

0 comments on commit b650847

Please sign in to comment.