diff --git a/PENDING.md b/PENDING.md index 416dc00d7d..654e342f8c 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,7 +44,7 @@ BREAKING CHANGES * [simulation] \#2162 Added back correct supply invariants * [x/slashing] \#2430 Simulate more slashes, check if validator is jailed before jailing * [x/stake] \#2393 Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker - + * SDK * [core] \#2219 Update to Tendermint 0.24.0 * Validator set updates delayed by one block @@ -69,14 +69,15 @@ BREAKING CHANGES * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index * [x/staking] \#2236 more distribution hooks for distribution * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock - * [x/stake] Global Paramstore refactored + * [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding + * [x/params] Global Paramstore refactored * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes * Mempool now won't build too large blocks, or too computationally expensive blocks * Maximum tx sizes and gas are now removed, and are implicitly the blocks maximums * ABCI validators no longer send the pubkey. The pubkey is only sent in validator updates - * Validator set changes are now delayed by one block + * Validator set changes are now delayed by one block * Block header now includes the next validator sets hash * BFT time is implemented * Secp256k1 signature format has changed @@ -92,6 +93,7 @@ FEATURES * [gaia-lite] [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) Add /sign endpoint to sign transactions generated with `generate_only=true`. * [gaia-lite] [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) Add /broadcast endpoint to broadcast transactions signed by the /sign endpoint. * [gaia-lite] [\#2113](https://github.com/cosmos/cosmos-sdk/issues/2113) Rename `/accounts/{address}/send` to `/bank/accounts/{address}/transfers`, rename `/accounts/{address}` to `/auth/accounts/{address}` + * [gaia-lite] [\#2478](https://github.com/cosmos/cosmos-sdk/issues/2478) Add query gov proposal's deposits endpoint * Gaia CLI (`gaiacli`) * [cli] Cmds to query staking pool and params diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 6eef4afd0a..fce58c253d 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -10,19 +10,19 @@ import ( "testing" "time" + "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" + p2p "github.com/tendermint/tendermint/p2p" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" - p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - rpc "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/codec" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" @@ -613,7 +613,7 @@ func TestSubmitProposal(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -635,7 +635,7 @@ func TestDeposit(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -650,7 +650,7 @@ func TestDeposit(t *testing.T) { require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5) tests.WaitForHeight(resultTx.Height+1, port) // query proposal @@ -669,7 +669,7 @@ func TestVote(t *testing.T) { defer cleanup() // create SubmitProposal TX - resultTx := doSubmitProposal(t, port, seed, name, password, addr) + resultTx := doSubmitProposal(t, port, seed, name, password, addr, 5) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -684,7 +684,7 @@ func TestVote(t *testing.T) { require.Equal(t, "Test", proposal.GetTitle()) // create SubmitProposal TX - resultTx = doDeposit(t, port, seed, name, password, addr, proposalID) + resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5) tests.WaitForHeight(resultTx.Height+1, port) // query proposal @@ -725,27 +725,52 @@ func TestProposalsQuery(t *testing.T) { defer cleanup() // Addr1 proposes (and deposits) proposals #1 and #2 - resultTx := doSubmitProposal(t, port, seed, name, password1, addr) + resultTx := doSubmitProposal(t, port, seed, name, password1, addr, 5) var proposalID1 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doSubmitProposal(t, port, seed, name, password1, addr) + resultTx = doSubmitProposal(t, port, seed, name, password1, addr, 5) var proposalID2 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 proposes (and deposits) proposals #3 - resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2) + resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2, 5) var proposalID3 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 deposits on proposals #2 & #3 - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2) + resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2, 5) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3) + resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3, 5) tests.WaitForHeight(resultTx.Height+1, port) + // check deposits match proposal and individual deposits + deposits := getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + deposit := getDeposit(t, port, proposalID1, addr) + require.Equal(t, deposit, deposits[0]) + + deposits = getDeposits(t, port, proposalID2) + require.Len(t, deposits, 2) + deposit = getDeposit(t, port, proposalID2, addr) + require.Equal(t, deposit, deposits[0]) + deposit = getDeposit(t, port, proposalID2, addr2) + require.Equal(t, deposit, deposits[1]) + + deposits = getDeposits(t, port, proposalID3) + require.Len(t, deposits, 1) + deposit = getDeposit(t, port, proposalID3, addr2) + require.Equal(t, deposit, deposits[0]) + + // increasing the amount of the deposit should update the existing one + resultTx = doDeposit(t, port, seed, name, password1, addr, proposalID1, 1) + tests.WaitForHeight(resultTx.Height+1, port) + + deposits = getDeposits(t, port, proposalID1) + require.Len(t, deposits, 1) + // Only proposals #1 should be in Deposit Period proposals := getProposalsFilterStatus(t, port, gov.StatusDepositPeriod) require.Len(t, proposals, 1) @@ -1168,6 +1193,15 @@ func getProposal(t *testing.T, port string, proposalID int64) gov.Proposal { return proposal } +func getDeposits(t *testing.T, port string, proposalID int64) []gov.Deposit { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var deposits []gov.Deposit + err := cdc.UnmarshalJSON([]byte(body), &deposits) + require.Nil(t, err) + return deposits +} + func getDeposit(t *testing.T, port string, proposalID int64, depositerAddr sdk.AccAddress) gov.Deposit { res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/deposits/%s", proposalID, depositerAddr), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1245,7 +1279,7 @@ func getProposalsFilterStatus(t *testing.T, port string, status gov.ProposalStat return proposals } -func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { +func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1259,7 +1293,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "description": "test", "proposal_type": "Text", "proposer": "%s", - "initial_deposit": [{ "denom": "steak", "amount": "5" }], + "initial_deposit": [{ "denom": "steak", "amount": "%d" }], "base_req": { "name": "%s", "password": "%s", @@ -1267,7 +1301,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "account_number":"%d", "sequence":"%d" } - }`, proposerAddr, name, password, chainID, accnum, sequence)) + }`, proposerAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", "/gov/proposals", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1278,7 +1312,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA return results } -func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.AccAddress, proposalID int64, amount int64) (resultTx ctypes.ResultBroadcastTxCommit) { acc := getAccount(t, port, proposerAddr) accnum := acc.GetAccountNumber() @@ -1289,7 +1323,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk // deposit on proposal jsonStr := []byte(fmt.Sprintf(`{ "depositer": "%s", - "amount": [{ "denom": "steak", "amount": "5" }], + "amount": [{ "denom": "steak", "amount": "%d" }], "base_req": { "name": "%s", "password": "%s", @@ -1297,7 +1331,7 @@ func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk "account_number":"%d", "sequence": "%d" } - }`, proposerAddr, name, password, chainID, accnum, sequence)) + }`, proposerAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/gov/proposals/%d/deposits", proposalID), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 2502baf0dc..6439ca0c71 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -1,5 +1,20 @@ # End-Block +## Unbonding Validator Queue + +For all unbonding validators that have finished their unbonding period, this switches their validator.Status +from sdk.Unbonding to sdk.Unbonded + +```golang +validatorQueue(currTime time.Time): + // unbonding validators are in ordered queue from oldest to newest + for all unbondingValidators whose CompleteTime < currTime: + validator = GetValidator(unbondingValidator.ValidatorAddr) + validator.Status = sdk.Bonded + SetValidator(unbondingValidator) + return +``` + ## Validator Set Changes The Tendermint validator set may be updated by state transitions that run at diff --git a/types/context.go b/types/context.go index f50503333a..a3150087b1 100644 --- a/types/context.go +++ b/types/context.go @@ -188,7 +188,9 @@ func (c Context) WithBlockTime(newTime time.Time) Context { } func (c Context) WithBlockHeight(height int64) Context { - return c.withValue(contextKeyBlockHeight, height) + newHeader := c.BlockHeader() + newHeader.Height = height + return c.withValue(contextKeyBlockHeight, height).withValue(contextKeyBlockHeader, newHeader) } func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { diff --git a/types/context_test.go b/types/context_test.go index 0026912298..0ab6c8dfc7 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -164,15 +164,16 @@ func TestContextWithCustom(t *testing.T) { meter := types.NewGasMeter(10000) minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)} - ctx = types.NewContext(nil, header, ischeck, logger). + ctx = types.NewContext(nil, header, ischeck, logger) + require.Equal(t, header, ctx.BlockHeader()) + + ctx = ctx. WithBlockHeight(height). WithChainID(chainid). WithTxBytes(txbytes). WithVoteInfos(voteinfos). WithGasMeter(meter). WithMinimumFees(minFees) - - require.Equal(t, header, ctx.BlockHeader()) require.Equal(t, height, ctx.BlockHeight()) require.Equal(t, chainid, ctx.ChainID()) require.Equal(t, ischeck, ctx.IsCheckTx()) diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index f66331ad5a..e7d9770e2c 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -31,13 +31,12 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc)).Methods("GET") - - r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc)).Methods("GET") - - r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc)).Methods("GET") + r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc, cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc, cliCtx)).Methods("GET") } type postProposalReq struct { @@ -164,7 +163,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc } } -func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -180,8 +179,6 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryProposalParams{ ProposalID: proposalID, } @@ -198,11 +195,41 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } -func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + strProposalID := vars[RestProposalID] + + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) + if !ok { + return + } + + params := gov.QueryDepositsParams{ + ProposalID: proposalID, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData("custom/gov/deposits", bz) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -232,8 +259,6 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryDepositParams{ ProposalID: proposalID, Depositer: depositerAddr, @@ -265,11 +290,11 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } -func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -299,8 +324,6 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryVoteParams{ Voter: voterAddr, ProposalID: proposalID, @@ -335,12 +358,12 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -356,8 +379,6 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryVotesParams{ ProposalID: proposalID, } @@ -373,12 +394,12 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { +func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { bechVoterAddr := r.URL.Query().Get(RestVoter) bechDepositerAddr := r.URL.Query().Get(RestDepositer) @@ -430,20 +451,18 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above -func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { +func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] @@ -461,8 +480,6 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - cliCtx := context.NewCLIContext().WithCodec(cdc) - params := gov.QueryTallyParams{ ProposalID: proposalID, } @@ -480,6 +497,6 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/gov/queryable.go b/x/gov/queryable.go index 606f73a9ca..b0cb423749 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -118,7 +118,7 @@ type QueryDepositsParams struct { // nolint: unparam func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var params QueryDepositParams + var params QueryDepositsParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) diff --git a/x/stake/handler.go b/x/stake/handler.go index 2367182891..57430d58fa 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -35,6 +35,8 @@ func NewHandler(k keeper.Keeper) sdk.Handler { func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { endBlockerTags := sdk.EmptyTags() + k.UnbondAllMatureValidatorQueue(ctx) + matureUnbonds := k.DequeueAllMatureUnbondingQueue(ctx, ctx.BlockHeader().Time) for _, dvPair := range matureUnbonds { err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 1802f30c13..de7aee90f4 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -629,6 +629,56 @@ func TestJailValidator(t *testing.T) { require.True(t, got.IsOK(), "expected ok, got %v", got) } +func TestValidatorQueue(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + EndBlocker(ctx, keeper) + + // unbond the all self-delegation to put validator in unbonding state + msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + origHeader := ctx.BlockHeader() + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should still be unbonding at time 6 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should be in unbonded state at time 7 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonded, "%v", validator) +} + func TestUnbondingPeriod(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) validatorAddr := sdk.ValAddress(keep.Addrs[0]) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 40bcae95db..6f5a44ac05 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -447,10 +447,10 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( // the longest wait - just unbonding period from now minTime = ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) - height = ctx.BlockHeader().Height + height = ctx.BlockHeight() return minTime, height, false - case validator.IsUnbonded(ctx): + case validator.Status == sdk.Unbonded: return minTime, height, true case validator.Status == sdk.Unbonding: diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 0964f10def..6c42feb413 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -374,12 +374,8 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - header := ctx.BlockHeader() - blockHeight := int64(10) - header.Height = blockHeight - blockTime := time.Unix(333, 0) - header.Time = blockTime - ctx = ctx.WithBlockHeader(header) + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) @@ -391,17 +387,12 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, blockHeight, validator.UnbondingHeight) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) - // change the context to one which makes the validator considered unbonded - header = ctx.BlockHeader() - blockHeight2 := int64(20) - header.Height = blockHeight2 - blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) - header.Time = blockTime2 - ctx = ctx.WithBlockHeader(header) + // unbond the validator + keeper.unbondingToUnbonded(ctx, validator) // unbond some of the other delegation's shares _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) @@ -696,12 +687,8 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator2 = testingUpdateValidator(keeper, ctx, validator2) require.Equal(t, sdk.Bonded, validator2.Status) - header := ctx.BlockHeader() - blockHeight := int64(10) - header.Height = blockHeight - blockTime := time.Unix(333, 0) - header.Time = blockTime - ctx = ctx.WithBlockHeader(header) + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) @@ -713,23 +700,18 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - require.Equal(t, blockHeight, validator.UnbondingHeight) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) params := keeper.GetParams(ctx) - require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) - // change the context to one which makes the validator considered unbonded - header = ctx.BlockHeader() - blockHeight2 := int64(20) - header.Height = blockHeight2 - blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) - header.Time = blockTime2 - ctx = ctx.WithBlockHeader(header) + // unbond the validator + keeper.unbondingToUnbonded(ctx, validator) - // unbond some of the other delegation's shares + // redelegate some of the delegation's shares _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) require.NoError(t, err) - // no ubd should have been found, coins should have been returned direcly to account - ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) - require.False(t, found, "%v", ubd) + // no red should have been found + red, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found, "%v", red) } diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index e84a35c21e..9f6592ef33 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -29,6 +29,7 @@ var ( RedelegationByValDstIndexKey = []byte{0x0C} // prefix for each key for an redelegation, by destination validator operator UnbondingQueueKey = []byte{0x0D} // prefix for the timestamps in unbonding queue RedelegationQueueKey = []byte{0x0E} // prefix for the timestamps in redelegations queue + ValidatorQueueKey = []byte{0x0F} // prefix for the timestamps in validator queue ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -86,6 +87,12 @@ func getValidatorPowerRank(validator types.Validator) []byte { return key } +// gets the prefix for all unbonding delegations from a delegator +func GetValidatorQueueTimeKey(timestamp time.Time) []byte { + bz := types.MsgCdc.MustMarshalBinary(timestamp) + return append(ValidatorQueueKey, bz...) +} + //______________________________________________________________________________ // gets the key for delegator bond with validator diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index dc57dc5dd4..a1562da33c 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -46,7 +46,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh } // should not be slashing unbonded - if validator.IsUnbonded(ctx) { + if validator.Status == sdk.Unbonded { panic(fmt.Sprintf("should not be slashing unbonded validator: %s", validator.GetOperator())) } diff --git a/x/stake/keeper/val_state_change.go b/x/stake/keeper/val_state_change.go index f9cf6e463f..89d3f681fa 100644 --- a/x/stake/keeper/val_state_change.go +++ b/x/stake/keeper/val_state_change.go @@ -131,8 +131,9 @@ func (k Keeper) unbondedToBonded(ctx sdk.Context, validator types.Validator) typ return k.bondValidator(ctx, validator) } +// switches a validator from unbonding state to unbonded state func (k Keeper) unbondingToUnbonded(ctx sdk.Context, validator types.Validator) types.Validator { - if validator.Status != sdk.Unbonded { + if validator.Status != sdk.Unbonding { panic(fmt.Sprintf("bad state transition unbondingToBonded, validator: %v\n", validator)) } return k.completeUnbondingValidator(ctx, validator) @@ -213,6 +214,9 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat k.SetValidatorByPowerIndex(ctx, validator, pool) + // Adds to unbonding validator queue + k.InsertValidatorQueue(ctx, validator) + // call the unbond hook if present if k.hooks != nil { k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 741b787d09..41cfe6faa6 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -3,6 +3,7 @@ package keeper import ( "container/list" "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -281,3 +282,69 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { } return validators[:i] // trim } + +// gets a specific validator queue timeslice. A timeslice is a slice of ValAddresses corresponding to unbonding validators +// that expire at a certain time. +func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (valAddrs []sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetValidatorQueueTimeKey(timestamp)) + if bz == nil { + return []sdk.ValAddress{} + } + k.cdc.MustUnmarshalBinary(bz, &valAddrs) + return valAddrs +} + +// Sets a specific validator queue timeslice. +func (k Keeper) SetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(keys) + store.Set(GetValidatorQueueTimeKey(timestamp), bz) +} + +// Insert an validator address to the appropriate timeslice in the validator queue +func (k Keeper) InsertValidatorQueue(ctx sdk.Context, val types.Validator) { + timeSlice := k.GetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime) + if len(timeSlice) == 0 { + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, []sdk.ValAddress{val.OperatorAddr}) + } else { + timeSlice = append(timeSlice, val.OperatorAddr) + k.SetValidatorQueueTimeSlice(ctx, val.UnbondingMinTime, timeSlice) + } +} + +// Returns all the validator queue timeslices from time 0 until endTime +func (k Keeper) ValidatorQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return store.Iterator(ValidatorQueueKey, sdk.InclusiveEndBytes(GetValidatorQueueTimeKey(endTime))) +} + +// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue +func (k Keeper) GetAllMatureValidatorQueue(ctx sdk.Context, currTime time.Time) (matureValsAddrs []sdk.ValAddress) { + // gets an iterator for all timeslices from time 0 until the current Blockheader time + validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) + for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { + timeslice := []sdk.ValAddress{} + k.cdc.MustUnmarshalBinary(validatorTimesliceIterator.Value(), ×lice) + matureValsAddrs = append(matureValsAddrs, timeslice...) + } + return matureValsAddrs +} + +// Unbonds all the unbonding validators that have finished their unbonding period +func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + validatorTimesliceIterator := k.ValidatorQueueIterator(ctx, ctx.BlockHeader().Time) + for ; validatorTimesliceIterator.Valid(); validatorTimesliceIterator.Next() { + timeslice := []sdk.ValAddress{} + k.cdc.MustUnmarshalBinary(validatorTimesliceIterator.Value(), ×lice) + for _, valAddr := range timeslice { + val, found := k.GetValidator(ctx, valAddr) + if !found || val.GetStatus() != sdk.Unbonding { + continue + } + k.unbondingToUnbonded(ctx, val) + } + store.Delete(validatorTimesliceIterator.Key()) + } +} diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index dbd4e2a540..d774f761bd 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -435,21 +435,6 @@ func (v Validator) BondedTokens() sdk.Dec { return sdk.ZeroDec() } -// TODO remove this once the validator queue logic is implemented -// Returns if the validator should be considered unbonded -func (v Validator) IsUnbonded(ctx sdk.Context) bool { - switch v.Status { - case sdk.Unbonded: - return true - case sdk.Unbonding: - ctxTime := ctx.BlockHeader().Time - if ctxTime.After(v.UnbondingMinTime) { - return true - } - } - return false -} - //______________________________________________________________________ // ensure fulfills the sdk validator types