Skip to content

Commit

Permalink
Use sizeTxF instead of length $ serializedTx (#4092)
Browse files Browse the repository at this point in the history
- [x] Remove dependency on
`Cardano.Wallet.Primitive.Types.Tx.{sealedTxFromCardanoBody,
serialisedTx}` by instead using `Ledger.sizeAlonzoTxF`

### Comments

### Issue Number

ADP-3081
  • Loading branch information
Anviking authored Aug 29, 2023
2 parents 288145c + 5248720 commit 165227b
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 58 deletions.
23 changes: 12 additions & 11 deletions lib/wallet/src/Cardano/Wallet/Write/Tx/Balance.hs
Original file line number Diff line number Diff line change
Expand Up @@ -684,12 +684,13 @@ balanceTransactionWithSelectionStrategyAndNoZeroAdaAdjustment
:: KeyWitnessCount
-> Cardano.Tx era
-> ExceptT ErrBalanceTx m (Cardano.Tx era)
guardTxSize witCount tx@(Cardano.Tx body _noKeyWits) =
withConstraints (recentEra @era) $ do
guardTxSize witCount cardanoTx =
withConstraints era $ do
let tx = fromCardanoTx cardanoTx
let maxSize = TxSize (pp ^. ppMaxTxSizeL)
when (estimateSignedTxSize pp witCount body > maxSize) $
when (estimateSignedTxSize era pp witCount tx > maxSize) $
throwE ErrBalanceTxMaxSizeLimitExceeded
pure tx
pure cardanoTx

guardTxBalanced :: Cardano.Tx era -> ExceptT ErrBalanceTx m (Cardano.Tx era)
guardTxBalanced tx = do
Expand All @@ -701,8 +702,8 @@ balanceTransactionWithSelectionStrategyAndNoZeroAdaAdjustment
txBalance :: Cardano.Tx era -> Cardano.Value
txBalance
= toCardanoValue @era
. evaluateTransactionBalance (recentEra @era) pp combinedUTxO
. txBody (recentEra @era)
. evaluateTransactionBalance era pp combinedUTxO
. txBody era
. fromCardanoTx

balanceAfterSettingMinFee
Expand All @@ -712,7 +713,7 @@ balanceTransactionWithSelectionStrategyAndNoZeroAdaAdjustment
balanceAfterSettingMinFee tx = ExceptT . pure $ do
let witCount = estimateKeyWitnessCount combinedUTxO (getBody tx)
minfee = W.toWalletCoin $ evaluateMinimumFee
(recentEra @era) pp (fromCardanoTx tx) witCount
era pp (fromCardanoTx tx) witCount
update = TxUpdate [] [] [] [] (UseNewTxFee minfee)
tx' <- left ErrBalanceTxUpdateError $ updateTx tx update
let balance = txBalance tx'
Expand Down Expand Up @@ -758,14 +759,14 @@ balanceTransactionWithSelectionStrategyAndNoZeroAdaAdjustment
]

extractOutputsFromTx :: Cardano.Tx era -> [W.TxOut]
extractOutputsFromTx (Cardano.ByronTx _) = case recentEra @era of {}
extractOutputsFromTx (Cardano.ByronTx _) = case era of {}
extractOutputsFromTx (Cardano.ShelleyTx _ tx) =
map fromLedgerTxOut
$ outputs (recentEra @era)
$ txBody (recentEra @era) tx
$ outputs era
$ txBody era tx
where
fromLedgerTxOut :: TxOut (ShelleyLedgerEra era) -> W.TxOut
fromLedgerTxOut o = case recentEra @era of
fromLedgerTxOut o = case era of
RecentEraBabbage -> W.fromBabbageTxOut o
RecentEraConway -> W.fromConwayTxOut o

Expand Down
55 changes: 30 additions & 25 deletions lib/wallet/src/Cardano/Wallet/Write/Tx/Sign.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
Expand Down Expand Up @@ -28,30 +27,37 @@ module Cardano.Wallet.Write.Tx.Sign
import Prelude

import Cardano.Ledger.Api
( Addr (..), addrTxOutL, ppMinFeeAL )
( Addr (..)
, addrTxOutL
, addrTxWitsL
, bootAddrTxWitsL
, ppMinFeeAL
, sizeTxF
, witsTxL
)
import Cardano.Ledger.Credential
( Credential (..) )
import Cardano.Ledger.UTxO
( txinLookup )
import qualified Cardano.Wallet.Primitive.Types.Coin as W
( Coin (..) )
import Cardano.Wallet.Primitive.Types.Tx
( sealedTxFromCardanoBody, serialisedTx )
import Cardano.Wallet.Primitive.Types.Tx.Constraints
( TxSize (..) )
import Cardano.Wallet.Shelley.Compatibility.Ledger
( toWalletCoin, toWalletScript )
import Cardano.Wallet.Write.Tx
( IsRecentEra (..)
, KeyWitnessCount (..)
, PParams
, RecentEra (..)
, ShelleyLedgerEra
, Tx
, TxIn
, UTxO
, withConstraints
)
import Control.Lens
( view, (^.) )
( view, (&), (.~), (^.) )
import Data.Maybe
( mapMaybe )
import Numeric.Natural
Expand All @@ -66,19 +72,20 @@ import qualified Cardano.Ledger.Api as Ledger
import qualified Cardano.Wallet.Primitive.Types.Coin as W.Coin
import qualified Cardano.Wallet.Shelley.Compatibility.Ledger as Ledger
import qualified Cardano.Wallet.Write.Tx as Write
import qualified Data.ByteString as BS
import qualified Data.Foldable as F
import qualified Data.List as L
import qualified Data.Map as Map

