Merge remote-tracking branch 'origin/develop' into rigel/fee-distribution

This commit is contained in:
rigelrozanski 2018-09-03 15:53:54 -04:00
commit d7794b483d
78 changed files with 2492 additions and 616 deletions

45
Gopkg.lock generated
View File

@ -34,7 +34,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:6aabc1566d6351115d561d038da82a4c19b46c3b6e17f4a0a2fa60260663dc79" digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8"
name = "github.com/btcsuite/btcd" name = "github.com/btcsuite/btcd"
packages = ["btcec"] packages = ["btcec"]
pruneopts = "UT" pruneopts = "UT"
@ -71,7 +71,7 @@
version = "v1.4.7" version = "v1.4.7"
[[projects]] [[projects]]
digest = "1:fa30c0652956e159cdb97dcb2ef8b8db63ed668c02a5c3a40961c8f0641252fe" digest = "1:fdf5169073fb0ad6dc12a70c249145e30f4058647bea25f0abd48b6d9f228a11"
name = "github.com/go-kit/kit" name = "github.com/go-kit/kit"
packages = [ packages = [
"log", "log",
@ -103,7 +103,7 @@
version = "v1.7.0" version = "v1.7.0"
[[projects]] [[projects]]
digest = "1:212285efb97b9ec2e20550d81f0446cb7897e57cbdfd7301b1363ab113d8be45" digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e"
name = "github.com/gogo/protobuf" name = "github.com/gogo/protobuf"
packages = [ packages = [
"gogoproto", "gogoproto",
@ -118,7 +118,7 @@
version = "v1.1.1" version = "v1.1.1"
[[projects]] [[projects]]
digest = "1:cb22af0ed7c72d495d8be1106233ee553898950f15fd3f5404406d44c2e86888" digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = [ packages = [
"proto", "proto",
@ -165,13 +165,12 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:ac64f01acc5eeea9dde40e326de6b6471e501392ec06524c3b51033aa50789bc" digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240"
name = "github.com/hashicorp/hcl" name = "github.com/hashicorp/hcl"
packages = [ packages = [
".", ".",
"hcl/ast", "hcl/ast",
"hcl/parser", "hcl/parser",
"hcl/printer",
"hcl/scanner", "hcl/scanner",
"hcl/strconv", "hcl/strconv",
"hcl/token", "hcl/token",
@ -263,7 +262,7 @@
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:98225904b7abff96c052b669b25788f18225a36673fba022fb93514bb9a2a64e" digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0"
name = "github.com/prometheus/client_golang" name = "github.com/prometheus/client_golang"
packages = [ packages = [
"prometheus", "prometheus",
@ -274,7 +273,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:0f37e09b3e92aaeda5991581311f8dbf38944b36a3edec61cc2d1991f527554a" digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4"
name = "github.com/prometheus/client_model" name = "github.com/prometheus/client_model"
packages = ["go"] packages = ["go"]
pruneopts = "UT" pruneopts = "UT"
@ -282,7 +281,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:dad2e5a2153ee7a6c9ab8fc13673a16ee4fb64434a7da980965a3741b0c981a3" digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5"
name = "github.com/prometheus/common" name = "github.com/prometheus/common"
packages = [ packages = [
"expfmt", "expfmt",
@ -294,7 +293,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:a37c98f4b7a66bb5c539c0539f0915a74ef1c8e0b3b6f45735289d94cae92bfd" digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290"
name = "github.com/prometheus/procfs" name = "github.com/prometheus/procfs"
packages = [ packages = [
".", ".",
@ -313,7 +312,7 @@
revision = "e2704e165165ec55d062f5919b4b29494e9fa790" revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
[[projects]] [[projects]]
digest = "1:37ace7f35375adec11634126944bdc45a673415e2fcc07382d03b75ec76ea94c" digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84"
name = "github.com/spf13/afero" name = "github.com/spf13/afero"
packages = [ packages = [
".", ".",
@ -332,7 +331,7 @@
version = "v1.2.0" version = "v1.2.0"
[[projects]] [[projects]]
digest = "1:627ab2f549a6a55c44f46fa24a4307f4d0da81bfc7934ed0473bf38b24051d26" digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e"
name = "github.com/spf13/cobra" name = "github.com/spf13/cobra"
packages = ["."] packages = ["."]
pruneopts = "UT" pruneopts = "UT"
@ -364,7 +363,7 @@
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:73697231b93fb74a73ebd8384b68b9a60c57ea6b13c56d2425414566a72c8e6d" digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6"
name = "github.com/stretchr/testify" name = "github.com/stretchr/testify"
packages = [ packages = [
"assert", "assert",
@ -376,7 +375,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:442d2ffa75ffae302ce8800bf4144696b92bef02917923ea132ce2d39efe7d65" digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5"
name = "github.com/syndtr/goleveldb" name = "github.com/syndtr/goleveldb"
packages = [ packages = [
"leveldb", "leveldb",
@ -397,7 +396,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:203b409c21115233a576f99e8f13d8e07ad82b25500491f7e1cca12588fb3232" digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722"
name = "github.com/tendermint/ed25519" name = "github.com/tendermint/ed25519"
packages = [ packages = [
".", ".",
@ -424,7 +423,7 @@
version = "v0.9.2" version = "v0.9.2"
[[projects]] [[projects]]
digest = "1:963f6c04345ce36f900c1d6367200eebc3cc2db6ee632ff865ea8dcf64b748a0" digest = "1:4f15e95fe3888cc75dd34f407d6394cbc7fd3ff24920851b92b295f6a8b556e6"
name = "github.com/tendermint/tendermint" name = "github.com/tendermint/tendermint"
packages = [ packages = [
"abci/client", "abci/client",
@ -491,7 +490,7 @@
version = "v0.23.1-rc0" version = "v0.23.1-rc0"
[[projects]] [[projects]]
digest = "1:ad879bb8c71020a3f92f0c61f414d93eae1d5dc2f37023b6abaa3cc84b00165e" digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e"
name = "github.com/tendermint/tmlibs" name = "github.com/tendermint/tmlibs"
packages = ["cli"] packages = ["cli"]
pruneopts = "UT" pruneopts = "UT"
@ -507,7 +506,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:2a3ce1f08dcae8bac666deb6e4c88b5d7170c510da38fd746231144cac351704" digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = [ packages = [
"blowfish", "blowfish",
@ -529,7 +528,7 @@
revision = "614d502a4dac94afa3a6ce146bd1736da82514c6" revision = "614d502a4dac94afa3a6ce146bd1736da82514c6"
[[projects]] [[projects]]
digest = "1:04dda8391c3e2397daf254ac68003f30141c069b228d06baec8324a5f81dc1e9" digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = [ packages = [
"context", "context",
@ -546,7 +545,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:c8baf78f0ac6eb27c645e264fe5e8a74d5a50db188ab41a7ff3b275c112e0735" digest = "1:86171d21d59449dcf7cee0b7d2da83dff989dab9b9b69bfe0a3d59c3c1ca6081"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = [ packages = [
"cpu", "cpu",
@ -556,7 +555,7 @@
revision = "11551d06cbcc94edc80a0facaccbda56473c19c1" revision = "11551d06cbcc94edc80a0facaccbda56473c19c1"
[[projects]] [[projects]]
digest = "1:7509ba4347d1f8de6ae9be8818b0cd1abc3deeffe28aeaf4be6d4b6b5178d9ca" digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text" name = "golang.org/x/text"
packages = [ packages = [
"collate", "collate",
@ -587,7 +586,7 @@
revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4" revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4"
[[projects]] [[projects]]
digest = "1:4515e3030c440845b046354fd5d57671238428b820deebce2e9dabb5cd3c51ac" digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
packages = [ packages = [
".", ".",
@ -664,6 +663,8 @@
"github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/common",
"github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/db",
"github.com/tendermint/tendermint/libs/log", "github.com/tendermint/tendermint/libs/log",
"github.com/tendermint/tendermint/lite",
"github.com/tendermint/tendermint/lite/proxy",
"github.com/tendermint/tendermint/node", "github.com/tendermint/tendermint/node",
"github.com/tendermint/tendermint/p2p", "github.com/tendermint/tendermint/p2p",
"github.com/tendermint/tendermint/privval", "github.com/tendermint/tendermint/privval",

View File

@ -163,6 +163,17 @@ test_sim_gaia_slow:
@echo "Running full Gaia simulation. This may take awhile!" @echo "Running full Gaia simulation. This may take awhile!"
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h
SIM_NUM_BLOCKS ?= 210
SIM_BLOCK_SIZE ?= 200
SIM_COMMIT ?= true
test_sim_gaia_benchmark:
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h
test_sim_gaia_profile:
@echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
test_cover: test_cover:
@bash tests/test_cover.sh @bash tests/test_cover.sh

View File

@ -23,6 +23,8 @@ BREAKING CHANGES
* [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] \#1901 Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface.
* [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period * [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period
* [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed" * [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed"
* [x/stake] [#1676] Revoked and jailed validators put into the unbonding state
* [x/stake] [#1877] Redelegations/unbonding-delegation from unbonding validator have reduced time
* [x/stake] \#2040 Validator operator type has now changed to `sdk.ValAddress` * [x/stake] \#2040 Validator operator type has now changed to `sdk.ValAddress`
* A new bech32 prefix has been introduced for Tendermint signing keys and * A new bech32 prefix has been introduced for Tendermint signing keys and
addresses, `cosmosconspub` and `cosmoscons` respectively. addresses, `cosmosconspub` and `cosmoscons` respectively.
@ -30,6 +32,7 @@ BREAKING CHANGES
* SDK * SDK
* [core] \#1807 Switch from use of rational to decimal * [core] \#1807 Switch from use of rational to decimal
* [types] \#1901 Validator interface's GetOwner() renamed to GetOperator() * [types] \#1901 Validator interface's GetOwner() renamed to GetOperator()
* [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period
* [types] \#2119 Parsed error messages and ABCI log errors to make them more human readable. * [types] \#2119 Parsed error messages and ABCI log errors to make them more human readable.
* [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) * [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153)
@ -40,6 +43,7 @@ FEATURES
* Gaia REST API (`gaiacli advanced rest-server`) * Gaia REST API (`gaiacli advanced rest-server`)
* [lcd] Endpoints to query staking pool and params * [lcd] Endpoints to query staking pool and params
* [lcd] \#2110 Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions
* Gaia CLI (`gaiacli`) * Gaia CLI (`gaiacli`)
* [cli] Cmds to query staking pool and params * [cli] Cmds to query staking pool and params
@ -48,6 +52,7 @@ FEATURES
provide desired Bech32 prefix encoding provide desired Bech32 prefix encoding
* [cli] \#2047 Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution. * [cli] \#2047 Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution.
* [cli] \#2047 The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0. * [cli] \#2047 The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0.
* [cli] \#2110 Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated.
* Gaia * Gaia
* [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address`
@ -55,6 +60,7 @@ FEATURES
* SDK * SDK
* [querier] added custom querier functionality, so ABCI query requests can be handled by keepers * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers
* [simulation] \#1924 allow operations to specify future operations * [simulation] \#1924 allow operations to specify future operations
* [simulation] \#1924 Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile"
* Tendermint * Tendermint
@ -73,7 +79,8 @@ IMPROVEMENTS
* Gaia * Gaia
* [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check. * [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check.
* [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046) * [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046)
* [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883).
* [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200)
* SDK * SDK
* [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present.
* [spec] Added simple piggy bank distribution spec * [spec] Added simple piggy bank distribution spec

View File

@ -1,14 +1,18 @@
package context package context
import ( import (
"io" "bytes"
"fmt"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
"io"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
tmlite "github.com/tendermint/tendermint/lite"
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
) )
@ -32,6 +36,8 @@ type CLIContext struct {
Async bool Async bool
JSON bool JSON bool
PrintResponse bool PrintResponse bool
Certifier tmlite.Certifier
DryRun bool
} }
// NewCLIContext returns a new initialized CLIContext with parameters from the // NewCLIContext returns a new initialized CLIContext with parameters from the
@ -57,9 +63,41 @@ func NewCLIContext() CLIContext {
Async: viper.GetBool(client.FlagAsync), Async: viper.GetBool(client.FlagAsync),
JSON: viper.GetBool(client.FlagJson), JSON: viper.GetBool(client.FlagJson),
PrintResponse: viper.GetBool(client.FlagPrintResponse), PrintResponse: viper.GetBool(client.FlagPrintResponse),
Certifier: createCertifier(),
DryRun: viper.GetBool(client.FlagDryRun),
} }
} }
func createCertifier() tmlite.Certifier {
trustNode := viper.GetBool(client.FlagTrustNode)
if trustNode {
return nil
}
chainID := viper.GetString(client.FlagChainID)
home := viper.GetString(cli.HomeFlag)
nodeURI := viper.GetString(client.FlagNode)
var errMsg bytes.Buffer
if chainID == "" {
errMsg.WriteString("chain-id ")
}
if home == "" {
errMsg.WriteString("home ")
}
if nodeURI == "" {
errMsg.WriteString("node ")
}
// errMsg is not empty
if errMsg.Len() != 0 {
panic(fmt.Errorf("can't create certifier for distrust mode, empty values from these options: %s", errMsg.String()))
}
certifier, err := tmliteProxy.GetCertifier(chainID, home, nodeURI)
if err != nil {
panic(err)
}
return certifier
}
// WithCodec returns a copy of the context with an updated codec. // WithCodec returns a copy of the context with an updated codec.
func (ctx CLIContext) WithCodec(cdc *wire.Codec) CLIContext { func (ctx CLIContext) WithCodec(cdc *wire.Codec) CLIContext {
ctx.Codec = cdc ctx.Codec = cdc
@ -117,3 +155,15 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext {
ctx.UseLedger = useLedger ctx.UseLedger = useLedger
return ctx return ctx
} }
// WithCertifier - return a copy of the context with an updated Certifier
func (ctx CLIContext) WithCertifier(certifier tmlite.Certifier) CLIContext {
ctx.Certifier = certifier
return ctx
}
// WithGasAdjustment returns a copy of the context with an updated GasAdjustment flag.
func (ctx CLIContext) WithGasAdjustment(adjustment float64) CLIContext {
ctx.GasAdjustment = adjustment
return ctx
}

View File

@ -10,9 +10,14 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/wire"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"strings"
) )
// GetNode returns an RPC client. If the context's client is not defined, an // GetNode returns an RPC client. If the context's client is not defined, an
@ -304,12 +309,77 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log)
} }
// Data from trusted node or subspace query doesn't need verification
if ctx.TrustNode || !isQueryStoreWithProof(path) {
return resp.Value, nil
}
err = ctx.verifyProof(path, resp)
if err != nil {
return nil, err
}
return resp.Value, nil return resp.Value, nil
} }
// verifyProof perform response proof verification
func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error {
if ctx.Certifier == nil {
return fmt.Errorf("missing valid certifier to verify data from untrusted node")
}
node, err := ctx.GetNode()
if err != nil {
return err
}
// AppHash for height H is in header H+1
commit, err := tmliteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier)
if err != nil {
return err
}
var multiStoreProof store.MultiStoreProof
cdc := wire.NewCodec()
err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof)
if err != nil {
return errors.Wrap(err, "failed to unmarshalBinary rangeProof")
}
// Verify the substore commit hash against trusted appHash
substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName,
multiStoreProof.StoreInfos, commit.Header.AppHash)
if err != nil {
return errors.Wrap(err, "failed in verifying the proof against appHash")
}
err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof)
if err != nil {
return errors.Wrap(err, "failed in the range proof verification")
}
return nil
}
// queryStore performs a query from a Tendermint node with the provided a store // queryStore performs a query from a Tendermint node with the provided a store
// name and path. // name and path.
func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) { func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) {
path := fmt.Sprintf("/store/%s/%s", storeName, endPath) path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
return ctx.query(path, key) return ctx.query(path, key)
} }
// isQueryStoreWithProof expects a format like /<queryType>/<storeName>/<subpath>
// queryType can be app or store
func isQueryStoreWithProof(path string) bool {
if !strings.HasPrefix(path, "/") {
return false
}
paths := strings.SplitN(path[1:], "/", 3)
if len(paths) != 3 {
return false
}
if store.RequireProof("/" + paths[2]) {
return true
}
return false
}

View File

@ -4,8 +4,11 @@ import "github.com/spf13/cobra"
// nolint // nolint
const ( const (
// DefaultGasAdjustment is applied to gas estimates to avoid tx
// execution failures due to state changes that might
// occur between the tx simulation and the actual run.
DefaultGasAdjustment = 1.0
DefaultGasLimit = 200000 DefaultGasLimit = 200000
DefaultGasAdjustment = 1.2
FlagUseLedger = "ledger" FlagUseLedger = "ledger"
FlagChainID = "chain-id" FlagChainID = "chain-id"
@ -23,6 +26,7 @@ const (
FlagAsync = "async" FlagAsync = "async"
FlagJson = "json" FlagJson = "json"
FlagPrintResponse = "print-response" FlagPrintResponse = "print-response"
FlagDryRun = "dry-run"
) )
// LineBreak can be included in a command list to provide a blank line // LineBreak can be included in a command list to provide a blank line
@ -54,10 +58,12 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain") c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
c.Flags().Int64(FlagGas, DefaultGasLimit, "gas limit to set per-transaction; set to 0 to calculate required gas automatically") c.Flags().Int64(FlagGas, DefaultGasLimit, "gas limit to set per-transaction; set to 0 to calculate required gas automatically")
c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation") c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously")
c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagJson, false, "return output in json format")
c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)")
c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for query responses")
c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it")
} }
return cmds return cmds
} }

View File

@ -2,6 +2,7 @@ package lcd
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
@ -265,11 +266,21 @@ func TestCoinSend(t *testing.T) {
require.Equal(t, int64(1), mycoins.Amount.Int64()) require.Equal(t, int64(1), mycoins.Amount.Int64())
// test failure with too little gas // test failure with too little gas
res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100) res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, 0, "")
require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) require.Equal(t, http.StatusInternalServerError, res.StatusCode, body)
// test success with just enough gas // test failure with wrong adjustment
res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 3000) res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0.1, "")
require.Equal(t, http.StatusInternalServerError, res.StatusCode, body)
// run simulation and test success with estimated gas
res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?simulate=true")
require.Equal(t, http.StatusOK, res.StatusCode, body)
var responseBody struct {
GasEstimate int64 `json:"gas_estimate"`
}
require.Nil(t, json.Unmarshal([]byte(body), &responseBody))
res, body, _ = doSendWithGas(t, port, seed, name, password, addr, responseBody.GasEstimate, 0, "")
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
} }
@ -720,7 +731,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
return acc return acc
} }
func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64) (res *http.Response, body string, receiveAddr sdk.AccAddress) { func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, gasAdjustment float64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) {
// create receive address // create receive address
kb := client.MockKeyBase() kb := client.MockKeyBase()
@ -744,22 +755,28 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc
"gas":"%v", "gas":"%v",
`, gas) `, gas)
} }
gasAdjustmentStr := ""
if gasAdjustment > 0 {
gasStr = fmt.Sprintf(`
"gas_adjustment":"%v",
`, gasAdjustment)
}
jsonStr := []byte(fmt.Sprintf(`{ jsonStr := []byte(fmt.Sprintf(`{
%v %v%v
"name":"%s", "name":"%s",
"password":"%s", "password":"%s",
"account_number":"%d", "account_number":"%d",
"sequence":"%d", "sequence":"%d",
"amount":[%s], "amount":[%s],
"chain_id":"%s" "chain_id":"%s"
}`, gasStr, name, password, accnum, sequence, coinbz, chainID)) }`, gasStr, gasAdjustmentStr, name, password, accnum, sequence, coinbz, chainID))
res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr) res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr)
return return
} }
func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) {
res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0) res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "")
require.Equal(t, http.StatusOK, res.StatusCode, body) require.Equal(t, http.StatusOK, res.StatusCode, body)
err := cdc.UnmarshalJSON([]byte(body), &resultTx) err := cdc.UnmarshalJSON([]byte(body), &resultTx)

View File

@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"os" "os"
client "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/keys"
rpc "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/rpc"
tx "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/wire"
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
@ -66,6 +66,7 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command {
cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to") cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to")
cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to") cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to")
cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections")
cmd.Flags().Bool(client.FlagTrustNode, false, "Whether trust connected full node")
return cmd return cmd
} }

View File

@ -190,6 +190,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
node, err := startTM(config, logger, genDoc, privVal, app) node, err := startTM(config, logger, genDoc, privVal, app)
require.NoError(t, err) require.NoError(t, err)
tests.WaitForNextHeightTM(tests.ExtractPortFromAddress(config.RPC.ListenAddress))
lcd, err := startLCD(logger, listenAddr, cdc) lcd, err := startLCD(logger, listenAddr, cdc)
require.NoError(t, err) require.NoError(t, err)

View File

@ -1,12 +1,45 @@
package utils package utils
import ( import (
"fmt"
"net/http" "net/http"
"strconv"
)
const (
queryArgDryRun = "simulate"
) )
// WriteErrorResponse prepares and writes a HTTP error // WriteErrorResponse prepares and writes a HTTP error
// given a status code and an error message. // given a status code and an error message.
func WriteErrorResponse(w *http.ResponseWriter, status int, msg string) { func WriteErrorResponse(w http.ResponseWriter, status int, msg string) {
(*w).WriteHeader(status) w.WriteHeader(status)
(*w).Write([]byte(msg)) w.Write([]byte(msg))
}
// WriteGasEstimateResponse prepares and writes an HTTP
// response for transactions simulations.
func WriteSimulationResponse(w http.ResponseWriter, gas int64) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas)))
}
// HasDryRunArg returns true if the request's URL query contains
// the dry run argument and its value is set to "true".
func HasDryRunArg(r *http.Request) bool {
return r.URL.Query().Get(queryArgDryRun) == "true"
}
// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default
// value if the string is empty. Write
func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) {
if len(s) == 0 {
return defaultIfEmpty, true
}
n, err := strconv.ParseFloat(s, 64)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return n, false
}
return n, true
} }

View File

@ -12,46 +12,26 @@ import (
"github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/common"
) )
// DefaultGasAdjustment is applied to gas estimates to avoid tx
// execution failures due to state changes that might
// occur between the tx simulation and the actual run.
const DefaultGasAdjustment = 1.2
// SendTx implements a auxiliary handler that facilitates sending a series of // SendTx implements a auxiliary handler that facilitates sending a series of
// messages in a signed transaction given a TxContext and a QueryContext. It // messages in a signed transaction given a TxContext and a QueryContext. It
// ensures that the account exists, has a proper number and sequence set. In // ensures that the account exists, has a proper number and sequence set. In
// addition, it builds and signs a transaction with the supplied messages. // addition, it builds and signs a transaction with the supplied messages.
// Finally, it broadcasts the signed transaction to a node. // Finally, it broadcasts the signed transaction to a node.
func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error { func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error {
if err := cliCtx.EnsureAccountExists(); err != nil { txCtx, err := prepareTxContext(txCtx, cliCtx)
return err
}
from, err := cliCtx.GetFromAddress()
if err != nil { if err != nil {
return err return err
} }
autogas := cliCtx.DryRun || (cliCtx.Gas == 0)
// TODO: (ref #1903) Allow for user supplied account number without if autogas {
// automatically doing a manual lookup. txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, msgs)
if txCtx.AccountNumber == 0 {
accNum, err := cliCtx.GetAccountNumber(from)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintf(os.Stdout, "estimated gas = %v\n", txCtx.Gas)
txCtx = txCtx.WithAccountNumber(accNum)
} }
if cliCtx.DryRun {
// TODO: (ref #1903) Allow for user supplied account sequence without return nil
// automatically doing a manual lookup.
if txCtx.Sequence == 0 {
accSeq, err := cliCtx.GetAccountSequence(from)
if err != nil {
return err
}
txCtx = txCtx.WithSequence(accSeq)
} }
passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName)
@ -59,13 +39,6 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg)
return err return err
} }
if cliCtx.Gas == 0 {
txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, passphrase, msgs)
if err != nil {
return err
}
}
// build and sign the transaction // build and sign the transaction
txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs) txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs)
if err != nil { if err != nil {
@ -75,24 +48,24 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg)
return cliCtx.EnsureBroadcastTx(txBytes) return cliCtx.EnsureBroadcastTx(txBytes)
} }
// EnrichCtxWithGas calculates the gas estimate that would be consumed by the // SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value.
// transaction and set the transaction's respective value accordingly. func SimulateMsgs(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, msgs []sdk.Msg, gas int64) (estimated, adjusted int64, err error) {
func EnrichCtxWithGas(txCtx authctx.TxContext, cliCtx context.CLIContext, name, passphrase string, msgs []sdk.Msg) (authctx.TxContext, error) { txBytes, err := txCtx.WithGas(gas).BuildWithPubKey(name, msgs)
txBytes, err := BuildAndSignTxWithZeroGas(txCtx, name, passphrase, msgs)
if err != nil { if err != nil {
return txCtx, err return
} }
estimate, adjusted, err := CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment) estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment)
if err != nil { return
return txCtx, err
}
fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted)
return txCtx.WithGas(adjusted), nil
} }
// BuildAndSignTxWithZeroGas builds transactions with GasWanted set to 0. // EnrichCtxWithGas calculates the gas estimate that would be consumed by the
func BuildAndSignTxWithZeroGas(txCtx authctx.TxContext, name, passphrase string, msgs []sdk.Msg) ([]byte, error) { // transaction and set the transaction's respective value accordingly.
return txCtx.WithGas(0).BuildAndSign(name, passphrase, msgs) func EnrichCtxWithGas(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authctx.TxContext, error) {
_, adjusted, err := SimulateMsgs(txCtx, cliCtx, name, msgs, 0)
if err != nil {
return txCtx, err
}
return txCtx.WithGas(adjusted), nil
} }
// CalculateGas simulates the execution of a transaction and returns // CalculateGas simulates the execution of a transaction and returns
@ -109,14 +82,10 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *
return return
} }
adjusted = adjustGasEstimate(estimate, adjustment) adjusted = adjustGasEstimate(estimate, adjustment)
fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted)
return return
} }
func adjustGasEstimate(estimate int64, adjustment float64) int64 { func adjustGasEstimate(estimate int64, adjustment float64) int64 {
if adjustment == 0 {
return int64(DefaultGasAdjustment * float64(estimate))
}
return int64(adjustment * float64(estimate)) return int64(adjustment * float64(estimate))
} }
@ -127,3 +96,35 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) {
} }
return simulationResult.GasUsed, nil return simulationResult.GasUsed, nil
} }
func prepareTxContext(txCtx authctx.TxContext, cliCtx context.CLIContext) (authctx.TxContext, error) {
if err := cliCtx.EnsureAccountExists(); err != nil {
return txCtx, err
}
from, err := cliCtx.GetFromAddress()
if err != nil {
return txCtx, err
}
// TODO: (ref #1903) Allow for user supplied account number without
// automatically doing a manual lookup.
if txCtx.AccountNumber == 0 {
accNum, err := cliCtx.GetAccountNumber(from)
if err != nil {
return txCtx, err
}
txCtx = txCtx.WithAccountNumber(accNum)
}
// TODO: (ref #1903) Allow for user supplied account sequence without
// automatically doing a manual lookup.
if txCtx.Sequence == 0 {
accSeq, err := cliCtx.GetAccountSequence(from)
if err != nil {
return txCtx, err
}
txCtx = txCtx.WithSequence(accSeq)
}
return txCtx, nil
}

View File

@ -93,9 +93,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams)
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace))
app.stakeKeeper = app.stakeKeeper.WithValidatorHooks(app.slashingKeeper.ValidatorHooks())
app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace))
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection)
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace))
// register message routes // register message routes
app.Router(). app.Router().

View File

@ -3,7 +3,9 @@ package app
import ( import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt"
"math/rand" "math/rand"
"os"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -15,6 +17,7 @@ import (
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
"github.com/cosmos/cosmos-sdk/x/gov"
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
"github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation"
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
@ -28,6 +31,7 @@ var (
blockSize int blockSize int
enabled bool enabled bool
verbose bool verbose bool
commit bool
) )
func init() { func init() {
@ -36,6 +40,7 @@ func init() {
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block") flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation") flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation")
flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output") flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output")
flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit")
} }
func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage {
@ -49,7 +54,7 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json
Coins: coins, Coins: coins,
}) })
} }
govGenesis := gov.DefaultGenesisState()
// Default genesis state // Default genesis state
stakeGenesis := stake.DefaultGenesisState() stakeGenesis := stake.DefaultGenesisState()
var validators []stake.Validator var validators []stake.Validator
@ -73,6 +78,7 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json
genesis := GenesisState{ genesis := GenesisState{
Accounts: genesisAccounts, Accounts: genesisAccounts,
StakeData: stakeGenesis, StakeData: stakeGenesis,
GovData: govGenesis,
} }
// Marshal genesis // Marshal genesis
@ -112,6 +118,39 @@ func invariants(app *GaiaApp) []simulation.Invariant {
} }
} }
// Profile with:
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out
func BenchmarkFullGaiaSimulation(b *testing.B) {
// Setup Gaia application
var logger log.Logger
logger = log.NewNopLogger()
var db dbm.DB
dir := os.TempDir()
db, _ = dbm.NewGoLevelDB("Simulation", dir)
defer func() {
db.Close()
os.RemoveAll(dir)
}()
app := NewGaiaApp(logger, db, nil)
// Run randomized simulation
// TODO parameterize numbers, save for a later PR
simulation.SimulateFromSeed(
b, app.BaseApp, appStateFn, seed,
testAndRunTxs(app),
[]simulation.RandSetup{},
invariants(app), // these shouldn't get ran
numBlocks,
blockSize,
commit,
)
if commit {
fmt.Println("GoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
}
}
func TestFullGaiaSimulation(t *testing.T) { func TestFullGaiaSimulation(t *testing.T) {
if !enabled { if !enabled {
t.Skip("Skipping Gaia simulation") t.Skip("Skipping Gaia simulation")
@ -136,9 +175,11 @@ func TestFullGaiaSimulation(t *testing.T) {
invariants(app), invariants(app),
numBlocks, numBlocks,
blockSize, blockSize,
false, commit,
) )
if commit {
fmt.Println("Database Size", db.Stats()["database.size"])
}
} }
// TODO: Make another test for the fuzzer itself, which just has noOp txs // TODO: Make another test for the fuzzer itself, which just has noOp txs

View File

@ -58,6 +58,13 @@ func TestGaiaCLISend(t *testing.T) {
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
// Test --dry-run
success := executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass)
require.True(t, success)
// Check state didn't change
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags))
require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64())
// test autosequencing // test autosequencing
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass)
tests.WaitForNextNBlocksTM(2, port) tests.WaitForNextNBlocksTM(2, port)
@ -148,6 +155,10 @@ func TestGaiaCLICreateValidator(t *testing.T) {
initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1))
// Test --dry-run
success := executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass)
require.True(t, success)
executeWrite(t, cvStr, app.DefaultKeyPass) executeWrite(t, cvStr, app.DefaultKeyPass)
tests.WaitForNextNBlocksTM(2, port) tests.WaitForNextNBlocksTM(2, port)
@ -164,7 +175,7 @@ func TestGaiaCLICreateValidator(t *testing.T) {
unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr)) unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr))
unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") unbondStr += fmt.Sprintf(" --shares-amount=%v", "1")
success := executeWrite(t, unbondStr, app.DefaultKeyPass) success = executeWrite(t, unbondStr, app.DefaultKeyPass)
require.True(t, success) require.True(t, success)
tests.WaitForNextNBlocksTM(2, port) tests.WaitForNextNBlocksTM(2, port)
@ -211,6 +222,10 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
spStr += fmt.Sprintf(" --title=%s", "Test") spStr += fmt.Sprintf(" --title=%s", "Test")
spStr += fmt.Sprintf(" --description=%s", "test") spStr += fmt.Sprintf(" --description=%s", "test")
// Test --dry-run
success := executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass)
require.True(t, success)
executeWrite(t, spStr, app.DefaultKeyPass) executeWrite(t, spStr, app.DefaultKeyPass)
tests.WaitForNextNBlocksTM(2, port) tests.WaitForNextNBlocksTM(2, port)

View File

@ -95,6 +95,8 @@ When you query an account balance with zero tokens, you will get this error: `No
### Send Tokens ### Send Tokens
The following command could be used to send coins from one account to another:
```bash ```bash
gaiacli send \ gaiacli send \
--amount=10faucetToken \ --amount=10faucetToken \
@ -110,7 +112,7 @@ The `--amount` flag accepts the format `--amount=<value|coin_name>`.
::: tip Note ::: tip Note
You may want to cap the maximum gas that can be consumed by the transaction via the `--gas` flag. You may want to cap the maximum gas that can be consumed by the transaction via the `--gas` flag.
If set to 0, the gas limit will be automatically estimated. If set to 0, the gas limit will be automatically estimated.
Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.2. Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.0.
::: :::
Now, view the updated balances of the origin and destination accounts: Now, view the updated balances of the origin and destination accounts:
@ -126,6 +128,17 @@ You can also check your balance at a given block by using the `--block` flag:
gaiacli account <account_cosmos> --block=<block_height> gaiacli account <account_cosmos> --block=<block_height>
``` ```
You can simulate a transaction without actually broadcasting it by appending the `--dry-run` flag to the command line:
```bash
gaiacli send \
--amount=10faucetToken \
--chain-id=<chain_id> \
--name=<key_name> \
--to=<destination_cosmosaccaddr> \
--dry-run
```
### Staking ### Staking
#### Set up a Validator #### Set up a Validator

View File

@ -135,3 +135,8 @@ func (vs *ValidatorSet) Jail(ctx sdk.Context, pubkey crypto.PubKey) {
func (vs *ValidatorSet) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { func (vs *ValidatorSet) Unjail(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented") panic("not implemented")
} }
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.ValAddress) sdk.Delegation {
panic("not implemented")
}

View File

@ -88,17 +88,17 @@ func TestMsgQuiz(t *testing.T) {
require.Equal(t, acc1, res1) require.Equal(t, acc1, res1)
// Set the trend, submit a really cool quiz and check for reward // Set the trend, submit a really cool quiz and check for reward
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, true, priv1)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, priv1) // result without reward mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, false, priv1) // result without reward
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, priv1) // reset the trend mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, true, priv1) // reset the trend
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, false, priv1) // the same answer will nolonger do!
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, priv1) // earlier answer now relevant again mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, true, priv1) // earlier answer now relevant again
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}})
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, false, priv1) // expect to fail to set the trend to something which is not cool
} }

View File

@ -74,13 +74,13 @@ func TestMsgMine(t *testing.T) {
// Mine and check for reward // Mine and check for reward
mineMsg1 := GenerateMsgMine(addr1, 1, 2) mineMsg1 := GenerateMsgMine(addr1, 1, 2)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}})
// Mine again and check for reward // Mine again and check for reward
mineMsg2 := GenerateMsgMine(addr1, 2, 3) mineMsg2 := GenerateMsgMine(addr1, 2, 3)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}})
// Mine again - should be invalid // Mine again - should be invalid
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, false, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}})
} }

View File

@ -1,16 +1,16 @@
package server package server
import ( import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/tendermint/tendermint/libs/log"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"os"
"bytes" "bytes"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/stretchr/testify/require"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/libs/log"
"io" "io"
"github.com/cosmos/cosmos-sdk/server/mock" "os"
) "testing"
)
func TestEmptyState(t *testing.T) { func TestEmptyState(t *testing.T) {
defer setupViper(t)() defer setupViper(t)()

View File

@ -129,7 +129,7 @@ func AppGenStateEmpty(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMess
// Return a validator, not much else // Return a validator, not much else
func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
validator = tmtypes.GenesisValidator{ validator = tmtypes.GenesisValidator{
PubKey: pk, PubKey: pk,

View File

@ -7,11 +7,11 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval" pvm "github.com/tendermint/tendermint/privval"
"github.com/cosmos/cosmos-sdk/client"
) )
// ShowNodeIDCmd - ported from Tendermint, dump node ID to stdout // ShowNodeIDCmd - ported from Tendermint, dump node ID to stdout

91
store/multistoreproof.go Normal file
View File

@ -0,0 +1,91 @@
package store
import (
"bytes"
"github.com/pkg/errors"
"github.com/tendermint/iavl"
cmn "github.com/tendermint/tendermint/libs/common"
)
// MultiStoreProof defines a collection of store proofs in a multi-store
type MultiStoreProof struct {
StoreInfos []storeInfo
StoreName string
RangeProof iavl.RangeProof
}
// buildMultiStoreProof build MultiStoreProof based on iavl proof and storeInfos
func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) []byte {
var rangeProof iavl.RangeProof
cdc.MustUnmarshalBinary(iavlProof, &rangeProof)
msp := MultiStoreProof{
StoreInfos: storeInfos,
StoreName: storeName,
RangeProof: rangeProof,
}
proof := cdc.MustMarshalBinary(msp)
return proof
}
// VerifyMultiStoreCommitInfo verify multiStoreCommitInfo against appHash
func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHash []byte) ([]byte, error) {
var substoreCommitHash []byte
var height int64
for _, storeInfo := range storeInfos {
if storeInfo.Name == storeName {
substoreCommitHash = storeInfo.Core.CommitID.Hash
height = storeInfo.Core.CommitID.Version
}
}
if len(substoreCommitHash) == 0 {
return nil, cmn.NewError("failed to get substore root commit hash by store name")
}
ci := commitInfo{
Version: height,
StoreInfos: storeInfos,
}
if !bytes.Equal(appHash, ci.Hash()) {
return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash")
}
return substoreCommitHash, nil
}
// VerifyRangeProof verify iavl RangeProof
func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error {
// verify the proof to ensure data integrity.
err := rangeProof.Verify(substoreCommitHash)
if err != nil {
return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash")
}
if len(value) != 0 {
// verify existence proof
err = rangeProof.VerifyItem(key, value)
if err != nil {
return errors.Wrap(err, "failed in existence verification")
}
} else {
// verify absence proof
err = rangeProof.VerifyAbsence(key)
if err != nil {
return errors.Wrap(err, "failed in absence verification")
}
}
return nil
}
// RequireProof return whether proof is require for the subpath
func RequireProof(subpath string) bool {
// Currently, only when query subpath is "/store" or "/key", will proof be included in response.
// If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212
if subpath == "/store" || subpath == "/key" {
return true
}
return false
}

View File

@ -0,0 +1,120 @@
package store
import (
"encoding/hex"
"github.com/stretchr/testify/assert"
"github.com/tendermint/iavl"
cmn "github.com/tendermint/tendermint/libs/common"
"testing"
)
func TestVerifyMultiStoreCommitInfo(t *testing.T) {
appHash, _ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84")
substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828")
storeName := "acc"
var storeInfos []storeInfo
gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78")
storeInfos = append(storeInfos, storeInfo{
Name: "gov",
Core: storeCore{
CommitID: CommitID{
Version: 689,
Hash: gocRootHash,
},
},
})
storeInfos = append(storeInfos, storeInfo{
Name: "main",
Core: storeCore{
CommitID: CommitID{
Version: 689,
Hash: nil,
},
},
})
accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828")
storeInfos = append(storeInfos, storeInfo{
Name: "acc",
Core: storeCore{
CommitID: CommitID{
Version: 689,
Hash: accRootHash,
},
},
})
storeInfos = append(storeInfos, storeInfo{
Name: "ibc",
Core: storeCore{
CommitID: CommitID{
Version: 689,
Hash: nil,
},
},
})
stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4")
storeInfos = append(storeInfos, storeInfo{
Name: "stake",
Core: storeCore{
CommitID: CommitID{
Version: 689,
Hash: stakeRootHash,
},
},
})
slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e")
storeInfos = append(storeInfos, storeInfo{
Name: "slashing",
Core: storeCore{
CommitID: CommitID{
Version: 689,
Hash: slashingRootHash,
},
},
})
commitHash, err := VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash)
assert.Nil(t, err)
assert.Equal(t, commitHash, substoreRootHash)
appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3")
_, err = VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash)
assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo")
}
func TestVerifyRangeProof(t *testing.T) {
tree := iavl.NewTree(nil, 0)
rand := cmn.NewRand()
rand.Seed(0) // for determinism
for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} {
key := []byte{ikey}
tree.Set(key, []byte(rand.Str(8)))
}
root := tree.Hash()
key := []byte{0x32}
val, proof, err := tree.GetWithProof(key)
assert.Nil(t, err)
assert.NotEmpty(t, val)
assert.NotEmpty(t, proof)
err = VerifyRangeProof(key, val, root, proof)
assert.Nil(t, err)
key = []byte{0x40}
val, proof, err = tree.GetWithProof(key)
assert.Nil(t, err)
assert.Empty(t, val)
assert.NotEmpty(t, proof)
err = VerifyRangeProof(key, val, root, proof)
assert.Nil(t, err)
}

View File

@ -291,6 +291,18 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery {
// trim the path and make the query // trim the path and make the query
req.Path = subpath req.Path = subpath
res := queryable.Query(req) res := queryable.Query(req)
if !req.Prove || !RequireProof(subpath) {
return res
}
commitInfo, errMsg := getCommitInfo(rs.db, res.Height)
if errMsg != nil {
return sdk.ErrInternal(errMsg.Error()).QueryResult()
}
res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos)
return res return res
} }

View File

@ -10,6 +10,7 @@ import (
tmclient "github.com/tendermint/tendermint/rpc/client" tmclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
rpcclient "github.com/tendermint/tendermint/rpc/lib/client" rpcclient "github.com/tendermint/tendermint/rpc/lib/client"
"strings"
) )
// Wait for the next tendermint block from the Tendermint RPC // Wait for the next tendermint block from the Tendermint RPC
@ -185,6 +186,17 @@ func WaitForRPC(laddr string) {
} }
} }
// ExtractPortFromAddress extract port from listenAddress
// The listenAddress must be some strings like tcp://0.0.0.0:12345
func ExtractPortFromAddress(listenAddress string) string {
stringList := strings.Split(listenAddress, ":")
length := len(stringList)
if length != 3 {
panic(fmt.Errorf("expected listen address: tcp://0.0.0.0:12345, got %s", listenAddress))
}
return stringList[2]
}
var cdc = amino.NewCodec() var cdc = amino.NewCodec()
func init() { func init() {

View File

@ -105,7 +105,7 @@ func (aa *AccAddress) UnmarshalJSON(data []byte) error {
var s string var s string
err := json.Unmarshal(data, &s) err := json.Unmarshal(data, &s)
if err != nil { if err != nil {
return nil return err
} }
aa2, err := AccAddressFromBech32(s) aa2, err := AccAddressFromBech32(s)

View File

@ -415,6 +415,14 @@ func MinDec(d1, d2 Dec) Dec {
return d2 return d2
} }
// maximum decimal between two
func MaxDec(d1, d2 Dec) Dec {
if d1.LT(d2) {
return d2
}
return d1
}
// intended to be used with require/assert: require.True(DecEq(...)) // intended to be used with require/assert: require.True(DecEq(...))
func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) { func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got

View File

@ -75,6 +75,10 @@ type ValidatorSet interface {
Slash(Context, crypto.PubKey, int64, int64, Dec) Slash(Context, crypto.PubKey, int64, int64, Dec)
Jail(Context, crypto.PubKey) // jail a validator Jail(Context, crypto.PubKey) // jail a validator
Unjail(Context, crypto.PubKey) // unjail a validator Unjail(Context, crypto.PubKey) // unjail a validator
// Delegation allows for getting a particular delegation for a given validator
// and delegator outside the scope of the staking module.
Delegation(Context, AccAddress, ValAddress) Delegation
} }
//_______________________________________________________________________________ //_______________________________________________________________________________
@ -95,3 +99,13 @@ type DelegationSet interface {
IterateDelegations(ctx Context, delegator AccAddress, IterateDelegations(ctx Context, delegator AccAddress,
fn func(index int64, delegation Delegation) (stop bool)) fn func(index int64, delegation Delegation) (stop bool))
} }
// validator event hooks
// These can be utilized to communicate between a staking keeper
// and another keeper which must take particular actions when
// validators are bonded and unbonded. The second keeper must implement
// this interface, which then the staking keeper can call.
type ValidatorHooks interface {
OnValidatorBonded(ctx Context, address ConsAddress) // Must be called when a validator is bonded
OnValidatorBeginUnbonding(ctx Context, address ConsAddress) // Must be called when a validator begins unbonding
}

View File

@ -65,7 +65,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
return newCtx, err.Result(), true return newCtx, err.Result(), true
} }
sigs := stdTx.GetSignatures() sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice.
signerAddrs := stdTx.GetSigners() signerAddrs := stdTx.GetSigners()
msgs := tx.GetMsgs() msgs := tx.GetMsgs()
@ -88,10 +88,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
// check signature, return account with incremented nonce // check signature, return account with incremented nonce
signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo())
signerAcc, res := processSig( signerAcc, res := processSig(newCtx, am, signerAddr, sig, signBytes, simulate)
newCtx, am,
signerAddr, sig, signBytes,
)
if !res.IsOK() { if !res.IsOK() {
return newCtx, res, true return newCtx, res, true
} }
@ -149,24 +146,24 @@ func validateBasic(tx StdTx) (err sdk.Error) {
// if the account doesn't have a pubkey, set it. // if the account doesn't have a pubkey, set it.
func processSig( func processSig(
ctx sdk.Context, am AccountMapper, ctx sdk.Context, am AccountMapper,
addr sdk.AccAddress, sig StdSignature, signBytes []byte) ( addr sdk.AccAddress, sig StdSignature, signBytes []byte, simulate bool) (
acc Account, res sdk.Result) { acc Account, res sdk.Result) {
// Get the account. // Get the account.
acc = am.GetAccount(ctx, addr) acc = am.GetAccount(ctx, addr)
if acc == nil { if acc == nil {
return nil, sdk.ErrUnknownAddress(addr.String()).Result() return nil, sdk.ErrUnknownAddress(addr.String()).Result()
} }
// Check account number.
accnum := acc.GetAccountNumber() accnum := acc.GetAccountNumber()
seq := acc.GetSequence()
// Check account number.
if accnum != sig.AccountNumber { if accnum != sig.AccountNumber {
return nil, sdk.ErrInvalidSequence( return nil, sdk.ErrInvalidSequence(
fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result()
} }
// Check and increment sequence number. // Check sequence number.
seq := acc.GetSequence()
if seq != sig.Sequence { if seq != sig.Sequence {
return nil, sdk.ErrInvalidSequence( return nil, sdk.ErrInvalidSequence(
fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result()
@ -176,31 +173,48 @@ func processSig(
// Handle w/ #870 // Handle w/ #870
panic(err) panic(err)
} }
pubKey, res := processPubKey(acc, sig, simulate)
if !res.IsOK() {
return nil, res
}
err = acc.SetPubKey(pubKey)
if err != nil {
return nil, sdk.ErrInternal("setting PubKey on signer's account").Result()
}
consumeSignatureVerificationGas(ctx.GasMeter(), pubKey)
if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}
return
}
func processPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) {
// If pubkey is not known for account, // If pubkey is not known for account,
// set it from the StdSignature. // set it from the StdSignature.
pubKey := acc.GetPubKey() pubKey := acc.GetPubKey()
if simulate {
// In simulate mode the transaction comes with no signatures, thus
// if the account's pubkey is nil, both signature verification
// and gasKVStore.Set() shall consume the largest amount, i.e.
// it takes more gas to verifiy secp256k1 keys than ed25519 ones.
if pubKey == nil {
return secp256k1.GenPrivKey().PubKey(), sdk.Result{}
}
return pubKey, sdk.Result{}
}
if pubKey == nil { if pubKey == nil {
pubKey = sig.PubKey pubKey = sig.PubKey
if pubKey == nil { if pubKey == nil {
return nil, sdk.ErrInvalidPubKey("PubKey not found").Result() return nil, sdk.ErrInvalidPubKey("PubKey not found").Result()
} }
if !bytes.Equal(pubKey.Address(), addr) { if !bytes.Equal(pubKey.Address(), acc.GetAddress()) {
return nil, sdk.ErrInvalidPubKey( return nil, sdk.ErrInvalidPubKey(
fmt.Sprintf("PubKey does not match Signer address %v", addr)).Result() fmt.Sprintf("PubKey does not match Signer address %v", acc.GetAddress())).Result()
}
err = acc.SetPubKey(pubKey)
if err != nil {
return nil, sdk.ErrInternal("setting PubKey on signer's account").Result()
} }
} }
return pubKey, sdk.Result{}
// Check sig.
consumeSignatureVerificationGas(ctx.GasMeter(), pubKey)
if !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}
return
} }
func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) {

View File

@ -4,14 +4,14 @@ import (
"fmt" "fmt"
"testing" "testing"
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
) )
func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
@ -567,3 +567,63 @@ func TestAnteHandlerSetPubKey(t *testing.T) {
acc2 = mapper.GetAccount(ctx, addr2) acc2 = mapper.GetAccount(ctx, addr2)
require.Nil(t, acc2.GetPubKey()) require.Nil(t, acc2.GetPubKey())
} }
func TestProcessPubKey(t *testing.T) {
ms, capKey, _ := setupMultiStore()
cdc := wire.NewCodec()
RegisterBaseAccount(cdc)
mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger())
// keys
_, addr1 := privAndAddr()
priv2, _ := privAndAddr()
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
type args struct {
acc Account
sig StdSignature
simulate bool
}
tests := []struct {
name string
args args
wantErr bool
}{
{"no sigs, simulate off", args{acc1, StdSignature{}, false}, true},
{"no sigs, simulate on", args{acc1, StdSignature{}, true}, false},
{"pubkey doesn't match addr, simulate off", args{acc1, StdSignature{PubKey: priv2.PubKey()}, false}, true},
{"pubkey doesn't match addr, simulate on", args{acc1, StdSignature{PubKey: priv2.PubKey()}, true}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := processPubKey(tt.args.acc, tt.args.sig, tt.args.simulate)
require.Equal(t, tt.wantErr, !err.IsOK())
})
}
}
func TestConsumeSignatureVerificationGas(t *testing.T) {
type args struct {
meter sdk.GasMeter
pubkey crypto.PubKey
}
tests := []struct {
name string
args args
gasConsumed int64
wantPanic bool
}{
{"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), ed25519.GenPrivKey().PubKey()}, ed25519VerifyCost, false},
{"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), secp256k1.GenPrivKey().PubKey()}, secp256k1VerifyCost, false},
{"unknown key", args{sdk.NewInfiniteGasMeter(), nil}, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantPanic {
require.Panics(t, func() { consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) })
} else {
consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey)
require.Equal(t, tt.args.meter.GasConsumed(), tt.gasConsumed)
}
})
}
}

View File

@ -148,3 +148,32 @@ func (ctx TxContext) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]by
return ctx.Sign(name, passphrase, msg) return ctx.Sign(name, passphrase, msg)
} }
// BuildWithPubKey builds a single message to be signed from a TxContext given a set of
// messages and attach the public key associated to the given name.
// It returns an error if a fee is supplied but cannot be parsed or the key cannot be
// retrieved.
func (ctx TxContext) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, error) {
msg, err := ctx.Build(msgs)
if err != nil {
return nil, err
}
keybase, err := keys.GetKeyBase()
if err != nil {
return nil, err
}
info, err := keybase.Get(name)
if err != nil {
return nil, err
}
sigs := []auth.StdSignature{{
AccountNumber: msg.AccountNumber,
Sequence: msg.Sequence,
PubKey: info.GetPubKey(),
}}
return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo))
}

