Skip to content

Commit 440cfbd

Browse files
committed
db-analyser: verify the Leios certificate on the full-application path
A cert-RB has an empty body. Its transactions come from the EB that its parent announced, and 'applyLeiosClosure' applies them with 'ValidateNone': no signatures, no scripts, no balances. The certificate stands in for that work, so an analysis that skips it applies the EB transactions unchecked. 'applyBlockLeios' never called 'verifyLeiosCert', in either mode, and 'benchmarkLedgerOps' held its own copy of the apply path that skipped it too. So --store-ledger --full-ledger-validation, --checkThunks, --trace-ledger and --benchmark-ledger-ops accepted any certificate, while the help text for --full-ledger-validation promises "validating signatures and scripts". Export 'Forker.applyBlock' and 'Ap', then call them from 'applyBlockAtTip' and 'storeLedgerStateAt'. 'applyBlockLeios' goes away. The analyser now runs the node's code, so the two cannot drift apart again, and an invalid certificate reports one of the node's four rejection reasons. 'benchmarkLedgerOps' times each phase of block application on its own, so it cannot call 'Forker.applyBlock'. It calls the new 'verifyCertRb', which makes the same four checks. That cost lands in 'totalTime' and 'mut', and it has no column of its own yet. 'LedgerReapply' still skips the certificate. That is what the node does on its 'ReapplyVal' path.
1 parent 7f27044 commit 440cfbd

2 files changed

Lines changed: 117 additions & 70 deletions

File tree

  • ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser
  • ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB

ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Analysis.hs

Lines changed: 115 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,17 @@ import Data.Word (Word16, Word32, Word64)
4949
import qualified Debug.Trace as Debug
5050
import qualified GHC.Stats as GC
5151
import LeiosDemoDb (LeiosDbConnection, leiosDbLookupEbBody)
52-
import LeiosDemoTypes (BytesSize, EbHash, LeiosPoint, leiosMempoolSize, pointEbHash)
52+
import LeiosDemoTypes
53+
( BytesSize
54+
, EbHash
55+
, HasLeiosVoting (..)
56+
, LeiosExtValidationError (..)
57+
, LeiosPoint
58+
, leiosMempoolSize
59+
, minCertificationThreshold
60+
, pointEbHash
61+
, verifyLeiosCert
62+
)
5363
import NoThunks.Class (noThunks)
5464
import Ouroboros.Consensus.Block
5565
import Ouroboros.Consensus.Config
@@ -65,8 +75,6 @@ import Ouroboros.Consensus.HeaderValidation
6575
import Ouroboros.Consensus.Ledger.Abstract
6676
( ApplyBlock (getBlockKeySets, reapplyBlockLedgerResult)
6777
, applyBlockLedgerResult
68-
, tickThenApply
69-
, tickThenReapply
7078
)
7179
import Ouroboros.Consensus.Ledger.Basics
7280
import Ouroboros.Consensus.Ledger.Extended
@@ -86,16 +94,14 @@ import Ouroboros.Consensus.Storage.ImmutableDB (ImmutableDB)
8694
import qualified Ouroboros.Consensus.Storage.ImmutableDB as ImmutableDB
8795
import qualified Ouroboros.Consensus.Storage.LedgerDB as LedgerDB
8896
import Ouroboros.Consensus.Storage.LedgerDB.Forker
89-
( Forker'
90-
, LeiosClosureApplied (..)
91-
, ResolveLeiosBlock
97+
( ResolveLeiosBlock
98+
, announcingRbHash
9299
, applyLeiosClosure
93100
, blockLeiosCert
94101
, headerLeiosAnnouncement
95102
, inlineLeiosClosure
96103
, leiosClosureTxKeySets
97104
, protocolStateLeiosAnnouncement
98-
, resolveAndApplyLeiosClosure
99105
, resolveLeiosClosure
100106
)
101107
import qualified Ouroboros.Consensus.Util.IOLike as IOLike
@@ -115,6 +121,7 @@ runAnalysis ::
115121
, LedgerSupportsMempool blk
116122
, LedgerSupportsProtocol blk
117123
, ResolveLeiosBlock blk
124+
, HasLeiosVoting blk
118125
, CanStowLedgerTables (LedgerState blk)
119126
) =>
120127
AnalysisName -> SomeAnalysis blk
@@ -532,6 +539,7 @@ storeLedgerStateAt ::
532539
forall blk.
533540
( LedgerSupportsProtocol blk
534541
, ResolveLeiosBlock blk
542+
, HasLeiosVoting blk
535543
, HasAnalysis blk
536544
) =>
537545
SlotNo ->
@@ -545,12 +553,18 @@ storeLedgerStateAt slotNo ledgerAppMode env = do
545553
FromLedgerState ldb internal = startFrom
546554

