Skip to content

Commit

Permalink
min ada value calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
polinavino authored and Jared Corduan committed Jan 20, 2021
1 parent ca7227f commit b23e200
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 112 deletions.
58 changes: 37 additions & 21 deletions shelley-ma/impl/src/Cardano/Ledger/Mary/Value.hs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import Cardano.Ledger.Val
EncodeMint (..),
Val (..),
)
import Cardano.Prelude (cborError)
import Cardano.Prelude (HeapWords (..), cborError)
import Control.DeepSeq (NFData (..))
import Control.Monad (forM_)
import Control.Monad.ST (runST)
Expand Down Expand Up @@ -159,26 +159,42 @@ instance CC.Crypto crypto => Val (Value crypto) where
modifyCoin f (Value c m) = Value n m where (Coin n) = f (Coin c)
pointwise p (Value c x) (Value d y) = (p c d) && (pointWise (pointWise p) x y)

size (Value _ v) =
-- add uint for the Coin portion in this size calculation
foldr accum uint v
where
-- add addrHashLen for each Policy ID
accum u ans = foldr accumIns (ans + policyIdLen) u
where
-- add assetNameLen and uint for each asset of that Policy ID
accumIns _ ans1 = ans1 + assetNameLen + uint
-- TODO move these constants somewhere (they are also specified in CDDL)
uint :: Integer
uint = 9

assetNameLen :: Integer
assetNameLen = 32

-- TODO dig up these constraints from Era
-- address hash length is always same as Policy ID length
policyIdLen :: Integer
policyIdLen = 28
-- returns the size, in Word64's, of the CompactValue representation of Value
size vv@(Value _ v)
-- when Value contains only ada
| v == mempty = fromIntegral $ adaWords
-- when Value contains ada as well as other tokens
-- sums up :
-- i) adaWords : the space taken up by the ada amount
-- ii) numberMulAssets : the space taken by number of words used to store number of non-ada assets in a value
-- iii) the space taken up by the rest of the representation (quantities, PIDs, AssetNames, indeces)
| otherwise =
fromIntegral $
(roundupBytesToWords $ representationSize (snd $ gettriples vv))
+ repOverhead

instance CC.Crypto crypto => HeapWords (Value crypto) where
heapWords v = fromIntegral $ size v

-- space (in Word64s) taken up by the ada amount
adaWords :: Int
adaWords = 1

-- 64 bit machine Word64 length
wordLength :: Int
wordLength = 8

-- overhead in MA compact rep
repOverhead :: Int
repOverhead = 4 + adaWords + numberMulAssets

-- number of words used to store number of MAs in a value
numberMulAssets :: Int
numberMulAssets = 1

-- converts bytes to words (rounding up)
roundupBytesToWords :: Int -> Int
roundupBytesToWords b = quot (b + wordLength - 1) wordLength

-- ==============================================================
-- CBOR
Expand Down
54 changes: 51 additions & 3 deletions shelley-ma/impl/src/Cardano/Ledger/ShelleyMA/Rules/Utxo.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

module Cardano.Ledger.ShelleyMA.Rules.Utxo where

import Cardano.Binary (FromCBOR (..), ToCBOR (..), encodeListLen)
import Cardano.Binary (FromCBOR (..), ToCBOR (..), encodeListLen, serialize)
import qualified Cardano.Ledger.Core as Core
import Cardano.Ledger.Era (Crypto, Era)
import Cardano.Ledger.Shelley.Constraints
Expand All @@ -30,10 +30,12 @@ import Cardano.Ledger.ShelleyMA.Timelocks
import Cardano.Ledger.ShelleyMA.TxBody (TxBody)
import Cardano.Ledger.Torsor (Torsor (..))
import qualified Cardano.Ledger.Val as Val
import Cardano.Prelude (heapWordsUnpacked)
import Cardano.Slotting.Slot (SlotNo)
import Control.Iterate.SetAlgebra (dom, eval, (∪), (⊆), (⋪), (◁))
import Control.Monad.Trans.Reader (asks)
import Control.State.Transition.Extended
import qualified Data.ByteString.Lazy as BSL (length)
import Data.Coders
( decodeList,
decodeRecordSum,
Expand Down Expand Up @@ -84,6 +86,52 @@ import Shelley.Spec.Ledger.UTxO
unUTxO,
)

