Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ For top level release notes, leave all the headers commented out.
### Breaking

- `TxLimits` has the new superclass constraint `TxEbMeasureByteSize (TxEbMeasure blk)`, and `CanHardFork` has `TxEbMeasureByteSize (HardForkTxEbMeasure xs)`. The new class `TxEbMeasureByteSize` has one method, `txEbMeasureByteSize`: the size of the transaction references that an endorser-block measure counts, without the CBOR framing. `TxMeasure blk` and `IgnoringOverflow ByteSize32` return zero, and `DijkstraEbMeasure` returns its `txReferencesSize`. A block with a new endorser-block measure type must give an instance. The EB-forged machine trace reports this size as `txReferencesSize`.
- `forgeBlock` returns the new record `ForgeBlockResult`. Its field `rankingBlock` is the forged block, and its field `endorserBlock` is the `Maybe LeiosEb` that the block announces. Only Dijkstra (`Praos2`) builds an endorser block, and only from a non-empty `fbEbTxs`. Every other era returns `Nothing`.
- `forgeBlock` returns the new record `ForgeBlockResult`. Its field `rankingBlock` is the forged block, and its field `endorserBlock` is the `Maybe ForgedLeiosEb` that the block announces. Only Dijkstra (`Praos2`) builds an endorser block, and only from a non-empty `fbEbTxs`. Every other era returns `Nothing`.
- `forgeShelleyBlock` returns a `ForgeBlockResult`, and requires `Foldable (WhenLeios proto)`. If the protocol has Leios and `fbEbTxs` is not empty, the header announces the endorser block that `forgeShelleyBlock` returns.
- `praos2SharedBlockForging` requires `LedgerSupportsProtocol (ShelleyBlock (Praos2 c) era)`. Its `forgeBlock` asserts that the endorser block it returns is not larger than the `llvMaxEbBodySize` of the `plvLeios` ledger view.
- `ForgeBlockArgs` has a new field, `fbEbTxs`: the transactions for the endorser block. `snapshotPartitionWithInitialPayload` selects them after the ranking-block transactions in `fbTxs`.
- `Ouroboros.Consensus.NodeKernel.Forge.forge` takes a new argument. It calls this argument with each forged endorser block after `forgeBlock` and before it adds the ranking block to the ChainDB. The node kernel and db-synthesizer pass a function that does nothing.
- `Ouroboros.Consensus.NodeKernel.Forge.forge` takes a new argument. It calls this argument with each forged endorser block after `forgeBlock` and before it adds the ranking block to the ChainDB. The node kernel passes `storeForgedEb`, and db-synthesizer passes a function that does nothing.
- `TraceForgeEvent` has a new constructor, `TraceForgedEndorserBlock`. `forge` traces it after `TraceForgedBlock` when `forgeBlock` returns an endorser block. The `tracing` library renders it under the namespace `ForgedEndorserBlock`, with the hash of the ranking block that announces the endorser block, the endorser block hash, the number of references, the size in bytes, and the selected transactions' measure (transaction bytes, execution units, and reference-script bytes).

### Non-Breaking

- `Ouroboros.Consensus.NodeKernel.Forge.forge` gives `ebCapacityTxMeasure` to `snapshotPartitionWithInitialPayload`. If the mempool has more transactions than the Dijkstra ranking block takes, the node forges an endorser block with the next transactions.
- New functions in `Ouroboros.Consensus.Leios.Types`: `leiosEbFromTxBytes` builds an endorser block from the bytes of its transactions, and `hashLeiosEb` gives the hash that a header announces for an endorser block.
- New in `Ouroboros.Consensus.Leios.Types`: the record `ForgedLeiosEb` holds an endorser block and the bytes of its transactions. `forgeLeiosEb` builds a `ForgedLeiosEb` from the bytes of the transactions.
13 changes: 13 additions & 0 deletions changelog.d/20261022_120000_damian.nadales_eb_forge_store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!--
A new scriv changelog fragment.

Uncomment the section that is right (remove the HTML comment wrapper).
For top level release notes, leave all the headers commented out.
-->

### Non-Breaking