547555
process :: () -> blk -> IO (NextStep, ())
548-
process _ blk = do
549-
oldLedger <- IOLike.atomically $ LedgerDB.getVolatileTip ldb
556+
process _ blk =
550557
LedgerDB.withTipForker
551558
ldb
552559
( \frk -> do
553-
result <- applyBlockLeios leiosDb ledgerAppMode OmitLedgerEvents cfg frk oldLedger blk
560+
result <-
561+
LedgerDB.applyBlock
562+
leiosDb
563+
OmitLedgerEvents
564+
(ExtLedgerCfg cfg)
565+
(apForMode ledgerAppMode blk)
566+
frk
567+
noBlockResolution
554568
case result of
555569
Right newLedger -> do
556570
LedgerDB.forkerPush frk newLedger
@@ -566,7 +580,8 @@ storeLedgerStateAt slotNo ledgerAppMode env = do
566580
when (blockSlot blk >= slotNo) storeLedgerState
567581
return (continue blk, ())
568582
Left err -> do
569-
traceWith tracer $ LedgerErrorEvent (blockPoint blk) err
583+
traceWith tracer $
584+
LedgerErrorEvent (blockPoint blk) (LedgerDB.annLedgerErr err)
570585
storeLedgerState
571586
pure (Stop, ())
572587
)
@@ -612,6 +627,7 @@ checkNoThunksEvery ::
612627
( HasAnalysis blk
613628
, LedgerSupportsProtocol blk
614629
, ResolveLeiosBlock blk
630+
, HasLeiosVoting blk
615631
, CanStowLedgerTables (LedgerState blk)
616632
) =>
617633
Word64 ->
@@ -663,6 +679,7 @@ traceLedgerProcessing ::
663679
( HasAnalysis blk
664680
, LedgerSupportsProtocol blk
665681
, ResolveLeiosBlock blk
682+
, HasLeiosVoting blk
666683
) =>
667684
Analysis blk StartFromLedgerState
668685
traceLedgerProcessing
@@ -711,6 +728,7 @@ benchmarkLedgerOps ::
711728
( LedgerSupportsProtocol blk
712729
, HasAnalysis blk
713730
, ResolveLeiosBlock blk
731+
, HasLeiosVoting blk
714732
) =>
715733
Maybe FilePath ->
716734
LedgerApplicationMode ->
@@ -760,6 +778,22 @@ benchmarkLedgerOps mOutfile ledgerAppMode AnalysisEnv{db, registry, startFrom, c
760778
) <-
761779
LedgerDB.withTipForker ledgerDB $ \frk -> do
762780
st <- IOLike.atomically $ LedgerDB.forkerGetLedgerState frk
781+
-- Verify the certificate before the EB it names is read, which is where
782+
-- 'LedgerDB.applyBlock' verifies it. The closure below applies without
783+
-- validation, so this is the only check its txs ever get.
784+
--
785+
-- The check runs inside the RTS-stats window, so its cost lands in
786+
-- 'DP.totalTime' and 'DP.mut'. It has no column of its own.
787+
case ledgerAppMode of
788+
LedgerReapply -> pure ()
789+
LedgerApply -> case verifyCertRb st blk of
790+
Left err ->
791+
fail $
792+
"benchmark doesn't support invalid certificates: "
793+
<> show rp
794+
<> " "
795+
<> show err
796+
Right () -> pure ()
763797
-- A cert-RB has an empty body. It applies the txs of the EB that its
764798
-- parent announced. Read those txs from the LeiosDb. A block that
765799
-- certifies no EB skips the read, so its EB figures stay 0. A 'clock'
@@ -928,6 +962,7 @@ getBlockApplicationMetrics ::
928962
( HasAnalysis blk
929963
, LedgerSupportsProtocol blk
930964
, ResolveLeiosBlock blk
965+
, HasLeiosVoting blk
931966
) =>
932967
NumberOfBlocks -> Maybe FilePath -> Analysis blk StartFromLedgerState
933968
getBlockApplicationMetrics (NumberOfBlocks nrBlocks) mOutFile env = do
@@ -983,6 +1018,7 @@ reproMempoolForge ::
9831018
, LedgerSupportsMempool blk
9841019
, LedgerSupportsProtocol blk
9851020
, ResolveLeiosBlock blk
1021+
, HasLeiosVoting blk
9861022
) =>
9871023
Int ->
9881024
Analysis blk StartFromLedgerState
@@ -1249,6 +1285,44 @@ parentAnnouncement ::
12491285
parentAnnouncement =
12501286
protocolStateLeiosAnnouncement @blk . headerStateChainDep . headerState
12511287

