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 <alexanderbez@users.noreply.github.com>

* rename keys

* rename functions

* Apply suggestions from code review

Co-Authored-By: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>

* address Aleks' comments

* fix test

* address Karoly's comments
This commit is contained in:
Federico Kunze 2019-06-04 20:38:11 +02:00 committed by Jack Zampolin
parent 3962b3ca23
commit fe695b8f7b
18 changed files with 831 additions and 576 deletions

View File

@ -0,0 +1 @@
#4437 Replace governance module store keys to use `[]byte` instead of `string`.

View File

@ -0,0 +1 @@
#4439 Implement governance module iterators.

View File

@ -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 (

122
x/gov/deposit.go Normal file
View File

@ -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
})
}

View File

@ -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
}

View File

@ -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()

View File

@ -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,

View File

@ -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)))
}

View File

@ -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())

155
x/gov/proposal.go Normal file
View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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<proposalID_Bytes>: Proposal
//
// - 0x01<endTime_Bytes><proposalID_Bytes>: activeProposalID
//
// - 0x02<endTime_Bytes><proposalID_Bytes>: inactiveProposalID
//
// - 0x03: nextProposalID
//
// - 0x10<proposalID_Bytes><depositorAddr_Bytes>: Deposit
//
// - 0x20<proposalID_Bytes><voterAddr_Bytes>: 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
}

70
x/gov/types/keys_test.go Normal file
View File

@ -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) })
}

View File

@ -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)
}

73
x/gov/vote.go Normal file
View File

@ -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))
}