Merge PR #1783: Slashing, validator set, and governance simulation
This commit is contained in:
parent
c9358ec198
commit
3d50567034
|
@ -85,7 +85,7 @@ jobs:
|
|||
export PATH="$GOBIN:$PATH"
|
||||
make test_cli
|
||||
|
||||
test_sim:
|
||||
test_sim_modules:
|
||||
<<: *defaults
|
||||
parallelism: 1
|
||||
steps:
|
||||
|
@ -96,11 +96,26 @@ jobs:
|
|||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Test simulation
|
||||
name: Test individual module simulations
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
export GAIA_SIMULATION_SEED=1531897442166404087
|
||||
make test_sim
|
||||
make test_sim_modules
|
||||
|
||||
test_sim_gaia_fast:
|
||||
<<: *defaults
|
||||
parallelism: 1
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- restore_cache:
|
||||
key: v1-pkg-cache
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: Test full Gaia simulation
|
||||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make test_sim_gaia_fast
|
||||
|
||||
test_cover:
|
||||
<<: *defaults
|
||||
|
@ -118,7 +133,7 @@ jobs:
|
|||
command: |
|
||||
export PATH="$GOBIN:$PATH"
|
||||
make install
|
||||
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do
|
||||
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation' | circleci tests split --split-by=timings); do
|
||||
id=$(basename "$pkg")
|
||||
GOCACHE=off go test -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
||||
done
|
||||
|
@ -161,7 +176,10 @@ workflows:
|
|||
- test_cli:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_sim:
|
||||
- test_sim_modules:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_sim_gaia_fast:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- test_cover:
|
||||
|
|
|
@ -66,6 +66,9 @@ FEATURES
|
|||
- Modules specify invariants and operations, preferably in an x/[module]/simulation package
|
||||
- Modules can test random combinations of their own operations
|
||||
- Applications can integrate operations and invariants from modules together for an integrated simulation
|
||||
- Simulates Tendermint's algorithm for validator set updates
|
||||
- Simulates validator signing/downtime with a Markov chain, and occaisional double-signatures
|
||||
- Includes simulated operations & invariants for staking, slashing, governance, and bank modules
|
||||
- [store] \#1481 Add transient store
|
||||
- [baseapp] Initialize validator set on ResponseInitChain
|
||||
- [baseapp] added BaseApp.Seal - ability to seal baseapp parameters once they've been set
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6aabc1566d6351115d561d038da82a4c19b46c3b6e17f4a0a2fa60260663dc79"
|
||||
digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
pruneopts = "UT"
|
||||
|
@ -71,7 +71,7 @@
|
|||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fa30c0652956e159cdb97dcb2ef8b8db63ed668c02a5c3a40961c8f0641252fe"
|
||||
digest = "1:fdf5169073fb0ad6dc12a70c249145e30f4058647bea25f0abd48b6d9f228a11"
|
||||
name = "github.com/go-kit/kit"
|
||||
packages = [
|
||||
"log",
|
||||
|
@ -103,7 +103,7 @@
|
|||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:212285efb97b9ec2e20550d81f0446cb7897e57cbdfd7301b1363ab113d8be45"
|
||||
digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"gogoproto",
|
||||
|
@ -118,7 +118,7 @@
|
|||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cb22af0ed7c72d495d8be1106233ee553898950f15fd3f5404406d44c2e86888"
|
||||
digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
|
@ -165,12 +165,13 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8951fe6e358876736d8fa1f3992624fdbb2dec6bc49401c1381d1ef8abbb544f"
|
||||
digest = "1:a361611b8c8c75a1091f00027767f7779b29cb37c456a71b8f2604c88057ab40"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
|
@ -262,7 +263,7 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:98225904b7abff96c052b669b25788f18225a36673fba022fb93514bb9a2a64e"
|
||||
digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = [
|
||||
"prometheus",
|
||||
|
@ -273,7 +274,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:0f37e09b3e92aaeda5991581311f8dbf38944b36a3edec61cc2d1991f527554a"
|
||||
digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4"
|
||||
name = "github.com/prometheus/client_model"
|
||||
packages = ["go"]
|
||||
pruneopts = "UT"
|
||||
|
@ -281,7 +282,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:dad2e5a2153ee7a6c9ab8fc13673a16ee4fb64434a7da980965a3741b0c981a3"
|
||||
digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = [
|
||||
"expfmt",
|
||||
|
@ -293,7 +294,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a37c98f4b7a66bb5c539c0539f0915a74ef1c8e0b3b6f45735289d94cae92bfd"
|
||||
digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -312,7 +313,7 @@
|
|||
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:37ace7f35375adec11634126944bdc45a673415e2fcc07382d03b75ec76ea94c"
|
||||
digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -331,7 +332,7 @@
|
|||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:627ab2f549a6a55c44f46fa24a4307f4d0da81bfc7934ed0473bf38b24051d26"
|
||||
digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
|
@ -340,11 +341,11 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805"
|
||||
digest = "1:8a020f916b23ff574845789daee6818daf8d25a4852419aae3f0b12378ba432a"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
|
||||
revision = "14d3d4c518341bea657dd8a226f5121c0ff8c9f2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9"
|
||||
|
@ -363,7 +364,7 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:73697231b93fb74a73ebd8384b68b9a60c57ea6b13c56d2425414566a72c8e6d"
|
||||
digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
|
@ -375,7 +376,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:922191411ad8f61bcd8018ac127589bb489712c1d1a0ab2497aca4b16de417d2"
|
||||
digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
|
@ -392,11 +393,11 @@
|
|||
"leveldb/util",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||
revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:203b409c21115233a576f99e8f13d8e07ad82b25500491f7e1cca12588fb3232"
|
||||
digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722"
|
||||
name = "github.com/tendermint/ed25519"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -423,7 +424,7 @@
|
|||
version = "v0.9.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:df232b6f3c44554161093af004100f75e564b398ad3ff63ecbc297fe400dcfdb"
|
||||
digest = "1:26146cdb2811ce481e72138439b9b1aa17a64d54364f96bb92f97a9ef8ba4f01"
|
||||
name = "github.com/tendermint/tendermint"
|
||||
packages = [
|
||||
"abci/client",
|
||||
|
@ -498,7 +499,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3c3c47c1f7587c380afcc1d76385a4a03a2917b9ccc1ac50864d8f87e0264ada"
|
||||
digest = "1:7a71fffde456d746c52f9cd09c50b034533a3180fb1f6320abb149f2ccc579e5"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"blowfish",
|
||||
|
@ -520,7 +521,7 @@
|
|||
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:04dda8391c3e2397daf254ac68003f30141c069b228d06baec8324a5f81dc1e9"
|
||||
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
|
@ -537,17 +538,17 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a97b28c54844d6b9848a840ae83d4d263292e831e8c2a586116fcab5c7cfe5f2"
|
||||
digest = "1:a989b95f72fce8876213e8e20492525b4cf69a9e7fee7f1d9897983ee0d547e9"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"cpu",
|
||||
"unix",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "98c5dad5d1a0e8a73845ecc8897d0bd56586511d"
|
||||
revision = "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7509ba4347d1f8de6ae9be8818b0cd1abc3deeffe28aeaf4be6d4b6b5178d9ca"
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
|
@ -575,10 +576,10 @@
|
|||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||
revision = "d0a8f471bba2dbb160885b0000d814ee5d559bad"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4515e3030c440845b046354fd5d57671238428b820deebce2e9dabb5cd3c51ac"
|
||||
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
|
|
22
Makefile
22
Makefile
|
@ -130,15 +130,17 @@ test_unit:
|
|||
test_race:
|
||||
@go test -race $(PACKAGES_NOSIMULATION)
|
||||
|
||||
test_sim:
|
||||
@echo "Running individual module simulations."
|
||||
@go test $(PACKAGES_SIMTEST) -v
|
||||
@echo "Running full Gaia simulation. This may take several minutes."
|
||||
@echo "Pass the flag 'SimulationSeed' to run with a constant seed."
|
||||
@echo "Pass the flag 'SimulationNumKeys' to run with the specified number of keys."
|
||||
@echo "Pass the flag 'SimulationNumBlocks' to run with the specified number of blocks."
|
||||
@echo "Pass the flag 'SimulationBlockSize' to run with the specified block size (operations per block)."
|
||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationBlockSize=200 -v
|
||||
test_sim_modules:
|
||||
@echo "Running individual module simulations..."
|
||||
@go test $(PACKAGES_SIMTEST)
|
||||
|
||||
test_sim_gaia_fast:
|
||||
@echo "Running full Gaia simulation. This may take several minutes..."
|
||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -v -timeout 24h
|
||||
|
||||
test_sim_gaia_slow:
|
||||
@echo "Running full Gaia simulation. This may take several minutes..."
|
||||
@go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h
|
||||
|
||||
test_cover:
|
||||
@bash tests/test_cover.sh
|
||||
|
@ -204,4 +206,4 @@ localnet-stop:
|
|||
check_tools check_dev_tools get_tools get_dev_tools get_vendor_deps draw_deps test test_cli test_unit \
|
||||
test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update \
|
||||
build-linux build-docker-gaiadnode localnet-start localnet-stop \
|
||||
format check-ledger test_sim update_tools update_dev_tools
|
||||
format check-ledger test_sim_modules test_sim_gaia_fast test_sim_gaia_slow update_tools update_dev_tools
|
||||
|
|
|
@ -379,7 +379,7 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
|
|||
} else {
|
||||
// In the first block, app.deliverState.ctx will already be initialized
|
||||
// by InitChain. Context is now updated with Header information.
|
||||
app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header)
|
||||
app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header).WithBlockHeight(req.Header.Height)
|
||||
}
|
||||
|
||||
if app.beginBlocker != nil {
|
||||
|
|
|
@ -8,47 +8,68 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
||||
govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation"
|
||||
stake "github.com/cosmos/cosmos-sdk/x/stake"
|
||||
stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation"
|
||||
)
|
||||
|
||||
var (
|
||||
seed int64
|
||||
numKeys int
|
||||
numBlocks int
|
||||
blockSize int
|
||||
enabled bool
|
||||
verbose bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed")
|
||||
flag.IntVar(&numKeys, "SimulationNumKeys", 10, "Number of keys (accounts)")
|
||||
flag.IntVar(&numBlocks, "SimulationNumBlocks", 100, "Number of blocks")
|
||||
flag.IntVar(&blockSize, "SimulationBlockSize", 100, "Operations per block")
|
||||
flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "Number of blocks")
|
||||
flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block")
|
||||
flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation")
|
||||
flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output")
|
||||
}
|
||||
|
||||
func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage {
|
||||
func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage {
|
||||
var genesisAccounts []GenesisAccount
|
||||
|
||||
// Randomly generate some genesis accounts
|
||||
for _, addr := range accs {
|
||||
for _, acc := range accs {
|
||||
coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(100)}}
|
||||
genesisAccounts = append(genesisAccounts, GenesisAccount{
|
||||
Address: addr,
|
||||
Address: acc,
|
||||
Coins: coins,
|
||||
})
|
||||
}
|
||||
|
||||
// Default genesis state
|
||||
stakeGenesis := stake.DefaultGenesisState()
|
||||
stakeGenesis.Pool.LooseTokens = sdk.NewRat(1000)
|
||||
var validators []stake.Validator
|
||||
var delegations []stake.Delegation
|
||||
// XXX Try different numbers of initially bonded validators
|
||||
numInitiallyBonded := int64(50)
|
||||
for i := 0; i < int(numInitiallyBonded); i++ {
|
||||
validator := stake.NewValidator(accs[i], keys[i].PubKey(), stake.Description{})
|
||||
validator.Tokens = sdk.NewRat(100)
|
||||
validator.DelegatorShares = sdk.NewRat(100)
|
||||
delegation := stake.Delegation{accs[i], accs[i], sdk.NewRat(100), 0}
|
||||
validators = append(validators, validator)
|
||||
delegations = append(delegations, delegation)
|
||||
}
|
||||
stakeGenesis.Pool.LooseTokens = sdk.NewRat(int64(100*250) + (numInitiallyBonded * 100))
|
||||
stakeGenesis.Validators = validators
|
||||
stakeGenesis.Bonds = delegations
|
||||
// No inflation, for now
|
||||
stakeGenesis.Params.InflationMax = sdk.NewRat(0)
|
||||
stakeGenesis.Params.InflationMin = sdk.NewRat(0)
|
||||
genesis := GenesisState{
|
||||
Accounts: genesisAccounts,
|
||||
StakeData: stakeGenesis,
|
||||
|
@ -69,16 +90,31 @@ func TestFullGaiaSimulation(t *testing.T) {
|
|||
}
|
||||
|
||||
// Setup Gaia application
|
||||
logger := log.NewNopLogger()
|
||||
var logger log.Logger
|
||||
if verbose {
|
||||
logger = log.TestingLogger()
|
||||
} else {
|
||||
logger = log.NewNopLogger()
|
||||
}
|
||||
db := dbm.NewMemDB()
|
||||
app := NewGaiaApp(logger, db, nil)
|
||||
require.Equal(t, "GaiaApp", app.Name())
|
||||
|
||||
allInvariants := func(t *testing.T, baseapp *baseapp.BaseApp, log string) {
|
||||
banksim.NonnegativeBalanceInvariant(app.accountMapper)(t, baseapp, log)
|
||||
govsim.AllInvariants()(t, baseapp, log)
|
||||
stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper)(t, baseapp, log)
|
||||
slashingsim.AllInvariants()(t, baseapp, log)
|
||||
}
|
||||
|
||||
// Run randomized simulation
|
||||
simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
[]simulation.TestAndRunTx{
|
||||
banksim.TestAndRunSingleInputMsgSend(app.accountMapper),
|
||||
govsim.SimulateMsgSubmitProposal(app.govKeeper, app.stakeKeeper),
|
||||
govsim.SimulateMsgDeposit(app.govKeeper, app.stakeKeeper),
|
||||
govsim.SimulateMsgVote(app.govKeeper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgEditValidator(app.stakeKeeper),
|
||||
stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper),
|
||||
|
@ -86,15 +122,57 @@ func TestFullGaiaSimulation(t *testing.T) {
|
|||
stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper),
|
||||
stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper),
|
||||
slashingsim.SimulateMsgUnrevoke(app.slashingKeeper),
|
||||
},
|
||||
[]simulation.RandSetup{},
|
||||
[]simulation.Invariant{
|
||||
banksim.NonnegativeBalanceInvariant(app.accountMapper),
|
||||
stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper),
|
||||
allInvariants,
|
||||
},
|
||||
numKeys,
|
||||
numBlocks,
|
||||
blockSize,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// TODO: Make this not depend on Gaia or any of the modules,
|
||||
// and place it in random_simulation_test.go
|
||||
//
|
||||
// Test doesn't use `app.ExportAppStateAndValidators` as that panics with the following:
|
||||
// panic: Stored pool should not have been nil [recovered]
|
||||
// panic: Stored pool should not have been nil
|
||||
// Change to `app.ExportAppStateAndValidators` once it is fixed
|
||||
func TestAppStateDeterminism(t *testing.T) {
|
||||
numTimesToRun := 5
|
||||
appHashList := make([]json.RawMessage, numTimesToRun)
|
||||
|
||||
seed := rand.Int63()
|
||||
for i := 0; i < numTimesToRun; i++ {
|
||||
logger := log.NewNopLogger()
|
||||
db := dbm.NewMemDB()
|
||||
app := NewGaiaApp(logger, db, nil)
|
||||
|
||||
noOpInvariant := func(t *testing.T, baseapp *baseapp.BaseApp, log string) {}
|
||||
noOpTestAndRunTx := func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||
privKeys []crypto.PrivKey, log string, event func(string),
|
||||
) (action string, err sdk.Error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Run randomized simulation
|
||||
simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
[]simulation.TestAndRunTx{
|
||||
noOpTestAndRunTx,
|
||||
},
|
||||
[]simulation.RandSetup{},
|
||||
[]simulation.Invariant{noOpInvariant},
|
||||
0,
|
||||
10,
|
||||
)
|
||||
appHash := app.LastCommitID().Hash
|
||||
appHashList[i] = appHash
|
||||
}
|
||||
for i := 1; i < numTimesToRun; i++ {
|
||||
require.Equal(t, appHashList[0], appHashList[i])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
PKGS=$(go list ./... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test)
|
||||
PKGS=$(go list ./... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | grep -v '/simulation')
|
||||
|
||||
set -e
|
||||
echo "mode: atomic" > coverage.txt
|
||||
|
|
|
@ -52,6 +52,7 @@ type Validator interface {
|
|||
func ABCIValidator(v Validator) abci.Validator {
|
||||
return abci.Validator{
|
||||
PubKey: tmtypes.TM2PB.PubKey(v.GetPubKey()),
|
||||
Address: v.GetPubKey().Address(),
|
||||
Power: v.GetPower().RoundInt64(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,10 @@ func TestAndRunSingleInputMsgSend(mapper auth.AccountMapper) simulation.TestAndR
|
|||
toAddr := sdk.AccAddress(toKey.PubKey().Address())
|
||||
initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins()
|
||||
|
||||
if len(initFromCoins) == 0 {
|
||||
return "skipping, no coins at all", nil
|
||||
}
|
||||
|
||||
denomIndex := r.Intn(len(initFromCoins))
|
||||
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
|
||||
if goErr != nil {
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
|
@ -24,7 +26,7 @@ func TestBankWithRandomMessages(t *testing.T) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
appStateFn := func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage {
|
||||
appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage {
|
||||
mock.RandomSetGenesis(r, mapp, accs, []string{"stake"})
|
||||
return json.RawMessage("{}")
|
||||
}
|
||||
|
@ -39,6 +41,6 @@ func TestBankWithRandomMessages(t *testing.T) {
|
|||
NonnegativeBalanceInvariant(mapper),
|
||||
TotalCoinsInvariant(mapper, func() sdk.Coins { return mapp.TotalCoinsSupply }),
|
||||
},
|
||||
100, 30, 30,
|
||||
30, 30,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -96,6 +96,8 @@ func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result {
|
|||
// Called every block, process inflation, update validator set
|
||||
func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) {
|
||||
|
||||
logger := ctx.Logger().With("module", "x/gov")
|
||||
|
||||
resTags = sdk.NewTags()
|
||||
|
||||
// Delete proposals that haven't met minDeposit
|
||||
|
@ -109,6 +111,9 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) {
|
|||
keeper.DeleteProposal(ctx, inactiveProposal)
|
||||
resTags.AppendTag(tags.Action, tags.ActionProposalDropped)
|
||||
resTags.AppendTag(tags.ProposalID, proposalIDBytes)
|
||||
|
||||
logger.Info("Proposal %d - \"%s\" - didn't mean minimum deposit (had only %s), deleted",
|
||||
inactiveProposal.GetProposalID(), inactiveProposal.GetTitle(), inactiveProposal.GetTotalDeposit())
|
||||
}
|
||||
|
||||
// Check if earliest Active Proposal ended voting period yet
|
||||
|
@ -136,6 +141,9 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) {
|
|||
activeProposal.SetTallyResult(tallyResults)
|
||||
keeper.SetProposal(ctx, activeProposal)
|
||||
|
||||
logger.Info("Proposal %d - \"%s\" - tallied, passed: %v",
|
||||
activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)
|
||||
|
||||
for _, valAddr := range nonVotingVals {
|
||||
val := keeper.ds.GetValidatorSet().Validator(ctx, valAddr)
|
||||
keeper.ds.GetValidatorSet().Slash(ctx,
|
||||
|
@ -143,6 +151,9 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) {
|
|||
ctx.BlockHeight(),
|
||||
val.GetPower().RoundInt64(),
|
||||
keeper.GetTallyingProcedure(ctx).GovernancePenalty)
|
||||
|
||||
logger.Info("Validator %s failed to vote on proposal %d, slashing",
|
||||
val.GetOwner(), activeProposal.GetProposalID())
|
||||
}
|
||||
|
||||
resTags.AppendTag(tags.Action, action)
|
||||
|
|
|
@ -119,6 +119,18 @@ func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk
|
|||
return nil
|
||||
}
|
||||
|
||||
// Get the last used proposal ID
|
||||
func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyNextProposalID)
|
||||
if bz == nil {
|
||||
return 0
|
||||
}
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
|
||||
proposalID--
|
||||
return
|
||||
}
|
||||
|
||||
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyNextProposalID)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
)
|
||||
|
||||
// AllInvariants tests all governance invariants
|
||||
func AllInvariants() simulation.Invariant {
|
||||
return func(t *testing.T, app *baseapp.BaseApp, log string) {
|
||||
// TODO Add some invariants!
|
||||
// Checking proposal queues, no passed-but-unexecuted proposals, etc.
|
||||
require.Nil(t, nil)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
const (
|
||||
denom = "steak"
|
||||
)
|
||||
|
||||
// SimulateMsgSubmitProposal
|
||||
func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.TestAndRunTx {
|
||||
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
|
||||
key := simulation.RandomKey(r, keys)
|
||||
addr := sdk.AccAddress(key.PubKey().Address())
|
||||
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()
|
||||
result := gov.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
// Update pool to keep invariants
|
||||
pool := sk.GetPool(ctx)
|
||||
pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewRatFromInt(deposit.AmountOf(denom)))
|
||||
sk.SetPool(ctx, pool)
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("gov/MsgSubmitProposal/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SimulateMsgDeposit
|
||||
func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.TestAndRunTx {
|
||||
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
|
||||
key := simulation.RandomKey(r, keys)
|
||||
addr := sdk.AccAddress(key.PubKey().Address())
|
||||
proposalID, ok := randomProposalID(r, k, ctx)
|
||||
if !ok {
|
||||
return "no-operation", nil
|
||||
}
|
||||
deposit := randomDeposit(r)
|
||||
msg := gov.NewMsgDeposit(addr, proposalID, deposit)
|
||||
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := gov.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
// Update pool to keep invariants
|
||||
pool := sk.GetPool(ctx)
|
||||
pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewRatFromInt(deposit.AmountOf(denom)))
|
||||
sk.SetPool(ctx, pool)
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("gov/MsgDeposit/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgDeposit: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SimulateMsgVote
|
||||
func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.TestAndRunTx {
|
||||
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
|
||||
key := simulation.RandomKey(r, keys)
|
||||
addr := sdk.AccAddress(key.PubKey().Address())
|
||||
proposalID, ok := randomProposalID(r, k, ctx)
|
||||
if !ok {
|
||||
return "no-operation", nil
|
||||
}
|
||||
option := randomVotingOption(r)
|
||||
msg := gov.NewMsgVote(addr, proposalID, option)
|
||||
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := gov.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("gov/MsgVote/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgVote: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pick a random deposit
|
||||
func randomDeposit(r *rand.Rand) sdk.Coins {
|
||||
// TODO Choose based on account balance and min deposit
|
||||
amount := int64(r.Intn(20)) + 1
|
||||
return sdk.Coins{sdk.NewInt64Coin(denom, amount)}
|
||||
}
|
||||
|
||||
// Pick a random proposal ID
|
||||
func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID int64, ok bool) {
|
||||
lastProposalID := k.GetLastProposalID(ctx)
|
||||
if lastProposalID < 1 {
|
||||
return 0, false
|
||||
}
|
||||
proposalID = int64(r.Intn(int(lastProposalID)))
|
||||
return proposalID, true
|
||||
}
|
||||
|
||||
// Pick a random voting option
|
||||
func randomVotingOption(r *rand.Rand) gov.VoteOption {
|
||||
switch r.Intn(4) {
|
||||
case 0:
|
||||
return gov.OptionYes
|
||||
case 1:
|
||||
return gov.OptionAbstain
|
||||
case 2:
|
||||
return gov.OptionNo
|
||||
case 3:
|
||||
return gov.OptionNoWithVeto
|
||||
}
|
||||
panic("should not happen")
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
// TestGovWithRandomMessages
|
||||
func TestGovWithRandomMessages(t *testing.T) {
|
||||
mapp := mock.NewApp()
|
||||
|
||||
bank.RegisterWire(mapp.Cdc)
|
||||
gov.RegisterWire(mapp.Cdc)
|
||||
mapper := mapp.AccountMapper
|
||||
coinKeeper := bank.NewKeeper(mapper)
|
||||
stakeKey := sdk.NewKVStoreKey("stake")
|
||||
stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, coinKeeper, stake.DefaultCodespace)
|
||||
paramKey := sdk.NewKVStoreKey("params")
|
||||
paramKeeper := params.NewKeeper(mapp.Cdc, paramKey)
|
||||
govKey := sdk.NewKVStoreKey("gov")
|
||||
govKeeper := gov.NewKeeper(mapp.Cdc, govKey, paramKeeper.Setter(), coinKeeper, stakeKeeper, gov.DefaultCodespace)
|
||||
mapp.Router().AddRoute("gov", gov.NewHandler(govKeeper))
|
||||
mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
gov.EndBlocker(ctx, govKeeper)
|
||||
return abci.ResponseEndBlock{}
|
||||
})
|
||||
|
||||
err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey, paramKey, govKey})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage {
|
||||
mock.RandomSetGenesis(r, mapp, accs, []string{"stake"})
|
||||
return json.RawMessage("{}")
|
||||
}
|
||||
|
||||
setup := func(r *rand.Rand, privKeys []crypto.PrivKey) {
|
||||
ctx := mapp.NewContext(false, abci.Header{})
|
||||
stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState())
|
||||
gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState())
|
||||
}
|
||||
|
||||
simulation.Simulate(
|
||||
t, mapp.BaseApp, appStateFn,
|
||||
[]simulation.TestAndRunTx{
|
||||
SimulateMsgSubmitProposal(govKeeper, stakeKeeper),
|
||||
SimulateMsgDeposit(govKeeper, stakeKeeper),
|
||||
SimulateMsgVote(govKeeper, stakeKeeper),
|
||||
}, []simulation.RandSetup{
|
||||
setup,
|
||||
}, []simulation.Invariant{
|
||||
AllInvariants(),
|
||||
}, 10, 100,
|
||||
)
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
@ -173,7 +174,32 @@ func GeneratePrivKeyAddressPairs(n int) (keys []crypto.PrivKey, addrs []sdk.AccA
|
|||
keys = make([]crypto.PrivKey, n, n)
|
||||
addrs = make([]sdk.AccAddress, n, n)
|
||||
for i := 0; i < n; i++ {
|
||||
if rand.Int63()%2 == 0 {
|
||||
keys[i] = secp256k1.GenPrivKey()
|
||||
} else {
|
||||
keys[i] = ed25519.GenPrivKey()
|
||||
}
|
||||
addrs[i] = sdk.AccAddress(keys[i].PubKey().Address())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GeneratePrivKeyAddressPairsFromRand generates a total of n private key, address
|
||||
// pairs using the provided randomness source.
|
||||
func GeneratePrivKeyAddressPairsFromRand(rand *rand.Rand, n int) (keys []crypto.PrivKey, addrs []sdk.AccAddress) {
|
||||
keys = make([]crypto.PrivKey, n, n)
|
||||
addrs = make([]sdk.AccAddress, n, n)
|
||||
for i := 0; i < n; i++ {
|
||||
secret := make([]byte, 32)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
panic("Could not read randomness")
|
||||
}
|
||||
if rand.Int63()%2 == 0 {
|
||||
keys[i] = secp256k1.GenPrivKeySecp256k1(secret)
|
||||
} else {
|
||||
keys[i] = ed25519.GenPrivKeyFromSecret(secret)
|
||||
}
|
||||
addrs[i] = sdk.AccAddress(keys[i].PubKey().Address())
|
||||
}
|
||||
return
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package simulation
|
||||
|
||||
const (
|
||||
// Fraction of double-signing evidence from a past height
|
||||
pastEvidenceFraction float64 = 0.5
|
||||
|
||||
// Minimum time per block
|
||||
minTimePerBlock int64 = 86400 / 2
|
||||
|
||||
// Maximum time per block
|
||||
maxTimePerBlock int64 = 86400
|
||||
|
||||
// Number of keys
|
||||
numKeys int = 250
|
||||
|
||||
// Chance that double-signing evidence is found on a given block
|
||||
evidenceFraction float64 = 0.01
|
||||
|
||||
// TODO Remove in favor of binary search for invariant violation
|
||||
onOperation bool = false
|
||||
)
|
||||
|
||||
var (
|
||||
// Currently there are 3 different liveness types, fully online, spotty connection, offline.
|
||||
initialLivenessWeightings = []int{40, 5, 5}
|
||||
livenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{
|
||||
{90, 20, 1},
|
||||
{10, 50, 5},
|
||||
{0, 10, 1000},
|
||||
})
|
||||
)
|
|
@ -7,75 +7,211 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// Simulate tests application by sending random messages.
|
||||
func Simulate(
|
||||
t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage, ops []TestAndRunTx, setups []RandSetup,
|
||||
invariants []Invariant, numKeys int, numBlocks int, blockSize int,
|
||||
t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, ops []TestAndRunTx, setups []RandSetup,
|
||||
invariants []Invariant, numBlocks int, blockSize int,
|
||||
) {
|
||||
time := time.Now().UnixNano()
|
||||
SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numKeys, numBlocks, blockSize)
|
||||
SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize)
|
||||
}
|
||||
|
||||
// SimulateFromSeed tests an application by running the provided
|
||||
// operations, testing the provided invariants, but using the provided seed.
|
||||
func SimulateFromSeed(
|
||||
t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []TestAndRunTx, setups []RandSetup,
|
||||
invariants []Invariant, numKeys int, numBlocks int, blockSize int,
|
||||
t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []TestAndRunTx, setups []RandSetup,
|
||||
invariants []Invariant, numBlocks int, blockSize int,
|
||||
) {
|
||||
log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed))
|
||||
keys, addrs := mock.GeneratePrivKeyAddressPairs(numKeys)
|
||||
fmt.Printf("%s\n", log)
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys)
|
||||
|
||||
// Setup event stats
|
||||
events := make(map[string]uint)
|
||||
event := func(what string) {
|
||||
log += "\nevent - " + what
|
||||
events[what]++
|
||||
}
|
||||
|
||||
app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, addrs)})
|
||||
timestamp := time.Unix(0, 0)
|
||||
timeDiff := maxTimePerBlock - minTimePerBlock
|
||||
|
||||
res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, keys, accs)})
|
||||
validators := make(map[string]mockValidator)
|
||||
for _, validator := range res.Validators {
|
||||
validators[string(validator.Address)] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)}
|
||||
}
|
||||
|
||||
for i := 0; i < len(setups); i++ {
|
||||
setups[i](r, keys)
|
||||
}
|
||||
app.Commit()
|
||||
|
||||
header := abci.Header{Height: 0}
|
||||
header := abci.Header{Height: 0, Time: timestamp}
|
||||
opCount := 0
|
||||
|
||||
request := abci.RequestBeginBlock{Header: header}
|
||||
|
||||
var pastTimes []time.Time
|
||||
|
||||
for i := 0; i < numBlocks; i++ {
|
||||
app.BeginBlock(abci.RequestBeginBlock{})
|
||||
|
||||
// Make sure invariants hold at beginning of block and when nothing was
|
||||
// done.
|
||||
// Log the header time for future lookup
|
||||
pastTimes = append(pastTimes, header.Time)
|
||||
|
||||
// Run the BeginBlock handler
|
||||
app.BeginBlock(request)
|
||||
|
||||
log += "\nBeginBlock"
|
||||
|
||||
// Make sure invariants hold at beginning of block
|
||||
AssertAllInvariants(t, app, invariants, log)
|
||||
|
||||
ctx := app.NewContext(false, header)
|
||||
|
||||
// TODO: Add modes to simulate "no load", "medium load", and
|
||||
// "high load" blocks.
|
||||
for j := 0; j < blockSize; j++ {
|
||||
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)
|
||||
}
|
||||
for j := 0; j < thisBlockSize; j++ {
|
||||
logUpdate, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log, event)
|
||||
log += "\n" + logUpdate
|
||||
|
||||
require.Nil(t, err, log)
|
||||
if onOperation {
|
||||
AssertAllInvariants(t, app, invariants, log)
|
||||
}
|
||||
|
||||
app.EndBlock(abci.RequestEndBlock{})
|
||||
header.Height++
|
||||
if opCount%200 == 0 {
|
||||
fmt.Printf("\rSimulating... block %d/%d, operation %d.", header.Height, numBlocks, opCount)
|
||||
}
|
||||
opCount++
|
||||
}
|
||||
|
||||
res := app.EndBlock(abci.RequestEndBlock{})
|
||||
header.Height++
|
||||
header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second)
|
||||
|
||||
log += "\nEndBlock"
|
||||
|
||||
// Make sure invariants hold at end of block
|
||||
AssertAllInvariants(t, app, invariants, log)
|
||||
|
||||
// Generate a random RequestBeginBlock with the current validator set for the next block
|
||||
request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, event, header, log)
|
||||
|
||||
// Update the validator set
|
||||
validators = updateValidators(t, r, validators, res.ValidatorUpdates, event)
|
||||
}
|
||||
|
||||
fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds): %v\n", header.Height, header.Time)
|
||||
DisplayEvents(events)
|
||||
}
|
||||
|
||||
// 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,
|
||||
pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock {
|
||||
if len(validators) == 0 {
|
||||
return abci.RequestBeginBlock{Header: header}
|
||||
}
|
||||
signingValidators := make([]abci.SigningValidator, len(validators))
|
||||
i := 0
|
||||
for _, mVal := range validators {
|
||||
mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState)
|
||||
signed := true
|
||||
|
||||
if mVal.livenessState == 1 {
|
||||
// spotty connection, 50% probability of success
|
||||
// See https://github.com/golang/go/issues/23804#issuecomment-365370418
|
||||
// for reasoning behind computing like this
|
||||
signed = r.Int63()%2 == 0
|
||||
} else if mVal.livenessState == 2 {
|
||||
// offline
|
||||
signed = false
|
||||
}
|
||||
if signed {
|
||||
event("beginblock/signing/signed")
|
||||
} else {
|
||||
event("beginblock/signing/missed")
|
||||
}
|
||||
signingValidators[i] = abci.SigningValidator{
|
||||
Validator: mVal.val,
|
||||
SignedLastBlock: signed,
|
||||
}
|
||||
i++
|
||||
}
|
||||
evidence := make([]abci.Evidence, 0)
|
||||
for r.Float64() < evidenceFraction {
|
||||
height := header.Height
|
||||
time := header.Time
|
||||
if r.Float64() < pastEvidenceFraction {
|
||||
height = int64(r.Intn(int(header.Height)))
|
||||
time = pastTimes[height]
|
||||
}
|
||||
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{
|
||||
Header: header,
|
||||
LastCommitInfo: abci.LastCommitInfo{
|
||||
Validators: signingValidators,
|
||||
},
|
||||
ByzantineValidators: evidence,
|
||||
}
|
||||
}
|
||||
|
||||
// AssertAllInvariants asserts a list of provided invariants against application state
|
||||
func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant, log string) {
|
||||
for i := 0; i < len(tests); i++ {
|
||||
tests[i](t, app, log)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
for _, update := range updates {
|
||||
switch {
|
||||
case update.Power == 0:
|
||||
require.NotNil(t, current[string(update.PubKey.Data)], "tried to delete a nonexistent validator")
|
||||
event("endblock/validatorupdates/kicked")
|
||||
delete(current, string(update.PubKey.Data))
|
||||
default:
|
||||
// Does validator already exist?
|
||||
if mVal, ok := current[string(update.PubKey.Data)]; ok {
|
||||
mVal.val = update
|
||||
event("endblock/validatorupdates/updated")
|
||||
} else {
|
||||
// Set this new validator
|
||||
current[string(update.PubKey.Data)] = mockValidator{update, GetMemberOfInitialState(r, initialLivenessWeightings)}
|
||||
event("endblock/validatorupdates/added")
|
||||
}
|
||||
}
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// TransitionMatrix is _almost_ a left stochastic matrix.
|
||||
// It is technically not one due to not normalizing the column values.
|
||||
// In the future, if we want to find the steady state distribution,
|
||||
// it will be quite easy to normalize these values to get a stochastic matrix.
|
||||
// Floats aren't currently used as the default due to non-determinism across
|
||||
// architectures
|
||||
type TransitionMatrix struct {
|
||||
weights [][]int
|
||||
// total in each column
|
||||
totals []int
|
||||
n int
|
||||
}
|
||||
|
||||
// CreateTransitionMatrix creates a transition matrix from the provided weights.
|
||||
// TODO: Provide example usage
|
||||
func CreateTransitionMatrix(weights [][]int) (TransitionMatrix, error) {
|
||||
n := len(weights)
|
||||
for i := 0; i < n; i++ {
|
||||
if len(weights[i]) != n {
|
||||
return TransitionMatrix{}, fmt.Errorf("Transition Matrix: Non-square matrix provided, error on row %d", i)
|
||||
}
|
||||
}
|
||||
totals := make([]int, n)
|
||||
for row := 0; row < n; row++ {
|
||||
for col := 0; col < n; col++ {
|
||||
totals[col] += weights[row][col]
|
||||
}
|
||||
}
|
||||
return TransitionMatrix{weights, totals, n}, nil
|
||||
}
|
||||
|
||||
// NextState returns the next state randomly chosen using r, and the weightings provided
|
||||
// in the transition matrix.
|
||||
func (t TransitionMatrix) NextState(r *rand.Rand, i int) int {
|
||||
randNum := r.Intn(t.totals[i])
|
||||
for row := 0; row < t.n; row++ {
|
||||
if randNum < t.weights[row][i] {
|
||||
return row
|
||||
}
|
||||
randNum -= t.weights[row][i]
|
||||
}
|
||||
// This line should never get executed
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetMemberOfInitialState takes an initial array of weights, of size n.
|
||||
// It returns a weighted random number in [0,n).
|
||||
func GetMemberOfInitialState(r *rand.Rand, weights []int) int {
|
||||
n := len(weights)
|
||||
total := 0
|
||||
for i := 0; i < n; i++ {
|
||||
total += weights[i]
|
||||
}
|
||||
randNum := r.Intn(total)
|
||||
for state := 0; state < n; state++ {
|
||||
if randNum < weights[state] {
|
||||
return state
|
||||
}
|
||||
randNum -= weights[state]
|
||||
}
|
||||
// This line should never get executed
|
||||
return -1
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
|
@ -25,6 +26,11 @@ type (
|
|||
// If the invariant has been broken, the function should halt the
|
||||
// test and output the log.
|
||||
Invariant func(t *testing.T, app *baseapp.BaseApp, log string)
|
||||
|
||||
mockValidator struct {
|
||||
val abci.Validator
|
||||
livenessState int
|
||||
}
|
||||
)
|
||||
|
||||
// PeriodicInvariant returns an Invariant function closure that asserts
|
||||
|
|
|
@ -3,8 +3,9 @@ package simulation
|
|||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
crypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
@ -39,8 +40,15 @@ func RandStringOfLength(r *rand.Rand, n int) string {
|
|||
|
||||
// Pretty-print events as a table
|
||||
func DisplayEvents(events map[string]uint) {
|
||||
// TODO
|
||||
fmt.Printf("Events: %v\n", events)
|
||||
var keys []string
|
||||
for key := range events {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
fmt.Printf("Event statistics: \n")
|
||||
for _, key := range keys {
|
||||
fmt.Printf(" % 60s => %d\n", key, events[key])
|
||||
}
|
||||
}
|
||||
|
||||
// Pick a random key from an array
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
)
|
||||
|
||||
// AllInvariants tests all slashing invariants
|
||||
func AllInvariants() simulation.Invariant {
|
||||
return func(t *testing.T, app *baseapp.BaseApp, log string) {
|
||||
// TODO Any invariants to check here?
|
||||
require.Nil(t, nil)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
)
|
||||
|
||||
// SimulateMsgUnrevoke
|
||||
func SimulateMsgUnrevoke(k slashing.Keeper) simulation.TestAndRunTx {
|
||||
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
|
||||
key := simulation.RandomKey(r, keys)
|
||||
address := sdk.AccAddress(key.PubKey().Address())
|
||||
msg := slashing.NewMsgUnrevoke(address)
|
||||
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
|
||||
ctx, write := ctx.CacheContext()
|
||||
result := slashing.NewHandler(k)(ctx, msg)
|
||||
if result.IsOK() {
|
||||
write()
|
||||
}
|
||||
event(fmt.Sprintf("slashing/MsgUnrevoke/%v", result.IsOK()))
|
||||
action = fmt.Sprintf("TestMsgUnrevoke: ok %v, msg %s", result.IsOK(), msg.GetSignBytes())
|
||||
return action, nil
|
||||
}
|
||||
}
|
|
@ -33,7 +33,6 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [
|
|||
|
||||
// Manually set indexes for the first time
|
||||
keeper.SetValidatorByPubKeyIndex(ctx, validator)
|
||||
|
||||
keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool)
|
||||
|
||||
if validator.Status == sdk.Bonded {
|
||||
|
|
|
@ -421,6 +421,9 @@ func (k Keeper) UpdateBondedValidators(
|
|||
if !validator.Revoked {
|
||||
if validator.Status != sdk.Bonded {
|
||||
validatorToBond = validator
|
||||
if newValidatorBonded {
|
||||
panic("already decided to bond a validator, can't bond another!")
|
||||
}
|
||||
newValidatorBonded = true
|
||||
}
|
||||
|
||||
|
@ -436,6 +439,10 @@ func (k Keeper) UpdateBondedValidators(
|
|||
|
||||
iterator.Close()
|
||||
|
||||
if newValidatorBonded && bytes.Equal(oldCliffValidatorAddr, validator.Owner) {
|
||||
panic("cliff validator has not been changed, yet we bonded a new validator")
|
||||
}
|
||||
|
||||
// clear or set the cliff validator
|
||||
if bondedValidatorsCount == int(maxValidators) {
|
||||
k.setCliffValidator(ctx, validator, k.GetPool(ctx))
|
||||
|
|
|
@ -235,7 +235,10 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.TestAndRunTx {
|
|||
func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup {
|
||||
return func(r *rand.Rand, privKeys []crypto.PrivKey) {
|
||||
ctx := mapp.NewContext(false, abci.Header{})
|
||||
stake.InitGenesis(ctx, k, stake.DefaultGenesisState())
|
||||
gen := stake.DefaultGenesisState()
|
||||
gen.Params.InflationMax = sdk.NewRat(0)
|
||||
gen.Params.InflationMin = sdk.NewRat(0)
|
||||
stake.InitGenesis(ctx, k, gen)
|
||||
params := k.GetParams(ctx)
|
||||
denom := params.BondDenom
|
||||
loose := sdk.ZeroInt()
|
||||
|
|
|
@ -5,12 +5,14 @@ import (
|
|||
"math/rand"
|
||||
"testing"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// TestStakeWithRandomMessages
|
||||
|
@ -35,7 +37,7 @@ func TestStakeWithRandomMessages(t *testing.T) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
appStateFn := func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage {
|
||||
appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage {
|
||||
mock.RandomSetGenesis(r, mapp, accs, []string{"stake"})
|
||||
return json.RawMessage("{}")
|
||||
}
|
||||
|
@ -54,6 +56,6 @@ func TestStakeWithRandomMessages(t *testing.T) {
|
|||
Setup(mapp, stakeKeeper),
|
||||
}, []simulation.Invariant{
|
||||
AllInvariants(coinKeeper, stakeKeeper, mapp.AccountMapper),
|
||||
}, 10, 100, 100,
|
||||
}, 10, 100,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -315,6 +315,7 @@ func (d Description) EnsureLength() (Description, sdk.Error) {
|
|||
func (v Validator) ABCIValidator() abci.Validator {
|
||||
return abci.Validator{
|
||||
PubKey: tmtypes.TM2PB.PubKey(v.PubKey),
|
||||
Address: v.PubKey.Address(),
|
||||
Power: v.BondedTokens().RoundInt64(),
|
||||
}
|
||||
}
|
||||
|
@ -324,6 +325,7 @@ func (v Validator) ABCIValidator() abci.Validator {
|
|||
func (v Validator) ABCIValidatorZero() abci.Validator {
|
||||
return abci.Validator{
|
||||
PubKey: tmtypes.TM2PB.PubKey(v.PubKey),
|
||||
Address: v.PubKey.Address(),
|
||||
Power: 0,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue