Merge branch 'develop' of https://github.com/cosmos/cosmos-sdk into fedekunze/2044-JSON-err-msgs

Merge develop
This commit is contained in:
Federico Kunze 2018-08-23 11:31:37 +02:00
commit 1ec9d16d94
71 changed files with 1170 additions and 386 deletions

View File

@ -5,13 +5,14 @@ v If a checkbox is n/a - please still include it but + a little note why
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
- [ ] Linked to github-issue with discussion and accepted design OR link to spec that describes this work.
- [ ] Updated all relevant documentation (`docs/`)
- [ ] Updated all relevant code comments
- [ ] Wrote tests
- [ ] Added entries in `PENDING.md` that include links to the relevant issue or PR that most accurately describes the change.
- [ ] Updated `cmd/gaia` and `examples/`
___________________________________
- [ ] Updated relevant documentation (`docs/`)
- [ ] Added entries in `PENDING.md` with issue #
- [ ] rereviewed `Files changed` in the github PR explorer
______
For Admin Use:
- [ ] Added appropriate labels to PR (ex. wip, ready-for-review, docs)
- [ ] Reviewers Assigned
- [ ] Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr))
- Added appropriate labels to PR (ex. wip, ready-for-review, docs)
- Reviewers Assigned
- Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr))

17
Gopkg.lock generated
View File

@ -490,6 +490,14 @@
revision = "013b9cef642f875634c614019ab13b17570778ad"
version = "v0.23.0"
[[projects]]
digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e"
name = "github.com/tendermint/tmlibs"
packages = ["cli"]
pruneopts = "UT"
revision = "49596e0a1f48866603813df843c9409fc19805c6"
version = "v0.9.0"
[[projects]]
digest = "1:4dcb0dd65feecb068ce23a234d1a07c7868a1e39f52a6defcae0bb371d03abf6"
name = "github.com/zondax/ledger-goclient"
@ -499,7 +507,7 @@
[[projects]]
branch = "master"
digest = "1:7a71fffde456d746c52f9cd09c50b034533a3180fb1f6320abb149f2ccc579e5"
digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a"
name = "golang.org/x/crypto"
packages = [
"blowfish",
@ -518,7 +526,7 @@
"salsa20/salsa",
]
pruneopts = "UT"
revision = "aabede6cba87e37f413b3e60ebfc214f8eeca1b0"
revision = "614d502a4dac94afa3a6ce146bd1736da82514c6"
[[projects]]
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
@ -538,14 +546,14 @@
[[projects]]
branch = "master"
digest = "1:ead82e3e398388679f3ad77633a087ac31a47a6be59ae20841e1d1b3a3fbbd22"
digest = "1:a0e12bc26f317c0e2d497baf767285e1790e526e8dd46553c5a67fcbc8692157"
name = "golang.org/x/sys"
packages = [
"cpu",
"unix",
]
pruneopts = "UT"
revision = "1a700e749ce29638d0bbcb531cce1094ea096bd3"
revision = "3b58ed4ad3395d483fc92d5d14123ce2c3581fec"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
@ -666,6 +674,7 @@
"github.com/tendermint/tendermint/rpc/lib/server",
"github.com/tendermint/tendermint/types",
"github.com/tendermint/tendermint/version",
"github.com/tendermint/tmlibs/cli",
"github.com/zondax/ledger-goclient",
"golang.org/x/crypto/blowfish",
"golang.org/x/crypto/ripemd160",

View File

@ -8,12 +8,15 @@ BREAKING CHANGES
* Gaia CLI (`gaiacli`)
* [x/stake] Validator.Owner renamed to Validator.Operator
* [cli] unsafe_reset_all, show_validator, and show_node_id have been renamed to unsafe-reset-all, show-validator, and show-node-id
* [cli] \#1983 --print-response now defaults to true in commands that create and send a transaction
* [cli] \#1983 you can now pass --pubkey or --address to gaiacli keys show to return a plaintext representation of the key's address or public key for use with other commands
* [cli] \#2061 changed proposalID in governance REST endpoints to proposal-id
* [cli] \#2014 `gaiacli advanced` no longer exists - to access `ibc`, `rest-server`, and `validator-set` commands use `gaiacli ibc`, `gaiacli rest-server`, and `gaiacli tendermint`, respectively
* Gaia
* Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013)
* [x/stake] \#1901 Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface.
* [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed"
* SDK
* [core] \#1807 Switch from use of rational to decimal
@ -29,10 +32,12 @@ FEATURES
* Gaia CLI (`gaiacli`)
* [cli] Cmds to query staking pool and params
* [gov][cli] #2062 added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in
* Gaia
* SDK
* [querier] added custom querier functionality, so ABCI query requests can be handled by keepers
* Tendermint
@ -66,6 +71,7 @@ BUG FIXES
* Gaia
* SDK
* \#1988 Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988)
* \#1988 Make us compile on OpenBSD (disable ledger) [#1988] (https://github.com/cosmos/cosmos-sdk/issues/1988)
* \#2105 Fix DB Iterator leak, which may leak a go routine.
* Tendermint

View File

@ -41,13 +41,14 @@ const (
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {
// initialized on creation
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
router Router // handle any kind of message
codespacer *sdk.Codespacer // handle module codespacing
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
router Router // handle any kind of message
queryRouter QueryRouter // router for redirecting query calls
codespacer *sdk.Codespacer // handle module codespacing
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
anteHandler sdk.AnteHandler // ante handler for fee and auth
@ -84,13 +85,14 @@ var _ abci.Application = (*BaseApp)(nil)
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp)) *BaseApp {
app := &BaseApp{
Logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
router: NewRouter(),
codespacer: sdk.NewCodespacer(),
txDecoder: txDecoder,
Logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
router: NewRouter(),
queryRouter: NewQueryRouter(),
codespacer: sdk.NewCodespacer(),
txDecoder: txDecoder,
}
// Register the undefined & root codespaces, which should not be used by
@ -266,6 +268,7 @@ func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
return abci.ResponseQuery{}
}
// Splits a string path using the delimter '/'. i.e. "this/is/funny" becomes []string{"this", "is", "funny"}
func splitPath(requestPath string) (path []string) {
path = strings.Split(requestPath, "/")
// first element is empty string
@ -291,6 +294,8 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
return handleQueryStore(app, path, req)
case "p2p":
return handleQueryP2P(app, path, req)
case "custom":
return handleQueryCustom(app, path, req)
}
msg := "unknown query path"
@ -362,6 +367,33 @@ func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abc
return sdk.ErrUnknownRequest(msg).QueryResult()
}
func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
// path[0] should be "custom" because "/custom" prefix is required for keeper queries.
// the queryRouter routes using path[1]. For example, in the path "custom/gov/proposal", queryRouter routes using "gov"
if path[1] == "" {
sdk.ErrUnknownRequest("No route for custom query specified").QueryResult()
}
querier := app.queryRouter.Route(path[1])
if querier == nil {
sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
}
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger)
// Passes the rest of the path as an argument to the querier.
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path
resBytes, err := querier(ctx, path[2:], req)
if err != nil {
return abci.ResponseQuery{
Code: uint32(err.ABCICode()),
Log: err.ABCILog(),
}
}
return abci.ResponseQuery{
Code: uint32(sdk.ABCICodeOK),
Value: resBytes,
}
}
// BeginBlock implements the ABCI application interface.
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
if app.cms.TracingEnabled() {

41
baseapp/queryrouter.go Normal file
View File

@ -0,0 +1,41 @@
package baseapp
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// QueryRouter provides queryables for each query path.
type QueryRouter interface {
AddRoute(r string, h sdk.Querier) (rtr QueryRouter)
Route(path string) (h sdk.Querier)
}
type queryrouter struct {
routes map[string]sdk.Querier
}
// nolint
// NewRouter - create new router
// TODO either make Function unexported or make return type (router) Exported
func NewQueryRouter() *queryrouter {
return &queryrouter{
routes: map[string]sdk.Querier{},
}
}
// AddRoute - Adds an sdk.Querier to the route provided. Panics on duplicate
func (rtr *queryrouter) AddRoute(r string, q sdk.Querier) QueryRouter {
if !isAlphaNumeric(r) {
panic("route expressions can only contain alphanumeric characters")
}
if rtr.routes[r] != nil {
panic("route has already been initialized")
}
rtr.routes[r] = q
return rtr
}
// Returns the sdk.Querier for a certain route path
func (rtr *queryrouter) Route(path string) (h sdk.Querier) {
return rtr.routes[path]
}

View File

@ -74,6 +74,9 @@ func (app *BaseApp) Router() Router {
}
return app.router
}
func (app *BaseApp) QueryRouter() QueryRouter {
return app.queryRouter
}
func (app *BaseApp) Seal() { app.sealed = true }
func (app *BaseApp) IsSealed() bool { return app.sealed }
func (app *BaseApp) enforceSeal() {

View File

@ -31,6 +31,11 @@ func (ctx CLIContext) Query(path string) (res []byte, err error) {
return ctx.query(path, nil)
}
// Query information about the connected node with a data payload
func (ctx CLIContext) QueryWithData(path string, data []byte) (res []byte, err error) {
return ctx.query(path, data)
}
// QueryStore performs a query from a Tendermint node with the provided key and
// store name.
func (ctx CLIContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) {

View File

@ -52,7 +52,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().Int64(FlagGas, 200000, "gas limit to set per-transaction")
c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously")
c.Flags().Bool(FlagJson, false, "return output in json format")
c.Flags().Bool(FlagPrintResponse, false, "return tx response (only works with async = false)")
c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)")
}
return cmds
}

View File

@ -4,10 +4,20 @@ import (
"encoding/json"
"net/http"
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tmlibs/cli"
)
const (
// FlagAddress is the flag for the user's address on the command line.
FlagAddress = "address"
// FlagPublicKey represents the user's public key on the command line.
FlagPublicKey = "pubkey"
)
var showKeysCmd = &cobra.Command{
@ -18,13 +28,38 @@ var showKeysCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
info, err := getKey(name)
if err == nil {
printInfo(info)
if err != nil {
return err
}
return err
showAddress := viper.GetBool(FlagAddress)
showPublicKey := viper.GetBool(FlagPublicKey)
outputSet := cmd.Flag(cli.OutputFlag).Changed
if showAddress && showPublicKey {
return errors.New("cannot use both --address and --pubkey at once")
}
if outputSet && (showAddress || showPublicKey) {
return errors.New("cannot use --output with --address or --pubkey")
}
if showAddress {
printKeyAddress(info)
return nil
}
if showPublicKey {
printPubKey(info)
return nil
}
printInfo(info)
return nil
},
}
func init() {
showKeysCmd.Flags().Bool(FlagAddress, false, "output the address only (overrides --output)")
showKeysCmd.Flags().Bool(FlagPublicKey, false, "output the public key only (overrides --output)")
}
func getKey(name string) (keys.Info, error) {
kb, err := GetKeyBase()
if err != nil {

View File

@ -6,7 +6,7 @@ import (
"github.com/spf13/viper"
keys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/tendermint/tendermint/libs/cli"
dbm "github.com/tendermint/tendermint/libs/db"
@ -173,3 +173,19 @@ func printInfos(infos []keys.Info) {
func printKeyOutput(ko KeyOutput) {
fmt.Printf("%s\t%s\t%s\t%s\n", ko.Name, ko.Type, ko.Address, ko.PubKey)
}
func printKeyAddress(info keys.Info) {
ko, err := Bech32KeyOutput(info)
if err != nil {
panic(err)
}
fmt.Println(ko.Address.String())
}
func printPubKey(info keys.Info) {
ko, err := Bech32KeyOutput(info)
if err != nil {
panic(err)
}
fmt.Println(ko.PubKey)
}

View File

@ -595,7 +595,7 @@ func TestVote(t *testing.T) {
require.Equal(t, gov.OptionYes, vote.Option)
}
func TestUnrevoke(t *testing.T) {
func TestUnjail(t *testing.T) {
_, password := "test", "1234567890"
addr, _ := CreateAddr(t, "test", password, GetKeyBase(t))
cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})

View File

@ -27,7 +27,20 @@ const (
func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
Short: "Search for all transactions that match the given tags.",
Long: strings.TrimSpace(`
Search for transactions that match the given tags. By default, transactions must match ALL tags
passed to the --tags option. To match any transaction, use the --any option.
For example:
$ gaiacli tendermint txs --tag test1,test2
will match any transaction tagged with both test1,test2. To match a transaction tagged with either
test1 or test2, use:
$ gaiacli tendermint txs --tag test1,test2 --any
`),
RunE: func(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
@ -52,7 +65,7 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
// TODO: change this to false once proofs built in
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
cmd.Flags().StringSlice(flagTags, nil, "Comma-separated list of tags that must match")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}

View File

@ -105,6 +105,9 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
AddRoute("gov", gov.NewHandler(app.govKeeper))
app.QueryRouter().
AddRoute("gov", gov.NewQuerier(app.govKeeper))
// initialize BaseApp
app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(app.BeginBlocker)
@ -185,7 +188,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
// load the address to pubkey map
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData)
gov.InitGenesis(ctx, app.govKeeper, gov.DefaultGenesisState())
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
return abci.ResponseInitChain{
Validators: validators,
@ -208,6 +211,7 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val
genState := GenesisState{
Accounts: accounts,
StakeData: stake.WriteGenesis(ctx, app.stakeKeeper),
GovData: gov.WriteGenesis(ctx, app.govKeeper),
}
appState, err = wire.MarshalJSONIndent(app.cdc, genState)
if err != nil {

View File

@ -11,6 +11,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/spf13/pflag"
@ -32,6 +33,7 @@ var (
type GenesisState struct {
Accounts []GenesisAccount `json:"accounts"`
StakeData stake.GenesisState `json:"stake"`
GovData gov.GenesisState `json:"gov"`
}
// GenesisAccount doesn't need pubkey or sequence
@ -216,6 +218,7 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
genesisState = GenesisState{
Accounts: genaccs,
StakeData: stakeData,
GovData: gov.DefaultGenesisState(),
}
return
}

View File

@ -98,7 +98,7 @@ func testAndRunTxs(app *GaiaApp) []simulation.TestAndRunTx {
stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper),
stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper),
stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper),
slashingsim.SimulateMsgUnrevoke(app.slashingKeeper),
slashingsim.SimulateMsgUnjail(app.slashingKeeper),
}
}

View File

@ -95,7 +95,7 @@ func main() {
stakecmd.GetCmdDelegate(cdc),
stakecmd.GetCmdUnbond("stake", cdc),
stakecmd.GetCmdRedelegate("stake", cdc),
slashingcmd.GetCmdUnrevoke(cdc),
slashingcmd.GetCmdUnjail(cdc),
)...)
rootCmd.AddCommand(
stakeCmd,

View File

@ -802,7 +802,7 @@ The GovernanceAPI exposes all functionality needed for casting votes on plain te
## ICS23 - SlashingAPI
The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) validators and delegators in Proof-of-Stake. The penalization is a fine of the staking coin and jail time, defined by governance parameters. During the jail period, the penalized validator is `Revoked`.
The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) validators and delegators in Proof-of-Stake. The penalization is a fine of the staking coin and jail time, defined by governance parameters. During the jail period, the penalized validator is "jailed".
### GET /slashing/validator/{validatorAddr}/signing-info

View File

@ -469,7 +469,7 @@ Tendermint consensus engine. It would be initialized by a Genesis file, and it
would be driven by blocks of transactions committed by the underlying Tendermint
consensus. We'll talk more about ABCI and how this all works a bit later, but
feel free to check the
[specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md).
[specification](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/abci-spec.md).
We'll also see how to connect our app to a complete suite of components
for running and using a live blockchain application.

View File

@ -16,7 +16,7 @@ here we will introduce the other ABCI requests sent by Tendermint, and
how we can use them to build more advanced applications. For a more complete
depiction of the ABCI and how its used, see
[the
specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md)
specification](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/abci-spec.md)
## InitChain

View File

@ -6,7 +6,7 @@
The Cosmos SDK has all the necessary pre-built modules to add functionality on top of a `BaseApp`, which is the template to build a blockchain dApp in Cosmos. In this context, a `module` is a fundamental unit in the Cosmos SDK.
Each module is an extension of the `BaseApp`'s functionalities that defines transactions, handles application state and manages the state transition logic. Each module also contains handlers for messages and transactions, as well as REST and CLI for secure user interactions.
Each module is an extension of the `BaseApp`'s functionalities that defines transactions, handles application state and manages the state transition logic. Each module also contains handlers for messages and transactions, queriers for handling query requests, as well as REST and CLI for secure user interactions.
Some of the most important modules in the SDK are:

View File

@ -6,6 +6,23 @@ Uuse the CLI to create a new proposal:
simplegovcli propose --title="Voting Period update" --description="Should we change the proposal voting period to 3 weeks?" --deposit=300Atoms
```
Or, via a json file:
```bash
simplegovcli propose --proposal="path/to/proposal.json"
```
Where proposal.json contains:
```json
{
"title": "Voting Period Update",
"description": "Should we change the proposal voting period to 3 weeks?",
"type": "Text",
"deposit": "300Atoms"
}
```
Get the details of your newly created proposal:
```bash

159
docs/spec/auth/vesting.md Normal file
View File

@ -0,0 +1,159 @@
## Vesting
### Intro and Requirements
This paper specifies vesting account implementation for the Cosmos Hub.
The requirements for this vesting account is that it should be initialized during genesis with
a starting balance X coins and a vesting endtime T. The owner of this account should be able to delegate to validators
and vote with locked coins, however they cannot send locked coins to other accounts until those coins have been unlocked.
The vesting account should also be able to spend any coins it receives from other users.
Thus, the bank module's `MsgSend` handler should error if a vesting account is trying to send an amount that exceeds their
unlocked coin amount.
### Implementation
##### Vesting Account implementation
NOTE: `Now = ctx.BlockHeader().Time`
```go
type VestingAccount interface {
Account
AssertIsVestingAccount() // existence implies that account is vesting.
// Calculates amount of coins that can be sent to other accounts given the current time
SendableCoins(sdk.Context) sdk.Coins
}
// Implements Vesting Account
// Continuously vests by unlocking coins linearly with respect to time
type ContinuousVestingAccount struct {
BaseAccount
OriginalVestingCoins sdk.Coins // Coins in account on Initialization
ReceivedCoins sdk.Coins // Coins received from other accounts
SentCoins sdk.Coins // Coins sent to other accounts
// StartTime and EndTime used to calculate how much of OriginalCoins is unlocked at any given point
StartTime time.Time
EndTime time.Time
}
// Uses time in context to calculate total unlocked coins
SendableCoins(vacc ContinuousVestingAccount, ctx sdk.Context) sdk.Coins:
// Coins unlocked by vesting schedule
unlockedCoins := ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime)
// Must still check for currentCoins constraint since some unlocked coins may have been delegated.
currentCoins := vacc.BaseAccount.GetCoins()
// min will return sdk.Coins with each denom having the minimum amount from unlockedCoins and currentCoins
return min(unlockedCoins, currentCoins)
```
The `VestingAccount` interface is used to assert that an account is a vesting account like so:
```go
vacc, ok := acc.(VestingAccount); ok
```
as well as to calculate the SendableCoins at any given moment.
The `ContinuousVestingAccount` struct implements the Vesting account interface. It uses `OriginalVestingCoins`, `ReceivedCoins`,
`SentCoins`, `StartTime`, and `EndTime` to calculate how many coins are sendable at any given point.
Since the vesting restrictions need to be implemented on a per-module basis, the `ContinuousVestingAccount` implements
the `Account` interface exactly like `BaseAccount`. Thus, `ContinuousVestingAccount.GetCoins()` will return the total of
both locked coins and unlocked coins currently in the account. Delegated coins are deducted from `Account.GetCoins()`, but do not count against unlocked coins because they are still at stake and will be reinstated (partially if slashed) after waiting the full unbonding period.
##### Changes to Keepers/Handler
Since a vesting account should be capable of doing everything but sending with its locked coins, the restriction should be
handled at the `bank.Keeper` level. Specifically in methods that are explicitly used for sending like
`sendCoins` and `inputOutputCoins`. These methods must check that an account is a vesting account using the check described above.
```go
if acc is VestingAccount and Now < vestingAccount.EndTime:
// Check if amount is less than currently allowed sendable coins
if msg.Amount > vestingAccount.SendableCoins(ctx) then fail
else:
vestingAccount.SentCoins += msg.Amount
else:
// Account has fully vested, treat like regular account
if msg.Amount > account.GetCoins() then fail
// All checks passed, send the coins
SendCoins(inputs, outputs)
```
Coins that are sent to a vesting account after initialization by users sending them coins should be spendable
immediately after receiving them. Thus, handlers (like staking or bank) that send coins that a vesting account did not
originally own should increment `ReceivedCoins` by the amount sent.
Unlocked coins that are sent to other accounts will increment the vesting account's `SentCoins` attribute.
CONTRACT: Handlers SHOULD NOT update `ReceivedCoins` if they were originally sent from the vesting account. For example, if a vesting account unbonds from a validator, their tokens should be added back to account but staking handlers SHOULD NOT update `ReceivedCoins`.
However when a user sends coins to vesting account, then `ReceivedCoins` SHOULD be incremented.
### Initializing at Genesis
To initialize both vesting accounts and base accounts, the `GenesisAccount` struct will include an EndTime. Accounts meant to be
BaseAccounts will have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into BaseAccounts and VestingAccounts
as appropriate.
```go
type GenesisAccount struct {
Address sdk.AccAddress `json:"address"`
GenesisCoins sdk.Coins `json:"coins"`
EndTime int64 `json:"lock"`
}
initChainer:
for gacc in GenesisAccounts:
baseAccount := BaseAccount{
Address: gacc.Address,
Coins: gacc.GenesisCoins,
}
if gacc.EndTime != 0:
vestingAccount := ContinuouslyVestingAccount{
BaseAccount: baseAccount,
OriginalVestingCoins: gacc.GenesisCoins,
StartTime: RequestInitChain.Time,
EndTime: gacc.EndTime,
}
AddAccountToState(vestingAccount)
else:
AddAccountToState(baseAccount)
```
### Formulas
`OriginalVestingCoins`: Amount of coins in account at Genesis
`CurrentCoins`: Coins currently in the baseaccount (both locked and unlocked: `vestingAccount.GetCoins`)
`ReceivedCoins`: Coins received from other accounts (always unlocked)
`LockedCoins`: Coins that are currently locked
`Delegated`: Coins that have been delegated (no longer in account; may be locked or unlocked)
`Sent`: Coins sent to other accounts (MUST be unlocked)
Maximum amount of coins vesting schedule allows to be sent:
`ReceivedCoins - SentCoins + OriginalVestingCoins * (Now - StartTime) / (EndTime - StartTime)`
`ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins`
Coins currently in Account:
`CurrentCoins = OriginalVestingCoins + ReceivedCoins - Delegated - Sent`
`CurrentCoins = vestingAccount.GetCoins()`
**Maximum amount of coins spendable right now:**
`min( ReceivedCoins - SentCoins + OriginalVestingCoins - LockedCoins, CurrentCoins )`

View File

@ -47,5 +47,5 @@ type ValidatorSigningInfo struct {
Where:
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not).
* `JailedUntil` is set whenever the candidate is revoked due to downtime
* `JailedUntil` is set whenever the candidate is jailed due to downtime
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.

View File

@ -4,7 +4,7 @@
The Tendermint validator set may be updated by state transitions that run at
the end of every block. The Tendermint validator set may be changed by
validators either being revoked due to inactivity/unexpected behaviour (covered
validators either being jailed due to inactivity/unexpected behaviour (covered
in slashing) or changed in validator power. Determining which validator set
changes must be made occurs during staking transactions (and slashing
transactions) - during end-block the already accounted changes are applied and

View File

@ -71,7 +71,7 @@ validator.
```golang
type Validator struct {
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
Revoked bool // has the validator been revoked?
Jailed bool // has the validator been jailed?
Status sdk.BondStatus // validator status (bonded/unbonding/unbonded)
Tokens sdk.Dec // delegated tokens (incl. self-delegation)

View File

@ -72,12 +72,12 @@ gaiacli stake signing-information <validator-pubkey>\
--chain-id=<chain_id>
```
### Unrevoke Validator
### Unjail Validator
When a validator is `Revoked` for downtime, you must submit an `Unrevoke` transaction in order to be able to get block proposer rewards again (depends on the zone fee distribution).
When a validator is "jailed" for downtime, you must submit an `Unjail` transaction in order to be able to get block proposer rewards again (depends on the zone fee distribution).
```bash
gaiacli stake unrevoke \
gaiacli stake unjail \
--from=<key_name> \
--chain-id=<chain_id>
--validator=<account_cosmosaccaddr> \
@ -113,11 +113,11 @@ gaiad start
Wait for your full node to catch up to the latest block. Next, run the following command. Note that `<cosmosaccaddr>` is the address of your validator account, and `<name>` is the name of the validator account. You can find this info by running `gaiacli keys list`.
```bash
gaiacli stake unrevoke <cosmosaccaddr> --chain-id=<chain_id> --name=<name>
gaiacli stake unjail <cosmosaccaddr> --chain-id=<chain_id> --name=<name>
```
::: danger Warning
If you don't wait for `gaiad` to sync before running `unrevoke`, you will receive an error message telling you your validator is still jailed.
If you don't wait for `gaiad` to sync before running `unjail`, you will receive an error message telling you your validator is still jailed.
:::
Lastly, check your validator again to see if your voting power is back.

View File

@ -72,7 +72,7 @@ func main() {
stakecmd.GetCmdDelegate(cdc),
stakecmd.GetCmdUnbond("stake", cdc),
stakecmd.GetCmdRedelegate("stake", cdc),
slashingcmd.GetCmdUnrevoke(cdc),
slashingcmd.GetCmdUnjail(cdc),
)...)
// add proxy, version and key info

View File

@ -44,7 +44,7 @@ func (v Validator) GetDelegatorShares() sdk.Dec {
}
// Implements sdk.Validator
func (v Validator) GetRevoked() bool {
func (v Validator) GetJailed() bool {
return false
}
@ -127,11 +127,11 @@ func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int6
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
func (vs *ValidatorSet) Jail(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
func (vs *ValidatorSet) Unjail(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented")
}

View File

@ -93,6 +93,7 @@ func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.AccAddress, asso
func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.AccAddress) (res []sdk.AccAddress) {
res = make([]sdk.AccAddress, valset.maxAssoc)
iter := sdk.KVStorePrefixIterator(valset.store, GetAssocPrefix(base))
defer iter.Close()
i := 0
for ; iter.Valid(); iter.Next() {
key := iter.Key()

View File

@ -28,6 +28,7 @@ func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.Valid
prefix := GetSignPrefix(p, keeper.cdc)
store := ctx.KVStore(keeper.key)
iter := sdk.KVStorePrefixIterator(store, prefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if valset.Validator(ctx, iter.Value()) != nil {
store.Delete(iter.Key())

View File

@ -69,7 +69,7 @@ func ShowValidatorCmd(ctx *Context) *cobra.Command {
func UnsafeResetAllCmd(ctx *Context) *cobra.Command {
return &cobra.Command{
Use: "unsafe-reset-all",
Short: "Reset blockchain database, priv_validator.json file, and the logger",
Short: "Resets the blockchain database, removes address book files, and resets priv_validator.json to the genesis state",
RunE: func(cmd *cobra.Command, args []string) error {
cfg := ctx.Config
tcmd.ResetAll(cfg.DBDir(), cfg.P2P.AddrBookFile(), cfg.PrivValidatorFile(), ctx.Logger)

View File

@ -168,6 +168,7 @@ func TestIAVLSubspaceIterator(t *testing.T) {
require.EqualValues(t, value, expectedKey)
i++
}
iter.Close()
require.Equal(t, len(expected), i)
iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(55), byte(255), byte(255)})
@ -183,6 +184,7 @@ func TestIAVLSubspaceIterator(t *testing.T) {
require.EqualValues(t, value, []byte("test4"))
i++
}
iter.Close()
require.Equal(t, len(expected), i)
iter = sdk.KVStorePrefixIterator(iavlStore, []byte{byte(255), byte(255)})
@ -198,6 +200,7 @@ func TestIAVLSubspaceIterator(t *testing.T) {
require.EqualValues(t, value, []byte("test4"))
i++
}
iter.Close()
require.Equal(t, len(expected), i)
}

View File

@ -1,6 +1,7 @@
package types
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
@ -108,6 +109,23 @@ func (bz AccAddress) Format(s fmt.State, verb rune) {
}
}
// Returns boolean for whether two AccAddresses are Equal
func (bz AccAddress) Equals(bz2 AccAddress) bool {
if bz.Empty() && bz2.Empty() {
return true
}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
// Returns boolean for whether an AccAddress is empty
func (bz AccAddress) Empty() bool {
if bz == nil {
return true
}
bz2 := AccAddress{}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
//__________________________________________________________
// AccAddress a wrapper around bytes meant to represent a validator address
@ -192,6 +210,23 @@ func (bz ValAddress) Format(s fmt.State, verb rune) {
}
}
// Returns boolean for whether two ValAddresses are Equal
func (bz ValAddress) Equals(bz2 ValAddress) bool {
if bz.Empty() && bz2.Empty() {
return true
}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
// Returns boolean for whether an AccAddress is empty
func (bz ValAddress) Empty() bool {
if bz == nil {
return true
}
bz2 := ValAddress{}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
// Bech32ifyAccPub takes AccountPubKey and returns the bech32 encoded string
func Bech32ifyAccPub(pub crypto.PubKey) (string, error) {
return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes())

6
types/queryable.go Normal file
View File

@ -0,0 +1,6 @@
package types
import abci "github.com/tendermint/tendermint/abci/types"
// Type for querier functions on keepers to implement to handle custom queries
type Querier = func(ctx Context, path []string, req abci.RequestQuery) (res []byte, err Error)

View File

@ -37,7 +37,7 @@ func (b BondStatus) Equal(b2 BondStatus) bool {
// validator for a delegated proof of stake system
type Validator interface {
GetRevoked() bool // whether the validator is revoked
GetJailed() bool // whether the validator is jailed
GetMoniker() string // moniker of the validator
GetStatus() BondStatus // status of the validator
GetOperator() AccAddress // owner AccAddress to receive/return validators coins
@ -73,8 +73,8 @@ type ValidatorSet interface {
// slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction
Slash(Context, crypto.PubKey, int64, int64, Dec)
Revoke(Context, crypto.PubKey) // revoke a validator
Unrevoke(Context, crypto.PubKey) // unrevoke a validator
Jail(Context, crypto.PubKey) // jail a validator
Unjail(Context, crypto.PubKey) // unjail a validator
}
//_______________________________________________________________________________

View File

@ -87,6 +87,7 @@ func (am AccountMapper) SetAccount(ctx sdk.Context, acc Account) {
func (am AccountMapper) IterateAccounts(ctx sdk.Context, process func(Account) (stop bool)) {
store := ctx.KVStore(am.key)
iter := sdk.KVStorePrefixIterator(store, []byte("account:"))
defer iter.Close()
for {
if !iter.Valid() {
return

View File

@ -12,9 +12,12 @@ import (
authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context"
"github.com/cosmos/cosmos-sdk/x/gov"
"encoding/json"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"io/ioutil"
"strings"
)
const (
@ -28,18 +31,51 @@ const (
flagDepositer = "depositer"
flagStatus = "status"
flagLatestProposalIDs = "latest"
flagProposal = "proposal"
)
type proposal struct {
Title string
Description string
Type string
Deposit string
}
var proposalFlags = []string{
flagTitle,
flagDescription,
flagProposalType,
flagDeposit,
}
// GetCmdSubmitProposal implements submitting a proposal transaction command.
func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "submit-proposal",
Short: "Submit a proposal along with an initial deposit",
Long: strings.TrimSpace(`
Submit a proposal along with an initial deposit. Proposal title, description, type and deposit can be given directly or through a proposal JSON file. For example:
$ gaiacli gov submit-proposal --proposal="path/to/proposal.json"
where proposal.json contains:
{
"title": "Test Proposal",
"description": "My awesome proposal",
"type": "Text",
"deposit": "1000test"
}
is equivalent to
$ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="1000test"
`),
RunE: func(cmd *cobra.Command, args []string) error {
title := viper.GetString(flagTitle)
description := viper.GetString(flagDescription)
strProposalType := viper.GetString(flagProposalType)
initialDeposit := viper.GetString(flagDeposit)
proposal, err := parseSubmitProposalFlags()
if err != nil {
return err
}
txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc)
cliCtx := context.NewCLIContext().
@ -52,17 +88,17 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
return err
}
amount, err := sdk.ParseCoins(initialDeposit)
amount, err := sdk.ParseCoins(proposal.Deposit)
if err != nil {
return err
}
proposalType, err := gov.ProposalTypeFromString(strProposalType)
proposalType, err := gov.ProposalTypeFromString(proposal.Type)
if err != nil {
return err
}
msg := gov.NewMsgSubmitProposal(title, description, proposalType, fromAddr, amount)
msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount)
err = msg.ValidateBasic()
if err != nil {
@ -80,10 +116,42 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
cmd.Flags().String(flagDescription, "", "description of proposal")
cmd.Flags().String(flagProposalType, "", "proposalType of proposal")
cmd.Flags().String(flagDeposit, "", "deposit of proposal")
cmd.Flags().String(flagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)")
return cmd
}
func parseSubmitProposalFlags() (*proposal, error) {
proposal := &proposal{}
proposalFile := viper.GetString(flagProposal)
if proposalFile == "" {
proposal.Title = viper.GetString(flagTitle)
proposal.Description = viper.GetString(flagDescription)
proposal.Type = viper.GetString(flagProposalType)
proposal.Deposit = viper.GetString(flagDeposit)
return proposal, nil
}
for _, flag := range proposalFlags {
if viper.GetString(flag) != "" {
return nil, fmt.Errorf("--%s flag provided alongside --proposal, which is a noop", flag)
}
}
contents, err := ioutil.ReadFile(proposalFile)
if err != nil {
return nil, err
}
err = json.Unmarshal(contents, proposal)
if err != nil {
return nil, err
}
return proposal, nil
}
// GetCmdDeposit implements depositing tokens for an active proposal.
func GetCmdDeposit(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{

View File

@ -0,0 +1,70 @@
package cli
import (
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"io/ioutil"
"testing"
)
func TestParseSubmitProposalFlags(t *testing.T) {
okJSON, err := ioutil.TempFile("", "proposal")
require.Nil(t, err, "unexpected error")
okJSON.WriteString(`
{
"title": "Test Proposal",
"description": "My awesome proposal",
"type": "Text",
"deposit": "1000test"
}
`)
badJSON, err := ioutil.TempFile("", "proposal")
require.Nil(t, err, "unexpected error")
badJSON.WriteString("bad json")
// nonexistent json
viper.Set(flagProposal, "fileDoesNotExist")
_, err = parseSubmitProposalFlags()
require.Error(t, err)
// invalid json
viper.Set(flagProposal, badJSON.Name())
_, err = parseSubmitProposalFlags()
require.Error(t, err)
// ok json
viper.Set(flagProposal, okJSON.Name())
proposal1, err := parseSubmitProposalFlags()
require.Nil(t, err, "unexpected error")
require.Equal(t, "Test Proposal", proposal1.Title)
require.Equal(t, "My awesome proposal", proposal1.Description)
require.Equal(t, "Text", proposal1.Type)
require.Equal(t, "1000test", proposal1.Deposit)
// flags that can't be used with --proposal
for _, incompatibleFlag := range proposalFlags {
viper.Set(incompatibleFlag, "some value")
_, err := parseSubmitProposalFlags()
require.Error(t, err)
viper.Set(incompatibleFlag, "")
}
// no --proposal, only flags
viper.Set(flagProposal, "")
viper.Set(flagTitle, proposal1.Title)
viper.Set(flagDescription, proposal1.Description)
viper.Set(flagProposalType, proposal1.Type)
viper.Set(flagDeposit, proposal1.Deposit)
proposal2, err := parseSubmitProposalFlags()
require.Nil(t, err, "unexpected error")
require.Equal(t, proposal1.Title, proposal2.Title)
require.Equal(t, proposal1.Description, proposal2.Description)
require.Equal(t, proposal1.Type, proposal2.Type)
require.Equal(t, proposal1.Deposit, proposal2.Deposit)
err = okJSON.Close()
require.Nil(t, err, "unexpected error")
err = badJSON.Close()
require.Nil(t, err, "unexpected error")
}

View File

@ -3,7 +3,6 @@ package rest
import (
"fmt"
"net/http"
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -21,6 +20,7 @@ const (
RestDepositer = "depositer"
RestVoter = "voter"
RestProposalStatus = "status"
RestNumLatest = "latest"
storeName = "gov"
)
@ -97,16 +97,13 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
var req depositReq
err = buildReq(w, r, cdc, &req)
err := buildReq(w, r, cdc, &req)
if err != nil {
return
}
@ -139,15 +136,13 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
var req voteReq
err = buildReq(w, r, cdc, &req)
err := buildReq(w, r, cdc, &req)
if err != nil {
return
}
@ -180,36 +175,33 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
params := gov.QueryProposalParams{
ProposalID: proposalID,
}
var proposal gov.Proposal
cdc.MustUnmarshalBinary(res, &proposal)
output, err := wire.MarshalJSONIndent(cdc, proposal)
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
res, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}
@ -227,12 +219,8 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
@ -255,36 +243,43 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName)
if err != nil || len(res) == 0 {
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
params := gov.QueryDepositParams{
ProposalID: proposalID,
Depositer: depositerAddr,
}
return
}
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID)
res, err := cliCtx.QueryWithData("custom/gov/deposit", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
var deposit gov.Deposit
cdc.MustUnmarshalBinary(res, &deposit)
output, err := wire.MarshalJSONIndent(cdc, deposit)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
cdc.UnmarshalJSON(res, &deposit)
if deposit.Empty() {
res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID}))
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}
@ -301,12 +296,8 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("proposalID [%s] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
@ -328,36 +319,47 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName)
if err != nil || len(res) == 0 {
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
params := gov.QueryVoteParams{
Voter: voterAddr,
ProposalID: proposalID,
}
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("voter [%s] did not vote on proposalID [%d]", bechVoterAddr, proposalID)
res, err := cliCtx.QueryWithData("custom/gov/vote", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
var vote gov.Vote
cdc.MustUnmarshalBinary(res, &vote)
output, err := wire.MarshalJSONIndent(cdc, vote)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
cdc.UnmarshalJSON(res, &vote)
if vote.Empty() {
bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID})
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
res, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("voter [%s] did not deposit on proposalID [%d]", bechVoterAddr, proposalID)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}
@ -376,59 +378,31 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("proposalID [%s] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
params := gov.QueryVotesParams{
ProposalID: proposalID,
}
var proposal gov.Proposal
cdc.MustUnmarshalBinary(res, &proposal)
if proposal.GetStatus() != gov.StatusVotingPeriod {
err := errors.Errorf("proposal is not in Voting Period", proposalID)
w.Write([]byte(err.Error()))
return
}
res2, err := cliCtx.QuerySubspace(gov.KeyVotesSubspace(proposalID), storeName)
if err != nil {
err = errors.New("ProposalID doesn't exist")
w.Write([]byte(err.Error()))
return
}
var votes []gov.Vote
for i := 0; i < len(res2); i++ {
var vote gov.Vote
cdc.MustUnmarshalBinary(res2[i].Value, &vote)
votes = append(votes, vote)
}
output, err := wire.MarshalJSONIndent(cdc, votes)
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
res, err := cliCtx.QueryWithData("custom/gov/votes", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(res)
}
}
@ -439,24 +413,23 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
bechVoterAddr := r.URL.Query().Get(RestVoter)
bechDepositerAddr := r.URL.Query().Get(RestDepositer)
strProposalStatus := r.URL.Query().Get(RestProposalStatus)
strNumLatest := r.URL.Query().Get(RestNumLatest)
var err error
var voterAddr sdk.AccAddress
var depositerAddr sdk.AccAddress
var proposalStatus gov.ProposalStatus
params := gov.QueryProposalsParams{}
if len(bechVoterAddr) != 0 {
voterAddr, err = sdk.AccAddressFromBech32(bechVoterAddr)
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter)
w.Write([]byte(err.Error()))
return
}
params.Voter = voterAddr
}
if len(bechDepositerAddr) != 0 {
depositerAddr, err = sdk.AccAddressFromBech32(bechDepositerAddr)
depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer)
@ -464,10 +437,11 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
return
}
params.Depositer = depositerAddr
}
if len(strProposalStatus) != 0 {
proposalStatus, err = gov.ProposalStatusFromString(strProposalStatus)
proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus)
@ -475,63 +449,33 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
return
}
params.ProposalStatus = proposalStatus
}
if len(strNumLatest) != 0 {
numLatest, ok := parseInt64OrReturnBadRequest(strNumLatest, w)
if !ok {
return
}
params.NumLatestProposals = numLatest
}
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyNextProposalID, storeName)
res, err := cliCtx.QueryWithData("custom/gov/proposals", bz)
if err != nil {
err = errors.New("no proposals exist yet and proposalID has not been set")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
var maxProposalID int64
cdc.MustUnmarshalBinary(res, &maxProposalID)
matchingProposals := []gov.Proposal{}
for proposalID := int64(0); proposalID < maxProposalID; proposalID++ {
if voterAddr != nil {
res, err = cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName)
if err != nil || len(res) == 0 {
continue
}
}
if depositerAddr != nil {
res, err = cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName)
if err != nil || len(res) == 0 {
continue
}
}
res, err = cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
continue
}
var proposal gov.Proposal
cdc.MustUnmarshalBinary(res, &proposal)
if len(strProposalStatus) != 0 {
if proposal.GetStatus() != proposalStatus {
continue
}
}
matchingProposals = append(matchingProposals, proposal)
}
output, err := wire.MarshalJSONIndent(cdc, matchingProposals)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}

View File

@ -1,8 +1,10 @@
package rest
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -100,3 +102,15 @@ func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq base
w.Write(output)
}
func parseInt64OrReturnBadRequest(s string, w http.ResponseWriter) (n int64, ok bool) {
var err error
n, err = strconv.ParseInt(s, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := fmt.Errorf("'%s' is not a valid int64", s)
w.Write([]byte(err.Error()))
return 0, false
}
return n, true
}

View File

@ -15,6 +15,17 @@ type Vote struct {
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
}
// Returns whether 2 votes are equal
func (voteA Vote) Equals(voteB Vote) bool {
return voteA.Voter.Equals(voteB.Voter) && voteA.ProposalID == voteB.ProposalID && voteA.Option == voteB.Option
}
// Returns whether a vote is empty
func (voteA Vote) Empty() bool {
voteB := Vote{}
return voteA.Equals(voteB)
}
// Deposit
type Deposit struct {
Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer
@ -22,6 +33,17 @@ type Deposit struct {
Amount sdk.Coins `json:"amount"` // Deposit amount
}
// Returns whether 2 deposits are equal
func (depositA Deposit) Equals(depositB Deposit) bool {
return depositA.Depositer.Equals(depositB.Depositer) && depositA.ProposalID == depositB.ProposalID && depositA.Amount.IsEqual(depositB.Amount)
}
// Returns whether a deposit is empty
func (depositA Deposit) Empty() bool {
depositB := Deposit{}
return depositA.Equals(depositB)
}
// Type that represents VoteOption as a byte
type VoteOption byte

View File

@ -108,6 +108,52 @@ func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) {
store.Delete(KeyProposal(proposal.GetProposalID()))
}
// nolint: gocyclo
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal {
maxProposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return nil
}
matchingProposals := []Proposal{}
if numLatest <= 0 {
numLatest = maxProposalID
}
for proposalID := maxProposalID - numLatest; proposalID < maxProposalID; proposalID++ {
if voterAddr != nil && len(voterAddr) != 0 {
_, found := keeper.GetVote(ctx, proposalID, voterAddr)
if !found {
continue
}
}
if depositerAddr != nil && len(depositerAddr) != 0 {
_, found := keeper.GetDeposit(ctx, proposalID, depositerAddr)
if !found {
continue
}
}
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
continue
}
if validProposalStatus(status) {
if proposal.GetStatus() != status {
continue
}
}
matchingProposals = append(matchingProposals, proposal)
}
return matchingProposals
}
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk.Error {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
@ -121,16 +167,15 @@ func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk
// Get the last used proposal ID
func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
proposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return 0
}
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
proposalID--
return
}
// Gets the next available ProposalID and increments it
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
@ -143,6 +188,17 @@ func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sd
return proposalID, nil
}
// Peeks the next available ProposalID without incrementing it
func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
}
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
return proposalID, nil
}
func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) {
proposal.SetVotingStartBlock(ctx.BlockHeight())
proposal.SetStatus(StatusVotingPeriod)

View File

@ -130,6 +130,7 @@ func TestDeposits(t *testing.T) {
require.Equal(t, fourSteak, deposit.Amount)
depositsIterator.Next()
require.False(t, depositsIterator.Valid())
depositsIterator.Close()
// Test Refund Deposits
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
@ -196,6 +197,7 @@ func TestVotes(t *testing.T) {
require.Equal(t, OptionNoWithVeto, vote.Option)
votesIterator.Next()
require.False(t, votesIterator.Valid())
votesIterator.Close()
}
func TestProposalQueues(t *testing.T) {

View File

@ -110,6 +110,7 @@ type ProposalKind byte
//nolint
const (
ProposalTypeNil ProposalKind = 0x00
ProposalTypeText ProposalKind = 0x01
ProposalTypeParameterChange ProposalKind = 0x02
ProposalTypeSoftwareUpgrade ProposalKind = 0x03
@ -203,6 +204,7 @@ type ProposalStatus byte
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
@ -220,6 +222,8 @@ func ProposalStatusFromString(str string) (ProposalStatus, error) {
return StatusPassed, nil
case "Rejected":
return StatusRejected, nil
case "":
return StatusNil, nil
default:
return ProposalStatus(0xff), errors.Errorf("'%s' is not a valid proposal status", str)
}

213
x/gov/queryable.go Normal file
View File

@ -0,0 +1,213 @@
package gov
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
abci "github.com/tendermint/tendermint/abci/types"
)
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] {
case "proposal":
return queryProposal(ctx, path[1:], req, keeper)
case "deposit":
return queryDeposit(ctx, path[1:], req, keeper)
case "vote":
return queryVote(ctx, path[1:], req, keeper)
case "deposits":
return queryDeposits(ctx, path[1:], req, keeper)
case "votes":
return queryVotes(ctx, path[1:], req, keeper)
case "proposals":
return queryProposals(ctx, path[1:], req, keeper)
case "tally":
return queryTally(ctx, path[1:], req, keeper)
default:
return nil, sdk.ErrUnknownRequest("unknown gov query endpoint")
}
}
}
// Params for query 'custom/gov/proposal'
type QueryProposalParams struct {
ProposalID int64
}
func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryProposalParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
proposal := keeper.GetProposal(ctx, params.ProposalID)
if proposal == nil {
return []byte{}, ErrUnknownProposal(DefaultCodespace, params.ProposalID)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, proposal)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/deposit'
type QueryDepositParams struct {
ProposalID int64
Depositer sdk.AccAddress
}
func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryDepositParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
deposit, _ := keeper.GetDeposit(ctx, params.ProposalID, params.Depositer)
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, deposit)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/vote'
type QueryVoteParams struct {
ProposalID int64
Voter sdk.AccAddress
}
func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryVoteParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
vote, _ := keeper.GetVote(ctx, params.ProposalID, params.Voter)
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, vote)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/deposits'
type QueryDepositsParams struct {
ProposalID int64
}
func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryDepositParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
var deposits []Deposit
depositsIterator := keeper.GetDeposits(ctx, params.ProposalID)
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := Deposit{}
keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit)
deposits = append(deposits, deposit)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, deposits)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/votes'
type QueryVotesParams struct {
ProposalID int64
}
func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryVotesParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
var votes []Vote
votesIterator := keeper.GetVotes(ctx, params.ProposalID)
for ; votesIterator.Valid(); votesIterator.Next() {
vote := Vote{}
keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote)
votes = append(votes, vote)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, votes)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/proposals'
type QueryProposalsParams struct {
Voter sdk.AccAddress
Depositer sdk.AccAddress
ProposalStatus ProposalStatus
NumLatestProposals int64
}
func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryProposalsParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals)
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, proposals)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/tally'
type QueryTallyParams struct {
ProposalID int64
}
func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
// TODO: Dependant on #1914
var proposalID int64
err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
return []byte{}, ErrUnknownProposal(DefaultCodespace, proposalID)
}
var tallyResult TallyResult
if proposal.GetStatus() == StatusDepositPeriod {
tallyResult = EmptyTallyResult()
} else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected {
tallyResult = proposal.GetTallyResult()
} else {
_, tallyResult, _ = tally(ctx, keeper, proposal)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, tallyResult)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}

View File

@ -36,6 +36,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall
// iterate over all the votes
votesIterator := keeper.GetVotes(ctx, proposal.GetProposalID())
defer votesIterator.Close()
for ; votesIterator.Valid(); votesIterator.Next() {
vote := &Vote{}
keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), vote)
@ -64,7 +65,6 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall
keeper.deleteVote(ctx, vote.ProposalID, vote.Voter)
}
votesIterator.Close()
// Iterate over the validators again to tally their voting power and see who didn't vote
nonVoting = []sdk.AccAddress{}

View File

@ -354,7 +354,7 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) {
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyRevokedValidator(t *testing.T) {
func TestTallyJailedValidator(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
mapp.BeginBlock(abci.RequestBeginBlock{})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
@ -368,7 +368,7 @@ func TestTallyRevokedValidator(t *testing.T) {
val2, found := sk.GetValidator(ctx, addrs[1])
require.True(t, found)
sk.Revoke(ctx, val2.PubKey)
sk.Jail(ctx, val2.PubKey)
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
proposalID := proposal.GetProposalID()

View File

@ -110,12 +110,12 @@ func TestSlashingMsgs(t *testing.T) {
require.Equal(t, addr1, validator.Operator)
require.Equal(t, sdk.Bonded, validator.Status)
require.True(sdk.DecEq(t, sdk.NewDec(10), validator.BondedTokens()))
unrevokeMsg := MsgUnrevoke{ValidatorAddr: sdk.AccAddress(validator.PubKey.Address())}
unjailMsg := MsgUnjail{ValidatorAddr: sdk.AccAddress(validator.PubKey.Address())}
// no signing info yet
checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false)
// unrevoke should fail with unknown validator
res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unrevokeMsg}, []int64{0}, []int64{1}, false, priv1)
require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotRevoked), res.Code)
// unjail should fail with unknown validator
res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, priv1)
require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), res.Code)
}

View File

@ -14,12 +14,12 @@ import (
"github.com/spf13/cobra"
)
// GetCmdUnrevoke implements the create unrevoke validator command.
func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command {
// GetCmdUnjail implements the create unjail validator command.
func GetCmdUnjail(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unrevoke",
Use: "unjail",
Args: cobra.ExactArgs(0),
Short: "unrevoke validator previously revoked for downtime",
Short: "unjail validator previously jailed for downtime",
RunE: func(cmd *cobra.Command, args []string) error {
txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc)
cliCtx := context.NewCLIContext().
@ -32,7 +32,7 @@ func GetCmdUnrevoke(cdc *wire.Codec) *cobra.Command {
return err
}
msg := slashing.NewMsgUnrevoke(validatorAddr)
msg := slashing.NewMsgUnjail(validatorAddr)
return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg})
},

View File

@ -19,13 +19,13 @@ import (
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
r.HandleFunc(
"/slashing/unrevoke",
unrevokeRequestHandlerFn(cdc, kb, cliCtx),
"/slashing/unjail",
unjailRequestHandlerFn(cdc, kb, cliCtx),
).Methods("POST")
}
// Unrevoke TX body
type UnrevokeBody struct {
// Unjail TX body
type UnjailBody struct {
LocalAccountName string `json:"name"`
Password string `json:"password"`
ChainID string `json:"chain_id"`
@ -35,9 +35,9 @@ type UnrevokeBody struct {
ValidatorAddr string `json:"validator_addr"`
}
func unrevokeRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc {
func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var m UnrevokeBody
var m UnjailBody
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
@ -79,7 +79,7 @@ func unrevokeRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C
Gas: m.Gas,
}
msg := slashing.NewMsgUnrevoke(validatorAddr)
msg := slashing.NewMsgUnjail(validatorAddr)
txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg})
if err != nil {

View File

@ -12,9 +12,9 @@ const (
// Default slashing codespace
DefaultCodespace sdk.CodespaceType = 10
CodeInvalidValidator CodeType = 101
CodeValidatorJailed CodeType = 102
CodeValidatorNotRevoked CodeType = 103
CodeInvalidValidator CodeType = 101
CodeValidatorJailed CodeType = 102
CodeValidatorNotJailed CodeType = 103
)
func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
@ -24,8 +24,8 @@ func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address")
}
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked")
return sdk.NewError(codespace, CodeValidatorJailed, "validator still jailed, cannot yet be unjailed")
}
func ErrValidatorNotRevoked(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeValidatorNotRevoked, "validator not revoked, cannot be unrevoked")
func ErrValidatorNotJailed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeValidatorNotJailed, "validator not jailed, cannot be unjailed")
}

View File

@ -8,17 +8,17 @@ func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
// NOTE msg already has validate basic run
switch msg := msg.(type) {
case MsgUnrevoke:
return handleMsgUnrevoke(ctx, msg, k)
case MsgUnjail:
return handleMsgUnjail(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
}
}
}
// Validators must submit a transaction to unrevoke itself after
// having been revoked (and thus unbonded) for downtime
func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result {
// Validators must submit a transaction to unjail itself after
// having been jailed (and thus unbonded) for downtime
func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result {
// Validator must exist
validator := k.validatorSet.Validator(ctx, msg.ValidatorAddr)
@ -26,8 +26,8 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result {
return ErrNoValidatorForAddress(k.codespace).Result()
}
if !validator.GetRevoked() {
return ErrValidatorNotRevoked(k.codespace).Result()
if !validator.GetJailed() {
return ErrValidatorNotJailed(k.codespace).Result()
}
addr := sdk.ValAddress(validator.GetPubKey().Address())
@ -38,19 +38,19 @@ func handleMsgUnrevoke(ctx sdk.Context, msg MsgUnrevoke, k Keeper) sdk.Result {
return ErrNoValidatorForAddress(k.codespace).Result()
}
// Cannot be unrevoked until out of jail
// Cannot be unjailed until out of jail
if ctx.BlockHeader().Time.Before(info.JailedUntil) {
return ErrValidatorJailed(k.codespace).Result()
}
// Update the starting height (so the validator can't be immediately revoked again)
// Update the starting height (so the validator can't be immediately jailed again)
info.StartHeight = ctx.BlockHeight()
k.setValidatorSigningInfo(ctx, addr, info)
// Unrevoke the validator
k.validatorSet.Unrevoke(ctx, validator.GetPubKey())
// Unjail the validator
k.validatorSet.Unjail(ctx, validator.GetPubKey())
tags := sdk.NewTags("action", []byte("unrevoke"), "validator", []byte(msg.ValidatorAddr.String()))
tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String()))
return sdk.Result{
Tags: tags,

View File

@ -9,7 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/stake"
)
func TestCannotUnrevokeUnlessRevoked(t *testing.T) {
func TestCannotUnjailUnlessJailed(t *testing.T) {
// initial setup
ctx, ck, sk, _, keeper := createTestInput(t)
slh := NewHandler(keeper)
@ -22,8 +22,8 @@ func TestCannotUnrevokeUnlessRevoked(t *testing.T) {
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower()))
// assert non-revoked validator can't be unrevoked
got = slh(ctx, NewMsgUnrevoke(addr))
require.False(t, got.IsOK(), "allowed unrevoke of non-revoked validator")
require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotRevoked), got.Code)
// assert non-jailed validator can't be unjailed
got = slh(ctx, NewMsgUnjail(addr))
require.False(t, got.IsOK(), "allowed unjail of non-jailed validator")
require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), got.Code)
}

View File

@ -59,10 +59,10 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
// Slash validator
k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, k.SlashFractionDoubleSign(ctx))
// Revoke validator
k.validatorSet.Revoke(ctx, pubkey)
// Jail validator
k.validatorSet.Jail(ctx, pubkey)
// Set validator jail duration
signInfo, found := k.getValidatorSigningInfo(ctx, address)
if !found {
panic(fmt.Sprintf("Expected signing info for validator %s but not found", address))
@ -113,16 +113,16 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p
minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx)
if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) {
validator := k.validatorSet.ValidatorByPubKey(ctx, pubkey)
if validator != nil && !validator.GetRevoked() {
// Downtime confirmed, slash, revoke, and jail the validator
if validator != nil && !validator.GetJailed() {
// Downtime confirmed: slash and jail the validator
logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d",
pubkey.Address(), minHeight, k.MinSignedPerWindow(ctx)))
k.validatorSet.Slash(ctx, pubkey, height, power, k.SlashFractionDowntime(ctx))
k.validatorSet.Revoke(ctx, pubkey)
k.validatorSet.Jail(ctx, pubkey)
signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx))
} else {
// Validator was (a) not found or (b) already revoked, don't slash
logger.Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already revoked",
// Validator was (a) not found or (b) already jailed, don't slash
logger.Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed",
pubkey.Address()))
}
}

View File

@ -39,10 +39,10 @@ func TestHandleDoubleSign(t *testing.T) {
// double sign less than max age
keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt)
// should be revoked
require.True(t, sk.Validator(ctx, addr).GetRevoked())
// unrevoke to measure power
sk.Unrevoke(ctx, val)
// should be jailed
require.True(t, sk.Validator(ctx, addr).GetJailed())
// unjail to measure power
sk.Unjail(ctx, val)
// power should be reduced
require.Equal(t, sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), sk.Validator(ctx, addr).GetPower())
ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.MaxEvidenceAge(ctx))})
@ -112,17 +112,17 @@ func TestHandleAbsentValidator(t *testing.T) {
require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter)
// validator should have been revoked
// validator should have been jailed
validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus())
// unrevocation should fail prior to jail expiration
got = slh(ctx, NewMsgUnrevoke(addr))
got = slh(ctx, NewMsgUnjail(addr))
require.False(t, got.IsOK())
// unrevocation should succeed after jail expiration
ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeUnbondDuration(ctx))})
got = slh(ctx, NewMsgUnrevoke(addr))
got = slh(ctx, NewMsgUnjail(addr))
require.True(t, got.IsOK())
// validator should be rebonded now
@ -140,7 +140,7 @@ func TestHandleAbsentValidator(t *testing.T) {
require.Equal(t, height, info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter)
// validator should not be immediately revoked again
// validator should not be immediately jailed again
height++
ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false)
@ -154,7 +154,7 @@ func TestHandleAbsentValidator(t *testing.T) {
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false)
}
// validator should be revoked again after 500 unsigned blocks
// validator should be jailed again after 500 unsigned blocks
nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1
for ; height <= nextHeight; height++ {
ctx = ctx.WithBlockHeight(height)
@ -166,7 +166,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// Test a new validator entering the validator set
// Ensure that SigningInfo.StartHeight is set correctly
// and that they are not immediately revoked
// and that they are not immediately jailed
func TestHandleNewValidator(t *testing.T) {
// initial setup
ctx, ck, sk, _, keeper := createTestInput(t)
@ -194,16 +194,16 @@ func TestHandleNewValidator(t *testing.T) {
require.Equal(t, int64(1), info.SignedBlocksCounter)
require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil)
// validator should be bonded still, should not have been revoked or slashed
// validator should be bonded still, should not have been jailed or slashed
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(100), pool.BondedTokens.RoundInt64())
}
// Test a revoked validator being "down" twice
// Test a jailed validator being "down" twice
// Ensure that they're only slashed once
func TestHandleAlreadyRevoked(t *testing.T) {
func TestHandleAlreadyJailed(t *testing.T) {
// initial setup
ctx, _, sk, _, keeper := createTestInput(t)
@ -228,7 +228,7 @@ func TestHandleAlreadyRevoked(t *testing.T) {
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false)
}
// validator should have been revoked and slashed
// validator should have been jailed and slashed
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus())

View File

@ -11,25 +11,25 @@ var cdc = wire.NewCodec()
const MsgType = "slashing"
// verify interface at compile time
var _ sdk.Msg = &MsgUnrevoke{}
var _ sdk.Msg = &MsgUnjail{}
// MsgUnrevoke - struct for unrevoking revoked validator
type MsgUnrevoke struct {
// MsgUnjail - struct for unjailing jailed validator
type MsgUnjail struct {
ValidatorAddr sdk.AccAddress `json:"address"` // address of the validator owner
}
func NewMsgUnrevoke(validatorAddr sdk.AccAddress) MsgUnrevoke {
return MsgUnrevoke{
func NewMsgUnjail(validatorAddr sdk.AccAddress) MsgUnjail {
return MsgUnjail{
ValidatorAddr: validatorAddr,
}
}
//nolint
func (msg MsgUnrevoke) Type() string { return MsgType }
func (msg MsgUnrevoke) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.ValidatorAddr} }
func (msg MsgUnjail) Type() string { return MsgType }
func (msg MsgUnjail) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.ValidatorAddr} }
// get the bytes for the message signer to sign on
func (msg MsgUnrevoke) GetSignBytes() []byte {
func (msg MsgUnjail) GetSignBytes() []byte {
b, err := cdc.MarshalJSON(msg)
if err != nil {
panic(err)
@ -38,7 +38,7 @@ func (msg MsgUnrevoke) GetSignBytes() []byte {
}
// quick validity check
func (msg MsgUnrevoke) ValidateBasic() sdk.Error {
func (msg MsgUnjail) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrBadValidatorAddr(DefaultCodespace)
}

View File

@ -8,9 +8,9 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestMsgUnrevokeGetSignBytes(t *testing.T) {
func TestMsgUnjailGetSignBytes(t *testing.T) {
addr := sdk.AccAddress("abcd")
msg := NewMsgUnrevoke(addr)
msg := NewMsgUnjail(addr)
bytes := msg.GetSignBytes()
require.Equal(t, string(bytes), `{"address":"cosmosaccaddr1v93xxeqhyqz5v"}`)
}

View File

@ -60,9 +60,9 @@ func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil t
// Signing info for a validator
type ValidatorSigningInfo struct {
StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unrevoked
StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed
IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array
JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unrevoked until
JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until
SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time)
}

View File

@ -15,20 +15,20 @@ import (
"github.com/cosmos/cosmos-sdk/x/slashing"
)
// SimulateMsgUnrevoke
func SimulateMsgUnrevoke(k slashing.Keeper) simulation.TestAndRunTx {
// SimulateMsgUnjail
func SimulateMsgUnjail(k slashing.Keeper) simulation.TestAndRunTx {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
key := simulation.RandomKey(r, keys)
address := sdk.AccAddress(key.PubKey().Address())
msg := slashing.NewMsgUnrevoke(address)
msg := slashing.NewMsgUnjail(address)
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
ctx, write := ctx.CacheContext()
result := slashing.NewHandler(k)(ctx, msg)
if result.IsOK() {
write()
}
event(fmt.Sprintf("slashing/MsgUnrevoke/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgUnrevoke: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
event(fmt.Sprintf("slashing/MsgUnjail/%v", result.IsOK()))
action = fmt.Sprintf("TestMsgUnjail: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
return action, nil
}
}

View File

@ -77,7 +77,7 @@ func TestBeginBlocker(t *testing.T) {
BeginBlocker(ctx, req, keeper)
}
// validator should be revoked
// validator should be jailed
validator, found := sk.GetValidatorByPubKey(ctx, pk)
require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.GetStatus())

View File

@ -6,7 +6,7 @@ import (
// Register concrete types on wire codec
func RegisterWire(cdc *wire.Codec) {
cdc.RegisterConcrete(MsgUnrevoke{}, "cosmos-sdk/MsgUnrevoke", nil)
cdc.RegisterConcrete(MsgUnjail{}, "cosmos-sdk/MsgUnjail", nil)
}
var cdcEmpty = wire.NewCodec()

View File

@ -136,8 +136,8 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper)
if msg.Delegation.Denom != k.GetParams(ctx).BondDenom {
return ErrBadDenom(k.Codespace()).Result()
}
if validator.Revoked == true {
return ErrValidatorRevoked(k.Codespace()).Result()
if validator.Jailed {
return ErrValidatorJailed(k.Codespace()).Result()
}
_, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true)
if err != nil {

View File

@ -80,9 +80,9 @@ func TestValidatorByPowerIndex(t *testing.T) {
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got)
// slash and revoke the first validator
// slash and jail the first validator
keeper.Slash(ctx, keep.PKs[0], 0, initBond, sdk.NewDecWithPrec(5, 1))
keeper.Revoke(ctx, keep.PKs[0])
keeper.Jail(ctx, keep.PKs[0])
validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.Status) // ensure is unbonded
@ -387,7 +387,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
}
// unbond them all by revoking delegation
// unbond them all by removing delegation
for i, validatorAddr := range validatorAddrs {
_, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
@ -449,7 +449,7 @@ func TestMultipleMsgDelegate(t *testing.T) {
}
}
func TestRevokeValidator(t *testing.T) {
func TestJailValidator(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1]
_ = setInstantUnbondPeriod(keeper, ctx)
@ -476,9 +476,9 @@ func TestRevokeValidator(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.True(t, validator.Revoked, "%v", validator)
require.True(t, validator.Jailed, "%v", validator)
// test that this address cannot yet be bonded too because is revoked
// test that this address cannot yet be bonded too because is jailed
got = handleMsgDelegate(ctx, msgDelegate, keeper)
require.False(t, got.IsOK(), "expected error, got %v", got)

View File

@ -283,9 +283,9 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr
if delegation.Shares.IsZero() {
// if the delegation is the operator of the validator then
// trigger a revoke validator
if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Revoked == false {
validator.Revoked = true
// trigger a jail validator
if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Jailed == false {
validator.Jailed = true
}
k.RemoveDelegation(ctx, delegation)
} else {

View File

@ -73,11 +73,11 @@ func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte {
potentialPower := validator.Tokens
powerBytes := []byte(potentialPower.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first)
revokedBytes := make([]byte, 1)
if validator.Revoked {
revokedBytes[0] = byte(0x00)
jailedBytes := make([]byte, 1)
if validator.Jailed {
jailedBytes[0] = byte(0x00)
} else {
revokedBytes[0] = byte(0x01)
jailedBytes[0] = byte(0x01)
}
// heightBytes and counterBytes represent strings like powerBytes does
@ -88,7 +88,7 @@ func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte {
return append(append(append(append(
ValidatorsByPowerIndexKey,
revokedBytes...),
jailedBytes...),
powerBytes...),
heightBytes...),
counterBytes...)

View File

@ -115,31 +115,31 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
return
}
// revoke a validator
func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
k.setRevoked(ctx, pubkey, true)
// jail a validator
func (k Keeper) Jail(ctx sdk.Context, pubkey crypto.PubKey) {
k.setJailed(ctx, pubkey, true)
logger := ctx.Logger().With("module", "x/stake")
logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address()))
logger.Info(fmt.Sprintf("Validator %s jailed", pubkey.Address()))
// TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803
return
}
// unrevoke a validator
func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
k.setRevoked(ctx, pubkey, false)
// unjail a validator
func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) {
k.setJailed(ctx, pubkey, false)
logger := ctx.Logger().With("module", "x/stake")
logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address()))
logger.Info(fmt.Sprintf("Validator %s unjailed", pubkey.Address()))
// TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803
return
}
// set the revoked flag on a validator
func (k Keeper) setRevoked(ctx sdk.Context, pubkey crypto.PubKey, revoked bool) {
// set the jailed flag on a validator
func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, jailed bool) {
validator, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot set revoked to %v", pubkey, revoked))
panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, jailed))
}
validator.Revoked = revoked
validator.Jailed = jailed
k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it
return
}

View File

@ -34,7 +34,7 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) {
return ctx, keeper, params
}
// tests Revoke, Unrevoke
// tests Jail, Unjail
func TestRevocation(t *testing.T) {
// setup
ctx, keeper, _ := setupHelper(t, 10)
@ -44,19 +44,19 @@ func TestRevocation(t *testing.T) {
// initial state
val, found := keeper.GetValidator(ctx, addr)
require.True(t, found)
require.False(t, val.GetRevoked())
require.False(t, val.GetJailed())
// test revoke
keeper.Revoke(ctx, pk)
// test jail
keeper.Jail(ctx, pk)
val, found = keeper.GetValidator(ctx, addr)
require.True(t, found)
require.True(t, val.GetRevoked())
require.True(t, val.GetJailed())
// test unrevoke
keeper.Unrevoke(ctx, pk)
// test unjail
keeper.Unjail(ctx, pk)
val, found = keeper.GetValidator(ctx, addr)
require.True(t, found)
require.False(t, val.GetRevoked())
require.False(t, val.GetJailed())
}

View File

@ -205,7 +205,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
pool := k.GetPool(ctx)
oldValidator, oldFound := k.GetValidator(ctx, validator.Operator)
validator = k.updateForRevoking(ctx, oldFound, oldValidator, validator)
validator = k.updateForJailing(ctx, oldFound, oldValidator, validator)
powerIncreasing := k.getPowerIncreasing(ctx, oldFound, oldValidator, validator)
validator.BondHeight, validator.BondIntraTxCounter = k.bondIncrement(ctx, oldFound, oldValidator, validator)
valPower := k.updateValidatorPower(ctx, oldFound, oldValidator, validator, pool)
@ -216,7 +216,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
// perform the following:
// a) update Tendermint
// b) check if the cliff validator needs to be updated
case powerIncreasing && !validator.Revoked &&
case powerIncreasing && !validator.Jailed &&
(oldFound && oldValidator.Status == sdk.Bonded):
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator())
@ -293,8 +293,8 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
if currVal.Status != sdk.Bonded || currVal.Revoked {
panic(fmt.Sprintf("unexpected revoked or unbonded validator for address: %s\n", ownerAddr))
if currVal.Status != sdk.Bonded || currVal.Jailed {
panic(fmt.Sprintf("unexpected jailed or unbonded validator for address: %s\n", ownerAddr))
}
newCliffVal = currVal
@ -319,11 +319,11 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato
}
}
func (k Keeper) updateForRevoking(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator {
if newValidator.Revoked && oldFound && oldValidator.Status == sdk.Bonded {
func (k Keeper) updateForJailing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator {
if newValidator.Jailed && oldFound && oldValidator.Status == sdk.Bonded {
newValidator = k.unbondValidator(ctx, newValidator)
// need to also clear the cliff validator spot because the revoke has
// need to also clear the cliff validator spot because the jail has
// opened up a new spot which will be filled when
// updateValidatorsBonded is called
k.clearCliffValidator(ctx)
@ -417,7 +417,7 @@ func (k Keeper) UpdateBondedValidators(
}
// increment bondedValidatorsCount / get the validator to bond
if !validator.Revoked {
if !validator.Jailed {
if validator.Status != sdk.Bonded {
validatorToBond = validator
if newValidatorBonded {
@ -529,11 +529,11 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) {
validator = k.bondValidator(ctx, validator)
}
if validator.Revoked {
if validator.Jailed {
// we should no longer consider jailed validators as they are ranked
// lower than any non-jailed/bonded validators
if validator.Status == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded for address: %s\n", ownerAddr))
panic(fmt.Sprintf("jailed validator cannot be bonded for address: %s\n", ownerAddr))
}
break

View File

@ -94,7 +94,7 @@ var (
ErrNoValidatorFound = types.ErrNoValidatorFound
ErrValidatorOwnerExists = types.ErrValidatorOwnerExists
ErrValidatorPubKeyExists = types.ErrValidatorPubKeyExists
ErrValidatorRevoked = types.ErrValidatorRevoked
ErrValidatorJailed = types.ErrValidatorJailed
ErrBadRemoveValidator = types.ErrBadRemoveValidator
ErrDescriptionLength = types.ErrDescriptionLength
ErrCommissionNegative = types.ErrCommissionNegative

View File

@ -44,8 +44,8 @@ func ErrValidatorPubKeyExists(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist for this pubkey, must use new validator pubkey")
}
func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked")
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently jailed")
}
func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error {

View File

@ -22,7 +22,7 @@ import (
type Validator struct {
Operator sdk.AccAddress `json:"operator"` // sender of BondTx - UnbondTx returns here
PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
Jailed bool `json:"jailed"` // has the validator been jailed from bonded status?
Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded)
Tokens sdk.Dec `json:"tokens"` // delegated tokens (incl. self-delegation)
@ -47,7 +47,7 @@ func NewValidator(operator sdk.AccAddress, pubKey crypto.PubKey, description Des
return Validator{
Operator: operator,
PubKey: pubKey,
Revoked: false,
Jailed: false,
Status: sdk.Unbonded,
Tokens: sdk.ZeroDec(),
DelegatorShares: sdk.ZeroDec(),
@ -66,7 +66,7 @@ func NewValidator(operator sdk.AccAddress, pubKey crypto.PubKey, description Des
// what's kept in the store value
type validatorValue struct {
PubKey crypto.PubKey
Revoked bool
Jailed bool
Status sdk.BondStatus
Tokens sdk.Dec
DelegatorShares sdk.Dec
@ -85,7 +85,7 @@ type validatorValue struct {
func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte {
val := validatorValue{
PubKey: validator.PubKey,
Revoked: validator.Revoked,
Jailed: validator.Jailed,
Status: validator.Status,
Tokens: validator.Tokens,
DelegatorShares: validator.DelegatorShares,
@ -127,7 +127,7 @@ func UnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) (validator
return Validator{
Operator: operatorAddr,
PubKey: storeValue.PubKey,
Revoked: storeValue.Revoked,
Jailed: storeValue.Jailed,
Tokens: storeValue.Tokens,
Status: storeValue.Status,
DelegatorShares: storeValue.DelegatorShares,
@ -155,7 +155,7 @@ func (v Validator) HumanReadableString() (string, error) {
resp := "Validator \n"
resp += fmt.Sprintf("Operator: %s\n", v.Operator)
resp += fmt.Sprintf("Validator: %s\n", bechVal)
resp += fmt.Sprintf("Revoked: %v\n", v.Revoked)
resp += fmt.Sprintf("Jailed: %v\n", v.Jailed)
resp += fmt.Sprintf("Status: %s\n", sdk.BondStatusToString(v.Status))
resp += fmt.Sprintf("Tokens: %s\n", v.Tokens.String())
resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String())
@ -177,7 +177,7 @@ func (v Validator) HumanReadableString() (string, error) {
type BechValidator struct {
Operator sdk.AccAddress `json:"operator"` // in bech32
PubKey string `json:"pub_key"` // in bech32
Revoked bool `json:"revoked"` // has the validator been revoked from bonded status?
Jailed bool `json:"jailed"` // has the validator been jailed from bonded status?
Status sdk.BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded)
Tokens sdk.Dec `json:"tokens"` // delegated tokens (incl. self-delegation)
@ -207,7 +207,7 @@ func (v Validator) Bech32Validator() (BechValidator, error) {
return BechValidator{
Operator: v.Operator,
PubKey: bechValPubkey,
Revoked: v.Revoked,
Jailed: v.Jailed,
Status: v.Status,
Tokens: v.Tokens,
@ -429,7 +429,7 @@ func (v Validator) BondedTokens() sdk.Dec {
var _ sdk.Validator = Validator{}
// nolint - for sdk.Validator
func (v Validator) GetRevoked() bool { return v.Revoked }
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) GetOperator() sdk.AccAddress { return v.Operator }