Aternate way to represent Proposals using trees rather than PrevGovActionIds
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
There is a slightly more accurate and clearer way being implemented in #3919
We now have a pretty solid implementation that was taken care of by #3978