View File

@ -33,13 +33,13 @@ func QueryAccountRequestHandlerFn(
addr, err := sdk.AccAddressFromBech32(bech32addr) addr, err := sdk.AccAddressFromBech32(bech32addr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error()))
return return
} }
@ -52,14 +52,14 @@ func QueryAccountRequestHandlerFn(
// decode the value // decode the value
account, err := decoder(res) account, err := decoder(res)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))
return return
} }
// print out whole account // print out whole account
output, err := cdc.MarshalJSON(account) output, err := cdc.MarshalJSON(account)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("couldn't marshall query result. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't marshall query result. Error: %s", err.Error()))
return return
} }

View File

@ -21,6 +21,7 @@ type (
} }
appTestCase struct { appTestCase struct {
expSimPass bool
expPass bool expPass bool
msgs []sdk.Msg msgs []sdk.Msg
accNums []int64 accNums []int64
@ -107,27 +108,29 @@ func TestMsgSendWithAccounts(t *testing.T) {
testCases := []appTestCase{ testCases := []appTestCase{
{ {
msgs: []sdk.Msg{sendMsg1}, msgs: []sdk.Msg{sendMsg1},
accNums: []int64{0}, accNums: []int64{0},
accSeqs: []int64{0}, accSeqs: []int64{0},
expPass: true, expSimPass: true,
privKeys: []crypto.PrivKey{priv1}, expPass: true,
privKeys: []crypto.PrivKey{priv1},
expectedBalances: []expectedBalance{ expectedBalances: []expectedBalance{
{addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}}, {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}},
{addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}},
}, },
}, },
{ {
msgs: []sdk.Msg{sendMsg1, sendMsg2}, msgs: []sdk.Msg{sendMsg1, sendMsg2},
accNums: []int64{0}, accNums: []int64{0},
accSeqs: []int64{0}, accSeqs: []int64{0},
expPass: false, expSimPass: false,
privKeys: []crypto.PrivKey{priv1}, expPass: false,
privKeys: []crypto.PrivKey{priv1},
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...)
for _, eb := range tc.expectedBalances { for _, eb := range tc.expectedBalances {
mock.CheckBalance(t, mapp, eb.addr, eb.coins) mock.CheckBalance(t, mapp, eb.addr, eb.coins)
@ -144,7 +147,7 @@ func TestMsgSendWithAccounts(t *testing.T) {
require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log)
// resigning the tx with the bumped sequence should work // resigning the tx with the bumped sequence should work
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, true, priv1)
} }
func TestMsgSendMultipleOut(t *testing.T) { func TestMsgSendMultipleOut(t *testing.T) {
@ -163,11 +166,12 @@ func TestMsgSendMultipleOut(t *testing.T) {
testCases := []appTestCase{ testCases := []appTestCase{
{ {
msgs: []sdk.Msg{sendMsg2}, msgs: []sdk.Msg{sendMsg2},
accNums: []int64{0}, accNums: []int64{0},
accSeqs: []int64{0}, accSeqs: []int64{0},
expPass: true, expSimPass: true,
privKeys: []crypto.PrivKey{priv1}, expPass: true,
privKeys: []crypto.PrivKey{priv1},
expectedBalances: []expectedBalance{ expectedBalances: []expectedBalance{
{addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}},
{addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 47)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 47)}},
@ -177,7 +181,7 @@ func TestMsgSendMultipleOut(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...)
for _, eb := range tc.expectedBalances { for _, eb := range tc.expectedBalances {
mock.CheckBalance(t, mapp, eb.addr, eb.coins) mock.CheckBalance(t, mapp, eb.addr, eb.coins)
@ -205,11 +209,12 @@ func TestSengMsgMultipleInOut(t *testing.T) {
testCases := []appTestCase{ testCases := []appTestCase{
{ {
msgs: []sdk.Msg{sendMsg3}, msgs: []sdk.Msg{sendMsg3},
accNums: []int64{0, 2}, accNums: []int64{0, 2},
accSeqs: []int64{0, 0}, accSeqs: []int64{0, 0},
expPass: true, expSimPass: true,
privKeys: []crypto.PrivKey{priv1, priv4}, expPass: true,
privKeys: []crypto.PrivKey{priv1, priv4},
expectedBalances: []expectedBalance{ expectedBalances: []expectedBalance{
{addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}},
{addr4, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr4, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}},
@ -220,7 +225,7 @@ func TestSengMsgMultipleInOut(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...)
for _, eb := range tc.expectedBalances { for _, eb := range tc.expectedBalances {
mock.CheckBalance(t, mapp, eb.addr, eb.coins) mock.CheckBalance(t, mapp, eb.addr, eb.coins)
@ -240,22 +245,24 @@ func TestMsgSendDependent(t *testing.T) {
testCases := []appTestCase{ testCases := []appTestCase{
{ {
msgs: []sdk.Msg{sendMsg1}, msgs: []sdk.Msg{sendMsg1},
accNums: []int64{0}, accNums: []int64{0},
accSeqs: []int64{0}, accSeqs: []int64{0},
expPass: true, expSimPass: true,
privKeys: []crypto.PrivKey{priv1}, expPass: true,
privKeys: []crypto.PrivKey{priv1},
expectedBalances: []expectedBalance{ expectedBalances: []expectedBalance{
{addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}},
{addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}},
}, },
}, },
{ {
msgs: []sdk.Msg{sendMsg4}, msgs: []sdk.Msg{sendMsg4},
accNums: []int64{1}, accNums: []int64{1},
accSeqs: []int64{0}, accSeqs: []int64{0},
expPass: true, expSimPass: true,
privKeys: []crypto.PrivKey{priv2}, expPass: true,
privKeys: []crypto.PrivKey{priv2},
expectedBalances: []expectedBalance{ expectedBalances: []expectedBalance{
{addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}}, {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}},
}, },
@ -263,7 +270,7 @@ func TestMsgSendDependent(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...)
for _, eb := range tc.expectedBalances { for _, eb := range tc.expectedBalances {
mock.CheckBalance(t, mapp, eb.addr, eb.coins) mock.CheckBalance(t, mapp, eb.addr, eb.coins)

View File

@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
cliclient "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
@ -31,6 +32,7 @@ type sendBody struct {
AccountNumber int64 `json:"account_number"` AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"` Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"` Gas int64 `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
} }
var msgCdc = wire.NewCodec() var msgCdc = wire.NewCodec()
@ -40,6 +42,7 @@ func init() {
} }
// SendRequestHandlerFn - http request handler to send coins to a address // SendRequestHandlerFn - http request handler to send coins to a address
// nolint: gocyclo
func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// collect data // collect data
@ -48,32 +51,32 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo
to, err := sdk.AccAddressFromBech32(bech32addr) to, err := sdk.AccAddressFromBech32(bech32addr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
var m sendBody var m sendBody
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
err = msgCdc.UnmarshalJSON(body, &m) err = msgCdc.UnmarshalJSON(body, &m)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
info, err := kb.Get(m.LocalAccountName) info, err := kb.Get(m.LocalAccountName)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
// build message // build message
msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount) msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount)
if err != nil { // XXX rechecking same error ? if err != nil { // XXX rechecking same error ?
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -85,10 +88,20 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo
Sequence: m.Sequence, Sequence: m.Sequence,
} }
if m.Gas == 0 { adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, cliclient.DefaultGasAdjustment)
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) if !ok {
return
}
cliCtx = cliCtx.WithGasAdjustment(adjustment)
if utils.HasDryRunArg(r) || m.Gas == 0 {
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if utils.HasDryRunArg(r) {
utils.WriteSimulationResponse(w, txCtx.Gas)
return return
} }
txCtx = newCtx txCtx = newCtx
@ -96,19 +109,19 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo
txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
res, err := cliCtx.BroadcastTx(txBytes) res, err := cliCtx.BroadcastTx(txBytes)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
output, err := wire.MarshalJSONIndent(cdc, res) output, err := wire.MarshalJSONIndent(cdc, res)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -21,7 +21,7 @@ import (
// SimulateSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both // SimulateSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both
// accounts already exist. // accounts already exist.
func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation { func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) {
fromKey := simulation.RandomKey(r, keys) fromKey := simulation.RandomKey(r, keys)
fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) fromAddr := sdk.AccAddress(fromKey.PubKey().Address())
toKey := simulation.RandomKey(r, keys) toKey := simulation.RandomKey(r, keys)
@ -58,7 +58,7 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation
Inputs: []bank.Input{bank.NewInput(fromAddr, coins)}, Inputs: []bank.Input{bank.NewInput(fromAddr, coins)},
Outputs: []bank.Output{bank.NewOutput(toAddr, coins)}, Outputs: []bank.Output{bank.NewOutput(toAddr, coins)},
} }
sendAndVerifyMsgSend(t, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey}) sendAndVerifyMsgSend(tb, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey})
event("bank/sendAndVerifyMsgSend/ok") event("bank/sendAndVerifyMsgSend/ok")
return action, nil, nil return action, nil, nil
@ -66,7 +66,7 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation
} }
// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs // Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs
func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { func sendAndVerifyMsgSend(tb testing.TB, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) {
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
AccountNumbers := make([]int64, len(msg.Inputs)) AccountNumbers := make([]int64, len(msg.Inputs))
@ -91,12 +91,12 @@ func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.Accoun
// TODO: Do this in a more 'canonical' way // TODO: Do this in a more 'canonical' way
fmt.Println(res) fmt.Println(res)
fmt.Println(log) fmt.Println(log)
t.FailNow() tb.FailNow()
} }
for i := 0; i < len(msg.Inputs); i++ { for i := 0; i < len(msg.Inputs); i++ {
terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins()
require.Equal(t, require.Equal(tb,
initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins),
terminalInputCoins, terminalInputCoins,
fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log),
@ -104,11 +104,9 @@ func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.Accoun
} }
for i := 0; i < len(msg.Outputs); i++ { for i := 0; i < len(msg.Outputs); i++ {
terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins()
require.Equal(t, if !terminalOutputCoins.IsEqual(initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins)) {
initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins), tb.Fatalf("Output #%d had an incorrect amount of coins\n%s", i, log)
terminalOutputCoins, }
fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log),
)
} }
} }

View File

@ -77,11 +77,11 @@ func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.Hand
msg := gov.NewMsgSubmitProposal(req.Title, req.Description, req.ProposalType, req.Proposer, req.InitialDeposit) msg := gov.NewMsgSubmitProposal(req.Title, req.Description, req.ProposalType, req.Proposer, req.InitialDeposit)
err = msg.ValidateBasic() err = msg.ValidateBasic()
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc)
} }
} }
@ -92,7 +92,7 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu
if len(strProposalID) == 0 { if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified") err := errors.New("proposalId required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -114,11 +114,11 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu
msg := gov.NewMsgDeposit(req.Depositer, proposalID, req.Amount) msg := gov.NewMsgDeposit(req.Depositer, proposalID, req.Amount)
err = msg.ValidateBasic() err = msg.ValidateBasic()
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc)
} }
} }
@ -129,7 +129,7 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc
if len(strProposalID) == 0 { if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified") err := errors.New("proposalId required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -151,11 +151,11 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc
msg := gov.NewMsgVote(req.Voter, proposalID, req.Option) msg := gov.NewMsgVote(req.Voter, proposalID, req.Option)
err = msg.ValidateBasic() err = msg.ValidateBasic()
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc)
} }
} }
@ -166,7 +166,7 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if len(strProposalID) == 0 { if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified") err := errors.New("proposalId required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -183,13 +183,13 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
bz, err := cdc.MarshalJSON(params) bz, err := cdc.MarshalJSON(params)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) res, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -205,7 +205,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if len(strProposalID) == 0 { if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified") err := errors.New("proposalId required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -216,14 +216,14 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if len(bechDepositerAddr) == 0 { if len(bechDepositerAddr) == 0 {
err := errors.New("depositer address required but not specified") err := errors.New("depositer address required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr)
if err != nil { if err != nil {
err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer)
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -236,13 +236,13 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
bz, err := cdc.MarshalJSON(params) bz, err := cdc.MarshalJSON(params)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
res, err := cliCtx.QueryWithData("custom/gov/deposit", bz) res, err := cliCtx.QueryWithData("custom/gov/deposit", bz)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -252,11 +252,11 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID})) res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID}))
if err != nil || len(res) == 0 { if err != nil || len(res) == 0 {
err := errors.Errorf("proposalID [%d] does not exist", proposalID) err := errors.Errorf("proposalID [%d] does not exist", proposalID)
utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return return
} }
err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID) err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID)
utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return return
} }
@ -272,7 +272,7 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if len(strProposalID) == 0 { if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified") err := errors.New("proposalId required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -283,14 +283,14 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if len(bechVoterAddr) == 0 { if len(bechVoterAddr) == 0 {
err := errors.New("voter address required but not specified") err := errors.New("voter address required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
if err != nil { if err != nil {
err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter)
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -302,13 +302,13 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
} }
bz, err := cdc.MarshalJSON(params) bz, err := cdc.MarshalJSON(params)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
res, err := cliCtx.QueryWithData("custom/gov/vote", bz) res, err := cliCtx.QueryWithData("custom/gov/vote", bz)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -317,17 +317,17 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if vote.Empty() { if vote.Empty() {
bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID}) bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) res, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil || len(res) == 0 { if err != nil || len(res) == 0 {
err := errors.Errorf("proposalID [%d] does not exist", proposalID) err := errors.Errorf("proposalID [%d] does not exist", proposalID)
utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return return
} }
err = errors.Errorf("voter [%s] did not deposit on proposalID [%d]", bechVoterAddr, proposalID) err = errors.Errorf("voter [%s] did not deposit on proposalID [%d]", bechVoterAddr, proposalID)
utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return return
} }
w.Write(res) w.Write(res)
@ -343,7 +343,7 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
if len(strProposalID) == 0 { if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified") err := errors.New("proposalId required but not specified")
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -359,13 +359,13 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
} }
bz, err := cdc.MarshalJSON(params) bz, err := cdc.MarshalJSON(params)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
res, err := cliCtx.QueryWithData("custom/gov/votes", bz) res, err := cliCtx.QueryWithData("custom/gov/votes", bz)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -388,7 +388,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
if err != nil { if err != nil {
err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter)
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
params.Voter = voterAddr params.Voter = voterAddr
@ -398,7 +398,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr)
if err != nil { if err != nil {
err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer)
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
params.Depositer = depositerAddr params.Depositer = depositerAddr
@ -408,7 +408,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus) proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus)
if err != nil { if err != nil {
err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus) err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus)
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
params.ProposalStatus = proposalStatus params.ProposalStatus = proposalStatus
@ -423,7 +423,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
bz, err := cdc.MarshalJSON(params) bz, err := cdc.MarshalJSON(params)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
@ -431,7 +431,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) res, err := cliCtx.QueryWithData("custom/gov/proposals", bz)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/client/utils"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -20,17 +21,18 @@ type baseReq struct {
AccountNumber int64 `json:"account_number"` AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"` Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"` Gas int64 `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
} }
func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req interface{}) error { func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req interface{}) error {
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return err return err
} }
err = cdc.UnmarshalJSON(body, req) err = cdc.UnmarshalJSON(body, req)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return err return err
} }
return nil return nil
@ -38,27 +40,27 @@ func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req inter
func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { func (req baseReq) baseReqValidate(w http.ResponseWriter) bool {
if len(req.Name) == 0 { if len(req.Name) == 0 {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Name required but not specified") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Name required but not specified")
return false return false
} }
if len(req.Password) == 0 { if len(req.Password) == 0 {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Password required but not specified") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Password required but not specified")
return false return false
} }
if len(req.ChainID) == 0 { if len(req.ChainID) == 0 {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "ChainID required but not specified") utils.WriteErrorResponse(w, http.StatusUnauthorized, "ChainID required but not specified")
return false return false
} }
if req.AccountNumber < 0 { if req.AccountNumber < 0 {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Account Number required but not specified") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Account Number required but not specified")
return false return false
} }
if req.Sequence < 0 { if req.Sequence < 0 {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Sequence required but not specified") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Sequence required but not specified")
return false return false
} }
return true return true
@ -66,7 +68,7 @@ func (req baseReq) baseReqValidate(w http.ResponseWriter) bool {
// TODO: Build this function out into a more generic base-request // TODO: Build this function out into a more generic base-request
// (probably should live in client/lcd). // (probably should live in client/lcd).
func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) {
var err error var err error
txCtx := authctx.TxContext{ txCtx := authctx.TxContext{
Codec: cdc, Codec: cdc,
@ -76,29 +78,39 @@ func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq base
Gas: baseReq.Gas, Gas: baseReq.Gas,
} }
if baseReq.Gas == 0 { adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment)
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if !ok {
return
}
cliCtx = cliCtx.WithGasAdjustment(adjustment)
if utils.HasDryRunArg(r) || baseReq.Gas == 0 {
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if utils.HasDryRunArg(r) {
utils.WriteSimulationResponse(w, txCtx.Gas)
return return
} }
txCtx = newCtx txCtx = newCtx
} }
txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
res, err := cliCtx.BroadcastTx(txBytes) res, err := cliCtx.BroadcastTx(txBytes)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
output, err := wire.MarshalJSONIndent(cdc, res) output, err := wire.MarshalJSONIndent(cdc, res)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -5,8 +5,6 @@ import (
"math/rand" "math/rand"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
@ -23,24 +21,15 @@ const (
// SimulateMsgSubmitProposal simulates a msg Submit Proposal // SimulateMsgSubmitProposal simulates a msg Submit Proposal
// Note: Currently doesn't ensure that the proposal txt is in JSON form // Note: Currently doesn't ensure that the proposal txt is in JSON form
func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { handler := gov.NewHandler(k)
key := simulation.RandomKey(r, keys) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) {
addr := sdk.AccAddress(key.PubKey().Address()) msg := simulationCreateMsgSubmitProposal(tb, r, keys, log)
deposit := randomDeposit(r)
msg := gov.NewMsgSubmitProposal(
simulation.RandStringOfLength(r, 5),
simulation.RandStringOfLength(r, 5),
gov.ProposalTypeText,
addr,
deposit,
)
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := gov.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
// Update pool to keep invariants // Update pool to keep invariants
pool := sk.GetPool(ctx) pool := sk.GetPool(ctx)
pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(deposit.AmountOf(denom))) pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom)))
sk.SetPool(ctx, pool) sk.SetPool(ctx, pool)
write() write()
} }
@ -50,9 +39,26 @@ func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operati
} }
} }
func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, keys []crypto.PrivKey, log string) gov.MsgSubmitProposal {
key := simulation.RandomKey(r, keys)
addr := sdk.AccAddress(key.PubKey().Address())
deposit := randomDeposit(r)
msg := gov.NewMsgSubmitProposal(
simulation.RandStringOfLength(r, 5),
simulation.RandStringOfLength(r, 5),
gov.ProposalTypeText,
addr,
deposit,
)
if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
return msg
}
// SimulateMsgDeposit // SimulateMsgDeposit
func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
key := simulation.RandomKey(r, keys) key := simulation.RandomKey(r, keys)
addr := sdk.AccAddress(key.PubKey().Address()) addr := sdk.AccAddress(key.PubKey().Address())
proposalID, ok := randomProposalID(r, k, ctx) proposalID, ok := randomProposalID(r, k, ctx)
@ -61,7 +67,9 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation {
} }
deposit := randomDeposit(r) deposit := randomDeposit(r)
msg := gov.NewMsgDeposit(addr, proposalID, deposit) msg := gov.NewMsgDeposit(addr, proposalID, deposit)
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := gov.NewHandler(k)(ctx, msg) result := gov.NewHandler(k)(ctx, msg)
if result.IsOK() { if result.IsOK() {
@ -79,7 +87,7 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation {
// SimulateMsgVote // SimulateMsgVote
func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
key := simulation.RandomKey(r, keys) key := simulation.RandomKey(r, keys)
addr := sdk.AccAddress(key.PubKey().Address()) addr := sdk.AccAddress(key.PubKey().Address())
proposalID, ok := randomProposalID(r, k, ctx) proposalID, ok := randomProposalID(r, k, ctx)
@ -88,7 +96,9 @@ func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation {
} }
option := randomVotingOption(r) option := randomVotingOption(r)
msg := gov.NewMsgVote(addr, proposalID, option) msg := gov.NewMsgVote(addr, proposalID, option)
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := gov.NewHandler(k)(ctx, msg) result := gov.NewHandler(k)(ctx, msg)
if result.IsOK() { if result.IsOK() {

View File

@ -70,10 +70,10 @@ func TestIBCMsgs(t *testing.T) {
Sequence: 0, Sequence: 0,
} }
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, emptyCoins) mock.CheckBalance(t, mapp, addr1, emptyCoins)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, false, priv1)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, coins) mock.CheckBalance(t, mapp, addr1, coins)
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, false, priv1)
} }