{- The scaledMinDeposit calculation uses the minUTxOValue protocol parameter
(passed to it as Coin mv) as a specification of "the cost of
making a Shelley-sized UTxO entry", calculated here by "utxoEntrySizeWithoutVal + uint",
using the constants in the "where" clause.
In the case when a UTxO entry contains coins only (and the Shelley
UTxO entry format is used - we will extend this to be correct for other
UTxO formats shortly), the deposit should be exactly the minUTxOValue.
This is the "inject (coin v) == v" case.
Otherwise, we calculate the per-byte deposit by multiplying the minimum deposit (which is
for the number of Shelley UTxO-entry bytes) by the size of a Shelley UTxO entry.
This is the "(mv * (utxoEntrySizeWithoutVal + uint))" calculation.
We then calculate the total deposit required for making a UTxO entry with a Val-class
member v by dividing "(mv * (utxoEntrySizeWithoutVal + uint))" by the
estimated total size of the UTxO entry containing v, ie by
"(utxoEntrySizeWithoutVal + size v)".
See the formal specification for details.
-}

-- This scaling function is right for UTxO, not EUTxO
--
scaledMinDeposit :: (Val.Val v) => v -> Coin -> Coin
scaledMinDeposit v (Coin mv)
| Val.inject (Val.coin v) == v = Coin mv -- without non-Coin assets, scaled deposit should be exactly minUTxOValue
-- The calculation should represent this equation
-- minValueParameter / coinUTxOSize = actualMinValue / valueUTxOSize
-- actualMinValue = (minValueParameter / coinUTxOSize) * valueUTxOSize
| otherwise = Coin $ max mv (adaPerUTxOWord * (utxoEntrySizeWithoutVal + Val.size v))
where
-- lengths obtained from tracing on HeapWords of inputs and outputs
-- obtained experimentally, and number used here
-- units are Word64s
txoutLenNoVal = 14
txinLen = 7

-- unpacked CompactCoin Word64 size in Word64s
coinSize :: Integer
coinSize = fromIntegral $ heapWordsUnpacked (CompactCoin 0)

utxoEntrySizeWithoutVal :: Integer
utxoEntrySizeWithoutVal = 6 + txoutLenNoVal + txinLen

-- how much ada does a Word64 of UTxO space cost, calculated from minAdaValue PP
-- round down
adaPerUTxOWord :: Integer
adaPerUTxOWord = quot mv (utxoEntrySizeWithoutVal + coinSize)

-- ==========================================================

data UtxoPredicateFailure era
Expand Down Expand Up @@ -247,7 +295,7 @@ utxoTransition = do
Val.pointwise
(>=)
v
(Val.inject $ Val.scaledMinDeposit v minUTxOValue)
(Val.inject $ scaledMinDeposit v minUTxOValue)
)
outputs
null outputsTooSmall ?! OutputTooSmallUTxO outputsTooSmall
Expand All @@ -256,7 +304,7 @@ utxoTransition = do
filter
( \out ->
let v = getField @"value" out
in Val.size v > 4000
in (BSL.length . serialize) v > 4000
-- TODO this is arbitrary, but sufficiently below the current
-- max transaction size. We will make it a protocol parameter
-- in the Alonzo era.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ policyFailure p =
]
]

outTooSmallFailure :: TxOut MaryTest -> Either [[PredicateFailure (LEDGER MaryTest)]] (UTxO MaryTest)
outTooSmallFailure out = Left [[UtxowFailure (UtxoFailure (OutputTooBigUTxO [out]))]]
outTooBigFailure :: TxOut MaryTest -> Either [[PredicateFailure (LEDGER MaryTest)]] (UTxO MaryTest)
outTooBigFailure out = Left [[UtxowFailure (UtxoFailure (OutputTooBigUTxO [out]))]]

----------------------------------------------------
-- Introduce a new Token Bundle, Purple Tokens
Expand Down Expand Up @@ -210,7 +210,7 @@ expectedUTxOSimpleEx1 =
----------------------------

minUtxoSimpleEx2 :: Coin
minUtxoSimpleEx2 = Coin 100
minUtxoSimpleEx2 = Coin 115

