From 5e1fcb376903edc84cf2004373b826e8aed78b86 Mon Sep 17 00:00:00 2001 From: Luke Plaster Date: Tue, 12 Mar 2019 15:49:02 +0800 Subject: [PATCH 1/5] readme: fix clone example url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 067879d28..20e72e81c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The reason is that we need to keep the import path. ```bash > cd $GOPATH/src/github.com > rm -rf cosmos/cosmos-sdk -> git clone https://github.com/BiJie/bnc-cosmos-sdk.git cosmos/cosmos-sdk +> git clone https://github.com/binance-chain/bnc-cosmos-sdk.git cosmos/cosmos-sdk > cd cosmos-sdk > git checkout develop > make get_vendor_deps @@ -63,4 +63,4 @@ See the [Cosmos Docs](https://cosmos.network/docs/) ## Disambiguation -This Cosmos-SDK project is not related to the [React-Cosmos](https://github.com/react-cosmos/react-cosmos) project (yet). Many thanks to Evan Coury and Ovidiu (@skidding) for this Github organization name. As per our agreement, this disambiguation notice will stay here. \ No newline at end of file +This Cosmos-SDK project is not related to the [React-Cosmos](https://github.com/react-cosmos/react-cosmos) project (yet). Many thanks to Evan Coury and Ovidiu (@skidding) for this Github organization name. As per our agreement, this disambiguation notice will stay here. From ca4e37ce7f5dd9ff5f8385e51ee862cd70fc2024 Mon Sep 17 00:00:00 2001 From: zjubfd Date: Mon, 1 Apr 2019 17:30:19 +0800 Subject: [PATCH 2/5] [R4R] prepare release v0.25.0-binance.15 (#105) * R4R: Add interface to remove validator (#91) * Add validator remove interface * add unit test * Fix failed unit test in gov and stake * Add proposal id checking in msg validate * Add validator consensus addr into proposal decription * Add remove validator command line interface * Add new proposal type to NormalizeProposalType * add validator consensus address checking * Rename consensus address flag name * refactor handler test import * Use json marshal to replace cdc marshal * Add automatic gov proposal when proposal id is zero * add deposit flag * Add routerCallRecord * Resolve comment, a tx only contains one msg * improve the validate logic for remove validator * refactor go import * remove necessary checking * stake: add fee addr for each validator * [R4R] update gov strategy (#73) * update gov strategy * refactor * revert change * [R4R] Add deposit address and do some refactor (#101) * add address for deposits * refactor * refactor * revert change * refactor * refactor * change proposals to publish * update deposit address --- baseapp/baseapp.go | 6 +- client/lcd/lcd_test.go | 1 + cmd/gaia/app/app.go | 2 +- cmd/gaia/app/app_test.go | 2 +- server/tm_cmds.go | 2 +- types/context.go | 10 ++ x/gov/client/cli/tx.go | 40 +++++-- x/gov/client/cli/tx_test.go | 5 +- x/gov/client/rest/rest.go | 21 +++- x/gov/client/utils.go | 8 ++ x/gov/endblocker_test.go | 206 +++++++++++++++++++++++++++++++--- x/gov/errors.go | 6 + x/gov/genesis.go | 44 +++----- x/gov/handler.go | 55 ++++----- x/gov/keeper.go | 119 +++++++++++--------- x/gov/keeper_test.go | 104 ++++++++++++++--- x/gov/msgs.go | 24 ++-- x/gov/msgs_test.go | 33 +++--- x/gov/params.go | 20 ++++ x/gov/procedures.go | 25 ----- x/gov/proposals.go | 39 +++++-- x/gov/queryable.go | 2 +- x/gov/simulation/msgs.go | 4 +- x/gov/simulation/sim_test.go | 2 +- x/gov/tally.go | 52 +++++---- x/gov/tally_test.go | 121 +++++++++++++++----- x/stake/client/cli/flags.go | 5 +- x/stake/client/cli/tx.go | 114 +++++++++++++++++-- x/stake/handler.go | 115 ++++++++++++++++--- x/stake/handler_test.go | 105 +++++++++++++++-- x/stake/keeper/keeper.go | 5 - x/stake/keeper/sdk_types.go | 19 ++++ x/stake/keeper/test_common.go | 5 +- x/stake/stake.go | 24 ++-- x/stake/types/codec.go | 1 + x/stake/types/errors.go | 8 ++ x/stake/types/msg.go | 97 +++++++++++++++- x/stake/types/validator.go | 20 +++- 38 files changed, 1150 insertions(+), 321 deletions(-) create mode 100644 x/gov/params.go delete mode 100644 x/gov/procedures.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index a82de8df3..2b117c69d 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -804,7 +804,11 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, txHash string, mode // Construct usable logs in multi-message transactions. logs = append(logs, fmt.Sprintf("Msg %d: %s", msgIdx, msgResult.Log)) } - + // A tx must only contain one msg. If the msg execution is success, record it + if code == sdk.ABCICodeOK { + routerName := msgs[0].Route() + ctx.RouterCallRecord()[routerName] = true + } result = sdk.Result{ Code: code, Data: data, diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 8ad983326..640308b74 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -1156,6 +1156,7 @@ func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerA "title": "Test", "description": "test", "proposal_type": "Text", + "voting_period": "1000", "proposer": "%s", "initial_deposit": [{ "denom": "steak", "amount": "%d" }], "base_req": { diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 9f7739d2f..0b7642d75 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -138,7 +138,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, - app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, app.stakeKeeper, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamSpace), app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace), app.Pool, ) diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index e348de906..26d165576 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -94,7 +94,7 @@ func NewMockGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppO app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, - app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, app.stakeKeeper, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamSpace), app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace), app.Pool, ) diff --git a/server/tm_cmds.go b/server/tm_cmds.go index eadb07b20..73c28117f 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -67,7 +67,7 @@ func ShowAddressCmd(ctx *Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cfg := ctx.Config privValidator := pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()) - valAddr := (sdk.ValAddress)(privValidator.GetAddress()) + valAddr := (sdk.ConsAddress)(privValidator.GetAddress()) if viper.GetBool(client.FlagJson) { return printlnJSON(valAddr) diff --git a/types/context.go b/types/context.go index 07ca5e381..367021726 100644 --- a/types/context.go +++ b/types/context.go @@ -45,6 +45,7 @@ func NewContext(ms MultiStore, header abci.Header, runTxMode RunTxMode, logger l c = c.WithTxBytes(nil) c = c.WithLogger(logger) c = c.WithVoteInfos(nil) + c = c.WithRouterCallRecord(make(map[string]bool)) return c } @@ -137,6 +138,7 @@ const ( contextKeyLogger contextKeyVoteInfos contextKeyAccountCache + contextKeyRouterCallRecord ) // NOTE: Do not expose MultiStore. @@ -183,6 +185,10 @@ func (c Context) AccountCache() AccountCache { return c.Value(contextKeyAccountCache).(AccountCache) } +func (c Context) RouterCallRecord() map[string]bool { + return c.Value(contextKeyRouterCallRecord).(map[string]bool) +} + func (c Context) WithMultiStore(ms MultiStore) Context { return c.withValue(contextKeyMultiStore, ms) } func (c Context) WithBlockHeader(header abci.Header) Context { @@ -233,6 +239,10 @@ func (c Context) WithAccountCache(cache AccountCache) Context { return c.withValue(contextKeyAccountCache, cache) } +func (c Context) WithRouterCallRecord(record map[string]bool) Context { + return c.withValue(contextKeyRouterCallRecord, record) +} + // Cache the multistore and return a new cached context. The cached context is // written to the context when writeCache is called. func (c Context) CacheContext() (cc Context, writeCache func()) { diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 33102471e..373ab5360 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -26,6 +26,7 @@ const ( flagTitle = "title" flagDescription = "description" flagProposalType = "type" + flagVotingPeriod = "voting-period" flagDeposit = "deposit" flagVoter = "voter" flagOption = "option" @@ -40,15 +41,17 @@ const ( ) type proposal struct { - Title string - Description string - Type string - Deposit string + Title string `json:"title"` + Description string `json:"description"` + VotingPeriod int64 `json:"voting_period"` + Type string `json:"type"` + Deposit string `json:"deposit"` } var proposalFlags = []string{ flagTitle, flagDescription, + flagVotingPeriod, flagProposalType, flagDeposit, } @@ -68,13 +71,14 @@ where proposal.json contains: { "title": "Test Proposal", "description": "My awesome proposal", + "voting_period": 1000, "type": "Text", "deposit": "1000:test" } is equivalent to -$ CLI gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="1000:test" +$ CLI gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="1000:test" --voting-period=1000 `), RunE: func(cmd *cobra.Command, args []string) error { proposal, err := parseSubmitProposalFlags() @@ -99,6 +103,15 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop WithCodec(cdc). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + if proposal.VotingPeriod <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(proposal.VotingPeriod) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return errors.New(fmt.Sprintf("voting period should be less than %d seconds", gov.MaxVotingPeriod/time.Second)) + } + fromAddr, err := cliCtx.GetFromAddress() if err != nil { return err @@ -114,7 +127,7 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop return err } - msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount) + msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount, votingPeriod) err = msg.ValidateBasic() if err != nil { return err @@ -133,6 +146,7 @@ $ CLI gov submit-proposal --title="Test Proposal" --description="My awesome prop cmd.Flags().String(flagTitle, "", "title of proposal") cmd.Flags().String(flagDescription, "", "description of proposal") + cmd.Flags().Int64(flagVotingPeriod, 7*24*60*60, "voting period in seconds") cmd.Flags().String(flagProposalType, "", "proposalType of proposal, types: text/parameter_change/software_upgrade") cmd.Flags().String(flagDeposit, "", "deposit of proposal") cmd.Flags().String(flagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)") @@ -147,6 +161,7 @@ func parseSubmitProposalFlags() (*proposal, error) { if proposalFile == "" { proposal.Title = viper.GetString(flagTitle) proposal.Description = viper.GetString(flagDescription) + proposal.VotingPeriod = viper.GetInt64(flagVotingPeriod) proposal.Type = client.NormalizeProposalType(viper.GetString(flagProposalType)) proposal.Deposit = viper.GetString(flagDeposit) return proposal, nil @@ -571,6 +586,7 @@ func GetCmdSubmitListProposal(cdc *codec.Codec) *cobra.Command { quoteAsset := viper.GetString(flagQuoteAsset) initPrice := viper.GetInt64(flagInitPrice) expireTimestamp := viper.GetInt64(flagExpireTime) + votingPeriodInSeconds := viper.GetInt64(flagVotingPeriod) if title == "" { return errors.New("Title should not be empty") @@ -597,6 +613,15 @@ func GetCmdSubmitListProposal(cdc *codec.Codec) *cobra.Command { return errors.New("expire time should after now") } + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return errors.New(fmt.Sprintf("voting period should be less than %d seconds", gov.MaxVotingPeriod/time.Second)) + } + fromAddr, err := cliCtx.GetFromAddress() if err != nil { return err @@ -619,7 +644,7 @@ func GetCmdSubmitListProposal(cdc *codec.Codec) *cobra.Command { if err != nil { return err } - msg := gov.NewMsgSubmitProposal(title, string(listParamsBz), gov.ProposalTypeListTradingPair, fromAddr, amount) + msg := gov.NewMsgSubmitProposal(title, string(listParamsBz), gov.ProposalTypeListTradingPair, fromAddr, amount, votingPeriod) err = msg.ValidateBasic() if err != nil { @@ -633,6 +658,7 @@ func GetCmdSubmitListProposal(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagTitle, "", "title of proposal") cmd.Flags().String(flagDescription, "", "description of proposal") + cmd.Flags().Int64(flagVotingPeriod, 7*24*60*60, "voting period in seconds") cmd.Flags().String(flagDeposit, "", "deposit of proposal") cmd.Flags().String(flagBaseAsset, "", "base asset symbol") cmd.Flags().String(flagQuoteAsset, "", "quote asset symbol") diff --git a/x/gov/client/cli/tx_test.go b/x/gov/client/cli/tx_test.go index e3aed05ff..73df8c290 100644 --- a/x/gov/client/cli/tx_test.go +++ b/x/gov/client/cli/tx_test.go @@ -1,10 +1,11 @@ package cli import ( - "github.com/spf13/viper" - "github.com/stretchr/testify/require" "io/ioutil" "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" ) func TestParseSubmitProposalFlags(t *testing.T) { diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 842edfdac..d49ffcd55 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -3,16 +3,17 @@ package rest import ( "fmt" "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/pkg/errors" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/gov/client" - "github.com/gorilla/mux" - "github.com/pkg/errors" ) // REST Variable names @@ -44,6 +45,7 @@ type postProposalReq struct { BaseReq utils.BaseReq `json:"base_req"` Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal + VotingPeriod int64 `json:"voting_period"` // Voting period in seconds ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit @@ -81,8 +83,19 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } + if req.VotingPeriod <= 0 { + utils.WriteErrorResponse(w, http.StatusBadRequest, "voting period should be positive") + return + } + + votingPeriod := time.Duration(req.VotingPeriod) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + utils.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("voting period should be less than %d seconds", gov.MaxVotingPeriod/time.Second)) + return + } + // create the message - msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit) + msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit, votingPeriod) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) diff --git a/x/gov/client/utils.go b/x/gov/client/utils.go index 013f3944a..9bed23a3d 100644 --- a/x/gov/client/utils.go +++ b/x/gov/client/utils.go @@ -24,6 +24,14 @@ func NormalizeProposalType(proposalType string) string { return "ParameterChange" case "SoftwareUpgrade", "software_upgrade": return "SoftwareUpgrade" + case "ListTradingPair", "list_trading_pair": + return "ListTradingPair" + case "FeeChange", "fee_change": + return "FeeChange" + case "CreateValidator", "create_validator": + return "CreateValidator" + case "RemoveValidator", "remove_validator": + return "RemoveValidator" } return "" } diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 255e36dbf..a2fc8f786 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -29,7 +29,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) - newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[1], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[1], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, 1000) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -37,6 +37,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { gov.EndBlocker(ctx, keeper) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) @@ -47,7 +48,7 @@ func TestTickExpiredDepositPeriod(t *testing.T) { require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod) ctx = ctx.WithBlockHeader(newHeader) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) @@ -57,13 +58,14 @@ func TestTickExpiredDepositPeriod(t *testing.T) { validatorCoins := ck.GetCoins(ctx, addrs[0]) // check distribute deposits to proposer require.Equal(t, validatorCoins, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 6000e8)}) + require.Equal(t, sdk.Coins(nil), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) } func TestTickMultipleExpiredDepositPeriod(t *testing.T) { - mapp, _, keeper, stakeKeeper, addrs, pubKeys, _ := getMockApp(t, 10) + mapp, ck, keeper, stakeKeeper, addrs, pubKeys, _ := getMockApp(t, 10) validator := stake.NewValidator(sdk.ValAddress(addrs[0]), pubKeys[0], stake.Description{}) mapp.BeginBlock(abci.RequestBeginBlock{}) @@ -78,7 +80,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) - newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, 1000) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -86,6 +88,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { gov.EndBlocker(ctx, keeper) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(2) * time.Second) @@ -95,12 +98,12 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) - newProposalMsg2 := gov.NewMsgSubmitProposal("Test2", "test2", gov.ProposalTypeText, addrs[1], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5)}) + newProposalMsg2 := gov.NewMsgSubmitProposal("Test2", "test2", gov.ProposalTypeText, addrs[1], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5)}, 1000) res = govHandler(ctx, newProposalMsg2) require.True(t, res.IsOK()) newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) + newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second) ctx = ctx.WithBlockHeader(newHeader) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) @@ -108,6 +111,7 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { gov.EndBlocker(ctx, keeper) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) newHeader = ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(5) * time.Second) @@ -118,10 +122,11 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { gov.EndBlocker(ctx, keeper) require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Equal(t, sdk.Coins(nil), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) } func TestTickPassedDepositPeriod(t *testing.T) { - mapp, _, keeper, _, addrs, _, _ := getMockApp(t, 10) + mapp, ck, keeper, _, addrs, _, _ := getMockApp(t, 10) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) govHandler := gov.NewHandler(keeper) @@ -131,7 +136,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) - newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, 1000) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -141,6 +146,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { gov.EndBlocker(ctx, keeper) require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) newHeader := ctx.BlockHeader() newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) @@ -157,6 +163,7 @@ func TestTickPassedDepositPeriod(t *testing.T) { require.NotNil(t, keeper.InactiveProposalQueuePeek(ctx)) require.True(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 2000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) gov.EndBlocker(ctx, keeper) @@ -164,7 +171,6 @@ func TestTickPassedDepositPeriod(t *testing.T) { require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) require.NotNil(t, keeper.ActiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) - } func TestTickPassedVotingPeriodRejected(t *testing.T) { @@ -177,6 +183,8 @@ func TestTickPassedVotingPeriodRejected(t *testing.T) { // create validator stakeKeeper.SetValidator(ctx, validator) stakeKeeper.SetValidatorByConsAddr(ctx, validator) + stakeKeeper.Delegate(ctx, sdk.AccAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 1000), validator, true) + stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) govHandler := gov.NewHandler(keeper) @@ -185,7 +193,8 @@ func TestTickPassedVotingPeriodRejected(t *testing.T) { require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) - newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + votingPeriod := 1000 * time.Second + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, votingPeriod) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -201,9 +210,19 @@ func TestTickPassedVotingPeriodRejected(t *testing.T) { require.True(t, res.IsOK()) gov.EndBlocker(ctx, keeper) + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) + ctx = ctx.WithBlockHeader(newHeader) + newVoteMsg := gov.NewMsgVote(addrs[0], int64(proposalID), gov.OptionNo) + res = govHandler(ctx, newVoteMsg) + require.True(t, res.IsOK()) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 2000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) + + gov.EndBlocker(ctx, keeper) + // pass voting period newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetVotingProcedure(ctx).VotingPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(votingPeriod) ctx = ctx.WithBlockHeader(newHeader) require.True(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) @@ -219,11 +238,11 @@ func TestTickPassedVotingPeriodRejected(t *testing.T) { require.False(t, depositsIterator.Valid()) depositsIterator.Close() require.Equal(t, gov.StatusRejected, keeper.GetProposal(ctx, int64(proposalID)).GetStatus()) - require.True(t, keeper.GetProposal(ctx, int64(proposalID)).GetTallyResult().Equals(gov.EmptyTallyResult())) // check distribute deposits to proposer validatorCoins := ck.GetCoins(ctx, addrs[0]) require.Equal(t, validatorCoins, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 6000e8)}) + require.Equal(t, sdk.Coins(nil), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) } func TestTickPassedVotingPeriodPassed(t *testing.T) { @@ -248,7 +267,8 @@ func TestTickPassedVotingPeriodPassed(t *testing.T) { require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) require.False(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) - newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + votingPeriod := 1000 * time.Second + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, votingPeriod) res := govHandler(ctx, newProposalMsg) require.True(t, res.IsOK()) @@ -262,6 +282,7 @@ func TestTickPassedVotingPeriodPassed(t *testing.T) { newDepositMsg := gov.NewMsgDeposit(addrs[1], int64(proposalID), sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) res = govHandler(ctx, newDepositMsg) require.True(t, res.IsOK()) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 2000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) gov.EndBlocker(ctx, keeper) newHeader = ctx.BlockHeader() @@ -269,13 +290,12 @@ func TestTickPassedVotingPeriodPassed(t *testing.T) { ctx = ctx.WithBlockHeader(newHeader) newVoteMsg := gov.NewMsgVote(addrs[0], int64(proposalID), gov.OptionYes) res = govHandler(ctx, newVoteMsg) - println(res.Log) require.True(t, res.IsOK()) gov.EndBlocker(ctx, keeper) // pass voting period newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetVotingProcedure(ctx).VotingPeriod) + newHeader.Time = ctx.BlockHeader().Time.Add(votingPeriod) ctx = ctx.WithBlockHeader(newHeader) require.True(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) @@ -292,6 +312,162 @@ func TestTickPassedVotingPeriodPassed(t *testing.T) { depositsIterator.Close() require.Equal(t, gov.StatusPassed, keeper.GetProposal(ctx, int64(proposalID)).GetStatus()) + // check refund deposits + validatorCoins := ck.GetCoins(ctx, addrs[0]) + require.Equal(t, validatorCoins, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5000e8)}) + require.Equal(t, sdk.Coins(nil), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) +} + +func TestTickPassedVotingPeriodUnreachedQuorum(t *testing.T) { + mapp, ck, keeper, stakeKeeper, addrs, pubKeys, _ := getMockApp(t, 3) + + validator0 := stake.NewValidator(sdk.ValAddress(addrs[0]), pubKeys[0], stake.Description{}) + validator1 := stake.NewValidator(sdk.ValAddress(addrs[1]), pubKeys[1], stake.Description{}) + + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{ProposerAddress: pubKeys[0].Address()}) + + // create and delegate validator + stakeKeeper.SetValidator(ctx, validator0) + stakeKeeper.SetValidatorByConsAddr(ctx, validator0) + stakeKeeper.SetValidator(ctx, validator1) + stakeKeeper.SetValidatorByConsAddr(ctx, validator1) + stakeKeeper.Delegate(ctx, sdk.AccAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 1000), validator0, true) + stakeKeeper.Delegate(ctx, sdk.AccAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 2000), validator1, true) + + stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator0, _ = stakeKeeper.GetValidator(ctx, validator0.OperatorAddr) + + govHandler := gov.NewHandler(keeper) + + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + require.False(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) + + votingPeriod := 1000 * time.Second + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, votingPeriod) + + res := govHandler(ctx, newProposalMsg) + require.True(t, res.IsOK()) + + proposalID, _ := strconv.Atoi(string(res.Data)) + + newHeader := ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) + ctx = ctx.WithBlockHeader(newHeader) + + newDepositMsg := gov.NewMsgDeposit(addrs[1], int64(proposalID), sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + res = govHandler(ctx, newDepositMsg) + require.True(t, res.IsOK()) + gov.EndBlocker(ctx, keeper) + + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) + ctx = ctx.WithBlockHeader(newHeader) + newVoteMsg := gov.NewMsgVote(addrs[0], int64(proposalID), gov.OptionYes) + res = govHandler(ctx, newVoteMsg) + require.True(t, res.IsOK()) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 2000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) + + gov.EndBlocker(ctx, keeper) + + // pass voting period + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(votingPeriod) + ctx = ctx.WithBlockHeader(newHeader) + + require.True(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) + depositsIterator := keeper.GetDeposits(ctx, int64(proposalID)) + require.True(t, depositsIterator.Valid()) + depositsIterator.Close() + require.Equal(t, gov.StatusVotingPeriod, keeper.GetProposal(ctx, int64(proposalID)).GetStatus()) + + gov.EndBlocker(ctx, keeper) + + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + depositsIterator = keeper.GetDeposits(ctx, int64(proposalID)) + require.False(t, depositsIterator.Valid()) + depositsIterator.Close() + require.Equal(t, gov.StatusRejected, keeper.GetProposal(ctx, int64(proposalID)).GetStatus()) + + // check refund deposits + validatorCoins := ck.GetCoins(ctx, addrs[0]) + require.Equal(t, validatorCoins, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5000e8)}) + require.Equal(t, sdk.Coins(nil), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) +} + +func TestTickPassedVotingPeriodAllAbstain(t *testing.T) { + mapp, ck, keeper, stakeKeeper, addrs, pubKeys, _ := getMockApp(t, 3) + + validator0 := stake.NewValidator(sdk.ValAddress(addrs[0]), pubKeys[0], stake.Description{}) + validator1 := stake.NewValidator(sdk.ValAddress(addrs[1]), pubKeys[1], stake.Description{}) + + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{ProposerAddress: pubKeys[0].Address()}) + + // create and delegate validator + stakeKeeper.SetValidator(ctx, validator0) + stakeKeeper.SetValidatorByConsAddr(ctx, validator0) + stakeKeeper.SetValidator(ctx, validator1) + stakeKeeper.SetValidatorByConsAddr(ctx, validator1) + stakeKeeper.Delegate(ctx, sdk.AccAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 1000), validator0, true) + stakeKeeper.Delegate(ctx, sdk.AccAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 2000), validator1, true) + + stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator0, _ = stakeKeeper.GetValidator(ctx, validator0.OperatorAddr) + + govHandler := gov.NewHandler(keeper) + + require.Nil(t, keeper.InactiveProposalQueuePeek(ctx)) + require.False(t, gov.ShouldPopInactiveProposalQueue(ctx, keeper)) + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + require.False(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) + + votingPeriod := 1000 * time.Second + newProposalMsg := gov.NewMsgSubmitProposal("Test", "test", gov.ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}, votingPeriod) + + res := govHandler(ctx, newProposalMsg) + require.True(t, res.IsOK()) + + proposalID, _ := strconv.Atoi(string(res.Data)) + + newHeader := ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) + ctx = ctx.WithBlockHeader(newHeader) + + newDepositMsg := gov.NewMsgDeposit(addrs[1], int64(proposalID), sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 1000e8)}) + res = govHandler(ctx, newDepositMsg) + require.True(t, res.IsOK()) + gov.EndBlocker(ctx, keeper) + + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) + ctx = ctx.WithBlockHeader(newHeader) + newVoteMsg := gov.NewMsgVote(addrs[1], int64(proposalID), gov.OptionAbstain) + res = govHandler(ctx, newVoteMsg) + require.True(t, res.IsOK()) + gov.EndBlocker(ctx, keeper) + + // pass voting period + newHeader = ctx.BlockHeader() + newHeader.Time = ctx.BlockHeader().Time.Add(votingPeriod) + ctx = ctx.WithBlockHeader(newHeader) + + require.True(t, gov.ShouldPopActiveProposalQueue(ctx, keeper)) + depositsIterator := keeper.GetDeposits(ctx, int64(proposalID)) + require.True(t, depositsIterator.Valid()) + depositsIterator.Close() + require.Equal(t, gov.StatusVotingPeriod, keeper.GetProposal(ctx, int64(proposalID)).GetStatus()) + + gov.EndBlocker(ctx, keeper) + + require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) + depositsIterator = keeper.GetDeposits(ctx, int64(proposalID)) + require.False(t, depositsIterator.Valid()) + depositsIterator.Close() + require.Equal(t, gov.StatusRejected, keeper.GetProposal(ctx, int64(proposalID)).GetStatus()) + // check refund deposits validatorCoins := ck.GetCoins(ctx, addrs[0]) require.Equal(t, validatorCoins, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 5000e8)}) diff --git a/x/gov/errors.go b/x/gov/errors.go index d9c53bbee..058fcb859 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -3,6 +3,7 @@ package gov import ( "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -22,6 +23,7 @@ const ( CodeInvalidGenesis sdk.CodeType = 10 CodeInvalidProposalStatus sdk.CodeType = 11 CodeInvalidProposal sdk.CodeType = 12 + CodeInvalidVotingPeriod sdk.CodeType = 13 ) //---------------------------------------- @@ -70,3 +72,7 @@ func ErrInvalidVote(codespace sdk.CodespaceType, voteOption VoteOption) sdk.Erro func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error { return sdk.NewError(codespace, CodeInvalidVote, msg) } + +func ErrInvalidVotingPeriod(codespace sdk.CodespaceType, votingPeriod time.Duration) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVotingPeriod, fmt.Sprintf("Voting period '%d' should larger than 0 and less than %s", votingPeriod, MaxVotingPeriod)) +} diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 72d90f3f3..0dfee258d 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -12,18 +12,16 @@ const ( // GenesisState - all staking state that must be provided at genesis type GenesisState struct { - StartingProposalID int64 `json:"starting_proposalID"` - DepositProcedure DepositProcedure `json:"deposit_period"` - VotingProcedure VotingProcedure `json:"voting_period"` - TallyingProcedure TallyingProcedure `json:"tallying_procedure"` + StartingProposalID int64 `json:"starting_proposalID"` + DepositParams DepositParams `json:"deposit_params"` + TallyParams TallyParams `json:"tally_params"` } -func NewGenesisState(startingProposalID int64, dp DepositProcedure, vp VotingProcedure, tp TallyingProcedure) GenesisState { +func NewGenesisState(startingProposalID int64, dp DepositParams, tp TallyParams) GenesisState { return GenesisState{ StartingProposalID: startingProposalID, - DepositProcedure: dp, - VotingProcedure: vp, - TallyingProcedure: tp, + DepositParams: dp, + TallyParams: tp, } } @@ -31,17 +29,14 @@ func NewGenesisState(startingProposalID int64, dp DepositProcedure, vp VotingPro func DefaultGenesisState() GenesisState { return GenesisState{ StartingProposalID: 1, - DepositProcedure: DepositProcedure{ + DepositParams: DepositParams{ MinDeposit: sdk.Coins{sdk.NewCoin(DefaultDepositDenom, 2000e8)}, - MaxDepositPeriod: time.Duration(2*7*24) * time.Hour, // 2 weeks + MaxDepositPeriod: time.Duration(2*24) * time.Hour, // 2 days }, - VotingProcedure: VotingProcedure{ - VotingPeriod: time.Duration(2*7*24) * time.Hour, // 2 weeks - }, - TallyingProcedure: TallyingProcedure{ - Threshold: sdk.NewDecWithPrec(5, 1), - Veto: sdk.NewDecWithPrec(334, 3), - GovernancePenalty: sdk.NewDecWithPrec(1, 2), + TallyParams: TallyParams{ + Quorum: sdk.NewDecWithPrec(5, 1), + Threshold: sdk.NewDecWithPrec(5, 1), + Veto: sdk.NewDecWithPrec(334, 3), }, } } @@ -53,22 +48,19 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // TODO: Handle this with #870 panic(err) } - k.setDepositProcedure(ctx, data.DepositProcedure) - k.setVotingProcedure(ctx, data.VotingProcedure) - k.setTallyingProcedure(ctx, data.TallyingProcedure) + k.setDepositParams(ctx, data.DepositParams) + k.setTallyParams(ctx, data.TallyParams) } // WriteGenesis - output genesis parameters func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { startingProposalID, _ := k.getNewProposalID(ctx) - depositProcedure := k.GetDepositProcedure(ctx) - votingProcedure := k.GetVotingProcedure(ctx) - tallyingProcedure := k.GetTallyingProcedure(ctx) + depositParams := k.GetDepositParams(ctx) + tallyingParams := k.GetTallyParams(ctx) return GenesisState{ StartingProposalID: startingProposalID, - DepositProcedure: depositProcedure, - VotingProcedure: votingProcedure, - TallyingProcedure: tallyingProcedure, + DepositParams: depositParams, + TallyParams: tallyingParams, } } diff --git a/x/gov/handler.go b/x/gov/handler.go index d045148d3..574653017 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -26,7 +26,7 @@ func NewHandler(keeper Keeper) sdk.Handler { func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result { - proposal := keeper.NewTextProposal(ctx, msg.Title, msg.Description, msg.ProposalType) + proposal := keeper.NewTextProposal(ctx, msg.Title, msg.Description, msg.ProposalType, msg.VotingPeriod) hooksErr := keeper.OnProposalSubmitted(ctx, proposal) if hooksErr != nil { @@ -83,17 +83,14 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result } func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { - isValidator := false - keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) { - if sdk.ValAddress(msg.Voter).Equals(validator.GetOperator()) { - isValidator = true - return true - } - return false - }) + validator := keeper.vs.Validator(ctx, sdk.ValAddress(msg.Voter)) + + if validator == nil { + return sdk.ErrUnauthorized("Vote is not from a validator operator").Result() + } - if !isValidator { - return sdk.ErrUnauthorized("Non validator").Result() + if validator.GetPower().IsZero() { + return sdk.ErrUnauthorized("Validator is not bonded").Result() } err := keeper.AddVote(ctx, msg.ProposalID, msg.Voter, msg.Option) @@ -114,13 +111,13 @@ func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { } // Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, passedProposals, failedProposals []int64) { +func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, refundProposals, notRefundProposals []int64) { logger := ctx.Logger().With("module", "x/gov") resTags = sdk.NewTags() - passedProposals = make([]int64, 0) - failedProposals = make([]int64, 0) + refundProposals = make([]int64, 0) + notRefundProposals = make([]int64, 0) // Delete proposals that haven't met minDeposit for ShouldPopInactiveProposalQueue(ctx, keeper) { @@ -135,6 +132,9 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, passedProposa keeper.DistributeDeposits(ctx, inactiveProposal.GetProposalID()) keeper.DeleteProposal(ctx, inactiveProposal) + + notRefundProposals = append(notRefundProposals, inactiveProposal.GetProposalID()) + resTags.AppendTag(tags.Action, tags.ActionProposalDropped) resTags.AppendTag(tags.ProposalID, proposalIDBytes) @@ -142,7 +142,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, passedProposa fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %v (had only %v); distribute to validator", inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), - keeper.GetDepositProcedure(ctx).MinDeposit, + keeper.GetDepositParams(ctx).MinDeposit, inactiveProposal.GetTotalDeposit(), ), ) @@ -153,12 +153,12 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, passedProposa activeProposal := keeper.ActiveProposalQueuePop(ctx) proposalStartTime := activeProposal.GetVotingStartTime() - votingPeriod := keeper.GetVotingProcedure(ctx).VotingPeriod + votingPeriod := activeProposal.GetVotingPeriod() if ctx.BlockHeader().Time.Before(proposalStartTime.Add(votingPeriod)) { continue } - passes, tallyResults := Tally(ctx, keeper, activeProposal) + passes, refundDeposits, tallyResults := Tally(ctx, keeper, activeProposal) proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) var action []byte if passes { @@ -167,14 +167,19 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, passedProposa // refund deposits keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) - passedProposals = append(passedProposals, activeProposal.GetProposalID()) + refundProposals = append(refundProposals, activeProposal.GetProposalID()) } else { activeProposal.SetStatus(StatusRejected) action = tags.ActionProposalRejected - // distribute deposits to proposer - keeper.DistributeDeposits(ctx, activeProposal.GetProposalID()) - failedProposals = append(failedProposals, activeProposal.GetProposalID()) + // if votes reached quorum and not all votes are abstain, distribute deposits to validator, else refund deposits + if refundDeposits { + keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) + refundProposals = append(refundProposals, activeProposal.GetProposalID()) + } else { + keeper.DistributeDeposits(ctx, activeProposal.GetProposalID()) + notRefundProposals = append(notRefundProposals, activeProposal.GetProposalID()) + } } activeProposal.SetTallyResult(tallyResults) @@ -189,27 +194,27 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags, passedProposa return } + func ShouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool { - depositProcedure := keeper.GetDepositProcedure(ctx) + depositParams := keeper.GetDepositParams(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)) { + } else if !ctx.BlockHeader().Time.Before(peekProposal.GetSubmitTime().Add(depositParams.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)) { + } else if !ctx.BlockHeader().Time.Before(peekProposal.GetVotingStartTime().Add(peekProposal.GetVotingPeriod())) { return true } return false diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 047a6a85f..1cb0206c2 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -2,6 +2,9 @@ package gov import ( "fmt" + "time" + + "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,22 +14,22 @@ import ( // Parameter store default namestore const ( - DefaultParamspace = "gov" + DefaultParamSpace = "gov" ) // Parameter store key var ( - ParamStoreKeyDepositProcedure = []byte("depositprocedure") - ParamStoreKeyVotingProcedure = []byte("votingprocedure") - ParamStoreKeyTallyingProcedure = []byte("tallyingprocedure") + ParamStoreKeyDepositParams = []byte("depositparams") + ParamStoreKeyTallyParams = []byte("tallyparams") + + DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("BinanceChainDepositedCoins"))) ) // Type declaration for parameters func ParamTypeTable() params.TypeTable { return params.NewTypeTable( - ParamStoreKeyDepositProcedure, DepositProcedure{}, - ParamStoreKeyVotingProcedure, VotingProcedure{}, - ParamStoreKeyTallyingProcedure, TallyingProcedure{}, + ParamStoreKeyDepositParams, DepositParams{}, + ParamStoreKeyTallyParams, TallyParams{}, ) } @@ -98,7 +101,7 @@ func (keeper Keeper) AddHooks(proposalType ProposalKind, hooks GovHooks) Keeper // Proposals // Creates a NewProposal -func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind) Proposal { +func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind, votingPeriod time.Duration) Proposal { proposalID, err := keeper.getNewProposalID(ctx) if err != nil { return nil @@ -108,6 +111,7 @@ func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description Title: title, Description: description, ProposalType: proposalType, + VotingPeriod: votingPeriod, Status: StatusDepositPeriod, TallyResult: EmptyTallyResult(), TotalDeposit: sdk.Coins{}, @@ -269,45 +273,32 @@ func (keeper Keeper) ActivateVotingPeriod(ctx sdk.Context, proposal Proposal) { } // ===================================================== -// Procedures +// Params -// Returns the current Deposit Procedure from the global param store +// Returns the current Deposit Params from the global param store // nolint: errcheck -func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure { - var depositProcedure DepositProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) - return depositProcedure +func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams { + var depositParams DepositParams + keeper.paramSpace.Get(ctx, ParamStoreKeyDepositParams, &depositParams) + return depositParams } -// Returns the current Voting Procedure from the global param store +// Returns the current Tally Params from the global param store // nolint: errcheck -func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure { - var votingProcedure VotingProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) - return votingProcedure +func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams { + var tallyParams TallyParams + keeper.paramSpace.Get(ctx, ParamStoreKeyTallyParams, &tallyParams) + return tallyParams } -// Returns the current Tallying Procedure from the global param store // nolint: errcheck -func (keeper Keeper) GetTallyingProcedure(ctx sdk.Context) TallyingProcedure { - var tallyingProcedure TallyingProcedure - keeper.paramSpace.Get(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) - return tallyingProcedure +func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams) } // nolint: errcheck -func (keeper Keeper) setDepositProcedure(ctx sdk.Context, depositProcedure DepositProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyDepositProcedure, &depositProcedure) -} - -// nolint: errcheck -func (keeper Keeper) setVotingProcedure(ctx sdk.Context, votingProcedure VotingProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyVotingProcedure, &votingProcedure) -} - -// nolint: errcheck -func (keeper Keeper) setTallyingProcedure(ctx sdk.Context, tallyingProcedure TallyingProcedure) { - keeper.paramSpace.Set(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure) +func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) { + keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams) } // ===================================================== @@ -401,13 +392,14 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false } - // Subtract coins from depositer's account - _, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount) + // Send coins from depositor's account to DepositedCoinsAccAddr account + _, err := keeper.ck.SendCoins(ctx, depositerAddr, DepositedCoinsAccAddr, depositAmount) if err != nil { return err, false } + if ctx.IsDeliverTx() { - keeper.pool.AddAddrs([]sdk.AccAddress{depositerAddr}) + keeper.pool.AddAddrs([]sdk.AccAddress{depositerAddr, DepositedCoinsAccAddr}) } // Update Proposal @@ -417,7 +409,7 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr // Check if deposit tipped proposal into voting period // Active voting period if so activatedVotingPeriod := false - if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositProcedure(ctx).MinDeposit) { + if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositParams(ctx).MinDeposit) { keeper.ActivateVotingPeriod(ctx, proposal) activatedVotingPeriod = true } @@ -450,22 +442,12 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) { deposit := &Deposit{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - _, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount) + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositer, deposit.Amount) if err != nil { panic(fmt.Sprintf("refund error(%s) should not happen", err.Error())) } - keeper.pool.AddAddrs([]sdk.AccAddress{deposit.Depositer}) - store.Delete(depositsIterator.Key()) - } -} - -// Deletes all the deposits on a specific proposal without refunding them -func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) { - store := ctx.KVStore(keeper.storeKey) - depositsIterator := keeper.GetDeposits(ctx, proposalID) - defer depositsIterator.Close() - for ; depositsIterator.Valid(); depositsIterator.Next() { + keeper.pool.AddAddrs([]sdk.AccAddress{deposit.Depositer, DepositedCoinsAccAddr}) store.Delete(depositsIterator.Key()) } } @@ -493,11 +475,11 @@ func (keeper Keeper) DistributeDeposits(ctx sdk.Context, proposalID int64) { ctx.Logger().Info("distribute empty deposits") } - _, _, err := keeper.ck.AddCoins(ctx, sdk.AccAddress(proposerAccAddr), depositCoins) + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, sdk.AccAddress(proposerAccAddr), depositCoins) if err != nil { panic(fmt.Sprintf("distribute deposits error(%s) should not happen", err.Error())) } - keeper.pool.AddAddrs([]sdk.AccAddress{sdk.AccAddress(proposerAccAddr)}) + keeper.pool.AddAddrs([]sdk.AccAddress{sdk.AccAddress(proposerAccAddr), DepositedCoinsAccAddr}) } // ===================================================== @@ -542,9 +524,34 @@ func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal { return keeper.GetProposal(ctx, frontElement) } -// Add a proposalID to the back of the ProposalQueue +// Add a proposalID to the ProposalQueue sorted by expire time func (keeper Keeper) ActiveProposalQueuePush(ctx sdk.Context, proposal Proposal) { - proposalQueue := append(keeper.getActiveProposalQueue(ctx), proposal.GetProposalID()) + proposalQueue := keeper.getActiveProposalQueue(ctx) + if len(proposalQueue) == 0 { + proposalQueue = append(proposalQueue, proposal.GetProposalID()) + } else { + votingExpireTime := proposal.GetVotingStartTime().Add(proposal.GetVotingPeriod()) + + // sort proposal queue by expire time + newProposalQueue := make(ProposalQueue, 0, len(proposalQueue)+1) + for idx, proposalId := range proposalQueue { + tmpProposal := keeper.GetProposal(ctx, proposalId) + tmpVotingExpireTime := tmpProposal.GetVotingStartTime().Add(tmpProposal.GetVotingPeriod()) + if tmpVotingExpireTime.After(votingExpireTime) { + newProposalQueue = append(newProposalQueue, proposal.GetProposalID()) + newProposalQueue = append(newProposalQueue, proposalQueue[idx:]...) + break + } else { + newProposalQueue = append(newProposalQueue, proposalId) + } + } + // insert proposal if there is no proposal in proposal queue which voting expire time after proposal + if len(newProposalQueue) == len(proposalQueue) { + newProposalQueue = append(newProposalQueue, proposal.GetProposalID()) + } + + proposalQueue = newProposalQueue + } keeper.setActiveProposalQueue(ctx, proposalQueue) } diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index b56cfb10e..ef216533d 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -4,10 +4,11 @@ import ( "testing" "time" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/gov" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" ) func TestGetSetProposal(t *testing.T) { @@ -15,7 +16,7 @@ func TestGetSetProposal(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() keeper.SetProposal(ctx, proposal) @@ -28,12 +29,12 @@ func TestIncrementProposalNumber(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) - keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) - keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) - proposal6 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposal6 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) require.Equal(t, int64(6), proposal6.GetProposalID()) } @@ -43,7 +44,7 @@ func TestActivateVotingPeriod(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) require.True(t, proposal.GetVotingStartTime().Equal(time.Time{})) require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) @@ -54,13 +55,80 @@ func TestActivateVotingPeriod(t *testing.T) { require.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID()) } +func TestPushActiveProposalQueue(t *testing.T) { + mapp, _, keeper, _, _, _, _ := getMockApp(t, 0) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) + + // insert one proposal + proposal1 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + + keeper.ActivateVotingPeriod(ctx, proposal1) + + require.Equal(t, proposal1.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + + require.Nil(t, keeper.ActiveProposalQueuePop(ctx)) + + // insert two proposal + proposal2 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposal3 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 2000*time.Second) + + keeper.ActivateVotingPeriod(ctx, proposal2) + keeper.ActivateVotingPeriod(ctx, proposal3) + + require.Equal(t, proposal2.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + require.Equal(t, proposal3.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + + // insert two proposal + proposal4 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 2000*time.Second) + proposal5 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + + keeper.ActivateVotingPeriod(ctx, proposal4) + keeper.ActivateVotingPeriod(ctx, proposal5) + + require.Equal(t, proposal5.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + require.Equal(t, proposal4.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + + require.Nil(t, keeper.ActiveProposalQueuePop(ctx)) + + // insert multiple proposals in order + proposal6 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposal7 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 2000*time.Second) + proposal8 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 3000*time.Second) + + keeper.ActivateVotingPeriod(ctx, proposal6) + keeper.ActivateVotingPeriod(ctx, proposal7) + keeper.ActivateVotingPeriod(ctx, proposal8) + + require.Equal(t, proposal6.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + require.Equal(t, proposal7.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + require.Equal(t, proposal8.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + + require.Nil(t, keeper.ActiveProposalQueuePop(ctx)) + + // insert multiple proposals in random order + proposal9 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposal10 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 2000*time.Second) + proposal11 := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 3000*time.Second) + + keeper.ActivateVotingPeriod(ctx, proposal11) + keeper.ActivateVotingPeriod(ctx, proposal9) + keeper.ActivateVotingPeriod(ctx, proposal10) + + require.Equal(t, proposal9.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + require.Equal(t, proposal10.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + require.Equal(t, proposal11.GetProposalID(), keeper.ActiveProposalQueuePop(ctx).GetProposalID()) + + require.Nil(t, keeper.ActiveProposalQueuePop(ctx)) +} + func TestDeposits(t *testing.T) { mapp, ck, keeper, _, addrs, _, _ := getMockApp(t, 2) SortAddresses(addrs) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() fiveHundredSteak := sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 500e8)} @@ -90,6 +158,7 @@ func TestDeposits(t *testing.T) { require.Equal(t, addrs[0], deposit.Depositer) require.Equal(t, fiveHundredSteak, keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) require.Equal(t, addr0Initial.Minus(fiveHundredSteak), ck.GetCoins(ctx, addrs[0])) + require.Equal(t, fiveHundredSteak, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) // Check a second deposit from same address err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[0], oneThousandSteak) @@ -101,6 +170,7 @@ func TestDeposits(t *testing.T) { require.Equal(t, addrs[0], deposit.Depositer) require.Equal(t, fiveHundredSteak.Plus(oneThousandSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) require.Equal(t, addr0Initial.Minus(fiveHundredSteak).Minus(oneThousandSteak), ck.GetCoins(ctx, addrs[0])) + require.Equal(t, fiveHundredSteak.Plus(oneThousandSteak), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) // Check third deposit from a new address err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[1], fiveHundredSteak) @@ -112,6 +182,7 @@ func TestDeposits(t *testing.T) { require.Equal(t, fiveHundredSteak, deposit.Amount) require.Equal(t, fiveHundredSteak.Plus(oneThousandSteak).Plus(fiveHundredSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit()) require.Equal(t, addr1Initial.Minus(fiveHundredSteak), ck.GetCoins(ctx, addrs[1])) + require.Equal(t, sdk.Coins{sdk.NewCoin(gov.DefaultDepositDenom, 2000e8)}, ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) // Check that proposal moved to voting period require.True(t, keeper.GetProposal(ctx, proposalID).GetVotingStartTime().Equal(ctx.BlockHeader().Time)) @@ -141,6 +212,7 @@ func TestDeposits(t *testing.T) { require.False(t, found) require.Equal(t, addr0Initial, ck.GetCoins(ctx, addrs[0])) require.Equal(t, addr1Initial, ck.GetCoins(ctx, addrs[1])) + require.Equal(t, sdk.Coins(nil), ck.GetCoins(ctx, gov.DepositedCoinsAccAddr)) } func TestVotes(t *testing.T) { @@ -149,7 +221,7 @@ func TestVotes(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) @@ -209,10 +281,10 @@ func TestProposalQueues(t *testing.T) { require.Nil(t, keeper.ActiveProposalQueuePeek(ctx)) // create test proposals - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) - proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", gov.ProposalTypeText) - proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", gov.ProposalTypeText) - proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", gov.ProposalTypeText, 1000*time.Second) + proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", gov.ProposalTypeText, 1000*time.Second) + proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", gov.ProposalTypeText, 1000*time.Second) // test pushing to inactive proposal queue keeper.InactiveProposalQueuePush(ctx, proposal) diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 82dc89860..390200b41 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -7,12 +7,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// name to idetify transaction types +// name to identify transaction types const ( MsgRoute = "gov" MaxTitleLength = 128 MaxDescriptionLength int = 2048 + MaxVotingPeriod = 2 * 7 * 24 * 60 * 60 * time.Second // 2 weeks ) var _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgDeposit{}, MsgVote{} @@ -34,15 +35,17 @@ type MsgSubmitProposal struct { ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer InitialDeposit sdk.Coins `json:"initial_deposit"` // Initial deposit paid by sender. Must be strictly positive. + VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period (s) } -func NewMsgSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins) MsgSubmitProposal { +func NewMsgSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins, votingPeriod time.Duration) MsgSubmitProposal { return MsgSubmitProposal{ Title: title, Description: description, ProposalType: proposalType, Proposer: proposer, InitialDeposit: initialDeposit, + VotingPeriod: votingPeriod, } } @@ -67,8 +70,8 @@ func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { if !validProposalType(msg.ProposalType) { return ErrInvalidProposalType(DefaultCodespace, msg.ProposalType) } - if len(msg.Proposer) == 0 { - return sdk.ErrInvalidAddress(msg.Proposer.String()) + if len(msg.Proposer) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("length of address(%s) should be %d", string(msg.Proposer), sdk.AddrLen)) } if !msg.InitialDeposit.IsValid() { return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) @@ -76,11 +79,14 @@ func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { if !msg.InitialDeposit.IsNotNegative() { return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) } + if msg.VotingPeriod <= 0 || msg.VotingPeriod > MaxVotingPeriod { + return ErrInvalidVotingPeriod(DefaultCodespace, msg.VotingPeriod) + } return nil } func (msg MsgSubmitProposal) String() string { - return fmt.Sprintf("MsgSubmitProposal{%s, %s, %s, %v}", msg.Title, msg.Description, msg.ProposalType, msg.InitialDeposit) + return fmt.Sprintf("MsgSubmitProposal{%s, %s, %s, %v, %s}", msg.Title, msg.Description, msg.ProposalType, msg.InitialDeposit, msg.VotingPeriod) } // Implements Msg. @@ -129,8 +135,8 @@ func (msg MsgDeposit) Type() string { return "deposit" } // Implements Msg. func (msg MsgDeposit) ValidateBasic() sdk.Error { - if len(msg.Depositer) == 0 { - return sdk.ErrInvalidAddress(msg.Depositer.String()) + if len(msg.Depositer) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("length of address(%s) should be %d", string(msg.Depositer), sdk.AddrLen)) } if !msg.Amount.IsValid() { return sdk.ErrInvalidCoins(msg.Amount.String()) @@ -194,8 +200,8 @@ func (msg MsgVote) Type() string { return "vote" } // Implements Msg. func (msg MsgVote) ValidateBasic() sdk.Error { - if len(msg.Voter.Bytes()) == 0 { - return sdk.ErrInvalidAddress(msg.Voter.String()) + if len(msg.Voter) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("length of address(%s) should be %d", string(msg.Voter), sdk.AddrLen)) } if msg.ProposalID < 0 { return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index 1e25b7334..5563f7b80 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -3,6 +3,7 @@ package gov_test import ( "strings" "testing" + "time" "github.com/stretchr/testify/require" @@ -27,24 +28,28 @@ func TestMsgSubmitProposal(t *testing.T) { proposalType gov.ProposalKind proposerAddr sdk.AccAddress initialDeposit sdk.Coins + votingPeriod time.Duration expectPass bool }{ - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsPos, true}, - {"", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsPos, false}, - {"Test Proposal", "", gov.ProposalTypeText, addrs[0], coinsPos, false}, - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeParameterChange, addrs[0], coinsPos, true}, - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeSoftwareUpgrade, addrs[0], coinsPos, true}, - {"Test Proposal", "the purpose of this proposal is to test", 0x06, addrs[0], coinsPos, false}, - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, sdk.AccAddress{}, coinsPos, false}, - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsZero, true}, - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsNeg, false}, - {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsMulti, true}, - {strings.Repeat("#", gov.MaxTitleLength*2), "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsMulti, false}, - {"Test Proposal", strings.Repeat("#", gov.MaxDescriptionLength*2), gov.ProposalTypeText, addrs[0], coinsMulti, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsPos, 1000 * time.Second, true}, + {"", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsPos, 1000 * time.Second, false}, + {"Test Proposal", "", gov.ProposalTypeText, addrs[0], coinsPos, 1000 * time.Second, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeParameterChange, addrs[0], coinsPos, 1000 * time.Second, true}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeSoftwareUpgrade, addrs[0], coinsPos, 1000 * time.Second, true}, + {"Test Proposal", "the purpose of this proposal is to test", 0x08, addrs[0], coinsPos, 1, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, sdk.AccAddress{}, coinsPos, 1000 * time.Second, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsZero, 1000 * time.Second, true}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsNeg, 1000 * time.Second, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsMulti, 1000 * time.Second, true}, + {strings.Repeat("#", gov.MaxTitleLength*2), "the purpose of this proposal is to test", gov.ProposalTypeText, addrs[0], coinsMulti, 1000 * time.Second, false}, + {"Test Proposal", strings.Repeat("#", gov.MaxDescriptionLength*2), gov.ProposalTypeText, addrs[0], coinsMulti, 1000 * time.Second, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeParameterChange, addrs[0], coinsPos, 0, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeParameterChange, addrs[0], coinsPos, 2 * gov.MaxVotingPeriod, false}, + {"Test Proposal", "the purpose of this proposal is to test", gov.ProposalTypeText, sdk.AccAddress{0, 1}, coinsZero, 1000 * time.Second, false}, } for i, tc := range tests { - msg := gov.NewMsgSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit) + msg := gov.NewMsgSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit, tc.votingPeriod) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) } else { @@ -65,6 +70,7 @@ func TestMsgDeposit(t *testing.T) { {0, addrs[0], coinsPos, true}, {-1, addrs[0], coinsPos, false}, {1, sdk.AccAddress{}, coinsPos, false}, + {1, sdk.AccAddress{0, 1}, coinsPos, false}, {1, addrs[0], coinsZero, true}, {1, addrs[0], coinsNeg, false}, {1, addrs[0], coinsMulti, true}, @@ -92,6 +98,7 @@ func TestMsgVote(t *testing.T) { {0, addrs[0], gov.OptionYes, true}, {-1, addrs[0], gov.OptionYes, false}, {0, sdk.AccAddress{}, gov.OptionYes, false}, + {0, sdk.AccAddress{1, 2}, gov.OptionYes, false}, {0, addrs[0], gov.OptionNo, true}, {0, addrs[0], gov.OptionNoWithVeto, true}, {0, addrs[0], gov.OptionAbstain, true}, diff --git a/x/gov/params.go b/x/gov/params.go new file mode 100644 index 000000000..e483be969 --- /dev/null +++ b/x/gov/params.go @@ -0,0 +1,20 @@ +package gov + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Param around Deposits for governance +type DepositParams struct { + MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period. + MaxDepositPeriod time.Duration `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months +} + +// Param around Tally votes in governance +type TallyParams struct { + Quorum sdk.Dec `json:"quorum"` // Minimum percentage of total stake needed to vote for a result to be considered valid. Initial value: 0.5 + Threshold sdk.Dec `json:"threshold"` // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5 + Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 +} diff --git a/x/gov/procedures.go b/x/gov/procedures.go deleted file mode 100644 index e453add79..000000000 --- a/x/gov/procedures.go +++ /dev/null @@ -1,25 +0,0 @@ -package gov - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Procedure around Deposits for governance -type DepositProcedure struct { - MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period. - MaxDepositPeriod time.Duration `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months -} - -// Procedure around Tallying votes in governance -type TallyingProcedure struct { - Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 - GovernancePenalty sdk.Dec `json:"governance_penalty"` // Penalty if validator does not vote -} - -// Procedure around Voting in governance -type VotingProcedure struct { - VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period. -} diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 43427c03f..e7a1fbf26 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -39,6 +39,9 @@ type Proposal interface { GetVotingStartTime() time.Time SetVotingStartTime(time.Time) + + GetVotingPeriod() time.Duration + SetVotingPeriod(time.Duration) } // checks if two proposals are equal @@ -51,7 +54,8 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { proposalA.GetTallyResult().Equals(proposalB.GetTallyResult()) && proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && - proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) { + proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) && + proposalA.GetVotingPeriod() == proposalB.GetVotingPeriod() { return true } return false @@ -60,10 +64,11 @@ func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { //----------------------------------------------------------- // Text Proposals type TextProposal struct { - ProposalID int64 `json:"proposal_id"` // ID of the proposal - Title string `json:"title"` // Title of the proposal - Description string `json:"description"` // Description of the proposal - ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} TallyResult TallyResult `json:"tally_result"` // Result of Tallys @@ -98,6 +103,10 @@ func (tp TextProposal) GetVotingStartTime() time.Time { return tp.V func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { tp.VotingStartTime = votingStartTime } +func (tp TextProposal) GetVotingPeriod() time.Duration { return tp.VotingPeriod } +func (tp *TextProposal) SetVotingPeriod(votingPeriod time.Duration) { + tp.VotingPeriod = votingPeriod +} //----------------------------------------------------------- // ProposalQueue @@ -117,8 +126,9 @@ const ( ProposalTypeSoftwareUpgrade ProposalKind = 0x03 ProposalTypeListTradingPair ProposalKind = 0x04 // ProposalTypeFeeChange belongs to ProposalTypeParameterChange. We use this to make it easily to distinguish。 - ProposalTypeFeeChange ProposalKind = 0x05 + ProposalTypeFeeChange ProposalKind = 0x05 ProposalTypeCreateValidator ProposalKind = 0x06 + ProposalTypeRemoveValidator ProposalKind = 0x07 ) // String to proposalType byte. Returns ff if invalid. @@ -136,6 +146,8 @@ func ProposalTypeFromString(str string) (ProposalKind, error) { return ProposalTypeFeeChange, nil case "CreateValidator": return ProposalTypeCreateValidator, nil + case "RemoveValidator": + return ProposalTypeRemoveValidator, nil default: return ProposalKind(0xff), errors.Errorf("'%s' is not a valid proposal type", str) } @@ -147,7 +159,9 @@ func validProposalType(pt ProposalKind) bool { pt == ProposalTypeParameterChange || pt == ProposalTypeSoftwareUpgrade || pt == ProposalTypeListTradingPair || - pt == ProposalTypeFeeChange { + pt == ProposalTypeFeeChange || + pt == ProposalTypeCreateValidator || + pt == ProposalTypeRemoveValidator { return true } return false @@ -200,6 +214,8 @@ func (pt ProposalKind) String() string { return "FeeChange" case ProposalTypeCreateValidator: return "CreateValidator" + case ProposalTypeRemoveValidator: + return "RemoveValidator" default: return "" } @@ -328,6 +344,7 @@ type TallyResult struct { Abstain sdk.Dec `json:"abstain"` No sdk.Dec `json:"no"` NoWithVeto sdk.Dec `json:"no_with_veto"` + Total sdk.Dec `json:"total"` } // checks if two proposals are equal @@ -337,13 +354,15 @@ func EmptyTallyResult() TallyResult { Abstain: sdk.ZeroDec(), No: sdk.ZeroDec(), NoWithVeto: sdk.ZeroDec(), + Total: sdk.ZeroDec(), } } // checks if two proposals are equal func (resultA TallyResult) Equals(resultB TallyResult) bool { return resultA.Yes.Equal(resultB.Yes) && - resultA.Abstain.Equal(resultB.Abstain) && - resultA.No.Equal(resultB.No) && - resultA.NoWithVeto.Equal(resultB.NoWithVeto) + resultA.Abstain.Equal(resultB.Abstain) && + resultA.No.Equal(resultB.No) && + resultA.NoWithVeto.Equal(resultB.NoWithVeto) && + resultA.Total.Equal(resultB.Total) } diff --git a/x/gov/queryable.go b/x/gov/queryable.go index 7e84324a7..ffdae92c0 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -221,7 +221,7 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke } else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { tallyResult = proposal.GetTallyResult() } else { - _, tallyResult = Tally(ctx, keeper, proposal) + _, _, tallyResult = Tally(ctx, keeper, proposal) } bz, err2 := codec.MarshalJSONIndent(keeper.cdc, tallyResult) diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 64c1ec209..9715a7084 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -64,10 +64,9 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe whoVotes := r.Perm(len(accs)) // didntVote := whoVotes[numVotes:] whoVotes = whoVotes[:numVotes] - votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod fops := make([]simulation.FutureOperation, numVotes+1) for i := 0; i < numVotes; i++ { - whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) + whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(msg.VotingPeriod))) * time.Second) fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, sk, accs[whoVotes[i]], proposalID)} } // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) @@ -117,6 +116,7 @@ func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) gov.ProposalTypeText, sender.Address, deposit, + time.Duration(simulation.RandomAmount(r, 10000))*time.Second, ) if msg.ValidateBasic() != nil { err = fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index 2dc20ded0..f19b16482 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -32,7 +32,7 @@ func TestGovWithRandomMessages(t *testing.T) { paramKeeper := params.NewKeeper(mapp.Cdc, paramKey, paramTKey) stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) govKey := sdk.NewKVStoreKey("gov") - govKeeper := gov.NewKeeper(mapp.Cdc, govKey, paramKeeper, paramKeeper.Subspace(gov.DefaultParamspace), bankKeeper, stakeKeeper, gov.DefaultCodespace, &sdk.Pool{}) + govKeeper := gov.NewKeeper(mapp.Cdc, govKey, paramKeeper, paramKeeper.Subspace(gov.DefaultParamSpace), bankKeeper, stakeKeeper, gov.DefaultCodespace, &sdk.Pool{}) mapp.Router().AddRoute("gov", gov.NewHandler(govKeeper)) mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { gov.EndBlocker(ctx, govKeeper) diff --git a/x/gov/tally.go b/x/gov/tally.go index 42ea4086a..685c70770 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -6,14 +6,14 @@ import ( // validatorGovInfo used for tallying type validatorGovInfo struct { - Address sdk.ValAddress // address of the validator operator - Power sdk.Dec // Power of a Validator - DelegatorShares sdk.Dec // Total outstanding delegator shares - Minus sdk.Dec // Minus of validator, used to compute validator's voting power - Vote VoteOption // Vote of the validator + Address sdk.ValAddress // address of the validator operator + Power sdk.Dec // Power of a Validator + DelegatorShares sdk.Dec // Total outstanding delegator shares + DelegatorDeductions sdk.Dec // Delegator deductions from validator's delegators voting independently + Vote VoteOption // Vote of the validator } -func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tallyResults TallyResult) { +func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, refundDeposits bool, tallyResults TallyResult) { results := make(map[VoteOption]sdk.Dec) results[OptionYes] = sdk.ZeroDec() results[OptionAbstain] = sdk.ZeroDec() @@ -25,11 +25,11 @@ func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) { currValidators[validator.GetOperator().String()] = validatorGovInfo{ - Address: validator.GetOperator(), - Power: validator.GetPower(), - DelegatorShares: validator.GetDelegatorShares(), - Minus: sdk.ZeroDec(), - Vote: OptionEmpty, + Address: validator.GetOperator(), + Power: validator.GetPower(), + DelegatorShares: validator.GetDelegatorShares(), + DelegatorDeductions: sdk.ZeroDec(), + Vote: OptionEmpty, } return false }) @@ -53,7 +53,7 @@ func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall valAddrStr := delegation.GetValidatorAddr().String() if val, ok := currValidators[valAddrStr]; ok { - val.Minus = val.Minus.Add(delegation.GetShares()) + val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares()) currValidators[valAddrStr] = val delegatorShare := delegation.GetShares().Quo(val.DelegatorShares) @@ -76,7 +76,7 @@ func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall continue } - sharesAfterMinus := val.DelegatorShares.Sub(val.Minus) + sharesAfterMinus := val.DelegatorShares.Sub(val.DelegatorDeductions) percentAfterMinus := sharesAfterMinus.Quo(val.DelegatorShares) votingPower := val.Power.Mul(percentAfterMinus) @@ -84,28 +84,38 @@ func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall totalVotingPower = totalVotingPower.Add(votingPower) } - tallyingProcedure := keeper.GetTallyingProcedure(ctx) - + tallyingParams := keeper.GetTallyParams(ctx) + totalPower := keeper.vs.TotalPower(ctx) tallyResults = TallyResult{ Yes: results[OptionYes], Abstain: results[OptionAbstain], No: results[OptionNo], NoWithVeto: results[OptionNoWithVeto], + Total: totalPower, } + // If there is no staked coins, the proposal fails + if keeper.vs.TotalPower(ctx).IsZero() { + return false, true, tallyResults + } + // If there is not enough quorum of votes, the proposal fails + percentVoting := totalVotingPower.Quo(totalPower) + if percentVoting.LT(tallyingParams.Quorum) { + return false, true, tallyResults + } // If no one votes, proposal fails if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroDec()) { - return false, tallyResults + return false, true, tallyResults } // If more than 1/3 of voters veto, proposal fails - if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { - return false, tallyResults + if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingParams.Veto) { + return false, false, tallyResults } // If more than 1/2 of non-abstaining voters vote Yes, proposal passes - if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingProcedure.Threshold) { - return true, tallyResults + if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingParams.Threshold) { + return true, true, tallyResults } // If more than 1/2 of non-abstaining voters vote No, proposal fails - return false, tallyResults + return false, false, tallyResults } diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index c08ff0411..a4ca620e1 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -2,6 +2,7 @@ package gov_test import ( "testing" + "time" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -47,14 +48,15 @@ func TestTallyNoOneVotes(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) + tallyResults.Total = sdk.ZeroDec() require.True(t, tallyResults.Equals(gov.EmptyTallyResult())) } @@ -72,7 +74,7 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 5}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -82,7 +84,7 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[1], gov.OptionYes) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) @@ -102,7 +104,7 @@ func TestTallyOnlyValidators51No(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{5, 6}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -112,7 +114,7 @@ func TestTallyOnlyValidators51No(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[1], gov.OptionNo) require.Nil(t, err) - passes, _ := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, _ := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) } @@ -131,7 +133,7 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -143,7 +145,7 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) @@ -163,7 +165,7 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -175,7 +177,7 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionNoWithVeto) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) @@ -195,7 +197,7 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -207,7 +209,7 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionYes) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) @@ -227,7 +229,7 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -239,12 +241,72 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) } +func TestTallyOnlyValidatorsUnreachedQuorum(t *testing.T) { + mapp, _, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) + stakeHandler := stake.NewStakeHandler(sk) + + valAddrs := make([]sdk.ValAddress, len(addrs[:3])) + for i, addr := range addrs[:3] { + valAddrs[i] = sdk.ValAddress(addr) + } + + createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposalID := proposal.GetProposalID() + proposal.SetStatus(gov.StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[1], gov.OptionYes) + require.Nil(t, err) + + passes, refundDeposits, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.True(t, refundDeposits) + require.False(t, passes) + require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) +} + +func TestTallyOnlyValidatorsAllAbstain(t *testing.T) { + mapp, _, keeper, sk, addrs, _, _ := getMockApp(t, 10) + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) + stakeHandler := stake.NewStakeHandler(sk) + + valAddrs := make([]sdk.ValAddress, len(addrs[:3])) + for i, addr := range addrs[:3] { + valAddrs[i] = sdk.ValAddress(addr) + } + + createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) + stake.EndBlocker(ctx, sk) + + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) + proposalID := proposal.GetProposalID() + proposal.SetStatus(gov.StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + err := keeper.AddVote(ctx, proposalID, addrs[1], gov.OptionAbstain) + require.Nil(t, err) + err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionAbstain) + require.Nil(t, err) + + passes, refundDeposits, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + + require.True(t, refundDeposits) + require.False(t, passes) + require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) +} + func TestTallyOnlyValidatorsNonVoter(t *testing.T) { mapp, _, keeper, sk, addrs, _, _ := getMockApp(t, 10) mapp.BeginBlock(abci.RequestBeginBlock{}) @@ -259,7 +321,7 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { createValidators(t, stakeHandler, ctx, valAddrs, []int64{6, 6, 7}) stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -269,8 +331,9 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, refundDeposits, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + require.False(t, refundDeposits) require.False(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) } @@ -292,7 +355,7 @@ func TestTallyDelgatorOverride(t *testing.T) { delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 30)) stakeHandler(ctx, delegator1Msg) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -306,13 +369,13 @@ func TestTallyDelgatorOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) } -func TestTallyDelgatorInherit(t *testing.T) { +func TestTallyDelegatorInherit(t *testing.T) { mapp, _, keeper, sk, addrs, _, _ := getMockApp(t, 10) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) @@ -329,7 +392,7 @@ func TestTallyDelgatorInherit(t *testing.T) { delegator1Msg := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(gov.DefaultDepositDenom, 30)) stakeHandler(ctx, delegator1Msg) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -341,13 +404,13 @@ func TestTallyDelgatorInherit(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionYes) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) } -func TestTallyDelgatorMultipleOverride(t *testing.T) { +func TestTallyDelegatorMultipleOverride(t *testing.T) { mapp, _, keeper, sk, addrs, _, _ := getMockApp(t, 10) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) @@ -366,7 +429,7 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { delegator1Msg2 := stake.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewCoin(gov.DefaultDepositDenom, 10)) stakeHandler(ctx, delegator1Msg2) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -380,13 +443,13 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) } -func TestTallyDelgatorMultipleInherit(t *testing.T) { +func TestTallyDelegatorMultipleInherit(t *testing.T) { mapp, _, keeper, sk, addrs, _, _ := getMockApp(t, 10) mapp.BeginBlock(abci.RequestBeginBlock{}) ctx := mapp.BaseApp.NewContext(sdk.RunTxModeDeliver, abci.Header{}) @@ -415,7 +478,7 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -427,7 +490,7 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) @@ -459,7 +522,7 @@ func TestTallyJailedValidator(t *testing.T) { stake.EndBlocker(ctx, sk) - proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText) + proposal := keeper.NewTextProposal(ctx, "Test", "description", gov.ProposalTypeText, 1000*time.Second) proposalID := proposal.GetProposalID() proposal.SetStatus(gov.StatusVotingPeriod) keeper.SetProposal(ctx, proposal) @@ -471,7 +534,7 @@ func TestTallyJailedValidator(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], gov.OptionNo) require.Nil(t, err) - passes, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _, tallyResults := gov.Tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(gov.EmptyTallyResult())) diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index 66378ec7a..fc6834a2c 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -30,7 +30,10 @@ const ( FlagNodeID = "node-id" FlagIP = "ip" - FlagProposalID = "proposal-id" + FlagProposalID = "proposal-id" + FlagConsAddrValidator = "cons-addr-validator" + FlagDeposit = "deposit" + FlagVotingPeriod = "voting-period" FlagOutputDocument = "output-document" // inspired by wget -O ) diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 6843c81c6..3416a1c76 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -2,7 +2,10 @@ package cli import ( "encoding/json" + "errors" "fmt" + "time" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" @@ -28,11 +31,11 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { WithCodec(cdc). WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - amounstStr := viper.GetString(FlagAmount) - if amounstStr == "" { + amountStr := viper.GetString(FlagAmount) + if amountStr == "" { return fmt.Errorf("Must specify amount to stake using --amount") } - amount, err := sdk.ParseCoin(amounstStr) + amount, err := sdk.ParseCoin(amountStr) if err != nil { return err } @@ -102,17 +105,37 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { proposalId := viper.GetInt64(FlagProposalID) if proposalId == 0 { - title := "" + depositStr := viper.GetString(FlagDeposit) + if depositStr == "" { + return fmt.Errorf("must specify deposit amount when proposalId is zero using --deposit") + } + deposit, err := sdk.ParseCoin(depositStr) + if err != nil { + return err + } + title := fmt.Sprintf("create validator %s", valAddr.String()) + description, err := json.Marshal(msg) if err != nil { return err } + votingPeriodInSeconds := viper.GetInt64(FlagVotingPeriod) + + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return errors.New(fmt.Sprintf("voting period should be less than %d seconds", gov.MaxVotingPeriod/time.Second)) + } + msg = gov.NewMsgSubmitProposal(title, string(description), - gov.ProposalTypeCreateValidator, valAddr, sdk.Coins{amount}) + gov.ProposalTypeCreateValidator, valAddr, sdk.Coins{deposit}, votingPeriod) } else { - msg = stake.MsgCreateValidatorProposal{ - MsgCreateValidator: msg.(stake.MsgCreateValidator), - ProposalId: proposalId, + msg = stake.MsgCreateValidatorProposal{ + MsgCreateValidator: msg.(stake.MsgCreateValidator), + ProposalId: proposalId, } } // build and sign the transaction, then broadcast to Tendermint @@ -121,8 +144,10 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { } cmd.Flags().Int64(FlagProposalID, 0, "id of the CreateValidator proposal") + cmd.Flags().Int64(FlagVotingPeriod, 7*24*60*60, "voting period in seconds") cmd.Flags().AddFlagSet(fsPk) cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().String(FlagDeposit, "", "deposit token amount") cmd.Flags().AddFlagSet(fsDescriptionCreate) cmd.Flags().AddFlagSet(fsCommissionCreate) cmd.Flags().AddFlagSet(fsDelegator) @@ -134,6 +159,79 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { return cmd } +// GetCmdEditValidator implements the create edit validator command. +func GetCmdRemoveValidator(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "remove-validator", + Short: "remove validator", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + launcher, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + validatorAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + validatorConsAddr, err := sdk.ConsAddressFromBech32(viper.GetString(FlagConsAddrValidator)) + if err != nil { + return err + } + proposalId := viper.GetInt64(FlagProposalID) + + var msg sdk.Msg + msg = stake.NewMsgRemoveValidator(launcher, validatorAddr, validatorConsAddr, proposalId) + if proposalId == 0 { + depositStr := viper.GetString(FlagDeposit) + if depositStr == "" { + return fmt.Errorf("must specify deposit amount when proposalId is zero using --deposit") + } + deposit, err := sdk.ParseCoin(depositStr) + if err != nil { + return err + } + title := "remove validator" + description, err := json.Marshal(msg) + if err != nil { + return err + } + + votingPeriodInSeconds := viper.GetInt64(FlagVotingPeriod) + if votingPeriodInSeconds <= 0 { + return errors.New("voting period should be positive") + } + votingPeriod := time.Duration(votingPeriodInSeconds) * time.Second + if votingPeriod > gov.MaxVotingPeriod { + return errors.New(fmt.Sprintf("voting period should be less than %d seconds", gov.MaxVotingPeriod/time.Second)) + } + + msg = gov.NewMsgSubmitProposal(title, string(description), + gov.ProposalTypeRemoveValidator, launcher, sdk.Coins{deposit}, votingPeriod) + } + + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, false) + } + // build and sign the transaction, then broadcast to Tendermint + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + + cmd.Flags().Int64(FlagProposalID, 0, "id of the remove validator proposal") + cmd.Flags().Int64(FlagVotingPeriod, 7*24*60*60, "voting period in seconds") + cmd.Flags().String(FlagAddressValidator, "", "validator address") + cmd.Flags().String(FlagConsAddrValidator, "", "validator consensus address") + cmd.Flags().String(FlagDeposit, "", "deposit token amount") + + return cmd +} + // GetCmdEditValidator implements the create edit validator command. func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ diff --git a/x/stake/handler.go b/x/stake/handler.go index f0d8b3437..4f065b403 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -2,10 +2,9 @@ package stake import ( "bytes" - "errors" + "encoding/json" "fmt" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/stake/keeper" @@ -20,6 +19,8 @@ func NewHandler(k keeper.Keeper, govKeeper gov.Keeper) sdk.Handler { switch msg := msg.(type) { case types.MsgCreateValidatorProposal: return handleMsgCreateValidatorAfterProposal(ctx, msg, k, govKeeper) + case types.MsgRemoveValidator: + return handleMsgRemoveValidatorAfterProposal(ctx, msg, k, govKeeper) // disabled other msg handling //case types.MsgEditValidator: // return handleMsgEditValidator(ctx, msg, k) @@ -105,7 +106,7 @@ func handleMsgCreateValidatorAfterProposal(ctx sdk.Context, msg MsgCreateValidat height := ctx.BlockHeader().Height // do not checkProposal for the genesis txs if height != 0 { - if err := checkProposal(ctx, k.Codec(), govKeeper, msg); err != nil { + if err := checkCreateProposal(ctx, k, govKeeper, msg); err != nil { return ErrInvalidProposal(k.Codespace(), err.Error()).Result() } } @@ -113,6 +114,36 @@ func handleMsgCreateValidatorAfterProposal(ctx sdk.Context, msg MsgCreateValidat return handleMsgCreateValidator(ctx, msg.MsgCreateValidator, k) } +func handleMsgRemoveValidatorAfterProposal(ctx sdk.Context, msg MsgRemoveValidator, k keeper.Keeper, govKeeper gov.Keeper) sdk.Result { + if err := checkRemoveProposal(ctx, k, govKeeper, msg); err != nil { + return ErrInvalidProposal(k.Codespace(), err.Error()).Result() + } + + var tags sdk.Tags + var result sdk.Result + k.IterateDelegationsToValidator(ctx, msg.ValAddr, func(del sdk.Delegation) (stop bool) { + msgBeginUnbonding := MsgBeginUnbonding{ + ValidatorAddr: del.GetValidatorAddr(), + DelegatorAddr: del.GetDelegatorAddr(), + SharesAmount: del.GetShares(), + } + result = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, k) + // handleMsgBeginUnbonding return error, abort execution + if !result.IsOK() { + return true + } + tags = tags.AppendTags(result.Tags) + return false + }) + + // If there is a failure in handling MsgBeginUnbonding, return an error + if !result.IsOK() { + return result + } + + return sdk.Result{Tags: tags} +} + func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k keeper.Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) @@ -129,7 +160,9 @@ func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k keeper. return ErrBadDenom(k.Codespace()).Result() } - validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) + // self-delegate address will be used to collect fees. + feeAddr := msg.DelegatorAddr + validator := NewValidatorWithFeeAddr(feeAddr, msg.ValidatorAddr, msg.PubKey, msg.Description) commission := NewCommissionWithTime( msg.Commission.Rate, msg.Commission.MaxRate, msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, @@ -164,33 +197,85 @@ func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k keeper. } } -func checkProposal(ctx sdk.Context, cdc *codec.Codec, govKeeper gov.Keeper, msg MsgCreateValidatorProposal) error { +func checkCreateProposal(ctx sdk.Context, keeper keeper.Keeper, govKeeper gov.Keeper, msg MsgCreateValidatorProposal) error { proposal := govKeeper.GetProposal(ctx, msg.ProposalId) if proposal == nil { - return errors.New(fmt.Sprintf("proposal %d does not exist", msg.ProposalId)) + return fmt.Errorf("proposal %d does not exist", msg.ProposalId) } if proposal.GetProposalType() != gov.ProposalTypeCreateValidator { - return errors.New(fmt.Sprintf("proposal type %s is not equal to %s", - proposal.GetProposalType(), gov.ProposalTypeCreateValidator)) + return fmt.Errorf("proposal type %s is not equal to %s", + proposal.GetProposalType().String(), gov.ProposalTypeCreateValidator.String()) } if proposal.GetStatus() != gov.StatusPassed { - return errors.New(fmt.Sprintf("proposal status %d is not not passed", - proposal.GetStatus())) + return fmt.Errorf("proposal status %s is not not passed", + proposal.GetStatus().String()) } - var createValidatorParams MsgCreateValidator - err := cdc.UnmarshalJSON([]byte(proposal.GetDescription()), &createValidatorParams) + var createValidatorJson CreateValidatorJsonMsg + err := json.Unmarshal([]byte(proposal.GetDescription()), &createValidatorJson) + if err != nil { + return fmt.Errorf("unmarshal createValidator params failed, err=%s", err.Error()) + } + createValidatorMsgProposal, err := createValidatorJson.ToMsgCreateValidator() if err != nil { - return errors.New(fmt.Sprintf("unmarshal createValidator params failed, err=%s", err.Error())) + return fmt.Errorf("invalid pubkey, err=%s", err.Error()) } - if !msg.MsgCreateValidator.Equals(createValidatorParams) { - return errors.New("createValidator msg is not identical to the proposal one") + if !msg.MsgCreateValidator.Equals(createValidatorMsgProposal) { + return fmt.Errorf("createValidator msg is not identical to the proposal one") } return nil } +func checkRemoveProposal(ctx sdk.Context, keeper keeper.Keeper, govKeeper gov.Keeper, msg MsgRemoveValidator) error { + proposal := govKeeper.GetProposal(ctx, msg.ProposalId) + if proposal == nil { + return fmt.Errorf("proposal %d does not exist", msg.ProposalId) + } + if proposal.GetProposalType() != gov.ProposalTypeRemoveValidator { + return fmt.Errorf("proposal type %s is not equal to %s", + proposal.GetProposalType().String(), gov.ProposalTypeRemoveValidator.String()) + } + if proposal.GetStatus() != gov.StatusPassed { + return fmt.Errorf("proposal status %s is not not passed", + proposal.GetStatus().String()) + } + + // Check proposal description + var proposalRemoveValidator MsgRemoveValidator + err := json.Unmarshal([]byte(proposal.GetDescription()), &proposalRemoveValidator) + if err != nil { + return fmt.Errorf("unmarshal removeValidator params failed, err=%s", err.Error()) + } + if !msg.ValAddr.Equals(proposalRemoveValidator.ValAddr) || !msg.ValConsAddr.Equals(proposalRemoveValidator.ValConsAddr) { + return fmt.Errorf("removeValidator msg is not identical to the proposal one") + } + + // Check validator information + validatorToRemove, ok := keeper.GetValidator(ctx, msg.ValAddr) + if !ok { + return fmt.Errorf("trying to remove a non-existing validator") + } + if !validatorToRemove.ConsAddress().Equals(msg.ValConsAddr) { + return fmt.Errorf("consensus address can't match actual validator consensus address") + } + + // Check launcher authority + if sdk.ValAddress(msg.LauncherAddr).Equals(msg.ValAddr) { + return nil + } + // If the launcher isn't the target validator operator, then the launcher must be the operator of other active validator + launcherValidator, ok := keeper.GetValidator(ctx, sdk.ValAddress(msg.LauncherAddr)) + if !ok { + return fmt.Errorf("the launcher is not a validator operator") + } + if launcherValidator.Status != sdk.Bonded { + return fmt.Errorf("the status of launcher validator is not bonded") + } + return nil +} + func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result { // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index c2c9ebd9a..0f014695b 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,7 +1,7 @@ package stake import ( - "fmt" + "encoding/json" "testing" "time" @@ -1023,35 +1023,120 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { func TestCreateValidatorAfterProposal(t *testing.T) { ctx, _, keeper, govKeeper, _ := keep.CreateTestInputWithGov(t, false, 1000) - err := govKeeper.SetInitialProposalID(ctx, 0) + err := govKeeper.SetInitialProposalID(ctx, 1) require.Nil(t, err) valA := sdk.ValAddress(keep.Addrs[0]) valB := sdk.ValAddress(keep.Addrs[1]) ctx = ctx.WithBlockHeight(1) - proposalDescA := fmt.Sprintf("{\"type\": \"test/stake/CreateValidator\",\"value\": {\"Description\": {\"moniker\": \"\",\"identity\": \"\",\"website\": \"\",\"details\": \"\"},\"Commission\": {\"rate\": \"0\",\"max_rate\": \"0\",\"max_change_rate\": \"0\"},\"delegator_address\": \"%s\",\"validator_address\": \"%s\",\"pubkey\": {\"type\": \"tendermint/PubKeyEd25519\",\"value\": \"C0hc/A7sxhlEBEhDb4/J30BWbyNp5yQAKBRUy1Uq8QA=\"},\"delegation\": {\"denom\": \"steak\",\"amount\": \"10000000000\"}}}", keep.Addrs[0].String(), valA.String()) - proposalA := govKeeper.NewTextProposal(ctx, "CreateValidatorProposal", proposalDescA, gov.ProposalTypeCreateValidator) + msgCreateValidator := NewTestMsgCreateValidator(valA, keep.PKs[0], 10) + proposalDesc, _ := json.Marshal(msgCreateValidator) + proposalA := govKeeper.NewTextProposal(ctx, "CreateValidatorProposal", string(proposalDesc), gov.ProposalTypeCreateValidator, 1000*time.Second) proposalA.SetStatus(gov.StatusPassed) govKeeper.SetProposal(ctx, proposalA) msgCreateValidatorA := MsgCreateValidatorProposal{ - MsgCreateValidator: NewTestMsgCreateValidator(valA, keep.PKs[0], 100), - ProposalId: 0, + MsgCreateValidator: NewTestMsgCreateValidator(valA, keep.PKs[0], 10), + ProposalId: 1, } result := handleMsgCreateValidatorAfterProposal(ctx, msgCreateValidatorA, keeper, govKeeper) require.True(t, result.IsOK()) ctx = ctx.WithBlockHeight(2) - proposalDescB := fmt.Sprintf("{\"type\": \"test/stake/CreateValidator\",\"value\": {\"Description\": {\"moniker\": \"\",\"identity\": \"\",\"website\": \"\",\"details\": \"\"},\"Commission\": {\"rate\": \"0\",\"max_rate\": \"0\",\"max_change_rate\": \"0\"},\"delegator_address\": \"%s\",\"validator_address\": \"%s\",\"pubkey\": {\"type\": \"tendermint/PubKeyEd25519\",\"value\": \"C0hc/A7sxhlEBEhDb4/J30BWbyNp5yQAKBRUy1Uq8QE=\"},\"delegation\": {\"denom\": \"steak\",\"amount\": \"10000000000\"}}}", keep.Addrs[1].String(), valB.String()) - proposalB := govKeeper.NewTextProposal(ctx, "CreateValidatorProposal", proposalDescB, gov.ProposalTypeCreateValidator) + msgCreateValidator = NewTestMsgCreateValidator(valB, keep.PKs[1], 10) + proposalDesc, _ = json.Marshal(msgCreateValidator) + proposalB := govKeeper.NewTextProposal(ctx, "CreateValidatorProposal", string(proposalDesc), gov.ProposalTypeCreateValidator, 1000*time.Second) proposalB.SetStatus(gov.StatusPassed) govKeeper.SetProposal(ctx, proposalB) msgCreateValidatorB := MsgCreateValidatorProposal{ - MsgCreateValidator: NewTestMsgCreateValidator(valB, keep.PKs[1], 1000), // I deliberately changed amount value to 1000, amount should be 100 - ProposalId: 1, + MsgCreateValidator: NewTestMsgCreateValidator(valB, keep.PKs[1], 100), // I deliberately changed amount value to 100, amount should be 10 + ProposalId: 2, } result = handleMsgCreateValidatorAfterProposal(ctx, msgCreateValidatorB, keeper, govKeeper) require.False(t, result.IsOK()) } + +func TestRemoveValidatorAfterProposal(t *testing.T) { + ctx, _, keeper, govKeeper, _ := keep.CreateTestInputWithGov(t, false, 1000) + + err := govKeeper.SetInitialProposalID(ctx, 0) + require.Nil(t, err) + valA := sdk.ValAddress(keep.Addrs[0]) + valB := sdk.ValAddress(keep.Addrs[1]) + valC := sdk.ValAddress(keep.Addrs[2]) + valD := sdk.ValAddress(keep.Addrs[3]) + valE := sdk.ValAddress(keep.Addrs[4]) + valF := sdk.ValAddress(keep.Addrs[5]) + + msgCreateValidator := NewTestMsgCreateValidator(valA, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(valB, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(valC, keep.PKs[2], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 3, len(updates)) + + msgCreateValidator = NewTestMsgCreateValidator(valD, keep.PKs[3], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(valF, keep.PKs[5], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + ctx = ctx.WithBlockHeight(1) + removeValidatorMsg := NewMsgRemoveValidator(nil, valA, sdk.ConsAddress(keep.PKs[0].Address()), 0) + proposalDesc, _ := json.Marshal(removeValidatorMsg) + + proposal := govKeeper.NewTextProposal(ctx, "RemoveValidatorProposal", string(proposalDesc), gov.ProposalTypeRemoveValidator, 1000*time.Second) + proposal.SetStatus(gov.StatusPassed) + govKeeper.SetProposal(ctx, proposal) + + // Launcher isn't a bonded validator + msgRemoveValidator := NewMsgRemoveValidator(sdk.AccAddress(valD), valA, sdk.ConsAddress(keep.PKs[0].Address()), 0) + result := handleMsgRemoveValidatorAfterProposal(ctx, msgRemoveValidator, keeper, govKeeper) + require.False(t, result.IsOK()) + + // Launcher isn't a validator + msgRemoveValidator = NewMsgRemoveValidator(sdk.AccAddress(valE), valA, sdk.ConsAddress(keep.PKs[0].Address()), 0) + result = handleMsgRemoveValidatorAfterProposal(ctx, msgRemoveValidator, keeper, govKeeper) + require.False(t, result.IsOK()) + + // Launcher is a bonded validator + msgRemoveValidator = NewMsgRemoveValidator(sdk.AccAddress(valC), valA, sdk.ConsAddress(keep.PKs[0].Address()), 0) + result = handleMsgRemoveValidatorAfterProposal(ctx, msgRemoveValidator, keeper, govKeeper) + require.True(t, result.IsOK()) + + ctx = ctx.WithBlockHeight(2) + removeValidatorMsg = NewMsgRemoveValidator(nil, valD, sdk.ConsAddress(keep.PKs[3].Address()), 0) + proposalDesc, _ = json.Marshal(removeValidatorMsg) + proposal = govKeeper.NewTextProposal(ctx, "RemoveValidatorProposal", string(proposalDesc), gov.ProposalTypeRemoveValidator, 1000*time.Second) + proposal.SetStatus(gov.StatusPassed) + govKeeper.SetProposal(ctx, proposal) + + // Launcher is the operator of target validator + msgRemoveValidator = NewMsgRemoveValidator(sdk.AccAddress(valD), valD, sdk.ConsAddress(keep.PKs[3].Address()), 1) + result = handleMsgRemoveValidatorAfterProposal(ctx, msgRemoveValidator, keeper, govKeeper) + require.True(t, result.IsOK()) + + ctx = ctx.WithBlockHeight(2) + removeValidatorMsg = NewMsgRemoveValidator(nil, valF, sdk.ConsAddress(keep.PKs[5].Address()), 0) + proposalDesc, _ = json.Marshal(removeValidatorMsg) + proposal = govKeeper.NewTextProposal(ctx, "RemoveValidatorProposal", string(proposalDesc), gov.ProposalTypeRemoveValidator, 1000*time.Second) + proposal.SetStatus(gov.StatusPassed) + govKeeper.SetProposal(ctx, proposal) + + // Try to remove a different validator + msgRemoveValidator = NewMsgRemoveValidator(sdk.AccAddress(valF), valB, sdk.ConsAddress(keep.PKs[1].Address()), 2) + result = handleMsgRemoveValidatorAfterProposal(ctx, msgRemoveValidator, keeper, govKeeper) + require.False(t, result.IsOK()) +} diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 09bde360d..3431eaed8 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -51,11 +51,6 @@ func (k Keeper) Codespace() sdk.CodespaceType { return k.codespace } -// return the amino codec -func (k Keeper) Codec() *codec.Codec { - return k.cdc -} - //_______________________________________________________________________ // load the pool diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 5569c979b..2b9042b4b 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -123,3 +123,22 @@ func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, i++ } } + +// iterate through all of the delegations to a validator +func (k Keeper) IterateDelegationsToValidator(ctx sdk.Context, valAddr sdk.ValAddress, + fn func(del sdk.Delegation) (stop bool)) { + + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + del := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + if !del.ValidatorAddr.Equals(valAddr) { + continue + } + stop := fn(del) + if stop { + break + } + } +} diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index b0758c272..462c07b42 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -60,6 +60,7 @@ func MakeTestCodec() *codec.Codec { cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil) cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil) + cdc.RegisterConcrete(types.MsgRemoveValidator{}, "test/stake/RemoveValidator", nil) cdc.RegisterConcrete(types.MsgCreateValidatorProposal{}, "test/stake/CreateValidatorProposal", nil) cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil) cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil) @@ -136,7 +137,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context } // hodgepodge of all sorts of input required for testing -func CreateTestInputWithGov(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountKeeper, Keeper, gov.Keeper, *codec.Codec ) { +func CreateTestInputWithGov(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountKeeper, Keeper, gov.Keeper, *codec.Codec) { keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") @@ -178,7 +179,7 @@ func CreateTestInputWithGov(t *testing.T, isCheckTx bool, initCoins int64) (sdk. pk := params.NewKeeper(cdc, keyParams, tkeyParams) keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) - govKeeper := gov.NewKeeper(cdc, govKey, pk, pk.Subspace(gov.DefaultParamspace), ck, keeper, gov.DefaultCodespace, &sdk.Pool{}) + govKeeper := gov.NewKeeper(cdc, govKey, pk, pk.Subspace(gov.DefaultParamSpace), ck, keeper, gov.DefaultCodespace, &sdk.Pool{}) keeper.SetPool(ctx, types.InitialPool()) keeper.SetParams(ctx, types.DefaultParams()) diff --git a/x/stake/stake.go b/x/stake/stake.go index b9a9cfae9..958c1067e 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -19,6 +19,7 @@ type ( Params = types.Params Pool = types.Pool MsgCreateValidator = types.MsgCreateValidator + MsgRemoveValidator = types.MsgRemoveValidator MsgCreateValidatorProposal = types.MsgCreateValidatorProposal MsgEditValidator = types.MsgEditValidator MsgDelegate = types.MsgDelegate @@ -28,6 +29,7 @@ type ( QueryDelegatorParams = querier.QueryDelegatorParams QueryValidatorParams = querier.QueryValidatorParams QueryBondsParams = querier.QueryBondsParams + CreateValidatorJsonMsg = types.CreateValidatorJsonMsg ) var ( @@ -64,18 +66,20 @@ var ( KeyMaxValidators = types.KeyMaxValidators KeyBondDenom = types.KeyBondDenom - DefaultParams = types.DefaultParams - InitialPool = types.InitialPool - NewValidator = types.NewValidator - NewDescription = types.NewDescription - NewCommission = types.NewCommission - NewCommissionMsg = types.NewCommissionMsg - NewCommissionWithTime = types.NewCommissionWithTime - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - RegisterCodec = types.RegisterCodec + DefaultParams = types.DefaultParams + InitialPool = types.InitialPool + NewValidator = types.NewValidator + NewValidatorWithFeeAddr = types.NewValidatorWithFeeAddr + NewDescription = types.NewDescription + NewCommission = types.NewCommission + NewCommissionMsg = types.NewCommissionMsg + NewCommissionWithTime = types.NewCommissionWithTime + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterCodec = types.RegisterCodec NewMsgCreateValidator = types.NewMsgCreateValidator + NewMsgRemoveValidator = types.NewMsgRemoveValidator NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf NewMsgEditValidator = types.NewMsgEditValidator NewMsgDelegate = types.NewMsgDelegate diff --git a/x/stake/types/codec.go b/x/stake/types/codec.go index 175b92ae1..95df5e9bb 100644 --- a/x/stake/types/codec.go +++ b/x/stake/types/codec.go @@ -7,6 +7,7 @@ import ( // Register concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) + cdc.RegisterConcrete(MsgRemoveValidator{}, "cosmos-sdk/MsgRemoveValidator", nil) cdc.RegisterConcrete(MsgCreateValidatorProposal{}, "cosmos-sdk/MsgCreateValidatorProposal", nil) cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 475c26f0e..c744a9748 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -29,6 +29,10 @@ func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") } +func ErrNilValidatorConsAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "validator consensus address is nil") +} + func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidAddress, "validator address is invalid") } @@ -90,6 +94,10 @@ func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") } +func ErrNilLauncherAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "launcher address of remove validator is nil") +} + func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination") } diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index 0cfd42530..10cbfe67c 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -2,9 +2,11 @@ package types import ( "bytes" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" ) // name to identify transaction routes @@ -17,7 +19,7 @@ var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} // MsgCreateValidator - struct for bonding transactions type MsgCreateValidator struct { - Description + Description Description Commission CommissionMsg DelegatorAddr sdk.AccAddress `json:"delegator_address"` ValidatorAddr sdk.ValAddress `json:"validator_address"` @@ -25,6 +27,33 @@ type MsgCreateValidator struct { Delegation sdk.Coin `json:"delegation"` } +type CreateValidatorJsonMsg struct { + Description Description + Commission CommissionMsg + DelegatorAddr sdk.AccAddress `json:"delegator_address"` + ValidatorAddr sdk.ValAddress `json:"validator_address"` + PubKey []byte `json:"pubkey"` + Delegation sdk.Coin `json:"delegation"` +} + +func (jsonMsg CreateValidatorJsonMsg) ToMsgCreateValidator() (MsgCreateValidator, error) { + if len(jsonMsg.PubKey) != ed25519.PubKeyEd25519Size { + return MsgCreateValidator{}, fmt.Errorf("pubkey size should be %d", ed25519.PubKeyEd25519Size) + } + + var pubkey ed25519.PubKeyEd25519 + copy(pubkey[:], jsonMsg.PubKey) + + return MsgCreateValidator{ + Description: jsonMsg.Description, + Commission: jsonMsg.Commission, + DelegatorAddr: jsonMsg.DelegatorAddr, + ValidatorAddr: jsonMsg.ValidatorAddr, + PubKey: pubkey, + Delegation: jsonMsg.Delegation, + }, nil +} + type MsgCreateValidatorProposal struct { MsgCreateValidator ProposalId int64 `json:"proposal_id"` @@ -116,6 +145,10 @@ func (msg MsgCreateValidator) Equals(other MsgCreateValidator) bool { return false } + if !msg.PubKey.Equals(other.PubKey) { + return false + } + return msg.Delegation.IsEqual(other.Delegation) && msg.DelegatorAddr.Equals(other.DelegatorAddr) && msg.ValidatorAddr.Equals(other.ValidatorAddr) && @@ -360,3 +393,65 @@ func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error { func (msg MsgBeginUnbonding) GetInvolvedAddresses() []sdk.AccAddress { return []sdk.AccAddress{msg.DelegatorAddr, sdk.AccAddress(msg.ValidatorAddr)} } + +type MsgRemoveValidator struct { + LauncherAddr sdk.AccAddress `json:"launcher_addr"` + ValAddr sdk.ValAddress `json:"val_addr"` + ValConsAddr sdk.ConsAddress `json:"val_cons_addr"` + ProposalId int64 `json:"proposal_id"` +} + +func NewMsgRemoveValidator(launcherAddr sdk.AccAddress, valAddr sdk.ValAddress, + valConsAddr sdk.ConsAddress, proposalId int64) MsgRemoveValidator { + return MsgRemoveValidator{ + LauncherAddr: launcherAddr, + ValAddr: valAddr, + ValConsAddr: valConsAddr, + ProposalId: proposalId, + } +} + +//nolint +func (msg MsgRemoveValidator) Route() string { return MsgRoute } +func (msg MsgRemoveValidator) Type() string { return "remove_validator" } +func (msg MsgRemoveValidator) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.LauncherAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgRemoveValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + LauncherAddr sdk.AccAddress `json:"launcher_addr"` + ValAddr sdk.ValAddress `json:"val_addr"` + ValConsAddr sdk.ConsAddress `json:"val_cons_addr"` + ProposalId int64 `json:"proposal_id"` + }{ + LauncherAddr: msg.LauncherAddr, + ValAddr: msg.ValAddr, + ValConsAddr: msg.ValConsAddr, + ProposalId: msg.ProposalId, + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// quick validity check +func (msg MsgRemoveValidator) ValidateBasic() sdk.Error { + if msg.LauncherAddr.Empty() { + return ErrNilLauncherAddr(DefaultCodespace) + } + if msg.ValAddr.Empty() { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValConsAddr.Empty() { + return ErrNilValidatorConsAddr(DefaultCodespace) + } + if msg.ProposalId <= 0 { + return ErrInvalidProposal(DefaultCodespace, fmt.Sprintf("Proposal id is expected to be positive, actual value is %d", msg.ProposalId)) + } + return nil +} + +func (msg MsgRemoveValidator) GetInvolvedAddresses() []sdk.AccAddress { + return []sdk.AccAddress{msg.LauncherAddr} +} diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index 962365106..f05ad763d 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -1,7 +1,6 @@ package types import ( - "bytes" "fmt" "time" @@ -21,6 +20,7 @@ import ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { + FeeAddr sdk.AccAddress `json:"fee_addr"` // address for fee collection OperatorAddr sdk.ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON ConsPubKey crypto.PubKey `json:"consensus_pubkey"` // the consensus public key of the validator; bech encoded in JSON Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? @@ -41,7 +41,12 @@ type Validator struct { // NewValidator - initialize a new validator func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { + return NewValidatorWithFeeAddr(sdk.AccAddress(operator), operator, pubKey, description) +} + +func NewValidatorWithFeeAddr(feeAddr sdk.AccAddress, operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { return Validator{ + FeeAddr: feeAddr, OperatorAddr: operator, ConsPubKey: pubKey, Jailed: false, @@ -59,6 +64,7 @@ func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Des // what's kept in the store value type validatorValue struct { + FeeAddr sdk.AccAddress ConsPubKey crypto.PubKey Jailed bool Status sdk.BondStatus @@ -75,6 +81,7 @@ type validatorValue struct { // return the redelegation without fields contained within the key for the store func MustMarshalValidator(cdc *codec.Codec, validator Validator) []byte { val := validatorValue{ + FeeAddr: validator.FeeAddr, ConsPubKey: validator.ConsPubKey, Jailed: validator.Jailed, Status: validator.Status, @@ -112,6 +119,7 @@ func UnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) (validator } return Validator{ + FeeAddr: storeValue.FeeAddr, OperatorAddr: operatorAddr, ConsPubKey: storeValue.ConsPubKey, Jailed: storeValue.Jailed, @@ -137,6 +145,7 @@ func (v Validator) HumanReadableString() (string, error) { } resp := "Validator \n" + resp += fmt.Sprintf("Fee Address: %s\n", v.FeeAddr) resp += fmt.Sprintf("Operator Address: %s\n", v.OperatorAddr) resp += fmt.Sprintf("Validator Consensus Pubkey: %s\n", bechConsPubKey) resp += fmt.Sprintf("Jailed: %v\n", v.Jailed) @@ -156,6 +165,7 @@ func (v Validator) HumanReadableString() (string, error) { // this is a helper struct used for JSON de- and encoding only type bechValidator struct { + FeeAddr sdk.AccAddress `json:"fee_addr"` // the bech32 address for fee collection OperatorAddr sdk.ValAddress `json:"operator_address"` // the bech32 address of the validator's operator ConsPubKey string `json:"consensus_pubkey"` // the bech32 consensus public key of the validator Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? @@ -182,6 +192,7 @@ func (v Validator) MarshalJSON() ([]byte, error) { } return codec.Cdc.MarshalJSON(bechValidator{ + FeeAddr: v.FeeAddr, OperatorAddr: v.OperatorAddr, ConsPubKey: bechConsPubKey, Jailed: v.Jailed, @@ -208,6 +219,7 @@ func (v *Validator) UnmarshalJSON(data []byte) error { return err } *v = Validator{ + FeeAddr: bv.FeeAddr, OperatorAddr: bv.OperatorAddr, ConsPubKey: consPubKey, Jailed: bv.Jailed, @@ -228,8 +240,9 @@ func (v *Validator) UnmarshalJSON(data []byte) error { // only the vitals - does not check bond height of IntraTxCounter func (v Validator) Equal(v2 Validator) bool { - return v.ConsPubKey.Equals(v2.ConsPubKey) && - bytes.Equal(v.OperatorAddr, v2.OperatorAddr) && + return v.FeeAddr.Equals(v2.FeeAddr) && + v.ConsPubKey.Equals(v2.ConsPubKey) && + v.OperatorAddr.Equals(v2.OperatorAddr) && v.Status.Equal(v2.Status) && v.Tokens.Equal(v2.Tokens) && v.DelegatorShares.Equal(v2.DelegatorShares) && @@ -445,6 +458,7 @@ var _ sdk.Validator = Validator{} func (v Validator) GetJailed() bool { return v.Jailed } func (v Validator) GetMoniker() string { return v.Description.Moniker } func (v Validator) GetStatus() sdk.BondStatus { return v.Status } +func (v Validator) GetFeeAddr() sdk.AccAddress { return v.FeeAddr } func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr } func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey } func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } From 1988a758cea68e1573db920c1244232b5c4ded41 Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Mon, 20 May 2019 14:55:15 +0800 Subject: [PATCH 3/5] #581 pass tx source into msg handler via context --- baseapp/baseapp.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b90569a0c..07d2daf92 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -34,6 +34,8 @@ var dbHeaderKey = []byte("header") const ( // we pass txHash of current handling message via context so that we can publish it as metadata of Msg TxHashKey = "txHash" + // we pass txSrc of current handling message via context so that we can publish it as metadata of Msg + TxSourceKey = "txSrc" //this number should be around the size of the transactions in a block, TODO: configurable TxMsgCacheSize = 4000 ) @@ -726,7 +728,7 @@ func (app *BaseApp) getContextWithCache(mode sdk.RunTxMode, txBytes []byte, txHa } // Iterates through msgs and executes them -func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, txHash string, mode sdk.RunTxMode) (result sdk.Result) { +func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) (result sdk.Result) { // accumulate results logs := make([]string, 0, len(msgs)) var data []byte // NOTE: we just append them all (?!) @@ -740,7 +742,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, txHash string, mode return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result() } - msgResult := handler(ctx.WithValue(TxHashKey, txHash).WithRunTxMode(mode), msg) + msgResult := handler(ctx.WithRunTxMode(mode), msg) msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Type()))) // Append Data and Tags @@ -830,7 +832,14 @@ func (app *BaseApp) RunTx(mode sdk.RunTxMode, txBytes []byte, tx sdk.Tx, txHash } } - result = app.runMsgs(ctx, msgs, txHash, mode) + var txSrc int64 + if stdTx, ok := tx.(auth.StdTx); ok { + txSrc = stdTx.GetSource() + } + result = app.runMsgs( + ctx.WithValue(TxHashKey, txHash).WithValue(TxSourceKey, txSrc), + msgs, + mode) if mode == sdk.RunTxModeSimulate { return @@ -884,7 +893,11 @@ func (app *BaseApp) ReRunTx(txBytes []byte, tx sdk.Tx) (result sdk.Result) { } var msgs = tx.GetMsgs() - result = app.runMsgs(ctx, msgs, txHash, mode) + var txSrc int64 + if stdTx, ok := tx.(auth.StdTx); ok { + txSrc = stdTx.GetSource() + } + result = app.runMsgs(ctx.WithValue(TxHashKey, txHash).WithValue(TxSourceKey, txSrc), msgs, mode) // only update state if all messages pass if result.IsOK() { From be2fce3b6911ddde587b0dfabeb00d9c9c0f79e6 Mon Sep 17 00:00:00 2001 From: cong Date: Mon, 27 May 2019 17:41:08 +0800 Subject: [PATCH 4/5] Revert "[R4R] #581 pass tx source into msg handler via context" --- baseapp/baseapp.go | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 07d2daf92..b90569a0c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -34,8 +34,6 @@ var dbHeaderKey = []byte("header") const ( // we pass txHash of current handling message via context so that we can publish it as metadata of Msg TxHashKey = "txHash" - // we pass txSrc of current handling message via context so that we can publish it as metadata of Msg - TxSourceKey = "txSrc" //this number should be around the size of the transactions in a block, TODO: configurable TxMsgCacheSize = 4000 ) @@ -728,7 +726,7 @@ func (app *BaseApp) getContextWithCache(mode sdk.RunTxMode, txBytes []byte, txHa } // Iterates through msgs and executes them -func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) (result sdk.Result) { +func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, txHash string, mode sdk.RunTxMode) (result sdk.Result) { // accumulate results logs := make([]string, 0, len(msgs)) var data []byte // NOTE: we just append them all (?!) @@ -742,7 +740,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result() } - msgResult := handler(ctx.WithRunTxMode(mode), msg) + msgResult := handler(ctx.WithValue(TxHashKey, txHash).WithRunTxMode(mode), msg) msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Type()))) // Append Data and Tags @@ -832,14 +830,7 @@ func (app *BaseApp) RunTx(mode sdk.RunTxMode, txBytes []byte, tx sdk.Tx, txHash } } - var txSrc int64 - if stdTx, ok := tx.(auth.StdTx); ok { - txSrc = stdTx.GetSource() - } - result = app.runMsgs( - ctx.WithValue(TxHashKey, txHash).WithValue(TxSourceKey, txSrc), - msgs, - mode) + result = app.runMsgs(ctx, msgs, txHash, mode) if mode == sdk.RunTxModeSimulate { return @@ -893,11 +884,7 @@ func (app *BaseApp) ReRunTx(txBytes []byte, tx sdk.Tx) (result sdk.Result) { } var msgs = tx.GetMsgs() - var txSrc int64 - if stdTx, ok := tx.(auth.StdTx); ok { - txSrc = stdTx.GetSource() - } - result = app.runMsgs(ctx.WithValue(TxHashKey, txHash).WithValue(TxSourceKey, txSrc), msgs, mode) + result = app.runMsgs(ctx, msgs, txHash, mode) // only update state if all messages pass if result.IsOK() { From 1bcf6c27210ad38345932d53e26326ce3de2a68d Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Mon, 27 May 2019 18:03:10 +0800 Subject: [PATCH 5/5] bring back #581 pass tx source into msg handler via context (on top of reverted master) --- baseapp/baseapp.go | 21 +++++++++++++++++---- x/stake/client/cli/tx.go | 1 - 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b90569a0c..07d2daf92 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -34,6 +34,8 @@ var dbHeaderKey = []byte("header") const ( // we pass txHash of current handling message via context so that we can publish it as metadata of Msg TxHashKey = "txHash" + // we pass txSrc of current handling message via context so that we can publish it as metadata of Msg + TxSourceKey = "txSrc" //this number should be around the size of the transactions in a block, TODO: configurable TxMsgCacheSize = 4000 ) @@ -726,7 +728,7 @@ func (app *BaseApp) getContextWithCache(mode sdk.RunTxMode, txBytes []byte, txHa } // Iterates through msgs and executes them -func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, txHash string, mode sdk.RunTxMode) (result sdk.Result) { +func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode sdk.RunTxMode) (result sdk.Result) { // accumulate results logs := make([]string, 0, len(msgs)) var data []byte // NOTE: we just append them all (?!) @@ -740,7 +742,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, txHash string, mode return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result() } - msgResult := handler(ctx.WithValue(TxHashKey, txHash).WithRunTxMode(mode), msg) + msgResult := handler(ctx.WithRunTxMode(mode), msg) msgResult.Tags = append(msgResult.Tags, sdk.MakeTag("action", []byte(msg.Type()))) // Append Data and Tags @@ -830,7 +832,14 @@ func (app *BaseApp) RunTx(mode sdk.RunTxMode, txBytes []byte, tx sdk.Tx, txHash } } - result = app.runMsgs(ctx, msgs, txHash, mode) + var txSrc int64 + if stdTx, ok := tx.(auth.StdTx); ok { + txSrc = stdTx.GetSource() + } + result = app.runMsgs( + ctx.WithValue(TxHashKey, txHash).WithValue(TxSourceKey, txSrc), + msgs, + mode) if mode == sdk.RunTxModeSimulate { return @@ -884,7 +893,11 @@ func (app *BaseApp) ReRunTx(txBytes []byte, tx sdk.Tx) (result sdk.Result) { } var msgs = tx.GetMsgs() - result = app.runMsgs(ctx, msgs, txHash, mode) + var txSrc int64 + if stdTx, ok := tx.(auth.StdTx); ok { + txSrc = stdTx.GetSource() + } + result = app.runMsgs(ctx.WithValue(TxHashKey, txHash).WithValue(TxSourceKey, txSrc), msgs, mode) // only update state if all messages pass if result.IsOK() { diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 12f32c1f9..44d76bf71 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -100,7 +100,6 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { } proposalId := viper.GetInt64(FlagProposalID) - if proposalId == -1 { depositStr := viper.GetString(FlagDeposit) if depositStr == "" {