View File

@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
@ -29,10 +30,12 @@ type transferBody struct {
AccountNumber int64 `json:"account_number"` AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"` Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"` Gas int64 `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
} }
// TransferRequestHandler - http request handler to transfer coins to a address // TransferRequestHandler - http request handler to transfer coins to a address
// on a different chain via IBC // on a different chain via IBC
// nolint: gocyclo
func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
@ -41,26 +44,26 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C
to, err := sdk.AccAddressFromBech32(bech32addr) to, err := sdk.AccAddressFromBech32(bech32addr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
var m transferBody var m transferBody
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
err = cdc.UnmarshalJSON(body, &m) err = cdc.UnmarshalJSON(body, &m)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
info, err := kb.Get(m.LocalAccountName) info, err := kb.Get(m.LocalAccountName)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
@ -76,10 +79,20 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C
Gas: m.Gas, Gas: m.Gas,
} }
if m.Gas == 0 { adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment)
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) if !ok {
return
}
cliCtx = cliCtx.WithGasAdjustment(adjustment)
if utils.HasDryRunArg(r) || m.Gas == 0 {
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if utils.HasDryRunArg(r) {
utils.WriteSimulationResponse(w, txCtx.Gas)
return return
} }
txCtx = newCtx txCtx = newCtx
@ -87,19 +100,19 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C
txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
res, err := cliCtx.BroadcastTx(txBytes) res, err := cliCtx.BroadcastTx(txBytes)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
output, err := cdc.MarshalJSON(res) output, err := cdc.MarshalJSON(res)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -61,14 +61,14 @@ func TestCheckAndDeliverGenTx(t *testing.T) {
SignCheckDeliver( SignCheckDeliver(
t, mApp.BaseApp, []sdk.Msg{msg}, t, mApp.BaseApp, []sdk.Msg{msg},
[]int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()},
true, privKeys[0], true, true, privKeys[0],
) )
// Signing a tx with the wrong privKey should result in an auth error // Signing a tx with the wrong privKey should result in an auth error
res := SignCheckDeliver( res := SignCheckDeliver(
t, mApp.BaseApp, []sdk.Msg{msg}, t, mApp.BaseApp, []sdk.Msg{msg},
[]int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1}, []int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1},
false, privKeys[1], true, false, privKeys[1],
) )
require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log)
@ -76,7 +76,7 @@ func TestCheckAndDeliverGenTx(t *testing.T) {
SignCheckDeliver( SignCheckDeliver(
t, mApp.BaseApp, []sdk.Msg{msg}, t, mApp.BaseApp, []sdk.Msg{msg},
[]int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1}, []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1},
true, privKeys[0], true, true, privKeys[0],
) )
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"math" "math"
"math/rand" "math/rand"
"os"
"sort" "sort"
"testing" "testing"
"time" "time"
@ -16,7 +17,6 @@ import (
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock"
"github.com/stretchr/testify/require"
) )
// Simulate tests application by sending random messages. // Simulate tests application by sending random messages.
@ -28,34 +28,10 @@ func Simulate(
SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit)
} }
// SimulateFromSeed tests an application by running the provided func initChain(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress, setups []RandSetup, app *baseapp.BaseApp,
// operations, testing the provided invariants, but using the provided seed. appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage) (validators map[string]mockValidator) {
func SimulateFromSeed(
t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup,
invariants []Invariant, numBlocks int, blockSize int, commit bool,
) {
log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed))
r := rand.New(rand.NewSource(seed))
unixTime := r.Int63n(int64(math.Pow(2, 40)))
// Set the timestamp for simulation
timestamp := time.Unix(unixTime, 0)
log = fmt.Sprintf("%s\nStarting the simulation from time %v, unixtime %v", log, timestamp.UTC().Format(time.UnixDate), timestamp.Unix())
fmt.Printf("%s\n", log)
timeDiff := maxTimePerBlock - minTimePerBlock
keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys)
// Setup event stats
events := make(map[string]uint)
event := func(what string) {
log += "\nevent - " + what
events[what]++
}
res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, keys, accs)}) res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, keys, accs)})
validators := make(map[string]mockValidator) validators = make(map[string]mockValidator)
for _, validator := range res.Validators { for _, validator := range res.Validators {
validators[string(validator.Address)] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} validators[string(validator.Address)] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)}
} }
@ -64,83 +40,161 @@ func SimulateFromSeed(
setups[i](r, keys) setups[i](r, keys)
} }
return
}
func randTimestamp(r *rand.Rand) time.Time {
unixTime := r.Int63n(int64(math.Pow(2, 40)))
return time.Unix(unixTime, 0)
}
// SimulateFromSeed tests an application by running the provided
// operations, testing the provided invariants, but using the provided seed.
func SimulateFromSeed(
tb testing.TB, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup,
invariants []Invariant, numBlocks int, blockSize int, commit bool,
) {
testingMode, t, b := getTestingMode(tb)
log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed))
r := rand.New(rand.NewSource(seed))
timestamp := randTimestamp(r)
log = updateLog(testingMode, log, "Starting the simulation from time %v, unixtime %v", timestamp.UTC().Format(time.UnixDate), timestamp.Unix())
fmt.Printf("%s\n", log)
timeDiff := maxTimePerBlock - minTimePerBlock
keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys)
// Setup event stats
events := make(map[string]uint)
event := func(what string) {
log = updateLog(testingMode, log, "event - %s", what)
events[what]++
}
validators := initChain(r, keys, accs, setups, app, appStateFn)
header := abci.Header{Height: 0, Time: timestamp} header := abci.Header{Height: 0, Time: timestamp}
opCount := 0 opCount := 0
request := abci.RequestBeginBlock{Header: header}
var pastTimes []time.Time var pastTimes []time.Time
var pastSigningValidators [][]abci.SigningValidator
request := RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log)
// These are operations which have been queued by previous operations // These are operations which have been queued by previous operations
operationQueue := make(map[int][]Operation) operationQueue := make(map[int][]Operation)
for i := 0; i < numBlocks; i++ { if !testingMode {
b.ResetTimer()
}
blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks)
for i := 0; i < numBlocks; i++ {
// Log the header time for future lookup // Log the header time for future lookup
pastTimes = append(pastTimes, header.Time) pastTimes = append(pastTimes, header.Time)
pastSigningValidators = append(pastSigningValidators, request.LastCommitInfo.Validators)
// Run the BeginBlock handler // Run the BeginBlock handler
app.BeginBlock(request) app.BeginBlock(request)
log = updateLog(testingMode, log, "BeginBlock")
log += "\nBeginBlock" if testingMode {
// Make sure invariants hold at beginning of block
// Make sure invariants hold at beginning of block AssertAllInvariants(t, app, invariants, log)
AssertAllInvariants(t, app, invariants, log) }
ctx := app.NewContext(false, header) ctx := app.NewContext(false, header)
thisBlockSize := getBlockSize(r, blockSize)
var thisBlockSize int
load := r.Float64()
switch {
case load < 0.33:
thisBlockSize = 0
case load < 0.66:
thisBlockSize = r.Intn(blockSize * 2)
default:
thisBlockSize = r.Intn(blockSize * 4)
}
// Run queued operations. Ignores blocksize if blocksize is too small // Run queued operations. Ignores blocksize if blocksize is too small
log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), t, r, app, ctx, keys, log, event) log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, log, event)
opCount += numQueuedOpsRan opCount += numQueuedOpsRan
thisBlockSize -= numQueuedOpsRan thisBlockSize -= numQueuedOpsRan
for j := 0; j < thisBlockSize; j++ { log, operations := blockSimulator(thisBlockSize, r, app, ctx, keys, log, header)
logUpdate, futureOps, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log, event) opCount += operations
log += "\n" + logUpdate
queueOperations(operationQueue, futureOps)
require.Nil(t, err, log)
if onOperation {
AssertAllInvariants(t, app, invariants, log)
}
if opCount%200 == 0 {
fmt.Printf("\rSimulating... block %d/%d, operation %d.", header.Height, numBlocks, opCount)
}
opCount++
}
res := app.EndBlock(abci.RequestEndBlock{}) res := app.EndBlock(abci.RequestEndBlock{})
header.Height++ header.Height++
header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second)
log = updateLog(testingMode, log, "EndBlock")
log += "\nEndBlock" if testingMode {
// Make sure invariants hold at end of block
// Make sure invariants hold at end of block AssertAllInvariants(t, app, invariants, log)
AssertAllInvariants(t, app, invariants, log) }
if commit { if commit {
app.Commit() app.Commit()
} }
// Generate a random RequestBeginBlock with the current validator set for the next block // Generate a random RequestBeginBlock with the current validator set for the next block
request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, event, header, log) request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log)
// Update the validator set // Update the validator set
validators = updateValidators(t, r, validators, res.ValidatorUpdates, event) validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event)
} }
fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds): %v\n", header.Height, header.Time) fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount)
DisplayEvents(events) DisplayEvents(events)
} }
// Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize
// memory overhead
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []Operation, operationQueue map[int][]Operation, totalNumBlocks int) func(
blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) {
return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
keys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) {
for j := 0; j < blocksize; j++ {
logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event)
log = updateLog(testingMode, log, logUpdate)
if err != nil {
tb.Fatalf("error on operation %d within block %d, %v, log %s", header.Height, opCount, err, log)
}
queueOperations(operationQueue, futureOps)
if testingMode {
if onOperation {
AssertAllInvariants(t, app, invariants, log)
}
if opCount%50 == 0 {
fmt.Printf("\rSimulating... block %d/%d, operation %d/%d.", header.Height, totalNumBlocks, opCount, blocksize)
}
}
opCount++
}
return log, opCount
}
}
func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) {
testingMode = false
if _t, ok := tb.(*testing.T); ok {
t = _t
testingMode = true
} else {
b = tb.(*testing.B)
}
return
}
func updateLog(testingMode bool, log string, update string, args ...interface{}) (updatedLog string) {
if testingMode {
update = fmt.Sprintf(update, args...)
return fmt.Sprintf("%s\n%s", log, update)
}
return ""
}
func getBlockSize(r *rand.Rand, blockSize int) int {
load := r.Float64()
switch {
case load < 0.33:
return 0
case load < 0.66:
return r.Intn(blockSize * 2)
default:
return r.Intn(blockSize * 4)
}
}
// adds all future operations into the operation queue. // adds all future operations into the operation queue.
func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) { func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) {
if futureOperations == nil { if futureOperations == nil {
@ -155,7 +209,7 @@ func queueOperations(queuedOperations map[int][]Operation, futureOperations []Fu
} }
} }
func runQueuedOperations(queueOperations map[int][]Operation, height int, t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, func runQueuedOperations(queueOperations map[int][]Operation, height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
privKeys []crypto.PrivKey, log string, event func(string)) (updatedLog string, numOpsRan int) { privKeys []crypto.PrivKey, log string, event func(string)) (updatedLog string, numOpsRan int) {
updatedLog = log updatedLog = log
if queuedOps, ok := queueOperations[height]; ok { if queuedOps, ok := queueOperations[height]; ok {
@ -164,9 +218,12 @@ func runQueuedOperations(queueOperations map[int][]Operation, height int, t *tes
// For now, queued operations cannot queue more operations. // For now, queued operations cannot queue more operations.
// If a need arises for us to support queued messages to queue more messages, this can // If a need arises for us to support queued messages to queue more messages, this can
// be changed. // be changed.
logUpdate, _, err := queuedOps[i](t, r, app, ctx, privKeys, updatedLog, event) logUpdate, _, err := queuedOps[i](tb, r, app, ctx, privKeys, updatedLog, event)
updatedLog += "\n" + logUpdate updatedLog = fmt.Sprintf("%s\n%s", updatedLog, logUpdate)
require.Nil(t, err, updatedLog) if err != nil {
fmt.Fprint(os.Stderr, updatedLog)
tb.FailNow()
}
} }
delete(queueOperations, height) delete(queueOperations, height)
return updatedLog, numOps return updatedLog, numOps
@ -186,14 +243,13 @@ func getKeys(validators map[string]mockValidator) []string {
} }
// RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction
func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64,
pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { pastTimes []time.Time, pastSigningValidators [][]abci.SigningValidator, event func(string), header abci.Header, log string) abci.RequestBeginBlock {
if len(validators) == 0 { if len(validators) == 0 {
return abci.RequestBeginBlock{Header: header} return abci.RequestBeginBlock{Header: header}
} }
signingValidators := make([]abci.SigningValidator, len(validators)) signingValidators := make([]abci.SigningValidator, len(validators))
i := 0 i := 0
for _, key := range getKeys(validators) { for _, key := range getKeys(validators) {
mVal := validators[key] mVal := validators[key]
mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState) mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState)
@ -219,27 +275,33 @@ func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]m
} }
i++ i++
} }
// TODO: Determine capacity before allocation
evidence := make([]abci.Evidence, 0) evidence := make([]abci.Evidence, 0)
for r.Float64() < evidenceFraction { // Anything but the first block
height := header.Height if len(pastTimes) > 0 {
time := header.Time for r.Float64() < evidenceFraction {
if r.Float64() < pastEvidenceFraction { height := header.Height
height = int64(r.Intn(int(header.Height))) time := header.Time
time = pastTimes[height] vals := signingValidators
if r.Float64() < pastEvidenceFraction {
height = int64(r.Intn(int(header.Height)))
time = pastTimes[height]
vals = pastSigningValidators[height]
}
validator := vals[r.Intn(len(vals))].Validator
var totalVotingPower int64
for _, val := range vals {
totalVotingPower += val.Validator.Power
}
evidence = append(evidence, abci.Evidence{
Type: tmtypes.ABCIEvidenceTypeDuplicateVote,
Validator: validator,
Height: height,
Time: time,
TotalVotingPower: totalVotingPower,
})
event("beginblock/evidence")
} }
validator := signingValidators[r.Intn(len(signingValidators))].Validator
var currentTotalVotingPower int64
for _, mVal := range validators {
currentTotalVotingPower += mVal.val.Power
}
evidence = append(evidence, abci.Evidence{
Type: tmtypes.ABCIEvidenceTypeDuplicateVote,
Validator: validator,
Height: height,
Time: time,
TotalVotingPower: currentTotalVotingPower,
})
event("beginblock/evidence")
} }
return abci.RequestBeginBlock{ return abci.RequestBeginBlock{
Header: header, Header: header,
@ -258,11 +320,19 @@ func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant,
} }
// updateValidators mimicks Tendermint's update logic // updateValidators mimicks Tendermint's update logic
func updateValidators(t *testing.T, r *rand.Rand, current map[string]mockValidator, updates []abci.Validator, event func(string)) map[string]mockValidator { func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.Validator, event func(string)) map[string]mockValidator {
for _, update := range updates { for _, update := range updates {
switch { switch {
case update.Power == 0: case update.Power == 0:
require.NotNil(t, current[string(update.PubKey.Data)], "tried to delete a nonexistent validator") // // TEMPORARY DEBUG CODE TO PROVE THAT THE OLD METHOD WAS BROKEN
// // (i.e. didn't catch in the event of problem)
// if val, ok := tb.(*testing.T); ok {
// require.NotNil(val, current[string(update.PubKey.Data)])
// }
// // CORRECT CHECK
// if _, ok := current[string(update.PubKey.Data)]; !ok {
// tb.Fatalf("tried to delete a nonexistent validator")
// }
event("endblock/validatorupdates/kicked") event("endblock/validatorupdates/kicked")
delete(current, string(update.PubKey.Data)) delete(current, string(update.PubKey.Data))
default: default:

View File

@ -23,7 +23,7 @@ type (
// Operations can optionally provide a list of "FutureOperations" to run later // Operations can optionally provide a list of "FutureOperations" to run later
// These will be ran at the beginning of the corresponding block. // These will be ran at the beginning of the corresponding block.
Operation func( Operation func(
t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
privKeys []crypto.PrivKey, log string, event func(string), privKeys []crypto.PrivKey, log string, event func(string),
) (action string, futureOperations []FutureOperation, err sdk.Error) ) (action string, futureOperations []FutureOperation, err sdk.Error)

View File

@ -71,13 +71,13 @@ func CheckGenTx(
// returned. // returned.
func SignCheckDeliver( func SignCheckDeliver(
t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64,
seq []int64, expPass bool, priv ...crypto.PrivKey, seq []int64, expSimPass, expPass bool, priv ...crypto.PrivKey,
) sdk.Result { ) sdk.Result {
tx := GenTx(msgs, accNums, seq, priv...) tx := GenTx(msgs, accNums, seq, priv...)
// Must simulate now as CheckTx doesn't run Msgs anymore // Must simulate now as CheckTx doesn't run Msgs anymore
res := app.Simulate(tx) res := app.Simulate(tx)
if expPass { if expSimPass {
require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log)
} else { } else {
require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log)

View File

@ -79,7 +79,7 @@ func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper,
} }
func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper,
addr sdk.ValAddress, expFound bool) ValidatorSigningInfo { addr sdk.ConsAddress, expFound bool) ValidatorSigningInfo {
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr)
require.Equal(t, expFound, found) require.Equal(t, expFound, found)
@ -102,7 +102,7 @@ func TestSlashingMsgs(t *testing.T) {
createValidatorMsg := stake.NewMsgCreateValidator( createValidatorMsg := stake.NewMsgCreateValidator(
sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description,
) )
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1)
mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)})
mapp.BeginBlock(abci.RequestBeginBlock{}) mapp.BeginBlock(abci.RequestBeginBlock{})
@ -113,9 +113,9 @@ func TestSlashingMsgs(t *testing.T) {
unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.PubKey.Address())} unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.PubKey.Address())}
// no signing info yet // no signing info yet
checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false) checkValidatorSigningInfo(t, mapp, keeper, sdk.ConsAddress(addr1), false)
// unjail should fail with unknown validator // unjail should fail with unknown validator
res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, priv1) res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, false, priv1)
require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), res.Code) require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), res.Code)
} }