- If a write to the in-memory LeiosDB fails, `writeEbPoint`, `writeEbBody` and `writeTxs` throw at submission. Thus a caller that does not `await` the `Promise` sees the failure.
- The node kernel writes each forged endorser block to the LeiosDB. The forge does not await the writes. On SQLite, it blocks while the write queue is full. If a write fails, the node stops.
- The depth of the LeiosDB SQLite write queue counts three jobs for the forge: the point, the body and the transactions of a forged endorser block.
- `Ouroboros.Consensus.Storage.LeiosDB.API` exports `storeForgedEb`, which writes a forged endorser block to the LeiosDB. The node kernel gives it to `forge`.
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ import Ouroboros.Consensus.Config
import Ouroboros.Consensus.Ledger.Abstract
import Ouroboros.Consensus.Ledger.SupportsMempool
import Ouroboros.Consensus.Leios.Types
( EndorserBlock
( ForgedLeiosEb (..)
, encodeEndorserBlockSize
, endorserBlockFromTxBytes
, endorserBlockHash
, forgeLeiosEb
)
import Ouroboros.Consensus.Protocol.Abstract (CanBeLeader)
import Ouroboros.Consensus.Protocol.Ledger.HotKey (HotKey)
Expand Down Expand Up @@ -99,13 +99,13 @@ forgeShelleyBlock
-- Only a protocol with Leios has a value in 'WhenLeios', so the other
-- protocols build no endorser block. CIP-0164 does not allow an empty
-- endorser block.
forgedEb :: WhenLeios proto (Maybe EndorserBlock)
forgedEb :: WhenLeios proto (Maybe ForgedLeiosEb)
forgedEb =
pureLeiosOnly @proto $
endorserBlockFromTxBytes . fmap (ebTxBytes . extractTx) <$> nonEmpty fbEbTxs
forgeLeiosEb . fmap (ebTxBytes . extractTx) <$> nonEmpty fbEbTxs

-- TODO Forging does not yet certify endorser blocks.
leiosFields = (,) False . maybeToStrictMaybe . fmap announce <$> forgedEb
leiosFields = (,) False . maybeToStrictMaybe . fmap (announce . forgedEbBody) <$> forgedEb

announce eb =
EbReferencesAnnouncement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Ouroboros.Consensus.Block
import Ouroboros.Consensus.Config (configConsensus, configLedger)
import Ouroboros.Consensus.Ledger.SupportsProtocol (LedgerSupportsProtocol (..))
import Ouroboros.Consensus.Leios.Protocol (LeiosLedgerView (..))
import Ouroboros.Consensus.Leios.Types (encodeEndorserBlockSize)
import Ouroboros.Consensus.Leios.Types (ForgedLeiosEb (..), encodeEndorserBlockSize)
import Ouroboros.Consensus.Protocol.Abstract (CanBeLeader, ConsensusConfig)
import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey
import Ouroboros.Consensus.Protocol.Praos
Expand Down Expand Up @@ -151,4 +151,4 @@ praos2SharedBlockForging hotKey slotToPeriod credentials =
where
Praos2HasLeios leiosLedgerView =
plvLeios $ protocolLedgerView (configLedger fbConfig) fbCurrentTickedLedgerState
fits eb = encodeEndorserBlockSize eb <= llvMaxEbBodySize leiosLedgerView
fits eb = encodeEndorserBlockSize (forgedEbBody eb) <= llvMaxEbBodySize leiosLedgerView
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Cardano.Slotting.EpochInfo (fixedEpochInfo)
import Codec.CBOR.Write (toStrictByteString)
import Control.Tracer (nullTracer)
import qualified Data.ByteString as BS
import Data.Foldable (toList)
import Data.Maybe (isJust)
import Lens.Micro ((&), (.~), (^.))
import Ouroboros.Consensus.Block
Expand All @@ -43,6 +44,8 @@ import Ouroboros.Consensus.Leios.Types
, encodeEndorserBlockSize
, endorserBlockHash
, endorserBlockReferences
, forgedEbBody
, forgedEbTxs
)
import Ouroboros.Consensus.Protocol.Praos
( ConsensusConfig (..)
Expand Down Expand Up @@ -94,30 +97,33 @@ prop_announcesEb =
forAllBlind (genCoreNode (SL.KESPeriod 0)) $ \coreNode ->
forAll genNumEbTxs $ \numEbTxs -> ioProperty $ do
let ebTxs = take numEbTxs distinctTxs
ForgeBlockResult blk forgedEb <- forgeWith coreNode ebTxs
(_, ForgeBlockResult blk forgedEb) <- forgeWith coreNode ebTxs
pure $ case forgedEb of
Nothing -> counterexample "no endorser block" False
Just eb ->
conjoin
[ counterexample "the endorser block does not reference the transactions in order" $
endorserBlockReferences eb === map reference ebTxs
, counterexample "encodeEndorserBlockSize differs from the length of encodeEndorserBlock" $
encodeEndorserBlockSize eb
=== fromIntegral (BS.length (toStrictByteString (encodeEndorserBlock eb)))
, announcement blk
=== SJust
EbReferencesAnnouncement
{ ebReferencesAnnouncementHash = unsafeMakeSafeHash (endorserBlockHash eb)
, ebReferencesAnnouncementSize = encodeEndorserBlockSize eb
}
, counterexample "the block does not match its header" $
blockMatchesHeader (getHeader blk) blk
]
Just forged ->
let eb = forgedEbBody forged
in conjoin
[ counterexample "the endorser block does not reference the transactions in order" $
endorserBlockReferences eb === map reference ebTxs
, counterexample "forgedEbTxs does not hold the referenced transaction bytes" $
toList (forgedEbTxs forged) === map txBytes ebTxs
, counterexample "encodeEndorserBlockSize differs from the length of encodeEndorserBlock" $
encodeEndorserBlockSize eb
=== fromIntegral (BS.length (toStrictByteString (encodeEndorserBlock eb)))
, announcement blk
=== SJust
EbReferencesAnnouncement
{ ebReferencesAnnouncementHash = unsafeMakeSafeHash (endorserBlockHash eb)
, ebReferencesAnnouncementSize = encodeEndorserBlockSize eb
}
, counterexample "the block does not match its header" $
blockMatchesHeader (getHeader blk) blk
]

prop_noEb :: Property
prop_noEb =
forAllBlind (genCoreNode (SL.KESPeriod 0)) $ \coreNode -> ioProperty $ do
ForgeBlockResult blk forgedEb <- forgeWith coreNode []
(_, ForgeBlockResult blk forgedEb) <- forgeWith coreNode []
pure $ (announcement blk, isJust forgedEb) === (SNothing, False)

-- | A number of endorser-block transactions. 'encodeEndorserBlockSize' writes a
Expand All @@ -130,12 +136,13 @@ genNumEbTxs =
]