1288+
-- | Verify the Leios certificate of a cert-RB against the committee of the
1289+
-- parent ledger state. 'Right ()' for a block that carries no certificate.
1290+
--
1291+
-- This repeats what 'LedgerDB.applyBlock' does on its 'LedgerDB.ApplyVal' path,
1292+
-- and it returns the same four rejection reasons. An analysis that calls
1293+
-- 'LedgerDB.applyBlock' gets the check from there and must not call this.
1294+
-- 'benchmarkLedgerOps' times each phase of block application on its own, so it
1295+
-- cannot call 'LedgerDB.applyBlock', and it calls this instead.
1296+
--
1297+
-- The check matters because 'applyLeiosClosure' applies the txs of the EB
1298+
-- without validation: no signatures, no scripts, no balances.
1299+
verifyCertRb ::
1300+
forall blk.
1301+
( ResolveLeiosBlock blk
1302+
, HasLeiosVoting blk
1303+
) =>
1304+
-- | The unticked parent state
1305+
ExtLedgerState blk EmptyMK ->
1306+
blk ->
1307+
Either LeiosExtValidationError ()
1308+
verifyCertRb parent blk = case blockLeiosCert blk of
1309+
Nothing -> Right ()
1310+
Just cert -> case parentAnnouncement parent of
1311+
-- A cert-RB certifies the EB that its predecessor announced. If the parent
1312+
-- announced none, there is nothing to certify.
1313+
Nothing -> Left (LeiosCertificateWithoutAnnouncement cert)
1314+
Just (announcedPoint, _size) -> case getLeiosCommittee (ledgerState parent) of
1315+
-- A cert-RB in an era with no Leios committee is a protocol violation.
1316+
Nothing -> Left (LeiosMissingCommittee announcedPoint cert)
1317+
Just committee -> case announcingRbHash blk of
1318+
-- A cert-RB always has a non-genesis announcing parent.
1319+
Nothing -> Left (LeiosCertificateAfterGenesis cert announcedPoint)
1320+
Just rbHash ->
1321+
case verifyLeiosCert committee minCertificationThreshold rbHash cert of
1322+
Left invalid ->
1323+
Left (LeiosInvalidCertificate cert announcedPoint rbHash invalid)
1324+
Right _weight -> Right ()
1325+
12521326
-- | The EB that this block certifies. 'Nothing' for a block that carries no
12531327
-- Leios certificate.
12541328
certifiedEbHash ::
@@ -1394,66 +1468,31 @@ applyClosure lcfg closureTxs parent tables =
13941468
where
13951469
stateBeforeEb = ledgerState parent `ltwith` castLedgerTables tables
13961470