View File

@ -25,7 +25,7 @@ func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command {
return err return err
} }
key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address()))
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(key, storeName) res, err := cliCtx.QueryStore(key, storeName)

View File

@ -30,7 +30,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *wire
return return
} }
key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address()))
res, err := cliCtx.QueryStore(key, storeName) res, err := cliCtx.QueryStore(key, storeName)
if err != nil { if err != nil {

View File

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
@ -33,37 +34,39 @@ type UnjailBody struct {
AccountNumber int64 `json:"account_number"` AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"` Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"` Gas int64 `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
ValidatorAddr string `json:"validator_addr"` ValidatorAddr string `json:"validator_addr"`
} }
// nolint: gocyclo
func unjailRequestHandlerFn(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) { return func(w http.ResponseWriter, r *http.Request) {
var m UnjailBody var m UnjailBody
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
err = json.Unmarshal(body, &m) err = json.Unmarshal(body, &m)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return return
} }
info, err := kb.Get(m.LocalAccountName) info, err := kb.Get(m.LocalAccountName)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
valAddr, err := sdk.ValAddressFromBech32(m.ValidatorAddr) valAddr, err := sdk.ValAddressFromBech32(m.ValidatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
if !bytes.Equal(info.GetPubKey().Address(), valAddr) { if !bytes.Equal(info.GetPubKey().Address(), valAddr) {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own validator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address")
return return
} }
@ -77,10 +80,20 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI
msg := slashing.NewMsgUnjail(valAddr) msg := slashing.NewMsgUnjail(valAddr)
if m.Gas == 0 { adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment)
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) if !ok {
return
}
cliCtx = cliCtx.WithGasAdjustment(adjustment)
if utils.HasDryRunArg(r) || m.Gas == 0 {
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if utils.HasDryRunArg(r) {
utils.WriteSimulationResponse(w, txCtx.Gas)
return return
} }
txCtx = newCtx txCtx = newCtx
@ -88,19 +101,19 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI
txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own validator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address")
return return
} }
res, err := cliCtx.BroadcastTx(txBytes) res, err := cliCtx.BroadcastTx(txBytes)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
output, err := json.MarshalIndent(res, "", " ") output, err := json.MarshalIndent(res, "", " ")
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -12,20 +12,28 @@ const (
// Default slashing codespace // Default slashing codespace
DefaultCodespace sdk.CodespaceType = 10 DefaultCodespace sdk.CodespaceType = 10
CodeInvalidValidator CodeType = 101 CodeInvalidValidator CodeType = 101
CodeValidatorJailed CodeType = 102 CodeValidatorJailed CodeType = 102
CodeValidatorNotJailed CodeType = 103 CodeValidatorNotJailed CodeType = 103
CodeMissingSelfDelegation CodeType = 104
) )
func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator")
} }
func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address")
} }
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeValidatorJailed, "validator still jailed, cannot yet be unjailed") return sdk.NewError(codespace, CodeValidatorJailed, "validator still jailed, cannot yet be unjailed")
} }
func ErrValidatorNotJailed(codespace sdk.CodespaceType) sdk.Error { func ErrValidatorNotJailed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeValidatorNotJailed, "validator not jailed, cannot be unjailed") return sdk.NewError(codespace, CodeValidatorNotJailed, "validator not jailed, cannot be unjailed")
} }
func ErrMissingSelfDelegation(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeMissingSelfDelegation, "validator has no self-delegation; cannot be unjailed")
}

