diff --git a/cabal.project b/cabal.project index da2ec2c4e6..d759310ea3 100644 --- a/cabal.project +++ b/cabal.project @@ -14,9 +14,9 @@ repository cardano-haskell-packages -- update either of these. index-state: -- Bump this if you need newer packages from Hackage - , hackage.haskell.org 2025-04-08T10:52:25Z + , hackage.haskell.org 2025-04-16T18:30:40Z -- Bump this if you need newer packages from CHaP - , cardano-haskell-packages 2025-04-08T11:09:22Z + , cardano-haskell-packages 2025-04-29T14:14:35Z packages: ouroboros-consensus @@ -34,6 +34,10 @@ multi-repl: True import: ./asserts.cabal +allow-newer: + serdoc-core:tasty-quickcheck + , kes-agent:base + package ouroboros-network -- Certain ThreadNet tests rely on transactions to be submitted promptly after -- a node (re)start. Therefore, we disable this flag (see @@ -45,6 +49,10 @@ if(os(windows)) constraints: bitvec -simd +if impl (ghc < 9) + allow-older: + kes-agent:template-haskell + if impl (ghc >= 9.12) allow-newer: -- https://github.com/phadej/vec/issues/118 @@ -57,3 +65,16 @@ if impl (ghc >= 9.12) -- https://github.com/kapralVV/Unique/issues/11 , Unique:hashable + + , serdoc-core:template-haskell + , serdoc-core:th-abstraction + , kes-agent:containers + , kes-agent:extra + +source-repository-package + type: git + location: https://github.com/input-output-hk/kes-agent + tag: a212f98b9bc567ebf3c8472e62f28450990f7821 + --sha256: sha256-b/6L3WqQ+fY1K6uBJVeoOTE4RAc3SUc72qZtXO0DAfE= + subdir: + kes-agent diff --git a/ouroboros-consensus-cardano/changelog.d/20250130_093803_tdammers_mlocked_kes_rebase.md b/ouroboros-consensus-cardano/changelog.d/20250130_093803_tdammers_mlocked_kes_rebase.md new file mode 100644 index 0000000000..073423d539 --- /dev/null +++ b/ouroboros-consensus-cardano/changelog.d/20250130_093803_tdammers_mlocked_kes_rebase.md @@ -0,0 +1,8 @@ +### Breaking + +- Use new mlocked KES API for all internal KES sign key handling. +- Add finalizers to all block forgings (required by `ouroboros-consensus`). +- Change `ShelleyLeaderCredentials` to not contain the KES sign key itself + anymore. Instead, the `CanBeLeader` data structure now contains a + `praosCanBeLeaderCredentialsSource` field, which specifies how to obtain the + actual credentials (OpCert and KES SignKey). diff --git a/ouroboros-consensus-cardano/ouroboros-consensus-cardano.cabal b/ouroboros-consensus-cardano/ouroboros-consensus-cardano.cabal index 6bae94980a..218732b6af 100644 --- a/ouroboros-consensus-cardano/ouroboros-consensus-cardano.cabal +++ b/ouroboros-consensus-cardano/ouroboros-consensus-cardano.cabal @@ -149,9 +149,11 @@ library cardano-strict-containers, cborg ^>=0.2.2, containers >=0.5 && <0.8, + contra-tracer, crypton, deepseq, formatting >=6.3 && <7.3, + kes-agent, measures, mempack, microlens, @@ -160,6 +162,7 @@ library ouroboros-consensus ^>=0.26, ouroboros-consensus-protocol ^>=0.12, ouroboros-network-api ^>=0.13, + serdoc-core, serialise ^>=0.2, singletons ^>=3.0, small-steps, @@ -323,7 +326,9 @@ library unstable-shelley-testlib cardano-slotting, cardano-strict-containers, containers, + contra-tracer, generic-random, + kes-agent, microlens, mtl, ouroboros-consensus:{ouroboros-consensus, unstable-consensus-testlib}, @@ -364,6 +369,7 @@ test-suite shelley-test cborg, constraints, containers, + contra-tracer, filepath, measures, microlens, @@ -415,6 +421,7 @@ library unstable-cardano-testlib cardano-strict-containers, cborg, containers, + contra-tracer, mempack, microlens, mtl, diff --git a/ouroboros-consensus-cardano/src/byron/Ouroboros/Consensus/Byron/Node.hs b/ouroboros-consensus-cardano/src/byron/Ouroboros/Consensus/Byron/Node.hs index 9fb24f382d..76082e4c4e 100644 --- a/ouroboros-consensus-cardano/src/byron/Ouroboros/Consensus/Byron/Node.hs +++ b/ouroboros-consensus-cardano/src/byron/Ouroboros/Consensus/Byron/Node.hs @@ -140,6 +140,7 @@ byronBlockForging creds = BlockForging { slot tickedPBftState , forgeBlock = \cfg -> return ....: forgeByronBlock cfg + , finalize = pure () } where canBeLeader = mkPBftCanBeLeader creds diff --git a/ouroboros-consensus-cardano/src/ouroboros-consensus-cardano/Ouroboros/Consensus/Cardano/Node.hs b/ouroboros-consensus-cardano/src/ouroboros-consensus-cardano/Ouroboros/Consensus/Cardano/Node.hs index aa5d04d4b9..446b67ba64 100644 --- a/ouroboros-consensus-cardano/src/ouroboros-consensus-cardano/Ouroboros/Consensus/Cardano/Node.hs +++ b/ouroboros-consensus-cardano/src/ouroboros-consensus-cardano/Ouroboros/Consensus/Cardano/Node.hs @@ -58,12 +58,12 @@ import qualified Cardano.Ledger.Api.Transition as L import qualified Cardano.Ledger.BaseTypes as SL import qualified Cardano.Ledger.Shelley.API as SL import Cardano.Prelude (cborError) -import qualified Cardano.Protocol.TPraos.OCert as Absolute (KESPeriod (..), - ocertKESPeriod) +import qualified Cardano.Protocol.TPraos.OCert as Absolute (KESPeriod (..)) import qualified Codec.CBOR.Decoding as CBOR import Codec.CBOR.Encoding (Encoding) import qualified Codec.CBOR.Encoding as CBOR import Control.Exception (assert) +import qualified Control.Tracer as Tracer import qualified Data.ByteString.Short as Short import Data.Functor.These (These1 (..)) import qualified Data.Map.Strict as Map @@ -98,10 +98,10 @@ import Ouroboros.Consensus.Ledger.Tables.Utils (forgetLedgerTables) import Ouroboros.Consensus.Node.NetworkProtocolVersion import Ouroboros.Consensus.Node.ProtocolInfo import Ouroboros.Consensus.Node.Run -import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey import Ouroboros.Consensus.Protocol.Praos (Praos, PraosParams (..)) +import Ouroboros.Consensus.Protocol.Praos.AgentClient import Ouroboros.Consensus.Protocol.Praos.Common - (praosCanBeLeaderOpCert) + (PraosCanBeLeader (..), instantiatePraosCredentials) import Ouroboros.Consensus.Protocol.TPraos (TPraos, TPraosParams (..)) import qualified Ouroboros.Consensus.Protocol.TPraos as Shelley import Ouroboros.Consensus.Shelley.HFEras () @@ -118,7 +118,6 @@ import qualified Ouroboros.Consensus.Shelley.Node.TPraos as TPraos import Ouroboros.Consensus.Storage.Serialisation import Ouroboros.Consensus.TypeFamilyWrappers import Ouroboros.Consensus.Util.Assert -import Ouroboros.Consensus.Util.IOLike {------------------------------------------------------------------------------- SerialiseHFC @@ -488,10 +487,13 @@ data CardanoProtocolParams c = CardanoProtocolParams { -- PRECONDITION: only a single set of Shelley credentials is allowed when used -- for mainnet (check against @'SL.gNetworkId' == 'SL.Mainnet'@). protocolInfoCardano :: - forall c m. (IOLike m, CardanoHardForkConstraints c) + forall c m. + ( CardanoHardForkConstraints c + , KESAgentContext c m + ) => CardanoProtocolParams c -> ( ProtocolInfo (CardanoBlock c) - , m [BlockForging m (CardanoBlock c)] + , Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (CardanoBlock c)] ) protocolInfoCardano paramsCardano | SL.Mainnet <- SL.sgNetworkId genesisShelley @@ -503,7 +505,7 @@ protocolInfoCardano paramsCardano pInfoConfig = cfg , pInfoInitLedger = initExtLedgerStateCardano } - , blockForging + , mkBlockForgings ) where CardanoProtocolParams { @@ -854,9 +856,9 @@ protocolInfoCardano paramsCardano -- credentials. If there are multiple Shelley credentials, we merge the -- Byron credentials with the first Shelley one but still have separate -- threads for the remaining Shelley ones. - blockForging :: m [BlockForging m (CardanoBlock c)] - blockForging = do - shelleyBased <- traverse blockForgingShelleyBased credssShelleyBased + mkBlockForgings :: Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (CardanoBlock c)] + mkBlockForgings tr = do + shelleyBased <- traverse (blockForgingShelleyBased tr) credssShelleyBased let blockForgings :: [NonEmptyOptNP (BlockForging m) (CardanoEras c)] blockForgings = case (mBlockForgingByron, shelleyBased) of (Nothing, shelleys) -> shelleys @@ -878,27 +880,24 @@ protocolInfoCardano paramsCardano return $ byronBlockForging creds `OptNP.at` IZ blockForgingShelleyBased :: - ShelleyLeaderCredentials c + Tracer.Tracer m KESAgentClientTrace + -> ShelleyLeaderCredentials c -> m (NonEmptyOptNP (BlockForging m) (CardanoEras c)) - blockForgingShelleyBased credentials = do - let ShelleyLeaderCredentials - { shelleyLeaderCredentialsInitSignKey = initSignKey - , shelleyLeaderCredentialsCanBeLeader = canBeLeader - } = credentials - - hotKey <- do - let maxKESEvo :: Word64 - maxKESEvo = assert (tpraosMaxKESEvo == praosMaxKESEvo) praosMaxKESEvo - - startPeriod :: Absolute.KESPeriod - startPeriod = Absolute.ocertKESPeriod $ praosCanBeLeaderOpCert canBeLeader - - HotKey.mkHotKey @m @c initSignKey startPeriod maxKESEvo + blockForgingShelleyBased tr credentials = do + let canBeLeader = shelleyLeaderCredentialsCanBeLeader credentials let slotToPeriod :: SlotNo -> Absolute.KESPeriod slotToPeriod (SlotNo slot) = assert (tpraosSlotsPerKESPeriod == praosSlotsPerKESPeriod) $ Absolute.KESPeriod $ fromIntegral $ slot `div` praosSlotsPerKESPeriod + maxKESEvo :: Word64 + maxKESEvo = assert (tpraosMaxKESEvo == praosMaxKESEvo) praosMaxKESEvo + + hotKey <- instantiatePraosCredentials + maxKESEvo + tr + (praosCanBeLeaderCredentialsSource canBeLeader) + let tpraos :: forall era. ShelleyEraWithCrypto c (TPraos c) era => BlockForging m (ShelleyBlock (TPraos c) era) diff --git a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/HFEras.hs b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/HFEras.hs index ec7e09e1bb..06d77f1022 100644 --- a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/HFEras.hs +++ b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/HFEras.hs @@ -18,9 +18,10 @@ module Ouroboros.Consensus.Shelley.HFEras ( , StandardShelleyBlock ) where +import Cardano.Protocol.Crypto import Ouroboros.Consensus.Protocol.Praos (Praos) import qualified Ouroboros.Consensus.Protocol.Praos as Praos -import Ouroboros.Consensus.Protocol.TPraos (StandardCrypto, TPraos) +import Ouroboros.Consensus.Protocol.TPraos (TPraos) import qualified Ouroboros.Consensus.Protocol.TPraos as TPraos import Ouroboros.Consensus.Shelley.Eras (AllegraEra, AlonzoEra, BabbageEra, ConwayEra, MaryEra, ShelleyEra) @@ -75,7 +76,7 @@ instance ShelleyCompatible (TPraos c) BabbageEra instance - (Praos.PraosCrypto c) => ShelleyCompatible (Praos c) BabbageEra + Praos.PraosCrypto c => ShelleyCompatible (Praos c) BabbageEra -- This instance is required since the ledger view forecast function for -- Praos/Conway still goes through the forecast for TPraos. Once this is @@ -85,4 +86,4 @@ instance ShelleyCompatible (TPraos c) ConwayEra instance - (Praos.PraosCrypto c) => ShelleyCompatible (Praos c) ConwayEra + Praos.PraosCrypto c => ShelleyCompatible (Praos c) ConwayEra diff --git a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Common.hs b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Common.hs index 6982e3ce00..5987b31750 100644 --- a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Common.hs +++ b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Common.hs @@ -20,12 +20,10 @@ module Ouroboros.Consensus.Shelley.Node.Common ( , shelleyBlockIssuerVKey ) where -import Cardano.Crypto.KES (UnsoundPureSignKeyKES) import Cardano.Ledger.BaseTypes (unNonZero) import qualified Cardano.Ledger.Keys as SL import qualified Cardano.Ledger.Shelley.API as SL import Cardano.Ledger.Slot -import Cardano.Protocol.Crypto import Data.Text (Text) import Ouroboros.Consensus.Block (CannotForge, ForgeStateInfo, ForgeStateUpdateError) @@ -50,12 +48,7 @@ import Ouroboros.Consensus.Storage.ImmutableDB -------------------------------------------------------------------------------} data ShelleyLeaderCredentials c = ShelleyLeaderCredentials - { -- | The unevolved signing KES key (at evolution 0). - -- - -- Note that this is not inside 'ShelleyCanBeLeader' since it gets evolved - -- automatically, whereas 'ShelleyCanBeLeader' does not change. - shelleyLeaderCredentialsInitSignKey :: UnsoundPureSignKeyKES (KES c), - shelleyLeaderCredentialsCanBeLeader :: PraosCanBeLeader c, + { shelleyLeaderCredentialsCanBeLeader :: PraosCanBeLeader c, -- | Identifier for this set of credentials. -- -- Useful when the node is running with multiple sets of credentials. diff --git a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Praos.hs b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Praos.hs index 87599b91c7..23395e78d2 100644 --- a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Praos.hs +++ b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/Praos.hs @@ -27,8 +27,6 @@ import qualified Ouroboros.Consensus.Ledger.SupportsMempool as Mempool import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey import Ouroboros.Consensus.Protocol.Praos (Praos, PraosParams (..), praosCheckCanForge) -import Ouroboros.Consensus.Protocol.Praos.Common - (PraosCanBeLeader (praosCanBeLeaderOpCert)) import Ouroboros.Consensus.Shelley.Ledger (ShelleyBlock, ShelleyCompatible, forgeShelleyBlock) import Ouroboros.Consensus.Shelley.Node.Common (ShelleyEraWithCrypto, @@ -48,21 +46,13 @@ praosBlockForging :: , IOLike m ) => PraosParams + -> HotKey.HotKey c m -> ShelleyLeaderCredentials c - -> m (BlockForging m (ShelleyBlock (Praos c) era)) -praosBlockForging praosParams credentials = do - hotKey <- HotKey.mkHotKey @m @c initSignKey startPeriod praosMaxKESEvo - pure $ praosSharedBlockForging hotKey slotToPeriod credentials + -> BlockForging m (ShelleyBlock (Praos c) era) +praosBlockForging praosParams hotKey credentials = + praosSharedBlockForging hotKey slotToPeriod credentials where - PraosParams {praosMaxKESEvo, praosSlotsPerKESPeriod} = praosParams - - ShelleyLeaderCredentials { - shelleyLeaderCredentialsInitSignKey = initSignKey - , shelleyLeaderCredentialsCanBeLeader = canBeLeader - } = credentials - - startPeriod :: Absolute.KESPeriod - startPeriod = SL.ocertKESPeriod $ praosCanBeLeaderOpCert canBeLeader + PraosParams {praosSlotsPerKESPeriod} = praosParams slotToPeriod :: SlotNo -> Absolute.KESPeriod slotToPeriod (SlotNo slot) = @@ -87,7 +77,7 @@ praosSharedBlockForging ShelleyLeaderCredentials { shelleyLeaderCredentialsCanBeLeader = canBeLeader , shelleyLeaderCredentialsLabel = label - } = do + } = BlockForging { forgeLabel = label <> "_" <> T.pack (L.eraName @era), canBeLeader = canBeLeader, @@ -102,5 +92,6 @@ praosSharedBlockForging forgeShelleyBlock hotKey canBeLeader - cfg + cfg, + finalize = HotKey.finalize hotKey } diff --git a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/TPraos.hs b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/TPraos.hs index 5f3449c9a9..d7a75e9378 100644 --- a/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/TPraos.hs +++ b/ouroboros-consensus-cardano/src/shelley/Ouroboros/Consensus/Shelley/Node/TPraos.hs @@ -10,6 +10,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableSuperClasses #-} @@ -32,19 +33,23 @@ module Ouroboros.Consensus.Shelley.Node.TPraos ( ) where import Cardano.Crypto.Hash (Hash) +import qualified Cardano.Crypto.KES as KES import qualified Cardano.Crypto.VRF as VRF +import qualified Cardano.KESAgent.Serialization.DirectCodec as Agent import qualified Cardano.Ledger.Api.Era as L import qualified Cardano.Ledger.Api.Transition as L import Cardano.Ledger.Hashes (HASH) import qualified Cardano.Ledger.Shelley.API as SL -import Cardano.Protocol.Crypto (VRF) +import Cardano.Protocol.Crypto (KES, VRF) import qualified Cardano.Protocol.TPraos.API as SL import qualified Cardano.Protocol.TPraos.OCert as Absolute (KESPeriod (..)) import qualified Cardano.Protocol.TPraos.OCert as SL import Cardano.Slotting.EpochInfo import Cardano.Slotting.Time (mkSlotLength) import Control.Monad.Except (Except) +import qualified Control.Tracer as Tracer import Data.Bifunctor (first) +import qualified Data.SerDoc.Class as SerDoc import qualified Data.Text as T import qualified Data.Text as Text import Lens.Micro ((^.)) @@ -60,6 +65,7 @@ import Ouroboros.Consensus.Node.ProtocolInfo import Ouroboros.Consensus.Protocol.Abstract import Ouroboros.Consensus.Protocol.Ledger.HotKey (HotKey) import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey +import Ouroboros.Consensus.Protocol.Praos.AgentClient import Ouroboros.Consensus.Protocol.Praos.Common import Ouroboros.Consensus.Protocol.TPraos import Ouroboros.Consensus.Shelley.Eras @@ -89,21 +95,13 @@ shelleyBlockForging :: , IOLike m ) => TPraosParams + -> HotKey c m -> ShelleyLeaderCredentials c - -> m (BlockForging m (ShelleyBlock (TPraos c) era)) -shelleyBlockForging tpraosParams credentials = do - hotKey <- HotKey.mkHotKey @m @c initSignKey startPeriod tpraosMaxKESEvo - pure $ shelleySharedBlockForging hotKey slotToPeriod credentials + -> BlockForging m (ShelleyBlock (TPraos c) era) +shelleyBlockForging tpraosParams hotKey credentials = do + shelleySharedBlockForging hotKey slotToPeriod credentials where - TPraosParams {tpraosMaxKESEvo, tpraosSlotsPerKESPeriod} = tpraosParams - - ShelleyLeaderCredentials { - shelleyLeaderCredentialsInitSignKey = initSignKey - , shelleyLeaderCredentialsCanBeLeader = canBeLeader - } = credentials - - startPeriod :: Absolute.KESPeriod - startPeriod = SL.ocertKESPeriod $ praosCanBeLeaderOpCert canBeLeader + TPraosParams {tpraosSlotsPerKESPeriod} = tpraosParams slotToPeriod :: SlotNo -> Absolute.KESPeriod slotToPeriod (SlotNo slot) = @@ -139,6 +137,7 @@ shelleySharedBlockForging hotKey slotToPeriod credentials = hotKey canBeLeader cfg + , finalize = HotKey.finalize hotKey } where ShelleyLeaderCredentials { @@ -170,14 +169,18 @@ validateGenesis = first errsToString . SL.validateGenesis protocolInfoShelley :: forall m c. ( IOLike m + , AgentCrypto c , ShelleyCompatible (TPraos c) ShelleyEra , TxLimits (ShelleyBlock (TPraos c) ShelleyEra) + , MonadKESAgent m + , SerDoc.HasInfo (Agent.DirectCodec m) (KES.VerKeyKES (KES c)) + , SerDoc.HasInfo (Agent.DirectCodec m) (KES.SignKeyKES (KES c)) ) => SL.ShelleyGenesis -> ProtocolParamsShelleyBased c -> SL.ProtVer -> ( ProtocolInfo (ShelleyBlock (TPraos c) ShelleyEra) - , m [BlockForging m (ShelleyBlock (TPraos c) ShelleyEra)] + , Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (ShelleyBlock (TPraos c) ShelleyEra)] ) protocolInfoShelley shelleyGenesis protocolParamsShelleyBased @@ -189,16 +192,16 @@ protocolInfoShelley shelleyGenesis protocolInfoTPraosShelleyBased :: forall m era c. - ( IOLike m - , ShelleyCompatible (TPraos c) era + ( ShelleyCompatible (TPraos c) era , TxLimits (ShelleyBlock (TPraos c) era) + , KESAgentContext c m ) => ProtocolParamsShelleyBased c -> L.TransitionConfig era -> SL.ProtVer -- ^ see 'shelleyProtVer', mutatis mutandi -> ( ProtocolInfo (ShelleyBlock (TPraos c) era) - , m [BlockForging m (ShelleyBlock (TPraos c) era)] + , Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (ShelleyBlock (TPraos c) era)] ) protocolInfoTPraosShelleyBased ProtocolParamsShelleyBased { shelleyBasedInitialNonce = initialNonce @@ -211,11 +214,23 @@ protocolInfoTPraosShelleyBased ProtocolParamsShelleyBased { pInfoConfig = topLevelConfig , pInfoInitLedger = initExtLedgerState } - , traverse - (shelleyBlockForging tpraosParams) - credentialss + , \tr -> traverse (mkBlockForging tr) credentialss ) where + mkBlockForging :: Tracer.Tracer m KESAgentClientTrace + -> ShelleyLeaderCredentials c + -> m (BlockForging m (ShelleyBlock (TPraos c) era)) + mkBlockForging tr credentials = do + let canBeLeader = shelleyLeaderCredentialsCanBeLeader credentials + + hotKey :: HotKey c m <- + instantiatePraosCredentials + (tpraosMaxKESEvo tpraosParams) + tr + (praosCanBeLeaderCredentialsSource canBeLeader) + + return $ shelleyBlockForging tpraosParams hotKey credentials + genesis :: SL.ShelleyGenesis genesis = transitionCfg ^. L.tcShelleyGenesisL diff --git a/ouroboros-consensus-cardano/src/unstable-byron-testlib/Ouroboros/Consensus/ByronDual/Node.hs b/ouroboros-consensus-cardano/src/unstable-byron-testlib/Ouroboros/Consensus/ByronDual/Node.hs index 884d17b8e4..b5de1bc866 100644 --- a/ouroboros-consensus-cardano/src/unstable-byron-testlib/Ouroboros/Consensus/ByronDual/Node.hs +++ b/ouroboros-consensus-cardano/src/unstable-byron-testlib/Ouroboros/Consensus/ByronDual/Node.hs @@ -66,6 +66,7 @@ dualByronBlockForging creds = BlockForging { fmap castForgeStateUpdateInfo .: updateForgeState (dualTopLevelConfigMain cfg) , checkCanForge = checkCanForge . dualTopLevelConfigMain , forgeBlock = return .....: forgeDualByronBlock + , finalize = return () } where BlockForging {..} = byronBlockForging creds diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/Consensus/Cardano/ProtocolInfo.hs b/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/Consensus/Cardano/ProtocolInfo.hs index 4b2fcdbf98..fa893508a0 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/Consensus/Cardano/ProtocolInfo.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/Consensus/Cardano/ProtocolInfo.hs @@ -27,6 +27,7 @@ import qualified Cardano.Ledger.BaseTypes as SL import Cardano.Protocol.Crypto (StandardCrypto) import qualified Cardano.Protocol.TPraos.OCert as SL import qualified Cardano.Slotting.Time as Time +import qualified Control.Tracer as Tracer import Data.Proxy (Proxy (..)) import Data.SOP.Strict import Data.Word (Word64) @@ -46,10 +47,11 @@ import Ouroboros.Consensus.Node.ProtocolInfo (NumCoreNodes (..), import Ouroboros.Consensus.NodeId (CoreNodeId (..)) import Ouroboros.Consensus.Protocol.PBFT (PBftParams, PBftSignatureThreshold (..)) +import Ouroboros.Consensus.Protocol.Praos.AgentClient + (KESAgentClientTrace, KESAgentContext) import Ouroboros.Consensus.Shelley.Node (ProtocolParamsShelleyBased (..), ShelleyGenesis, ShelleyLeaderCredentials) -import Ouroboros.Consensus.Util.IOLike (IOLike) import qualified Test.Cardano.Ledger.Alonzo.Examples.Consensus as SL import qualified Test.Cardano.Ledger.Conway.Examples.Consensus as SL import qualified Test.ThreadNet.Infra.Byron as Byron @@ -143,7 +145,7 @@ hardForkInto Conway = -- mkSimpleTestProtocolInfo :: forall c - . (CardanoHardForkConstraints c) + . (CardanoHardForkConstraints c, KESAgentContext c IO) => Shelley.DecentralizationParam -- ^ Network decentralization parameter. -> SecurityParam @@ -214,7 +216,9 @@ mkSimpleTestProtocolInfo -- mkTestProtocolInfo :: forall m c - . (CardanoHardForkConstraints c, IOLike m) + . ( CardanoHardForkConstraints c + , KESAgentContext c m + ) => (CoreNodeId, Shelley.CoreNode c) -- ^ Id of the node for which the protocol info will be elaborated. -> ShelleyGenesis @@ -232,7 +236,7 @@ mkTestProtocolInfo :: -- that __might__ appear in the 'CardanoHardForkTriggers' parameter. -> CardanoHardForkTriggers -- ^ Specification of the era to which the initial state should hard-fork to. - -> (ProtocolInfo (CardanoBlock c), m [BlockForging m (CardanoBlock c)]) + -> (ProtocolInfo (CardanoBlock c), Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (CardanoBlock c)]) mkTestProtocolInfo (coreNodeId, coreNode) shelleyGenesis diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/ThreadNet/Infra/ShelleyBasedHardFork.hs b/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/ThreadNet/Infra/ShelleyBasedHardFork.hs index 6c6f044823..b751cdce1d 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/ThreadNet/Infra/ShelleyBasedHardFork.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/ThreadNet/Infra/ShelleyBasedHardFork.hs @@ -47,6 +47,7 @@ import qualified Cardano.Ledger.UMap as SL import Codec.CBOR.Decoding import Codec.CBOR.Encoding import Control.Monad.Except (runExcept) +import qualified Control.Tracer as Tracer import Data.Coerce import qualified Data.Map.Strict as Map import Data.MemPack @@ -62,7 +63,7 @@ import Data.Void (Void) import Lens.Micro ((^.)) import NoThunks.Class (NoThunks) import Ouroboros.Consensus.Block.Abstract (BlockProtocol) -import Ouroboros.Consensus.Block.Forging (BlockForging) +import Ouroboros.Consensus.Block.Forging (BlockForging, KESTracer) import Ouroboros.Consensus.Cardano.CanHardFork (crossEraForecastAcrossShelley, translateChainDepStateAcrossShelley) @@ -80,6 +81,8 @@ import Ouroboros.Consensus.Ledger.SupportsProtocol import Ouroboros.Consensus.Ledger.Tables.Utils import Ouroboros.Consensus.Node import Ouroboros.Consensus.Node.NetworkProtocolVersion +import Ouroboros.Consensus.Protocol.Praos.AgentClient + (KESAgentClientTrace, KESAgentContext) import Ouroboros.Consensus.Protocol.TPraos import Ouroboros.Consensus.Shelley.Eras import Ouroboros.Consensus.Shelley.Ledger @@ -89,7 +92,6 @@ import Ouroboros.Consensus.Storage.LedgerDB import Ouroboros.Consensus.TypeFamilyWrappers import Ouroboros.Consensus.Util (eitherToMaybe) import Ouroboros.Consensus.Util.IndexedMemPack -import Ouroboros.Consensus.Util.IOLike (IOLike) import Test.ThreadNet.TxGen import Test.ThreadNet.TxGen.Shelley () @@ -362,16 +364,20 @@ instance ShelleyBasedHardForkConstraints proto1 era1 proto2 era2 Protocol info -------------------------------------------------------------------------------} +type instance KESTracer (ShelleyBlock proto era) = KESAgentClientTrace + protocolInfoShelleyBasedHardFork :: forall m proto1 era1 proto2 era2. - (IOLike m, ShelleyBasedHardForkConstraints proto1 era1 proto2 era2) + ( KESAgentContext (ProtoCrypto proto2) m + , ShelleyBasedHardForkConstraints proto1 era1 proto2 era2 + ) => ProtocolParamsShelleyBased (ProtoCrypto proto1) -> SL.ProtVer -> SL.ProtVer -> L.TransitionConfig era2 -> TriggerHardFork -> ( ProtocolInfo (ShelleyBasedHardForkBlock proto1 era1 proto2 era2) - , m [BlockForging m (ShelleyBasedHardForkBlock proto1 era1 proto2 era2)] + , Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (ShelleyBasedHardForkBlock proto1 era1 proto2 era2)] ) protocolInfoShelleyBasedHardFork protocolParamsShelleyBased protVer1 @@ -403,7 +409,7 @@ protocolInfoShelleyBasedHardFork protocolParamsShelleyBased genesis = transCfg2 ^. L.tcShelleyGenesisL protocolInfo1 :: ProtocolInfo (ShelleyBlock proto1 era1) - blockForging1 :: m [BlockForging m (ShelleyBlock proto1 era1)] + blockForging1 :: Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (ShelleyBlock proto1 era1)] (protocolInfo1, blockForging1) = protocolInfoTPraosShelleyBased protocolParamsShelleyBased @@ -424,7 +430,7 @@ protocolInfoShelleyBasedHardFork protocolParamsShelleyBased -- Era 2 protocolInfo2 :: ProtocolInfo (ShelleyBlock proto2 era2) - blockForging2 :: m [BlockForging m (ShelleyBlock proto2 era2)] + blockForging2 :: Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (ShelleyBlock proto2 era2)] (protocolInfo2, blockForging2) = protocolInfoTPraosShelleyBased ProtocolParamsShelleyBased { diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Key.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Key.hs index 6322d3aa3d..559035aebc 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Key.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Key.hs @@ -9,7 +9,6 @@ module Cardano.Api.Key ( , CastSigningKeyRole (..) , CastVerificationKeyRole (..) , Key (..) - , generateSigningKey ) where import Cardano.Api.Any @@ -51,16 +50,17 @@ class (Eq (VerificationKey keyrole), verificationKeyHash :: VerificationKey keyrole -> Hash keyrole --- TODO: We should move this into the Key type class, with the existing impl as the default impl. --- For KES we can then override it to keep the seed and key in mlocked memory at all times. --- | Generate a 'SigningKey' using a seed from operating system entropy. --- -generateSigningKey :: Key keyrole => AsType keyrole -> IO (SigningKey keyrole) -generateSigningKey keytype = do - seed <- Crypto.readSeedFromSystemEntropy seedSize - return $! deterministicSigningKey keytype seed - where - seedSize = deterministicSigningKeySeedSize keytype + -- | Generate a 'SigningKey' using a seed from operating system entropy. + generateSigningKey :: AsType keyrole -> IO (SigningKey keyrole) + generateSigningKey keytype = do + -- + -- For KES we can override this to keep the seed and key in mlocked memory + -- at all times. + -- + seed <- Crypto.readSeedFromSystemEntropy seedSize + return $! deterministicSigningKey keytype seed + where + seedSize = deterministicSigningKeySeedSize keytype instance HasTypeProxy a => HasTypeProxy (VerificationKey a) where diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/KeysPraos.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/KeysPraos.hs index 815371f197..1ae9354668 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/KeysPraos.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/KeysPraos.hs @@ -14,7 +14,7 @@ -- module Cardano.Api.KeysPraos ( -- * Key types - KesKey + UnsoundPureKesKey , VrfKey -- * Data family instances , AsType (..) @@ -39,95 +39,94 @@ import Data.String (IsString (..)) -- KES keys -- -data KesKey +data UnsoundPureKesKey -instance HasTypeProxy KesKey where - data AsType KesKey = AsKesKey - proxyToAsType _ = AsKesKey +instance HasTypeProxy UnsoundPureKesKey where + data AsType UnsoundPureKesKey = AsUnsoundPureKesKey + proxyToAsType _ = AsUnsoundPureKesKey -instance Key KesKey where +instance Key UnsoundPureKesKey where - newtype VerificationKey KesKey = + newtype VerificationKey UnsoundPureKesKey = KesVerificationKey (Crypto.VerKeyKES (KES StandardCrypto)) deriving stock (Eq) - deriving (Show, IsString) via UsingRawBytesHex (VerificationKey KesKey) + deriving (Show, IsString) via UsingRawBytesHex (VerificationKey UnsoundPureKesKey) deriving newtype (EncCBOR, DecCBOR, ToCBOR, FromCBOR) deriving anyclass SerialiseAsCBOR - newtype SigningKey KesKey = + newtype SigningKey UnsoundPureKesKey = KesSigningKey (Crypto.UnsoundPureSignKeyKES (KES StandardCrypto)) - deriving (Show, IsString) via UsingRawBytesHex (SigningKey KesKey) + deriving (Show, IsString) via UsingRawBytesHex (SigningKey UnsoundPureKesKey) deriving newtype (ToCBOR, FromCBOR) deriving anyclass (EncCBOR, DecCBOR, SerialiseAsCBOR) --This loses the mlock safety of the seed, since it starts from a normal in-memory seed. - deterministicSigningKey :: AsType KesKey -> Crypto.Seed -> SigningKey KesKey - deterministicSigningKey AsKesKey = + deterministicSigningKey :: AsType UnsoundPureKesKey -> Crypto.Seed -> SigningKey UnsoundPureKesKey + deterministicSigningKey AsUnsoundPureKesKey = KesSigningKey . Crypto.unsoundPureGenKeyKES - deterministicSigningKeySeedSize :: AsType KesKey -> Word - deterministicSigningKeySeedSize AsKesKey = + deterministicSigningKeySeedSize :: AsType UnsoundPureKesKey -> Word + deterministicSigningKeySeedSize AsUnsoundPureKesKey = Crypto.seedSizeKES proxy where proxy :: Proxy (KES StandardCrypto) proxy = Proxy - getVerificationKey :: SigningKey KesKey -> VerificationKey KesKey + getVerificationKey :: SigningKey UnsoundPureKesKey -> VerificationKey UnsoundPureKesKey getVerificationKey (KesSigningKey sk) = KesVerificationKey (Crypto.unsoundPureDeriveVerKeyKES sk) - verificationKeyHash :: VerificationKey KesKey -> Hash KesKey + verificationKeyHash :: VerificationKey UnsoundPureKesKey -> Hash UnsoundPureKesKey verificationKeyHash (KesVerificationKey vkey) = - KesKeyHash (Crypto.hashVerKeyKES vkey) + UnsoundPureKesKeyHash (Crypto.hashVerKeyKES vkey) -instance SerialiseAsRawBytes (VerificationKey KesKey) where +instance SerialiseAsRawBytes (VerificationKey UnsoundPureKesKey) where serialiseToRawBytes (KesVerificationKey vk) = Crypto.rawSerialiseVerKeyKES vk - deserialiseFromRawBytes (AsVerificationKey AsKesKey) bs = + deserialiseFromRawBytes (AsVerificationKey AsUnsoundPureKesKey) bs = KesVerificationKey <$> Crypto.rawDeserialiseVerKeyKES bs -instance SerialiseAsRawBytes (SigningKey KesKey) where +instance SerialiseAsRawBytes (SigningKey UnsoundPureKesKey) where serialiseToRawBytes (KesSigningKey sk) = Crypto.rawSerialiseUnsoundPureSignKeyKES sk - deserialiseFromRawBytes (AsSigningKey AsKesKey) bs = + deserialiseFromRawBytes (AsSigningKey AsUnsoundPureKesKey) bs = KesSigningKey <$> Crypto.rawDeserialiseUnsoundPureSignKeyKES bs -instance SerialiseAsBech32 (VerificationKey KesKey) where +instance SerialiseAsBech32 (VerificationKey UnsoundPureKesKey) where bech32PrefixFor _ = "kes_vk" bech32PrefixesPermitted _ = ["kes_vk"] -instance SerialiseAsBech32 (SigningKey KesKey) where +instance SerialiseAsBech32 (SigningKey UnsoundPureKesKey) where bech32PrefixFor _ = "kes_sk" bech32PrefixesPermitted _ = ["kes_sk"] - -newtype instance Hash KesKey = - KesKeyHash (Crypto.Hash HASH +newtype instance Hash UnsoundPureKesKey = + UnsoundPureKesKeyHash (Crypto.Hash HASH (Crypto.VerKeyKES (KES StandardCrypto))) deriving stock (Eq, Ord) - deriving (Show, IsString) via UsingRawBytesHex (Hash KesKey) - deriving (ToCBOR, FromCBOR) via UsingRawBytes (Hash KesKey) + deriving (Show, IsString) via UsingRawBytesHex (Hash UnsoundPureKesKey) + deriving (ToCBOR, FromCBOR) via UsingRawBytes (Hash UnsoundPureKesKey) deriving anyclass SerialiseAsCBOR -instance SerialiseAsRawBytes (Hash KesKey) where - serialiseToRawBytes (KesKeyHash vkh) = +instance SerialiseAsRawBytes (Hash UnsoundPureKesKey) where + serialiseToRawBytes (UnsoundPureKesKeyHash vkh) = Crypto.hashToBytes vkh - deserialiseFromRawBytes (AsHash AsKesKey) bs = - KesKeyHash <$> Crypto.hashFromBytes bs + deserialiseFromRawBytes (AsHash AsUnsoundPureKesKey) bs = + UnsoundPureKesKeyHash <$> Crypto.hashFromBytes bs -instance HasTextEnvelope (VerificationKey KesKey) where +instance HasTextEnvelope (VerificationKey UnsoundPureKesKey) where textEnvelopeType _ = "KesVerificationKey_" <> fromString (Crypto.algorithmNameKES proxy) where proxy :: Proxy (KES StandardCrypto) proxy = Proxy -instance HasTextEnvelope (SigningKey KesKey) where +instance HasTextEnvelope (SigningKey UnsoundPureKesKey) where textEnvelopeType _ = "KesSigningKey_" <> fromString (Crypto.algorithmNameKES proxy) where @@ -227,4 +226,3 @@ instance HasTextEnvelope (SigningKey VrfKey) where where proxy :: Proxy (VRF StandardCrypto) proxy = Proxy - diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/OperationalCertificate.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/OperationalCertificate.hs index b8eb736501..514516591c 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/OperationalCertificate.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/OperationalCertificate.hs @@ -94,7 +94,7 @@ instance HasTypeProxy OperationalCertificateIssueCounter where instance HasTextEnvelope OperationalCertificate where textEnvelopeType _ = "NodeOperationalCertificate" -getHotKey :: OperationalCertificate -> VerificationKey KesKey +getHotKey :: OperationalCertificate -> VerificationKey UnsoundPureKesKey getHotKey (OperationalCertificate cert _) = KesVerificationKey $ Shelley.ocertVkHot cert getKesPeriod :: OperationalCertificate -> Word diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Protocol/Types.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Protocol/Types.hs index a1b804ebd4..7a63065f99 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Protocol/Types.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Api/Protocol/Types.hs @@ -4,8 +4,10 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UndecidableInstances #-} -- DUPLICATE -- adapted from: cardano-api/src/Cardano/Api/Protocol/Types.hs @@ -18,6 +20,7 @@ module Cardano.Api.Protocol.Types ( ) where import Cardano.Chain.Slotting (EpochSlots) +import qualified Control.Tracer as Tracer import Data.Bifunctor (bimap) import Ouroboros.Consensus.Block.Forging (BlockForging) import Ouroboros.Consensus.Byron.ByronHFC (ByronBlockHFC) @@ -30,6 +33,7 @@ import qualified Ouroboros.Consensus.Ledger.SupportsProtocol as Consensus import Ouroboros.Consensus.Node.ProtocolInfo (ProtocolClientInfo (..), ProtocolInfo (..)) import Ouroboros.Consensus.Node.Run (RunNode) +import Ouroboros.Consensus.Protocol.Praos.AgentClient import qualified Ouroboros.Consensus.Protocol.TPraos as Consensus import qualified Ouroboros.Consensus.Shelley.Eras as Consensus (ShelleyEra) import Ouroboros.Consensus.Shelley.HFEras () @@ -37,13 +41,12 @@ import qualified Ouroboros.Consensus.Shelley.Ledger.Block as Consensus (ShelleyBlock) import Ouroboros.Consensus.Shelley.Ledger.SupportsProtocol () import Ouroboros.Consensus.Shelley.ShelleyHFC (ShelleyBlockHFC) -import Ouroboros.Consensus.Util.IOLike (IOLike) - +import Ouroboros.Consensus.Util.IOLike class (RunNode blk, IOLike m) => Protocol m blk where data ProtocolInfoArgs m blk protocolInfo :: ProtocolInfoArgs m blk -> ( ProtocolInfo blk - , m [BlockForging m blk] + , Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m blk] ) -- | Node client support for each consensus protocol. @@ -60,10 +63,14 @@ class RunNode blk => ProtocolClient blk where instance IOLike m => Protocol m ByronBlockHFC where data ProtocolInfoArgs m ByronBlockHFC = ProtocolInfoArgsByron ProtocolParamsByron protocolInfo (ProtocolInfoArgsByron params) = ( inject $ protocolInfoByron params - , pure . map inject $ blockForgingByron params + , \_ -> pure . map inject $ blockForgingByron params ) -instance (CardanoHardForkConstraints StandardCrypto, IOLike m) => Protocol m (CardanoBlock StandardCrypto) where +instance ( CardanoHardForkConstraints StandardCrypto + , IOLike m + , MonadKESAgent m + ) + => Protocol m (CardanoBlock StandardCrypto) where data ProtocolInfoArgs m (CardanoBlock StandardCrypto) = ProtocolInfoArgsCardano (CardanoProtocolParams StandardCrypto) @@ -84,6 +91,7 @@ instance CardanoHardForkConstraints StandardCrypto => ProtocolClient (CardanoBlo protocolClientInfoCardano epochSlots instance ( IOLike m + , MonadKESAgent m , Consensus.LedgerSupportsProtocol (Consensus.ShelleyBlock (Consensus.TPraos StandardCrypto) ShelleyEra) @@ -94,7 +102,9 @@ instance ( IOLike m (ProtocolParamsShelleyBased StandardCrypto) ProtVer protocolInfo (ProtocolInfoArgsShelley genesis shelleyBasedProtocolParams' protVer) = - bimap inject (fmap $ map inject) $ protocolInfoShelley genesis shelleyBasedProtocolParams' protVer + bimap inject injectBlockForging $ protocolInfoShelley genesis shelleyBasedProtocolParams' protVer + where + injectBlockForging bf tr = fmap (map inject) $ bf tr instance Consensus.LedgerSupportsProtocol (Consensus.ShelleyBlock diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Node/Protocol/Shelley.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Node/Protocol/Shelley.hs index 38f87e5ca3..935e5bb311 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Node/Protocol/Shelley.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Node/Protocol/Shelley.hs @@ -45,7 +45,7 @@ import qualified Data.ByteString as BS import qualified Data.Text as T import qualified Ouroboros.Consensus.Cardano as Consensus import Ouroboros.Consensus.Protocol.Praos.Common - (PraosCanBeLeader (..)) + (PraosCanBeLeader (..), PraosCredentialsSource (..)) import Ouroboros.Consensus.Shelley.Node (Nonce (..), ProtocolParamsShelleyBased (..), ShelleyGenesis (..), ShelleyLeaderCredentials (..)) @@ -170,12 +170,12 @@ opCertKesKeyCheck :: -- ^ KES key -> FilePath -- ^ Operational certificate - -> ExceptT PraosLeaderCredentialsError IO (OperationalCertificate, SigningKey KesKey) + -> ExceptT PraosLeaderCredentialsError IO (OperationalCertificate, SigningKey UnsoundPureKesKey) opCertKesKeyCheck kesFile certFile = do opCert <- firstExceptT FileError (newExceptT $ readFileTextEnvelope AsOperationalCertificate certFile) kesSKey <- - firstExceptT FileError (newExceptT $ readFileTextEnvelope (AsSigningKey AsKesKey) kesFile) + firstExceptT FileError (newExceptT $ readFileTextEnvelope (AsSigningKey AsUnsoundPureKesKey) kesFile) let opCertSpecifiedKesKeyhash = verificationKeyHash $ getHotKey opCert suppliedKesKeyHash = verificationKeyHash $ getVerificationKey kesSKey -- Specified KES key in operational certificate should match the one @@ -200,11 +200,11 @@ readLeaderCredentialsBulk ProtocolFilepaths { shelleyBulkCredsFile = mfp } = parseShelleyCredentials :: ShelleyCredentials -> ExceptT PraosLeaderCredentialsError IO (ShelleyLeaderCredentials StandardCrypto) - parseShelleyCredentials ShelleyCredentials { scCert, scVrf, scKes } = do + parseShelleyCredentials ShelleyCredentials { scCert, scVrf, scKes } = mkPraosLeaderCredentials - <$> parseEnvelope AsOperationalCertificate scCert - <*> parseEnvelope (AsSigningKey AsVrfKey) scVrf - <*> parseEnvelope (AsSigningKey AsKesKey) scKes + <$> parseEnvelope AsOperationalCertificate scCert + <*> parseEnvelope (AsSigningKey AsVrfKey) scVrf + <*> parseEnvelope (AsSigningKey AsUnsoundPureKesKey) scKes readBulkFile :: Maybe FilePath @@ -228,7 +228,7 @@ readLeaderCredentialsBulk ProtocolFilepaths { shelleyBulkCredsFile = mfp } = mkPraosLeaderCredentials :: OperationalCertificate -> SigningKey VrfKey - -> SigningKey KesKey + -> SigningKey UnsoundPureKesKey -> ShelleyLeaderCredentials StandardCrypto mkPraosLeaderCredentials (OperationalCertificate opcert (StakePoolVerificationKey vkey)) @@ -237,11 +237,10 @@ mkPraosLeaderCredentials ShelleyLeaderCredentials { shelleyLeaderCredentialsCanBeLeader = PraosCanBeLeader { - praosCanBeLeaderOpCert = opcert, praosCanBeLeaderColdVerKey = coerceKeyRole vkey, - praosCanBeLeaderSignKeyVRF = vrfKey + praosCanBeLeaderSignKeyVRF = vrfKey, + praosCanBeLeaderCredentialsSource = PraosCredentialsUnsound opcert kesKey }, - shelleyLeaderCredentialsInitSignKey = kesKey, shelleyLeaderCredentialsLabel = "Shelley" } diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBSynthesizer/Run.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBSynthesizer/Run.hs index 1403723ed8..09b2a95ffa 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBSynthesizer/Run.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBSynthesizer/Run.hs @@ -23,6 +23,7 @@ import Data.Aeson as Aeson (FromJSON, Result (..), Value, import Data.Bool (bool) import Data.ByteString as BS (ByteString, readFile) import qualified Data.Set as Set +import qualified Ouroboros.Consensus.Block.Forging as BlockForging import Ouroboros.Consensus.Cardano.Block import Ouroboros.Consensus.Cardano.Node import Ouroboros.Consensus.Config (TopLevelConfig, configStorage) @@ -140,7 +141,7 @@ synthesize genTxs DBSynthesizerConfig{confOptions, confShelleyGenesis, confDbDir flavargs $ ChainDB.defaultArgs - forgers <- blockForging + (_, forgers) <- allocate registry (const $ mkForgers nullTracer) (mapM_ BlockForging.finalize) let fCount = length forgers putStrLn $ "--> forger count: " ++ show fCount if fCount > 0 @@ -169,9 +170,10 @@ synthesize genTxs DBSynthesizerConfig{confOptions, confShelleyGenesis, confDbDir { pInfoConfig , pInfoInitLedger } - , blockForging + , mkForgers ) = protocolInfoCardano runP + preOpenChainDB :: DBSynthesizerOpenMode -> FilePath -> IO () preOpenChainDB mode db = doesDirectoryExist db >>= bool create checkMode diff --git a/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/Consensus/Shelley/MockCrypto.hs b/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/Consensus/Shelley/MockCrypto.hs index b4f3ab5e4b..3ce4c29059 100644 --- a/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/Consensus/Shelley/MockCrypto.hs +++ b/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/Consensus/Shelley/MockCrypto.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -14,10 +15,14 @@ module Test.Consensus.Shelley.MockCrypto ( , MockCrypto ) where +import Cardano.Crypto.DSIGN (MockDSIGN) import Cardano.Crypto.KES (MockKES) import qualified Cardano.Crypto.KES as KES (Signable) import Cardano.Crypto.Util (SignableRepresentation) import Cardano.Crypto.VRF (MockVRF) +import qualified Cardano.KESAgent.KES.Crypto as Agent +import qualified Cardano.KESAgent.Processes.ServiceClient as Agent +import qualified Cardano.KESAgent.Protocols.VersionedProtocol as Agent import Cardano.Ledger.BaseTypes (Seed) import qualified Cardano.Ledger.Shelley.API as SL import qualified Cardano.Ledger.Shelley.Core as Core @@ -29,6 +34,8 @@ import Control.State.Transition.Extended (PredicateFailure) import Ouroboros.Consensus.Ledger.SupportsProtocol (LedgerSupportsProtocol) import qualified Ouroboros.Consensus.Protocol.Praos as Praos +import Ouroboros.Consensus.Protocol.Praos.AgentClient + (AgentCrypto (..)) import Ouroboros.Consensus.Protocol.TPraos (TPraos) import Ouroboros.Consensus.Shelley.Eras (ShelleyEra) import Ouroboros.Consensus.Shelley.Ledger (ShelleyBlock, @@ -79,3 +86,16 @@ type CanMock proto era = , Arbitrary (Core.GovState era) , Arbitrary (SL.CertState era) ) + +instance Agent.NamedCrypto MockCrypto where + cryptoName _ = Agent.CryptoName "Mock" + +instance Agent.ServiceClientDrivers MockCrypto where + availableServiceClientDrivers = [] + +instance Agent.Crypto MockCrypto where + type KES MockCrypto = MockKES 10 + type DSIGN MockCrypto = MockDSIGN + +instance AgentCrypto MockCrypto where + type ACrypto MockCrypto = MockCrypto diff --git a/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/ThreadNet/Infra/Shelley.hs b/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/ThreadNet/Infra/Shelley.hs index 13f16e693a..8756f45492 100644 --- a/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/ThreadNet/Infra/Shelley.hs +++ b/ouroboros-consensus-cardano/src/unstable-shelley-testlib/Test/ThreadNet/Infra/Shelley.hs @@ -38,7 +38,8 @@ module Test.ThreadNet.Infra.Shelley ( import Cardano.Crypto.DSIGN (DSIGNAlgorithm (..), SignKeyDSIGN, seedSizeDSIGN) -import Cardano.Crypto.KES (KESAlgorithm (..), UnsoundPureSignKeyKES, +import Cardano.Crypto.KES (KESAlgorithm (..), + UnsoundPureKESAlgorithm (..), UnsoundPureSignKeyKES, seedSizeKES, unsoundPureDeriveVerKeyKES, unsoundPureGenKeyKES) import Cardano.Crypto.Seed (mkSeedFromBytes) @@ -60,6 +61,7 @@ import Cardano.Protocol.TPraos.OCert import qualified Cardano.Protocol.TPraos.OCert as SL (KESPeriod, OCert (OCert), OCertSignable (..)) import Control.Monad.Except (throwError) +import qualified Control.Tracer as Tracer import qualified Data.ByteString as BS import Data.Coerce (coerce) import Data.ListMap (ListMap (ListMap)) @@ -78,9 +80,12 @@ import Ouroboros.Consensus.Block import Ouroboros.Consensus.BlockchainTime import Ouroboros.Consensus.Config.SecurityParam import Ouroboros.Consensus.Node.ProtocolInfo +import Ouroboros.Consensus.Protocol.Praos.AgentClient + (KESAgentClientTrace, KESAgentContext) import Ouroboros.Consensus.Protocol.Praos.Common (PraosCanBeLeader (PraosCanBeLeader), - praosCanBeLeaderColdVerKey, praosCanBeLeaderOpCert, + PraosCredentialsSource (..), praosCanBeLeaderColdVerKey, + praosCanBeLeaderCredentialsSource, praosCanBeLeaderSignKeyVRF) import Ouroboros.Consensus.Protocol.TPraos import Ouroboros.Consensus.Shelley.Eras (ShelleyEra) @@ -90,7 +95,6 @@ import Ouroboros.Consensus.Shelley.Ledger (GenTx (..), import Ouroboros.Consensus.Shelley.Node import Ouroboros.Consensus.Shelley.Protocol.Abstract (ProtoCrypto) import Ouroboros.Consensus.Util.Assert -import Ouroboros.Consensus.Util.IOLike import Quiet (Quiet (..)) import qualified Test.Cardano.Ledger.Core.KeyPair as TL (KeyPair (..), mkWitnessesVKey) @@ -183,8 +187,8 @@ genCoreNode startKESPeriod = do genKey <- genKeyDSIGN <$> genSeed (seedSizeDSIGN (Proxy @LK.DSIGN)) delKey <- genKeyDSIGN <$> genSeed (seedSizeDSIGN (Proxy @LK.DSIGN)) stkKey <- genKeyDSIGN <$> genSeed (seedSizeDSIGN (Proxy @LK.DSIGN)) - vrfKey <- genKeyVRF <$> genSeed (seedSizeVRF (Proxy @(VRF c))) - kesKey <- unsoundPureGenKeyKES <$> genSeed (seedSizeKES (Proxy @(KES c))) + vrfKey <- genKeyVRF <$> genSeed (seedSizeVRF (Proxy @(VRF c))) + kesKey <- unsoundPureGenKeyKES <$> genSeed (seedSizeKES (Proxy @(KES c))) let kesPub = unsoundPureDeriveVerKeyKES kesKey sigma = LK.signedDSIGN delKey @@ -215,9 +219,8 @@ genCoreNode startKESPeriod = do mkLeaderCredentials :: CoreNode c -> ShelleyLeaderCredentials c mkLeaderCredentials CoreNode { cnDelegateKey, cnVRF, cnKES, cnOCert } = ShelleyLeaderCredentials { - shelleyLeaderCredentialsInitSignKey = cnKES - , shelleyLeaderCredentialsCanBeLeader = PraosCanBeLeader { - praosCanBeLeaderOpCert = cnOCert + shelleyLeaderCredentialsCanBeLeader = PraosCanBeLeader { + praosCanBeLeaderCredentialsSource = PraosCredentialsUnsound cnOCert cnKES , praosCanBeLeaderColdVerKey = SL.VKey $ deriveVerKeyDSIGN cnDelegateKey , praosCanBeLeaderSignKeyVRF = cnVRF } @@ -408,13 +411,15 @@ mkGenesisConfig pVer k f d maxLovelaceSupply slotLength kesCfg coreNodes = mkProtocolShelley :: forall m c. - (IOLike m, ShelleyCompatible (TPraos c) ShelleyEra) + ( KESAgentContext c m + , ShelleyCompatible (TPraos c) ShelleyEra + ) => ShelleyGenesis -> SL.Nonce -> ProtVer -> CoreNode c -> ( ProtocolInfo (ShelleyBlock (TPraos c) ShelleyEra) - , m [BlockForging m (ShelleyBlock (TPraos c) ShelleyEra)] + , Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (ShelleyBlock (TPraos c) ShelleyEra)] ) mkProtocolShelley genesis initialNonce protVer coreNode = protocolInfoShelley @@ -424,6 +429,7 @@ mkProtocolShelley genesis initialNonce protVer coreNode = , shelleyBasedLeaderCredentials = [mkLeaderCredentials coreNode] } protVer + {------------------------------------------------------------------------------- Necessary transactions for updating the 'DecentralizationParam' -------------------------------------------------------------------------------} diff --git a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/AllegraMary.hs b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/AllegraMary.hs index 84625dc6c8..72e20ee50b 100644 --- a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/AllegraMary.hs +++ b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/AllegraMary.hs @@ -21,6 +21,7 @@ import qualified Cardano.Ledger.Shelley.Core as SL import qualified Cardano.Protocol.TPraos.OCert as SL import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..)) import Control.Monad (replicateM) +import Control.Tracer (nullTracer) import qualified Data.Map.Strict as Map import Data.Maybe (maybeToList) import Data.Proxy (Proxy (..)) @@ -252,7 +253,7 @@ prop_simple_allegraMary_convergence TestSetup (SlotNo $ unNumSlots numSlots) -- never expire setupD -- unchanged , tniProtocolInfo = protocolInfo - , tniBlockForging = blockForging + , tniBlockForging = blockForging nullTracer } , mkRekeyM = Nothing } diff --git a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/Cardano.hs b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/Cardano.hs index c2a6b5db3a..0b4484aa8b 100644 --- a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/Cardano.hs +++ b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/Cardano.hs @@ -26,6 +26,7 @@ import qualified Cardano.Protocol.TPraos.OCert as SL import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..)) import Control.Exception (assert) import Control.Monad (replicateM) +import qualified Control.Tracer as Tracer import qualified Data.Map.Strict as Map import Data.Maybe (maybeToList) import Data.Proxy (Proxy (..)) @@ -41,6 +42,7 @@ import Ouroboros.Consensus.Byron.Ledger.Block (ByronBlock) import Ouroboros.Consensus.Byron.Ledger.Conversions import Ouroboros.Consensus.Byron.Node import Ouroboros.Consensus.Cardano.Block +import Ouroboros.Consensus.Cardano.CanHardFork import Ouroboros.Consensus.Cardano.Condense () import Ouroboros.Consensus.Config.SecurityParam import Ouroboros.Consensus.HardFork.Combinator @@ -53,10 +55,11 @@ import Ouroboros.Consensus.Node.NetworkProtocolVersion import Ouroboros.Consensus.Node.ProtocolInfo import Ouroboros.Consensus.NodeId import Ouroboros.Consensus.Protocol.PBFT +import Ouroboros.Consensus.Protocol.Praos.AgentClient + (KESAgentClientTrace, KESAgentContext) import Ouroboros.Consensus.Shelley.HFEras () import Ouroboros.Consensus.Shelley.Ledger.SupportsProtocol () import Ouroboros.Consensus.Shelley.Node -import Ouroboros.Consensus.Util.IOLike (IOLike) import Test.Consensus.Cardano.ProtocolInfo (hardForkOnDefaultProtocolVersions, mkTestProtocolInfo) import Test.QuickCheck @@ -437,7 +440,7 @@ prop_simple_cardano_convergence TestSetup property $ unNonZero (maxRollbacks setupK) >= finalIntersectionDepth mkProtocolCardanoAndHardForkTxs :: - forall c m. (IOLike m, c ~ StandardCrypto) + forall c m. (CardanoHardForkConstraints c, KESAgentContext c m) -- Byron => PBftParams -> CoreNodeId @@ -456,7 +459,7 @@ mkProtocolCardanoAndHardForkTxs TestNodeInitialization { tniCrucialTxs = crucialTxs , tniProtocolInfo = protocolInfo - , tniBlockForging = blockForging + , tniBlockForging = blockForging Tracer.nullTracer } where crucialTxs :: [GenTx (CardanoBlock c)] @@ -475,7 +478,7 @@ mkProtocolCardanoAndHardForkTxs propPV protocolInfo :: ProtocolInfo (CardanoBlock c) - blockForging :: m [BlockForging m (CardanoBlock c)] + blockForging :: Tracer.Tracer m KESAgentClientTrace -> m [BlockForging m (CardanoBlock c)] (setByronProtVer -> protocolInfo, blockForging) = mkTestProtocolInfo (coreNodeId, coreNodeShelley) diff --git a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/MaryAlonzo.hs b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/MaryAlonzo.hs index ac4116f72f..bb4d05d09c 100644 --- a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/MaryAlonzo.hs +++ b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/MaryAlonzo.hs @@ -22,6 +22,7 @@ import qualified Cardano.Ledger.Shelley.Core as SL import qualified Cardano.Protocol.TPraos.OCert as SL import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..)) import Control.Monad (replicateM) +import Control.Tracer (nullTracer) import qualified Data.Map.Strict as Map import Data.Maybe (maybeToList) import Data.Proxy (Proxy (..)) @@ -259,7 +260,7 @@ prop_simple_allegraAlonzo_convergence TestSetup (SlotNo $ unNumSlots numSlots) -- never expire setupD -- unchanged , tniProtocolInfo = protocolInfo - , tniBlockForging = blockForging + , tniBlockForging = blockForging nullTracer } , mkRekeyM = Nothing } diff --git a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/ShelleyAllegra.hs b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/ShelleyAllegra.hs index cadeac97ca..c3c80b3175 100644 --- a/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/ShelleyAllegra.hs +++ b/ouroboros-consensus-cardano/test/cardano-test/Test/ThreadNet/ShelleyAllegra.hs @@ -21,6 +21,7 @@ import qualified Cardano.Ledger.Shelley.Core as SL import qualified Cardano.Protocol.TPraos.OCert as SL import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..)) import Control.Monad (replicateM) +import Control.Tracer (nullTracer) import qualified Data.Map.Strict as Map import Data.Maybe (maybeToList) import Data.Proxy (Proxy (..)) @@ -263,7 +264,7 @@ prop_simple_shelleyAllegra_convergence TestSetup (SlotNo $ unNumSlots numSlots) -- never expire setupD -- unchanged , tniProtocolInfo = protocolInfo - , tniBlockForging = blockForging + , tniBlockForging = blockForging nullTracer } , mkRekeyM = Nothing } diff --git a/ouroboros-consensus-cardano/test/shelley-test/Test/ThreadNet/Shelley.hs b/ouroboros-consensus-cardano/test/shelley-test/Test/ThreadNet/Shelley.hs index 5296d90cc8..c7bc51d3c0 100644 --- a/ouroboros-consensus-cardano/test/shelley-test/Test/ThreadNet/Shelley.hs +++ b/ouroboros-consensus-cardano/test/shelley-test/Test/ThreadNet/Shelley.hs @@ -16,6 +16,7 @@ import qualified Cardano.Ledger.Shelley.Translation as SL import qualified Cardano.Protocol.TPraos.OCert as SL import Cardano.Slotting.EpochInfo (fixedEpochInfo) import Control.Monad (replicateM) +import Control.Tracer (nullTracer) import qualified Data.Map.Strict as Map import Data.Word (Word64) import Lens.Micro ((^.)) @@ -262,7 +263,7 @@ prop_simple_real_tpraos_convergence TestSetup nextProtVer sentinel -- Does not expire during test setupD2 - , tniBlockForging = blockForging + , tniBlockForging = blockForging nullTracer } , mkRekeyM = Nothing } diff --git a/ouroboros-consensus-diffusion/changelog.d/20250130_100651_tdammers_mlocked_kes_rebase.md b/ouroboros-consensus-diffusion/changelog.d/20250130_100651_tdammers_mlocked_kes_rebase.md new file mode 100644 index 0000000000..44c5f98e69 --- /dev/null +++ b/ouroboros-consensus-diffusion/changelog.d/20250130_100651_tdammers_mlocked_kes_rebase.md @@ -0,0 +1,3 @@ +### Non-Breaking + +- Ensure that block forging threads finalize their keys when shutting down. diff --git a/ouroboros-consensus-diffusion/ouroboros-consensus-diffusion.cabal b/ouroboros-consensus-diffusion/ouroboros-consensus-diffusion.cabal index 9b0d029512..f439ee53b7 100644 --- a/ouroboros-consensus-diffusion/ouroboros-consensus-diffusion.cabal +++ b/ouroboros-consensus-diffusion/ouroboros-consensus-diffusion.cabal @@ -91,6 +91,7 @@ library mtl, network-mux ^>=0.7, ouroboros-consensus ^>=0.26, + ouroboros-consensus-protocol ^>=0.12, ouroboros-network ^>=0.20.1, ouroboros-network-api ^>=0.13, ouroboros-network-framework ^>=0.17, @@ -145,6 +146,7 @@ library unstable-diffusion-testlib mtl, ouroboros-consensus:{ouroboros-consensus, unstable-consensus-testlib}, ouroboros-consensus-diffusion, + ouroboros-consensus-protocol, ouroboros-network, ouroboros-network-api, ouroboros-network-framework, diff --git a/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/Node/Tracers.hs b/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/Node/Tracers.hs index 9b5a6eb8db..ba68c88610 100644 --- a/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/Node/Tracers.hs +++ b/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/Node/Tracers.hs @@ -38,6 +38,8 @@ import Ouroboros.Consensus.MiniProtocol.ChainSync.Server import Ouroboros.Consensus.MiniProtocol.LocalTxSubmission.Server (TraceLocalTxSubmissionServerEvent (..)) import Ouroboros.Consensus.Node.GSM (TraceGsmEvent) +import Ouroboros.Consensus.Protocol.Praos.AgentClient + (KESAgentClientTrace (..)) import Ouroboros.Network.Block (Tip) import Ouroboros.Network.BlockFetch (TraceFetchClientState, TraceLabelPeer) @@ -74,6 +76,7 @@ data Tracers' remotePeer localPeer blk f = Tracers , gddTracer :: f (TraceGDDEvent remotePeer blk) , csjTracer :: f (TraceLabelPeer remotePeer (CSJumping.TraceEventCsj remotePeer blk)) , dbfTracer :: f (CSJumping.TraceEventDbf remotePeer) + , kesAgentTracer :: f KESAgentClientTrace } instance (forall a. Semigroup (f a)) @@ -99,6 +102,7 @@ instance (forall a. Semigroup (f a)) , gddTracer = f gddTracer , csjTracer = f csjTracer , dbfTracer = f dbfTracer + , kesAgentTracer = f kesAgentTracer } where f :: forall a. Semigroup a @@ -132,6 +136,7 @@ nullTracers = Tracers , gddTracer = nullTracer , csjTracer = nullTracer , dbfTracer = nullTracer + , kesAgentTracer = nullTracer } showTracers :: ( Show blk @@ -168,6 +173,7 @@ showTracers tr = Tracers , gddTracer = showTracing tr , csjTracer = showTracing tr , dbfTracer = showTracing tr + , kesAgentTracer = showTracing tr } {------------------------------------------------------------------------------- diff --git a/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/NodeKernel.hs b/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/NodeKernel.hs index b5691e391a..7cc941cc01 100644 --- a/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/NodeKernel.hs +++ b/ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/NodeKernel.hs @@ -455,9 +455,11 @@ forkBlockForging :: -> BlockForging m blk -> m (Thread m Void) forkBlockForging IS{..} blockForging = - forkLinkedWatcher registry threadLabel - $ knownSlotWatcher btime - $ \currentSlot -> withRegistry (\rr -> withEarlyExit_ $ go rr currentSlot) + forkLinkedWatcherFinalize registry threadLabel + (knownSlotWatcher btime + $ \currentSlot -> withRegistry (\rr -> withEarlyExit_ $ go rr currentSlot) + ) + (finalize blockForging) where threadLabel :: String threadLabel = diff --git a/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/General.hs b/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/General.hs index 348d183e31..5cbe8621a8 100644 --- a/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/General.hs +++ b/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/General.hs @@ -52,6 +52,7 @@ import Ouroboros.Consensus.Node.Run import Ouroboros.Consensus.NodeId import Ouroboros.Consensus.Protocol.Abstract (LedgerView) import Ouroboros.Consensus.Protocol.LeaderSchedule +import Ouroboros.Consensus.Protocol.Praos.AgentClient (MonadKESAgent) import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB import Ouroboros.Consensus.TypeFamilyWrappers import Ouroboros.Consensus.Util.Condense @@ -206,7 +207,7 @@ runTestNetwork :: ) => TestConfig -> TestConfigB blk - -> (forall m. IOLike m => TestConfigMB m blk) + -> (forall m. (IOLike m, MonadKESAgent m) => TestConfigMB m blk) -> TestOutput blk runTestNetwork TestConfig { numCoreNodes diff --git a/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/Network.hs b/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/Network.hs index 0ef7e84bba..61eae9c55a 100644 --- a/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/Network.hs +++ b/ouroboros-consensus-diffusion/src/unstable-diffusion-testlib/Test/ThreadNet/Network.hs @@ -64,6 +64,7 @@ import Network.TypedProtocol.Codec (AnyMessage (..), CodecFailure, mapFailureCodec) import qualified Network.TypedProtocol.Codec as Codec import Ouroboros.Consensus.Block +import qualified Ouroboros.Consensus.Block.Forging as BlockForging import Ouroboros.Consensus.BlockchainTime import Ouroboros.Consensus.Config import Ouroboros.Consensus.Ledger.Abstract @@ -796,7 +797,7 @@ runThreadNetwork systemTime ThreadNetworkArgs -> m ( NodeKernel m NodeId Void blk , LimitedApp m NodeId blk ) - forkNode coreNodeId clock joinSlot registry pInfo blockForging nodeInfo txs0 = do + forkNode coreNodeId clock joinSlot registry pInfo mkBlockForging nodeInfo txs0 = do let ProtocolInfo{..} = pInfo let NodeInfo @@ -1037,9 +1038,9 @@ runThreadNetwork systemTime ThreadNetworkArgs nodeKernel <- initNodeKernel nodeKernelArgs - blockForging' <- - map (\bf -> bf { forgeBlock = customForgeBlock bf }) - <$> blockForging + (_, blockForging) <- allocate registry (const mkBlockForging) (mapM_ BlockForging.finalize) + let blockForging' = + map (\bf -> bf { forgeBlock = customForgeBlock bf }) blockForging setBlockForging nodeKernel blockForging' let mempool = getMempool nodeKernel diff --git a/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/A.hs b/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/A.hs index 91a0bd4f64..feadc263d7 100644 --- a/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/A.hs +++ b/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/A.hs @@ -333,6 +333,7 @@ blockForgingA = BlockForging { , checkCanForge = \_ _ _ _ _ -> return () , forgeBlock = \cfg bno slot st txs proof -> return $ forgeBlockA cfg bno slot st (fmap txForgetValidated txs) proof + , finalize = return () } -- | See 'Ouroboros.Consensus.HardFork.History.EraParams.safeFromTip' diff --git a/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/B.hs b/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/B.hs index 0e43f6fbf8..1e44e90e88 100644 --- a/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/B.hs +++ b/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/HardFork/Combinator/B.hs @@ -277,6 +277,7 @@ blockForgingB = BlockForging { , checkCanForge = \_ _ _ _ _ -> return () , forgeBlock = \cfg bno slot st txs proof -> return $ forgeBlockB cfg bno slot st (fmap txForgetValidated txs) proof + , finalize = return () } -- | A basic 'History.SafeZone' diff --git a/ouroboros-consensus-diffusion/test/mock-test/Test/ThreadNet/Praos.hs b/ouroboros-consensus-diffusion/test/mock-test/Test/ThreadNet/Praos.hs index eec86b15ff..b0835373b2 100644 --- a/ouroboros-consensus-diffusion/test/mock-test/Test/ThreadNet/Praos.hs +++ b/ouroboros-consensus-diffusion/test/mock-test/Test/ThreadNet/Praos.hs @@ -50,7 +50,7 @@ data TestSetup = TestSetup genEvolvingStake :: EpochSize -> TestConfig -> Gen PraosEvolvingStake genEvolvingStake epochSize TestConfig {numSlots, numCoreNodes} = do - chosenEpochs <- sublistOf [0..EpochNo $ max 1 maxEpochs - 1] + chosenEpochs <- sublistOf [EpochNo 0..EpochNo $ max 1 maxEpochs - 1] let l = fromIntegral maxEpochs stakeDists <- replicateM l genStakeDist return . PraosEvolvingStake . Map.fromList $ zip chosenEpochs stakeDists diff --git a/ouroboros-consensus-protocol/changelog.d/20250130_101128_tdammers_mlocked_kes_rebase.md b/ouroboros-consensus-protocol/changelog.d/20250130_101128_tdammers_mlocked_kes_rebase.md new file mode 100644 index 0000000000..804067168e --- /dev/null +++ b/ouroboros-consensus-protocol/changelog.d/20250130_101128_tdammers_mlocked_kes_rebase.md @@ -0,0 +1,18 @@ +### Breaking + +- Use new mlocked KES API for all internal KES sign key handling. +- Add finalizers to all block forgings (required by `ouroboros-consensus`). +- Change `HotKey` to manage not only KES sign keys, but also the corresponding + OpCerts. This is in preparation for KES agent connectivity: with the new + design, the KES agent will provide both KES sign keys and matching OpCerts + together, and we need to be able to dynamically replace them both together. +- Add finalizer to `HotKey`. This takes care of securely forgetting any KES + keys the HotKey may still hold, and will be called automatically when the + owning block forging terminates. +- Change `PraosCanBeLeader` to not contain the KES sign key itself anymore. + Instead, it now contains a `PraosCredentialsSource` field, which + specifies how to obtain the actual credentials (OpCert and KES SignKey). For + now, the only supported method is passing an OpCert and an + UnsoundPureSignKeyKES, presumably loaded from disk + (`PraosCredentialsUnsound`); future iterations will add support for + connecting to a KES agent. diff --git a/ouroboros-consensus-protocol/ouroboros-consensus-protocol.cabal b/ouroboros-consensus-protocol/ouroboros-consensus-protocol.cabal index 54c550fb53..5c783b6d98 100644 --- a/ouroboros-consensus-protocol/ouroboros-consensus-protocol.cabal +++ b/ouroboros-consensus-protocol/ouroboros-consensus-protocol.cabal @@ -56,6 +56,7 @@ library Ouroboros.Consensus.Protocol.Ledger.HotKey Ouroboros.Consensus.Protocol.Ledger.Util Ouroboros.Consensus.Protocol.Praos + Ouroboros.Consensus.Protocol.Praos.AgentClient Ouroboros.Consensus.Protocol.Praos.Common Ouroboros.Consensus.Protocol.Praos.Header Ouroboros.Consensus.Protocol.Praos.VRF @@ -63,6 +64,7 @@ library Ouroboros.Consensus.Protocol.TPraos build-depends: + Win32-network ^>=0.2, base >=4.14 && <4.22, bytestring, cardano-binary, @@ -74,9 +76,17 @@ library cardano-slotting, cborg, containers, + contra-tracer ^>=0.1.0, + io-classes ^>=1.5.0, + io-sim, + kes-agent, mtl, + network ^>=3.2.7, nothunks, ouroboros-consensus >=0.23 && <0.27, + ouroboros-network-framework ^>=0.17, + ouroboros-network-testing ^>=0.8, + serdoc-core, serialise, text, @@ -120,7 +130,7 @@ test-suite protocol-test cardano-crypto-class ^>=2.2, cardano-ledger-binary:testlib, cardano-ledger-core ^>=1.17, - cardano-protocol-tpraos ^>=1.4, + cardano-protocol-tpraos, containers, ouroboros-consensus:{ouroboros-consensus, unstable-consensus-testlib}, ouroboros-consensus-protocol, diff --git a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Ledger/HotKey.hs b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Ledger/HotKey.hs index c729d20944..8f277d066a 100644 --- a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Ledger/HotKey.hs +++ b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Ledger/HotKey.hs @@ -1,9 +1,16 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} -- | Hot key -- @@ -20,7 +27,12 @@ module Ouroboros.Consensus.Protocol.Ledger.HotKey ( , HotKey (..) , KESEvolutionError (..) , KESEvolutionInfo + , finalize + , getOCert + , mkDynamicHotKey + , mkEmptyHotKey , mkHotKey + , mkHotKeyAtEvolution , sign ) where @@ -28,9 +40,12 @@ import qualified Cardano.Crypto.KES as KES import qualified Cardano.Crypto.KES as Relative (Period) import Cardano.Protocol.Crypto (Crypto (..)) import qualified Cardano.Protocol.TPraos.OCert as Absolute (KESPeriod (..)) +import qualified Cardano.Protocol.TPraos.OCert as OCert +import Control.Monad (forM_) import Data.Word (Word64) import GHC.Generics (Generic) import GHC.Stack (HasCallStack) +import NoThunks.Class (OnlyCheckWhnfNamed (..)) import Ouroboros.Consensus.Block.Forging (UpdateInfo (..)) import Ouroboros.Consensus.Util.IOLike @@ -126,19 +141,52 @@ data HotKey c m = HotKey { -- -- When the key cannot evolve anymore, we poison it. evolve :: Absolute.KESPeriod -> m KESEvolutionInfo + -- | Return 'KESInfo' of the signing key. , getInfo :: m KESInfo - -- | Return 'True' when the signing key is poisoned because it expired. + + -- | Return the 'OCert' corresponding to the KES signing key, if any. + , getOCertMaybe :: m (Maybe (OCert.OCert c)) + + -- | Check whether a valid KES signing key exists. "Poisoned" means no + -- key exists; reasons for this could be: + -- - no signing key has been set yet + -- - the signing key has been explicitly erased ('forget') + -- - the signing key has been evolved past the end of the available + -- evolutions , isPoisoned :: m Bool + -- | Sign the given @toSign@ with the current signing key. -- -- PRECONDITION: the key is not poisoned. -- -- POSTCONDITION: the signature is in normal form. , sign_ :: forall toSign. (KES.Signable (KES c) toSign, HasCallStack) - => toSign -> m (KES.SignedKES (KES c) toSign) + => toSign + -> m (KES.SignedKES (KES c) toSign) + + -- | Securely erase the key and release its memory. + , forget :: m () + + -- | Release any resources held by the 'HotKey', except for the signing + -- key itself. User code should use 'finalize' instead. + , finalize_ :: m () } +-- | Release all resources held by the 'HotKey', including the signing key +-- itself. Use this exactly once per 'HotKey' instance. +finalize :: Monad m => HotKey c m -> m () +finalize hotKey = forget hotKey >> finalize_ hotKey + +deriving via (OnlyCheckWhnfNamed "HotKey" (HotKey c m)) instance NoThunks (HotKey c m) + +getOCert :: Monad m => HotKey c m -> m (OCert.OCert c) +getOCert hotKey = do + ocertMay <- getOCertMaybe hotKey + case ocertMay of + Just ocert -> return ocert + Nothing -> error "trying to read OpCert for poisoned key" + sign :: (KES.Signable (KES c) toSign, HasCallStack) => HotKey c m @@ -148,15 +196,15 @@ sign = sign_ -- | The actual KES key, unless it expired, in which case it is replaced by -- \"poison\". data KESKey c = - KESKey !(KES.UnsoundPureSignKeyKES (KES c)) + KESKey !(OCert.OCert c) !(KES.SignKeyKES (KES c)) | KESKeyPoisoned deriving (Generic) -instance Crypto c => NoThunks (KESKey c) +instance (NoThunks (KES.SignKeyKES (KES c)), Crypto c) => NoThunks (KESKey c) kesKeyIsPoisoned :: KESKey c -> Bool kesKeyIsPoisoned KESKeyPoisoned = True -kesKeyIsPoisoned (KESKey _) = False +kesKeyIsPoisoned (KESKey _ _) = False data KESState c = KESState { kesStateInfo :: !KESInfo @@ -164,44 +212,149 @@ data KESState c = KESState { } deriving (Generic) -instance Crypto c => NoThunks (KESState c) +instance (NoThunks (KES.SignKeyKES (KES c)), Crypto c) => NoThunks (KESState c) +-- Create a new 'HotKey' and initialize it to the given initial KES key. The +-- initial key must be at evolution 0 (i.e., freshly generated and never +-- evolved). mkHotKey :: forall m c. (Crypto c, IOLike m) - => KES.UnsoundPureSignKeyKES (KES c) + => OCert.OCert c + -> KES.SignKeyKES (KES c) -> Absolute.KESPeriod -- ^ Start period -> Word64 -- ^ Max KES evolutions -> m (HotKey c m) -mkHotKey initKey startPeriod@(Absolute.KESPeriod start) maxKESEvolutions = do +mkHotKey = mkHotKeyAtEvolution 0 + +-- Create a new 'HotKey' and initialize it to the given initial KES key. The +-- initial key should be at the given evolution. +mkHotKeyAtEvolution :: + forall m c. (Crypto c, IOLike m) + => Word + -> OCert.OCert c + -> KES.SignKeyKES (KES c) + -> Absolute.KESPeriod -- ^ Start period + -> Word64 -- ^ Max KES evolutions + -> m (HotKey c m) +mkHotKeyAtEvolution evolution ocert initKey startPeriod maxKESEvolutions = + mkHotKeyWith + (Just (ocert, initKey, evolution, startPeriod)) + maxKESEvolutions + Nothing + (pure ()) + +-- | Create a new 'HotKey' and initialize it to a poisoned state (containing no +-- valid KES sign key). +mkEmptyHotKey :: + forall m c. (Crypto c, IOLike m) + => Word64 -- ^ Max KES evolutions + -> m () + -> m (HotKey c m) +mkEmptyHotKey maxKESEvolutions = + mkDynamicHotKey maxKESEvolutions Nothing + +mkKESState :: Word64 -> OCert.OCert c -> KES.SignKeyKES (KES c) -> Word -> Absolute.KESPeriod -> KESState c +mkKESState maxKESEvolutions newOCert newKey evolution startPeriod@(Absolute.KESPeriod start) = + KESState { + kesStateInfo = KESInfo { + kesStartPeriod = startPeriod + , kesEndPeriod = Absolute.KESPeriod (start + fromIntegral maxKESEvolutions) + , kesEvolution = evolution + } + , kesStateKey = KESKey newOCert newKey + } + +type KeyProducer c m = + (OCert.OCert c -> KES.SignKeyKES (KES c) -> Word -> Absolute.KESPeriod -> m ()) + -- ^ Callback that will be invoked when a new key has been received + -> m () + -- ^ Callback that will be invoked when a key deletion has been received + -> m () + +-- | Create a new 'HotKey' that runs a key-producer action on a separate thread. +-- The key producer action will receive a callback that can be used to pass +-- keys into the HotKey; the HotKey will dynamically update its internal state +-- to reflect new keys as they arrive. +mkDynamicHotKey :: + forall m c. (Crypto c, IOLike m) + => Word64 -- ^ Max KES evolutions + -> Maybe (KeyProducer c m) + -> m () + -> m (HotKey c m) +mkDynamicHotKey = mkHotKeyWith Nothing + +-- | The most general function for creating a new 'HotKey', accepting an initial +-- set of credentials, a key producer action, and a custom finalizer. +mkHotKeyWith :: + forall m c. (Crypto c, IOLike m) + => Maybe (OCert.OCert c, KES.SignKeyKES (KES c), Word, Absolute.KESPeriod) + -> Word64 -- ^ Max KES evolutions + -> Maybe (KeyProducer c m) + -> m () + -> m (HotKey c m) +mkHotKeyWith initialStateMay maxKESEvolutions keyThreadMay finalizer = do varKESState <- newMVar initKESState + + let set newOCert newKey evolution startPeriod = + modifyMVar_ varKESState $ \oldState -> do + _ <- poisonState oldState + return $ mkKESState maxKESEvolutions newOCert newKey evolution startPeriod + unset = + modifyMVar_ varKESState $ poisonState + + forM_ initialStateMay $ \(newOCert, newKey, evolution, startPeriod) -> + set newOCert newKey evolution startPeriod + + finalizer' <- case keyThreadMay of + Just keyThread -> do + keyThreadAsync <- async $ do + labelThisThread "HotKey receiver" + keyThread set unset + return (cancel keyThreadAsync >> finalizer) + Nothing -> + return finalizer + return HotKey { evolve = evolveKey varKESState , getInfo = kesStateInfo <$> readMVar varKESState + , getOCertMaybe = kesStateKey <$> readMVar varKESState >>= \case + KESKeyPoisoned -> return Nothing + KESKey ocert _ -> return (Just ocert) + , isPoisoned = kesKeyIsPoisoned . kesStateKey <$> readMVar varKESState , sign_ = \toSign -> do - KESState { kesStateInfo, kesStateKey } <- readMVar varKESState - case kesStateKey of - KESKeyPoisoned -> error "trying to sign with a poisoned key" - KESKey key -> do - let evolution = kesEvolution kesStateInfo - signed = KES.unsoundPureSignedKES () evolution toSign key - -- Force the signature to WHNF (for 'SignedKES', WHNF implies - -- NF) so that we don't have any thunks holding on to a key that - -- might be destructively updated when evolved. - evaluate signed + withMVar varKESState $ \KESState { kesStateInfo, kesStateKey } -> do + case kesStateKey of + KESKeyPoisoned -> + error "trying to sign with a poisoned key" + KESKey _ key -> do + let evolution = kesEvolution kesStateInfo + KES.signedKES () evolution toSign key + , forget = unset + , finalize_ = finalizer' } where initKESState :: KESState c initKESState = KESState { kesStateInfo = KESInfo { - kesStartPeriod = startPeriod - , kesEndPeriod = Absolute.KESPeriod (start + fromIntegral maxKESEvolutions) - -- We always start from 0 as the key hasn't evolved yet. + kesStartPeriod = Absolute.KESPeriod 0 + , kesEndPeriod = Absolute.KESPeriod 0 , kesEvolution = 0 } - , kesStateKey = KESKey initKey + , kesStateKey = KESKeyPoisoned } +poisonState :: forall m c. (KES.KESAlgorithm (KES c), IOLike m) + => KESState c -> m (KESState c) +poisonState kesState = do + case kesStateKey kesState of + KESKeyPoisoned -> do + -- already poisoned + return kesState + KESKey _ sk -> do + forgetSignKeyKES sk + return kesState { kesStateKey = KESKeyPoisoned } + -- | Evolve the 'HotKey' so that its evolution matches the given KES period. -- -- When the given KES period is after the end period of the 'HotKey', we @@ -216,7 +369,7 @@ mkHotKey initKey startPeriod@(Absolute.KESPeriod start) maxKESEvolutions = do -- -- When the key is poisoned, we always return 'UpdateFailed'. evolveKey :: - forall m c. (Crypto c, IOLike m) + forall m c. (IOLike m, KES.ContextKES (KES c) ~ (), KES.KESAlgorithm (KES c)) => StrictMVar m (KESState c) -> Absolute.KESPeriod -> m KESEvolutionInfo evolveKey varKESState targetPeriod = modifyMVar varKESState $ \kesState -> do let info = kesStateInfo kesState @@ -230,7 +383,7 @@ evolveKey varKESState targetPeriod = modifyMVar varKESState $ \kesState -> do let err = KESKeyAlreadyPoisoned info targetPeriod in return (kesState, UpdateFailed err) - KESKey key -> case kesStatus info targetPeriod of + KESKey ocert key -> case kesStatus info targetPeriod of -- When the absolute period is before the start period, we can't -- update the key. 'checkCanForge' will say we can't forge because the -- key is not valid yet. @@ -239,9 +392,10 @@ evolveKey varKESState targetPeriod = modifyMVar varKESState $ \kesState -> do -- When the absolute period is after the end period, we can't evolve -- anymore and poison the expired key. - AfterKESEnd {} -> + AfterKESEnd {} -> do let err = KESCouldNotEvolve info targetPeriod - in return (poisonState kesState, UpdateFailed err) + poisonedState <- poisonState kesState + return (poisonedState, UpdateFailed err) InKESRange targetEvolution -- No evolving needed @@ -251,30 +405,27 @@ evolveKey varKESState targetPeriod = modifyMVar varKESState $ \kesState -> do -- Evolving needed | otherwise -> (\s' -> (s', Updated (kesStateInfo s'))) <$> - go targetEvolution info key + go targetEvolution info ocert key where - poisonState :: KESState c -> KESState c - poisonState kesState = kesState { kesStateKey = KESKeyPoisoned } - -- | PRECONDITION: -- -- > targetEvolution >= curEvolution - go :: KESEvolution -> KESInfo -> KES.UnsoundPureSignKeyKES (KES c) -> m (KESState c) - go targetEvolution info key + go :: KESEvolution -> KESInfo -> OCert.OCert c -> KES.SignKeyKES (KES c) -> m (KESState c) + go targetEvolution info ocert key | targetEvolution <= curEvolution - = return $ KESState { kesStateInfo = info, kesStateKey = KESKey key } + = return $ KESState { kesStateInfo = info, kesStateKey = KESKey ocert key } | otherwise - = case KES.unsoundPureUpdateKES () key curEvolution of - -- This cannot happen - Nothing -> error "Could not update KES key" - Just !key' -> do - -- Clear the memory associated with the old key - -- FIXME: Secure forgetting is not available through the unsound KES API, - -- but we must restore this invocation when moving to the new mlocked KES - -- API. - -- forgetSignKeyKES key - let info' = info { kesEvolution = curEvolution + 1 } - go targetEvolution info' key' + = do + maybeKey' <- KES.updateKES () key curEvolution + case maybeKey' of + Nothing -> + -- This cannot happen + error "Could not update KES key" + Just !key' -> do + -- Clear the memory associated with the old key + forgetSignKeyKES key + let info' = info { kesEvolution = curEvolution + 1 } + go targetEvolution info' ocert key' where curEvolution = kesEvolution info diff --git a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs index 9198fa982e..ad99a31ea3 100644 --- a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs +++ b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos.hs @@ -10,6 +10,7 @@ {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableSuperClasses #-} {-# LANGUAGE ViewPatterns #-} @@ -157,27 +158,25 @@ forgePraosFields hotKey PraosCanBeLeader { praosCanBeLeaderColdVerKey, - praosCanBeLeaderSignKeyVRF, - praosCanBeLeaderOpCert + praosCanBeLeaderSignKeyVRF } PraosIsLeader {praosIsLeaderVrfRes} mkToSign = do + ocert <- HotKey.getOCert hotKey + let signedFields = + PraosToSign + { praosToSignIssuerVK = praosCanBeLeaderColdVerKey, + praosToSignVrfVK = VRF.deriveVerKeyVRF praosCanBeLeaderSignKeyVRF, + praosToSignVrfRes = praosIsLeaderVrfRes, + praosToSignOCert = ocert + } + toSign = mkToSign signedFields signature <- HotKey.sign hotKey toSign return PraosFields { praosSignature = signature, praosToSign = toSign } - where - toSign = mkToSign signedFields - - signedFields = - PraosToSign - { praosToSignIssuerVK = praosCanBeLeaderColdVerKey, - praosToSignVrfVK = VRF.deriveVerKeyVRF praosCanBeLeaderSignKeyVRF, - praosToSignVrfRes = praosIsLeaderVrfRes, - praosToSignOCert = praosCanBeLeaderOpCert - } {------------------------------------------------------------------------------- Protocol proper diff --git a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/AgentClient.hs b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/AgentClient.hs new file mode 100644 index 0000000000..7353714a65 --- /dev/null +++ b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/AgentClient.hs @@ -0,0 +1,188 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeSynonymInstances #-} + +module Ouroboros.Consensus.Protocol.Praos.AgentClient ( + AgentCrypto (..) + , KESAgentClientTrace (..) + , KESAgentContext + , MonadKESAgent (..) + , runKESAgentClient + ) where + +import Cardano.Crypto.DirectSerialise (DirectDeserialise, + DirectSerialise) +import Cardano.Crypto.KES.Class +import Cardano.Crypto.VRF.Class +import qualified Cardano.KESAgent.KES.Bundle as Agent +import qualified Cardano.KESAgent.KES.Crypto as Agent +import qualified Cardano.KESAgent.KES.OCert as Agent +import qualified Cardano.KESAgent.Processes.ServiceClient as Agent +import qualified Cardano.KESAgent.Protocols.RecvResult as Agent +import qualified Cardano.KESAgent.Protocols.StandardCrypto as Agent +import qualified Cardano.KESAgent.Protocols.VersionedProtocol as Agent +import qualified Cardano.KESAgent.Serialization.DirectCodec as Agent +import Cardano.KESAgent.Util.RefCounting +import Cardano.Ledger.Keys (DSIGN) +import Cardano.Protocol.Crypto (Crypto, KES, StandardCrypto, VRF) +import qualified Cardano.Protocol.TPraos.OCert as OCert +import Control.Monad (forever) +import Control.Monad.Class.MonadAsync +import Control.Monad.IOSim +import Control.Tracer +import Data.Coerce (coerce) +import Data.Kind +import Data.SerDoc.Class as SerDoc +import Data.Typeable +import Network.Socket +import Ouroboros.Consensus.Util.IOLike +import Ouroboros.Network.RawBearer +import Ouroboros.Network.Snocket +import qualified Simulation.Network.Snocket as SimSnocket +import System.IOManager +import Test.Ouroboros.Network.Data.AbsBearerInfo as ABI + +type KESAgentContext c m = + ( AgentCrypto c + , MonadKESAgent m + , SerDoc.HasInfo (Agent.DirectCodec m) (VerKeyKES (KES c)) + , SerDoc.HasInfo (Agent.DirectCodec m) (SignKeyKES (KES c)) + , IOLike m + ) + +data KESAgentClientTrace + = KESAgentClientException SomeException + | KESAgentClientTrace Agent.ServiceClientTrace + deriving (Show) + +class ( Crypto c + , Agent.Crypto (ACrypto c) + , Agent.NamedCrypto (ACrypto c) + , Agent.KES (ACrypto c) ~ KES c + + , ContextKES (KES c) ~ () + , ContextVRF (VRF c) ~ () + , Typeable (ACrypto c) + , Agent.ServiceClientDrivers (ACrypto c) + , DirectSerialise (SignKeyKES (KES c)) + , DirectDeserialise (SignKeyKES (KES c)) + ) + => AgentCrypto c where + type ACrypto c :: Type + +instance AgentCrypto StandardCrypto where + type ACrypto StandardCrypto = Agent.StandardCrypto + +convertOCert :: (AgentCrypto c, Agent.DSIGN (ACrypto c) ~ DSIGN) => Agent.OCert (ACrypto c) -> OCert.OCert c +convertOCert oca = + OCert.OCert + { OCert.ocertVkHot = Agent.ocertVkHot oca + , OCert.ocertN = Agent.ocertN oca + , OCert.ocertKESPeriod = OCert.KESPeriod (Agent.unKESPeriod $ Agent.ocertKESPeriod oca) + , OCert.ocertSigma = coerce (Agent.ocertSigma oca) + } + +convertPeriod :: Agent.KESPeriod -> OCert.KESPeriod +convertPeriod (Agent.KESPeriod p) = OCert.KESPeriod p + +class (MonadFail m, Show (Addr m)) => MonadKESAgent m where + type FD m :: Type + type Addr m :: Type + withAgentContext :: (Snocket m (FD m) (Addr m) -> m a) -> m a + makeRawBearer :: MakeRawBearer m (FD m) + makeAddress :: Proxy m -> FilePath -> Addr m + +instance MonadKESAgent IO where + type FD IO = Socket + type Addr IO = SockAddr + withAgentContext inner = + withIOManager $ \ioManager -> + inner (socketSnocket ioManager) + makeRawBearer = makeSocketRawBearer + makeAddress _ = SockAddrUnix + +instance MonadKESAgent (IOSim s) where + type FD (IOSim s) = SimSnocket.FD (IOSim s) (TestAddress FilePath) + type Addr (IOSim s) = TestAddress FilePath + withAgentContext inner = do + SimSnocket.withSnocket + nullTracer + (toBearerInfo $ absNoAttenuation {abiConnectionDelay = SmallDelay}) + mempty + $ \snocket _observe -> inner snocket + makeRawBearer = SimSnocket.makeFDRawBearer nullTracer + makeAddress _ = TestAddress + +instance SimSnocket.GlobalAddressScheme FilePath where + getAddressType = const SimSnocket.IPv4Address + ephemeralAddress _ty num = TestAddress $ "simSnocket_" <> show num + +runKESAgentClient :: forall m c. + ( KESAgentContext c m + , Agent.DSIGN (ACrypto c) ~ DSIGN + ) + => Tracer m KESAgentClientTrace + -> FilePath + -> (OCert.OCert c -> SignKeyKES (KES c) -> Word -> OCert.KESPeriod -> m ()) + -> m () + -> m () +runKESAgentClient tracer path handleKey handleDropKey = do + withAgentContext $ \snocket -> do + forever $ do + Agent.runServiceClient + (Proxy @(ACrypto c)) + makeRawBearer + (Agent.ServiceClientOptions + { Agent.serviceClientSnocket = snocket + , Agent.serviceClientAddress = makeAddress (Proxy @m) path + } :: Agent.ServiceClientOptions m (FD m) (Addr m) + ) + (\(Agent.TaggedBundle mBundle _) -> do + case mBundle of + Just (Agent.Bundle skpRef ocert) -> do + -- We take ownership of the key, so we acquire one extra reference, + -- preventing the key from being discarded after `handleKey` + -- finishes. + _ <- acquireCRef skpRef + withCRefValue skpRef $ \(SignKeyWithPeriodKES sk p) -> + handleKey (convertOCert ocert) sk p (convertPeriod $ Agent.ocertKESPeriod ocert) + return Agent.RecvOK + _ -> do + handleDropKey + return Agent.RecvOK + ) + (contramap KESAgentClientTrace tracer) + `catch` ( \(_e :: AsyncCancelled) -> + return () + ) + `catch` ( \(e :: SomeException) -> + traceWith tracer (KESAgentClientException e) + ) + threadDelay 10000000 + +toBearerInfo :: ABI.AbsBearerInfo -> SimSnocket.BearerInfo +toBearerInfo abi = + SimSnocket.BearerInfo + { SimSnocket.biConnectionDelay = ABI.delay (ABI.abiConnectionDelay abi) + , SimSnocket.biInboundAttenuation = attenuation (ABI.abiInboundAttenuation abi) + , SimSnocket.biOutboundAttenuation = attenuation (ABI.abiOutboundAttenuation abi) + , SimSnocket.biInboundWriteFailure = ABI.abiInboundWriteFailure abi + , SimSnocket.biOutboundWriteFailure = ABI.abiOutboundWriteFailure abi + , SimSnocket.biAcceptFailures = + ( \(errDelay, errType) -> + ( ABI.delay errDelay + , errType + ) + ) + <$> abiAcceptFailure abi + , SimSnocket.biSDUSize = toSduSize (ABI.abiSDUSize abi) + } diff --git a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/Common.hs b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/Common.hs index 210457ea89..a5713f6936 100644 --- a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/Common.hs +++ b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/Praos/Common.hs @@ -2,8 +2,14 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -- | Various things common to iterations of the Praos protocol. @@ -13,26 +19,35 @@ module Ouroboros.Consensus.Protocol.Praos.Common ( , PraosChainSelectView (..) , VRFTiebreakerFlavor (..) -- * node support + , PraosCredentialsSource (..) , PraosNonces (..) , PraosProtocolSupportsNode (..) + , instantiatePraosCredentials ) where +import qualified Cardano.Crypto.KES.Class as KES +import Cardano.Crypto.VRF import qualified Cardano.Crypto.VRF as VRF +import qualified Cardano.KESAgent.KES.Crypto as Agent import Cardano.Ledger.BaseTypes (Nonce) import qualified Cardano.Ledger.BaseTypes as SL -import Cardano.Ledger.Keys (KeyHash, KeyRole (BlockIssuer)) +import Cardano.Ledger.Keys (DSIGN, KeyHash, KeyRole (BlockIssuer)) import qualified Cardano.Ledger.Shelley.API as SL -import Cardano.Protocol.Crypto (Crypto, VRF) +import Cardano.Protocol.Crypto (Crypto, KES, VRF) import qualified Cardano.Protocol.TPraos.OCert as OCert import Cardano.Slotting.Block (BlockNo) import Cardano.Slotting.Slot (SlotNo) +import qualified Control.Tracer as Tracer import Data.Function (on) import Data.Map.Strict (Map) import Data.Ord (Down (Down)) import Data.Word (Word64) import GHC.Generics (Generic) -import NoThunks.Class (NoThunks) +import NoThunks.Class import Ouroboros.Consensus.Protocol.Abstract +import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey +import Ouroboros.Consensus.Protocol.Praos.AgentClient +import Ouroboros.Consensus.Util.IOLike -- | The maximum major protocol version. -- @@ -244,16 +259,63 @@ instance Crypto c => ChainOrder (PraosChainSelectView c) where preferCandidate cfg ours cand = comparePraos cfg ours cand == LT data PraosCanBeLeader c = PraosCanBeLeader - { -- | Certificate delegating rights from the stake pool cold key (or - -- genesis stakeholder delegate cold key) to the online KES key. - praosCanBeLeaderOpCert :: !(OCert.OCert c), - -- | Stake pool cold key or genesis stakeholder delegate cold key. - praosCanBeLeaderColdVerKey :: !(SL.VKey 'SL.BlockIssuer), - praosCanBeLeaderSignKeyVRF :: !(VRF.SignKeyVRF (VRF c)) + { -- | Stake pool cold key or genesis stakeholder delegate cold key. + praosCanBeLeaderColdVerKey :: !(SL.VKey 'SL.BlockIssuer), + praosCanBeLeaderSignKeyVRF :: !(SignKeyVRF (VRF c)), + -- | How to obtain KES credentials (ocert + sign key) + praosCanBeLeaderCredentialsSource :: !(PraosCredentialsSource c) } deriving (Generic) -instance Crypto c => NoThunks (PraosCanBeLeader c) +instance (NoThunks (SignKeyVRF (VRF c)), NoThunks (KES.UnsoundPureSignKeyKES (KES c)), Crypto c) => NoThunks (PraosCanBeLeader c) + +-- | Defines a method for obtaining Praos credentials (opcert + KES signing +-- key). +data PraosCredentialsSource c where + -- | Pass an opcert and sign key directly. This uses + -- 'KES.UnsoundPureSignKeyKES', which does not provide mlocking guarantees, + -- violating the rule that KES secrets must never be stored on disk, but + -- allows the sign key to be loaded from a local file. This method is + -- provided for backwards compatibility. + PraosCredentialsUnsound :: OCert.OCert c -> KES.UnsoundPureSignKeyKES (KES c) -> PraosCredentialsSource c + -- | Connect to a KES agent listening on a service socket at the given path. + PraosCredentialsAgent :: Agent.DSIGN (ACrypto c) ~ DSIGN => FilePath -> PraosCredentialsSource c + +instance (NoThunks (KES.UnsoundPureSignKeyKES (KES c)), Crypto c) => NoThunks (PraosCredentialsSource c) where + wNoThunks ctxt = \case + PraosCredentialsUnsound oca k -> allNoThunks [ + noThunks ctxt oca + , noThunks ctxt k + ] + PraosCredentialsAgent fp -> noThunks ctxt fp + + showTypeOf _ = "PraosCredentialsSource" + +instantiatePraosCredentials :: forall m c. + ( KESAgentContext c m + ) + => Word64 + -> Tracer.Tracer m KESAgentClientTrace + -> PraosCredentialsSource c + -> m (HotKey.HotKey c m) +instantiatePraosCredentials maxKESEvolutions _ (PraosCredentialsUnsound ocert skUnsound) = do + sk <- KES.unsoundPureSignKeyKESToSoundSignKeyKES skUnsound + let startPeriod :: OCert.KESPeriod + startPeriod = OCert.ocertKESPeriod ocert + + HotKey.mkHotKey + ocert + sk + startPeriod + maxKESEvolutions + +instantiatePraosCredentials maxKESEvolutions tr (PraosCredentialsAgent path) = do + HotKey.mkDynamicHotKey + maxKESEvolutions + (Just $ \handleKey handleDrop -> do + runKESAgentClient tr path handleKey handleDrop + ) + (pure ()) -- | See 'PraosProtocolSupportsNode' data PraosNonces = PraosNonces { diff --git a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/TPraos.hs b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/TPraos.hs index fab03fedc1..200e05b008 100644 --- a/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/TPraos.hs +++ b/ouroboros-consensus-protocol/src/ouroboros-consensus-protocol/Ouroboros/Consensus/Protocol/TPraos.hs @@ -133,21 +133,21 @@ forgeTPraosFields :: -> (TPraosToSign c -> toSign) -> m (TPraosFields c toSign) forgeTPraosFields hotKey PraosCanBeLeader{..} TPraosIsLeader{..} mkToSign = do + ocert <- HotKey.getOCert hotKey + let signedFields = + TPraosToSign { + tpraosToSignIssuerVK = praosCanBeLeaderColdVerKey + , tpraosToSignVrfVK = VRF.deriveVerKeyVRF praosCanBeLeaderSignKeyVRF + , tpraosToSignEta = tpraosIsLeaderEta + , tpraosToSignLeader = tpraosIsLeaderProof + , tpraosToSignOCert = ocert + } + toSign = mkToSign signedFields signature <- HotKey.sign hotKey toSign return TPraosFields { tpraosSignature = signature , tpraosToSign = toSign } - where - toSign = mkToSign signedFields - - signedFields = TPraosToSign { - tpraosToSignIssuerVK = praosCanBeLeaderColdVerKey - , tpraosToSignVrfVK = VRF.deriveVerKeyVRF praosCanBeLeaderSignKeyVRF - , tpraosToSignEta = tpraosIsLeaderEta - , tpraosToSignLeader = tpraosIsLeaderProof - , tpraosToSignOCert = praosCanBeLeaderOpCert - } -- | Because we are using the executable spec, rather than implementing the -- protocol directly here, we have a fixed header type rather than an diff --git a/ouroboros-consensus/changelog.d/20250130_093251_tdammers_mlocked_kes_rebase.md b/ouroboros-consensus/changelog.d/20250130_093251_tdammers_mlocked_kes_rebase.md new file mode 100644 index 0000000000..0aa430bf84 --- /dev/null +++ b/ouroboros-consensus/changelog.d/20250130_093251_tdammers_mlocked_kes_rebase.md @@ -0,0 +1,8 @@ +### Breaking + +- Use new mlocked KES API to represent KES sign keys internally. This ensures + that KES keys are securely erased when replaced with a newer evolution or a + fresh key, and that they will not spill to disk or swap. See + https://github.com/IntersectMBO/cardano-base/pull/255. +- Add `finalize` method to `BlockForging`, and use it where necessary to clean + up when a block forging thread terminates (see `forkLinkedWatcherFinalize`) diff --git a/ouroboros-consensus/ouroboros-consensus.cabal b/ouroboros-consensus/ouroboros-consensus.cabal index af9a1c338b..ba580cc679 100644 --- a/ouroboros-consensus/ouroboros-consensus.cabal +++ b/ouroboros-consensus/ouroboros-consensus.cabal @@ -523,7 +523,7 @@ library unstable-mock-block cardano-binary, cardano-crypto-class, cardano-ledger-core, - cardano-slotting:{cardano-slotting, testlib}, + cardano-slotting, cborg, containers, deepseq, diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Block/Forging.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Block/Forging.hs index b5f6522913..9df4430f23 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Block/Forging.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Block/Forging.hs @@ -19,6 +19,7 @@ module Ouroboros.Consensus.Block.Forging ( , checkShouldForge , forgeStateUpdateInfoFromUpdateInfo -- * 'UpdateInfo' + , KESTracer , UpdateInfo (..) ) where @@ -72,6 +73,8 @@ castForgeStateUpdateInfo = \case ForgeStateUpdateFailed x -> ForgeStateUpdateFailed x ForgeStateUpdateSuppressed -> ForgeStateUpdateSuppressed +type family KESTracer blk + -- | Stateful wrapper around block production -- -- NOTE: do not refer to the consensus or ledger config in the closure of this @@ -143,6 +146,15 @@ data BlockForging m blk = BlockForging { -> [Validated (GenTx blk)] -- Transactions to include -> IsLeader (BlockProtocol blk) -- Proof we are leader -> m blk + + -- | Clean up any unmanaged resources. + -- + -- Such resources may include KES keys that require explicit erasing + -- ("secure forgetting"), and threads that connect to a KES agent. + -- This method will be run once when the block forging thread + -- terminates, whether cleanly or due to an exception. + , finalize :: m () + } data ShouldForge blk = diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Binary.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Binary.hs index 97ab5adc7a..5693c61ee8 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Binary.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Binary.hs @@ -3,10 +3,12 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeOperators #-} module Ouroboros.Consensus.HardFork.Combinator.Embed.Binary (protocolInfoBinary) where import Control.Exception (assert) +import qualified Control.Tracer as Tracer import Data.Align (alignWith) import Data.SOP.Counting (exactlyTwo) import Data.SOP.Functors (Flip (..)) @@ -30,21 +32,21 @@ import Ouroboros.Consensus.TypeFamilyWrappers protocolInfoBinary :: forall m blk1 blk2. - (CanHardFork '[blk1, blk2], Monad m) + (CanHardFork '[blk1, blk2], Monad m, KESTracer blk1 ~ KESTracer blk2) -- First era => ProtocolInfo blk1 - -> m [BlockForging m blk1] + -> (Tracer.Tracer m (KESTracer blk1) -> m [BlockForging m blk1]) -> History.EraParams -> (ConsensusConfig (BlockProtocol blk1) -> PartialConsensusConfig (BlockProtocol blk1)) -> (LedgerConfig blk1 -> PartialLedgerConfig blk1) -- Second era -> ProtocolInfo blk2 - -> m [BlockForging m blk2] + -> (Tracer.Tracer m (KESTracer blk2) -> m [BlockForging m blk2]) -> History.EraParams -> (ConsensusConfig (BlockProtocol blk2) -> PartialConsensusConfig (BlockProtocol blk2)) -> (LedgerConfig blk2 -> PartialLedgerConfig blk2) -> ( ProtocolInfo (HardForkBlock '[blk1, blk2]) - , m [BlockForging m (HardForkBlock '[blk1, blk2])] + , Tracer.Tracer m (KESTracer blk1) -> m [BlockForging m (HardForkBlock '[blk1, blk2])] ) protocolInfoBinary protocolInfo1 blockForging1 eraParams1 toPartialConsensusConfig1 toPartialLedgerConfig1 protocolInfo2 blockForging2 eraParams2 toPartialConsensusConfig2 toPartialLedgerConfig2 = @@ -92,7 +94,7 @@ protocolInfoBinary protocolInfo1 blockForging1 eraParams1 toPartialConsensusConf headerStateChainDep initHeaderState1 } } - , alignWith alignBlockForging <$> blockForging1 <*> blockForging2 + , \tr -> alignWith alignBlockForging <$> blockForging1 tr <*> blockForging2 tr ) where ProtocolInfo { diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Unary.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Unary.hs index 5236b6ca7e..435481be91 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Unary.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Unary.hs @@ -420,6 +420,7 @@ instance Functor m => Isomorphic (BlockForging m) where project BlockForging {..} = BlockForging { forgeLabel = forgeLabel , canBeLeader = project' (Proxy @(WrapCanBeLeader blk)) canBeLeader + , finalize = finalize , updateForgeState = \cfg sno tickedChainDepSt -> project <$> updateForgeState @@ -467,6 +468,7 @@ instance Functor m => Isomorphic (BlockForging m) where inject BlockForging {..} = BlockForging { forgeLabel = forgeLabel , canBeLeader = inject' (Proxy @(WrapCanBeLeader blk)) canBeLeader + , finalize = finalize , updateForgeState = \cfg sno tickedChainDepSt -> inject <$> updateForgeState diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Forging.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Forging.hs index 4ffcdc5037..a26e89dc9b 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Forging.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Forging.hs @@ -15,9 +15,12 @@ module Ouroboros.Consensus.HardFork.Combinator.Forging ( , hardForkBlockForging ) where +import Control.Monad (void) import Data.Functor.Product import Data.Maybe (fromMaybe) +import Data.SOP (Top) import Data.SOP.BasicFunctors +import Data.SOP.Constraint (All) import Data.SOP.Functors (Product2 (..)) import Data.SOP.Index import Data.SOP.InPairs (InPairs) @@ -89,6 +92,7 @@ hardForkBlockForging label blockForging = , updateForgeState = hardForkUpdateForgeState blockForging , checkCanForge = hardForkCheckCanForge blockForging , forgeBlock = hardForkForgeBlock blockForging + , finalize = hardForkFinalize blockForging } hardForkCanBeLeader :: @@ -98,6 +102,11 @@ hardForkCanBeLeader = SomeErasCanBeLeader . hmap (WrapCanBeLeader . canBeLeader) +hardForkFinalize :: (Monad m, All Top xs) + => NonEmptyOptNP (BlockForging m) xs -> m () +hardForkFinalize blockForging = + void $ htraverse_ finalize blockForging + -- | POSTCONDITION: the returned 'ForgeStateUpdateInfo' is from the same era as -- the ticked 'ChainDepState'. hardForkUpdateForgeState :: diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/Jumping.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/Jumping.hs index 73f50e0b3e..f0164b349e 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/Jumping.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/Jumping.hs @@ -362,9 +362,7 @@ data Instruction blk deriving instance (Typeable blk, HasHeader (Header blk), Eq (Header blk)) => Eq (Instruction blk) deriving instance (Typeable blk, HasHeader (Header blk), Show (Header blk)) => Show (Instruction blk) deriving anyclass instance - ( HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + ( LedgerSupportsProtocol blk ) => NoThunks (Instruction blk) data JumpInstruction blk @@ -384,9 +382,7 @@ instance (Typeable blk, HasHeader (Header blk), Show (Header blk)) => Show (Jump showParen (p > 10) $ showString "JumpToGoodPoint " . shows (AF.headPoint $ jTheirFragment jumpInfo) deriving anyclass instance - ( HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + ( LedgerSupportsProtocol blk ) => NoThunks (JumpInstruction blk) -- | The result of a jump request, either accepted or rejected. @@ -399,9 +395,7 @@ deriving instance (Typeable blk, HasHeader (Header blk), Eq (Header blk)) => Eq deriving instance (Typeable blk, HasHeader (Header blk), Show (Header blk)) => Show (JumpResult blk) deriving anyclass instance - ( HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + ( LedgerSupportsProtocol blk ) => NoThunks (JumpResult blk) -- | Compute the next instruction for the given peer. In the majority of cases, diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/State.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/State.hs index 9d0a9d8af0..c177de0460 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/State.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client/State.hs @@ -97,9 +97,7 @@ data ChainSyncClientHandle m blk = ChainSyncClientHandle { deriving anyclass instance ( IOLike m, - HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + LedgerSupportsProtocol blk ) => NoThunks (ChainSyncClientHandle m blk) -- | A collection of ChainSync client handles for the peers of this node. @@ -125,10 +123,8 @@ data ChainSyncClientHandleCollection peer m blk = ChainSyncClientHandleCollectio deriving anyclass instance ( IOLike m, - HasHeader blk, LedgerSupportsProtocol blk, NoThunks (STM m ()), - NoThunks (Header blk), NoThunks (STM m (Map peer (ChainSyncClientHandle m blk))), NoThunks (STM m (StrictSeq (peer, ChainSyncClientHandle m blk))) ) => NoThunks (ChainSyncClientHandleCollection peer m blk) @@ -175,9 +171,7 @@ data DynamoInitState blk deriving (Generic) deriving anyclass instance - ( HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + ( LedgerSupportsProtocol blk ) => NoThunks (DynamoInitState blk) data ObjectorInitState @@ -241,9 +235,7 @@ data ChainSyncJumpingState m blk deriving anyclass instance ( IOLike m, - HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + LedgerSupportsProtocol blk ) => NoThunks (ChainSyncJumpingState m blk) -- | The ChainSync state required for jumps @@ -295,7 +287,5 @@ data ChainSyncJumpingJumperState blk deriving (Generic) deriving anyclass instance - ( HasHeader blk, - LedgerSupportsProtocol blk, - NoThunks (Header blk) + ( LedgerSupportsProtocol blk ) => NoThunks (ChainSyncJumpingJumperState blk) diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/EarlyExit.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/EarlyExit.hs index 899e95a16f..fa1573885b 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/EarlyExit.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/EarlyExit.hs @@ -340,7 +340,7 @@ instance MonadLabelledSTM m => MonadLabelledSTM (WithEarlyExit m) where instance MonadSay m => MonadSay (WithEarlyExit m) where say = lift . say -instance (MonadInspectSTM m, Monad (InspectMonad m)) => MonadInspectSTM (WithEarlyExit m) where +instance (MonadInspectSTM m) => MonadInspectSTM (WithEarlyExit m) where type InspectMonad (WithEarlyExit m) = InspectMonad m inspectTVar _ = inspectTVar (Proxy @m) inspectTMVar _ = inspectTMVar (Proxy @m) diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/STM.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/STM.hs index 4d441861b7..064c44ab24 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/STM.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util/STM.hs @@ -12,6 +12,7 @@ module Ouroboros.Consensus.Util.STM ( -- * 'Watcher' Watcher (..) , forkLinkedWatcher + , forkLinkedWatcherFinalize , withWatcher -- * Misc , Fingerprint (..) @@ -167,6 +168,19 @@ forkLinkedWatcher :: forall m a fp. (IOLike m, Eq fp, HasCallStack) forkLinkedWatcher registry label watcher = forkLinkedThread registry label $ runWatcher watcher +-- | Spawn a new thread that runs a 'Watcher', executing a finalizer when the +-- thread terminates. +-- +-- The thread will be linked to the registry. +forkLinkedWatcherFinalize :: forall m a fp. (IOLike m, Eq fp, HasCallStack) + => ResourceRegistry m + -> String -- ^ Label for the thread + -> Watcher m a fp + -> m () + -> m (Thread m Void) +forkLinkedWatcherFinalize registry label watcher finalizer = + forkLinkedThread registry label $ runWatcher watcher `finally` finalizer + -- | Spawn a new thread that runs a 'Watcher' -- -- The thread is bracketed via 'withAsync' and 'link'ed. diff --git a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Ledger/Block.hs b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Ledger/Block.hs index 143df4ea50..6db75eaff8 100644 --- a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Ledger/Block.hs +++ b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Ledger/Block.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} @@ -170,6 +171,8 @@ instance (SimpleCrypto c, Typeable ext, Typeable ext') headerIsEBB = const Nothing +type KnownHashSize c = KnownNat (Hash.SizeHash (SimpleHash c)) + data SimpleStdHeader c ext = SimpleStdHeader { simplePrev :: ChainHash (SimpleBlock c ext) , simpleSlotNo :: SlotNo @@ -180,7 +183,7 @@ data SimpleStdHeader c ext = SimpleStdHeader { deriving stock (Generic, Show, Eq) deriving anyclass (NoThunks) -deriving anyclass instance KnownNat (Hash.SizeHash (SimpleHash c)) => +deriving anyclass instance KnownHashSize c => Serialise (SimpleStdHeader c ext) data SimpleBody = SimpleBody { @@ -665,7 +668,7 @@ instance InspectLedger (SimpleBlock c ext) where Crypto needed for simple blocks -------------------------------------------------------------------------------} -class (KnownNat (Hash.SizeHash (SimpleHash c)), HashAlgorithm (SimpleHash c), Typeable c) => SimpleCrypto c where +class (KnownHashSize c, HashAlgorithm (SimpleHash c), Typeable c) => SimpleCrypto c where type family SimpleHash c :: Type data SimpleStandardCrypto @@ -722,7 +725,7 @@ instance Condense ext' => Condense (SimpleBlock' c ext ext') where instance ToCBOR SimpleBody where toCBOR = encode -encodeSimpleHeader :: KnownNat (Hash.SizeHash (SimpleHash c)) +encodeSimpleHeader :: KnownHashSize c => (ext' -> CBOR.Encoding) -> Header (SimpleBlock' c ext ext') -> CBOR.Encoding diff --git a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node.hs b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node.hs index 71935d6475..43a956f749 100644 --- a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node.hs +++ b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node.hs @@ -100,6 +100,7 @@ simpleBlockForging aCanBeLeader aForgeExt = BlockForging { lst (map txForgetValidated txs) proof + , finalize = pure () } where _ = keepRedundantConstraint (Proxy @(ForgeStateUpdateError (SimpleBlock c ext) ~ Void)) diff --git a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/PBFT.hs b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/PBFT.hs index c2b22c8c40..96f58b752f 100644 --- a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/PBFT.hs +++ b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/PBFT.hs @@ -110,4 +110,5 @@ pbftBlockForging canBeLeader = BlockForging { lst (map txForgetValidated txs) proof + , finalize = pure () } diff --git a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/Praos.hs b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/Praos.hs index 0b3ce9a5d9..5756154ad9 100644 --- a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/Praos.hs +++ b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Node/Praos.hs @@ -136,4 +136,5 @@ praosBlockForging cid initHotKey = do tickedLedgerSt (map txForgetValidated txs) isLeader + , finalize = pure () } diff --git a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Protocol/Praos.hs b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Protocol/Praos.hs index 232eab4df5..4beb8a02c1 100644 --- a/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Protocol/Praos.hs +++ b/ouroboros-consensus/src/unstable-mock-block/Ouroboros/Consensus/Mock/Protocol/Praos.hs @@ -76,7 +76,6 @@ import Ouroboros.Consensus.NodeId (CoreNodeId (..)) import Ouroboros.Consensus.Protocol.Abstract import Ouroboros.Consensus.Protocol.Signed import Ouroboros.Consensus.Util.Condense -import Test.Cardano.Slotting.Numeric () -- The Praos paper can be located at https://ia.cr/2017/573 -- @@ -207,7 +206,8 @@ data HotKey c = | HotKeyPoisoned deriving (Generic) -instance PraosCrypto c => NoThunks (HotKey c) +instance (PraosCrypto c, NoThunks (UnsoundPureSignKeyKES (PraosKES c))) => NoThunks (HotKey c) + instance PraosCrypto c => Show (HotKey c) where show (HotKey p _) = "HotKey " ++ show p ++ "