Merge remote-tracking branch 'origin/develop' into rigel/fee-distribution
This commit is contained in:
commit
d7794b483d
|
@ -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",
|
||||||
|
|
11
Makefile
11
Makefile
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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().
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)}})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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...)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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...)...)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue