diff --git a/x/mock/simulation/account.go b/x/mock/simulation/account.go new file mode 100644 index 0000000000..37dfbb2cdb --- /dev/null +++ b/x/mock/simulation/account.go @@ -0,0 +1,51 @@ +package simulation + +import ( + "math/rand" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Account contains a privkey, pubkey, address tuple +// eventually more useful data can be placed in here. +// (e.g. number of coins) +type Account struct { + PrivKey crypto.PrivKey + PubKey crypto.PubKey + Address sdk.AccAddress +} + +// are two accounts equal +func (acc Account) Equals(acc2 Account) bool { + return acc.Address.Equals(acc2.Address) +} + +// RandomAcc pick a random account from an array +func RandomAcc(r *rand.Rand, accs []Account) Account { + return accs[r.Intn( + len(accs), + )] +} + +// RandomAccounts generates n random accounts +func RandomAccounts(r *rand.Rand, n int) []Account { + accs := make([]Account, n) + for i := 0; i < n; i++ { + // don't need that much entropy for simulation + privkeySeed := make([]byte, 15) + r.Read(privkeySeed) + useSecp := r.Int63()%2 == 0 + if useSecp { + accs[i].PrivKey = secp256k1.GenPrivKeySecp256k1(privkeySeed) + } else { + accs[i].PrivKey = ed25519.GenPrivKeyFromSecret(privkeySeed) + } + accs[i].PubKey = accs[i].PrivKey.PubKey() + accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) + } + return accs +} diff --git a/x/mock/simulation/invariants.go b/x/mock/simulation/invariants.go new file mode 100644 index 0000000000..b8f9aad21d --- /dev/null +++ b/x/mock/simulation/invariants.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" +) + +// An Invariant is a function which tests a particular invariant. +// If the invariant has been broken, it should return an error +// containing a descriptive message about what happened. +// The simulator will then halt and print the logs. +type Invariant func(app *baseapp.BaseApp) error + +// assertAllInvariants asserts a list of provided invariants against +// application state +func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, + invariants []Invariant, where string, displayLogs func()) { + + for i := 0; i < len(invariants); i++ { + err := invariants[i](app) + if err != nil { + fmt.Printf("Invariants broken after %s\n", where) + fmt.Println(err.Error()) + displayLogs() + t.Fatal() + } + } +} diff --git a/x/mock/simulation/rand_util.go b/x/mock/simulation/rand_util.go index 076995d360..c40d2a65ca 100644 --- a/x/mock/simulation/rand_util.go +++ b/x/mock/simulation/rand_util.go @@ -5,9 +5,6 @@ import ( "math/rand" "time" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" ) @@ -39,13 +36,6 @@ func RandStringOfLength(r *rand.Rand, n int) string { return string(b) } -// RandomAcc pick a random account from an array -func RandomAcc(r *rand.Rand, accs []Account) Account { - return accs[r.Intn( - len(accs), - )] -} - // Generate a random amount func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { return sdk.NewInt(int64(r.Intn(int(max.Int64())))) @@ -57,25 +47,6 @@ func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec { return sdk.NewDecFromBigIntWithPrec(randInt, sdk.Precision) } -// RandomAccounts generates n random accounts -func RandomAccounts(r *rand.Rand, n int) []Account { - accs := make([]Account, n) - for i := 0; i < n; i++ { - // don't need that much entropy for simulation - privkeySeed := make([]byte, 15) - r.Read(privkeySeed) - useSecp := r.Int63()%2 == 0 - if useSecp { - accs[i].PrivKey = secp256k1.GenPrivKeySecp256k1(privkeySeed) - } else { - accs[i].PrivKey = ed25519.GenPrivKeyFromSecret(privkeySeed) - } - accs[i].PubKey = accs[i].PrivKey.PubKey() - accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) - } - return accs -} - // RandomSetGenesis wraps mock.RandomSetGenesis, but using simulation accounts func RandomSetGenesis(r *rand.Rand, app *mock.App, accs []Account, denoms []string) { addrs := make([]sdk.AccAddress, len(accs)) diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/simulate.go similarity index 88% rename from x/mock/simulation/random_simulate_blocks.go rename to x/mock/simulation/simulate.go index 9cfe27011f..7342a91c15 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/simulate.go @@ -14,7 +14,6 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" @@ -188,7 +187,8 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, } if header.ProposerAddress == nil { - fmt.Printf("\nSimulation stopped early as all validators have been unbonded, there is nobody left propose a block!\n") + fmt.Printf("\nSimulation stopped early as all validators " + + "have been unbonded, there is nobody left propose a block!\n") stopEarly = true break } @@ -204,11 +204,16 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, DisplayEvents(events) return } - fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount) + fmt.Printf("\nSimulation complete. Final height (blocks): %d, "+ + "final time (seconds), : %v, operations ran %d\n", + header.Height, header.Time, opCount) + DisplayEvents(events) return nil } +//______________________________________________________________________________ + type blockSimFn func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, header abci.Header, logWriter func(string)) (opCount int) @@ -302,7 +307,7 @@ func getBlockSize(r *rand.Rand, params Params, } else { blocksize = 0 } - return + return state, blocksize } // adds all future operations into the operation queue. @@ -313,6 +318,7 @@ func queueOperations(queuedOperations map[int][]Operation, if futureOperations == nil { return } + for _, futureOp := range futureOperations { if futureOp.BlockHeight != 0 { if val, ok := queuedOperations[futureOp.BlockHeight]; ok { @@ -322,7 +328,12 @@ func queueOperations(queuedOperations map[int][]Operation, } } else { // TODO: Replace with proper sorted data structure, so don't have the copy entire slice - index := sort.Search(len(queuedTimeOperations), func(i int) bool { return queuedTimeOperations[i].BlockTime.After(futureOp.BlockTime) }) + index := sort.Search( + len(queuedTimeOperations), + func(i int) bool { + return queuedTimeOperations[i].BlockTime.After(futureOp.BlockTime) + }, + ) queuedTimeOperations = append(queuedTimeOperations, FutureOperation{}) copy(queuedTimeOperations[index+1:], queuedTimeOperations[index:]) queuedTimeOperations[index] = futureOp @@ -377,32 +388,6 @@ func runQueuedTimeOperations(queueOperations []FutureOperation, return numOpsRan } -func getKeys(validators map[string]mockValidator) []string { - keys := make([]string, len(validators)) - i := 0 - for key := range validators { - keys[i] = key - i++ - } - sort.Strings(keys) - return keys -} - -// randomProposer picks a random proposer from the current validator set -func randomProposer(r *rand.Rand, validators map[string]mockValidator) cmn.HexBytes { - keys := getKeys(validators) - if len(keys) == 0 { - return nil - } - key := keys[r.Intn(len(keys))] - proposer := validators[key].val - pk, err := tmtypes.PB2TM.PubKey(proposer.PubKey) - if err != nil { - panic(err) - } - return pk.Address() -} - // RandomRequestBeginBlock generates a list of signing validators according to // the provided list of validators, signing fraction, and evidence fraction func RandomRequestBeginBlock(r *rand.Rand, params Params, @@ -483,38 +468,3 @@ func RandomRequestBeginBlock(r *rand.Rand, params Params, ByzantineValidators: evidence, } } - -// updateValidators mimicks Tendermint's update logic -// nolint: unparam -func updateValidators(tb testing.TB, r *rand.Rand, params Params, - current map[string]mockValidator, updates []abci.ValidatorUpdate, - event func(string)) map[string]mockValidator { - - for _, update := range updates { - str := fmt.Sprintf("%v", update.PubKey) - switch { - case update.Power == 0: - if _, ok := current[str]; !ok { - tb.Fatalf("tried to delete a nonexistent validator") - } - - event("endblock/validatorupdates/kicked") - delete(current, str) - default: - // Does validator already exist? - if mVal, ok := current[str]; ok { - mVal.val = update - event("endblock/validatorupdates/updated") - } else { - // Set this new validator - current[str] = mockValidator{ - update, - GetMemberOfInitialState(r, params.InitialLivenessWeightings), - } - event("endblock/validatorupdates/added") - } - } - } - - return current -} diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 65118547f5..1014042a71 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -6,8 +6,6 @@ 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" ) // Operation runs a state machine transition, @@ -28,31 +26,6 @@ type Operation func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, // RandSetup performs the random setup the mock module needs. type RandSetup func(r *rand.Rand, accounts []Account) -// An Invariant is a function which tests a particular invariant. -// If the invariant has been broken, it should return an error -// containing a descriptive message about what happened. -// The simulator will then halt and print the logs. -type Invariant func(app *baseapp.BaseApp) error - -// Account contains a privkey, pubkey, address tuple -// eventually more useful data can be placed in here. -// (e.g. number of coins) -type Account struct { - PrivKey crypto.PrivKey - PubKey crypto.PubKey - Address sdk.AccAddress -} - -// are two accounts equal -func (acc Account) Equals(acc2 Account) bool { - return acc.Address.Equals(acc2.Address) -} - -type mockValidator struct { - val abci.ValidatorUpdate - livenessState int -} - // FutureOperation is an operation which will be ran at the // beginning of the provided BlockHeight. // If both a BlockHeight and BlockTime are specified, it will use the BlockHeight. diff --git a/x/mock/simulation/validator.go b/x/mock/simulation/validator.go new file mode 100644 index 0000000000..e758f4da4c --- /dev/null +++ b/x/mock/simulation/validator.go @@ -0,0 +1,78 @@ +package simulation + +import ( + "fmt" + "math/rand" + "sort" + "testing" + + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" +) + +type mockValidator struct { + val abci.ValidatorUpdate + livenessState int +} + +func getKeys(validators map[string]mockValidator) []string { + keys := make([]string, len(validators)) + i := 0 + for key := range validators { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// randomProposer picks a random proposer from the current validator set +func randomProposer(r *rand.Rand, validators map[string]mockValidator) cmn.HexBytes { + keys := getKeys(validators) + if len(keys) == 0 { + return nil + } + key := keys[r.Intn(len(keys))] + proposer := validators[key].val + pk, err := tmtypes.PB2TM.PubKey(proposer.PubKey) + if err != nil { + panic(err) + } + return pk.Address() +} + +// updateValidators mimicks Tendermint's update logic +// nolint: unparam +func updateValidators(tb testing.TB, r *rand.Rand, params Params, + current map[string]mockValidator, updates []abci.ValidatorUpdate, + event func(string)) map[string]mockValidator { + + for _, update := range updates { + str := fmt.Sprintf("%v", update.PubKey) + switch { + case update.Power == 0: + if _, ok := current[str]; !ok { + tb.Fatalf("tried to delete a nonexistent validator") + } + + event("endblock/validatorupdates/kicked") + delete(current, str) + default: + // Does validator already exist? + if mVal, ok := current[str]; ok { + mVal.val = update + event("endblock/validatorupdates/updated") + } else { + // Set this new validator + current[str] = mockValidator{ + update, + GetMemberOfInitialState(r, params.InitialLivenessWeightings), + } + event("endblock/validatorupdates/added") + } + } + } + + return current +}