44{-# LANGUAGE NamedFieldPuns #-}
55{-# LANGUAGE NumericUnderscores #-}
66{-# LANGUAGE OverloadedRecordDot #-}
7+ {-# LANGUAGE PatternSynonyms #-}
78{-# LANGUAGE RankNTypes #-}
89{-# LANGUAGE ScopedTypeVariables #-}
910{-# LANGUAGE TypeApplications #-}
@@ -32,19 +33,24 @@ import Cardano.Ledger.Api
3233import Cardano.Ledger.Api.Transition (mkLatestTransitionConfig )
3334import Cardano.Ledger.Api.Tx.In (TxIn (.. ))
3435import Cardano.Ledger.BaseTypes (ProtVer (.. ), TxIx (.. ), knownNonZeroBounded )
36+ import qualified Cardano.Ledger.Block as SL
3537import Cardano.Protocol.Crypto (StandardCrypto )
3638import Cardano.Protocol.TPraos.OCert (KESPeriod (.. ))
3739import Cardano.Slotting.Time (SlotLength , slotLengthFromSec )
38- import Control.Monad (replicateM )
40+ import qualified Control.Concurrent.Class.MonadSTM.Strict.TVar as StrictTVar
41+ import Control.Monad (foldM , replicateM )
42+ import Control.Monad.IOSim (runSimOrThrow )
3943import Data.Foldable (toList )
4044import Data.Function ((&) )
45+ import Data.Functor.Identity (runIdentity )
4146import Data.Map (Map )
4247import qualified Data.Map.Strict as Map
4348import Data.Maybe (isNothing , mapMaybe )
4449import Data.Proxy (Proxy (.. ))
4550import Data.Sequence.Strict ((|>) )
4651import qualified Data.Set as Set
4752import Data.Word (Word64 )
53+ import LeiosDemoDb (LeiosDbHandle , newLeiosDBInMemoryWith )
4854import LeiosDemoTypes (LeiosPoint (.. ), TraceLeiosKernel (.. ), hashLeiosEb )
4955import Lens.Micro (each , (%~) , (^.) , (^..) )
5056import Ouroboros.Consensus.Block (SlotNo (.. ))
@@ -55,13 +61,29 @@ import Ouroboros.Consensus.Cardano
5561 , ProtocolParamsShelleyBased (.. )
5662 , ShelleyGenesis (.. )
5763 )
64+ import Ouroboros.Consensus.Cardano.Block (pattern BlockConway )
5865import Ouroboros.Consensus.Cardano.Node (CardanoProtocolParams (.. ), protocolInfoCardano )
59- import Ouroboros.Consensus.Config (SecurityParam (.. ))
66+ import Ouroboros.Consensus.Config (SecurityParam (.. ), TopLevelConfig )
67+ import Ouroboros.Consensus.Ledger.Abstract
68+ ( ComputeLedgerEvents (OmitLedgerEvents )
69+ , LedgerCfg
70+ , tickThenReapply
71+ )
72+ import Ouroboros.Consensus.Ledger.Basics (EmptyMK , LedgerState , ValuesMK )
73+ import Ouroboros.Consensus.Ledger.Extended
74+ ( ExtLedgerCfg (.. )
75+ , ExtLedgerState (.. )
76+ , ledgerState
77+ )
6078import Ouroboros.Consensus.Ledger.SupportsMempool (extractTxs )
79+ import Ouroboros.Consensus.Ledger.Tables.Utils (applyDiffs , forgetLedgerTables )
6180import Ouroboros.Consensus.Mempool (TraceEventMempool (.. ))
62- import Ouroboros.Consensus.Node.ProtocolInfo (NumCoreNodes (.. ))
81+ import Ouroboros.Consensus.Node.ProtocolInfo (NumCoreNodes (.. ), ProtocolInfo ( .. ) )
6382import Ouroboros.Consensus.NodeId (CoreNodeId (.. ))
83+ import Ouroboros.Consensus.Shelley.Ledger.Block (shelleyBlockRaw )
6484import Ouroboros.Consensus.Shelley.Ledger.SupportsProtocol ()
85+ import Ouroboros.Consensus.Storage.LedgerDB.Forker (ResolveLeiosBlock (.. ))
86+ import qualified Ouroboros.Network.Mock.Chain as Chain
6587import Test.Cardano.Ledger.Alonzo.Examples.Consensus (exampleAlonzoGenesis )
6688import Test.Cardano.Ledger.Conway.Examples.Consensus (exampleConwayGenesis )
6789import Test.Consensus.Cardano.ProtocolInfo (Era (Conway ), hardForkInto )
@@ -72,6 +94,7 @@ import Test.QuickCheck
7294 , counterexample
7395 , tabulate
7496 , withMaxSuccess
97+ , (.&&.)
7598 , (===)
7699 )
77100import Test.Tasty (TestTree , testGroup )
@@ -97,7 +120,8 @@ import Test.ThreadNet.Infra.Shelley
97120 , signTx
98121 )
99122import Test.ThreadNet.Network
100- ( NodeOutput (.. )
123+ ( LeiosState (.. )
124+ , NodeOutput (.. )
101125 , TestNodeInitialization (.. )
102126 , _FromLeios
103127 , _FromMempool
@@ -118,6 +142,8 @@ tests =
118142 " Leios ThreadNet"
119143 [ testProperty " EB production" $
120144 withMaxSuccess 1 prop_leios_blocksProduced
145+ , testProperty " EB certificate inclusion" $
146+ withMaxSuccess 1 prop_leios_ebCertificateInclusion
121147 ]
122148
123149prop_leios_blocksProduced :: Seed -> Property
@@ -144,6 +170,7 @@ prop_leios_blocksProduced seed =
144170 & counterexample (" mempool total rejected: " <> show (length mempoolRejectedTxs))
145171 & tabulate " Praos blocks forged" [show $ length forgedBlocks]
146172 & tabulate " Leios blocks forged" [show $ length forgedEBs]
173+ & tabulate " Certifying blocks" [show $ length certifyingBlocks]
147174 & tabulate " Effective throughput" [show throughput]
148175 where
149176 traces = testOutput. allTraces
@@ -174,58 +201,148 @@ prop_leios_blocksProduced seed =
174201 TraceMempoolRejectedTx tx _ _ -> Just tx
175202 _ -> Nothing
176203
204+ nodeChains = nodeOutputFinalChain <$> testOutput. testOutputNodes
205+
206+ certifyingBlocks =
207+ [ blk
208+ | blk@ (BlockConway shelleyBlk) <- concatMap Chain. toOldestFirst nodeChains
209+ , SL. blockCertifiesEb (shelleyBlockRaw shelleyBlk)
210+ ]
211+
177212 throughput = fromIntegral (sum includedTxCounts) / fromRational numSlots :: Double
178213
179- testOutput =
214+ ( testOutput, _protocolInfo) =
180215 runThreadNet seed (NumSlots $ ceiling numSlots) (NumCoreNodes $ fromIntegral numNodes)
181216
182217 numNodes = 3 :: Integer
183218
184219 numSlots = 200
185220
221+ -- | Verify that independently replaying the chain with EB resolution produces
222+ -- the same ledger state as the one computed by the ChainDB during the
223+ -- simulation. This proves that EB transactions from certified EBs are
224+ -- actually applied to the ledger.
225+ prop_leios_ebCertificateInclusion :: Seed -> Property
226+ prop_leios_ebCertificateInclusion seed =
227+ ( not (null certifyingBlocks)
228+ & counterexample " no certifying blocks — test is vacuous"
229+ )
230+ .&&. (foldedLedger === expectedLedger)
231+ where
232+ (testOutput, ProtocolInfo {pInfoConfig, pInfoInitLedger}) =
233+ runThreadNet seed (NumSlots numSlots) (NumCoreNodes $ fromIntegral numNodes)
234+
235+ -- Pick any node — all nodes should converge to the same chain.
236+ someNode = snd . Map. findMin $ testOutput. testOutputNodes
237+
238+ nodeChains = nodeOutputFinalChain <$> testOutput. testOutputNodes
239+
240+ certifyingBlocks =
241+ [ blk
242+ | blk@ (BlockConway shelleyBlk) <- concatMap Chain. toOldestFirst nodeChains
243+ , SL. blockCertifiesEb (shelleyBlockRaw shelleyBlk)
244+ ]
245+
246+ -- nodeOutputFinalLedger has EmptyMK (tables stripped by ChainDB on
247+ -- extraction), so we strip the fold result's tables for comparison.
248+ expectedLedger = nodeOutputFinalLedger someNode
249+
250+ foldedLedger = replayNodeChain pInfoConfig pInfoInitLedger someNode
251+
252+ numNodes = 3 :: Integer
253+ numSlots = 200 :: Word64
254+
255+ -- | Replay a node's chain with Leios block resolution and return the
256+ -- resulting ledger state.
257+ replayNodeChain ::
258+ TopLevelConfig (CardanoBlock StandardCrypto ) ->
259+ ExtLedgerState (CardanoBlock StandardCrypto ) ValuesMK ->
260+ NodeOutput (CardanoBlock StandardCrypto ) ->
261+ LedgerState (CardanoBlock StandardCrypto ) EmptyMK
262+ replayNodeChain topConfig initLedger node = runSimOrThrow $ do
263+ let db = runIdentity . lsLeiosDb . nodeLeiosState $ node
264+ stateVar <- StrictTVar. newTVarIO db
265+ leiosDb <- newLeiosDBInMemoryWith stateVar
266+ let chain = Chain. toOldestFirst . nodeOutputFinalChain $ node
267+ cfg = ExtLedgerCfg topConfig
268+ foldedState <- foldWithResolution leiosDb cfg chain initLedger
269+ pure $ forgetLedgerTables . ledgerState $ foldedState
270+
271+ -- | Fold a chain of blocks over an initial ledger state, resolving Leios
272+ -- blocks (filling in EB transaction closures for certifying blocks) before
273+ -- each application.
274+ --
275+ -- We use 'tickThenReapply' because the blocks have already been validated
276+ -- by the ChainDB. We use 'applyDiffs' instead of 'applyDiffForKeys'
277+ -- because we need to accumulate the full UTxO — 'applyDiffForKeys' only
278+ -- retains entries referenced by the current block, which is designed for
279+ -- the LedgerDB's backing store architecture.
280+ foldWithResolution ::
281+ Monad m =>
282+ LeiosDbHandle m ->
283+ LedgerCfg (ExtLedgerState (CardanoBlock StandardCrypto )) ->
284+ [CardanoBlock StandardCrypto ] ->
285+ ExtLedgerState (CardanoBlock StandardCrypto ) ValuesMK ->
286+ m (ExtLedgerState (CardanoBlock StandardCrypto ) ValuesMK )
287+ foldWithResolution leiosDb cfg blks initState =
288+ foldM step initState blks
289+ where
290+ step state blk = do
291+ blk' <- resolveLeiosBlock leiosDb state blk
292+ pure $ applyDiffs state $ tickThenReapply OmitLedgerEvents cfg blk' state
293+
186294-- * Running the thread net
187295
188- runThreadNet :: Seed -> NumSlots -> NumCoreNodes -> TestOutput (CardanoBlock StandardCrypto )
296+ runThreadNet ::
297+ Seed ->
298+ NumSlots ->
299+ NumCoreNodes ->
300+ (TestOutput (CardanoBlock StandardCrypto ), ProtocolInfo (CardanoBlock StandardCrypto ))
189301runThreadNet initSeed numSlots numCoreNodes =
190- runTestNetwork
191- testConfig
192- testConfigB
193- TestConfigMB
194- { nodeInfo = \ (CoreNodeId nid) ->
195- let (protocolInfo, blockForging) =
196- protocolInfoCardano
197- CardanoProtocolParams
198- { byronProtocolParams =
199- ProtocolParamsByron
200- { byronGenesis
201- , byronPbftSignatureThreshold = Nothing
202- , byronProtocolVersion = Byron. ProtocolVersion 0 0 0
203- , byronSoftwareVersion = theProposedSoftwareVersion
204- , byronLeaderCredentials = Nothing
205- }
206- , shelleyBasedProtocolParams =
207- ProtocolParamsShelleyBased
208- { shelleyBasedInitialNonce = NeutralNonce
209- , shelleyBasedLeaderCredentials =
210- -- NOTE: Needed to hard-fork into shelley. After
211- -- that, with d=0, it's stake based leaders.
212- pure . mkLeaderCredentials $ coreNodes !! fromIntegral nid
213- }
214- , cardanoHardForkTriggers = hardForkInto Conway
215- , cardanoLedgerTransitionConfig =
216- -- TODO: provide better alonzo/conway genesis
217- mkLatestTransitionConfig shelleyGenesis exampleAlonzoGenesis exampleConwayGenesis
218- , cardanoCheckpoints = mempty
219- , cardanoProtocolVersion = conwayProtVer
220- }
221- in TestNodeInitialization
222- { tniProtocolInfo = protocolInfo
223- , tniCrucialTxs = []
224- , tniBlockForging = blockForging
225- }
226- , mkRekeyM = Nothing
227- }
302+ ( runTestNetwork
303+ testConfig
304+ testConfigB
305+ TestConfigMB
306+ { nodeInfo = \ (CoreNodeId nid) ->
307+ let (protocolInfo, blockForging) = protocolInfoCardano (cardanoProtocolParams nid)
308+ in TestNodeInitialization
309+ { tniProtocolInfo = protocolInfo
310+ , tniCrucialTxs = []
311+ , tniBlockForging = blockForging
312+ }
313+ , mkRekeyM = Nothing
314+ }
315+ , protocolInfo0
316+ )
228317 where
318+ protocolInfo0 = fst $ protocolInfoCardano @ StandardCrypto @ IO (cardanoProtocolParams (0 :: Word64 ))
319+
320+ cardanoProtocolParams nid =
321+ CardanoProtocolParams
322+ { byronProtocolParams =
323+ ProtocolParamsByron
324+ { byronGenesis
325+ , byronPbftSignatureThreshold = Nothing
326+ , byronProtocolVersion = Byron. ProtocolVersion 0 0 0
327+ , byronSoftwareVersion = theProposedSoftwareVersion
328+ , byronLeaderCredentials = Nothing
329+ }
330+ , shelleyBasedProtocolParams =
331+ ProtocolParamsShelleyBased
332+ { shelleyBasedInitialNonce = NeutralNonce
333+ , shelleyBasedLeaderCredentials =
334+ -- NOTE: Needed to hard-fork into shelley. After
335+ -- that, with d=0, it's stake based leaders.
336+ pure . mkLeaderCredentials $ coreNodes !! fromIntegral nid
337+ }
338+ , cardanoHardForkTriggers = hardForkInto Conway
339+ , cardanoLedgerTransitionConfig =
340+ -- TODO: provide better alonzo/conway genesis
341+ mkLatestTransitionConfig shelleyGenesis exampleAlonzoGenesis exampleConwayGenesis
342+ , cardanoCheckpoints = mempty
343+ , cardanoProtocolVersion = conwayProtVer
344+ }
345+
229346 conwayProtVer = ProtVer (eraProtVerLow @ ConwayEra ) 0
230347
231348 NumCoreNodes n = numCoreNodes
0 commit comments