aliceCoinsSimpleEx2 :: Coin
aliceCoinsSimpleEx2 = aliceCoinSimpleEx1 <-> (feeEx <+> minUtxoSimpleEx2)
Expand Down Expand Up @@ -351,7 +351,7 @@ expectedUTxOTimeEx1 =
----------------------------------------

mintTimeEx2 :: Coin
mintTimeEx2 = Coin 100
mintTimeEx2 = Coin 120

bobTokensTimeEx2 :: Value TestCrypto
bobTokensTimeEx2 =
Expand Down Expand Up @@ -545,23 +545,29 @@ testNegEx2 = do
-- Create a Value that is too big
--

minUtxoBigEx :: Coin
minUtxoBigEx = Coin 50000

smallValue :: Value TestCrypto
smallValue =
Value 0 $
Map.singleton purplePolicyId (Map.fromList [(plum, 13), (amethyst, 2)])

smallOut :: TxOut MaryTest
smallOut = TxOut Cast.aliceAddr $ smallValue <+> (Val.inject (aliceInitCoin <-> (feeEx <+> Coin 100)))
smallOut = TxOut Cast.aliceAddr $ smallValue <+> (Val.inject (aliceInitCoin <-> (feeEx <+> minUtxoBigEx)))

numAssets :: Int
numAssets = 1000

bigValue :: Value TestCrypto
bigValue =
Value 0 $
Map.singleton
purplePolicyId
(Map.fromList $ map (\x -> ((AssetName . BS.pack . show $ x), 1)) [1 .. 97 :: Int])
(Map.fromList $ map (\x -> ((AssetName . BS.pack . show $ x), 1)) [1 .. numAssets])

bigOut :: TxOut MaryTest
bigOut = TxOut Cast.aliceAddr $ bigValue <+> (Val.inject (Coin 100))
bigOut = TxOut Cast.aliceAddr $ bigValue <+> (Val.inject minUtxoBigEx)

txbodyWithBigValue :: TxBody MaryTest
txbodyWithBigValue =
Expand Down Expand Up @@ -673,5 +679,5 @@ multiAssetsExample =
initUTxO
txBigValue
(ledgerEnv $ SlotNo 0)
(outTooSmallFailure bigOut)
(outTooBigFailure bigOut)
]
72 changes: 0 additions & 72 deletions shelley/chain-and-ledger/executable-spec/src/Cardano/Ledger/Val.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ module Cardano.Ledger.Val
scale,
invert,
sumVal,
scaledMinDeposit,
DecodeNonNegative (..),
DecodeMint (..),
EncodeMint (..),
Expand Down Expand Up @@ -99,77 +98,6 @@ instance Val Coin where

deriving via Coin instance Val DeltaCoin

{- The scaledMinDeposit calculation uses the minUTxOValue protocol parameter
(passed to it as Coin mv) as a specification of "the cost of
making a Shelley-sized UTxO entry", calculated here by "utxoEntrySizeWithoutVal + uint",
using the constants in the "where" clause.
In the case when a UTxO entry contains coins only (and the Shelley
UTxO entry format is used - we will extend this to be correct for other
UTxO formats shortly), the deposit should be exactly the minUTxOValue.
This is the "inject (coin v) == v" case.
Otherwise, we calculate the per-byte deposit by multiplying the minimum deposit (which is
for the number of Shelley UTxO-entry bytes) by the size of a Shelley UTxO entry.
This is the "(mv * (utxoEntrySizeWithoutVal + uint))" calculation.
We then calculate the total deposit required for making a UTxO entry with a Val-class
member v by dividing "(mv * (utxoEntrySizeWithoutVal + uint))" by the
estimated total size of the UTxO entry containing v, ie by
"(utxoEntrySizeWithoutVal + size v)".
See the formal specification for details.
-}

-- TODO : This scaling function is right for UTxO, not EUTxO
-- constants are temporary, the UTxO entry size calculation will be moved
scaledMinDeposit :: (Val v) => v -> Coin -> Coin
scaledMinDeposit v (Coin mv)
| inject (coin v) == v = Coin mv -- without non-Coin assets, scaled deposit should be exactly minUTxOValue
-- The calculation should represent this equation
-- minValueParameter / coinUTxOSize = actualMinValue / valueUTxOSize
-- actualMinValue = (minValueParameter / coinUTxOSize) * valueUTxOSize
| otherwise = Coin $ adaPerUTxOByte * (utxoEntrySizeWithoutVal + size v) -- round down
where
-- address hash length is always same as Policy ID length
addrHashLen :: Integer
addrHashLen = 28