1397-
-- | Apply one block against the tip forker. For a CertRB, fold the EB closure
1398-
-- its parent announced onto the ledger state before applying the (empty) CertRB,
1399-
-- as 'Forker.applyBlock' does; other blocks apply as before.
1400-
applyBlockLeios ::
1401-
forall blk.
1402-
( LedgerSupportsProtocol blk
1403-
, ResolveLeiosBlock blk
1404-
) =>
1405-
LeiosDbConnection IO ->
1406-
LedgerApplicationMode ->
1407-
ComputeLedgerEvents ->
1408-
TopLevelConfig blk ->
1409-
Forker' IO blk ->
1410-
-- | Parent tip
1411-
ExtLedgerState blk EmptyMK ->
1412-
blk ->
1413-
IO (Either (ExtValidationError blk) (ExtLedgerState blk DiffMK))
1414-
applyBlockLeios leiosConn mode evs cfg frk parent blk =
1415-
case blockLeiosCert blk of
1416-
Nothing -> do
1417-
-- Ordinary block: read its inputs, apply its body.
1418-
values <- LedgerDB.forkerReadTables frk (getBlockKeySets blk)
1419-
pure $ applyInMode (parent `withLedgerTables` values)
1420-
Just{} ->
1421-
case parentAnnouncement parent of
1422-
Nothing ->
1423-
-- A CertRB's parent always announced an EB; its absence is a bug.
1424-
error "applyBlockLeios: CertRB whose parent announced no EB"
1425-
Just (announcedPoint, _size) -> do
1426-
res <-
1427-
resolveAndApplyLeiosClosure
1428-
leiosConn
1429-
(configLedger cfg)
1430-
(pointEbHash announcedPoint)
1431-
(fmap castLedgerTables . LedgerDB.forkerReadTables frk . castLedgerTables)
1432-
(castLedgerTables (getBlockKeySets blk :: LedgerTables (ExtLedgerState blk) KeysMK)) -- the RB body's own input keys
1433-
(ledgerState parent)
1434-
pure $ case res of
1435-
Left lerr ->
1436-
Left (ExtValidationErrorLedger lerr)
1437-
Right LeiosClosureApplied{lcaStateAfterEB, lcaClosureDiff} ->
1438-
-- Apply the CertRB (empty body) on the post-EB state, then prepend
1439-
-- the closure diff so the pushed diff covers both.
1440-
prependDiffs lcaClosureDiff
1441-
<$> applyInMode (parent{ledgerState = lcaStateAfterEB})
1442-
where
1443-
-- Apply 'blk' to the given ledger state, reapplying or fully applying per 'mode'.
1444-
applyInMode ::
1445-
ExtLedgerState blk ValuesMK ->
1446-
Either (ExtValidationError blk) (ExtLedgerState blk DiffMK)
1447-
applyInMode st = case mode of
1448-
LedgerReapply -> Right (tickThenReapply evs (ExtLedgerCfg cfg) blk st)
1449-
LedgerApply -> runExcept (tickThenApply evs (ExtLedgerCfg cfg) blk st)
1450-
1451-
-- | Read the ledger state at the tip (with the UTxOs this block consumes) and
1452-
-- apply the block to it via 'applyBlockLeios'. Fails loudly on any error: an
1453-
-- invalid block, or an EB closure that is missing or won't apply.
1471+
-- | The 'Ap' that a 'LedgerApplicationMode' selects.
1472+
--
1473+
-- The difference is not only the cost. On 'LedgerApply' the node verifies the
1474+
-- Leios certificate of a cert-RB, and on 'LedgerReapply' it does not. See
1475+
-- 'verifyCertRb'.
1476+
apForMode :: LedgerApplicationMode -> blk -> LedgerDB.Ap IO (ExtLedgerState blk) blk
1477+
apForMode = \case
1478+
LedgerApply -> LedgerDB.ApplyVal
1479+
LedgerReapply -> LedgerDB.ReapplyVal
1480+
1481+
-- | 'LedgerDB.applyBlock' resolves a block only for its @*Ref@ constructors, and
1482+
-- every caller here passes @*Val@. So no analysis ever reaches this.
1483+
noBlockResolution :: LedgerDB.ResolveBlock IO blk
1484+
noBlockResolution _ =
1485+
error "db-analyser: applyBlock asked to resolve a block, but every Ap is a *Val"
1486+
1487+
-- | Read the ledger state at the tip (with the UTxOs this block
1488+
-- consumes) and apply the block to it via the node's own
1489+
-- 'LedgerDB.applyBlock'. Fails on any error: an invalid block, an
1490+
-- invalid Leios certificate, or an EB closure that is missing or
1491+
-- won't apply.
14541492
applyBlockAtTip ::
14551493
( LedgerSupportsProtocol blk
14561494
, ResolveLeiosBlock blk
1495+
, HasLeiosVoting blk
14571496
) =>
14581497
LeiosDbConnection IO ->
14591498
LedgerApplicationMode ->
@@ -1467,8 +1506,14 @@ applyBlockAtTip leiosConn mode cfg ldb blk =
14671506
oldLedgerTbs <- LedgerDB.forkerReadTables frk (getBlockKeySets blk)
14681507
let preState = oldLedgerSt `withLedgerTables` oldLedgerTbs
14691508
applied <-
1470-
either (error . show) id
1471-
<$> applyBlockLeios leiosConn mode OmitLedgerEvents cfg frk oldLedgerSt blk
1509+
either (error . show . LedgerDB.annLedgerErr) id
1510+
<$> LedgerDB.applyBlock
1511+
leiosConn
1512+
OmitLedgerEvents
1513+
(ExtLedgerCfg cfg)
1514+
(apForMode mode blk)
1515+
frk
1516+
noBlockResolution
14721517
pure (preState, applied)
14731518

14741519
{-------------------------------------------------------------------------------

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/Forker.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ module Ouroboros.Consensus.Storage.LedgerDB.Forker
4646
-- * Validation
4747
, AnnLedgerError (..)
4848
, AnnLedgerError'
49+
, Ap (..)
4950
, ResolveBlock
5051
, LeiosClosureApplied (..)
5152
, ResolveLeiosBlock (..)
53+
, applyBlock
5254
, resolveAndApplyLeiosClosure
5355
, resolveLeiosBlock
5456
, SuccessForkerAction (..)

0 commit comments

Comments
 (0)