diff --git a/Gopkg.lock b/Gopkg.lock index c3b08bd5c..169893877 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -164,13 +164,12 @@ version = "v1.2.0" [[projects]] - digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10" + digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8" name = "github.com/hashicorp/hcl" packages = [ ".", "hcl/ast", "hcl/parser", - "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -447,7 +446,7 @@ version = "v0.11.0" [[projects]] - digest = "1:f4fcc1a4dbe079b200556ca26c1ff1dacf23712125b9c265d8f02c0dbc318f39" + digest = "1:a69eebd15b05045ffdb10a984e001fadc5666f74383de3d2a9ee5862ee99cfdc" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -478,6 +477,7 @@ "libs/clist", "libs/common", "libs/db", + "libs/errors", "libs/events", "libs/flowrate", "libs/log", @@ -512,8 +512,8 @@ "version", ] pruneopts = "UT" - revision = "d419fffe18531317c28c29a292ad7d253f6cafdf" - version = "v0.24.0" + revision = "0c9c3292c918617624f6f3fbcd95eceade18bcd5" + version = "v0.25.0" [[projects]] digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666" diff --git a/Gopkg.toml b/Gopkg.toml index 45ffc8317..b63234f46 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -57,7 +57,7 @@ [[override]] name = "github.com/tendermint/tendermint" - version = "=0.24.0" + version = "=0.25.0" [[constraint]] name = "github.com/bartekn/go-bip39" diff --git a/PENDING.md b/PENDING.md index 44a4f87b6..cc0e6a817 100644 --- a/PENDING.md +++ b/PENDING.md @@ -43,6 +43,7 @@ BREAKING CHANGES * [x/gov] \#2256 Removed slashing for governance non-voting validators * [simulation] \#2162 Added back correct supply invariants * [x/slashing] \#2430 Simulate more slashes, check if validator is jailed before jailing + * [x/stake] \#2393 Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker * SDK * [core] \#2219 Update to Tendermint 0.24.0 @@ -70,6 +71,16 @@ BREAKING CHANGES * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock * Tendermint + * Update tendermint version from v0.23.0 to v0.25.0, notable changes + * Mempool now won't build too large blocks, or too computationally expensive blocks + * Maximum tx sizes and gas are now removed, and are implicitly the blocks maximums + * ABCI validators no longer send the pubkey. The pubkey is only sent in validator updates + * Validator set changes are now delayed by one block + * Block header now includes the next validator sets hash + * BFT time is implemented + * Secp256k1 signature format has changed + * There is now a threshold multisig format + * See the [tendermint changelog](https://github.com/tendermint/tendermint/blob/master/CHANGELOG.md) for other changes. FEATURES @@ -136,6 +147,7 @@ IMPROVEMENTS * [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200) * [x/stake] \#2435 Improve memory efficiency of getting the various store keys * [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state. + * [genesis] \#2450 Validate staking genesis parameters. * Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) \#1571 * \#1941(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`. * [x/distribution] \#1671 add distribution types and tests diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 84f0ea7fa..bb06ed24b 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -13,7 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/stake" - stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/spf13/pflag" @@ -242,29 +241,13 @@ func GaiaValidateGenesisState(genesisState GenesisState) (err error) { if err != nil { return } - err = validateGenesisStateValidators(genesisState.StakeData.Validators) + err = stake.ValidateGenesis(genesisState.StakeData) if err != nil { return } return } -func validateGenesisStateValidators(validators []stakeTypes.Validator) (err error) { - addrMap := make(map[string]bool, len(validators)) - for i := 0; i < len(validators); i++ { - val := validators[i] - strKey := string(val.ConsPubKey.Bytes()) - if _, ok := addrMap[strKey]; ok { - return fmt.Errorf("Duplicate validator in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) - } - if val.Jailed && val.Status == sdk.Bonded { - return fmt.Errorf("Validator is bonded and jailed in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) - } - addrMap[strKey] = true - } - return -} - // Ensures that there are no duplicate accounts in the genesis state, func validateGenesisStateAccounts(accs []GenesisAccount) (err error) { addrMap := make(map[string]bool, len(accs)) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index db08d872f..78c35eb60 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -97,9 +97,7 @@ func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { {5, stakesim.SimulateMsgEditValidator(app.stakeKeeper)}, {100, stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper)}, {100, stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper)}, {100, stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper)}, - {100, stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper)}, {100, slashingsim.SimulateMsgUnjail(app.slashingKeeper)}, } } diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index a29469261..2502baf0d 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -16,3 +16,34 @@ EndBlock() ValidatorSetChanges ClearTendermintUpdates() return vsc ``` + +## CompleteUnbonding + +Complete the unbonding and transfer the coins to the delegate. Realize any +slashing that occurred during the unbonding period. + +```golang +unbondingQueue(currTime time.Time): + // unbondings are in ordered queue from oldest to newest + for all unbondings whose CompleteTime < currTime: + validator = GetValidator(unbonding.ValidatorAddr) + AddCoins(unbonding.DelegatorAddr, unbonding.Balance) + removeUnbondingDelegation(unbonding) + return +``` + +## CompleteRedelegation + +Note that unlike CompleteUnbonding slashing of redelegating shares does not +take place during completion. Slashing on redelegated shares takes place +actively as a slashing occurs. The redelegation completion queue serves simply to +clean up state, as redelegations older than an unbonding period need not be kept, +as that is the max time that their old validator's evidence can be used to slash them. + +```golang +redelegationQueue(currTime time.Time): + // redelegations are in ordered queue from oldest to newest + for all redelegations whose CompleteTime < currTime: + removeRedelegation(redelegation) + return +``` \ No newline at end of file diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 6ed90343b..74ea67c85 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -7,9 +7,7 @@ corresponding updates to the state. Transactions: * TxEditValidator * TxDelegation * TxStartUnbonding -* TxCompleteUnbonding * TxRedelegate -* TxCompleteRedelegation Other important state changes: @@ -188,27 +186,6 @@ startUnbonding(tx TxStartUnbonding): return ``` -### TxCompleteUnbonding - -Complete the unbonding and transfer the coins to the delegate. Perform any -slashing that occurred during the unbonding period. - -```golang -type TxUnbondingComplete struct { - DelegatorAddr sdk.Address - ValidatorAddr sdk.Address -} - -redelegationComplete(tx TxRedelegate): - unbonding = getUnbondingDelegation(tx.DelegatorAddr, tx.Validator) - if unbonding.CompleteTime >= CurrentBlockTime && unbonding.CompleteHeight >= CurrentBlockHeight - validator = GetValidator(tx.ValidatorAddr) - returnTokens = ExpectedTokens * tx.startSlashRatio/validator.SlashRatio - AddCoins(unbonding.DelegatorAddr, returnTokens) - removeUnbondingDelegation(unbonding) - return -``` - ### TxRedelegation The redelegation command allows delegators to instantly switch validators. Once @@ -243,26 +220,6 @@ redelegate(tx TxRedelegate): return ``` -### TxCompleteRedelegation - -Note that unlike TxCompleteUnbonding slashing of redelegating shares does not -take place during completion. Slashing on redelegated shares takes place -actively as a slashing occurs. - -```golang -type TxRedelegationComplete struct { - DelegatorAddr Address - ValidatorFrom Validator - ValidatorTo Validator -} - -redelegationComplete(tx TxRedelegate): - redelegation = getRedelegation(tx.DelegatorAddr, tx.validatorFrom, tx.validatorTo) - if redelegation.CompleteTime >= CurrentBlockTime && redelegation.CompleteHeight >= CurrentBlockHeight - removeRedelegation(redelegation) - return -``` - ### Update Validators Within many transactions the validator set must be updated based on changes in diff --git a/types/context.go b/types/context.go index fd2e07688..cb5958c5c 100644 --- a/types/context.go +++ b/types/context.go @@ -4,6 +4,7 @@ package types import ( "context" "sync" + "time" "github.com/golang/protobuf/proto" @@ -181,6 +182,12 @@ func (c Context) WithBlockHeader(header abci.Header) Context { return c.withValue(contextKeyBlockHeader, header) } +func (c Context) WithBlockTime(newTime time.Time) Context { + newHeader := c.BlockHeader() + newHeader.Time = newTime + return c.WithBlockHeader(newHeader) +} + func (c Context) WithBlockHeight(height int64) Context { return c.withValue(contextKeyBlockHeight, height) } @@ -190,7 +197,7 @@ func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { return c } return c.withValue(contextKeyConsensusParams, params). - WithGasMeter(NewGasMeter(params.TxSize.MaxGas)) + WithGasMeter(NewGasMeter(params.BlockSize.MaxGas)) } func (c Context) WithChainID(chainID string) Context { return c.withValue(contextKeyChainID, chainID) } diff --git a/types/store.go b/types/store.go index e895b24c9..7bab93b97 100644 --- a/types/store.go +++ b/types/store.go @@ -306,6 +306,13 @@ func PrefixEndBytes(prefix []byte) []byte { return end } +// InclusiveEndBytes returns the []byte that would end a +// range query such that the input would be included +func InclusiveEndBytes(inclusiveBytes []byte) (exclusiveBytes []byte) { + exclusiveBytes = append(inclusiveBytes, byte(0x00)) + return exclusiveBytes +} + // TransientStoreKey is used for indexing transient stores in a MultiStore type TransientStoreKey struct { name string diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 7aebf0d0b..059b19059 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -70,9 +70,8 @@ func TestJailedValidatorDelegations(t *testing.T) { got = stake.NewHandler(stakeKeeper)(ctx, msgBeginUnbonding) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got) - msgCompleteUnbonding := stake.NewMsgCompleteUnbonding(sdk.AccAddress(valAddr), valAddr) - got = stake.NewHandler(stakeKeeper)(ctx, msgCompleteUnbonding) - require.True(t, got.IsOK(), "expected complete unbonding validator msg to be ok, got: %v", got) + err := stakeKeeper.CompleteUnbonding(ctx, sdk.AccAddress(valAddr), valAddr) + require.Nil(t, err, "expected complete unbonding validator to be ok, got: %v", err) // verify validator still exists and is jailed validator, found := stakeKeeper.GetValidator(ctx, valAddr) diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 41c8dd312..4272f4e9f 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -209,7 +209,6 @@ func GetCmdRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { cmd.AddCommand( client.PostCommands( GetCmdBeginRedelegate(storeName, cdc), - GetCmdCompleteRedelegate(cdc), )...) return cmd @@ -270,47 +269,6 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } -// GetCmdCompleteRedelegate implements the complete redelegation command. -func GetCmdCompleteRedelegate(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "complete", - Short: "complete redelegation", - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - valSrcAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidatorSrc)) - if err != nil { - return err - } - - valDstAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidatorDst)) - if err != nil { - return err - } - - msg := stake.NewMsgCompleteRedelegate(delAddr, valSrcAddr, valDstAddr) - - if cliCtx.GenerateOnly { - return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) - } - // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } - - cmd.Flags().AddFlagSet(fsRedelegation) - - return cmd -} - // GetCmdUnbond implements the unbond validator command. func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ @@ -321,7 +279,6 @@ func GetCmdUnbond(storeName string, cdc *codec.Codec) *cobra.Command { cmd.AddCommand( client.PostCommands( GetCmdBeginUnbonding(storeName, cdc), - GetCmdCompleteUnbonding(cdc), )...) return cmd @@ -374,39 +331,3 @@ func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } - -// GetCmdCompleteUnbonding implements the complete unbonding validator command. -func GetCmdCompleteUnbonding(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "complete", - Short: "complete unbonding", - RunE: func(cmd *cobra.Command, args []string) error { - txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - - delAddr, err := cliCtx.GetFromAddress() - if err != nil { - return err - } - - valAddr, err := sdk.ValAddressFromBech32(viper.GetString(FlagAddressValidator)) - if err != nil { - return err - } - - msg := stake.NewMsgCompleteUnbonding(delAddr, valAddr) - - if cliCtx.GenerateOnly { - return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) - } - // build and sign the transaction, then broadcast to Tendermint - return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) - }, - } - - cmd.Flags().AddFlagSet(fsValidator) - - return cmd -} diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index ee2432228..427fb5e19 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -41,31 +41,18 @@ type ( SharesAmount string `json:"shares"` } - msgCompleteRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 - } - msgBeginUnbondingInput struct { DelegatorAddr string `json:"delegator_addr"` // in bech32 ValidatorAddr string `json:"validator_addr"` // in bech32 SharesAmount string `json:"shares"` } - msgCompleteUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - } - // the request body for edit delegations EditDelegationsReq struct { - BaseReq utils.BaseReq `json:"base_req"` - Delegations []msgDelegationsInput `json:"delegations"` - BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` - CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` - BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` - CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` + BaseReq utils.BaseReq `json:"base_req"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` } ) @@ -106,9 +93,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte // build messages messages := make([]sdk.Msg, len(req.Delegations)+ len(req.BeginRedelegates)+ - len(req.CompleteRedelegates)+ - len(req.BeginUnbondings)+ - len(req.CompleteUnbondings)) + len(req.BeginUnbondings)) i := 0 for _, msg := range req.Delegations { @@ -177,39 +162,6 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range req.CompleteRedelegates { - delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) - return - } - - valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) - return - } - - valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) - return - } - - if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") - return - } - - messages[i] = stake.MsgCompleteRedelegate{ - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - } - - i++ - } - for _, msg := range req.BeginUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { @@ -243,32 +195,6 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range req.CompleteUnbondings { - delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) - return - } - - valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) - return - } - - if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") - return - } - - messages[i] = stake.MsgCompleteUnbonding{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - } - - i++ - } - simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 1d500bd82..3cd104e66 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -1,6 +1,8 @@ package stake import ( + "fmt" + abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" @@ -81,3 +83,58 @@ func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisVali return } + +// ValidateGenesis validates the provided staking genesis state to ensure the +// expected invariants holds. (i.e. params in correct bounds, no duplicate validators) +func ValidateGenesis(data types.GenesisState) error { + err := validateGenesisStateValidators(data.Validators) + if err != nil { + return err + } + err = validateParams(data.Params) + if err != nil { + return err + } + + return nil +} + +func validateParams(params types.Params) error { + if params.GoalBonded.LTE(sdk.ZeroDec()) { + bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() + return fmt.Errorf("staking parameter GoalBonded should be positive, instead got %s percent", bondedPercent) + } + if params.GoalBonded.GT(sdk.OneDec()) { + bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() + return fmt.Errorf("staking parameter GoalBonded should be less than 100 percent, instead got %s percent", bondedPercent) + } + if params.BondDenom == "" { + return fmt.Errorf("staking parameter BondDenom can't be an empty string") + } + if params.InflationMax.LT(params.InflationMin) { + return fmt.Errorf("staking parameter Max inflation must be greater than or equal to min inflation") + } + return nil +} + +func validateGenesisStateValidators(validators []types.Validator) (err error) { + addrMap := make(map[string]bool, len(validators)) + for i := 0; i < len(validators); i++ { + val := validators[i] + strKey := string(val.ConsPubKey.Bytes()) + if _, ok := addrMap[strKey]; ok { + return fmt.Errorf("duplicate validator in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) + } + if val.Jailed && val.Status == sdk.Bonded { + return fmt.Errorf("validator is bonded and jailed in genesis state: moniker %v, Address %v", val.Description.Moniker, val.ConsAddress()) + } + if val.Tokens.IsZero() { + return fmt.Errorf("genesis validator cannot have zero pool shares, validator: %v", val) + } + if val.DelegatorShares.IsZero() { + return fmt.Errorf("genesis validator cannot have zero delegator shares, validator: %v", val) + } + addrMap[strKey] = true + } + return +} diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go index 0051ff252..2bbd8e97a 100644 --- a/x/stake/genesis_test.go +++ b/x/stake/genesis_test.go @@ -4,13 +4,15 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" - abci "github.com/tendermint/tendermint/abci/types" + "github.com/stretchr/testify/assert" sdk "github.com/cosmos/cosmos-sdk/types" keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" ) func TestInitGenesis(t *testing.T) { @@ -105,3 +107,59 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) { require.Equal(t, abcivals, vals) } + +func TestValidateGenesis(t *testing.T) { + genValidators1 := make([]types.Validator, 1, 5) + pk := ed25519.GenPrivKey().PubKey() + genValidators1[0] = types.NewValidator(sdk.ValAddress(pk.Address()), pk, types.NewDescription("", "", "", "")) + genValidators1[0].Tokens = sdk.OneDec() + genValidators1[0].DelegatorShares = sdk.OneDec() + + tests := []struct { + name string + mutate func(*types.GenesisState) + wantErr bool + }{ + {"default", func(*types.GenesisState) {}, false}, + // validate params + {"200% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Add(sdk.OneDec()) }, true}, + {"-67% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Neg() }, true}, + {"no bond denom", func(data *types.GenesisState) { (*data).Params.BondDenom = "" }, true}, + {"min inflation > max inflation", func(data *types.GenesisState) { + (*data).Params.InflationMin = (*data).Params.InflationMax.Add(sdk.OneDec()) + }, true}, + {"min inflation = max inflation", func(data *types.GenesisState) { + (*data).Params.InflationMax = (*data).Params.InflationMin + }, false}, + // validate genesis validators + {"duplicate validator", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators = append((*data).Validators, genValidators1[0]) + }, true}, + {"no pool shares", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators[0].Tokens = sdk.ZeroDec() + }, true}, + {"no delegator shares", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators[0].DelegatorShares = sdk.ZeroDec() + }, true}, + {"jailed and bonded validator", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators[0].Jailed = true + (*data).Validators[0].Status = sdk.Bonded + }, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + genesisState := types.DefaultGenesisState() + tt.mutate(&genesisState) + if tt.wantErr { + assert.Error(t, ValidateGenesis(genesisState)) + } else { + assert.NoError(t, ValidateGenesis(genesisState)) + } + }) + } +} diff --git a/x/stake/handler.go b/x/stake/handler.go index f3a007fd5..236718289 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -23,12 +23,8 @@ func NewHandler(k keeper.Keeper) sdk.Handler { return handleMsgDelegate(ctx, msg, k) case types.MsgBeginRedelegate: return handleMsgBeginRedelegate(ctx, msg, k) - case types.MsgCompleteRedelegate: - return handleMsgCompleteRedelegate(ctx, msg, k) case types.MsgBeginUnbonding: return handleMsgBeginUnbonding(ctx, msg, k) - case types.MsgCompleteUnbonding: - return handleMsgCompleteUnbonding(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -37,6 +33,35 @@ func NewHandler(k keeper.Keeper) sdk.Handler { // Called every block, process inflation, update validator set func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { + endBlockerTags := sdk.EmptyTags() + + matureUnbonds := k.DequeueAllMatureUnbondingQueue(ctx, ctx.BlockHeader().Time) + for _, dvPair := range matureUnbonds { + err := k.CompleteUnbonding(ctx, dvPair.DelegatorAddr, dvPair.ValidatorAddr) + if err != nil { + continue + } + endBlockerTags.AppendTags(sdk.NewTags( + tags.Action, ActionCompleteUnbonding, + tags.Delegator, []byte(dvPair.DelegatorAddr.String()), + tags.SrcValidator, []byte(dvPair.ValidatorAddr.String()), + )) + } + + matureRedelegations := k.DequeueAllMatureRedelegationQueue(ctx, ctx.BlockHeader().Time) + for _, dvvTriplet := range matureRedelegations { + err := k.CompleteRedelegation(ctx, dvvTriplet.DelegatorAddr, dvvTriplet.ValidatorSrcAddr, dvvTriplet.ValidatorDstAddr) + if err != nil { + continue + } + endBlockerTags.AppendTags(sdk.NewTags( + tags.Action, tags.ActionCompleteRedelegation, + tags.Delegator, []byte(dvvTriplet.DelegatorAddr.String()), + tags.SrcValidator, []byte(dvvTriplet.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(dvvTriplet.ValidatorDstAddr.String()), + )) + } + pool := k.GetPool(ctx) // Process provision inflation @@ -185,62 +210,37 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) } func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result { - err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) + ubd, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) if err != nil { return err.Result() } + finishTime := types.MsgCdc.MustMarshalBinary(ubd.MinTime) + tags := sdk.NewTags( tags.Action, tags.ActionBeginUnbonding, tags.Delegator, []byte(msg.DelegatorAddr.String()), tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + tags.EndTime, finishTime, ) - return sdk.Result{Tags: tags} -} - -func handleMsgCompleteUnbonding(ctx sdk.Context, msg types.MsgCompleteUnbonding, k keeper.Keeper) sdk.Result { - - err := k.CompleteUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr) - if err != nil { - return err.Result() - } - - tags := sdk.NewTags( - tags.Action, ActionCompleteUnbonding, - tags.Delegator, []byte(msg.DelegatorAddr.String()), - tags.SrcValidator, []byte(msg.ValidatorAddr.String()), - ) - - return sdk.Result{Tags: tags} + return sdk.Result{Data: finishTime, Tags: tags} } func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { - err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, + red, err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr, msg.SharesAmount) if err != nil { return err.Result() } + finishTime := types.MsgCdc.MustMarshalBinary(red.MinTime) + tags := sdk.NewTags( tags.Action, tags.ActionBeginRedelegation, tags.Delegator, []byte(msg.DelegatorAddr.String()), tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + tags.EndTime, finishTime, ) - return sdk.Result{Tags: tags} -} - -func handleMsgCompleteRedelegate(ctx sdk.Context, msg types.MsgCompleteRedelegate, k keeper.Keeper) sdk.Result { - err := k.CompleteRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr) - if err != nil { - return err.Result() - } - - tags := sdk.NewTags( - tags.Action, tags.ActionCompleteRedelegation, - tags.Delegator, []byte(msg.DelegatorAddr.String()), - tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), - tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), - ) - return sdk.Result{Tags: tags} + return sdk.Result{Data: finishTime, Tags: tags} } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 0e4a2bf65..1802f30c1 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -125,11 +125,12 @@ func TestValidatorByPowerIndex(t *testing.T) { // unbond self-delegation msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(1000000)) - msgCompleteUnbonding := NewMsgCompleteUnbonding(sdk.AccAddress(validatorAddr), validatorAddr) got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) EndBlocker(ctx, keeper) @@ -260,13 +261,14 @@ func TestLegacyValidatorDelegations(t *testing.T) { // unbond validator total self-delegations (which should jail the validator) unbondShares := sdk.NewDec(10) msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) - msgCompleteUnbonding := NewMsgCompleteUnbonding(sdk.AccAddress(valAddr), valAddr) got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, got.IsOK(), "expected complete unbonding validator msg to be ok, got %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) // verify the validator record still exists, is jailed, and has correct tokens validator, found = keeper.GetValidator(ctx, valAddr) @@ -427,13 +429,14 @@ func TestIncrementsMsgUnbond(t *testing.T) { // TODO use decimals here unbondShares := sdk.NewDec(10) msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) - msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) numUnbonds := 5 for i := 0; i < numUnbonds; i++ { got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) //Check that the accounts and the bond account have the appropriate values validator, found = keeper.GetValidator(ctx, validatorAddr) @@ -522,11 +525,12 @@ func TestMultipleMsgCreateValidator(t *testing.T) { _, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddrs[i], validatorAddr, sdk.NewDec(10)) // remove delegation - msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddrs[i], validatorAddr) got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) //Check that the account is unbonded validators := keeper.GetValidators(ctx, 100) @@ -569,6 +573,10 @@ func TestMultipleMsgDelegate(t *testing.T) { msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) //Check that the account is unbonded _, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) @@ -591,12 +599,14 @@ func TestJailValidator(t *testing.T) { got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) - validator, _ := keeper.GetValidator(ctx, validatorAddr) - // unbond the validators bond portion msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) @@ -608,11 +618,11 @@ func TestJailValidator(t *testing.T) { // test that the delegator can still withdraw their bonds msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) - msgCompleteUnbondingDelegator := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) require.True(t, got.IsOK(), "expected no error") - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingDelegator, keeper) - require.True(t, got.IsOK(), "expected no error") + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) // verify that the pubkey can now be reused got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) @@ -633,30 +643,74 @@ func TestUnbondingPeriod(t *testing.T) { got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + EndBlocker(ctx, keeper) + // begin unbonding msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.True(t, got.IsOK(), "expected no error") + origHeader := ctx.BlockHeader() + + _, found := keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found, "should not have unbonded") // cannot complete unbonding at same time - msgCompleteUnbonding := NewMsgCompleteUnbonding(sdk.AccAddress(validatorAddr), validatorAddr) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, !got.IsOK(), "expected an error") + EndBlocker(ctx, keeper) + _, found = keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found, "should not have unbonded") // cannot complete unbonding at time 6 seconds later - origHeader := ctx.BlockHeader() - headerTime6 := origHeader - headerTime6.Time = headerTime6.Time.Add(time.Second * 6) - ctx = ctx.WithBlockHeader(headerTime6) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) - require.True(t, !got.IsOK(), "expected an error") + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + _, found = keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found, "should not have unbonded") // can complete unbonding at time 7 seconds later - headerTime7 := origHeader - headerTime7.Time = headerTime7.Time.Add(time.Second * 7) - ctx = ctx.WithBlockHeader(headerTime7) - got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + _, found = keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.False(t, found, "should have unbonded") +} + +func TestUnbondingFromUnbondingValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // create the validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + // unbond the validators bond portion + msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDec(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) require.True(t, got.IsOK(), "expected no error") + + // change the ctx to Block Time one second before the validator would have unbonded + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinary(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1)) + + // unbond the delegator from the validator + msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDec(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") + + // move the Block time forward by one second + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(time.Second * 1)) + + // Run the EndBlocker + EndBlocker(ctx, keeper) + + // Check to make sure that the unbonding delegation is no longer in state + // (meaning it was deleted in the above EndBlocker) + _, found := keeper.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + require.False(t, found, "should be removed from state") } func TestRedelegationPeriod(t *testing.T) { @@ -697,25 +751,24 @@ func TestRedelegationPeriod(t *testing.T) { bal2 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins() require.Equal(t, bal1, bal2) + origHeader := ctx.BlockHeader() + // cannot complete redelegation at same time - msgCompleteRedelegate := NewMsgCompleteRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) - got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) - require.True(t, !got.IsOK(), "expected an error") + EndBlocker(ctx, keeper) + _, found := keeper.GetRedelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) + require.True(t, found, "should not have unbonded") // cannot complete redelegation at time 6 seconds later - origHeader := ctx.BlockHeader() - headerTime6 := origHeader - headerTime6.Time = headerTime6.Time.Add(time.Second * 6) - ctx = ctx.WithBlockHeader(headerTime6) - got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) - require.True(t, !got.IsOK(), "expected an error") + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + _, found = keeper.GetRedelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) + require.True(t, found, "should not have unbonded") // can complete redelegation at time 7 seconds later - headerTime7 := origHeader - headerTime7.Time = headerTime7.Time.Add(time.Second * 7) - ctx = ctx.WithBlockHeader(headerTime7) - got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) - require.True(t, got.IsOK(), "expected no error") + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + _, found = keeper.GetRedelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) + require.False(t, found, "should have unbonded") } func TestTransitiveRedelegation(t *testing.T) { @@ -753,9 +806,7 @@ func TestTransitiveRedelegation(t *testing.T) { require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) // complete first redelegation - msgCompleteRedelegate := NewMsgCompleteRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) - got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) - require.True(t, got.IsOK(), "expected no error") + EndBlocker(ctx, keeper) // now should be able to redelegate from the second validator to the third got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index d7e9c5795..4f1f8e122 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -155,6 +155,57 @@ func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDe store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) } +// gets a specific unbonding queue timeslice. A timeslice is a slice of DVPairs corresponding to unbonding delegations +// that expire at a certain time. +func (k Keeper) GetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvPairs []types.DVPair) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetUnbondingDelegationTimeKey(timestamp)) + if bz == nil { + return []types.DVPair{} + } + k.cdc.MustUnmarshalBinary(bz, &dvPairs) + return dvPairs +} + +// Sets a specific unbonding queue timeslice. +func (k Keeper) SetUnbondingQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVPair) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(keys) + store.Set(GetUnbondingDelegationTimeKey(timestamp), bz) +} + +// Insert an unbonding delegation to the appropriate timeslice in the unbonding queue +func (k Keeper) InsertUnbondingQueue(ctx sdk.Context, ubd types.UnbondingDelegation) { + timeSlice := k.GetUnbondingQueueTimeSlice(ctx, ubd.MinTime) + dvPair := types.DVPair{ubd.DelegatorAddr, ubd.ValidatorAddr} + if len(timeSlice) == 0 { + k.SetUnbondingQueueTimeSlice(ctx, ubd.MinTime, []types.DVPair{dvPair}) + } else { + timeSlice = append(timeSlice, dvPair) + k.SetUnbondingQueueTimeSlice(ctx, ubd.MinTime, timeSlice) + } +} + +// Returns all the unbonding queue timeslices from time 0 until endTime +func (k Keeper) UnbondingQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return store.Iterator(UnbondingQueueKey, sdk.InclusiveEndBytes(GetUnbondingDelegationTimeKey(endTime))) +} + +// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue +func (k Keeper) DequeueAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Time) (matureUnbonds []types.DVPair) { + store := ctx.KVStore(k.storeKey) + // gets an iterator for all timeslices from time 0 until the current Blockheader time + unbondingTimesliceIterator := k.UnbondingQueueIterator(ctx, ctx.BlockHeader().Time) + for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() { + timeslice := []types.DVPair{} + k.cdc.MustUnmarshalBinary(unbondingTimesliceIterator.Value(), ×lice) + matureUnbonds = append(matureUnbonds, timeslice...) + store.Delete(unbondingTimesliceIterator.Key()) + } + return matureUnbonds +} + //_____________________________________________________________________________________ // return a given amount of all the delegator redelegations @@ -241,6 +292,57 @@ func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) } +// Gets a specific redelegation queue timeslice. A timeslice is a slice of DVVTriplets corresponding to redelegations +// that expire at a certain time. +func (k Keeper) GetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (dvvTriplets []types.DVVTriplet) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetRedelegationTimeKey(timestamp)) + if bz == nil { + return []types.DVVTriplet{} + } + k.cdc.MustUnmarshalBinary(bz, &dvvTriplets) + return dvvTriplets +} + +// Sets a specific redelegation queue timeslice. +func (k Keeper) SetRedelegationQueueTimeSlice(ctx sdk.Context, timestamp time.Time, keys []types.DVVTriplet) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(keys) + store.Set(GetRedelegationTimeKey(timestamp), bz) +} + +// Insert an redelegation delegation to the appropriate timeslice in the redelegation queue +func (k Keeper) InsertRedelegationQueue(ctx sdk.Context, red types.Redelegation) { + timeSlice := k.GetRedelegationQueueTimeSlice(ctx, red.MinTime) + dvvTriplet := types.DVVTriplet{red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr} + if len(timeSlice) == 0 { + k.SetRedelegationQueueTimeSlice(ctx, red.MinTime, []types.DVVTriplet{dvvTriplet}) + } else { + timeSlice = append(timeSlice, dvvTriplet) + k.SetRedelegationQueueTimeSlice(ctx, red.MinTime, timeSlice) + } +} + +// Returns all the redelegation queue timeslices from time 0 until endTime +func (k Keeper) RedelegationQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return store.Iterator(RedelegationQueueKey, sdk.InclusiveEndBytes(GetRedelegationTimeKey(endTime))) +} + +// Returns a concatenated list of all the timeslices before currTime, and deletes the timeslices from the queue +func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time.Time) (matureRedelegations []types.DVVTriplet) { + store := ctx.KVStore(k.storeKey) + // gets an iterator for all timeslices from time 0 until the current Blockheader time + redelegationTimesliceIterator := k.RedelegationQueueIterator(ctx, ctx.BlockHeader().Time) + for ; redelegationTimesliceIterator.Valid(); redelegationTimesliceIterator.Next() { + timeslice := []types.DVVTriplet{} + k.cdc.MustUnmarshalBinary(redelegationTimesliceIterator.Value(), ×lice) + matureRedelegations = append(matureRedelegations, timeslice...) + store.Delete(redelegationTimesliceIterator.Key()) + } + return matureRedelegations +} + //_____________________________________________________________________________________ // Perform a delegation, set/update everything necessary within the store. @@ -339,6 +441,7 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sd minTime time.Time, height int64, completeNow bool) { validator, found := k.GetValidator(ctx, valSrcAddr) + switch { case !found || validator.Status == sdk.Bonded: @@ -362,31 +465,32 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sd // begin unbonding an unbonding record func (k Keeper) BeginUnbonding(ctx sdk.Context, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) sdk.Error { + delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.UnbondingDelegation, sdk.Error) { // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 _, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr) if found { - return types.ErrExistingUnbondingDelegation(k.Codespace()) - } - - returnAmount, err := k.unbond(ctx, delAddr, valAddr, sharesAmount) - if err != nil { - return err + return types.UnbondingDelegation{}, types.ErrExistingUnbondingDelegation(k.Codespace()) } // create the unbonding delegation params := k.GetParams(ctx) minTime, height, completeNow := k.getBeginInfo(ctx, params, valAddr) + + returnAmount, err := k.unbond(ctx, delAddr, valAddr, sharesAmount) + if err != nil { + return types.UnbondingDelegation{}, err + } + balance := sdk.NewCoin(params.BondDenom, returnAmount.RoundInt()) // no need to create the ubd object just complete now if completeNow { _, _, err := k.bankKeeper.AddCoins(ctx, delAddr, sdk.Coins{balance}) if err != nil { - return err + return types.UnbondingDelegation{}, err } - return nil + return types.UnbondingDelegation{MinTime: minTime}, nil } ubd := types.UnbondingDelegation{ @@ -398,10 +502,12 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, InitialBalance: balance, } k.SetUnbondingDelegation(ctx, ubd) - return nil + k.InsertUnbondingQueue(ctx, ubd) + return ubd, nil } // complete unbonding an unbonding record +// CONTRACT: Expects unbonding passed in has finished the unbonding period func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Error { ubd, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr) @@ -409,12 +515,6 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd return types.ErrNoUnbondingDelegation(k.Codespace()) } - // ensure that enough time has passed - ctxTime := ctx.BlockHeader().Time - if ubd.MinTime.After(ctxTime) { - return types.ErrNotMature(k.Codespace(), "unbonding", "unit-time", ubd.MinTime, ctxTime) - } - _, _, err := k.bankKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance}) if err != nil { return err @@ -425,34 +525,34 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd // complete unbonding an unbonding record func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, - valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) sdk.Error { + valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) { - return types.ErrTransitiveRedelegation(k.Codespace()) + return types.Redelegation{}, types.ErrTransitiveRedelegation(k.Codespace()) } returnAmount, err := k.unbond(ctx, delAddr, valSrcAddr, sharesAmount) if err != nil { - return err + return types.Redelegation{}, err } params := k.GetParams(ctx) returnCoin := sdk.NewCoin(params.BondDenom, returnAmount.RoundInt()) dstValidator, found := k.GetValidator(ctx, valDstAddr) if !found { - return types.ErrBadRedelegationDst(k.Codespace()) + return types.Redelegation{}, types.ErrBadRedelegationDst(k.Codespace()) } sharesCreated, err := k.Delegate(ctx, delAddr, returnCoin, dstValidator, false) if err != nil { - return err + return types.Redelegation{}, err } // create the unbonding delegation minTime, height, completeNow := k.getBeginInfo(ctx, params, valSrcAddr) if completeNow { // no need to create the redelegation object - return nil + return types.Redelegation{MinTime: minTime}, nil } red := types.Redelegation{ @@ -467,7 +567,8 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, InitialBalance: returnCoin, } k.SetRedelegation(ctx, red) - return nil + k.InsertRedelegationQueue(ctx, red) + return red, nil } // complete unbonding an ongoing redelegation diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 863c08a96..0964f10de 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -250,7 +250,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { keeper.SetDelegation(ctx, delegation) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -306,7 +306,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { // unbond the all self-delegation to put validator in unbonding state val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -328,7 +328,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond some of the other delegation's shares - err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) require.NoError(t, err) // retrieve the unbonding delegation @@ -382,7 +382,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -404,7 +404,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond some of the other delegation's shares - err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) require.NoError(t, err) // no ubd should have been found, coins should have been returned direcly to account @@ -553,7 +553,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDec(10)) + _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -618,7 +618,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -640,7 +640,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond some of the other delegation's shares - err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) + _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) require.NoError(t, err) // retrieve the unbonding delegation @@ -704,7 +704,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) // end block @@ -726,7 +726,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond some of the other delegation's shares - err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) + _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) require.NoError(t, err) // no ubd should have been found, coins should have been returned direcly to account diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 6beb17937..d965dce05 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/binary" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -25,6 +26,8 @@ var ( RedelegationKey = []byte{0x0A} // key for a redelegation RedelegationByValSrcIndexKey = []byte{0x0B} // prefix for each key for an redelegation, by source validator operator RedelegationByValDstIndexKey = []byte{0x0C} // prefix for each key for an redelegation, by destination validator operator + UnbondingQueueKey = []byte{0x0D} // prefix for the timestamps in unbonding queue + RedelegationQueueKey = []byte{0x0E} // prefix for the timestamps in redelegations queue ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -134,6 +137,12 @@ func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte { return append(UnbondingDelegationByValIndexKey, valAddr.Bytes()...) } +// gets the prefix for all unbonding delegations from a delegator +func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte { + bz := types.MsgCdc.MustMarshalBinary(timestamp) + return append(UnbondingQueueKey, bz...) +} + //________________________________________________________________________________ // gets the key for a redelegation @@ -202,6 +211,12 @@ func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte { return GetREDKey(delAddr, valSrcAddr, valDstAddr) } +// gets the prefix for all unbonding delegations from a delegator +func GetRedelegationTimeKey(timestamp time.Time) []byte { + bz, _ := timestamp.MarshalBinary() + return append(RedelegationQueueKey, bz...) +} + //______________ // gets the prefix keyspace for redelegations from a delegator diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index 8f5b2fa39..e41b8203b 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -62,9 +62,7 @@ func MakeTestCodec() *codec.Codec { cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil) cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil) cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil) - cdc.RegisterConcrete(types.MsgCompleteUnbonding{}, "test/stake/CompleteUnbonding", nil) cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/stake/BeginRedelegate", nil) - cdc.RegisterConcrete(types.MsgCompleteRedelegate{}, "test/stake/CompleteRedelegate", nil) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 0cd4e08a9..9c2b1b98d 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -178,33 +178,6 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. } } -// SimulateMsgCompleteUnbonding -func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - - validatorAcc := simulation.RandomAcc(r, accs) - validatorAddress := sdk.ValAddress(validatorAcc.Address) - delegatorAcc := simulation.RandomAcc(r, accs) - delegatorAddress := delegatorAcc.Address - msg := stake.MsgCompleteUnbonding{ - DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - } - if msg.ValidateBasic() != nil { - return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - } - ctx, write := ctx.CacheContext() - result := handler(ctx, msg) - if result.IsOK() { - write() - } - event(fmt.Sprintf("stake/MsgCompleteUnbonding/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgCompleteUnbonding: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) - return action, nil, nil - } -} - // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) @@ -245,36 +218,6 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation } } -// SimulateMsgCompleteRedelegate -func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { - handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - - validatorSrcAcc := simulation.RandomAcc(r, accs) - validatorSrcAddress := sdk.ValAddress(validatorSrcAcc.Address) - validatorDstAcc := simulation.RandomAcc(r, accs) - validatorDstAddress := sdk.ValAddress(validatorDstAcc.Address) - delegatorAcc := simulation.RandomAcc(r, accs) - delegatorAddress := delegatorAcc.Address - msg := stake.MsgCompleteRedelegate{ - DelegatorAddr: delegatorAddress, - ValidatorSrcAddr: validatorSrcAddress, - ValidatorDstAddr: validatorDstAddress, - } - if msg.ValidateBasic() != nil { - return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - } - ctx, write := ctx.CacheContext() - result := handler(ctx, msg) - if result.IsOK() { - write() - } - event(fmt.Sprintf("stake/MsgCompleteRedelegate/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgCompleteRedelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) - return action, nil, nil - } -} - // Setup // nolint: errcheck func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index b81555f03..0f637b51d 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -49,9 +49,7 @@ func TestStakeWithRandomMessages(t *testing.T) { {5, SimulateMsgEditValidator(stakeKeeper)}, {15, SimulateMsgDelegate(mapper, stakeKeeper)}, {10, SimulateMsgBeginUnbonding(mapper, stakeKeeper)}, - {3, SimulateMsgCompleteUnbonding(stakeKeeper)}, {10, SimulateMsgBeginRedelegate(mapper, stakeKeeper)}, - {3, SimulateMsgCompleteRedelegate(stakeKeeper)}, }, []simulation.RandSetup{ Setup(mapp, stakeKeeper), }, []simulation.Invariant{ diff --git a/x/stake/stake.go b/x/stake/stake.go index a9a3ca3cd..7bec2d962 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -9,27 +9,25 @@ import ( ) type ( - Keeper = keeper.Keeper - Validator = types.Validator - Description = types.Description - Commission = types.Commission - Delegation = types.Delegation - DelegationSummary = types.DelegationSummary - UnbondingDelegation = types.UnbondingDelegation - Redelegation = types.Redelegation - Params = types.Params - Pool = types.Pool - MsgCreateValidator = types.MsgCreateValidator - MsgEditValidator = types.MsgEditValidator - MsgDelegate = types.MsgDelegate - MsgBeginUnbonding = types.MsgBeginUnbonding - MsgCompleteUnbonding = types.MsgCompleteUnbonding - MsgBeginRedelegate = types.MsgBeginRedelegate - MsgCompleteRedelegate = types.MsgCompleteRedelegate - GenesisState = types.GenesisState - QueryDelegatorParams = querier.QueryDelegatorParams - QueryValidatorParams = querier.QueryValidatorParams - QueryBondsParams = querier.QueryBondsParams + Keeper = keeper.Keeper + Validator = types.Validator + Description = types.Description + Commission = types.Commission + Delegation = types.Delegation + DelegationSummary = types.DelegationSummary + UnbondingDelegation = types.UnbondingDelegation + Redelegation = types.Redelegation + Params = types.Params + Pool = types.Pool + MsgCreateValidator = types.MsgCreateValidator + MsgEditValidator = types.MsgEditValidator + MsgDelegate = types.MsgDelegate + MsgBeginUnbonding = types.MsgBeginUnbonding + MsgBeginRedelegate = types.MsgBeginRedelegate + GenesisState = types.GenesisState + QueryDelegatorParams = querier.QueryDelegatorParams + QueryValidatorParams = querier.QueryValidatorParams + QueryBondsParams = querier.QueryBondsParams ) var ( @@ -76,9 +74,7 @@ var ( NewMsgEditValidator = types.NewMsgEditValidator NewMsgDelegate = types.NewMsgDelegate NewMsgBeginUnbonding = types.NewMsgBeginUnbonding - NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding NewMsgBeginRedelegate = types.NewMsgBeginRedelegate - NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate NewQuerier = querier.NewQuerier ) diff --git a/x/stake/tags/tags.go b/x/stake/tags/tags.go index 8ef60aa84..959fa6f59 100644 --- a/x/stake/tags/tags.go +++ b/x/stake/tags/tags.go @@ -20,4 +20,5 @@ var ( Delegator = sdk.TagDelegator Moniker = "moniker" Identity = "identity" + EndTime = "end-time" ) diff --git a/x/stake/types/codec.go b/x/stake/types/codec.go index 4921cdf8e..bc3764ffe 100644 --- a/x/stake/types/codec.go +++ b/x/stake/types/codec.go @@ -10,9 +10,7 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) cdc.RegisterConcrete(MsgBeginUnbonding{}, "cosmos-sdk/BeginUnbonding", nil) - cdc.RegisterConcrete(MsgCompleteUnbonding{}, "cosmos-sdk/CompleteUnbonding", nil) cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil) - cdc.RegisterConcrete(MsgCompleteRedelegate{}, "cosmos-sdk/CompleteRedelegate", nil) } // generic sealed codec to be used throughout sdk diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index 57ac70a57..d38911521 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -9,6 +9,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// DVPair is struct that just has a delegator-validator pair with no other data. +// It is intended to be used as a marshalable pointer. For example, a DVPair can be used to construct the +// key to getting an UnbondingDelegation from state. +type DVPair struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress +} + +// DVVTriplet is struct that just has a delegator-validator-validator triplet with no other data. +// It is intended to be used as a marshalable pointer. For example, a DVVTriplet can be used to construct the +// key to getting a Redelegation from state. +type DVVTriplet struct { + DelegatorAddr sdk.AccAddress + ValidatorSrcAddr sdk.ValAddress + ValidatorDstAddr sdk.ValAddress +} + // Delegation represents the bond with tokens held by an account. It is // owned by one delegator, and is associated with the voting power of one // pubKey. diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index a313dd64f..da46415d2 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -12,12 +12,10 @@ const MsgType = "stake" // Verify interface at compile time var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} -var _, _ sdk.Msg = &MsgBeginUnbonding{}, &MsgCompleteUnbonding{} -var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{} //______________________________________________________________________ -// MsgCreateValidator - struct for unbonding transactions +// MsgCreateValidator - struct for bonding transactions type MsgCreateValidator struct { Description Commission CommissionMsg @@ -276,51 +274,6 @@ func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { return nil } -// MsgDelegate - struct for bonding transactions -type MsgCompleteRedelegate struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorSrcAddr sdk.ValAddress `json:"validator_source_addr"` - ValidatorDstAddr sdk.ValAddress `json:"validator_destination_addr"` -} - -func NewMsgCompleteRedelegate(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) MsgCompleteRedelegate { - return MsgCompleteRedelegate{ - DelegatorAddr: delAddr, - ValidatorSrcAddr: valSrcAddr, - ValidatorDstAddr: valDstAddr, - } -} - -//nolint -func (msg MsgCompleteRedelegate) Type() string { return MsgType } -func (msg MsgCompleteRedelegate) Name() string { return "complete_redelegate" } -func (msg MsgCompleteRedelegate) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{msg.DelegatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgCompleteRedelegate) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} - -// quick validity check -func (msg MsgCompleteRedelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrNilDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorSrcAddr == nil { - return ErrNilValidatorAddr(DefaultCodespace) - } - if msg.ValidatorDstAddr == nil { - return ErrNilValidatorAddr(DefaultCodespace) - } - return nil -} - //______________________________________________________________________ // MsgBeginUnbonding - struct for unbonding transactions @@ -373,43 +326,3 @@ func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error { } return nil } - -// MsgCompleteUnbonding - struct for unbonding transactions -type MsgCompleteUnbonding struct { - DelegatorAddr sdk.AccAddress `json:"delegator_addr"` - ValidatorAddr sdk.ValAddress `json:"validator_addr"` -} - -func NewMsgCompleteUnbonding(delAddr sdk.AccAddress, valAddr sdk.ValAddress) MsgCompleteUnbonding { - return MsgCompleteUnbonding{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - } -} - -//nolint -func (msg MsgCompleteUnbonding) Type() string { return MsgType } -func (msg MsgCompleteUnbonding) Name() string { return "complete_unbonding" } -func (msg MsgCompleteUnbonding) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{msg.DelegatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgCompleteUnbonding) GetSignBytes() []byte { - b, err := MsgCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} - -// quick validity check -func (msg MsgCompleteUnbonding) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrNilDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrNilValidatorAddr(DefaultCodespace) - } - return nil -} diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go index b5adbd0ad..7c1800b8e 100644 --- a/x/stake/types/msg_test.go +++ b/x/stake/types/msg_test.go @@ -177,31 +177,6 @@ func TestMsgBeginRedelegate(t *testing.T) { } } -// test ValidateBasic for MsgUnbond -func TestMsgCompleteRedelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorSrcAddr sdk.ValAddress - validatorDstAddr sdk.ValAddress - expectPass bool - }{ - {"regular", sdk.AccAddress(addr1), addr2, addr3, true}, - {"empty delegator", sdk.AccAddress(emptyAddr), addr1, addr3, false}, - {"empty source validator", sdk.AccAddress(addr1), emptyAddr, addr3, false}, - {"empty destination validator", sdk.AccAddress(addr1), addr2, emptyAddr, false}, - } - - for _, tc := range tests { - msg := NewMsgCompleteRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - // test ValidateBasic for MsgUnbond func TestMsgBeginUnbonding(t *testing.T) { tests := []struct { @@ -227,26 +202,3 @@ func TestMsgBeginUnbonding(t *testing.T) { } } } - -// test ValidateBasic for MsgUnbond -func TestMsgCompleteUnbonding(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.AccAddress - validatorAddr sdk.ValAddress - expectPass bool - }{ - {"regular", sdk.AccAddress(addr1), addr2, true}, - {"empty delegator", sdk.AccAddress(emptyAddr), addr1, false}, - {"empty validator", sdk.AccAddress(addr1), emptyAddr, false}, - } - - for _, tc := range tests { - msg := NewMsgCompleteUnbonding(tc.delegatorAddr, tc.validatorAddr) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -}