Skip to content

Commit 9ed2a72

Browse files
committed
consensus: use Natural for ByteSize
At the cost of a single pointer indirection --- which we expect to be dwarfed by crypto checks etc --- the Mempool is trivially innoculated against overflows. The worst-case is actually at most two pointers, and that can only happen when a Word64 actually would have overflowed. This should never happen in practice due to the Diffusion Layer's finitely-bounded message buffers (for the TxSubmission mini protocol, in particular) (perhaps unless the node is severely configured). But a minor performance hit seems a preferable worst-case compared to crashes/confused conditionals/etc.
1 parent 93829e2 commit 9ed2a72

File tree

9 files changed

+48
-30
lines changed
  • ouroboros-consensus-cardano
  • ouroboros-consensus-diffusion
    • src/ouroboros-consensus-diffusion/Ouroboros/Consensus
    • test/consensus-test/Test/Consensus/HardFork/Combinator
  • ouroboros-consensus

9 files changed

+48
-30
lines changed

ouroboros-consensus-cardano/src/byron/Ouroboros/Consensus/Byron/Ledger/Mempool.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ instance TxLimits ByronBlock where
129129

130130
blockCapacityTxMeasure _cfg st =
131131
ByteSize
132+
$ fromIntegral
132133
$ CC.getMaxBlockSize cvs - byronBlockEncodingOverhead
133134
where
134135
cvs = tickedByronLedgerState st

ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Ledger/Mempool.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ txsMaxBytes ::
282282
-> ByteSize
283283
txsMaxBytes TickedShelleyLedgerState { tickedShelleyLedgerState } =
284284
-- `maxBlockBodySize` is expected to be bigger than `fixedBlockBodyOverhead`
285-
ByteSize $ maxBlockBodySize - fixedBlockBodyOverhead
285+
ByteSize $ fromIntegral $ maxBlockBodySize - fixedBlockBodyOverhead
286286
where
287287
maxBlockBodySize = getPParams tickedShelleyLedgerState ^. ppMaxBBSizeL
288288

ouroboros-consensus-cardano/test/shelley-test/Test/Consensus/Shelley/Coherence.hs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import qualified Data.Measure as Measure
55
import Data.Word (Word32)
66
import Ouroboros.Consensus.Ledger.SupportsMempool (ByteSize (..))
77
import Ouroboros.Consensus.Shelley.Ledger.Mempool (AlonzoMeasure (..),
8-
fromExUnits)
8+
ConwayMeasure (..), fromExUnits)
99
import Test.Cardano.Ledger.Alonzo.Serialisation.Generators ()
1010
import Test.Tasty
1111
import Test.Tasty.QuickCheck
@@ -16,11 +16,18 @@ tests = testGroup "Shelley coherences" [
1616
]
1717

1818
-- | 'Measure.<=' and @'pointWiseExUnits' (<=)@ must agree
19-
leqCoherence :: Word32 -> ExUnits -> ExUnits -> Property
20-
leqCoherence w eu1 eu2 =
19+
leqCoherence :: Word32 -> Word32 -> ExUnits -> ExUnits -> Property
20+
leqCoherence w1 w2 eu1 eu2 =
2121
actual === expected
2222
where
23-
inj eu = AlonzoMeasure (ByteSize w) (fromExUnits eu)
23+
-- ConwayMeasure is the fullest TxMeasure and mainnet's
24+
inj eu =
25+
ConwayMeasure
26+
(AlonzoMeasure
27+
(ByteSize (fromIntegral w1))
28+
(fromExUnits eu)
29+
)
30+
(ByteSize (fromIntegral w2))
2431

2532
actual = inj eu1 Measure.<= inj eu2
2633
expected = pointWiseExUnits (<=) eu1 eu2

ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/NodeKernel.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,10 @@ getMempoolReader mempool = MempoolReader.TxSubmissionMempoolReader
706706
snapshotHasTx } =
707707
MempoolReader.MempoolSnapshot
708708
{ mempoolTxIdsAfter = \idx ->
709-
[ (txId (txForgetValidated tx), idx', unByteSize byteSize)
709+
[ ( txId (txForgetValidated tx)
710+
, idx'
711+
, fromIntegral $ unByteSize byteSize -- TODO overflow?
712+
)
710713
| (tx, idx', byteSize) <- snapshotTxsAfter idx
711714
]
712715
, mempoolLookupTx = snapshotLookupTx

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/A.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,7 @@ instance LedgerSupportsMempool BlockA where
330330

331331
instance TxLimits BlockA where
332332
type TxMeasure BlockA = ByteSize
333-
-- default mempool capacity is two blocks, so maxBound/2 avoids overflow
334-
blockCapacityTxMeasure _cfg _st = ByteSize $ maxBound `div` 2
333+
blockCapacityTxMeasure _cfg _st = ByteSize $ 100 * 1024 -- arbitrary
335334
txMeasure _cfg _st _tx = ByteSize 0
336335

337336
newtype instance TxId (GenTx BlockA) = TxIdA Int

ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/B.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,7 @@ instance LedgerSupportsMempool BlockB where
266266

267267
instance TxLimits BlockB where
268268
type TxMeasure BlockB = ByteSize
269-
-- default mempool capacity is two blocks, so maxBound/2 avoids overflow
270-
blockCapacityTxMeasure _cfg _st = ByteSize $ maxBound `div` 2
269+
blockCapacityTxMeasure _cfg _st = ByteSize $ 100 * 1024 -- arbitrary
271270
txMeasure _cfg _st _tx = ByteSize 0
272271

273272
data instance TxId (GenTx BlockB)

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Ledger/SupportsMempool.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ import Data.ByteString.Short (ShortByteString)
2626
import Data.DerivingVia (InstantiatedAt (..))
2727
import Data.Kind (Type)
2828
import Data.Measure (Measure)
29-
import Data.Word (Word32)
3029
import GHC.Stack (HasCallStack)
3130
import NoThunks.Class
31+
import Numeric.Natural (Natural)
3232
import Ouroboros.Consensus.Block.Abstract
3333
import Ouroboros.Consensus.Ledger.Abstract
3434
import Ouroboros.Consensus.Ticked
@@ -204,7 +204,10 @@ class ( Measure (TxMeasure blk)
204204
-> TickedLedgerState blk
205205
-> TxMeasure blk
206206

207-
newtype ByteSize = ByteSize { unByteSize :: Word32 }
207+
-- | We intentionally do not declare a 'Num' instance! We prefer @ByteSize@ to
208+
-- occur explicitly in the code where possible, for legibility/perspciousness.
209+
-- We also do not need nor want subtraction.
210+
newtype ByteSize = ByteSize { unByteSize :: Natural }
208211
deriving stock (Show)
209212
deriving newtype (Eq, Ord)
210213
deriving newtype (Measure)

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/LocalTxMonitor/Server.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ localTxMonitorServer mempool =
6161
, recvMsgGetSizes = do
6262
let MempoolSize{msNumTxs,msNumBytes} = snapshotMempoolSize snapshot
6363
let sizes = MempoolSizeAndCapacity
64-
{ capacityInBytes = unByteSize capacity
65-
, sizeInBytes = unByteSize msNumBytes
64+
{ capacityInBytes = fromIntegral $ unByteSize capacity
65+
, sizeInBytes = fromIntegral $ unByteSize msNumBytes
6666
, numberOfTxs = msNumTxs
67-
}
67+
} -- TODO what to do about overflow?
6868
pure $ SendMsgReplyGetSizes sizes (serverStAcquired s txs)
6969
, recvMsgAwaitAcquire = do
7070
s' <- atomically $ do

ouroboros-consensus/test/consensus-test/Test/Consensus/Mempool.hs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ module Test.Consensus.Mempool (tests) where
3535
import Cardano.Binary (Encoding, toCBOR)
3636
import Cardano.Crypto.Hash
3737
import Control.Exception (assert)
38-
import Control.Monad (foldM, forM, forM_, void)
38+
import Control.Monad (foldM, forM, forM_, void, when)
3939
import Control.Monad.Except (Except, runExcept)
4040
import Control.Monad.IOSim (runSimOrThrow)
4141
import Control.Monad.State (State, evalState, get, modify)
@@ -48,8 +48,9 @@ import qualified Data.Map.Strict as Map
4848
import Data.Maybe (mapMaybe)
4949
import Data.Semigroup (stimes)
5050
import qualified Data.Set as Set
51-
import Data.Word
51+
import Data.Word (Word32)
5252
import GHC.Stack (HasCallStack)
53+
import Numeric.Natural (Natural)
5354
import Ouroboros.Consensus.Block
5455
import Ouroboros.Consensus.BlockchainTime
5556
import Ouroboros.Consensus.Config.SecurityParam
@@ -355,7 +356,7 @@ ppTestTxWithHash x = condense
355356
--
356357
-- The generated 'testMempoolCap' will be:
357358
-- > foldMap 'txSize' 'testInitialTxs' + extraCapacity
358-
genTestSetupWithExtraCapacity :: Int -> Word32 -> Gen (TestSetup, LedgerState TestBlock)
359+
genTestSetupWithExtraCapacity :: Int -> Natural -> Gen (TestSetup, LedgerState TestBlock)
359360
genTestSetupWithExtraCapacity maxInitialTxs extraCapacity = do
360361
ledgerSize <- choose (0, maxInitialTxs)
361362
nbInitialTxs <- choose (0, maxInitialTxs)
@@ -831,20 +832,26 @@ instance Arbitrary MempoolCapTestSetup where
831832
-- The Mempool should at least be capable of containing the transactions
832833
-- it already contains.
833834
let currentSize = foldMap txSize (testInitialTxs testSetup)
834-
ByteSize capacityMinBound = currentSize
835+
capacityMinBound = currentSize
835836
validTxsToAdd = [tx | (tx, True) <- txs]
836837
-- Use the current size + the sum of all the valid transactions to add
837838
-- as the upper bound.
838-
ByteSize capacityMaxBound = currentSize <> foldMap txSize validTxsToAdd
839+
capacityMaxBound = currentSize <> foldMap txSize validTxsToAdd
839840
-- Note that we could pick @currentSize@, meaning that we can't add any
840841
-- more transactions to the Mempool
842+
843+
when (unByteSize capacityMaxBound >= 2^(32 :: Int)) $ do
844+
error "impossible!" -- could 'QC.discard' if this is actually feasible
845+
841846
capacity <- choose
842-
( capacityMinBound
843-
, capacityMaxBound
847+
( fromIntegral (unByteSize capacityMinBound) :: Word32
848+
, fromIntegral (unByteSize capacityMaxBound) :: Word32
844849
)
845850
let testSetup' = testSetup {
846851
testMempoolCapOverride =
847-
MempoolCapacityBytesOverride (ByteSize capacity)
852+
MempoolCapacityBytesOverride
853+
$ ByteSize
854+
$ fromIntegral (capacity :: Word32)
848855
}
849856
return $ MempoolCapTestSetup testSetupWithTxs { testSetup = testSetup' }
850857

@@ -942,8 +949,8 @@ data TxSizeSplitTestSetup = TxSizeSplitTestSetup
942949

943950
instance Arbitrary TxSizeSplitTestSetup where
944951
arbitrary = do
945-
let txSizeMaxBound = 10 * 1024 * 1024 -- 10MB transaction max bound
946-
txSizes <- listOf $ choose (1, txSizeMaxBound)
952+
let txSizeMaxBound = 10 * 1024 * 1024 -- 10 mebibyte transaction max bound
953+
txSizes <- listOf $ choose (1, txSizeMaxBound :: Word32)
947954
let totalTxsSize = sum txSizes
948955
txSizeToSplitOn <- frequency
949956
[ (1, pure 0)
@@ -952,8 +959,8 @@ instance Arbitrary TxSizeSplitTestSetup where
952959
, (1, choose (totalTxsSize + 1, totalTxsSize + 1000))
953960
]
954961
pure TxSizeSplitTestSetup
955-
{ tssTxSizes = map ByteSize txSizes
956-
, tssTxSizeToSplitOn = ByteSize txSizeToSplitOn
962+
{ tssTxSizes = map (ByteSize . fromIntegral) txSizes
963+
, tssTxSizeToSplitOn = ByteSize $ fromIntegral txSizeToSplitOn
957964
}
958965

959966
shrink TxSizeSplitTestSetup { tssTxSizes, tssTxSizeToSplitOn = ByteSize x } =
@@ -1031,9 +1038,8 @@ prop_Mempool_idx_consistency (Actions actions) =
10311038
, testMempoolCapOverride =
10321039
MempoolCapacityBytesOverride
10331040
$ ByteSize
1034-
$ maxBound - unByteSize simpleBlockCapacity
1035-
--- can't use maxBound, because then 'computeMempoolCapacity'
1036-
--- calculation overflows, resulting in a capacity of just one block
1041+
$ 1024*1024*1024
1042+
-- There's no way this test will need more than a gibibyte.
10371043
}
10381044

10391045
lastOfMempoolRemoved txsInMempool = \case

0 commit comments

Comments
 (0)