From fe695b8f7b1c8e35bb933d4af5174b6cbaae50b7 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Tue, 4 Jun 2019 20:38:11 +0200 Subject: [PATCH] Merge PR #4460: Gov Keys and Iterators * refactor gov keys * iterators and renamings * invert queue key order * changelog * fix tests * update alias.go * Apply suggestions from code review Co-Authored-By: Alexander Bezobchuk * rename keys * rename functions * Apply suggestions from code review Co-Authored-By: Alexander Bezobchuk * address Aleks' comments * fix test * address Karoly's comments --- .pending/breaking/sdk/4437-bytes-gov-keys | 1 + .../improvements/sdk/4439-add-governance- | 1 + x/gov/alias.go | 95 ++-- x/gov/deposit.go | 122 +++++ x/gov/endblocker.go | 73 +-- x/gov/endblocker_test.go | 2 +- x/gov/genesis.go | 76 +-- x/gov/keeper.go | 510 ++++++------------ x/gov/keeper_test.go | 4 +- x/gov/proposal.go | 155 ++++++ x/gov/querier.go | 18 +- x/gov/simulation/msgs.go | 11 +- x/gov/tally.go | 12 +- x/gov/types/deposit.go | 7 +- x/gov/types/keys.go | 170 ++++-- x/gov/types/keys_test.go | 70 +++ x/gov/types/vote.go | 7 +- x/gov/vote.go | 73 +++ 18 files changed, 831 insertions(+), 576 deletions(-) create mode 100644 .pending/breaking/sdk/4437-bytes-gov-keys create mode 100644 .pending/improvements/sdk/4439-add-governance- create mode 100644 x/gov/deposit.go create mode 100644 x/gov/proposal.go create mode 100644 x/gov/types/keys_test.go create mode 100644 x/gov/vote.go diff --git a/.pending/breaking/sdk/4437-bytes-gov-keys b/.pending/breaking/sdk/4437-bytes-gov-keys new file mode 100644 index 0000000000..eb9db534c8 --- /dev/null +++ b/.pending/breaking/sdk/4437-bytes-gov-keys @@ -0,0 +1 @@ +#4437 Replace governance module store keys to use `[]byte` instead of `string`. diff --git a/.pending/improvements/sdk/4439-add-governance- b/.pending/improvements/sdk/4439-add-governance- new file mode 100644 index 0000000000..bb7cc018a2 --- /dev/null +++ b/.pending/improvements/sdk/4439-add-governance- @@ -0,0 +1 @@ +#4439 Implement governance module iterators. diff --git a/x/gov/alias.go b/x/gov/alias.go index 84a4372964..766fa7914d 100644 --- a/x/gov/alias.go +++ b/x/gov/alias.go @@ -48,52 +48,61 @@ const ( var ( // functions aliases - RegisterCodec = types.RegisterCodec - RegisterProposalTypeCodec = types.RegisterProposalTypeCodec - ValidateAbstract = types.ValidateAbstract - ErrUnknownProposal = types.ErrUnknownProposal - ErrInactiveProposal = types.ErrInactiveProposal - ErrAlreadyActiveProposal = types.ErrAlreadyActiveProposal - ErrAlreadyFinishedProposal = types.ErrAlreadyFinishedProposal - ErrAddressNotStaked = types.ErrAddressNotStaked - ErrInvalidProposalContent = types.ErrInvalidProposalContent - ErrInvalidProposalType = types.ErrInvalidProposalType - ErrInvalidVote = types.ErrInvalidVote - ErrInvalidGenesis = types.ErrInvalidGenesis - ErrNoProposalHandlerExists = types.ErrNoProposalHandlerExists - KeyProposal = types.KeyProposal - KeyDeposit = types.KeyDeposit - KeyVote = types.KeyVote - KeyDepositsSubspace = types.KeyDepositsSubspace - KeyVotesSubspace = types.KeyVotesSubspace - PrefixActiveProposalQueueTime = types.PrefixActiveProposalQueueTime - KeyActiveProposalQueueProposal = types.KeyActiveProposalQueueProposal - PrefixInactiveProposalQueueTime = types.PrefixInactiveProposalQueueTime - KeyInactiveProposalQueueProposal = types.KeyInactiveProposalQueueProposal - NewMsgSubmitProposal = types.NewMsgSubmitProposal - NewMsgDeposit = types.NewMsgDeposit - NewMsgVote = types.NewMsgVote - NewProposal = types.NewProposal - ProposalStatusFromString = types.ProposalStatusFromString - ValidProposalStatus = types.ValidProposalStatus - NewTallyResult = types.NewTallyResult - NewTallyResultFromMap = types.NewTallyResultFromMap - EmptyTallyResult = types.EmptyTallyResult - NewTextProposal = types.NewTextProposal - NewSoftwareUpgradeProposal = types.NewSoftwareUpgradeProposal - RegisterProposalType = types.RegisterProposalType - ContentFromProposalType = types.ContentFromProposalType - IsValidProposalType = types.IsValidProposalType - ProposalHandler = types.ProposalHandler - VoteOptionFromString = types.VoteOptionFromString - ValidVoteOption = types.ValidVoteOption + RegisterCodec = types.RegisterCodec + RegisterProposalTypeCodec = types.RegisterProposalTypeCodec + ValidateAbstract = types.ValidateAbstract + NewDeposit = types.NewDeposit + ErrUnknownProposal = types.ErrUnknownProposal + ErrInactiveProposal = types.ErrInactiveProposal + ErrAlreadyActiveProposal = types.ErrAlreadyActiveProposal + ErrAlreadyFinishedProposal = types.ErrAlreadyFinishedProposal + ErrAddressNotStaked = types.ErrAddressNotStaked + ErrInvalidProposalContent = types.ErrInvalidProposalContent + ErrInvalidProposalType = types.ErrInvalidProposalType + ErrInvalidVote = types.ErrInvalidVote + ErrInvalidGenesis = types.ErrInvalidGenesis + ErrNoProposalHandlerExists = types.ErrNoProposalHandlerExists + ProposalKey = types.ProposalKey + ActiveProposalByTimeKey = types.ActiveProposalByTimeKey + ActiveProposalQueueKey = types.ActiveProposalQueueKey + InactiveProposalByTimeKey = types.InactiveProposalByTimeKey + InactiveProposalQueueKey = types.InactiveProposalQueueKey + DepositsKey = types.DepositsKey + DepositKey = types.DepositKey + VotesKey = types.VotesKey + VoteKey = types.VoteKey + SplitProposalKey = types.SplitProposalKey + SplitActiveProposalQueueKey = types.SplitActiveProposalQueueKey + SplitInactiveProposalQueueKey = types.SplitInactiveProposalQueueKey + SplitKeyDeposit = types.SplitKeyDeposit + SplitKeyVote = types.SplitKeyVote + NewMsgSubmitProposal = types.NewMsgSubmitProposal + NewMsgDeposit = types.NewMsgDeposit + NewMsgVote = types.NewMsgVote + NewProposal = types.NewProposal + ProposalStatusFromString = types.ProposalStatusFromString + ValidProposalStatus = types.ValidProposalStatus + NewTallyResult = types.NewTallyResult + NewTallyResultFromMap = types.NewTallyResultFromMap + EmptyTallyResult = types.EmptyTallyResult + NewTextProposal = types.NewTextProposal + NewSoftwareUpgradeProposal = types.NewSoftwareUpgradeProposal + RegisterProposalType = types.RegisterProposalType + ContentFromProposalType = types.ContentFromProposalType + IsValidProposalType = types.IsValidProposalType + ProposalHandler = types.ProposalHandler + NewVote = types.NewVote + VoteOptionFromString = types.VoteOptionFromString + ValidVoteOption = types.ValidVoteOption // variable aliases ModuleCdc = types.ModuleCdc - KeyDelimiter = types.KeyDelimiter - KeyNextProposalID = types.KeyNextProposalID - PrefixActiveProposalQueue = types.PrefixActiveProposalQueue - PrefixInactiveProposalQueue = types.PrefixInactiveProposalQueue + ProposalsKeyPrefix = types.ProposalsKeyPrefix + ActiveProposalQueuePrefix = types.ActiveProposalQueuePrefix + InactiveProposalQueuePrefix = types.InactiveProposalQueuePrefix + ProposalIDKey = types.ProposalIDKey + DepositsKeyPrefix = types.DepositsKeyPrefix + VotesKeyPrefix = types.VotesKeyPrefix ) type ( diff --git a/x/gov/deposit.go b/x/gov/deposit.go new file mode 100644 index 0000000000..292d12012d --- /dev/null +++ b/x/gov/deposit.go @@ -0,0 +1,122 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// GetDeposit gets the deposit of a specific depositor on a specific proposal +func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) (deposit Deposit, found bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(types.DepositKey(proposalID, depositorAddr)) + if bz == nil { + return deposit, false + } + + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit) + return deposit, true +} + +func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, deposit Deposit) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit) + store.Set(types.DepositKey(proposalID, depositorAddr), bz) +} + +// AddDeposit adds or updates a deposit of a specific depositor on a specific proposal +// Activates voting period when appropriate +func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { + // Checks to see if proposal exists + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + return ErrUnknownProposal(keeper.codespace, proposalID), false + } + + // Check if proposal is still depositable + if (proposal.Status != StatusDepositPeriod) && (proposal.Status != StatusVotingPeriod) { + return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false + } + + // Send coins from depositor's account to DepositedCoinsAccAddr account + // TODO: Don't use an account for this purpose; it's clumsy and prone to misuse. + err := keeper.ck.SendCoins(ctx, depositorAddr, DepositedCoinsAccAddr, depositAmount) + if err != nil { + return err, false + } + + // Update proposal + proposal.TotalDeposit = proposal.TotalDeposit.Add(depositAmount) + keeper.SetProposal(ctx, proposal) + + // Check if deposit has provided sufficient total funds to transition the proposal into the voting period + activatedVotingPeriod := false + if proposal.Status == StatusDepositPeriod && proposal.TotalDeposit.IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { + keeper.activateVotingPeriod(ctx, proposal) + activatedVotingPeriod = true + } + + // Add or update deposit object + deposit, found := keeper.GetDeposit(ctx, proposalID, depositorAddr) + if found { + deposit.Amount = deposit.Amount.Add(depositAmount) + } else { + deposit = NewDeposit(proposalID, depositorAddr, depositAmount) + } + + keeper.setDeposit(ctx, proposalID, depositorAddr, deposit) + + return nil, activatedVotingPeriod +} + +// GetAllDeposits returns all the deposits from the store +func (keeper Keeper) GetAllDeposits(ctx sdk.Context) (deposits Deposits) { + keeper.IterateAllDeposits(ctx, func(deposit Deposit) bool { + deposits = append(deposits, deposit) + return false + }) + return +} + +// GetDeposits returns all the deposits from a proposal +func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) (deposits Deposits) { + keeper.IterateDeposits(ctx, proposalID, func(deposit Deposit) bool { + deposits = append(deposits, deposit) + return false + }) + return +} + +// GetDepositsIterator gets all the deposits on a specific proposal as an sdk.Iterator +func (keeper Keeper) GetDepositsIterator(ctx sdk.Context, proposalID uint64) sdk.Iterator { + store := ctx.KVStore(keeper.storeKey) + return sdk.KVStorePrefixIterator(store, types.DepositsKey(proposalID)) +} + +// RefundDeposits refunds and deletes all the deposits on a specific proposal +func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + + keeper.IterateDeposits(ctx, proposalID, func(deposit Deposit) bool { + err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, deposit.Amount) + if err != nil { + panic(err) + } + store.Delete(DepositKey(proposalID, deposit.Depositor)) + return false + }) +} + +// DeleteDeposits deletes all the deposits on a specific proposal without refunding them +func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + + keeper.IterateDeposits(ctx, proposalID, func(deposit Deposit) bool { + err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount) + if err != nil { + panic(err) + } + + store.Delete(DepositKey(proposalID, deposit.Depositor)) + return false + }) +} diff --git a/x/gov/endblocker.go b/x/gov/endblocker.go index a8fd67f24a..a3fe512e58 100644 --- a/x/gov/endblocker.go +++ b/x/gov/endblocker.go @@ -7,100 +7,85 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov/tags" ) -// Called every block, process inflation, update validator set +// EndBlocker called every block, process inflation, update validator set func EndBlocker(ctx sdk.Context, keeper Keeper) sdk.Tags { logger := keeper.Logger(ctx) resTags := sdk.NewTags() - inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) - defer inactiveIterator.Close() - for ; inactiveIterator.Valid(); inactiveIterator.Next() { - var proposalID uint64 + // delete inactive proposal from store and its deposits + keeper.IterateInactiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal Proposal) bool { + keeper.DeleteProposal(ctx, proposal.ProposalID) + keeper.DeleteDeposits(ctx, proposal.ProposalID) - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) - inactiveProposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - panic(fmt.Sprintf("proposal %d does not exist", proposalID)) - } - - keeper.DeleteProposal(ctx, proposalID) - keeper.DeleteDeposits(ctx, proposalID) // delete any associated deposits (burned) - - resTags = resTags.AppendTag(tags.ProposalID, fmt.Sprintf("%d", proposalID)) + resTags = resTags.AppendTag(tags.ProposalID, fmt.Sprintf("%d", proposal.ProposalID)) resTags = resTags.AppendTag(tags.ProposalResult, tags.ActionProposalDropped) logger.Info( fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", - inactiveProposal.ProposalID, - inactiveProposal.GetTitle(), + proposal.ProposalID, + proposal.GetTitle(), keeper.GetDepositParams(ctx).MinDeposit, - inactiveProposal.TotalDeposit, + proposal.TotalDeposit, ), ) - } + return false + }) // fetch active proposals whose voting periods have ended (are passed the block time) - activeIterator := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) - defer activeIterator.Close() - for ; activeIterator.Valid(); activeIterator.Next() { - var proposalID uint64 - - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) - activeProposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - panic(fmt.Sprintf("proposal %d does not exist", proposalID)) - } - passes, burnDeposits, tallyResults := tally(ctx, keeper, activeProposal) - + keeper.IterateActiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal Proposal) bool { var tagValue, logMsg string + passes, burnDeposits, tallyResults := tally(ctx, keeper, proposal) + if burnDeposits { - keeper.DeleteDeposits(ctx, activeProposal.ProposalID) + keeper.DeleteDeposits(ctx, proposal.ProposalID) } else { - keeper.RefundDeposits(ctx, activeProposal.ProposalID) + keeper.RefundDeposits(ctx, proposal.ProposalID) } if passes { - handler := keeper.router.GetRoute(activeProposal.ProposalRoute()) + handler := keeper.router.GetRoute(proposal.ProposalRoute()) cacheCtx, writeCache := ctx.CacheContext() // The proposal handler may execute state mutating logic depending // on the proposal content. If the handler fails, no state mutation // is written and the error message is logged. - err := handler(cacheCtx, activeProposal.Content) + err := handler(cacheCtx, proposal.Content) if err == nil { - activeProposal.Status = StatusPassed + proposal.Status = StatusPassed tagValue = tags.ActionProposalPassed logMsg = "passed" // write state to the underlying multi-store writeCache() } else { - activeProposal.Status = StatusFailed + proposal.Status = StatusFailed tagValue = tags.ActionProposalFailed logMsg = fmt.Sprintf("passed, but failed on execution: %s", err.ABCILog()) } } else { - activeProposal.Status = StatusRejected + proposal.Status = StatusRejected tagValue = tags.ActionProposalRejected logMsg = "rejected" } - activeProposal.FinalTallyResult = tallyResults + proposal.FinalTallyResult = tallyResults - keeper.SetProposal(ctx, activeProposal) - keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.VotingEndTime, activeProposal.ProposalID) + keeper.SetProposal(ctx, proposal) + keeper.RemoveFromActiveProposalQueue(ctx, proposal.ProposalID, proposal.VotingEndTime) logger.Info( fmt.Sprintf( "proposal %d (%s) tallied; result: %s", - activeProposal.ProposalID, activeProposal.GetTitle(), logMsg, + proposal.ProposalID, proposal.GetTitle(), logMsg, ), ) - resTags = resTags.AppendTag(tags.ProposalID, fmt.Sprintf("%d", proposalID)) + resTags = resTags.AppendTag(tags.ProposalID, fmt.Sprintf("%d", proposal.ProposalID)) resTags = resTags.AppendTag(tags.ProposalResult, tagValue) - } + + return false + }) return resTags } diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index bfc733b265..00898ac114 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -232,7 +232,7 @@ func TestTickPassedVotingPeriod(t *testing.T) { proposal, ok := input.keeper.GetProposal(ctx, activeProposalID) require.True(t, ok) require.Equal(t, StatusVotingPeriod, proposal.Status) - depositsIterator := input.keeper.GetDeposits(ctx, proposalID) + depositsIterator := input.keeper.GetDepositsIterator(ctx, proposalID) require.True(t, depositsIterator.Valid()) depositsIterator.Close() activeQueue.Close() diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 841c125bd6..7818b795ba 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -16,27 +16,16 @@ const ( // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - StartingProposalID uint64 `json:"starting_proposal_id"` - Deposits []DepositWithMetadata `json:"deposits"` - Votes []VoteWithMetadata `json:"votes"` - Proposals []Proposal `json:"proposals"` - DepositParams DepositParams `json:"deposit_params"` - VotingParams VotingParams `json:"voting_params"` - TallyParams TallyParams `json:"tally_params"` -} - -// DepositWithMetadata (just for genesis) -type DepositWithMetadata struct { - ProposalID uint64 `json:"proposal_id"` - Deposit Deposit `json:"deposit"` -} - -// VoteWithMetadata (just for genesis) -type VoteWithMetadata struct { - ProposalID uint64 `json:"proposal_id"` - Vote Vote `json:"vote"` + StartingProposalID uint64 `json:"starting_proposal_id"` + Deposits Deposits `json:"deposits"` + Votes Votes `json:"votes"` + Proposals []Proposal `json:"proposals"` + DepositParams DepositParams `json:"deposit_params"` + VotingParams VotingParams `json:"voting_params"` + TallyParams TallyParams `json:"tally_params"` } +// NewGenesisState creates a new genesis state for the governance module func NewGenesisState(startingProposalID uint64, dp DepositParams, vp VotingParams, tp TallyParams) GenesisState { return GenesisState{ StartingProposalID: startingProposalID, @@ -79,7 +68,7 @@ func (data GenesisState) IsEmpty() bool { return data.Equal(emptyGenState) } -// ValidateGenesis +// ValidateGenesis checks if parameters are within valid ranges func ValidateGenesis(data GenesisState) error { threshold := data.TallyParams.Threshold if threshold.IsNegative() || threshold.GT(sdk.OneDec()) { @@ -103,26 +92,23 @@ func ValidateGenesis(data GenesisState) error { // InitGenesis - store genesis parameters func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - err := k.setInitialProposalID(ctx, data.StartingProposalID) - if err != nil { - // TODO: Handle this with #870 - panic(err) - } + + k.setProposalID(ctx, data.StartingProposalID) k.setDepositParams(ctx, data.DepositParams) k.setVotingParams(ctx, data.VotingParams) k.setTallyParams(ctx, data.TallyParams) for _, deposit := range data.Deposits { - k.setDeposit(ctx, deposit.ProposalID, deposit.Deposit.Depositor, deposit.Deposit) + k.setDeposit(ctx, deposit.ProposalID, deposit.Depositor, deposit) } for _, vote := range data.Votes { - k.setVote(ctx, vote.ProposalID, vote.Vote.Voter, vote.Vote) + k.setVote(ctx, vote.ProposalID, vote.Voter, vote) } for _, proposal := range data.Proposals { switch proposal.Status { case StatusDepositPeriod: - k.InsertInactiveProposalQueue(ctx, proposal.DepositEndTime, proposal.ProposalID) + k.InsertInactiveProposalQueue(ctx, proposal.ProposalID, proposal.DepositEndTime) case StatusVotingPeriod: - k.InsertActiveProposalQueue(ctx, proposal.VotingEndTime, proposal.ProposalID) + k.InsertActiveProposalQueue(ctx, proposal.ProposalID, proposal.VotingEndTime) } k.SetProposal(ctx, proposal) } @@ -130,35 +116,27 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // ExportGenesis - output genesis parameters func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { - startingProposalID, _ := k.peekCurrentProposalID(ctx) + startingProposalID, _ := k.GetProposalID(ctx) depositParams := k.GetDepositParams(ctx) votingParams := k.GetVotingParams(ctx) tallyParams := k.GetTallyParams(ctx) - var deposits []DepositWithMetadata - var votes []VoteWithMetadata + proposals := k.GetProposalsFiltered(ctx, nil, nil, StatusNil, 0) + + var proposalsDeposits Deposits + var proposalsVotes Votes for _, proposal := range proposals { - proposalID := proposal.ProposalID - depositsIterator := k.GetDeposits(ctx, proposalID) - defer depositsIterator.Close() - for ; depositsIterator.Valid(); depositsIterator.Next() { - var deposit Deposit - k.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) - deposits = append(deposits, DepositWithMetadata{proposalID, deposit}) - } - votesIterator := k.GetVotes(ctx, proposalID) - defer votesIterator.Close() - for ; votesIterator.Valid(); votesIterator.Next() { - var vote Vote - k.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) - votes = append(votes, VoteWithMetadata{proposalID, vote}) - } + deposits := k.GetDeposits(ctx, proposal.ProposalID) + proposalsDeposits = append(proposalsDeposits, deposits...) + + votes := k.GetVotes(ctx, proposal.ProposalID) + proposalsVotes = append(proposalsVotes, votes...) } return GenesisState{ StartingProposalID: startingProposalID, - Deposits: deposits, - Votes: votes, + Deposits: proposalsDeposits, + Votes: proposalsVotes, Proposals: proposals, DepositParams: depositParams, VotingParams: votingParams, diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 79786cc0ba..0b48d942ed 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -1,10 +1,12 @@ package gov import ( + "fmt" "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/cosmos-sdk/x/params" "github.com/tendermint/tendermint/crypto" @@ -78,173 +80,6 @@ func NewKeeper( // Logger returns a module-specific logger. func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/gov") } -// Proposals -func (keeper Keeper) SubmitProposal(ctx sdk.Context, content Content) (Proposal, sdk.Error) { - if !keeper.router.HasRoute(content.ProposalRoute()) { - return Proposal{}, ErrNoProposalHandlerExists(keeper.codespace, content) - } - - // Execute the proposal content in a cache-wrapped context to validate the - // actual parameter changes before the proposal proceeds through the - // governance process. State is not persisted. - cacheCtx, _ := ctx.CacheContext() - handler := keeper.router.GetRoute(content.ProposalRoute()) - if err := handler(cacheCtx, content); err != nil { - return Proposal{}, ErrInvalidProposalContent(keeper.codespace, err.Result().Log) - } - - proposalID, err := keeper.getNewProposalID(ctx) - if err != nil { - return Proposal{}, err - } - - submitTime := ctx.BlockHeader().Time - depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod - - proposal := NewProposal(content, proposalID, submitTime, submitTime.Add(depositPeriod)) - - keeper.SetProposal(ctx, proposal) - keeper.InsertInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID) - - return proposal, nil -} - -// Get Proposal from store by ProposalID -func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal Proposal, ok bool) { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyProposal(proposalID)) - if bz == nil { - return - } - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal) - return proposal, true -} - -// Implements sdk.AccountKeeper. -func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { - store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) - store.Set(KeyProposal(proposal.ProposalID), bz) -} - -// Implements sdk.AccountKeeper. -func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - proposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - panic("DeleteProposal cannot fail to GetProposal") - } - keeper.RemoveFromInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID) - keeper.RemoveFromActiveProposalQueue(ctx, proposal.VotingEndTime, proposalID) - store.Delete(KeyProposal(proposalID)) -} - -// Get Proposal from store by ProposalID -// voterAddr will filter proposals by whether or not that address has voted on them -// depositorAddr will filter proposals by whether or not that address has deposited to them -// status will filter proposals by status -// numLatest will fetch a specified number of the most recent proposals, or 0 for all proposals -func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal { - - maxProposalID, err := keeper.peekCurrentProposalID(ctx) - if err != nil { - return nil - } - - matchingProposals := []Proposal{} - - if numLatest == 0 { - numLatest = maxProposalID - } - - for proposalID := maxProposalID - numLatest; proposalID < maxProposalID; proposalID++ { - if voterAddr != nil && len(voterAddr) != 0 { - _, found := keeper.GetVote(ctx, proposalID, voterAddr) - if !found { - continue - } - } - - if depositorAddr != nil && len(depositorAddr) != 0 { - _, found := keeper.GetDeposit(ctx, proposalID, depositorAddr) - if !found { - continue - } - } - - proposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - continue - } - - if ValidProposalStatus(status) { - if proposal.Status != status { - continue - } - } - - matchingProposals = append(matchingProposals, proposal) - } - return matchingProposals -} - -// Set the initial proposal ID -func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sdk.Error { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyNextProposalID) - if bz != nil { - return ErrInvalidGenesis(keeper.codespace, "initial proposal ID already set") - } - bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) - store.Set(KeyNextProposalID, bz) - return nil -} - -// Get the last used proposal ID -func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID uint64) { - proposalID, err := keeper.peekCurrentProposalID(ctx) - if err != nil { - return 0 - } - proposalID-- - return -} - -// Gets the next available ProposalID and increments it -func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyNextProposalID) - if bz == nil { - return 0, ErrInvalidGenesis(keeper.codespace, "initial proposal ID never set") - } - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) - bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1) - store.Set(KeyNextProposalID, bz) - return proposalID, nil -} - -// Peeks the next available ProposalID without incrementing it -func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyNextProposalID) - if bz == nil { - return 0, ErrInvalidGenesis(keeper.codespace, "initial proposal ID never set") - } - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) - return proposalID, nil -} - -func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { - proposal.VotingStartTime = ctx.BlockHeader().Time - votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod - proposal.VotingEndTime = proposal.VotingStartTime.Add(votingPeriod) - proposal.Status = StatusVotingPeriod - keeper.SetProposal(ctx, proposal) - - keeper.RemoveFromInactiveProposalQueue(ctx, proposal.DepositEndTime, proposal.ProposalID) - keeper.InsertActiveProposalQueue(ctx, proposal.VotingEndTime, proposal.ProposalID) -} - // Params // Returns the current DepositParams from the global param store @@ -280,205 +115,160 @@ func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams) } -// Votes - -// Adds a vote on a specific proposal -func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { - proposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - return ErrUnknownProposal(keeper.codespace, proposalID) - } - if proposal.Status != StatusVotingPeriod { - return ErrInactiveProposal(keeper.codespace, proposalID) - } - - if !ValidVoteOption(option) { - return ErrInvalidVote(keeper.codespace, option) - } - - vote := Vote{ - ProposalID: proposalID, - Voter: voterAddr, - Option: option, - } - keeper.setVote(ctx, proposalID, voterAddr, vote) - - return nil -} - -// Gets the vote of a specific voter on a specific proposal -func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (Vote, bool) { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyVote(proposalID, voterAddr)) - if bz == nil { - return Vote{}, false - } - var vote Vote - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) - return vote, true -} - -func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) { - store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) - store.Set(KeyVote(proposalID, voterAddr), bz) -} - -// Gets all the votes on a specific proposal -func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) sdk.Iterator { - store := ctx.KVStore(keeper.storeKey) - return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID)) -} - -func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { - store := ctx.KVStore(keeper.storeKey) - store.Delete(KeyVote(proposalID, voterAddr)) -} - -// Deposits - -// Gets the deposit of a specific depositor on a specific proposal -func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) (Deposit, bool) { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyDeposit(proposalID, depositorAddr)) - if bz == nil { - return Deposit{}, false - } - var deposit Deposit - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit) - return deposit, true -} - -func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, deposit Deposit) { - store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit) - store.Set(KeyDeposit(proposalID, depositorAddr), bz) -} - -// Adds or updates a deposit of a specific depositor on a specific proposal -// Activates voting period when appropriate -func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { - // Checks to see if proposal exists - proposal, ok := keeper.GetProposal(ctx, proposalID) - if !ok { - return ErrUnknownProposal(keeper.codespace, proposalID), false - } - - // Check if proposal is still depositable - if (proposal.Status != StatusDepositPeriod) && (proposal.Status != StatusVotingPeriod) { - return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false - } - - // Send coins from depositor's account to DepositedCoinsAccAddr account - // TODO: Don't use an account for this purpose; it's clumsy and prone to misuse. - err := keeper.ck.SendCoins(ctx, depositorAddr, DepositedCoinsAccAddr, depositAmount) - if err != nil { - return err, false - } - - // Update proposal - proposal.TotalDeposit = proposal.TotalDeposit.Add(depositAmount) - keeper.SetProposal(ctx, proposal) - - // Check if deposit has provided sufficient total funds to transition the proposal into the voting period - activatedVotingPeriod := false - if proposal.Status == StatusDepositPeriod && proposal.TotalDeposit.IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) { - keeper.activateVotingPeriod(ctx, proposal) - activatedVotingPeriod = true - } - - // Add or update deposit object - currDeposit, found := keeper.GetDeposit(ctx, proposalID, depositorAddr) - if !found { - newDeposit := Deposit{depositorAddr, proposalID, depositAmount} - keeper.setDeposit(ctx, proposalID, depositorAddr, newDeposit) - } else { - currDeposit.Amount = currDeposit.Amount.Add(depositAmount) - keeper.setDeposit(ctx, proposalID, depositorAddr, currDeposit) - } - - return nil, activatedVotingPeriod -} - -// Gets all the deposits on a specific proposal as an sdk.Iterator -func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) sdk.Iterator { - store := ctx.KVStore(keeper.storeKey) - return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID)) -} - -// Refunds and deletes all the deposits on a specific proposal -func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - depositsIterator := keeper.GetDeposits(ctx, proposalID) - defer depositsIterator.Close() - for ; depositsIterator.Valid(); depositsIterator.Next() { - deposit := &Deposit{} - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - - err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, deposit.Amount) - if err != nil { - panic("should not happen") - } - - store.Delete(depositsIterator.Key()) - } -} - -// Deletes all the deposits on a specific proposal without refunding them -func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - depositsIterator := keeper.GetDeposits(ctx, proposalID) - defer depositsIterator.Close() - for ; depositsIterator.Valid(); depositsIterator.Next() { - deposit := &Deposit{} - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - - // TODO: Find a way to do this without using accounts. - err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount) - if err != nil { - panic("should not happen") - } - - store.Delete(depositsIterator.Key()) - } -} - // ProposalQueues -// Returns an iterator for all the proposals in the Active Queue that expire by endTime +// InsertActiveProposalQueue inserts a ProposalID into the active proposal queue at endTime +func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, proposalID uint64, endTime time.Time) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(types.ActiveProposalQueueKey(proposalID, endTime), bz) +} + +// RemoveFromActiveProposalQueue removes a proposalID from the Active Proposal Queue +func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, proposalID uint64, endTime time.Time) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(types.ActiveProposalQueueKey(proposalID, endTime)) +} + +// InsertInactiveProposalQueue Inserts a ProposalID into the inactive proposal queue at endTime +func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, proposalID uint64, endTime time.Time) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(types.InactiveProposalQueueKey(proposalID, endTime), bz) +} + +// RemoveFromInactiveProposalQueue removes a proposalID from the Inactive Proposal Queue +func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, proposalID uint64, endTime time.Time) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(types.InactiveProposalQueueKey(proposalID, endTime)) +} + +// Iterators + +// IterateProposals iterates over the all the proposals and performs a callback function +func (keeper Keeper) IterateProposals(ctx sdk.Context, cb func(proposal types.Proposal) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.ProposalsKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var proposal types.Proposal + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &proposal) + + if cb(proposal) { + break + } + } +} + +// IterateActiveProposalsQueue iterates over the proposals in the active proposal queue +// and performs a callback function +func (keeper Keeper) IterateActiveProposalsQueue(ctx sdk.Context, endTime time.Time, cb func(proposal types.Proposal) (stop bool)) { + iterator := keeper.ActiveProposalQueueIterator(ctx, endTime) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + proposalID, _ := types.SplitActiveProposalQueueKey(iterator.Key()) + proposal, found := keeper.GetProposal(ctx, proposalID) + if !found { + panic(fmt.Sprintf("proposal %d does not exist", proposalID)) + } + + if cb(proposal) { + break + } + } +} + +// IterateInactiveProposalsQueue iterates over the proposals in the inactive proposal queue +// and performs a callback function +func (keeper Keeper) IterateInactiveProposalsQueue(ctx sdk.Context, endTime time.Time, cb func(proposal types.Proposal) (stop bool)) { + iterator := keeper.InactiveProposalQueueIterator(ctx, endTime) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + proposalID, _ := types.SplitInactiveProposalQueueKey(iterator.Key()) + proposal, found := keeper.GetProposal(ctx, proposalID) + if !found { + panic(fmt.Sprintf("proposal %d does not exist", proposalID)) + } + + if cb(proposal) { + break + } + } +} + +// IterateAllDeposits iterates over the all the stored deposits and performs a callback function +func (keeper Keeper) IterateAllDeposits(ctx sdk.Context, cb func(deposit types.Deposit) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.DepositsKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var deposit types.Deposit + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit) + + if cb(deposit) { + break + } + } +} + +// IterateDeposits iterates over the all the proposals deposits and performs a callback function +func (keeper Keeper) IterateDeposits(ctx sdk.Context, proposalID uint64, cb func(deposit types.Deposit) (stop bool)) { + iterator := keeper.GetDepositsIterator(ctx, proposalID) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var deposit types.Deposit + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit) + + if cb(deposit) { + break + } + } +} + +// IterateAllVotes iterates over the all the stored votes and performs a callback function +func (keeper Keeper) IterateAllVotes(ctx sdk.Context, cb func(vote types.Vote) (stop bool)) { + store := ctx.KVStore(keeper.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.VotesKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var vote types.Vote + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) + + if cb(vote) { + break + } + } +} + +// IterateVotes iterates over the all the proposals votes and performs a callback function +func (keeper Keeper) IterateVotes(ctx sdk.Context, proposalID uint64, cb func(vote types.Vote) (stop bool)) { + iterator := keeper.GetVotesIterator(ctx, proposalID) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var vote types.Vote + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &vote) + + if cb(vote) { + break + } + } +} + +// ActiveProposalQueueIterator returns an sdk.Iterator for all the proposals in the Active Queue that expire by endTime func (keeper Keeper) ActiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(PrefixActiveProposalQueueTime(endTime))) + return store.Iterator(ActiveProposalQueuePrefix, sdk.PrefixEndBytes(types.ActiveProposalByTimeKey(endTime))) } -// Inserts a ProposalID into the active proposal queue at endTime -func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) - store.Set(KeyActiveProposalQueueProposal(endTime, proposalID), bz) -} - -// removes a proposalID from the Active Proposal Queue -func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - store.Delete(KeyActiveProposalQueueProposal(endTime, proposalID)) -} - -// Returns an iterator for all the proposals in the Inactive Queue that expire by endTime +// InactiveProposalQueueIterator returns an sdk.Iterator for all the proposals in the Inactive Queue that expire by endTime func (keeper Keeper) InactiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { store := ctx.KVStore(keeper.storeKey) - return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(PrefixInactiveProposalQueueTime(endTime))) -} - -// Inserts a ProposalID into the inactive proposal queue at endTime -func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) - store.Set(KeyInactiveProposalQueueProposal(endTime, proposalID), bz) -} - -// removes a proposalID from the Inactive Proposal Queue -func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { - store := ctx.KVStore(keeper.storeKey) - store.Delete(KeyInactiveProposalQueueProposal(endTime, proposalID)) + return store.Iterator(InactiveProposalQueuePrefix, sdk.PrefixEndBytes(types.InactiveProposalByTimeKey(endTime))) } diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 7900bfcd8e..29ddfde903 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -157,7 +157,7 @@ func TestDeposits(t *testing.T) { require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) // Test deposit iterator - depositsIterator := input.keeper.GetDeposits(ctx, proposalID) + depositsIterator := input.keeper.GetDepositsIterator(ctx, proposalID) require.True(t, depositsIterator.Valid()) input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) require.Equal(t, input.addrs[0], deposit.Depositor) @@ -224,7 +224,7 @@ func TestVotes(t *testing.T) { require.Equal(t, OptionNoWithVeto, vote.Option) // Test vote iterator - votesIterator := input.keeper.GetVotes(ctx, proposalID) + votesIterator := input.keeper.GetVotesIterator(ctx, proposalID) require.True(t, votesIterator.Valid()) input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) require.True(t, votesIterator.Valid()) diff --git a/x/gov/proposal.go b/x/gov/proposal.go new file mode 100644 index 0000000000..cdba45bbf3 --- /dev/null +++ b/x/gov/proposal.go @@ -0,0 +1,155 @@ +package gov + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// SubmitProposal create new proposal given a content +func (keeper Keeper) SubmitProposal(ctx sdk.Context, content Content) (Proposal, sdk.Error) { + if !keeper.router.HasRoute(content.ProposalRoute()) { + return Proposal{}, ErrNoProposalHandlerExists(keeper.codespace, content) + } + + // Execute the proposal content in a cache-wrapped context to validate the + // actual parameter changes before the proposal proceeds through the + // governance process. State is not persisted. + cacheCtx, _ := ctx.CacheContext() + handler := keeper.router.GetRoute(content.ProposalRoute()) + if err := handler(cacheCtx, content); err != nil { + return Proposal{}, ErrInvalidProposalContent(keeper.codespace, err.Result().Log) + } + + proposalID, err := keeper.GetProposalID(ctx) + if err != nil { + return Proposal{}, err + } + + submitTime := ctx.BlockHeader().Time + depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod + + proposal := NewProposal(content, proposalID, submitTime, submitTime.Add(depositPeriod)) + + keeper.SetProposal(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime) + keeper.setProposalID(ctx, proposalID+1) + + return proposal, nil +} + +// GetProposal get Proposal from store by ProposalID +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal Proposal, ok bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(ProposalKey(proposalID)) + if bz == nil { + return + } + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal) + return proposal, true +} + +// SetProposal set a proposal to store +func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) + store.Set(ProposalKey(proposal.ProposalID), bz) +} + +// DeleteProposal deletes a proposal from store +func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + panic(fmt.Sprintf("couldn't find proposal with id#%d", proposalID)) + } + keeper.RemoveFromInactiveProposalQueue(ctx, proposalID, proposal.DepositEndTime) + keeper.RemoveFromActiveProposalQueue(ctx, proposalID, proposal.VotingEndTime) + store.Delete(ProposalKey(proposalID)) +} + +// GetProposals returns all the proposals from store +func (keeper Keeper) GetProposals(ctx sdk.Context) (proposals Proposals) { + keeper.IterateProposals(ctx, func(proposal types.Proposal) bool { + proposals = append(proposals, proposal) + return false + }) + return +} + +// GetProposalsFiltered get Proposals from store by ProposalID +// voterAddr will filter proposals by whether or not that address has voted on them +// depositorAddr will filter proposals by whether or not that address has deposited to them +// status will filter proposals by status +// numLatest will fetch a specified number of the most recent proposals, or 0 for all proposals +func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal { + + maxProposalID, err := keeper.GetProposalID(ctx) + if err != nil { + return []Proposal{} + } + + matchingProposals := []Proposal{} + + if numLatest == 0 { + numLatest = maxProposalID + } + + for proposalID := maxProposalID - numLatest; proposalID < maxProposalID; proposalID++ { + if voterAddr != nil && len(voterAddr) != 0 { + _, found := keeper.GetVote(ctx, proposalID, voterAddr) + if !found { + continue + } + } + + if depositorAddr != nil && len(depositorAddr) != 0 { + _, found := keeper.GetDeposit(ctx, proposalID, depositorAddr) + if !found { + continue + } + } + + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + continue + } + + if ValidProposalStatus(status) && proposal.Status != status { + continue + } + + matchingProposals = append(matchingProposals, proposal) + } + return matchingProposals +} + +// GetProposalID gets the highest proposal ID +func (keeper Keeper) GetProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(ProposalIDKey) + if bz == nil { + return 0, ErrInvalidGenesis(keeper.codespace, "initial proposal ID hasn't been set") + } + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + return proposalID, nil +} + +// Set the proposal ID +func (keeper Keeper) setProposalID(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(ProposalIDKey, bz) +} + +func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { + proposal.VotingStartTime = ctx.BlockHeader().Time + votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod + proposal.VotingEndTime = proposal.VotingStartTime.Add(votingPeriod) + proposal.Status = StatusVotingPeriod + keeper.SetProposal(ctx, proposal) + + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.ProposalID, proposal.DepositEndTime) + keeper.InsertActiveProposalQueue(ctx, proposal.ProposalID, proposal.VotingEndTime) +} diff --git a/x/gov/querier.go b/x/gov/querier.go index 87202800da..0aff0a0dd7 100644 --- a/x/gov/querier.go +++ b/x/gov/querier.go @@ -179,14 +179,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - var deposits []Deposit - depositsIterator := keeper.GetDeposits(ctx, params.ProposalID) - defer depositsIterator.Close() - for ; depositsIterator.Valid(); depositsIterator.Next() { - deposit := Deposit{} - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) - deposits = append(deposits, deposit) - } + deposits := keeper.GetDeposits(ctx, params.ProposalID) bz, err := codec.MarshalJSONIndent(keeper.cdc, deposits) if err != nil { @@ -237,14 +230,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - var votes []Vote - votesIterator := keeper.GetVotes(ctx, params.ProposalID) - defer votesIterator.Close() - for ; votesIterator.Valid(); votesIterator.Next() { - vote := Vote{} - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) - votes = append(votes, vote) - } + votes := keeper.GetVotes(ctx, params.ProposalID) bz, err := codec.MarshalJSONIndent(keeper.cdc, votes) if err != nil { diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 80fa851ded..7053ec6552 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -64,7 +64,12 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, contentSim Con return opMsg, nil, nil } - proposalID := k.GetLastProposalID(ctx) + proposalID, err := k.GetProposalID(ctx) + if err != nil { + return simulation.NoOpMsg(), nil, err + } + + proposalID = uint64(math.Max(float64(proposalID)-1, 0)) // 2) Schedule operations for votes // 2.1) first pick a number of people to vote. @@ -192,7 +197,9 @@ func randomDeposit(r *rand.Rand) sdk.Coins { // Pick a random proposal ID func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID uint64, ok bool) { - lastProposalID := k.GetLastProposalID(ctx) + lastProposalID, _ := k.GetProposalID(ctx) + lastProposalID = uint64(math.Max(float64(lastProposalID)-1, 0)) + if lastProposalID < 1 || lastProposalID == (2<<63-1) { return 0, false } diff --git a/x/gov/tally.go b/x/gov/tally.go index 09b5cc71e5..a54bc3d0e3 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -2,6 +2,7 @@ package gov import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" ) // validatorGovInfo used for tallying @@ -49,13 +50,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn return false }) - // iterate over all the votes - votesIterator := keeper.GetVotes(ctx, proposal.ProposalID) - defer votesIterator.Close() - for ; votesIterator.Valid(); votesIterator.Next() { - vote := &Vote{} - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), vote) - + keeper.IterateVotes(ctx, proposal.ProposalID, func(vote types.Vote) bool { // if validator, just record it in the map // if delegator tally voting power valAddrStr := sdk.ValAddress(vote.Voter).String() @@ -83,7 +78,8 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, burn } keeper.deleteVote(ctx, vote.ProposalID, vote.Voter) - } + return false + }) // iterate over the validators again to tally their voting power for _, val := range currValidators { diff --git a/x/gov/types/deposit.go b/x/gov/types/deposit.go index 3d66e2a22d..5afe443f80 100644 --- a/x/gov/types/deposit.go +++ b/x/gov/types/deposit.go @@ -8,11 +8,16 @@ import ( // Deposit type Deposit struct { - Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal + Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor Amount sdk.Coins `json:"amount"` // Deposit amount } +// NewDeposit creates a new Deposit instance +func NewDeposit(proposalID uint64, depositor sdk.AccAddress, amount sdk.Coins) Deposit { + return Deposit{proposalID, depositor, amount} +} + func (d Deposit) String() string { return fmt.Sprintf("deposit by %s on Proposal %d is for the amount %s", d.Depositor, d.ProposalID, d.Amount) diff --git a/x/gov/types/keys.go b/x/gov/types/keys.go index b73c9ea5ff..41f81c2fbf 100644 --- a/x/gov/types/keys.go +++ b/x/gov/types/keys.go @@ -1,7 +1,7 @@ package types import ( - "bytes" + "encoding/binary" "fmt" "time" @@ -9,7 +9,7 @@ import ( ) const ( - // ModuleKey is the name of the module + // ModuleName is the name of the module ModuleName = "gov" // StoreKey is the store key string for gov @@ -21,74 +21,146 @@ const ( // QuerierRoute is the querier route for gov QuerierRoute = ModuleName - // Parameter store default namestore + // DefaultParamspace default name for parameter store DefaultParamspace = ModuleName ) -// Key for getting a the next available proposalID from the store +// Keys for governance store +// Items are stored with the following key: values +// +// - 0x00: Proposal +// +// - 0x01: activeProposalID +// +// - 0x02: inactiveProposalID +// +// - 0x03: nextProposalID +// +// - 0x10: Deposit +// +// - 0x20: Voter var ( - KeyDelimiter = []byte(":") + ProposalsKeyPrefix = []byte{0x00} + ActiveProposalQueuePrefix = []byte{0x01} + InactiveProposalQueuePrefix = []byte{0x02} + ProposalIDKey = []byte{0x03} - KeyNextProposalID = []byte("newProposalID") - PrefixActiveProposalQueue = []byte("activeProposalQueue") - PrefixInactiveProposalQueue = []byte("inactiveProposalQueue") + DepositsKeyPrefix = []byte{0x10} + + VotesKeyPrefix = []byte{0x20} ) -// Key for getting a specific proposal from the store -func KeyProposal(proposalID uint64) []byte { - return []byte(fmt.Sprintf("proposals:%d", proposalID)) +var lenTime = len(sdk.FormatTimeBytes(time.Now())) + +// ProposalKey gets a specific proposal from the store +func ProposalKey(proposalID uint64) []byte { + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, proposalID) + return append(ProposalsKeyPrefix, bz...) } -// Key for getting a specific deposit from the store -func KeyDeposit(proposalID uint64, depositorAddr sdk.AccAddress) []byte { - return []byte(fmt.Sprintf("deposits:%d:%d", proposalID, depositorAddr)) +// ActiveProposalByTimeKey gets the active proposal queue key by endTime +func ActiveProposalByTimeKey(endTime time.Time) []byte { + return append(ActiveProposalQueuePrefix, sdk.FormatTimeBytes(endTime)...) } -// Key for getting a specific vote from the store -func KeyVote(proposalID uint64, voterAddr sdk.AccAddress) []byte { - return []byte(fmt.Sprintf("votes:%d:%d", proposalID, voterAddr)) +// ActiveProposalQueueKey returns the key for a proposalID in the activeProposalQueue +func ActiveProposalQueueKey(proposalID uint64, endTime time.Time) []byte { + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, proposalID) + + return append(ActiveProposalByTimeKey(endTime), bz...) } -// Key for getting all deposits on a proposal from the store -func KeyDepositsSubspace(proposalID uint64) []byte { - return []byte(fmt.Sprintf("deposits:%d:", proposalID)) +// InactiveProposalByTimeKey gets the inactive proposal queue key by endTime +func InactiveProposalByTimeKey(endTime time.Time) []byte { + return append(InactiveProposalQueuePrefix, sdk.FormatTimeBytes(endTime)...) } -// Key for getting all votes on a proposal from the store -func KeyVotesSubspace(proposalID uint64) []byte { - return []byte(fmt.Sprintf("votes:%d:", proposalID)) +// InactiveProposalQueueKey returns the key for a proposalID in the inactiveProposalQueue +func InactiveProposalQueueKey(proposalID uint64, endTime time.Time) []byte { + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, proposalID) + + return append(InactiveProposalByTimeKey(endTime), bz...) } -// Returns the key for a proposalID in the activeProposalQueue -func PrefixActiveProposalQueueTime(endTime time.Time) []byte { - return bytes.Join([][]byte{ - PrefixActiveProposalQueue, - sdk.FormatTimeBytes(endTime), - }, KeyDelimiter) +// DepositsKey gets the first part of the deposits key based on the proposalID +func DepositsKey(proposalID uint64) []byte { + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, proposalID) + return append(DepositsKeyPrefix, bz...) } -// Returns the key for a proposalID in the activeProposalQueue -func KeyActiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { - return bytes.Join([][]byte{ - PrefixActiveProposalQueue, - sdk.FormatTimeBytes(endTime), - sdk.Uint64ToBigEndian(proposalID), - }, KeyDelimiter) +// DepositKey key of a specific deposit from the store +func DepositKey(proposalID uint64, depositorAddr sdk.AccAddress) []byte { + return append(DepositsKey(proposalID), depositorAddr.Bytes()...) } -// Returns the key for a proposalID in the activeProposalQueue -func PrefixInactiveProposalQueueTime(endTime time.Time) []byte { - return bytes.Join([][]byte{ - PrefixInactiveProposalQueue, - sdk.FormatTimeBytes(endTime), - }, KeyDelimiter) +// VotesKey gets the first part of the votes key based on the proposalID +func VotesKey(proposalID uint64) []byte { + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, proposalID) + return append(VotesKeyPrefix, bz...) } -// Returns the key for a proposalID in the activeProposalQueue -func KeyInactiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { - return bytes.Join([][]byte{ - PrefixInactiveProposalQueue, - sdk.FormatTimeBytes(endTime), - sdk.Uint64ToBigEndian(proposalID), - }, KeyDelimiter) +// VoteKey key of a specific vote from the store +func VoteKey(proposalID uint64, voterAddr sdk.AccAddress) []byte { + return append(VotesKey(proposalID), voterAddr.Bytes()...) +} + +// Split keys function; used for iterators + +// SplitProposalKey split the proposal key and returns the proposal id +func SplitProposalKey(key []byte) (proposalID uint64) { + if len(key[1:]) != 8 { + panic(fmt.Sprintf("unexpected key length (%d ≠ 8)", len(key[1:]))) + } + + return binary.LittleEndian.Uint64(key[1:]) +} + +// SplitActiveProposalQueueKey split the active proposal key and returns the proposal id and endTime +func SplitActiveProposalQueueKey(key []byte) (proposalID uint64, endTime time.Time) { + return splitKeyWithTime(key) +} + +// SplitInactiveProposalQueueKey split the inactive proposal key and returns the proposal id and endTime +func SplitInactiveProposalQueueKey(key []byte) (proposalID uint64, endTime time.Time) { + return splitKeyWithTime(key) +} + +// SplitKeyDeposit split the deposits key and returns the proposal id and depositor address +func SplitKeyDeposit(key []byte) (proposalID uint64, depositorAddr sdk.AccAddress) { + return splitKeyWithAddress(key) +} + +// SplitKeyVote split the votes key and returns the proposal id and voter address +func SplitKeyVote(key []byte) (proposalID uint64, voterAddr sdk.AccAddress) { + return splitKeyWithAddress(key) +} + +// private functions + +func splitKeyWithTime(key []byte) (proposalID uint64, endTime time.Time) { + if len(key[1:]) != 8+lenTime { + panic(fmt.Sprintf("unexpected key length (%d ≠ %d)", len(key[1:]), lenTime+8)) + } + + endTime, err := sdk.ParseTimeBytes(key[1 : 1+lenTime]) + if err != nil { + panic(err) + } + proposalID = binary.LittleEndian.Uint64(key[1+lenTime:]) + return +} + +func splitKeyWithAddress(key []byte) (proposalID uint64, addr sdk.AccAddress) { + if len(key[1:]) != 8+sdk.AddrLen { + panic(fmt.Sprintf("unexpected key length (%d ≠ %d)", len(key), 8+sdk.AddrLen)) + } + + proposalID = binary.LittleEndian.Uint64(key[1:9]) + addr = sdk.AccAddress(key[9:]) + return } diff --git a/x/gov/types/keys_test.go b/x/gov/types/keys_test.go new file mode 100644 index 0000000000..93434d457d --- /dev/null +++ b/x/gov/types/keys_test.go @@ -0,0 +1,70 @@ +package types + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +var addr = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + +func TestProposalKeys(t *testing.T) { + // key proposal + key := ProposalKey(1) + proposalID := SplitProposalKey(key) + require.Equal(t, int(proposalID), 1) + + // key active proposal queue + now := time.Now() + key = ActiveProposalQueueKey(3, now) + proposalID, expTime := SplitActiveProposalQueueKey(key) + require.Equal(t, int(proposalID), 3) + require.True(t, now.Equal(expTime)) + + // key inactive proposal queue + key = InactiveProposalQueueKey(3, now) + proposalID, expTime = SplitInactiveProposalQueueKey(key) + require.Equal(t, int(proposalID), 3) + require.True(t, now.Equal(expTime)) + + // invalid key + require.Panics(t, func() { SplitProposalKey([]byte("test")) }) + require.Panics(t, func() { SplitInactiveProposalQueueKey([]byte("test")) }) +} + +func TestDepositKeys(t *testing.T) { + + key := DepositsKey(2) + proposalID := SplitProposalKey(key) + require.Equal(t, int(proposalID), 2) + + key = DepositKey(2, addr) + proposalID, depositorAddr := SplitKeyDeposit(key) + require.Equal(t, int(proposalID), 2) + require.Equal(t, addr, depositorAddr) + + // invalid key + addr2 := sdk.AccAddress("test1") + key = DepositKey(5, addr2) + require.Panics(t, func() { SplitKeyDeposit(key) }) +} + +func TestVoteKeys(t *testing.T) { + + key := VotesKey(2) + proposalID := SplitProposalKey(key) + require.Equal(t, int(proposalID), 2) + + key = VoteKey(2, addr) + proposalID, voterAddr := SplitKeyDeposit(key) + require.Equal(t, int(proposalID), 2) + require.Equal(t, addr, voterAddr) + + // invalid key + addr2 := sdk.AccAddress("test1") + key = VoteKey(5, addr2) + require.Panics(t, func() { SplitKeyVote(key) }) +} diff --git a/x/gov/types/vote.go b/x/gov/types/vote.go index 78087bbbcf..3dbf89a0fb 100644 --- a/x/gov/types/vote.go +++ b/x/gov/types/vote.go @@ -9,11 +9,16 @@ import ( // Vote type Vote struct { - Voter sdk.AccAddress `json:"voter"` // address of the voter ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal + Voter sdk.AccAddress `json:"voter"` // address of the voter Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } +// NewVote creates a new Vote instance +func NewVote(proposalID uint64, voter sdk.AccAddress, option VoteOption) Vote { + return Vote{proposalID, voter, option} +} + func (v Vote) String() string { return fmt.Sprintf("voter %s voted with option %s on proposal %d", v.Voter, v.Option, v.ProposalID) } diff --git a/x/gov/vote.go b/x/gov/vote.go new file mode 100644 index 0000000000..a92e24af4d --- /dev/null +++ b/x/gov/vote.go @@ -0,0 +1,73 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// AddVote Adds a vote on a specific proposal +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { + proposal, ok := keeper.GetProposal(ctx, proposalID) + if !ok { + return ErrUnknownProposal(keeper.codespace, proposalID) + } + if proposal.Status != StatusVotingPeriod { + return ErrInactiveProposal(keeper.codespace, proposalID) + } + + if !ValidVoteOption(option) { + return ErrInvalidVote(keeper.codespace, option) + } + + vote := NewVote(proposalID, voterAddr, option) + keeper.setVote(ctx, proposalID, voterAddr, vote) + + return nil +} + +// GetAllVotes returns all the votes from the store +func (keeper Keeper) GetAllVotes(ctx sdk.Context) (votes Votes) { + keeper.IterateAllVotes(ctx, func(vote Vote) bool { + votes = append(votes, vote) + return false + }) + return +} + +// GetVotes returns all the votes from a proposal +func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) (votes Votes) { + keeper.IterateVotes(ctx, proposalID, func(vote Vote) bool { + votes = append(votes, vote) + return false + }) + return +} + +// GetVote gets the vote from an address on a specific proposal +func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (vote Vote, found bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(types.VoteKey(proposalID, voterAddr)) + if bz == nil { + return vote, false + } + + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) + return vote, true +} + +func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) + store.Set(types.VoteKey(proposalID, voterAddr), bz) +} + +// GetVotesIterator gets all the votes on a specific proposal as an sdk.Iterator +func (keeper Keeper) GetVotesIterator(ctx sdk.Context, proposalID uint64) sdk.Iterator { + store := ctx.KVStore(keeper.storeKey) + return sdk.KVStorePrefixIterator(store, types.VotesKey(proposalID)) +} + +func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(types.VoteKey(proposalID, voterAddr)) +}