View File

@ -19,35 +19,38 @@ func NewHandler(k Keeper) sdk.Handler {
// Validators must submit a transaction to unjail itself after // Validators must submit a transaction to unjail itself after
// having been jailed (and thus unbonded) for downtime // having been jailed (and thus unbonded) for downtime
func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result {
// Validator must exist
validator := k.validatorSet.Validator(ctx, msg.ValidatorAddr) validator := k.validatorSet.Validator(ctx, msg.ValidatorAddr)
if validator == nil { if validator == nil {
return ErrNoValidatorForAddress(k.codespace).Result() return ErrNoValidatorForAddress(k.codespace).Result()
} }
// cannot be unjailed if no self-delegation exists
selfDel := k.validatorSet.Delegation(ctx, sdk.AccAddress(msg.ValidatorAddr), msg.ValidatorAddr)
if selfDel == nil {
return ErrMissingSelfDelegation(k.codespace).Result()
}
if !validator.GetJailed() { if !validator.GetJailed() {
return ErrValidatorNotJailed(k.codespace).Result() return ErrValidatorNotJailed(k.codespace).Result()
} }
addr := sdk.ValAddress(validator.GetPubKey().Address()) addr := sdk.ConsAddress(validator.GetPubKey().Address())
// Signing info must exist
info, found := k.getValidatorSigningInfo(ctx, addr) info, found := k.getValidatorSigningInfo(ctx, addr)
if !found { if !found {
return ErrNoValidatorForAddress(k.codespace).Result() return ErrNoValidatorForAddress(k.codespace).Result()
} }
// Cannot be unjailed until out of jail // cannot be unjailed until out of jail
if ctx.BlockHeader().Time.Before(info.JailedUntil) { if ctx.BlockHeader().Time.Before(info.JailedUntil) {
return ErrValidatorJailed(k.codespace).Result() return ErrValidatorJailed(k.codespace).Result()
} }
// Update the starting height (so the validator can't be immediately jailed again) // update the starting height so the validator can't be immediately jailed
// again
info.StartHeight = ctx.BlockHeight() info.StartHeight = ctx.BlockHeight()
k.setValidatorSigningInfo(ctx, addr, info) k.setValidatorSigningInfo(ctx, addr, info)
// Unjail the validator
k.validatorSet.Unjail(ctx, validator.GetPubKey()) k.validatorSet.Unjail(ctx, validator.GetPubKey())
tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String())) tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String()))

View File

@ -2,6 +2,7 @@ package slashing
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -19,7 +20,7 @@ func TestCannotUnjailUnlessJailed(t *testing.T) {
got := stake.NewHandler(sk)(ctx, msg) got := stake.NewHandler(sk)(ctx, msg)
require.True(t, got.IsOK()) require.True(t, got.IsOK())
stake.EndBlocker(ctx, sk) stake.EndBlocker(ctx, sk)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()))
// assert non-jailed validator can't be unjailed // assert non-jailed validator can't be unjailed
@ -27,3 +28,64 @@ func TestCannotUnjailUnlessJailed(t *testing.T) {
require.False(t, got.IsOK(), "allowed unjail of non-jailed validator") require.False(t, got.IsOK(), "allowed unjail of non-jailed validator")
require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), got.Code) require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), got.Code)
} }
func TestJailedValidatorDelegations(t *testing.T) {
ctx, _, stakeKeeper, _, slashingKeeper := createTestInput(t)
stakeParams := stakeKeeper.GetParams(ctx)
stakeParams.UnbondingTime = 0
stakeKeeper.SetParams(ctx, stakeParams)
// create a validator
amount := int64(10)
valPubKey, bondAmount := pks[0], sdk.NewInt(amount)
valAddr, consAddr := sdk.ValAddress(addrs[1]), sdk.ConsAddress(addrs[0])
msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount)
got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal)
require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got)
// set dummy signing info
newInfo := ValidatorSigningInfo{
StartHeight: int64(0),
IndexOffset: int64(0),
JailedUntil: time.Unix(0, 0),
SignedBlocksCounter: int64(0),
}
slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo)
// delegate tokens to the validator
delAddr := sdk.AccAddress(addrs[2])
msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount)
got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate)
require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got)
unbondShares := sdk.NewDec(10)
// unbond validator total self-delegations (which should jail the validator)
msgBeginUnbonding := stake.NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares)
got = stake.NewHandler(stakeKeeper)(ctx, msgBeginUnbonding)
require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got)
msgCompleteUnbonding := stake.NewMsgCompleteUnbonding(sdk.AccAddress(valAddr), valAddr)
got = stake.NewHandler(stakeKeeper)(ctx, msgCompleteUnbonding)
require.True(t, got.IsOK(), "expected complete unbonding validator msg to be ok, got: %v", got)
// verify validator still exists and is jailed
validator, found := stakeKeeper.GetValidator(ctx, valAddr)
require.True(t, found)
require.True(t, validator.GetJailed())
// verify the validator cannot unjail itself
got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr))
require.False(t, got.IsOK(), "expected jailed validator to not be able to unjail, got: %v", got)
// self-delegate to validator
msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount)
got = stake.NewHandler(stakeKeeper)(ctx, msgSelfDelegate)
require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got)
// verify the validator can now unjail itself
got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr))
require.True(t, got.IsOK(), "expected jailed validator to be able to unjail, got: %v", got)
}

46
x/slashing/hooks.go Normal file
View File

@ -0,0 +1,46 @@
package slashing
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Create a new slashing period when a validator is bonded
func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) {
slashingPeriod := ValidatorSlashingPeriod{
ValidatorAddr: address,
StartHeight: ctx.BlockHeight(),
EndHeight: 0,
SlashedSoFar: sdk.ZeroDec(),
}
k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod)
}
// Mark the slashing period as having ended when a validator begins unbonding
func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) {
slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, ctx.BlockHeight())
slashingPeriod.EndHeight = ctx.BlockHeight()
k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod)
}
// Wrapper struct for sdk.ValidatorHooks
type ValidatorHooks struct {
k Keeper
}
// Assert implementation
var _ sdk.ValidatorHooks = ValidatorHooks{}
// Return a sdk.ValidatorHooks interface over the wrapper struct
func (k Keeper) ValidatorHooks() sdk.ValidatorHooks {
return ValidatorHooks{k}
}
// Implements sdk.ValidatorHooks
func (v ValidatorHooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) {
v.k.onValidatorBonded(ctx, address)
}
// Implements sdk.ValidatorHooks
func (v ValidatorHooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) {
v.k.onValidatorBeginUnbonding(ctx, address)
}

26
x/slashing/hooks_test.go Normal file
View File

@ -0,0 +1,26 @@
package slashing
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestHookOnValidatorBonded(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t)
addr := sdk.ConsAddress(addrs[0])
keeper.onValidatorBonded(ctx, addr)
period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight())
require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), 0, sdk.ZeroDec()}, period)
}
func TestHookOnValidatorBeginUnbonding(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t)
addr := sdk.ConsAddress(addrs[0])
keeper.onValidatorBonded(ctx, addr)
keeper.onValidatorBeginUnbonding(ctx, addr)
period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight())
require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), ctx.BlockHeight(), sdk.ZeroDec()}, period)
}

View File

@ -40,7 +40,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
logger := ctx.Logger().With("module", "x/slashing") logger := ctx.Logger().With("module", "x/slashing")
time := ctx.BlockHeader().Time time := ctx.BlockHeader().Time
age := time.Sub(timestamp) age := time.Sub(timestamp)
address := sdk.ValAddress(addr) address := sdk.ConsAddress(addr)
pubkey, err := k.getPubkey(ctx, addr) pubkey, err := k.getPubkey(ctx, addr)
if err != nil { if err != nil {
panic(fmt.Sprintf("Validator address %v not found", addr)) panic(fmt.Sprintf("Validator address %v not found", addr))
@ -56,8 +56,14 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
// Double sign confirmed // Double sign confirmed
logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge)) logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge))
// Cap the amount slashed to the penalty for the worst infraction
// within the slashing period when this infraction was committed
fraction := k.SlashFractionDoubleSign(ctx)
revisedFraction := k.capBySlashingPeriod(ctx, address, fraction, infractionHeight)
logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction))
// Slash validator // Slash validator
k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, k.SlashFractionDoubleSign(ctx)) k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, revisedFraction)
// Jail validator // Jail validator
k.validatorSet.Jail(ctx, pubkey) k.validatorSet.Jail(ctx, pubkey)
@ -76,7 +82,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) {
logger := ctx.Logger().With("module", "x/slashing") logger := ctx.Logger().With("module", "x/slashing")
height := ctx.BlockHeight() height := ctx.BlockHeight()
address := sdk.ValAddress(addr) address := sdk.ConsAddress(addr)
pubkey, err := k.getPubkey(ctx, addr) pubkey, err := k.getPubkey(ctx, addr)
if err != nil { if err != nil {
panic(fmt.Sprintf("Validator address %v not found", addr)) panic(fmt.Sprintf("Validator address %v not found", addr))
@ -169,7 +175,3 @@ func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
store.Delete(getAddrPubkeyRelationKey(addr)) store.Delete(getAddrPubkeyRelationKey(addr))
} }
func getAddrPubkeyRelationKey(address []byte) []byte {
return append([]byte{0x03}, address...)
}

View File

@ -24,13 +24,14 @@ func TestHandleDoubleSign(t *testing.T) {
// initial setup // initial setup
ctx, ck, sk, _, keeper := createTestInput(t) ctx, ck, sk, _, keeper := createTestInput(t)
sk = sk.WithValidatorHooks(keeper.ValidatorHooks())
amtInt := int64(100) amtInt := int64(100)
addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt)
got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(sdk.ValAddress(addr), val, amt)) got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(sdk.ValAddress(addr), val, amt))
require.True(t, got.IsOK()) require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk) validatorUpdates := stake.EndBlocker(ctx, sk)
keeper.AddValidators(ctx, validatorUpdates) keeper.AddValidators(ctx, validatorUpdates)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()))
// handle a signature to set signing info // handle a signature to set signing info
@ -58,12 +59,68 @@ func TestHandleDoubleSign(t *testing.T) {
) )
} }
// Test that the amount a validator is slashed for multiple double signs
// is correctly capped by the slashing period in which they were committed
func TestSlashingPeriodCap(t *testing.T) {
// initial setup
ctx, ck, sk, _, keeper := createTestInput(t)
sk = sk.WithValidatorHooks(keeper.ValidatorHooks())
amtInt := int64(100)
addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt)
got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt))
require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk)
keeper.AddValidators(ctx, validatorUpdates)
require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower()))
// handle a signature to set signing info
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true)
// double sign less than max age
keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt)
// should be jailed
require.True(t, sk.Validator(ctx, addr).GetJailed())
// update block height
ctx = ctx.WithBlockHeight(int64(1))
// unjail to measure power
sk.Unjail(ctx, val)
// power should be reduced
expectedPower := sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20)))
require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower())
// double sign again, same slashing period
keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt)
// should be jailed
require.True(t, sk.Validator(ctx, addr).GetJailed())
// update block height
ctx = ctx.WithBlockHeight(int64(2))
// unjail to measure power
sk.Unjail(ctx, val)
// power should be equal, no more should have been slashed
expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20)))
require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower())
// double sign again, new slashing period
keeper.handleDoubleSign(ctx, val.Address(), 2, time.Unix(0, 0), amtInt)
// should be jailed
require.True(t, sk.Validator(ctx, addr).GetJailed())
// unjail to measure power
sk.Unjail(ctx, val)
// power should be reduced
expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(18).Quo(sdk.NewDec(20)))
require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower())
}
// Test a validator through uptime, downtime, revocation, // Test a validator through uptime, downtime, revocation,
// unrevocation, starting height reset, and revocation again // unrevocation, starting height reset, and revocation again
func TestHandleAbsentValidator(t *testing.T) { func TestHandleAbsentValidator(t *testing.T) {
// initial setup // initial setup
ctx, ck, sk, _, keeper := createTestInput(t) ctx, ck, sk, _, keeper := createTestInput(t)
sk = sk.WithValidatorHooks(keeper.ValidatorHooks())
amtInt := int64(100) amtInt := int64(100)
addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt)
sh := stake.NewHandler(sk) sh := stake.NewHandler(sk)
@ -72,9 +129,9 @@ func TestHandleAbsentValidator(t *testing.T) {
require.True(t, got.IsOK()) require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk) validatorUpdates := stake.EndBlocker(ctx, sk)
keeper.AddValidators(ctx, validatorUpdates) keeper.AddValidators(ctx, validatorUpdates)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()))
info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.False(t, found) require.False(t, found)
require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, int64(0), info.IndexOffset) require.Equal(t, int64(0), info.IndexOffset)
@ -89,7 +146,7 @@ func TestHandleAbsentValidator(t *testing.T) {
ctx = ctx.WithBlockHeight(height) ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true)
} }
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found) require.True(t, found)
require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx), info.SignedBlocksCounter) require.Equal(t, keeper.SignedBlocksWindow(ctx), info.SignedBlocksCounter)
@ -99,7 +156,7 @@ func TestHandleAbsentValidator(t *testing.T) {
ctx = ctx.WithBlockHeight(height) ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false)
} }
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found) require.True(t, found)
require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter)
@ -113,14 +170,14 @@ func TestHandleAbsentValidator(t *testing.T) {
// 501st block missed // 501st block missed
ctx = ctx.WithBlockHeight(height) ctx = ctx.WithBlockHeight(height)
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false)
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found) require.True(t, found)
require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter)
// validator should have been jailed // validator should have been jailed
validator, _ = sk.GetValidatorByPubKey(ctx, val) validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus()) require.Equal(t, sdk.Unbonding, validator.GetStatus())
// unrevocation should fail prior to jail expiration // unrevocation should fail prior to jail expiration
got = slh(ctx, NewMsgUnjail(sdk.ValAddress(addr))) got = slh(ctx, NewMsgUnjail(sdk.ValAddress(addr)))
@ -141,7 +198,7 @@ func TestHandleAbsentValidator(t *testing.T) {
require.Equal(t, int64(amtInt)-slashAmt, pool.BondedTokens.RoundInt64()) require.Equal(t, int64(amtInt)-slashAmt, pool.BondedTokens.RoundInt64())
// validator start height should have been changed // validator start height should have been changed
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found) require.True(t, found)
require.Equal(t, height, info.StartHeight) require.Equal(t, height, info.StartHeight)
require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter)
@ -167,7 +224,7 @@ func TestHandleAbsentValidator(t *testing.T) {
keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false)
} }
validator, _ = sk.GetValidatorByPubKey(ctx, val) validator, _ = sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus()) require.Equal(t, sdk.Unbonding, validator.GetStatus())
} }
// Test a new validator entering the validator set // Test a new validator entering the validator set
@ -182,7 +239,7 @@ func TestHandleNewValidator(t *testing.T) {
require.True(t, got.IsOK()) require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk) validatorUpdates := stake.EndBlocker(ctx, sk)
keeper.AddValidators(ctx, validatorUpdates) keeper.AddValidators(ctx, validatorUpdates)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}})
require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()) require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())
// 1000 first blocks not a validator // 1000 first blocks not a validator
@ -193,7 +250,7 @@ func TestHandleNewValidator(t *testing.T) {
ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2)
keeper.handleValidatorSignature(ctx, val.Address(), 100, false) keeper.handleValidatorSignature(ctx, val.Address(), 100, false)
info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address()))
require.True(t, found) require.True(t, found)
require.Equal(t, int64(keeper.SignedBlocksWindow(ctx)+1), info.StartHeight) require.Equal(t, int64(keeper.SignedBlocksWindow(ctx)+1), info.StartHeight)
require.Equal(t, int64(2), info.IndexOffset) require.Equal(t, int64(2), info.IndexOffset)
@ -236,7 +293,7 @@ func TestHandleAlreadyJailed(t *testing.T) {
// validator should have been jailed and slashed // validator should have been jailed and slashed
validator, _ := sk.GetValidatorByPubKey(ctx, val) validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Unbonded, validator.GetStatus()) require.Equal(t, sdk.Unbonding, validator.GetStatus())
// validator should have been slashed // validator should have been slashed
require.Equal(t, int64(amtInt-1), validator.GetTokens().RoundInt64()) require.Equal(t, int64(amtInt-1), validator.GetTokens().RoundInt64())

43
x/slashing/keys.go Normal file
View File

@ -0,0 +1,43 @@
package slashing
import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// key prefix bytes
var (
ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info
ValidatorSigningBitArrayKey = []byte{0x02} // Prefix for signature bit array
ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period
AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation
)
// stored by *Tendermint* address (not owner address)
func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte {
return append(ValidatorSigningInfoKey, v.Bytes()...)
}
// stored by *Tendermint* address (not owner address)
func GetValidatorSigningBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append(ValidatorSigningBitArrayKey, append(v.Bytes(), b...)...)
}
// stored by *Tendermint* address (not owner address)
func GetValidatorSlashingPeriodPrefix(v sdk.ConsAddress) []byte {
return append(ValidatorSlashingPeriodKey, v.Bytes()...)
}
// stored by *Tendermint* address (not owner address) followed by start height
func GetValidatorSlashingPeriodKey(v sdk.ConsAddress, startHeight int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(startHeight))
return append(GetValidatorSlashingPeriodPrefix(v), b...)
}
func getAddrPubkeyRelationKey(address []byte) []byte {
return append(AddrPubkeyRelationKey, address...)
}

