Skip to content

Commit

Permalink
InspectLedger instance for Shelley
Browse files Browse the repository at this point in the history
Fixes #2452.

Co-authored-by: Thomas Winant <thomas@well-typed.com>
  • Loading branch information
edsko and mrBliss committed Sep 23, 2020
1 parent 8b5537e commit af6c334
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ instance NoHardForks ByronBlock where
byronEraParamsNeverHardForks (byronGenesisConfig (configBlock cfg))
toPartialConsensusConfig _ = id
toPartialLedgerConfig _ cfg = ByronPartialLedgerConfig {
byronLedgerConfig = cfg
, triggerHardFork = TriggerHardForkNever
byronLedgerConfig = cfg
, byronTriggerHardFork = TriggerHardForkNever
}

{-------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,24 @@ byronTransition ByronPartialLedgerConfig{..} shelleyMajorVersion state =
takeAny :: [a] -> Maybe a
takeAny = listToMaybe

{-------------------------------------------------------------------------------
Figure out the transition point for Shelley
-------------------------------------------------------------------------------}

shelleyTransition ::
PartialLedgerConfig (ShelleyBlock sc)
-> Word16 -- ^ Next era's major protocol version
-> LedgerState (ShelleyBlock sc)
-> Maybe EpochNo
shelleyTransition = undefined

{-------------------------------------------------------------------------------
SingleEraBlock Byron
-------------------------------------------------------------------------------}

instance SingleEraBlock ByronBlock where
singleEraTransition pcfg _eraParams _eraStart ledgerState =
case triggerHardFork pcfg of
case byronTriggerHardFork pcfg of
TriggerHardForkNever -> Nothing
TriggerHardForkAtEpoch epoch -> Just epoch
TriggerHardForkAtVersion shelleyMajorVersion ->
Expand Down Expand Up @@ -228,8 +239,8 @@ data TriggerHardFork =
-- condition for the hard fork to Shelley, as we don't have to modify the
-- ledger config for standalone Byron.
data ByronPartialLedgerConfig = ByronPartialLedgerConfig {
byronLedgerConfig :: !(LedgerConfig ByronBlock)
, triggerHardFork :: !TriggerHardFork
byronLedgerConfig :: !(LedgerConfig ByronBlock)
, byronTriggerHardFork :: !TriggerHardFork
}
deriving (Generic, NoUnexpectedThunks)

Expand All @@ -244,8 +255,15 @@ instance HasPartialLedgerConfig ByronBlock where
-------------------------------------------------------------------------------}

instance TPraosCrypto era => SingleEraBlock (ShelleyBlock era) where
-- No transition from Shelley to Goguen yet
singleEraTransition _cfg _eraParams _eraStart _st = Nothing
singleEraTransition pcfg _eraParams _eraStart ledgerState =
case shelleyTriggerHardFork pcfg of
TriggerHardForkNever -> Nothing
TriggerHardForkAtEpoch epoch -> Just epoch
TriggerHardForkAtVersion shelleyMajorVersion ->
shelleyTransition
pcfg
shelleyMajorVersion
ledgerState

