cosmos-sdk/x/mock/simulation/random_simulate_blocks.go

355 lines
12 KiB
Go
Raw Normal View History

2018-07-16 18:25:15 -07:00
package simulation
import (
2018-07-17 11:33:53 -07:00
"encoding/json"
"fmt"
"math"
"math/rand"
2018-08-29 23:02:15 -07:00
"os"
"sort"
"testing"
"time"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
tmtypes "github.com/tendermint/tendermint/types"
2018-07-16 18:15:50 -07:00
"github.com/cosmos/cosmos-sdk/baseapp"
2018-07-17 15:04:10 -07:00
sdk "github.com/cosmos/cosmos-sdk/types"
2018-07-16 18:25:15 -07:00
"github.com/cosmos/cosmos-sdk/x/mock"
)
2018-07-17 11:33:53 -07:00
// Simulate tests application by sending random messages.
func Simulate(
t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, ops []Operation, setups []RandSetup,
invariants []Invariant, numBlocks int, blockSize int, commit bool,
) {
time := time.Now().UnixNano()
SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit)
}
2018-08-29 23:02:15 -07:00
func initChain(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress, setups []RandSetup, app *baseapp.BaseApp,
appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage) (validators map[string]mockValidator) {
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)
}
return
}
func randTimestamp(r *rand.Rand) time.Time {
unixTime := r.Int63n(int64(math.Pow(2, 40)))
return time.Unix(unixTime, 0)
}
2018-07-17 11:33:53 -07:00
// SimulateFromSeed tests an application by running the provided
// operations, testing the provided invariants, but using the provided seed.
2018-07-17 11:33:53 -07:00
func SimulateFromSeed(
2018-08-30 00:13:31 -07:00
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,
) {
2018-09-01 12:32:24 -07:00
testingMode, t, b := getTestingMode(tb)
2018-07-17 15:04:10 -07:00
log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed))
r := rand.New(rand.NewSource(seed))
2018-08-29 23:02:15 -07:00
timestamp := randTimestamp(r)
2018-09-01 12:32:24 -07:00
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)
2018-07-17 16:27:51 -07:00
// Setup event stats
events := make(map[string]uint)
event := func(what string) {
2018-09-01 12:32:24 -07:00
log = updateLog(testingMode, log, "event - %s", what)
2018-07-17 22:50:04 -07:00
events[what]++
2018-07-17 16:27:51 -07:00
}
2018-08-29 23:02:15 -07:00
validators := initChain(r, keys, accs, setups, app, appStateFn)
header := abci.Header{Height: 0, Time: timestamp}
opCount := 0
2018-09-01 12:32:24 -07:00
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
operationQueue := make(map[int][]Operation)
2018-09-01 12:32:24 -07:00
if !testingMode {
2018-08-30 00:13:31 -07:00
b.ResetTimer()
}
2018-09-01 12:32:24 -07:00
blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks)
2018-08-30 00:13:31 -07:00
for i := 0; i < numBlocks; i++ {
2018-09-01 12:32:24 -07:00
// Log the header time for future lookup
pastTimes = append(pastTimes, header.Time)
pastSigningValidators = append(pastSigningValidators, request.LastCommitInfo.Validators)
2018-09-01 12:32:24 -07:00
// Run the BeginBlock handler
app.BeginBlock(request)
2018-09-01 12:32:24 -07:00
log = updateLog(testingMode, log, "BeginBlock")
2018-09-01 12:32:24 -07:00
if testingMode {
2018-08-30 00:13:31 -07:00
// Make sure invariants hold at beginning of block
AssertAllInvariants(t, app, invariants, log)
}
ctx := app.NewContext(false, header)
2018-08-29 23:02:15 -07:00
thisBlockSize := getBlockSize(r, blockSize)
// Run queued operations. Ignores blocksize if blocksize is too small
2018-08-30 00:13:31 -07:00
log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, log, event)
opCount += numQueuedOpsRan
thisBlockSize -= numQueuedOpsRan
log, operations := blockSimulator(thisBlockSize, r, app, ctx, keys, log, header)
opCount += operations
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)
2018-09-01 12:32:24 -07:00
log = updateLog(testingMode, log, "EndBlock")
2018-09-01 12:32:24 -07:00
if testingMode {
2018-08-30 00:13:31 -07:00
// Make sure invariants hold at end of block
AssertAllInvariants(t, app, invariants, log)
}
if commit {
app.Commit()
}
// Generate a random RequestBeginBlock with the current validator set for the next block
request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log)
// Update the validator set
2018-08-30 00:13:31 -07:00
validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event)
}
2018-07-17 16:27:51 -07:00
fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount)
DisplayEvents(events)
}
// Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize
// memory overhead
2018-09-01 12:32:24 -07:00
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)
2018-09-01 12:32:24 -07:00
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)
2018-09-01 12:32:24 -07:00
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
}
}
2018-09-01 12:32:24 -07:00
func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) {
testingMode = false
if _t, ok := tb.(*testing.T); ok {
t = _t
2018-09-01 12:32:24 -07:00
testingMode = true
2018-08-30 00:35:02 -07:00
} else {
b = tb.(*testing.B)
2018-08-30 00:13:31 -07:00
}
return
}
2018-09-01 12:32:24 -07:00
func updateLog(testingMode bool, log string, update string, args ...interface{}) (updatedLog string) {
if testingMode {
2018-08-30 00:35:02 -07:00
update = fmt.Sprintf(update, args...)
2018-08-30 00:13:31 -07:00
return fmt.Sprintf("%s\n%s", log, update)
}
return ""
}
2018-08-29 23:02:15 -07:00
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.
func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) {
if futureOperations == nil {
return
}
for _, futureOp := range futureOperations {
if val, ok := queuedOperations[futureOp.BlockHeight]; ok {
queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op)
} else {
queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op}
}
}
}
2018-09-03 09:47:24 -07:00
// nolint: errcheck
2018-08-29 23:02:15 -07:00
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) {
updatedLog = log
if queuedOps, ok := queueOperations[height]; ok {
numOps := len(queuedOps)
for i := 0; i < numOps; i++ {
// For now, queued operations cannot queue more operations.
// If a need arises for us to support queued messages to queue more messages, this can
// be changed.
2018-08-29 23:02:15 -07:00
logUpdate, _, err := queuedOps[i](tb, r, app, ctx, privKeys, updatedLog, event)
updatedLog = fmt.Sprintf("%s\n%s", updatedLog, logUpdate)
if err != nil {
fmt.Fprint(os.Stderr, updatedLog)
tb.FailNow()
}
}
delete(queueOperations, height)
return updatedLog, numOps
}
return log, 0
}
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
}
// RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction
2018-08-31 15:22:37 -07:00
// nolint: unparam
2018-08-29 23:02:15 -07:00
func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64,
pastTimes []time.Time, pastSigningValidators [][]abci.SigningValidator, 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 _, key := range getKeys(validators) {
mVal := validators[key]
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++
}
2018-08-29 23:02:15 -07:00
// TODO: Determine capacity before allocation
evidence := make([]abci.Evidence, 0)
// Anything but the first block
if len(pastTimes) > 0 {
for r.Float64() < evidenceFraction {
height := header.Height
time := header.Time
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")
}
}
return abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Validators: signingValidators,
},
ByzantineValidators: evidence,
}
}
2018-07-17 15:04:10 -07:00
// AssertAllInvariants asserts a list of provided invariants against application state
2018-07-16 18:15:50 -07:00
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
2018-09-03 09:47:24 -07:00
// nolint: unparam
2018-08-29 23:02:15 -07:00
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 {
switch {
case update.Power == 0:
2018-08-29 23:02:15 -07:00
// // 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")
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
}