View File

@ -1,7 +1,6 @@
package slashing package slashing
import ( import (
"encoding/binary"
"fmt" "fmt"
"time" "time"
@ -9,7 +8,7 @@ import (
) )
// Stored by *validator* address (not owner address) // Stored by *validator* address (not owner address)
func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress) (info ValidatorSigningInfo, found bool) { func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info ValidatorSigningInfo, found bool) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
bz := store.Get(GetValidatorSigningInfoKey(address)) bz := store.Get(GetValidatorSigningInfoKey(address))
if bz == nil { if bz == nil {
@ -22,14 +21,14 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress)
} }
// Stored by *validator* address (not owner address) // Stored by *validator* address (not owner address)
func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress, info ValidatorSigningInfo) { func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info ValidatorSigningInfo) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(info) bz := k.cdc.MustMarshalBinary(info)
store.Set(GetValidatorSigningInfoKey(address), bz) store.Set(GetValidatorSigningInfoKey(address), bz)
} }
// Stored by *validator* address (not owner address) // Stored by *validator* address (not owner address)
func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddress, index int64) (signed bool) { func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) bz := store.Get(GetValidatorSigningBitArrayKey(address, index))
if bz == nil { if bz == nil {
@ -42,7 +41,7 @@ func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddr
} }
// Stored by *validator* address (not owner address) // Stored by *validator* address (not owner address)
func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddress, index int64, signed bool) { func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, signed bool) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(signed) bz := k.cdc.MustMarshalBinary(signed)
store.Set(GetValidatorSigningBitArrayKey(address, index), bz) store.Set(GetValidatorSigningBitArrayKey(address, index), bz)
@ -71,15 +70,3 @@ func (i ValidatorSigningInfo) HumanReadableString() string {
return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, signed blocks counter: %d", return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, signed blocks counter: %d",
i.StartHeight, i.IndexOffset, i.JailedUntil, i.SignedBlocksCounter) i.StartHeight, i.IndexOffset, i.JailedUntil, i.SignedBlocksCounter)
} }
// Stored by *validator* address (not owner address)
func GetValidatorSigningInfoKey(v sdk.ValAddress) []byte {
return append([]byte{0x01}, v.Bytes()...)
}
// Stored by *validator* address (not owner address)
func GetValidatorSigningBitArrayKey(v sdk.ValAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append([]byte{0x02}, append(v.Bytes(), b...)...)
}

View File

@ -11,7 +11,7 @@ import (
func TestGetSetValidatorSigningInfo(t *testing.T) { func TestGetSetValidatorSigningInfo(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t) ctx, _, _, _, keeper := createTestInput(t)
info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0])) info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]))
require.False(t, found) require.False(t, found)
newInfo := ValidatorSigningInfo{ newInfo := ValidatorSigningInfo{
StartHeight: int64(4), StartHeight: int64(4),
@ -19,8 +19,8 @@ func TestGetSetValidatorSigningInfo(t *testing.T) {
JailedUntil: time.Unix(2, 0), JailedUntil: time.Unix(2, 0),
SignedBlocksCounter: int64(10), SignedBlocksCounter: int64(10),
} }
keeper.setValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0]), newInfo) keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo)
info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0])) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]))
require.True(t, found) require.True(t, found)
require.Equal(t, info.StartHeight, int64(4)) require.Equal(t, info.StartHeight, int64(4))
require.Equal(t, info.IndexOffset, int64(3)) require.Equal(t, info.IndexOffset, int64(3))
@ -30,9 +30,9 @@ func TestGetSetValidatorSigningInfo(t *testing.T) {
func TestGetSetValidatorSigningBitArray(t *testing.T) { func TestGetSetValidatorSigningBitArray(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t) ctx, _, _, _, keeper := createTestInput(t)
signed := keeper.getValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0) signed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0)
require.False(t, signed) // treat empty key as unsigned require.False(t, signed) // treat empty key as unsigned
keeper.setValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0, true) keeper.setValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true)
signed = keeper.getValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0) signed = keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0)
require.True(t, signed) // now should be signed require.True(t, signed) // now should be signed
} }

View File

@ -5,8 +5,6 @@ import (
"math/rand" "math/rand"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
@ -17,11 +15,13 @@ import (
// SimulateMsgUnjail // SimulateMsgUnjail
func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation { func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
key := simulation.RandomKey(r, keys) key := simulation.RandomKey(r, keys)
address := sdk.ValAddress(key.PubKey().Address()) address := sdk.ValAddress(key.PubKey().Address())
msg := slashing.NewMsgUnjail(address) msg := slashing.NewMsgUnjail(address)
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := slashing.NewHandler(k)(ctx, msg) result := slashing.NewHandler(k)(ctx, msg)
if result.IsOK() { if result.IsOK() {

View File

@ -0,0 +1,107 @@
package slashing
import (
"encoding/binary"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Cap an infraction's slash amount by the slashing period in which it was committed
func (k Keeper) capBySlashingPeriod(ctx sdk.Context, address sdk.ConsAddress, fraction sdk.Dec, infractionHeight int64) (revisedFraction sdk.Dec) {
// Fetch the newest slashing period starting before this infraction was committed
slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, infractionHeight)
// Sanity check
if slashingPeriod.EndHeight > 0 && slashingPeriod.EndHeight < infractionHeight {
panic(fmt.Sprintf("slashing period ended before infraction: infraction height %d, slashing period ended at %d", infractionHeight, slashingPeriod.EndHeight))
}
// Calculate the updated total slash amount
// This is capped at the slashing fraction for the worst infraction within this slashing period
totalToSlash := sdk.MaxDec(slashingPeriod.SlashedSoFar, fraction)
// Calculate the remainder which we now must slash
revisedFraction = totalToSlash.Sub(slashingPeriod.SlashedSoFar)
// Update the slashing period struct
slashingPeriod.SlashedSoFar = totalToSlash
k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod)
return
}
// Stored by validator Tendermint address (not owner address)
// This function retrieves the most recent slashing period starting
// before a particular height - so the slashing period that was "in effect"
// at the time of an infraction committed at that height.
func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk.ConsAddress, height int64) (slashingPeriod ValidatorSlashingPeriod) {
store := ctx.KVStore(k.storeKey)
// Get the most recent slashing period at or before the infraction height
start := GetValidatorSlashingPeriodPrefix(address)
end := sdk.PrefixEndBytes(GetValidatorSlashingPeriodKey(address, height))
iterator := store.ReverseIterator(start, end)
if !iterator.Valid() {
panic("expected to find slashing period, but none was found")
}
slashingPeriod = k.unmarshalSlashingPeriodKeyValue(iterator.Key(), iterator.Value())
return
}
// Stored by validator Tendermint address (not owner address)
// This function sets a validator slashing period for a particular validator,
// start height, end height, and current slashed-so-far total, or updates
// an existing slashing period for the same validator and start height.
func (k Keeper) addOrUpdateValidatorSlashingPeriod(ctx sdk.Context, slashingPeriod ValidatorSlashingPeriod) {
slashingPeriodValue := ValidatorSlashingPeriodValue{
EndHeight: slashingPeriod.EndHeight,
SlashedSoFar: slashingPeriod.SlashedSoFar,
}
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(slashingPeriodValue)
store.Set(GetValidatorSlashingPeriodKey(slashingPeriod.ValidatorAddr, slashingPeriod.StartHeight), bz)
}
// Unmarshal key/value into a ValidatorSlashingPeriod
func (k Keeper) unmarshalSlashingPeriodKeyValue(key []byte, value []byte) ValidatorSlashingPeriod {
var slashingPeriodValue ValidatorSlashingPeriodValue
k.cdc.MustUnmarshalBinary(value, &slashingPeriodValue)
address := sdk.ConsAddress(key[1 : 1+sdk.AddrLen])
startHeight := int64(binary.LittleEndian.Uint64(key[1+sdk.AddrLen : 1+sdk.AddrLen+8]))
return ValidatorSlashingPeriod{
ValidatorAddr: address,
StartHeight: startHeight,
EndHeight: slashingPeriodValue.EndHeight,
SlashedSoFar: slashingPeriodValue.SlashedSoFar,
}
}
// Construct a new `ValidatorSlashingPeriod` struct
func NewValidatorSlashingPeriod(startHeight int64, endHeight int64, slashedSoFar sdk.Dec) ValidatorSlashingPeriod {
return ValidatorSlashingPeriod{
StartHeight: startHeight,
EndHeight: endHeight,
SlashedSoFar: slashedSoFar,
}
}
// Slashing period for a validator
type ValidatorSlashingPeriod struct {
ValidatorAddr sdk.ConsAddress `json:"validator_addr"` // validator which this slashing period is for
StartHeight int64 `json:"start_height"` // starting height of the slashing period
EndHeight int64 `json:"end_height"` // ending height of the slashing period, or sentinel value of 0 for in-progress
SlashedSoFar sdk.Dec `json:"slashed_so_far"` // fraction of validator stake slashed so far in this slashing period
}
// Value part of slashing period (validator address & start height are stored in the key)
type ValidatorSlashingPeriodValue struct {
EndHeight int64 `json:"end_height"`
SlashedSoFar sdk.Dec `json:"slashed_so_far"`
}
// Return human readable slashing period
func (p ValidatorSlashingPeriod) HumanReadableString() string {
return fmt.Sprintf("Start height: %d, end height: %d, slashed so far: %v",
p.StartHeight, p.EndHeight, p.SlashedSoFar)
}

View File

@ -0,0 +1,86 @@
package slashing
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestGetSetValidatorSlashingPeriod(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t)
addr := sdk.ConsAddress(addrs[0])
height := int64(5)
require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) })
newPeriod := ValidatorSlashingPeriod{
ValidatorAddr: addr,
StartHeight: height,
EndHeight: height + 10,
SlashedSoFar: sdk.ZeroDec(),
}
keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod)
// Get at start height
retrieved := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height)
require.Equal(t, newPeriod, retrieved)
// Get after start height (works)
retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(6))
require.Equal(t, newPeriod, retrieved)
// Get before start height (panic)
require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(0)) })
// Get after end height (panic)
newPeriod.EndHeight = int64(4)
keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod)
require.Panics(t, func() { keeper.capBySlashingPeriod(ctx, addr, sdk.ZeroDec(), height) })
// Back to old end height
newPeriod.EndHeight = height + 10
keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod)
// Set a new, later period
anotherPeriod := ValidatorSlashingPeriod{
ValidatorAddr: addr,
StartHeight: height + 1,
EndHeight: height + 11,
SlashedSoFar: sdk.ZeroDec(),
}
keeper.addOrUpdateValidatorSlashingPeriod(ctx, anotherPeriod)
// Old period retrieved for prior height
retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height)
require.Equal(t, newPeriod, retrieved)
// New period retrieved at new height
retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height+1)
require.Equal(t, anotherPeriod, retrieved)
}
func TestValidatorSlashingPeriodCap(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t)
addr := sdk.ConsAddress(addrs[0])
height := int64(5)
newPeriod := ValidatorSlashingPeriod{
ValidatorAddr: addr,
StartHeight: height,
EndHeight: height + 10,
SlashedSoFar: sdk.ZeroDec(),
}
keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod)
half := sdk.NewDec(1).Quo(sdk.NewDec(2))
// First slash should be full
fractionA := keeper.capBySlashingPeriod(ctx, addr, half, height)
require.True(t, fractionA.Equal(half))
// Second slash should be capped
fractionB := keeper.capBySlashingPeriod(ctx, addr, half, height)
require.True(t, fractionB.Equal(sdk.ZeroDec()))
// Third slash should be capped to difference
fractionC := keeper.capBySlashingPeriod(ctx, addr, sdk.OneDec(), height)
require.True(t, fractionC.Equal(half))
}

View File

@ -4,6 +4,7 @@ import (
"encoding/hex" "encoding/hex"
"os" "os"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -30,10 +31,10 @@ var (
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"),
} }
addrs = []sdk.AccAddress{ addrs = []sdk.ValAddress{
sdk.AccAddress(pks[0].Address()), sdk.ValAddress(pks[0].Address()),
sdk.AccAddress(pks[1].Address()), sdk.ValAddress(pks[1].Address()),
sdk.AccAddress(pks[2].Address()), sdk.ValAddress(pks[2].Address()),
} }
initCoins = sdk.NewInt(200) initCoins = sdk.NewInt(200)
) )
@ -61,7 +62,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
err := ms.LoadLatestVersion() err := ms.LoadLatestVersion()
require.Nil(t, err) require.Nil(t, err)
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewTMLogger(os.Stdout)) ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout))
cdc := createTestCodec() cdc := createTestCodec()
accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount)
ck := bank.NewKeeper(accountMapper) ck := bank.NewKeeper(accountMapper)
@ -75,7 +76,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para
require.Nil(t, err) require.Nil(t, err)
for _, addr := range addrs { for _, addr := range addrs {
_, _, err = ck.AddCoins(ctx, addr, sdk.Coins{ _, _, err = ck.AddCoins(ctx, sdk.AccAddress(addr), sdk.Coins{
{sk.GetParams(ctx).BondDenom, initCoins}, {sk.GetParams(ctx).BondDenom, initCoins},
}) })
} }
@ -108,3 +109,11 @@ func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt
Delegation: sdk.Coin{"steak", amt}, Delegation: sdk.Coin{"steak", amt},
} }
} }
func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) stake.MsgDelegate {
return stake.MsgDelegate{
DelegatorAddr: delAddr,
ValidatorAddr: valAddr,
Delegation: sdk.Coin{"steak", delAmount},
}
}

View File

@ -21,7 +21,7 @@ func TestBeginBlocker(t *testing.T) {
require.True(t, got.IsOK()) require.True(t, got.IsOK())
validatorUpdates := stake.EndBlocker(ctx, sk) validatorUpdates := stake.EndBlocker(ctx, sk)
keeper.AddValidators(ctx, validatorUpdates) keeper.AddValidators(ctx, validatorUpdates)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()))
val := abci.Validator{ val := abci.Validator{
@ -40,7 +40,7 @@ func TestBeginBlocker(t *testing.T) {
} }
BeginBlocker(ctx, req, keeper) BeginBlocker(ctx, req, keeper)
info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(pk.Address())) info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address()))
require.True(t, found) require.True(t, found)
require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, ctx.BlockHeight(), info.StartHeight)
require.Equal(t, int64(1), info.IndexOffset) require.Equal(t, int64(1), info.IndexOffset)
@ -80,5 +80,5 @@ func TestBeginBlocker(t *testing.T) {
// validator should be jailed // validator should be jailed
validator, found := sk.GetValidatorByPubKey(ctx, pk) validator, found := sk.GetValidatorByPubKey(ctx, pk)
require.True(t, found) require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.GetStatus()) require.Equal(t, sdk.Unbonding, validator.GetStatus())
} }

View File

@ -131,7 +131,7 @@ func TestStakeMsgs(t *testing.T) {
sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description,
) )
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1)
mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)})
mApp.BeginBlock(abci.RequestBeginBlock{}) mApp.BeginBlock(abci.RequestBeginBlock{})
@ -145,7 +145,7 @@ func TestStakeMsgs(t *testing.T) {
addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description,
) )
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, priv1, priv2) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, true, priv1, priv2)
mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)})
mApp.BeginBlock(abci.RequestBeginBlock{}) mApp.BeginBlock(abci.RequestBeginBlock{})
@ -161,7 +161,7 @@ func TestStakeMsgs(t *testing.T) {
description = NewDescription("bar_moniker", "", "", "") description = NewDescription("bar_moniker", "", "", "")
editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description) editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, priv1) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, true, priv1)
validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true)
require.Equal(t, description, validator.Description) require.Equal(t, description, validator.Description)
@ -169,13 +169,13 @@ func TestStakeMsgs(t *testing.T) {
mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin})
delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin) delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, priv2) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, true, priv2)
mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)})
checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDec(10)) checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDec(10))
// begin unbonding // begin unbonding
beginUnbondingMsg := NewMsgBeginUnbonding(addr2, sdk.ValAddress(addr1), sdk.NewDec(10)) beginUnbondingMsg := NewMsgBeginUnbonding(addr2, sdk.ValAddress(addr1), sdk.NewDec(10))
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, priv2) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, true, priv2)
// delegation should exist anymore // delegation should exist anymore
checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{}) checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{})

View File

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
@ -60,6 +61,7 @@ type EditDelegationsBody struct {
AccountNumber int64 `json:"account_number"` AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"` Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"` Gas int64 `json:"gas"`
GasAdjustment string `json:"gas_adjustment"`
Delegations []msgDelegationsInput `json:"delegations"` Delegations []msgDelegationsInput `json:"delegations"`
BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"`
CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"`
@ -106,18 +108,18 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
for _, msg := range m.Delegations { for _, msg := range m.Delegations {
delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))
return return
} }
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
if !bytes.Equal(info.GetPubKey().Address(), delAddr) { if !bytes.Equal(info.GetPubKey().Address(), delAddr) {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address")
return return
} }
@ -133,29 +135,29 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
for _, msg := range m.BeginRedelegates { for _, msg := range m.BeginRedelegates {
delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
if !bytes.Equal(info.GetPubKey().Address(), delAddr) { if !bytes.Equal(info.GetPubKey().Address(), delAddr) {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address")
return return
} }
valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
shares, err := sdk.NewDecFromStr(msg.SharesAmount) shares, err := sdk.NewDecFromStr(msg.SharesAmount)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))
return return
} }
@ -172,24 +174,24 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
for _, msg := range m.CompleteRedelegates { for _, msg := range m.CompleteRedelegates {
delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))
return return
} }
valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
if !bytes.Equal(info.GetPubKey().Address(), delAddr) { if !bytes.Equal(info.GetPubKey().Address(), delAddr) {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address")
return return
} }
@ -205,24 +207,24 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
for _, msg := range m.BeginUnbondings { for _, msg := range m.BeginUnbondings {
delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))
return return
} }
if !bytes.Equal(info.GetPubKey().Address(), delAddr) { if !bytes.Equal(info.GetPubKey().Address(), delAddr) {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address")
return return
} }
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
shares, err := sdk.NewDecFromStr(msg.SharesAmount) shares, err := sdk.NewDecFromStr(msg.SharesAmount)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))
return return
} }
@ -238,18 +240,18 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
for _, msg := range m.CompleteUnbondings { for _, msg := range m.CompleteUnbondings {
delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))
return return
} }
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr) valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddr)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))
return return
} }
if !bytes.Equal(info.GetPubKey().Address(), delAddr) { if !bytes.Equal(info.GetPubKey().Address(), delAddr) {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address")
return return
} }
@ -276,10 +278,20 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
m.Sequence++ m.Sequence++
if m.Gas == 0 { adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment)
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) if !ok {
return
}
cliCtx = cliCtx.WithGasAdjustment(adjustment)
if utils.HasDryRunArg(r) || m.Gas == 0 {
newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if utils.HasDryRunArg(r) {
utils.WriteSimulationResponse(w, txCtx.Gas)
return return
} }
txCtx = newCtx txCtx = newCtx
@ -287,7 +299,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg})
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())
return return
} }
@ -301,7 +313,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
for i, txBytes := range signedTxs { for i, txBytes := range signedTxs {
res, err := cliCtx.BroadcastTx(txBytes) res, err := cliCtx.BroadcastTx(txBytes)
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }
@ -310,7 +322,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex
output, err := wire.MarshalJSONIndent(cdc, results[:]) output, err := wire.MarshalJSONIndent(cdc, results[:])
if err != nil { if err != nil {
utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return return
} }

View File

@ -1,6 +1,7 @@
package stake package stake
import ( import (
"bytes"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -128,17 +129,19 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe
} }
func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result { func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result {
validator, found := k.GetValidator(ctx, msg.ValidatorAddr) validator, found := k.GetValidator(ctx, msg.ValidatorAddr)
if !found { if !found {
return ErrNoValidatorFound(k.Codespace()).Result() return ErrNoValidatorFound(k.Codespace()).Result()
} }
if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { if msg.Delegation.Denom != k.GetParams(ctx).BondDenom {
return ErrBadDenom(k.Codespace()).Result() return ErrBadDenom(k.Codespace()).Result()
} }
if validator.Jailed {
if validator.Jailed && !bytes.Equal(validator.Operator, msg.DelegatorAddr) {
return ErrValidatorJailed(k.Codespace()).Result() return ErrValidatorJailed(k.Codespace()).Result()
} }
_, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true) _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true)
if err != nil { if err != nil {
return err.Result() return err.Result()
@ -149,6 +152,7 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper)
tags.Delegator, []byte(msg.DelegatorAddr.String()), tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.DstValidator, []byte(msg.ValidatorAddr.String()), tags.DstValidator, []byte(msg.ValidatorAddr.String()),
) )
return sdk.Result{ return sdk.Result{
Tags: tags, Tags: tags,
} }

View File

@ -85,8 +85,8 @@ func TestValidatorByPowerIndex(t *testing.T) {
keeper.Jail(ctx, keep.PKs[0]) keeper.Jail(ctx, keep.PKs[0])
validator, found = keeper.GetValidator(ctx, validatorAddr) validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found) require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.Status) // ensure is unbonded require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding
require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure is unbonded require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure tokens slashed
// the old power record should have been deleted as the power changed // the old power record should have been deleted as the power changed
require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power))
@ -193,6 +193,97 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) {
require.False(t, got.IsOK(), "%v", got) require.False(t, got.IsOK(), "%v", got)
} }
func TestLegacyValidatorDelegations(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, int64(1000))
setInstantUnbondPeriod(keeper, ctx)
bondAmount := int64(10)
valAddr, valPubKey := sdk.ValAddress(keep.Addrs[0]), keep.PKs[0]
delAddr := keep.Addrs[1]
// create validator
msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount)
got := handleMsgCreateValidator(ctx, msgCreateVal, keeper)
require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got)
// verify the validator exists and has the correct attributes
validator, found := keeper.GetValidator(ctx, valAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status)
require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64())
require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64())
// delegate tokens to the validator
msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount)
got = handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got)
// verify validator bonded shares
validator, found = keeper.GetValidator(ctx, valAddr)
require.True(t, found)
require.Equal(t, bondAmount*2, validator.DelegatorShares.RoundInt64())
require.Equal(t, bondAmount*2, validator.BondedTokens().RoundInt64())
// unbond validator total self-delegations (which should jail the validator)
unbondShares := sdk.NewDec(10)
msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares)
msgCompleteUnbonding := NewMsgCompleteUnbonding(sdk.AccAddress(valAddr), valAddr)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, got.IsOK(), "expected complete unbonding validator msg to be ok, got %v", got)
// verify the validator record still exists, is jailed, and has correct tokens
validator, found = keeper.GetValidator(ctx, valAddr)
require.True(t, found)
require.True(t, validator.Jailed)
require.Equal(t, sdk.NewDec(10), validator.Tokens)
// verify delegation still exists
bond, found := keeper.GetDelegation(ctx, delAddr, valAddr)
require.True(t, found)
require.Equal(t, bondAmount, bond.Shares.RoundInt64())
require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64())
// verify a delegator cannot create a new delegation to the now jailed validator
msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount)
got = handleMsgDelegate(ctx, msgDelegate, keeper)
require.False(t, got.IsOK(), "expected delegation to not be ok, got %v", got)
// verify the validator can still self-delegate
msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount)
got = handleMsgDelegate(ctx, msgSelfDelegate, keeper)
require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got)
// verify validator bonded shares
validator, found = keeper.GetValidator(ctx, valAddr)
require.True(t, found)
require.Equal(t, bondAmount*2, validator.DelegatorShares.RoundInt64())
require.Equal(t, bondAmount*2, validator.Tokens.RoundInt64())
// unjail the validator now that is has non-zero self-delegated shares
keeper.Unjail(ctx, valPubKey)
// verify the validator can now accept delegations
msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount)
got = handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got)
// verify validator bonded shares
validator, found = keeper.GetValidator(ctx, valAddr)
require.True(t, found)
require.Equal(t, bondAmount*3, validator.DelegatorShares.RoundInt64())
require.Equal(t, bondAmount*3, validator.Tokens.RoundInt64())
// verify new delegation
bond, found = keeper.GetDelegation(ctx, delAddr, valAddr)
require.True(t, found)
require.Equal(t, bondAmount*2, bond.Shares.RoundInt64())
require.Equal(t, bondAmount*3, validator.DelegatorShares.RoundInt64())
}
func TestIncrementsMsgDelegate(t *testing.T) { func TestIncrementsMsgDelegate(t *testing.T) {
initBond := int64(1000) initBond := int64(1000)
ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond)

View File

@ -2,6 +2,7 @@ package keeper
import ( import (
"bytes" "bytes"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/cosmos/cosmos-sdk/x/stake/types"
@ -288,6 +289,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA
if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Jailed == false { if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Jailed == false {
validator.Jailed = true validator.Jailed = true
} }
k.RemoveDelegation(ctx, delegation) k.RemoveDelegation(ctx, delegation)
} else { } else {
// Update height // Update height
@ -307,11 +309,37 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA
k.RemoveValidator(ctx, validator.Operator) k.RemoveValidator(ctx, validator.Operator)
} }
return return amount, nil
} }
//______________________________________________________________________________________________________ //______________________________________________________________________________________________________
// get info for begin functions: MinTime and CreationHeight
func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sdk.ValAddress) (
minTime time.Time, height int64, completeNow bool) {
validator, found := k.GetValidator(ctx, valSrcAddr)
switch {
case !found || validator.Status == sdk.Bonded:
// the longest wait - just unbonding period from now
minTime = ctx.BlockHeader().Time.Add(params.UnbondingTime)
height = ctx.BlockHeader().Height
return minTime, height, false
case validator.IsUnbonded(ctx):
return minTime, height, true
case validator.Status == sdk.Unbonding:
minTime = validator.UnbondingMinTime
height = validator.UnbondingHeight
return minTime, height, false
default:
panic("unknown validator status")
}
}
// complete unbonding an unbonding record // complete unbonding an unbonding record
func (k Keeper) BeginUnbonding(ctx sdk.Context, func (k Keeper) BeginUnbonding(ctx sdk.Context,
delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) sdk.Error { delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) sdk.Error {
@ -329,12 +357,22 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context,
// create the unbonding delegation // create the unbonding delegation
params := k.GetParams(ctx) params := k.GetParams(ctx)
minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) minTime, height, completeNow := k.getBeginInfo(ctx, params, valAddr)
balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()}
// no need to create the ubd object just complete now
if completeNow {
_, _, err := k.coinKeeper.AddCoins(ctx, delAddr, sdk.Coins{balance})
if err != nil {
return err
}
return nil
}
ubd := types.UnbondingDelegation{ ubd := types.UnbondingDelegation{
DelegatorAddr: delAddr, DelegatorAddr: delAddr,
ValidatorAddr: valAddr, ValidatorAddr: valAddr,
CreationHeight: height,
MinTime: minTime, MinTime: minTime,
Balance: balance, Balance: balance,
InitialBalance: balance, InitialBalance: balance,
@ -391,12 +429,17 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress,
} }
// create the unbonding delegation // create the unbonding delegation
minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) minTime, height, completeNow := k.getBeginInfo(ctx, params, valSrcAddr)
if completeNow { // no need to create the redelegation object
return nil
}
red := types.Redelegation{ red := types.Redelegation{
DelegatorAddr: delAddr, DelegatorAddr: delAddr,
ValidatorSrcAddr: valSrcAddr, ValidatorSrcAddr: valSrcAddr,
ValidatorDstAddr: valDstAddr, ValidatorDstAddr: valDstAddr,
CreationHeight: height,
MinTime: minTime, MinTime: minTime,
SharesDst: sharesCreated, SharesDst: sharesCreated,
SharesSrc: sharesAmount, SharesSrc: sharesAmount,

View File

@ -7,6 +7,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/cosmos/cosmos-sdk/x/stake/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -162,9 +163,7 @@ func TestUnbondDelegation(t *testing.T) {
} }
keeper.SetDelegation(ctx, delegation) keeper.SetDelegation(ctx, delegation)
var err error amount, err := keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDec(6))
var amount sdk.Dec
amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDec(6))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(6), amount.RoundInt64()) // shares to be added to an unbonding delegation / redelegation require.Equal(t, int64(6), amount.RoundInt64()) // shares to be added to an unbonding delegation / redelegation
@ -180,6 +179,190 @@ func TestUnbondDelegation(t *testing.T) {
require.Equal(t, int64(4), pool.BondedTokens.RoundInt64()) require.Equal(t, int64(4), pool.BondedTokens.RoundInt64())
} }
// test removing all self delegation from a validator which should
// shift it from the bonded to unbonded state
func TestUndelegateSelfDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = sdk.NewDec(20)
//create a validator with a self-delegation
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
selfDelegation := types.Delegation{
DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()),
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, selfDelegation)
// create a second delegation to this validator
validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
val0AccAddr := sdk.AccAddress(addrVals[0].Bytes())
err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10))
require.NoError(t, err)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, int64(10), validator.Tokens.RoundInt64())
require.Equal(t, sdk.Unbonding, validator.Status)
}
func TestUndelegateFromUnbondingValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = sdk.NewDec(20)
//create a validator with a self-delegation
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
selfDelegation := types.Delegation{
DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()),
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, selfDelegation)
// create a second delegation to this validator
validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
header := ctx.BlockHeader()
blockHeight := int64(10)
header.Height = blockHeight
blockTime := time.Unix(333, 0)
header.Time = blockTime
ctx = ctx.WithBlockHeader(header)
// unbond the all self-delegation to put validator in unbonding state
val0AccAddr := sdk.AccAddress(addrVals[0].Bytes())
err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10))
require.NoError(t, err)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, blockHeight, validator.UnbondingHeight)
params := keeper.GetParams(ctx)
require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime))
//change the context
header = ctx.BlockHeader()
blockHeight2 := int64(20)
header.Height = blockHeight2
blockTime2 := time.Unix(444, 0)
header.Time = blockTime2
ctx = ctx.WithBlockHeader(header)
// unbond some of the other delegation's shares
err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6))
require.NoError(t, err)
// retrieve the unbonding delegation
ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6)))
assert.Equal(t, blockHeight, ubd.CreationHeight)
assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime))
}
func TestUndelegateFromUnbondedValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = sdk.NewDec(20)
//create a validator with a self-delegation
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
val0AccAddr := sdk.AccAddress(addrVals[0].Bytes())
selfDelegation := types.Delegation{
DelegatorAddr: val0AccAddr,
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, selfDelegation)
// create a second delegation to this validator
validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
header := ctx.BlockHeader()
blockHeight := int64(10)
header.Height = blockHeight
blockTime := time.Unix(333, 0)
header.Time = blockTime
ctx = ctx.WithBlockHeader(header)
// unbond the all self-delegation to put validator in unbonding state
err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10))
require.NoError(t, err)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, blockHeight, validator.UnbondingHeight)
params := keeper.GetParams(ctx)
require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime))
// change the context to one which makes the validator considered unbonded
header = ctx.BlockHeader()
blockHeight2 := int64(20)
header.Height = blockHeight2
blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime)
header.Time = blockTime2
ctx = ctx.WithBlockHeader(header)
// unbond some of the other delegation's shares
err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6))
require.NoError(t, err)
// no ubd should have been found, coins should have been returned direcly to account
ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.False(t, found, "%v", ubd)
}
// Make sure that that the retrieving the delegations doesn't affect the state // Make sure that that the retrieving the delegations doesn't affect the state
func TestGetRedelegationsFromValidator(t *testing.T) { func TestGetRedelegationsFromValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0) ctx, _, keeper := CreateTestInput(t, false, 0)
@ -259,3 +442,206 @@ func TestRedelegation(t *testing.T) {
_, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.False(t, found) require.False(t, found)
} }
func TestRedelegateSelfDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = sdk.NewDec(30)
//create a validator with a self-delegation
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
val0AccAddr := sdk.AccAddress(addrVals[0].Bytes())
selfDelegation := types.Delegation{
DelegatorAddr: val0AccAddr,
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, selfDelegation)
// create a second validator
validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{})
validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator2 = keeper.UpdateValidator(ctx, validator2)
// create a second delegation to this validator
validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDec(10))
require.NoError(t, err)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, int64(10), validator.Tokens.RoundInt64())
require.Equal(t, sdk.Unbonding, validator.Status)
}
func TestRedelegateFromUnbondingValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = sdk.NewDec(30)
//create a validator with a self-delegation
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
val0AccAddr := sdk.AccAddress(addrVals[0].Bytes())
selfDelegation := types.Delegation{
DelegatorAddr: val0AccAddr,
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, selfDelegation)
// create a second delegation to this validator
validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
// create a second validator
validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{})
validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator2 = keeper.UpdateValidator(ctx, validator2)
header := ctx.BlockHeader()
blockHeight := int64(10)
header.Height = blockHeight
blockTime := time.Unix(333, 0)
header.Time = blockTime
ctx = ctx.WithBlockHeader(header)
// unbond the all self-delegation to put validator in unbonding state
err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10))
require.NoError(t, err)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, blockHeight, validator.UnbondingHeight)
params := keeper.GetParams(ctx)
require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime))
//change the context
header = ctx.BlockHeader()
blockHeight2 := int64(20)
header.Height = blockHeight2
blockTime2 := time.Unix(444, 0)
header.Time = blockTime2
ctx = ctx.WithBlockHeader(header)
// unbond some of the other delegation's shares
err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6))
require.NoError(t, err)
// retrieve the unbonding delegation
ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found)
require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6)))
assert.Equal(t, blockHeight, ubd.CreationHeight)
assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime))
}
func TestRedelegateFromUnbondedValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = sdk.NewDec(30)
//create a validator with a self-delegation
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
val0AccAddr := sdk.AccAddress(addrVals[0].Bytes())
selfDelegation := types.Delegation{
DelegatorAddr: val0AccAddr,
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, selfDelegation)
// create a second delegation to this validator
validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
// create a second validator
validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{})
validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, int64(10), issuedShares.RoundInt64())
keeper.SetPool(ctx, pool)
validator2 = keeper.UpdateValidator(ctx, validator2)
header := ctx.BlockHeader()
blockHeight := int64(10)
header.Height = blockHeight
blockTime := time.Unix(333, 0)
header.Time = blockTime
ctx = ctx.WithBlockHeader(header)
// unbond the all self-delegation to put validator in unbonding state
err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10))
require.NoError(t, err)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, blockHeight, validator.UnbondingHeight)
params := keeper.GetParams(ctx)
require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime))
// change the context to one which makes the validator considered unbonded
header = ctx.BlockHeader()
blockHeight2 := int64(20)
header.Height = blockHeight2
blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime)
header.Time = blockTime2
ctx = ctx.WithBlockHeader(header)
// unbond some of the other delegation's shares
err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6))
require.NoError(t, err)
// no ubd should have been found, coins should have been returned direcly to account
ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.False(t, found, "%v", ubd)
}

View File

@ -10,9 +10,10 @@ import (
// keeper of the stake store // keeper of the stake store
type Keeper struct { type Keeper struct {
storeKey sdk.StoreKey storeKey sdk.StoreKey
cdc *wire.Codec cdc *wire.Codec
coinKeeper bank.Keeper coinKeeper bank.Keeper
validatorHooks sdk.ValidatorHooks
// codespace // codespace
codespace sdk.CodespaceType codespace sdk.CodespaceType
@ -20,14 +21,24 @@ type Keeper struct {
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
keeper := Keeper{ keeper := Keeper{
storeKey: key, storeKey: key,
cdc: cdc, cdc: cdc,
coinKeeper: ck, coinKeeper: ck,
codespace: codespace, validatorHooks: nil,
codespace: codespace,
} }
return keeper return keeper
} }
// Set the validator hooks
func (k Keeper) WithValidatorHooks(v sdk.ValidatorHooks) Keeper {
if k.validatorHooks != nil {
panic("cannot set validator hooks twice")
}
k.validatorHooks = v
return k
}
//_________________________________________________________________________ //_________________________________________________________________________
// return the codespace // return the codespace

View File

@ -91,6 +91,7 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.
if !ok { if !ok {
return nil return nil
} }
return bond return bond
} }

View File

@ -18,8 +18,12 @@ import (
// Infraction committed equal to or less than an unbonding period in the past, // Infraction committed equal to or less than an unbonding period in the past,
// so all unbonding delegations and redelegations from that height are stored // so all unbonding delegations and redelegations from that height are stored
// CONTRACT: // CONTRACT:
// Slash will not slash unbonded validators (for the above reason)
// CONTRACT:
// Infraction committed at the current height or at a past height, // Infraction committed at the current height or at a past height,
// not at a height in the future // not at a height in the future
//
// nolint: gocyclo
func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Dec) { func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Dec) {
logger := ctx.Logger().With("module", "x/stake") logger := ctx.Logger().With("module", "x/stake")
@ -43,6 +47,12 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
pubkey.Address())) pubkey.Address()))
return return
} }
// should not be slashing unbonded
if validator.IsUnbonded(ctx) {
panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator))
}
operatorAddress := validator.GetOperator() operatorAddress := validator.GetOperator()
// Track remaining slash amount for the validator // Track remaining slash amount for the validator
@ -91,25 +101,24 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
// Cannot decrease balance below zero // Cannot decrease balance below zero
tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens) tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens)
// Get the current pool // burn validator's tokens
pool := k.GetPool(ctx) pool := k.GetPool(ctx)
// remove tokens from the validator
validator, pool = validator.RemoveTokens(pool, tokensToBurn) validator, pool = validator.RemoveTokens(pool, tokensToBurn)
// burn tokens
pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn)
// update the pool
k.SetPool(ctx, pool) k.SetPool(ctx, pool)
// update the validator, possibly kicking it out // update the validator, possibly kicking it out
validator = k.UpdateValidator(ctx, validator) validator = k.UpdateValidator(ctx, validator)
// remove validator if it has been reduced to zero shares
// remove validator if it has no more tokens
if validator.Tokens.IsZero() { if validator.Tokens.IsZero() {
k.RemoveValidator(ctx, validator.Operator) k.RemoveValidator(ctx, validator.Operator)
} }
// Log that a slash occurred! // Log that a slash occurred!
logger.Info(fmt.Sprintf( logger.Info(fmt.Sprintf(
"Validator %s slashed by slashFactor %v, burned %v tokens", "Validator %s slashed by slashFactor %s, burned %v tokens",
pubkey.Address(), slashFactor, tokensToBurn)) pubkey.Address(), slashFactor.String(), tokensToBurn))
// TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803
return return
@ -134,12 +143,12 @@ func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) {
} }
// set the jailed flag on a validator // set the jailed flag on a validator
func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, jailed bool) { func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, isJailed bool) {
validator, found := k.GetValidatorByPubKey(ctx, pubkey) validator, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found { if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, jailed)) panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed))
} }
validator.Jailed = jailed validator.Jailed = isJailed
k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it
return return
} }
@ -179,6 +188,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount) unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount)
k.SetUnbondingDelegation(ctx, unbondingDelegation) k.SetUnbondingDelegation(ctx, unbondingDelegation)
pool := k.GetPool(ctx) pool := k.GetPool(ctx)
// Burn loose tokens // Burn loose tokens
// Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760 // Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760
pool.LooseTokens = pool.LooseTokens.Sub(slashAmount) pool.LooseTokens = pool.LooseTokens.Sub(slashAmount)
@ -239,6 +249,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re
if err != nil { if err != nil {
panic(fmt.Errorf("error unbonding delegator: %v", err)) panic(fmt.Errorf("error unbonding delegator: %v", err))
} }
// Burn loose tokens // Burn loose tokens
pool := k.GetPool(ctx) pool := k.GetPool(ctx)
pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn)

View File

@ -11,8 +11,8 @@ import (
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
) )
// setup helper function // TODO integrate with test_common.go helper (CreateTestInput)
// creates two validators // setup helper function - creates two validators
func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) {
// setup // setup
ctx, _, keeper := CreateTestInput(t, false, amt) ctx, _, keeper := CreateTestInput(t, false, amt)
@ -34,8 +34,11 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) {
return ctx, keeper, params return ctx, keeper, params
} }
//_________________________________________________________________________________
// tests Jail, Unjail // tests Jail, Unjail
func TestRevocation(t *testing.T) { func TestRevocation(t *testing.T) {
// setup // setup
ctx, keeper, _ := setupHelper(t, 10) ctx, keeper, _ := setupHelper(t, 10)
addr := addrVals[0] addr := addrVals[0]
@ -57,7 +60,6 @@ func TestRevocation(t *testing.T) {
val, found = keeper.GetValidator(ctx, addr) val, found = keeper.GetValidator(ctx, addr)
require.True(t, found) require.True(t, found)
require.False(t, val.GetJailed()) require.False(t, val.GetJailed())
} }
// tests slashUnbondingDelegation // tests slashUnbondingDelegation
@ -95,8 +97,10 @@ func TestSlashUnbondingDelegation(t *testing.T) {
require.Equal(t, int64(5), slashAmount.RoundInt64()) require.Equal(t, int64(5), slashAmount.RoundInt64())
ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found) require.True(t, found)
// initialbalance unchanged // initialbalance unchanged
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.InitialBalance) require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.InitialBalance)
// balance decreased // balance decreased
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Balance) require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Balance)
newPool := keeper.GetPool(ctx) newPool := keeper.GetPool(ctx)
@ -155,14 +159,18 @@ func TestSlashRedelegation(t *testing.T) {
require.Equal(t, int64(5), slashAmount.RoundInt64()) require.Equal(t, int64(5), slashAmount.RoundInt64())
rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
require.True(t, found) require.True(t, found)
// initialbalance unchanged // initialbalance unchanged
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.InitialBalance) require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.InitialBalance)
// balance decreased // balance decreased
require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Balance) require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Balance)
// shares decreased // shares decreased
del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1])
require.True(t, found) require.True(t, found)
require.Equal(t, int64(5), del.Shares.RoundInt64()) require.Equal(t, int64(5), del.Shares.RoundInt64())
// pool bonded tokens decreased // pool bonded tokens decreased
newPool := keeper.GetPool(ctx) newPool := keeper.GetPool(ctx)
require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64())
@ -177,7 +185,7 @@ func TestSlashAtFutureHeight(t *testing.T) {
} }
// tests Slash at the current height // tests Slash at the current height
func TestSlashAtCurrentHeight(t *testing.T) { func TestSlashValidatorAtCurrentHeight(t *testing.T) {
ctx, keeper, _ := setupHelper(t, 10) ctx, keeper, _ := setupHelper(t, 10)
pk := PKs[0] pk := PKs[0]
fraction := sdk.NewDecWithPrec(5, 1) fraction := sdk.NewDecWithPrec(5, 1)

View File

@ -2,6 +2,7 @@ package keeper
import ( import (
"bytes" "bytes"
"container/list"
"fmt" "fmt"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
@ -11,6 +12,19 @@ import (
"github.com/cosmos/cosmos-sdk/x/stake/types" "github.com/cosmos/cosmos-sdk/x/stake/types"
) )
// Cache the amino decoding of validators, as it can be the case that repeated slashing calls
// cause many calls to GetValidator, which were shown to throttle the state machine in our
// simulation. Note this is quite biased though, as the simulator does more slashes than a
// live chain should, however we require the slashing to be fast as noone pays gas for it.
type cachedValidator struct {
val types.Validator
marshalled string // marshalled amino bytes for the validator object (not operator address)
}
// validatorCache-key: validator amino bytes
var validatorCache = make(map[string]cachedValidator, 500)
var validatorCacheList = list.New()
// get a single validator // get a single validator
func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator types.Validator, found bool) { func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator types.Validator, found bool) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -18,6 +32,28 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty
if value == nil { if value == nil {
return validator, false return validator, false
} }
// If these amino encoded bytes are in the cache, return the cached validator
strValue := string(value)
if val, ok := validatorCache[strValue]; ok {
valToReturn := val.val
// Doesn't mutate the cache's value
valToReturn.Operator = addr
return valToReturn, true
}
// amino bytes weren't found in cache, so amino unmarshal and add it to the cache
validator = types.MustUnmarshalValidator(k.cdc, addr, value)
cachedVal := cachedValidator{validator, strValue}
validatorCache[strValue] = cachedValidator{validator, strValue}
validatorCacheList.PushBack(cachedVal)
// if the cache is too big, pop off the last element from it
if validatorCacheList.Len() > 500 {
valToRemove := validatorCacheList.Remove(validatorCacheList.Front()).(cachedValidator)
delete(validatorCache, valToRemove.marshalled)
}
validator = types.MustUnmarshalValidator(k.cdc, addr, value) validator = types.MustUnmarshalValidator(k.cdc, addr, value)
return validator, true return validator, true
} }
@ -212,6 +248,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
cliffPower := k.GetCliffValidatorPower(ctx) cliffPower := k.GetCliffValidatorPower(ctx)
switch { switch {
// if the validator is already bonded and the power is increasing, we need // if the validator is already bonded and the power is increasing, we need
// perform the following: // perform the following:
// a) update Tendermint // a) update Tendermint
@ -240,10 +277,11 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower
// skip to completion // skip to completion
// default case - validator was either: default:
// default case - validator was either:
// a) not-bonded and now has power-rank greater than cliff validator // a) not-bonded and now has power-rank greater than cliff validator
// b) bonded and now has decreased in power // b) bonded and now has decreased in power
default:
// update the validator set for this validator // update the validator set for this validator
updatedVal, updated := k.UpdateBondedValidators(ctx, validator) updatedVal, updated := k.UpdateBondedValidators(ctx, validator)
if updated { if updated {
@ -307,10 +345,13 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato
newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool) newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool)
if bytes.Equal(affectedVal.Operator, newCliffVal.Operator) { if bytes.Equal(affectedVal.Operator, newCliffVal.Operator) {
// The affected validator remains the cliff validator, however, since // The affected validator remains the cliff validator, however, since
// the store does not contain the new power, update the new power rank. // the store does not contain the new power, update the new power rank.
store.Set(ValidatorPowerCliffKey, affectedValRank) store.Set(ValidatorPowerCliffKey, affectedValRank)
} else if bytes.Compare(affectedValRank, newCliffValRank) > 0 { } else if bytes.Compare(affectedValRank, newCliffValRank) > 0 {
// The affected validator no longer remains the cliff validator as it's // The affected validator no longer remains the cliff validator as it's
// power is greater than the new cliff validator. // power is greater than the new cliff validator.
k.setCliffValidator(ctx, newCliffVal, pool) k.setCliffValidator(ctx, newCliffVal, pool)
@ -321,7 +362,7 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato
func (k Keeper) updateForJailing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator { func (k Keeper) updateForJailing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator {
if newValidator.Jailed && oldFound && oldValidator.Status == sdk.Bonded { if newValidator.Jailed && oldFound && oldValidator.Status == sdk.Bonded {
newValidator = k.unbondValidator(ctx, newValidator) newValidator = k.beginUnbondingValidator(ctx, newValidator)
// need to also clear the cliff validator spot because the jail has // need to also clear the cliff validator spot because the jail has
// opened up a new spot which will be filled when // opened up a new spot which will be filled when
@ -416,20 +457,20 @@ func (k Keeper) UpdateBondedValidators(
} }
} }
// increment bondedValidatorsCount / get the validator to bond // if we've reached jailed validators no further bonded validators exist
if !validator.Jailed { if validator.Jailed {
if validator.Status != sdk.Bonded {
validatorToBond = validator
if newValidatorBonded {
panic("already decided to bond a validator, can't bond another!")
}
newValidatorBonded = true
}
} else {
// TODO: document why we must break here.
break break
} }
// increment bondedValidatorsCount / get the validator to bond
if validator.Status != sdk.Bonded {
validatorToBond = validator
if newValidatorBonded {
panic("already decided to bond a validator, can't bond another!")
}
newValidatorBonded = true
}
// increment the total number of bonded validators and potentially mark // increment the total number of bonded validators and potentially mark
// the validator to bond // the validator to bond
if validator.Status != sdk.Bonded { if validator.Status != sdk.Bonded {
@ -464,13 +505,15 @@ func (k Keeper) UpdateBondedValidators(
} }
if bytes.Equal(validatorToBond.Operator, affectedValidator.Operator) { if bytes.Equal(validatorToBond.Operator, affectedValidator.Operator) {
// unbond the old cliff validator iff the affected validator was
// newly bonded and has greater power // begin unbonding the old cliff validator iff the affected
k.unbondValidator(ctx, oldCliffVal) // validator was newly bonded and has greater power
k.beginUnbondingValidator(ctx, oldCliffVal)
} else { } else {
// otherwise unbond the affected validator, which must have been
// kicked out // otherwise begin unbonding the affected validator, which must
affectedValidator = k.unbondValidator(ctx, affectedValidator) // have been kicked out
affectedValidator = k.beginUnbondingValidator(ctx, affectedValidator)
} }
} }
@ -563,25 +606,30 @@ func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) {
if !found { if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
} }
k.unbondValidator(ctx, validator) k.beginUnbondingValidator(ctx, validator)
} }
} }
// perform all the store operations for when a validator status becomes unbonded // perform all the store operations for when a validator status becomes unbonded
func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator { func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validator) types.Validator {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx) pool := k.GetPool(ctx)
params := k.GetParams(ctx)
// sanity check // sanity check
if validator.Status == sdk.Unbonded { if validator.Status == sdk.Unbonded ||
panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator)) validator.Status == sdk.Unbonding {
panic(fmt.Sprintf("should not already be unbonded or unbonding, validator: %v\n", validator))
} }
// set the status // set the status
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) validator, pool = validator.UpdateStatus(pool, sdk.Unbonding)
k.SetPool(ctx, pool) k.SetPool(ctx, pool)
validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime)
validator.UnbondingHeight = ctx.BlockHeader().Height
// save the now unbonded validator record // save the now unbonded validator record
k.SetValidator(ctx, validator) k.SetValidator(ctx, validator)
@ -591,6 +639,13 @@ func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) type
// also remove from the Bonded types.Validators Store // also remove from the Bonded types.Validators Store
store.Delete(GetValidatorsBondedIndexKey(validator.Operator)) store.Delete(GetValidatorsBondedIndexKey(validator.Operator))
// call the unbond hook if present
if k.validatorHooks != nil {
k.validatorHooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress())
}
// return updated validator
return validator return validator
} }
@ -617,6 +672,12 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.
bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator()) bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator())
store.Set(GetTendermintUpdatesKey(validator.Operator), bzABCI) store.Set(GetTendermintUpdatesKey(validator.Operator), bzABCI)
// call the bond hook if present
if k.validatorHooks != nil {
k.validatorHooks.OnValidatorBonded(ctx, validator.ConsAddress())
}
// return updated validator
return validator return validator
} }

View File

@ -137,7 +137,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) {
expectedValStatus := map[int]sdk.BondStatus{ expectedValStatus := map[int]sdk.BondStatus{
9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded, 9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded,
0: sdk.Unbonded, 1: sdk.Unbonded, 2: sdk.Unbonded, 3: sdk.Unbonded, 6: sdk.Unbonded, 0: sdk.Unbonding, 1: sdk.Unbonding, 2: sdk.Unbonding, 3: sdk.Unbonding, 6: sdk.Unbonding,
} }
// require all the validators have their respective statuses // require all the validators have their respective statuses
@ -145,9 +145,11 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) {
valAddr := validators[valIdx].Operator valAddr := validators[valIdx].Operator
val, _ := keeper.GetValidator(ctx, valAddr) val, _ := keeper.GetValidator(ctx, valAddr)
require.Equal( assert.Equal(
t, val.GetStatus(), status, t, status, val.GetStatus(),
fmt.Sprintf("expected validator to have status: %s", sdk.BondStatusToString(status))) fmt.Sprintf("expected validator at index %v to have status: %s",
valIdx,
sdk.BondStatusToString(status)))
} }
} }
@ -610,8 +612,8 @@ func TestFullValidatorSetPowerChange(t *testing.T) {
validators[i], found = keeper.GetValidator(ctx, validators[i].Operator) validators[i], found = keeper.GetValidator(ctx, validators[i].Operator)
require.True(t, found) require.True(t, found)
} }
assert.Equal(t, sdk.Unbonded, validators[0].Status) assert.Equal(t, sdk.Unbonding, validators[0].Status)
assert.Equal(t, sdk.Unbonded, validators[1].Status) assert.Equal(t, sdk.Unbonding, validators[1].Status)
assert.Equal(t, sdk.Bonded, validators[2].Status) assert.Equal(t, sdk.Bonded, validators[2].Status)
assert.Equal(t, sdk.Bonded, validators[3].Status) assert.Equal(t, sdk.Bonded, validators[3].Status)
assert.Equal(t, sdk.Unbonded, validators[4].Status) assert.Equal(t, sdk.Unbonded, validators[4].Status)

View File

@ -45,6 +45,7 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim
case sdk.Bonded: case sdk.Bonded:
bonded = bonded.Add(validator.GetPower()) bonded = bonded.Add(validator.GetPower())
case sdk.Unbonding: case sdk.Unbonding:
loose = loose.Add(validator.GetTokens().RoundInt())
case sdk.Unbonded: case sdk.Unbonded:
loose = loose.Add(validator.GetTokens().RoundInt()) loose = loose.Add(validator.GetTokens().RoundInt())
} }

View File

@ -5,8 +5,6 @@ import (
"math/rand" "math/rand"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
@ -19,7 +17,9 @@ import (
// SimulateMsgCreateValidator // SimulateMsgCreateValidator
func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
denom := k.GetParams(ctx).BondDenom denom := k.GetParams(ctx).BondDenom
description := stake.Description{ description := stake.Description{
Moniker: simulation.RandStringOfLength(r, 10), Moniker: simulation.RandStringOfLength(r, 10),
@ -41,9 +41,11 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation
PubKey: pubkey, PubKey: pubkey,
Delegation: sdk.NewCoin(denom, amount), Delegation: sdk.NewCoin(denom, amount),
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }
@ -56,7 +58,9 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation
// SimulateMsgEditValidator // SimulateMsgEditValidator
func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
description := stake.Description{ description := stake.Description{
Moniker: simulation.RandStringOfLength(r, 10), Moniker: simulation.RandStringOfLength(r, 10),
Identity: simulation.RandStringOfLength(r, 10), Identity: simulation.RandStringOfLength(r, 10),
@ -70,9 +74,11 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation {
Description: description, Description: description,
ValidatorAddr: address, ValidatorAddr: address,
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }
@ -84,7 +90,9 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation {
// SimulateMsgDelegate // SimulateMsgDelegate
func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
denom := k.GetParams(ctx).BondDenom denom := k.GetParams(ctx).BondDenom
validatorKey := simulation.RandomKey(r, keys) validatorKey := simulation.RandomKey(r, keys)
validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address())
@ -102,9 +110,11 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat
ValidatorAddr: validatorAddress, ValidatorAddr: validatorAddress,
Delegation: sdk.NewCoin(denom, amount), Delegation: sdk.NewCoin(denom, amount),
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }
@ -116,7 +126,9 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat
// SimulateMsgBeginUnbonding // SimulateMsgBeginUnbonding
func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation { func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
denom := k.GetParams(ctx).BondDenom denom := k.GetParams(ctx).BondDenom
validatorKey := simulation.RandomKey(r, keys) validatorKey := simulation.RandomKey(r, keys)
validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address())
@ -134,9 +146,11 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.
ValidatorAddr: validatorAddress, ValidatorAddr: validatorAddress,
SharesAmount: sdk.NewDecFromInt(amount), SharesAmount: sdk.NewDecFromInt(amount),
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }
@ -148,7 +162,9 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.
// SimulateMsgCompleteUnbonding // SimulateMsgCompleteUnbonding
func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
validatorKey := simulation.RandomKey(r, keys) validatorKey := simulation.RandomKey(r, keys)
validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address())
delegatorKey := simulation.RandomKey(r, keys) delegatorKey := simulation.RandomKey(r, keys)
@ -157,9 +173,11 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation {
DelegatorAddr: delegatorAddress, DelegatorAddr: delegatorAddress,
ValidatorAddr: validatorAddress, ValidatorAddr: validatorAddress,
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }
@ -171,7 +189,9 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation {
// SimulateMsgBeginRedelegate // SimulateMsgBeginRedelegate
func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
denom := k.GetParams(ctx).BondDenom denom := k.GetParams(ctx).BondDenom
sourceValidatorKey := simulation.RandomKey(r, keys) sourceValidatorKey := simulation.RandomKey(r, keys)
sourceValidatorAddress := sdk.ValAddress(sourceValidatorKey.PubKey().Address()) sourceValidatorAddress := sdk.ValAddress(sourceValidatorKey.PubKey().Address())
@ -193,9 +213,11 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation
ValidatorDstAddr: destValidatorAddress, ValidatorDstAddr: destValidatorAddress,
SharesAmount: sdk.NewDecFromInt(amount), SharesAmount: sdk.NewDecFromInt(amount),
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }
@ -207,7 +229,9 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation
// SimulateMsgCompleteRedelegate // SimulateMsgCompleteRedelegate
func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { handler := stake.NewHandler(k)
return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) {
validatorSrcKey := simulation.RandomKey(r, keys) validatorSrcKey := simulation.RandomKey(r, keys)
validatorSrcAddress := sdk.ValAddress(validatorSrcKey.PubKey().Address()) validatorSrcAddress := sdk.ValAddress(validatorSrcKey.PubKey().Address())
validatorDstKey := simulation.RandomKey(r, keys) validatorDstKey := simulation.RandomKey(r, keys)
@ -219,9 +243,11 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation {
ValidatorSrcAddr: validatorSrcAddress, ValidatorSrcAddr: validatorSrcAddress,
ValidatorDstAddr: validatorDstAddress, ValidatorDstAddr: validatorDstAddress,
} }
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) if msg.ValidateBasic() != nil {
tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log)
}
ctx, write := ctx.CacheContext() ctx, write := ctx.CacheContext()
result := stake.NewHandler(k)(ctx, msg) result := handler(ctx, msg)
if result.IsOK() { if result.IsOK() {
write() write()
} }

View File

@ -3,6 +3,7 @@ package types
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"time"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
@ -31,15 +32,14 @@ type Validator struct {
Description Description `json:"description"` // description terms for the validator Description Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding
UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding
Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
LastBondedTokens sdk.Dec `json:"prev_bonded_tokens"` // Previous bonded tokens held
} }
// NewValidator - initialize a new validator // NewValidator - initialize a new validator
@ -54,12 +54,12 @@ func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Des
Description: description, Description: description,
BondHeight: int64(0), BondHeight: int64(0),
BondIntraTxCounter: int16(0), BondIntraTxCounter: int16(0),
ProposerRewardPool: sdk.Coins{}, UnbondingHeight: int64(0),
UnbondingMinTime: time.Unix(0, 0),
Commission: sdk.ZeroDec(), Commission: sdk.ZeroDec(),
CommissionMax: sdk.ZeroDec(), CommissionMax: sdk.ZeroDec(),
CommissionChangeRate: sdk.ZeroDec(), CommissionChangeRate: sdk.ZeroDec(),
CommissionChangeToday: sdk.ZeroDec(), CommissionChangeToday: sdk.ZeroDec(),
LastBondedTokens: sdk.ZeroDec(),
} }
} }
@ -73,12 +73,12 @@ type validatorValue struct {
Description Description Description Description
BondHeight int64 BondHeight int64
BondIntraTxCounter int16 BondIntraTxCounter int16
ProposerRewardPool sdk.Coins UnbondingHeight int64
UnbondingMinTime time.Time
Commission sdk.Dec Commission sdk.Dec
CommissionMax sdk.Dec CommissionMax sdk.Dec
CommissionChangeRate sdk.Dec CommissionChangeRate sdk.Dec
CommissionChangeToday sdk.Dec CommissionChangeToday sdk.Dec
LastBondedTokens sdk.Dec
} }
// return the redelegation without fields contained within the key for the store // return the redelegation without fields contained within the key for the store
@ -92,12 +92,12 @@ func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte {
Description: validator.Description, Description: validator.Description,
BondHeight: validator.BondHeight, BondHeight: validator.BondHeight,
BondIntraTxCounter: validator.BondIntraTxCounter, BondIntraTxCounter: validator.BondIntraTxCounter,
ProposerRewardPool: validator.ProposerRewardPool, UnbondingHeight: validator.UnbondingHeight,
UnbondingMinTime: validator.UnbondingMinTime,
Commission: validator.Commission, Commission: validator.Commission,
CommissionMax: validator.CommissionMax, CommissionMax: validator.CommissionMax,
CommissionChangeRate: validator.CommissionChangeRate, CommissionChangeRate: validator.CommissionChangeRate,
CommissionChangeToday: validator.CommissionChangeToday, CommissionChangeToday: validator.CommissionChangeToday,
LastBondedTokens: validator.LastBondedTokens,
} }
return cdc.MustMarshalBinary(val) return cdc.MustMarshalBinary(val)
} }
@ -108,7 +108,6 @@ func MustUnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) Validat
if err != nil { if err != nil {
panic(err) panic(err)
} }
return validator return validator
} }
@ -134,12 +133,12 @@ func UnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) (validator
Description: storeValue.Description, Description: storeValue.Description,
BondHeight: storeValue.BondHeight, BondHeight: storeValue.BondHeight,
BondIntraTxCounter: storeValue.BondIntraTxCounter, BondIntraTxCounter: storeValue.BondIntraTxCounter,
ProposerRewardPool: storeValue.ProposerRewardPool, UnbondingHeight: storeValue.UnbondingHeight,
UnbondingMinTime: storeValue.UnbondingMinTime,
Commission: storeValue.Commission, Commission: storeValue.Commission,
CommissionMax: storeValue.CommissionMax, CommissionMax: storeValue.CommissionMax,
CommissionChangeRate: storeValue.CommissionChangeRate, CommissionChangeRate: storeValue.CommissionChangeRate,
CommissionChangeToday: storeValue.CommissionChangeToday, CommissionChangeToday: storeValue.CommissionChangeToday,
LastBondedTokens: storeValue.LastBondedTokens,
}, nil }, nil
} }
@ -161,12 +160,12 @@ func (v Validator) HumanReadableString() (string, error) {
resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String()) resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String())
resp += fmt.Sprintf("Description: %s\n", v.Description) resp += fmt.Sprintf("Description: %s\n", v.Description)
resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight)
resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) resp += fmt.Sprintf("Unbonding Height: %d\n", v.UnbondingHeight)
resp += fmt.Sprintf("Minimum Unbonding Time: %v\n", v.UnbondingMinTime)
resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) resp += fmt.Sprintf("Commission: %s\n", v.Commission.String())
resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String())
resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String()) resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String())
resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String())
resp += fmt.Sprintf("Previous Bonded Tokens: %s\n", v.LastBondedTokens.String())
return resp, nil return resp, nil
} }
@ -186,15 +185,14 @@ type BechValidator struct {
Description Description `json:"description"` // description terms for the validator Description Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding
UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding
Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
LastBondedTokens sdk.Dec `json:"prev_bonded_shares"` // last bonded token amount
} }
// get the bech validator from the the regular validator // get the bech validator from the the regular validator
@ -216,14 +214,13 @@ func (v Validator) Bech32Validator() (BechValidator, error) {
Description: v.Description, Description: v.Description,
BondHeight: v.BondHeight, BondHeight: v.BondHeight,
BondIntraTxCounter: v.BondIntraTxCounter, BondIntraTxCounter: v.BondIntraTxCounter,
ProposerRewardPool: v.ProposerRewardPool, UnbondingHeight: v.UnbondingHeight,
UnbondingMinTime: v.UnbondingMinTime,
Commission: v.Commission, Commission: v.Commission,
CommissionMax: v.CommissionMax, CommissionMax: v.CommissionMax,
CommissionChangeRate: v.CommissionChangeRate, CommissionChangeRate: v.CommissionChangeRate,
CommissionChangeToday: v.CommissionChangeToday, CommissionChangeToday: v.CommissionChangeToday,
LastBondedTokens: v.LastBondedTokens,
}, nil }, nil
} }
@ -238,12 +235,15 @@ func (v Validator) Equal(c2 Validator) bool {
v.Tokens.Equal(c2.Tokens) && v.Tokens.Equal(c2.Tokens) &&
v.DelegatorShares.Equal(c2.DelegatorShares) && v.DelegatorShares.Equal(c2.DelegatorShares) &&
v.Description == c2.Description && v.Description == c2.Description &&
v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) &&
v.Commission.Equal(c2.Commission) && v.Commission.Equal(c2.Commission) &&
v.CommissionMax.Equal(c2.CommissionMax) && v.CommissionMax.Equal(c2.CommissionMax) &&
v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && v.CommissionChangeRate.Equal(c2.CommissionChangeRate) &&
v.CommissionChangeToday.Equal(c2.CommissionChangeToday) && v.CommissionChangeToday.Equal(c2.CommissionChangeToday)
v.LastBondedTokens.Equal(c2.LastBondedTokens) }
// return the TM validator address
func (v Validator) ConsAddress() sdk.ConsAddress {
return sdk.ConsAddress(v.PubKey.Address())
} }
// constant used in flags to indicate that description field should not be updated // constant used in flags to indicate that description field should not be updated
@ -423,6 +423,20 @@ func (v Validator) BondedTokens() sdk.Dec {
return sdk.ZeroDec() return sdk.ZeroDec()
} }
// Returns if the validator should be considered unbonded
func (v Validator) IsUnbonded(ctx sdk.Context) bool {
switch v.Status {
case sdk.Unbonded:
return true
case sdk.Unbonding:
ctxTime := ctx.BlockHeader().Time
if ctxTime.After(v.UnbondingMinTime) {
return true
}
}
return false
}
//______________________________________________________________________ //______________________________________________________________________
// ensure fulfills the sdk validator types // ensure fulfills the sdk validator types