smallArray :: Integer
smallArray = 1

hashLen :: Integer
hashLen = 32

uint :: Integer
uint = 5

hashObj :: Integer
hashObj = 2 + hashLen

addrHeader :: Integer
addrHeader = 1

address :: Integer
address = 2 + addrHeader + 2 * addrHashLen

-- input size
inputSize :: Integer
inputSize = smallArray + uint + hashObj

-- size of output not including the Val (compute that part with vsize later)
outputSizeWithoutVal :: Integer
outputSizeWithoutVal = smallArray + address

-- size of the UTxO entry (ie the space the scaled minUTxOValue deposit pays)
utxoEntrySizeWithoutVal :: Integer
utxoEntrySizeWithoutVal = inputSize + outputSizeWithoutVal

-- parameter is implicit from the minAdaValue parameter
adaPerUTxOByte :: Integer
adaPerUTxOByte = quot mv (utxoEntrySizeWithoutVal + uint)

-- =============================================================

class DecodeNonNegative v where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ where
import Cardano.Binary (FromCBOR (..), ToCBOR (..))
import Cardano.Ledger.Compactible
import qualified Cardano.Ledger.Torsor as Torsor
import Cardano.Prelude (HeapWords)
import Control.DeepSeq (NFData)
import Data.Aeson (FromJSON, ToJSON)
import Data.Group (Abelian, Group (..))
Expand All @@ -46,10 +47,10 @@ newtype Coin = Coin {unCoin :: Integer}
)
deriving (Show) via Quiet Coin
deriving (Semigroup, Monoid, Group, Abelian) via Sum Integer
deriving newtype (PartialOrd, FromCBOR, ToCBOR)
deriving newtype (PartialOrd, FromCBOR, ToCBOR, HeapWords)

newtype DeltaCoin = DeltaCoin Integer
deriving (Eq, Ord, Generic, Enum, NoThunks, NFData, FromCBOR, ToCBOR)
deriving (Eq, Ord, Generic, Enum, NoThunks, NFData, FromCBOR, ToCBOR, HeapWords)
deriving (Show) via Quiet DeltaCoin
deriving (Semigroup, Monoid, Group, Abelian) via Sum Integer
deriving newtype (PartialOrd)
Expand Down Expand Up @@ -80,7 +81,7 @@ rationalToCoinViaFloor r = Coin . floor $ r
-- with an erroring bounds check here. where should this really live?
instance Compactible Coin where
newtype CompactForm Coin = CompactCoin Word64
deriving (Eq, Show, NoThunks, NFData, Typeable)
deriving (Eq, Show, NoThunks, NFData, Typeable, HeapWords)

toCompact (Coin c) = CompactCoin <$> integerToWord64 c
fromCompact (CompactCoin c) = word64ToCoin c
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Shelley.Spec.Ledger.Orphans where

import Cardano.Crypto.Hash (Hash (..))
import qualified Cardano.Crypto.Hash as Hash
import qualified Cardano.Crypto.Hash.Class as HS
import Cardano.Crypto.Util (SignableRepresentation (..))
import qualified Cardano.Crypto.Wallet as WC
import Cardano.Prelude (readEither)
import Cardano.Prelude (HeapWords (..), readEither)
import Cardano.Slotting.Slot (WithOrigin (..))
import Control.DeepSeq (NFData (rnf))
import Data.Aeson
Expand Down Expand Up @@ -107,3 +111,5 @@ instance Default (Hash a b) where

instance Default Bool where
def = False

deriving newtype instance HeapWords (HS.Hash h a)
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ utxoInductive = do
filter
( \x ->
let c = getField @"value" x
in (Val.coin c) < (Val.scaledMinDeposit c minUTxOValue)
in (Val.coin c) < minUTxOValue
)
outputs
null outputsTooSmall ?! OutputTooSmallUTxO outputsTooSmall
Expand Down

0 comments on commit b23e200

Please sign in to comment.