cardano-ledger icon indicating copy to clipboard operation
cardano-ledger copied to clipboard

Aternate way to represent Proposals using trees rather than PrevGovActionIds

Open TimSheard opened this issue 2 years ago • 1 comments

issue #3855 proposes one way to represent the tree-like structure of proposals by adding PrevGovActionIds as symbolic links to the parent Proposals (the proposal the the current one MUST follow). This was implemented in PR #3855 .

An alternate way to get the same effect, is to use a tree to represent Proposals. This design was engineered to make minimal change to he current data structures. It just adds an additional 'index' (phantom parameter) to some types, turning them into GADTs. In this framework there are no "hidden" invaiants about what PrevGovActionIds must refer to. These invariants are made explicit in the subtree links. A perfect example of "parse, don't validate" technique.

data GovActionPurpose
  = PParamUpdatePurpose
  | HardForkPurpose
  | CommitteePurpose
  | ConstitutionPurpose
  | MiscPurpose
  | RootPurpose
  deriving (Eq, Show)

data ProposalTree era purpose where
  Root ::
    !(Forest era  'PParamUpdatePurpose) ->
    !(Forest era 'HardForkPurpose) ->
    !(Forest era 'CommitteePurpose) ->
    !(Forest era 'ConstitutionPurpose) ->
    !(Forest era 'MiscPurpose) -> 
    ProposalTree era 'RootPurpose
  Leaf :: ProposalTree era x -- Maybe we don't need this, by having an empty OMap in Node
  Node :: !(GovActionState era purpose) ->
          !(Forest era purpose) ->
          ProposalTree era purpose

newtype Forest era purpose = Forest (OMap.OMap (GovActionId (EraCrypto era)) (ProposalTree era purpose))

data GovActionState era purpose = GovActionState
  { gasId :: !(GovActionId (EraCrypto era))
  , gasCommitteeVotes :: !(Map (Credential 'HotCommitteeRole (EraCrypto era)) Vote)
  , gasDRepVotes :: !(Map (Credential 'DRepRole (EraCrypto era)) Vote)
  , gasStakePoolVotes :: !(Map (KeyHash 'StakePool (EraCrypto era)) Vote)
  , gasDeposit :: !Coin
  , gasReturnAddr :: !(RewardAcnt (EraCrypto era))
  , gasAction :: !(GovAction era purpose)
  , gasProposedIn :: !EpochNo
  , gasExpiresAfter :: !EpochNo
  }
  deriving (Generic)

data GovAction era purpose where
   ParameterChange::  !(PParamsUpdate era) -> GovAction era 'PParamUpdatePurpose
   HardForkInitiation :: !ProtVer -> GovAction era 'HardForkPurpose
   NoConfidence :: GovAction era  'CommitteePurpose
   UpdateCommittee:: 
       -- | Constitutional Committe members to be removed
      !(Set (Credential 'ColdCommitteeRole (EraCrypto era))) -> 
      -- | Constitutional committee members to be added
      !(Map (Credential 'ColdCommitteeRole (EraCrypto era)) EpochNo) ->
      -- | New quorum
      !UnitInterval -> GovAction era 'CommitteePurpose
   NewConstitution :: !(Constitution era) -> GovAction era  ConstitutionPurpose
   TreasuryWithdrawals :: !(Map (RewardAcnt (EraCrypto era)) Coin) -> GovAction era 'MiscPurpose
   InfoAction ::  GovAction era 'MiscPurpose

-- An example of one operation
proposalTreeLookupId ::
  GovActionId (EraCrypto era) ->
  ProposalTree era purpose ->
  Maybe (GovActionState era purpose)
proposalTreeLookupId gai Root{} = Nothing
proposalTreeLookupId gai (Node gas@(GovActionState{gasId = x}) (Forest subtrees)) =
  if gai == x
     then Just gas
     else case OMap.lookup gai subtrees of
            Nothing -> Nothing
            Just proptree -> proposalTreeLookupId gai proptree

TimSheard avatar Nov 29 '23 14:11 TimSheard

There is a slightly more accurate and clearer way being implemented in #3919

lehins avatar Jan 03 '24 13:01 lehins

We now have a pretty solid implementation that was taken care of by #3978

lehins avatar Mar 08 '24 00:03 lehins