diff --git a/PENDING.md b/PENDING.md index 84a08a5abe26..f1c15e5fd380 100644 --- a/PENDING.md +++ b/PENDING.md @@ -39,6 +39,7 @@ IMPROVEMENTS * Gaia CLI (`gaiacli`) * Gaia + - #2637 [x/gov] Switched inactive and active proposal queues to an iterator based queue * SDK - #2573 [x/distribution] add accum invariance diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index cb25721e891d..de8f7fcd8b1e 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -629,7 +629,7 @@ func TestSubmitProposal(t *testing.T) { require.Equal(t, uint32(0), resultTx.DeliverTx.Code) var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + cdc.MustUnmarshalBinary(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -651,7 +651,7 @@ func TestDeposit(t *testing.T) { require.Equal(t, uint32(0), resultTx.DeliverTx.Code) var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + cdc.MustUnmarshalBinary(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -685,7 +685,7 @@ func TestVote(t *testing.T) { require.Equal(t, uint32(0), resultTx.DeliverTx.Code) var proposalID int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID) + cdc.MustUnmarshalBinary(resultTx.DeliverTx.GetData(), &proposalID) // query proposal proposal := getProposal(t, port, proposalID) @@ -733,17 +733,17 @@ func TestProposalsQuery(t *testing.T) { // Addr1 proposes (and deposits) proposals #1 and #2 resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5) var proposalID1 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) + cdc.MustUnmarshalBinary(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5) var proposalID2 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) + cdc.MustUnmarshalBinary(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 proposes (and deposits) proposals #3 resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], 5) var proposalID3 int64 - cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) + cdc.MustUnmarshalBinary(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 deposits on proposals #2 & #3 diff --git a/types/utils.go b/types/utils.go index 10ec854728ef..db6851f80d8f 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,6 +1,7 @@ package types import ( + "encoding/binary" "encoding/json" "time" @@ -36,6 +37,13 @@ func MustSortJSON(toSortJSON []byte) []byte { return js } +// marshals int64 to a bigendian byte slice so it can be sorted +func Int64ToSortableBytes(i int64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(i)) + return b +} + // Slight modification of the RFC3339Nano but it right pads all zeros and drops the time zone info const SortableTimeFormat = "2006-01-02T15:04:05.000000000" diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 27eff15f6443..9f7803264273 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -16,35 +16,40 @@ func TestTickExpiredDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader = ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() + EndBlocker(ctx, keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() } func TestTickMultipleExpiredDepositPeriod(t *testing.T) { @@ -53,25 +58,26 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(2) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res = govHandler(ctx, newProposalMsg2) @@ -81,21 +87,25 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader = ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(5) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, inactiveQueue.Valid()) + inactiveQueue.Close() EndBlocker(ctx, keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() } func TestTickPassedDepositPeriod(t *testing.T) { @@ -104,45 +114,39 @@ func TestTickPassedDepositPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + keeper.cdc.MustUnmarshalBinary(res.Data, &proposalID) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) - EndBlocker(ctx, keeper) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) - require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.True(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - - EndBlocker(ctx, keeper) - - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) - + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() } func TestTickPassedVotingPeriod(t *testing.T) { @@ -152,17 +156,19 @@ func TestTickPassedVotingPeriod(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) govHandler := NewHandler(keeper) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.False(t, shouldPopInactiveProposalQueue(ctx, keeper)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.False(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 5)}) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) + keeper.cdc.MustUnmarshalBinary(res.Data, &proposalID) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) @@ -172,24 +178,27 @@ func TestTickPassedVotingPeriod(t *testing.T) { res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) - EndBlocker(ctx, keeper) - newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(keeper.GetVotingProcedure(ctx).VotingPeriod) ctx = ctx.WithBlockHeader(newHeader) - require.True(t, shouldPopActiveProposalQueue(ctx, keeper)) + inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, inactiveQueue.Valid()) + inactiveQueue.Close() + + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.True(t, activeQueue.Valid()) + var activeProposalID int64 + keeper.cdc.UnmarshalBinary(activeQueue.Value(), &activeProposalID) + require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, activeProposalID).GetStatus()) depositsIterator := keeper.GetDeposits(ctx, proposalID) require.True(t, depositsIterator.Valid()) depositsIterator.Close() - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) + activeQueue.Close() EndBlocker(ctx, keeper) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - depositsIterator = keeper.GetDeposits(ctx, proposalID) - require.False(t, depositsIterator.Valid()) - depositsIterator.Close() - require.Equal(t, StatusRejected, keeper.GetProposal(ctx, proposalID).GetStatus()) - require.True(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) + activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + require.False(t, activeQueue.Valid()) + activeQueue.Close() } diff --git a/x/gov/handler.go b/x/gov/handler.go index dc1dbfb10e40..7c3500b7c60c 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -33,7 +33,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos return err.Result() } - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(proposal.GetProposalID()) + proposalIDBytes := keeper.cdc.MustMarshalBinary(proposal.GetProposalID()) resTags := sdk.NewTags( tags.Action, tags.ActionSubmitProposal, @@ -102,40 +102,35 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { resTags = sdk.NewTags() - // Delete proposals that haven't met minDeposit - for shouldPopInactiveProposalQueue(ctx, keeper) { - inactiveProposal := keeper.InactiveProposalQueuePop(ctx) - if inactiveProposal.GetStatus() != StatusDepositPeriod { - continue - } + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + for ; inactiveIterator.Valid(); inactiveIterator.Next() { + var proposalID int64 + keeper.cdc.MustUnmarshalBinary(inactiveIterator.Value(), &proposalID) + inactiveProposal := keeper.GetProposal(ctx, proposalID) + keeper.RefundDeposits(ctx, proposalID) + keeper.DeleteProposal(ctx, proposalID) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(inactiveProposal.GetProposalID()) - keeper.DeleteProposal(ctx, inactiveProposal) resTags = resTags.AppendTag(tags.Action, tags.ActionProposalDropped) - resTags = resTags.AppendTag(tags.ProposalID, proposalIDBytes) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) logger.Info( - fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v steak (had only %v steak); deleted", + fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), - keeper.GetDepositProcedure(ctx).MinDeposit.AmountOf("steak"), - inactiveProposal.GetTotalDeposit().AmountOf("steak"), + keeper.GetDepositProcedure(ctx).MinDeposit, + inactiveProposal.GetTotalDeposit(), ), ) } + inactiveIterator.Close() - // Check if earliest Active Proposal ended voting period yet - for shouldPopActiveProposalQueue(ctx, keeper) { - activeProposal := keeper.ActiveProposalQueuePop(ctx) - - proposalStartTime := activeProposal.GetVotingStartTime() - votingPeriod := keeper.GetVotingProcedure(ctx).VotingPeriod - if ctx.BlockHeader().Time.Before(proposalStartTime.Add(votingPeriod)) { - continue - } - + activeIterator := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + for ; activeIterator.Valid(); activeIterator.Next() { + var proposalID int64 + keeper.cdc.MustUnmarshalBinary(activeIterator.Value(), &proposalID) + activeProposal := keeper.GetProposal(ctx, proposalID) passes, tallyResults := tally(ctx, keeper, activeProposal) - proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) + var action []byte if passes { keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) @@ -149,37 +144,14 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { activeProposal.SetTallyResult(tallyResults) keeper.SetProposal(ctx, activeProposal) + keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.GetVotingEndTime(), activeProposal.GetProposalID()) + logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) resTags = resTags.AppendTag(tags.Action, action) - resTags = resTags.AppendTag(tags.ProposalID, proposalIDBytes) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) } return resTags } -func shouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - depositProcedure := keeper.GetDepositProcedure(ctx) - peekProposal := keeper.InactiveProposalQueuePeek(ctx) - - if peekProposal == nil { - return false - } else if peekProposal.GetStatus() != StatusDepositPeriod { - return true - } else if !ctx.BlockHeader().Time.Before(peekProposal.GetSubmitTime().Add(depositProcedure.MaxDepositPeriod)) { - return true - } - return false -} - -func shouldPopActiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - votingProcedure := keeper.GetVotingProcedure(ctx) - peekProposal := keeper.ActiveProposalQueuePeek(ctx) - - if peekProposal == nil { - return false - } else if !ctx.BlockHeader().Time.Before(peekProposal.GetVotingStartTime().Add(votingProcedure.VotingPeriod)) { - return true - } - return false -} diff --git a/x/gov/keeper.go b/x/gov/keeper.go index d061f1206232..e0e83f3dccb8 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -1,6 +1,8 @@ package gov import ( + "time" + codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" @@ -92,8 +94,12 @@ func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description TotalDeposit: sdk.Coins{}, SubmitTime: ctx.BlockHeader().Time, } + + depositPeriod := keeper.GetDepositProcedure(ctx).MaxDepositPeriod + proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) + keeper.SetProposal(ctx, proposal) - keeper.InactiveProposalQueuePush(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) return proposal } @@ -119,9 +125,12 @@ func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { } // Implements sdk.AccountKeeper. -func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) { +func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID int64) { store := ctx.KVStore(keeper.storeKey) - store.Delete(KeyProposal(proposal.GetProposalID())) + proposal := keeper.GetProposal(ctx, proposalID) + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) + keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID) + store.Delete(KeyProposal(proposalID)) } // Get Proposal from store by ProposalID @@ -216,9 +225,13 @@ func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID int64, e func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { proposal.SetVotingStartTime(ctx.BlockHeader().Time) + votingPeriod := keeper.GetVotingProcedure(ctx).VotingPeriod + proposal.SetVotingEndTime(proposal.GetVotingStartTime().Add(votingPeriod)) proposal.SetStatus(StatusVotingPeriod) keeper.SetProposal(ctx, proposal) - keeper.ActiveProposalQueuePush(ctx, proposal) + + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) + keeper.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) } // ===================================================== @@ -426,93 +439,40 @@ func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { // ===================================================== // ProposalQueues -func (keeper Keeper) getActiveProposalQueue(ctx sdk.Context) ProposalQueue { +// Returns an 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) - bz := store.Get(KeyActiveProposalQueue) - if bz == nil { - return nil - } - - var proposalQueue ProposalQueue - keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) - - return proposalQueue + return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(ActiveProposalQueueTimePrefix(endTime))) } -func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { +// Inserts a ProposalID into the active proposal queue at endTime +func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID int64) { store := ctx.KVStore(keeper.storeKey) - bz := keeper.cdc.MustMarshalBinary(proposalQueue) - store.Set(KeyActiveProposalQueue, bz) -} - -// Return the Proposal at the front of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePeek(ctx sdk.Context) Proposal { - proposalQueue := keeper.getActiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) -} - -// Remove and return a Proposal from the front of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal { - proposalQueue := keeper.getActiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setActiveProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) -} - -// Add a proposalID to the back of the ProposalQueue -func (keeper Keeper) ActiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getActiveProposalQueue(ctx), proposal.GetProposalID()) - keeper.setActiveProposalQueue(ctx, proposalQueue) + bz := keeper.cdc.MustMarshalBinary(proposalID) + store.Set(ActiveProposalQueueProposalKey(endTime, proposalID), bz) } -func (keeper Keeper) getInactiveProposalQueue(ctx sdk.Context) ProposalQueue { +// removes a proposalID from the Active Proposal Queue +func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID int64) { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(KeyInactiveProposalQueue) - if bz == nil { - return nil - } - - var proposalQueue ProposalQueue - - keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue) - - return proposalQueue + store.Set(ActiveProposalQueueProposalKey(endTime, proposalID), nil) } -func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { +// Returns an 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) - bz := keeper.cdc.MustMarshalBinary(proposalQueue) - store.Set(KeyInactiveProposalQueue, bz) + return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(InactiveProposalQueueTimePrefix(endTime))) } -// Return the Proposal at the front of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePeek(ctx sdk.Context) Proposal { - proposalQueue := keeper.getInactiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) -} - -// Remove and return a Proposal from the front of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePop(ctx sdk.Context) Proposal { - proposalQueue := keeper.getInactiveProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setInactiveProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) +// Inserts a ProposalID into the inactive proposal queue at endTime +func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID int64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinary(proposalID) + store.Set(InactiveProposalQueueProposalKey(endTime, proposalID), bz) } -// Add a proposalID to the back of the ProposalQueue -func (keeper Keeper) InactiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getInactiveProposalQueue(ctx), proposal.GetProposalID()) - keeper.setInactiveProposalQueue(ctx, proposalQueue) +// removes a proposalID from the Inactive Proposal Queue +func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID int64) { + store := ctx.KVStore(keeper.storeKey) + store.Set(InactiveProposalQueueProposalKey(endTime, proposalID), nil) } diff --git a/x/gov/keeper_keys.go b/x/gov/keeper_keys.go index 7b1bf43f2f77..57f38fdd6b29 100644 --- a/x/gov/keeper_keys.go +++ b/x/gov/keeper_keys.go @@ -1,7 +1,9 @@ package gov import ( + "bytes" "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -10,9 +12,9 @@ import ( // Key for getting a the next available proposalID from the store var ( - KeyNextProposalID = []byte("newProposalID") - KeyActiveProposalQueue = []byte("activeProposalQueue") - KeyInactiveProposalQueue = []byte("inactiveProposalQueue") + KeyNextProposalID = []byte("newProposalID") + PrefixActiveProposalQueue = []byte("activeProposalQueue") + PrefixInactiveProposalQueue = []byte("inactiveProposalQueue") ) // Key for getting a specific proposal from the store @@ -39,3 +41,37 @@ func KeyDepositsSubspace(proposalID int64) []byte { func KeyVotesSubspace(proposalID int64) []byte { return []byte(fmt.Sprintf("votes:%d:", proposalID)) } + +// Returns the key for a proposalID in the activeProposalQueue +func ActiveProposalQueueTimePrefix(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, []byte("/")) +} + +// Returns the key for a proposalID in the activeProposalQueue +func ActiveProposalQueueProposalKey(endTime time.Time, proposalID int64) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Int64ToSortableBytes(proposalID), + }, []byte("/")) +} + +// Returns the key for a proposalID in the activeProposalQueue +func InactiveProposalQueueTimePrefix(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, []byte("/")) +} + +// Returns the key for a proposalID in the activeProposalQueue +func InactiveProposalQueueProposalKey(endTime time.Time, proposalID int64) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Int64ToSortableBytes(proposalID), + }, []byte("/")) +} diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 91c41d7d7dde..656abc843a7b 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -47,12 +47,17 @@ func TestActivateVotingPeriod(t *testing.T) { proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) require.True(t, proposal.GetVotingStartTime().Equal(time.Time{})) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) keeper.activateVotingPeriod(ctx, proposal) require.True(t, proposal.GetVotingStartTime().Equal(ctx.BlockHeader().Time)) - require.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + require.True(t, activeIterator.Valid()) + var proposalID int64 + keeper.cdc.UnmarshalBinary(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + activeIterator.Close() } func TestDeposits(t *testing.T) { @@ -79,7 +84,6 @@ func TestDeposits(t *testing.T) { deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) require.False(t, found) require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(time.Time{})) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) // Check first deposit err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourSteak) @@ -116,8 +120,6 @@ func TestDeposits(t *testing.T) { // Check that proposal moved to voting period require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(ctx.BlockHeader().Time)) - require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) - require.Equal(t, proposalID, keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) // Test deposit iterator depositsIterator := keeper.GetDeposits(ctx, proposalID) @@ -207,44 +209,21 @@ func TestProposalQueues(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) mapp.InitChainer(ctx, abci.RequestInitChain{}) - require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) - require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) - // create test proposals proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) - proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", ProposalTypeText) - proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", ProposalTypeText) - proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", ProposalTypeText) - - // test pushing to inactive proposal queue - keeper.InactiveProposalQueuePush(ctx, proposal) - keeper.InactiveProposalQueuePush(ctx, proposal2) - keeper.InactiveProposalQueuePush(ctx, proposal3) - keeper.InactiveProposalQueuePush(ctx, proposal4) - - // test peeking and popping from inactive proposal queue - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) - require.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) - - // test pushing to active proposal queue - keeper.ActiveProposalQueuePush(ctx, proposal) - keeper.ActiveProposalQueuePush(ctx, proposal2) - keeper.ActiveProposalQueuePush(ctx, proposal3) - keeper.ActiveProposalQueuePush(ctx, proposal4) - - // test peeking and popping from active proposal queue - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID()) - require.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID()) + + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.GetDepositEndTime()) + require.True(t, inactiveIterator.Valid()) + var proposalID int64 + keeper.cdc.UnmarshalBinary(inactiveIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + inactiveIterator.Close() + + keeper.activateVotingPeriod(ctx, proposal) + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.GetVotingEndTime()) + require.True(t, activeIterator.Valid()) + keeper.cdc.UnmarshalBinary(activeIterator.Value(), &proposalID) + require.Equal(t, proposalID, proposal.GetProposalID()) + activeIterator.Close() } diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 9d1ba860adae..1d9d562a6b25 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -34,11 +34,17 @@ type Proposal interface { GetSubmitTime() time.Time SetSubmitTime(time.Time) + GetDepositEndTime() time.Time + SetDepositEndTime(time.Time) + GetTotalDeposit() sdk.Coins SetTotalDeposit(sdk.Coins) GetVotingStartTime() time.Time SetVotingStartTime(time.Time) + + GetVotingEndTime() time.Time + SetVotingEndTime(time.Time) } // checks if two proposals are equal @@ -68,10 +74,12 @@ type TextProposal struct { Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} TallyResult TallyResult `json:"tally_result"` // Result of Tallys - SubmitTime time.Time `json:"submit_time"` // Height of the block where TxGovSubmitProposal was included - TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit + SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included + DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met + TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit - VotingStartTime time.Time `json:"voting_start_time"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied } // Implements Proposal Interface @@ -92,12 +100,20 @@ func (tp TextProposal) GetTallyResult() TallyResult { return tp.T func (tp *TextProposal) SetTallyResult(tallyResult TallyResult) { tp.TallyResult = tallyResult } func (tp TextProposal) GetSubmitTime() time.Time { return tp.SubmitTime } func (tp *TextProposal) SetSubmitTime(submitTime time.Time) { tp.SubmitTime = submitTime } -func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } -func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } -func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } +func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } +func (tp *TextProposal) SetDepositEndTime(depositEndTime time.Time) { + tp.DepositEndTime = depositEndTime +} +func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } +func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } +func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { tp.VotingStartTime = votingStartTime } +func (tp TextProposal) GetVotingEndTime() time.Time { return tp.VotingEndTime } +func (tp *TextProposal) SetVotingEndTime(votingEndTime time.Time) { + tp.VotingEndTime = votingEndTime +} //----------------------------------------------------------- // ProposalQueue