-- | Estimate the size of the transaction when fully signed.
--
-- NOTE: Existing key witnesses in the tx are ignored.
estimateSignedTxSize
:: forall era. Write.IsRecentEra era
=> Write.PParams (Write.ShelleyLedgerEra era)
:: forall era. RecentEra era
-> PParams (ShelleyLedgerEra era)
-> KeyWitnessCount
-> Cardano.TxBody era
-> Tx (ShelleyLedgerEra era) -- ^ existing wits in tx are ignored
-> TxSize
estimateSignedTxSize pparams nWits body =
estimateSignedTxSize era pparams nWits txWithWits = withConstraints era $
let
-- Hack which allows us to rely on the ledger to calculate the size of
-- witnesses:
Expand All @@ -100,32 +107,30 @@ estimateSignedTxSize pparams nWits body =
, show feePerByte
, "lovelace/byte"
]

sizeOfTx :: TxSize
sizeOfTx = TxSize
. fromIntegral
. BS.length
. serialisedTx
$ sealedTxFromCardanoBody body
sizeOfTx = withConstraints era
$ fromIntegral @Integer @TxSize
$ unsignedTx ^. sizeTxF
in
sizeOfTx <> sizeOfWits
where
unsignedTx :: Tx (ShelleyLedgerEra era)
unsignedTx = withConstraints era $
txWithWits
& (witsTxL . addrTxWitsL) .~ mempty
& (witsTxL . bootAddrTxWitsL) .~ mempty

coinQuotRem :: W.Coin -> W.Coin -> (Natural, Natural)
coinQuotRem (W.Coin p) (W.Coin q) = quotRem p q

minfee :: KeyWitnessCount -> W.Coin
minfee witCount = toWalletCoin $ Write.evaluateMinimumFee
(recentEra @era) pparams (toLedgerTx body) witCount

toLedgerTx :: Cardano.TxBody era -> Write.Tx (Write.ShelleyLedgerEra era)
toLedgerTx b = case Cardano.Tx b [] of
Byron.ByronTx {} -> case Write.recentEra @era of {}
Cardano.ShelleyTx _era ledgerTx -> ledgerTx
era pparams unsignedTx witCount

feePerByte :: W.Coin
feePerByte = Ledger.toWalletCoin $
case Write.recentEra @era of
Write.RecentEraBabbage -> pparams ^. ppMinFeeAL
Write.RecentEraConway -> pparams ^. ppMinFeeAL
feePerByte = withConstraints era $ Ledger.toWalletCoin $
pparams ^. ppMinFeeAL