singleEraInfo _ = SingleEraInfo {
singleEraName = "Shelley"
Expand All @@ -259,23 +277,24 @@ instance TPraosCrypto era => HasPartialConsensusConfig (TPraos era) where
-- 'ChainSelConfig' is ()
partialChainSelConfig _ _ = ()

newtype ShelleyPartialLedgerConfig era = ShelleyPartialLedgerConfig {
data ShelleyPartialLedgerConfig era = ShelleyPartialLedgerConfig {
-- | We cache the non-partial ledger config containing a dummy
-- 'EpochInfo' that needs to be replaced with the correct one.
--
-- We do this to avoid recomputing the ledger config each time
-- 'completeLedgerConfig' is called, as 'mkShelleyLedgerConfig' does
-- some rather expensive computations that shouldn't be repeated too
-- often (e.g., 'sgActiveSlotCoeff').
getShelleyPartialLedgerConfig :: ShelleyLedgerConfig era
shelleyLedgerConfig :: !(ShelleyLedgerConfig era)
, shelleyTriggerHardFork :: !TriggerHardFork
}
deriving (Generic, NoUnexpectedThunks)

instance TPraosCrypto era => HasPartialLedgerConfig (ShelleyBlock era) where
type PartialLedgerConfig (ShelleyBlock era) = ShelleyPartialLedgerConfig era

-- Replace the dummy 'EpochInfo' with the real one
completeLedgerConfig _ epochInfo (ShelleyPartialLedgerConfig cfg) =
completeLedgerConfig _ epochInfo (ShelleyPartialLedgerConfig cfg _) =
cfg {
shelleyLedgerGlobals = (shelleyLedgerGlobals cfg) {
SL.epochInfo = epochInfo
Expand Down
40 changes: 25 additions & 15 deletions ouroboros-consensus-cardano/src/Ouroboros/Consensus/Cardano/Node.hs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ protocolInfoCardano
-> ProtocolInfo m (CardanoBlock c)
protocolInfoCardano genesisByron mSigThresh pVer sVer mbCredsByron
genesisShelley initialNonce protVer maxMajorPV mbCredsShelley
mbLowerBound triggerHardFork =
mbLowerBound byronTriggerHardFork =
assertWithMsg (validateGenesis genesisShelley) $
ProtocolInfo {
pInfoConfig = cfg
Expand Down Expand Up @@ -314,8 +314,8 @@ protocolInfoCardano genesisByron mSigThresh pVer sVer mbCredsByron

partialLedgerConfigByron :: PartialLedgerConfig ByronBlock
partialLedgerConfigByron = ByronPartialLedgerConfig {
byronLedgerConfig = ledgerConfigByron
, triggerHardFork = triggerHardFork
byronLedgerConfig = ledgerConfigByron
, byronTriggerHardFork = byronTriggerHardFork
}

kByron :: SecurityParam
Expand Down Expand Up @@ -459,9 +459,16 @@ ledgerConfigCardano ::
-> Maybe EpochNo -- ^ lower bound on first Shelley epoch

-> CardanoLedgerConfig c
ledgerConfigCardano genesisByron
genesisShelley maxMajorPV
triggerHardFork mbLowerBound =
ledgerConfigCardano
-- Byron
genesisByron
-- Shelley
genesisShelley
maxMajorPV
-- HFC
byronTriggerHardFork
mbLowerBound
=
HardForkLedgerConfig {
hardForkLedgerConfigShape = shape
, hardForkLedgerConfigPerEra = PerEraLedgerConfig
Expand All @@ -475,8 +482,8 @@ ledgerConfigCardano genesisByron

partialLedgerConfigByron :: PartialLedgerConfig ByronBlock
partialLedgerConfigByron = ByronPartialLedgerConfig {
byronLedgerConfig = genesisByron
, triggerHardFork = triggerHardFork
byronLedgerConfig = genesisByron
, byronTriggerHardFork = byronTriggerHardFork
}

-- Shelley
Expand Down Expand Up @@ -504,13 +511,16 @@ mkPartialLedgerConfigShelley ::
-> MaxMajorProtVer
-> PartialLedgerConfig (ShelleyBlock (ShelleyEra c))
mkPartialLedgerConfigShelley genesisShelley maxMajorPV =
ShelleyPartialLedgerConfig $
Shelley.mkShelleyLedgerConfig
genesisShelley
-- 'completeLedgerConfig' will replace the 'History.dummyEpochInfo' in
-- the partial ledger config with the correct one.
History.dummyEpochInfo
maxMajorPV
ShelleyPartialLedgerConfig {
shelleyLedgerConfig =
Shelley.mkShelleyLedgerConfig
genesisShelley
-- 'completeLedgerConfig' will replace the 'History.dummyEpochInfo'
-- in the partial ledger config with the correct one.
History.dummyEpochInfo
maxMajorPV
, shelleyTriggerHardFork = TriggerHardForkNever -- TODO
}

{-------------------------------------------------------------------------------
Helpers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ type ShelleyBlockHFC era = HardForkBlock '[ShelleyBlock era]
instance TPraosCrypto era => NoHardForks (ShelleyBlock era) where
getEraParams cfg = shelleyLedgerEraParams (configLedger cfg)
toPartialConsensusConfig _ = tpraosParams
toPartialLedgerConfig _ = ShelleyPartialLedgerConfig
toPartialLedgerConfig _ cfg = ShelleyPartialLedgerConfig {
shelleyLedgerConfig = cfg
, shelleyTriggerHardFork = TriggerHardForkNever
}

{-------------------------------------------------------------------------------
SupportedNetworkProtocolVersion instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ library
Ouroboros.Consensus.Shelley.Ledger.Block
Ouroboros.Consensus.Shelley.Ledger.Config
Ouroboros.Consensus.Shelley.Ledger.Forge
Ouroboros.Consensus.Shelley.Ledger.Inspect
Ouroboros.Consensus.Shelley.Ledger.Integrity
Ouroboros.Consensus.Shelley.Ledger.Ledger
Ouroboros.Consensus.Shelley.Ledger.Mempool
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

{-# OPTIONS_GHC -Wno-orphans #-}
module Ouroboros.Consensus.Shelley.Ledger.Inspect (
ProtocolUpdate(..)
, UpdateProposal(..)
, UpdateState(..)
) where

import Control.Monad
import Data.List (sortBy)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Ord (comparing)
import Data.Tuple (swap)
import Data.Void
import Data.Word (Word64)

import Ouroboros.Consensus.Block
import Ouroboros.Consensus.Config
import Ouroboros.Consensus.Ledger.Abstract
import Ouroboros.Consensus.Ledger.Inspect
import Ouroboros.Consensus.Util
import Ouroboros.Consensus.Util.Condense

import Shelley.Spec.Ledger.BaseTypes (strictMaybeToMaybe)
import qualified Shelley.Spec.Ledger.Genesis as SL
import qualified Shelley.Spec.Ledger.Keys as SL
import qualified Shelley.Spec.Ledger.LedgerState as SL
import qualified Shelley.Spec.Ledger.PParams as SL

import Ouroboros.Consensus.Shelley.Ledger.Block
import Ouroboros.Consensus.Shelley.Ledger.Ledger

data ProtocolUpdate era = ProtocolUpdate {
protocolUpdateProposal :: UpdateProposal era
, protocolUpdateState :: UpdateState era
}
deriving (Show, Eq)

-- | Update proposal
--
-- As in Byron, a proposal is a partial map from parameters to their values.
data UpdateProposal era = UpdateProposal {
-- | The protocol parameters changed by this update proposal
--
-- An update is /identified/ by how it updates the protocol parameters.
proposalParams :: SL.PParamsUpdate era

-- | New version (if changed by this proposal)
--
-- The protocol version itself is also considered to be just another
-- parameter, and parameters can change /without/ changing the protocol
-- version, although a convention /could/ be established that the protocol
-- version must change if any of the parameters do; but the specification
-- itself does not mandate this.
--
-- We record the version separately for the convenience of the HFC.
, proposalVersion :: Maybe SL.ProtVer

-- | The 'EpochNo' the proposal becomes active in, if it is adopted
, proposalEpoch :: EpochNo
}
deriving (Show, Eq)

-- | Proposal state
--
-- The update mechanism in Shelley is simpler than it is in Byron. There is no
-- distinction between votes and proposals: to \"vote\" for a proposal one
-- merely submits the exact same proposal. There is also no separate
-- endorsement step. The procedure is as follows:
--
-- 1. During each epoch, a genesis key can submit (via its delegates) zero,
-- one, or many proposals; each submission overrides the previous one.
-- 2. \"Voting\" (submitting of proposals) ends @2 * stabilityWindow@ slots
-- (i.e. @6k/f@) before the end of the epoch. In other words, proposals
-- for the upcoming epoch must be submitted within the first @4k/f@ slots
-- of this one.
-- 3. At the end of an epoch, if the majority of nodes (as determined by the
-- @Quorum@ specification constant, which must be greater than half the
-- nodes) have most recently submitted the same exact proposal, then it is
-- adopted.
-- 4. The next epoch is always started with a clean slate, proposals from the
-- previous epoch that didn't make it are discarded (except for "future
-- proposals that are explicitly marked for future epochs).
data UpdateState era = UpdateState {
-- | The genesis delegates that voted for this proposal
proposalVotes :: [SL.KeyHash 'SL.Genesis era]

-- | Has this proposal reached sufficient votes to be adopted?
, proposalReachedQuorum :: Bool
}
deriving (Show, Eq)

protocolUpdates ::
forall era.
LedgerConfig (ShelleyBlock era)
-> LedgerState (ShelleyBlock era)
-> [ProtocolUpdate era]
protocolUpdates cfg st = [
ProtocolUpdate {
protocolUpdateProposal = UpdateProposal {
proposalParams = proposal
, proposalEpoch = succ currentEpoch
, proposalVersion = strictMaybeToMaybe $
SL._protocolVersion proposal
}
, protocolUpdateState = UpdateState {
proposalVotes = votes
, proposalReachedQuorum = length votes >= fromIntegral quorum
}
}
| (proposal, votes) <- proposalsInv
]
where
proposalsInv :: [(SL.PParamsUpdate era, [SL.KeyHash 'SL.Genesis era])]
proposalsInv =
groupSplit id
. sortBy (comparing fst)
$ map swap (Map.toList proposals)

-- Updated proposed within the proposal window
proposals :: Map (SL.KeyHash 'SL.Genesis era) (SL.PParamsUpdate era)
SL.ProposedPPUpdates proposals =
SL.proposals
. SL._ppups
. SL._utxoState
. SL.esLState
. SL.nesEs
. shelleyLedgerState
$ st

-- A proposal is accepted if the number of votes is equal to or greater
-- than the quorum. The quorum itself must be strictly greater than half
-- the number of genesis keys, but we do not rely on that property here.
quorum :: Word64
quorum = SL.sgUpdateQuorum . shelleyLedgerGenesis $ cfg

-- The proposals in 'SL.proposals' are for the upcoming epoch
-- (we ignore 'futureProposals')
currentEpoch :: EpochNo
currentEpoch = SL.nesEL . shelleyLedgerState $ st

{-------------------------------------------------------------------------------
Inspection
-------------------------------------------------------------------------------}

data ShelleyLedgerUpdate era =
ShelleyUpdatedProtocolUpdates [ProtocolUpdate era]
deriving (Show, Eq)

instance Condense (ShelleyLedgerUpdate era) where
condense = show

instance InspectLedger (ShelleyBlock era) where
type LedgerWarning (ShelleyBlock era) = Void
type LedgerUpdate (ShelleyBlock era) = ShelleyLedgerUpdate era

inspectLedger tlc before after = do
guard $ updatesBefore /= updatesAfter
return $ LedgerUpdate $ ShelleyUpdatedProtocolUpdates updatesAfter
where
updatesBefore, updatesAfter :: [ProtocolUpdate era]
updatesBefore = protocolUpdates (configLedger tlc) before
updatesAfter = protocolUpdates (configLedger tlc) after
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import Ouroboros.Consensus.Config.SupportsNode
import Ouroboros.Consensus.HeaderValidation
import Ouroboros.Consensus.Ledger.Abstract
import Ouroboros.Consensus.Ledger.Extended
import Ouroboros.Consensus.Ledger.Inspect
import Ouroboros.Consensus.Node.ProtocolInfo
import Ouroboros.Consensus.Node.Run
import Ouroboros.Consensus.Protocol.Abstract
Expand All @@ -67,6 +66,7 @@ import qualified Shelley.Spec.Ledger.STS.Tickn as SL
import qualified Shelley.Spec.Ledger.UTxO as SL

import Ouroboros.Consensus.Shelley.Ledger
import Ouroboros.Consensus.Shelley.Ledger.Inspect ()
import Ouroboros.Consensus.Shelley.Ledger.NetworkProtocolVersion ()
import Ouroboros.Consensus.Shelley.Node.Serialisation ()
import Ouroboros.Consensus.Shelley.Protocol
Expand Down Expand Up @@ -328,15 +328,6 @@ protocolClientInfoShelley =
pClientInfoCodecConfig = ShelleyCodecConfig
}

{-------------------------------------------------------------------------------
Inspection
-------------------------------------------------------------------------------}

-- TODO: This should be updated as soon as we start preparing for the
-- hard fork transition out of Shelley.
instance InspectLedger (ShelleyBlock era) where
-- Use defaults

{-------------------------------------------------------------------------------
ConfigSupportsNode instance
-------------------------------------------------------------------------------}
Expand Down
Loading

0 comments on commit af6c334

Please sign in to comment.