Skip to content

Commit

Permalink
Merge PR #2720: x/mock/simulation cleanup and re-org
Browse files Browse the repository at this point in the history
  • Loading branch information
cwgoes committed Nov 9, 2018
2 parents 2aa14d9 + b1ba6a4 commit 1049a26
Show file tree
Hide file tree
Showing 15 changed files with 935 additions and 740 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ BUG FIXES
* Gaia
* [\#2670](https://github.com/cosmos/cosmos-sdk/issues/2670) [x/stake] fixed incorrect `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators`
* [\#2691](https://github.com/cosmos/cosmos-sdk/issues/2691) Fix local testnet creation by using a single canonical genesis time
- [\#2670](https://github.com/cosmos/cosmos-sdk/issues/2670) [x/stake] fixed incorrent `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators`
- [\#2648](https://github.com/cosmos/cosmos-sdk/issues/2648) [gaiad] Fix `gaiad export` / `gaiad import` consistency, test in CI
* [\#2648](https://github.com/cosmos/cosmos-sdk/issues/2648) [gaiad] Fix `gaiad export` / `gaiad import` consistency, test in CI

* SDK
* [\#2625](https://github.com/cosmos/cosmos-sdk/issues/2625) [x/gov] fix AppendTag function usage error
Expand Down
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ IMPROVEMENTS
* Gaia

* SDK
- [x/mock/simulation] [\#2720] major cleanup, introduction of helper objects, reorganization

* Tendermint

Expand Down
51 changes: 51 additions & 0 deletions x/mock/simulation/account.go
Original file line number Diff line number Diff line change
@@ -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
}
32 changes: 15 additions & 17 deletions x/mock/simulation/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,24 @@
Package simulation implements a simulation framework for any state machine
built on the SDK which utilizes auth.
It is primarily intended for fuzz testing the integration of modules.
It will test that the provided operations are interoperable,
and that the desired invariants hold.
It can additionally be used to detect what the performance benchmarks in the
system are, by using benchmarking mode and cpu / mem profiling.
If it detects a failure, it provides the entire log of what was ran,
It is primarily intended for fuzz testing the integration of modules. It will
test that the provided operations are interoperable, and that the desired
invariants hold. It can additionally be used to detect what the performance
benchmarks in the system are, by using benchmarking mode and cpu / mem
profiling. If it detects a failure, it provides the entire log of what was ran.
The simulator takes as input: a random seed, the set of operations to run,
the invariants to test, and additional parameters to configure how long to run,
and misc. parameters that affect simulation speed.
The simulator takes as input: a random seed, the set of operations to run, the
invariants to test, and additional parameters to configure how long to run, and
misc. parameters that affect simulation speed.
It is intended that every module provides a list of Operations which will randomly
create and run a message / tx in a manner that is interesting to fuzz, and verify that
the state transition was executed as expected.
Each module should additionally provide methods to assert that the desired invariants hold.
It is intended that every module provides a list of Operations which will
randomly create and run a message / tx in a manner that is interesting to fuzz,
and verify that the state transition was executed as expected. Each module
should additionally provide methods to assert that the desired invariants hold.
Then to perform a randomized simulation, select the set of desired operations,
the weightings for each, the invariants you want to test, and how long to run it for.
Then run simulation.Simulate!
The simulator will handle things like ensuring that validators periodically double signing,
or go offline.
the weightings for each, the invariants you want to test, and how long to run
it for. Then run simulation.Simulate! The simulator will handle things like
ensuring that validators periodically double signing, or go offline.
*/
package simulation
30 changes: 30 additions & 0 deletions x/mock/simulation/event_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package simulation

import (
"fmt"
"sort"
)

type eventStats map[string]uint

func newEventStats() eventStats {
events := make(map[string]uint)
return events
}

func (es eventStats) tally(eventDesc string) {
es[eventDesc]++
}

// Pretty-print events as a table
func (es eventStats) Print() {
var keys []string
for key := range es {
keys = append(keys, key)
}
sort.Strings(keys)
fmt.Printf("Event statistics: \n")
for _, key := range keys {
fmt.Printf(" % 60s => %d\n", key, es[key])
}
}
30 changes: 30 additions & 0 deletions x/mock/simulation/invariants.go
Original file line number Diff line number Diff line change
@@ -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

// group of Invarient
type Invariants []Invariant

// assertAll asserts the all invariants against application state
func (invs Invariants) assertAll(t *testing.T, app *baseapp.BaseApp,
event string, displayLogs func()) {

for i := 0; i < len(invs); i++ {
if err := invs[i](app); err != nil {
fmt.Printf("Invariants broken after %s\n%s\n", event, err.Error())
displayLogs()
t.Fatal()
}
}
}
201 changes: 201 additions & 0 deletions x/mock/simulation/mock_tendermint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package simulation

import (
"fmt"
"math/rand"
"sort"
"testing"
"time"

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
}

type mockValidators map[string]mockValidator

// get mockValidators from abci validators
func newMockValidators(r *rand.Rand, abciVals []abci.ValidatorUpdate,
params Params) mockValidators {

validators := make(mockValidators)
for _, validator := range abciVals {
str := fmt.Sprintf("%v", validator.PubKey)
liveliness := GetMemberOfInitialState(r,
params.InitialLivenessWeightings)

validators[str] = mockValidator{
val: validator,
livenessState: liveliness,
}
}

return validators
}

// TODO describe usage
func (vals mockValidators) getKeys() []string {
keys := make([]string, len(vals))
i := 0
for key := range vals {
keys[i] = key
i++
}
sort.Strings(keys)
return keys
}

//_________________________________________________________________________________

// randomProposer picks a random proposer from the current validator set
func (vals mockValidators) randomProposer(r *rand.Rand) cmn.HexBytes {
keys := vals.getKeys()
if len(keys) == 0 {
return nil
}
key := keys[r.Intn(len(keys))]
proposer := vals[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)

if update.Power == 0 {
if _, ok := current[str]; !ok {
tb.Fatalf("tried to delete a nonexistent validator")
}
event("endblock/validatorupdates/kicked")
delete(current, str)

} else if mVal, ok := current[str]; ok {
// validator already exists
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
}

// 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,
validators mockValidators, pastTimes []time.Time,
pastVoteInfos [][]abci.VoteInfo,
event func(string), header abci.Header) abci.RequestBeginBlock {

if len(validators) == 0 {
return abci.RequestBeginBlock{
Header: header,
}
}

voteInfos := make([]abci.VoteInfo, len(validators))
for i, key := range validators.getKeys() {
mVal := validators[key]
mVal.livenessState = params.LivenessTransitionMatrix.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")
}

pubkey, err := tmtypes.PB2TM.PubKey(mVal.val.PubKey)
if err != nil {
panic(err)
}
voteInfos[i] = abci.VoteInfo{
Validator: abci.Validator{
Address: pubkey.Address(),
Power: mVal.val.Power,
},
SignedLastBlock: signed,
}
}

// return if no past times
if len(pastTimes) <= 0 {
return abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: voteInfos,
},
}
}

// TODO: Determine capacity before allocation
evidence := make([]abci.Evidence, 0)
for r.Float64() < params.EvidenceFraction {

height := header.Height
time := header.Time
vals := voteInfos

if r.Float64() < params.PastEvidenceFraction {
height = int64(r.Intn(int(header.Height) - 1))
time = pastTimes[height]
vals = pastVoteInfos[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")
}

return abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: voteInfos,
},
ByzantineValidators: evidence,
}
}
Loading

0 comments on commit 1049a26

Please sign in to comment.