numberOfShelleyWitnesses :: Word -> KeyWitnessCount
numberOfShelleyWitnesses n = KeyWitnessCount n 0
Expand Down
90 changes: 68 additions & 22 deletions lib/wallet/test/unit/Cardano/Wallet/Shelley/TransactionSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3343,10 +3343,11 @@ prop_balanceTransactionValid
-> Write.UTxO (Write.ShelleyLedgerEra era)
-> Property
prop_validSize tx@(Cardano.Tx body _) utxo = do
let era = recentEra @era
let (TxSize size) =
estimateSignedTxSize ledgerPParams
estimateSignedTxSize era ledgerPParams
(estimateKeyWitnessCount utxo body)
body
(Write.fromCardanoTx tx)
let limit = ledgerPParams ^. ppMaxTxSizeL
let msg = unwords
[ "The tx size "
Expand Down Expand Up @@ -3736,29 +3737,57 @@ estimateSignedTxSizeSpec = describe "estimateSignedTxSize" $ do
-> ByteString
-> Cardano.Tx era
-> IO ()
test _name bs tx@(Cardano.Tx (body :: Cardano.TxBody era) _) = do
test _name bs cTx@(Cardano.Tx body _) = do
let pparams = Write.pparamsLedger $ mockPParamsForBalancing @era
utxo = utxoPromisingInputsHaveVkPaymentCreds body
witCount = estimateKeyWitnessCount (Write.fromCardanoUTxO utxo) body
witCount dummyAddr = estimateKeyWitnessCount
(Write.fromCardanoUTxO
$ utxoPromisingInputsHaveAddress dummyAddr body)
body
era = recentEra @era

ledgerTx :: Write.Tx (Write.ShelleyLedgerEra era)
ledgerTx = Write.fromCardanoTx @era tx
tx :: Write.Tx (Write.ShelleyLedgerEra era)
tx = Write.fromCardanoTx @era cTx

noScripts = Write.withConstraints (recentEra @era) $
Map.null $ ledgerTx ^. witsTxL . scriptTxWitsL
Map.null $ tx ^. witsTxL . scriptTxWitsL
noBootWits = Write.withConstraints (recentEra @era) $
Set.null $ ledgerTx ^. witsTxL . bootAddrTxWitsL
Set.null $ tx ^. witsTxL . bootAddrTxWitsL
testDoesNotYetSupport x =
pendingWith $ "Test setup does not work for txs with " <> x

signedBinarySize = TxSize $ fromIntegral $ BS.length bs

case (noScripts, noBootWits) of
(True, True) -> do
estimateSignedTxSize pparams witCount body
`shouldBe`
TxSize (fromIntegral (BS.length bs))
(False, False) -> testDoesNotYetSupport "bootstrap wits + scripts"
(True, False) -> testDoesNotYetSupport "bootstrap wits"
estimateSignedTxSize era pparams (witCount vkCredAddr) tx
`shouldBeInclusivelyWithin`
( signedBinarySize - correction
, signedBinarySize
)
(False, False) ->
testDoesNotYetSupport "bootstrap wits + scripts"
(True, False) ->
estimateSignedTxSize era pparams (witCount bootAddr) tx
`shouldBeInclusivelyWithin`
( signedBinarySize - correction
, signedBinarySize + bootWitsCanBeLongerBy
)
(False, True) -> testDoesNotYetSupport "scripts"
where
-- Apparently the cbor encoding used by the ledger for size checks
-- (`toCBORForSizeComputation`) is a few bytes smaller than the actual
-- serialized size for these goldens.
correction = TxSize 6

-- | Checks for membership in the given closed interval [a, b]
x `shouldBeInclusivelyWithin` (a, b) =
if a <= x && x <= b
then pure ()
else expectationFailure $ unwords
[ show x
, "not in the expected interval"
, "[" <> show a <> ", " <> show b <> "]"
]

forAllGoldens
:: [(String, ByteString)]
Expand All @@ -3782,17 +3811,17 @@ estimateSignedTxSizeSpec = describe "estimateSignedTxSize" $ do
-- keep tese tests working, we can create a UTxO with dummy values as long
-- as estimateSignedTxSize can tell that all inputs in the tx correspond to
-- outputs with vk payment credentials.
utxoPromisingInputsHaveVkPaymentCreds
:: forall era. HasCallStack
=> Cardano.IsShelleyBasedEra era
=> Cardano.TxBody era
utxoPromisingInputsHaveAddress
:: forall era. (HasCallStack, Cardano.IsShelleyBasedEra era)
=> Address
-> Cardano.TxBody era
-> Cardano.UTxO era
utxoPromisingInputsHaveVkPaymentCreds (Cardano.TxBody body) =
utxoPromisingInputsHaveAddress addr (Cardano.TxBody body) =
Cardano.UTxO $ Map.fromList $
[ (i
, Compatibility.toCardanoTxOut
(shelleyBasedEra @era)
(TxOut paymentAddr mempty)
(TxOut addr mempty)
)
| i <- allTxIns body
]
Expand All @@ -3805,8 +3834,25 @@ estimateSignedTxSizeSpec = describe "estimateSignedTxSize" $ do
Cardano.TxInsCollateralNone -> []
ins = Cardano.txIns body

paymentAddr = Address $ unsafeFromHex
"6079467c69a9ac66280174d09d62575ba955748b21dec3b483a9469a65"
-- An address with a vk payment credential. For the test above, this is the
-- only aspect which matters.
vkCredAddr = Address $ unsafeFromHex
"6000000000000000000000000000000000000000000000000000000000"

-- This is a short bootstrap address retrieved from
-- "byron-address-format.md".
bootAddr = Address $ unsafeFromHex
"82d818582183581cba970ad36654d8dd8f74274b733452ddeab9a62a397746be3c42ccdda0001a9026da5b"

-- With more attributes, the address can be longer. This value was chosen
-- /experimentally/ to make the tests pass. The ledger has been validating
-- new outputs with bootstrap addresses have attributes not larger than 64
-- bytes. The ledger has done so since the middle of the Byron era.
-- Address attributes are included in the bootstrap witnesses.
--
-- NOTE: If we had access to the real UTxO set for the inputs of the test
-- txs, we wouldn't need this fuzziness. Related: ADP-2987.
bootWitsCanBeLongerBy = TxSize 45

fst6 :: (a, b, c, d, e, f) -> a
fst6 (a,_,_,_,_,_) = a
Expand Down

0 comments on commit 165227b

Please sign in to comment.