33{-# LANGUAGE FlexibleContexts #-}
44{-# LANGUAGE NumericUnderscores #-}
55{-# LANGUAGE OverloadedLists #-}
6+ {-# LANGUAGE PatternSynonyms #-}
67{-# LANGUAGE ScopedTypeVariables #-}
78{-# LANGUAGE TypeApplications #-}
89
910module Test.Cardano.Ledger.Conway.Imp.BbodySpec (
1011 spec ,
1112) where
1213
14+ import Cardano.Ledger.Alonzo.Rules (AlonzoUtxosPredFailure )
1315import Cardano.Ledger.BHeaderView (BHeaderView (.. ))
1416import Cardano.Ledger.Babbage.Core
15- import Cardano.Ledger.BaseTypes (BlocksMade (.. ), Mismatch (.. ), ProtVer (.. ))
17+ import Cardano.Ledger.BaseTypes (BlocksMade (.. ), Mismatch (.. ), ProtVer (.. ), natVersion )
1618import Cardano.Ledger.Block
1719import Cardano.Ledger.Coin (Coin (.. ))
1820import Cardano.Ledger.Conway.Rules (
1921 ConwayBbodyPredFailure (.. ),
2022 maxRefScriptSizePerBlock ,
2123 maxRefScriptSizePerTx ,
24+ totalRefScriptSizeInBlock ,
2225 )
2326import Cardano.Ledger.Conway.State
24- import Cardano.Ledger.Plutus (SLanguage (.. ))
27+ import Cardano.Ledger.Plutus (SLanguage (.. ), hashPlutusScript )
2528import Cardano.Ledger.Shelley.LedgerState
2629import Cardano.Ledger.Shelley.Rules (
2730 BbodyEnv (.. ),
2831 Event ,
2932 ShelleyBbodyState (.. ),
3033 )
34+ import Cardano.Ledger.Shelley.Scripts (
35+ pattern RequireSignature ,
36+ )
37+ import Cardano.Ledger.TxIn
3138import Control.Monad (forM )
39+ import Data.List.NonEmpty (NonEmpty (.. ))
3240import qualified Data.List.NonEmpty as NE
3341import qualified Data.Map as Map
3442import qualified Data.Sequence.Strict as SSeq
35- import Lens.Micro ((^.) )
43+ import Lens.Micro ((&) , (.~) , ( ^.) )
3644import Lens.Micro.Mtl (use )
3745import Test.Cardano.Ledger.Babbage.ImpTest
46+ import Test.Cardano.Ledger.Core.Utils (txInAt )
3847import Test.Cardano.Ledger.Imp.Common
39- import Test.Cardano.Ledger.Plutus.Examples (purposeIsWellformedNoDatum )
48+ import Test.Cardano.Ledger.Plutus.Examples (alwaysFailsNoDatum , purposeIsWellformedNoDatum )
4049
4150spec ::
4251 forall era .
4352 ( AlonzoEraImp era
4453 , BabbageEraTxBody era
4554 , InjectRuleFailure " BBODY" ConwayBbodyPredFailure era
55+ , InjectRuleFailure " LEDGER" AlonzoUtxosPredFailure era
4656 , ToExpr (Event (EraRule " BBODY" era ))
4757 ) =>
4858 SpecWith (ImpInit (LedgerSpec era ))
@@ -87,6 +97,170 @@ spec = do
8797 }
8898 )
8999 ]
100+
101+ it " BodyRefScriptsSizeTooBig with reference scripts in the same block" $
102+ whenMajorVersionAtLeast @ 11 $ do
103+ Just plutusScript <- pure $ mkPlutusScript @ era $ purposeIsWellformedNoDatum SPlutusV2
104+ let scriptSize = originalBytesSize plutusScript
105+
106+ txScriptCounts <-
107+ genNumAdditionsExceeding
108+ scriptSize
109+ maxRefScriptSizePerTx
110+ maxRefScriptSizePerBlock
111+
112+ let expectedTotalRefScriptSize = scriptSize * sum txScriptCounts
113+
114+ -- We are creating reference scripts and transaction that depend on them in a "simulation",
115+ -- so the result will be correctly constructed that are not applied to the ledger state
116+ txs :: [Tx era ] <- simulateThenRestore $ do
117+ concat
118+ <$> forM
119+ txScriptCounts
120+ ( \ n -> do
121+ -- produce reference scripts
122+ refScriptTxs <-
123+ replicateM n (produceRefScriptsTx (fromPlutusScript plutusScript :| [] ))
124+
125+ -- spend using the reference scripts
126+ let txIns = (`mkTxInPartial` 0 ) . txIdTx <$> refScriptTxs
127+ rootIn <- fst <$> getImpRootTxOut
128+ spendTx <- submitTxWithRefInputs rootIn (NE. fromList txIns)
129+ pure $ refScriptTxs ++ [spendTx]
130+ )
131+
132+ predFailures <- expectLeftExpr =<< tryRunBBODY txs
133+ predFailures
134+ `shouldBe` NE. fromList
135+ [ injectFailure
136+ ( BodyRefScriptsSizeTooBig $
137+ Mismatch
138+ { mismatchSupplied = expectedTotalRefScriptSize
139+ , mismatchExpected = maxRefScriptSizePerBlock
140+ }
141+ )
142+ ]
143+
144+ it " totalRefScriptSizeInBlock" $ do
145+ script <- RequireSignature @ era <$> freshKeyHash
146+ let scriptSize = originalBytesSize script
147+ scriptSpendIn <- impAddNativeScript script >>= produceScript
148+ scriptSpendIn2 <- impAddNativeScript script >>= produceScript
149+ protVer <- getsPParams ppProtocolVersionL
150+
151+ -- We want to verify that the total size of reference scripts in a list of transactions
152+ -- remains unchanged before and after applying them to the ledger state.
153+ -- To do this, we generate the expected transactions, simulate submitting them to obtain
154+ -- their individual reference script sizes, and then restore the original state -
155+ -- meaning the transactions are not actually applied.
156+ -- Finally, we check that the accumulated sizes from both before and after match.
157+ txsWithRefScriptSizes :: ([(Tx era , Int )], Int ) <- simulateThenRestore $ do
158+ let mkTxWithExpectedSize expectedSize txAction = do
159+ tx <- txAction
160+ totalRefScriptSizeInBlock protVer [tx] <$> getUTxO `shouldReturn` expectedSize
161+ pure (tx, expectedSize)
162+
163+ -- submit reference scripts
164+ refScriptTx1 <-
165+ mkTxWithExpectedSize 0 $
166+ produceRefScriptsTx (fromNativeScript script :| [] )
167+ let refScriptTx1In = txInAt 0 (fst refScriptTx1)
168+ refScriptTx2 <-
169+ mkTxWithExpectedSize 0 $
170+ produceRefScriptsTx (fromNativeScript script :| [] )
171+ -- spend script using the reference script
172+ spendScriptWithRefScriptTx <-
173+ mkTxWithExpectedSize scriptSize $
174+ submitTxWithRefInputs scriptSpendIn [refScriptTx1In]
175+ -- spend using two ref inputs
176+ spendScriptWithTwoRefScriptsTx <-
177+ mkTxWithExpectedSize (2 * scriptSize) $
178+ submitTxWithRefInputs scriptSpendIn2 [refScriptTx1In, txInAt 0 (fst refScriptTx2)]
179+ -- spend the root utxo
180+ rootIn <- fst <$> getImpRootTxOut
181+ spendRootUtxoTx <-
182+ mkTxWithExpectedSize scriptSize $
183+ submitTxWithRefInputs rootIn [refScriptTx1In]
184+ -- spend the reference script itself
185+ -- We must check the size without submitting the transaction,
186+ -- since applying it removes the reference script from the UTxO
187+ spendRefScriptTx <-
188+ mkTxWithExpectedSize scriptSize $
189+ fixupTx $
190+ mkTxWithRefInputs refScriptTx1In (NE. fromList [refScriptTx1In])
191+
192+ let txsWithRefScriptSizes =
193+ [ refScriptTx1
194+ , refScriptTx2
195+ , spendScriptWithRefScriptTx
196+ , spendScriptWithTwoRefScriptsTx
197+ , spendRootUtxoTx
198+ , spendRefScriptTx
199+ ]
200+
201+ -- check and return the accumulated reference script size of all transactions,
202+ -- so we can check that the same sum for the unapplied transactions matches
203+ let expectedTotalRefScriptSize = 5 * scriptSize
204+ totalRefScriptSizeInBlock protVer (SSeq. fromList (fst <$> txsWithRefScriptSizes))
205+ <$> getUTxO `shouldReturn` expectedTotalRefScriptSize
206+ pure (txsWithRefScriptSizes, expectedTotalRefScriptSize)
207+
208+ let (txWithSizes, expectedTotalSize) = txsWithRefScriptSizes
209+
210+ -- for each prefix of the list, the accumulated sum should match the sum of the applied transactions
211+ forM_ ([1 .. length txWithSizes] :: [Int ]) $ \ ix -> do
212+ let slice = take ix txWithSizes
213+
214+ totalRefScriptSizeInBlock protVer (SSeq. fromList (fst <$> slice))
215+ <$> getUTxO
216+ `shouldReturn` (if isPostV10 protVer then sum (snd <$> slice) else 0 )
217+
218+ totalRefScriptSizeInBlock protVer (SSeq. fromList (fst <$> txWithSizes))
219+ <$> getUTxO
220+ `shouldReturn` (if isPostV10 protVer then expectedTotalSize else 0 )
221+
222+ -- disabled in conformance because submiting phase2-invalid transactions are not supported atm
223+ disableImpInitExpectLedgerRuleConformance $
224+ it " Use a reference script in a collateral output" $ do
225+ protVer <- getsPParams ppProtocolVersionL
226+
227+ -- produce an utxo with a failing script
228+ failingPlutusTxIn <- do
229+ let plutus = alwaysFailsNoDatum SPlutusV3
230+ produceScript $ hashPlutusScript plutus
231+
232+ -- produce a utxo with a succeeding script
233+ script <- RequireSignature @ era <$> freshKeyHash
234+ scriptTxIn <- impAddNativeScript script >>= produceScript
235+ let scriptSize = originalBytesSize script
236+
237+ -- prepare a txout with the succeeding script as reference script
238+ collRefScriptTxOut <- do
239+ addr <- freshKeyAddr_
240+ pure $ mkBasicTxOut addr mempty & referenceScriptTxOutL .~ pure (fromNativeScript script)
241+
242+ (txs :: [Tx era ]) <- simulateThenRestore $ do
243+ -- submit an invalid transaction which attempts to consume the failing script
244+ -- and specifies as collateral return the txout with reference script
245+ createCollateralTx <-
246+ submitPhase2Invalid $
247+ mkBasicTx
248+ ( mkBasicTxBody
249+ & inputsTxBodyL .~ [failingPlutusTxIn]
250+ & collateralReturnTxBodyL .~ pure collRefScriptTxOut
251+ )
252+ totalRefScriptSizeInBlock protVer [createCollateralTx] <$> getUTxO `shouldReturn` 0
253+
254+ -- consume the script, passing the output from the previous collateral as reference input
255+ let refScriptTxIn = txInAt 1 createCollateralTx
256+ useCollateralTx <- submitTxWithRefInputs scriptTxIn [refScriptTxIn]
257+ totalRefScriptSizeInBlock protVer [createCollateralTx, useCollateralTx]
258+ <$> getUTxO `shouldReturn` scriptSize
259+ pure [createCollateralTx, useCollateralTx]
260+
261+ totalRefScriptSizeInBlock protVer (SSeq. fromList txs)
262+ <$> getUTxO
263+ `shouldReturn` (if isPostV10 protVer then scriptSize else 0 )
90264 where
91265 tryRunBBODY txs = do
92266 let txSeq = toTxSeq @ era $ SSeq. fromList txs
@@ -107,6 +281,7 @@ spec = do
107281 (BbodyEnv pp (nes ^. chainAccountStateL))
108282 (BbodyState ls (BlocksMade Map. empty))
109283 (Block bhView txSeq)
284+ isPostV10 protVer = pvMajor protVer >= natVersion @ 11
110285
111286-- Generate a list of integers such that the sum of their multiples by scale is greater than toExceed
112287-- and each individual value multiplied by the scale is smaller than maxSingle
0 commit comments