@@ -35,7 +35,7 @@ module Test.Consensus.Mempool (tests) where
3535import Cardano.Binary (Encoding , toCBOR )
3636import Cardano.Crypto.Hash
3737import Control.Exception (assert )
38- import Control.Monad (foldM , forM , forM_ , void )
38+ import Control.Monad (foldM , forM , forM_ , guard , void )
3939import Control.Monad.Except (Except , runExcept )
4040import Control.Monad.IOSim (runSimOrThrow )
4141import Control.Monad.State (State , evalState , get , modify )
@@ -47,6 +47,7 @@ import Data.Map.Strict (Map)
4747import qualified Data.Map.Strict as Map
4848import Data.Maybe (mapMaybe )
4949import Data.Semigroup (stimes )
50+ import Data.Set (Set )
5051import qualified Data.Set as Set
5152import Data.Word (Word32 )
5253import GHC.Stack (HasCallStack )
@@ -65,6 +66,7 @@ import Ouroboros.Consensus.Util (repeatedly, repeatedlyM,
6566 safeMaximumOn , (.:) )
6667import Ouroboros.Consensus.Util.Condense (condense )
6768import Ouroboros.Consensus.Util.IOLike
69+ import Test.Crypto.Hash ()
6870import Test.QuickCheck hiding (elements )
6971import Test.Tasty (TestTree , testGroup )
7072import Test.Tasty.QuickCheck (testProperty )
@@ -506,7 +508,8 @@ genValidTxs = go []
506508 (tx, ledger') <- genValidTx ledger
507509 go (tx: txs) (n - 1 ) ledger'
508510
509- -- | Generate a valid transaction (but ignoring any per-tx size limits).
511+ -- | Generate a valid transaction (but ignoring any per-tx size limits, see Note
512+ -- [Transaction size limit]).
510513genValidTx :: LedgerState TestBlock -> Gen (TestTx , LedgerState TestBlock )
511514genValidTx ledgerState@ (SimpleLedgerState MockState { mockUtxo = utxo }) = do
512515 -- Never let someone go broke, otherwise we risk concentrating all the
@@ -557,6 +560,23 @@ genInvalidTx ledgerState@(SimpleLedgerState MockState { mockUtxo = utxo }) = do
557560 tx = mkSimpleGenTx $ Tx DoNotExpire ins outs
558561 return $ assert (not (txIsValid testLedgerConfigNoSizeLimits ledgerState tx)) tx
559562
563+ -- | Generate an invalid tx that is larger than the given measure.
564+ genLargeInvalidTx :: TheMeasure -> Gen TestTx
565+ genLargeInvalidTx (IgnoringOverflow sz) = go Set. empty
566+ where
567+ go ins = case isLargeTx ins of
568+ Just tx -> pure tx
569+ Nothing -> do
570+ newTxIn <- arbitrary
571+ go (Set. insert newTxIn ins)
572+
573+ isLargeTx :: Set TxIn -> Maybe TestTx
574+ isLargeTx ins = do
575+ let outs = []
576+ tx = mkSimpleGenTx $ Tx DoNotExpire ins outs
577+ guard $ genTxSize tx > sz
578+ pure tx
579+
560580-- | Apply a transaction to the ledger
561581--
562582-- We don't have blocks in this test, but transactions only. In this function
@@ -619,6 +639,21 @@ validTxs = map fst . filter snd . txs
619639invalidTxs :: TestSetupWithTxs -> [GenTx TestBlock ]
620640invalidTxs = map fst . filter (not . snd ) . txs
621641
642+ {-
643+ Note [Transaction size limit]
644+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
645+
646+ An important property of the mempool is that adding a transaction that can never
647+ fit into the mempool must not block, also see
648+ https://github.com/IntersectMBO/ouroboros-consensus/issues/1226. We test this
649+ while generating a TestSetupWithTxs by always including a transaction that is
650+ larger than the entire mempool, and setting the per-tx size limit such that just
651+ this transaction is invalid due to its size, but not impacting the validity of
652+ any other transactions. Therefore, we disable the size limit in e.g.
653+ 'genValidTx' to only capture UTxO-related validity for them by using an
654+ appropriate ledger config ('testLedgerConfigNoSizeLimits').
655+ -}
656+
622657instance Arbitrary TestSetupWithTxs where
623658 arbitrary = sized $ \ n -> do
624659 nbTxs <- choose (0 , n)
@@ -639,7 +674,28 @@ instance Arbitrary TestSetupWithTxs where
639674 then NoMempoolCapacityBytesOverride
640675 else MempoolCapacityBytesOverride $ mpCap <> newSize
641676 }
642- return TestSetupWithTxs { testSetup = testSetup', txs }
677+ let mempoolCap :: TheMeasure
678+ mempoolCap = computeMempoolCapacity
679+ testLedgerConfigNoSizeLimits
680+ (TickedSimpleLedgerState ledger)
681+ (testMempoolCapOverride testSetup)
682+
683+
684+ largeInvalidTx <- genLargeInvalidTx mempoolCap
685+ let txs' = (largeInvalidTx, False ) : txs
686+ -- Set the maximum tx size to the mempool capacity. This won't
687+ -- invalidate any valid tx in @txs@ as the capacity was chosen such that
688+ -- all @txs@ fit into the mempool. Also see Note [Transaction size
689+ -- limit].
690+ testSetup'' = testSetup' { testLedgerCfg =
691+ (testLedgerCfg testSetup') { simpleLedgerMockConfig =
692+ MockConfig {
693+ mockCfgMaxTxSize = Just (unIgnoringOverflow mempoolCap)
694+ }
695+ }
696+ }
697+
698+ return TestSetupWithTxs { testSetup = testSetup'', txs = txs' }
643699
644700 shrink TestSetupWithTxs { testSetup, txs } =
645701 [ TestSetupWithTxs { testSetup = testSetup', txs }
0 commit comments