-- | Forge the block after the example ledger state, with the given endorser
-- block transactions and no ranking block transactions.
-- block transactions and no ranking block transactions. Also return the
-- config of the forge.
forgeWith ::
CoreNode StandardCrypto ->
[GenTx StandardDijkstraBlock] ->
IO (ForgeBlockResult StandardDijkstraBlock)
forgeWith coreNode ebTxs = do
IO (TopLevelConfig StandardDijkstraBlock, ForgeBlockResult StandardDijkstraBlock)
forgeWith coreNode ebTxs = fmap ((,) topLevelConfig) $ do
hotKey <-
instantiatePraosCredentials
(SL.maxKESEvo globals)
Expand Down Expand Up @@ -240,12 +247,14 @@ distinctTxs =
-- | The reference that an endorser block holds for a transaction: the
-- Blake2b-256 hash and the length of the transaction bytes.
reference :: GenTx StandardDijkstraBlock -> (TxHash, BytesSize)
reference (ShelleyTx _ tx) =
( Hash.castHash (Hash.hashWith @HASH id bytes)
, fromIntegral (BS.length bytes)
reference tx =
( Hash.castHash (Hash.hashWith @HASH id (txBytes tx))
, fromIntegral (BS.length (txBytes tx))
)
where
bytes = serialize' (eraProtVerLow @DijkstraEra) tx

-- | The bytes of a transaction that an endorser block references.
txBytes :: GenTx StandardDijkstraBlock -> BS.ByteString
txBytes (ShelleyTx _ tx) = serialize' (eraProtVerLow @DijkstraEra) tx

exampleDijkstraBlock :: StandardDijkstraBlock
exampleDijkstraBlock = example Examples.exampleBlock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ import Ouroboros.Consensus.Storage.ChainDB.API
import qualified Ouroboros.Consensus.Storage.ChainDB.API as ChainDB
import Ouroboros.Consensus.Storage.ChainDB.Init (InitChainDB)
import qualified Ouroboros.Consensus.Storage.ChainDB.Init as InitChainDB
import Ouroboros.Consensus.Storage.LeiosDB.API (LeiosDbHandle)
import Ouroboros.Consensus.Storage.LeiosDB.API
( LeiosDbHandle
, LeiosDbWriter (..)
, openWriter
, storeForgedEb
)
import Ouroboros.Consensus.Util.AnchoredFragment
( preferAnchoredCandidate
)
Expand Down Expand Up @@ -487,6 +492,7 @@ data InternalState m addrNTN addrNTC blk = IS
, registry :: ResourceRegistry m
, btime :: BlockchainTime m
, chainDB :: ChainDB m blk
, leiosDB :: LeiosDbHandle m
, blockFetchInterface ::
BlockFetchConsensusInterface (ConnectionId addrNTN) (HeaderWithTime blk) blk blk m
, fetchClientRegistry :: FetchClientRegistry (ConnectionId addrNTN) (HeaderWithTime blk) blk blk m
Expand All @@ -511,6 +517,7 @@ initInternalState
NodeKernelArgs
{ tracers
, chainDB
, leiosDB
, registry
, cfg
, blockFetchSize
Expand Down Expand Up @@ -582,9 +589,9 @@ forkBlockForging IS{..} (MkBlockForging blockForgingM) =
forkLinkedWatcherAllocate
registry
label
blockForgingMLabel
finalize
( \bf -> knownSlotWatcher btime $
allocateForging
finalizeForging
( \(bf, leiosDbWriter) -> knownSlotWatcher btime $
\currentSlot ->
withEarlyExit_ $
forge
Expand All @@ -594,17 +601,23 @@ forkBlockForging IS{..} (MkBlockForging blockForgingM) =
chainDB
mempool
bf
(\_ _ -> pure ())
(storeForgedEb leiosDbWriter . blockSlot)
currentSlot
)
where
label :: String
label = "NodeKernel.blockForging"

blockForgingMLabel = do
allocateForging = do
bf <- blockForgingM
labelThisThread $ Text.unpack $ forgeLabel bf
pure bf
leiosDbWriter <- openWriter leiosDB
pure (bf, leiosDbWriter)

-- 'finalize' erases the KES key, so it runs first: 'closeWriter' waits for
-- the LeiosDB write queue.
finalizeForging (bf, leiosDbWriter) =
finalize bf `finally` closeWriter leiosDbWriter

{-------------------------------------------------------------------------------
TxSubmission integration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import Ouroboros.Consensus.Ledger.Extended
import Ouroboros.Consensus.Ledger.SupportsMempool
import Ouroboros.Consensus.Ledger.SupportsProtocol
import Ouroboros.Consensus.Ledger.Tables.Utils (forgetLedgerTables)
import Ouroboros.Consensus.Leios.Types (EndorserBlock)
import Ouroboros.Consensus.Leios.Types (ForgedLeiosEb (..))
import Ouroboros.Consensus.Mempool
import Ouroboros.Consensus.Mempool.API (MempoolMeasure (..))
import Ouroboros.Consensus.Node.Run
Expand Down Expand Up @@ -71,7 +71,7 @@ forge ::
-- | 'forge' calls this function after 'forgeBlock' and before
-- 'addBlockToChainDB'. The arguments are the header of the forged ranking
-- block and the endorser block that it announces.
(Header blk -> EndorserBlock -> m ()) ->
(Header blk -> ForgedLeiosEb -> m ()) ->
SlotNo ->
WithEarlyExit m ()
forge forgeEventTracer forgeStateInfoTracer cfg chainDB mempool blockForging onForgedLeiosEb currentSlot = do
Expand Down Expand Up @@ -165,7 +165,7 @@ forge forgeEventTracer forgeStateInfoTracer cfg chainDB mempool blockForging onF
TraceForgedEndorserBlock
currentSlot
(blockHash (Block.rankingBlock forgeRes))
eb
(forgedEbBody eb)
(endorserBlockMeasure selectedTxs)

addBlockToChainDB trace chainDB mempool currentSlot (fbTxs fbArgs) (Block.rankingBlock forgeRes)
Expand Down
2 changes: 2 additions & 0 deletions ouroboros-consensus-diffusion/test/consensus-test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import qualified Test.Consensus.Genesis.TestSuite.SmallKey.Tests (tests)
import qualified Test.Consensus.Genesis.Tests (tests)
import qualified Test.Consensus.HardFork.Combinator (tests)
import qualified Test.Consensus.Leios.ChainDB (tests)
import qualified Test.Consensus.Leios.Forge (tests)
import qualified Test.Consensus.Leios.LeiosFetch (tests)
import qualified Test.Consensus.Leios.LeiosNotify (tests)
import qualified Test.Consensus.Leios.Relay (tests)
Expand Down Expand Up @@ -42,6 +43,7 @@ tests =
, Test.Consensus.BlockTree.Tests.tests
, Test.Consensus.Genesis.TestSuite.SmallKey.Tests.tests
, Test.Consensus.Leios.ChainDB.tests
, Test.Consensus.Leios.Forge.tests
, Test.Consensus.Leios.LeiosFetch.tests
, Test.Consensus.Leios.LeiosNotify.tests
, Test.Consensus.Leios.Relay.tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

module Test.Consensus.HardFork.Combinator (tests) where

import qualified Cardano.Crypto.Hash as Hash
import Cardano.Ledger.BaseTypes (knownNonZeroBounded, nonZero, unNonZero)
import Data.ByteString.Short (fromShort)
import Data.Function (on)
import Data.Functor.Identity (Identity (..))
import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
import qualified Data.Map.Strict as Map
import Data.MemPack
import Data.SOP.BasicFunctors
Expand Down Expand Up @@ -55,7 +55,7 @@ import Ouroboros.Consensus.Ledger.Abstract
import Ouroboros.Consensus.Ledger.Extended
import Ouroboros.Consensus.Ledger.Peras (initPerasState)
import Ouroboros.Consensus.Ledger.SupportsMempool
import Ouroboros.Consensus.Leios.Types (EndorserBlock, mkEndorserBlock)
import Ouroboros.Consensus.Leios.Types (ForgedLeiosEb, forgeLeiosEb)
import Ouroboros.Consensus.Node.NetworkProtocolVersion
import Ouroboros.Consensus.Node.ProtocolInfo
import Ouroboros.Consensus.NodeId
Expand Down Expand Up @@ -112,7 +112,7 @@ unit_hardForkForgeBlockPassesTxsAndReturnsEb = do
case forged of
Z (I blkA) -> blkA_body blkA @?= [txA 1, txA 2]
S _ -> assertFailure "the block is not from era A"
forgedEb @?= Just (ebListing [txA 3])
forgedEb @?= Just (mkForgedLeiosEb (txA 3 :| []))
where
hardForkForging :: BlockForging Identity TestBlock
hardForkForging =
Expand All @@ -134,14 +134,12 @@ unit_hardForkForgeBlockPassesTxsAndReturnsEb = do
pure
ForgeBlockResult
{ rankingBlock = blkA{blkA_body = map forgetValidatedGenTxA (fbTxs argsA)}
, endorserBlock = Just $ ebListing $ map forgetValidatedGenTxA (fbEbTxs argsA)
, endorserBlock = mkForgedLeiosEb <$> nonEmpty (map forgetValidatedGenTxA (fbEbTxs argsA))
}
}

ebListing :: [GenTx BlockA] -> EndorserBlock
ebListing txs =
mkEndorserBlock
[(Hash.castHash (Hash.hashWith fromShort (toRawTxIdHash (txId tx))), 0) | tx <- txs]
mkForgedLeiosEb :: NonEmpty (GenTx BlockA) -> ForgedLeiosEb
mkForgedLeiosEb = forgeLeiosEb . fmap (fromShort . toRawTxIdHash . txId)

txA :: Int -> GenTx BlockA
txA n = TxA (TxIdA n) InitiateAtoB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ tests =
-- announcements and the GC has run, these are the bodies of A and B that the
-- LeiosDB still holds.
bodiesAfterGc :: IOSim s ([(TxHash, BytesSize)], [(TxHash, BytesSize)])
bodiesAfterGc = withNode $ \nut -> do
let leiosDb = getLeiosDB (nutKernel nut)
bodiesAfterGc = withNode $ \node -> do
let leiosDb = getLeiosDB (nodeKernel node)
withWriter leiosDb $ \writer ->
forM_ [(pointA, ebA), (pointB, ebB)] $ \(point, eb) -> do
await =<< writeEbPoint writer point (encodeEndorserBlockSize eb)
Expand All @@ -57,7 +57,7 @@ bodiesAfterGc = withNode $ \nut -> do
]
-- Let the slots of the chain's blocks pass, so none is from the future.
threadDelay 20
mapM_ (ChainDB.addBlock_ (nutChainDB nut) noPunishment) chain
mapM_ (ChainDB.addBlock_ (nodeChainDB node) noPunishment) chain
-- Long enough for the copy to the ImmutableDB and for the delayed GC.
threadDelay 100
withReader leiosDb $ \reader ->
Expand Down
Loading
Loading