109 lines
3.1 KiB
Go
109 lines
3.1 KiB
Go
package simulation
|
|
|
|
import (
|
|
"math/rand"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// Operation runs a state machine transition, and ensures the transition
|
|
// happened as expected. The operation could be running and testing a fuzzed
|
|
// transaction, or doing the same for a message.
|
|
//
|
|
// For ease of debugging, an operation returns a descriptive message "action",
|
|
// which details what this fuzzed state machine transition actually did.
|
|
//
|
|
// Operations can optionally provide a list of "FutureOperations" to run later
|
|
// These will be ran at the beginning of the corresponding block.
|
|
type Operation func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
|
accounts []Account, event func(string)) (
|
|
action string, futureOperations []FutureOperation, err error)
|
|
|
|
// queue of operations
|
|
type OperationQueue map[int][]Operation
|
|
|
|
// adds all future operations into the operation queue.
|
|
func queueOperations(queuedOps OperationQueue,
|
|
queuedTimeOps []FutureOperation,
|
|
futureOps []FutureOperation) {
|
|
|
|
if futureOps == nil {
|
|
return
|
|
}
|
|
|
|
for _, futureOp := range futureOps {
|
|
if futureOp.BlockHeight != 0 {
|
|
if val, ok := queuedOps[futureOp.BlockHeight]; ok {
|
|
queuedOps[futureOp.BlockHeight] = append(val, futureOp.Op)
|
|
} else {
|
|
queuedOps[futureOp.BlockHeight] = []Operation{futureOp.Op}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// TODO: Replace with proper sorted data structure, so don't have the
|
|
// copy entire slice
|
|
index := sort.Search(
|
|
len(queuedTimeOps),
|
|
func(i int) bool {
|
|
return queuedTimeOps[i].BlockTime.After(futureOp.BlockTime)
|
|
},
|
|
)
|
|
queuedTimeOps = append(queuedTimeOps, FutureOperation{})
|
|
copy(queuedTimeOps[index+1:], queuedTimeOps[index:])
|
|
queuedTimeOps[index] = futureOp
|
|
}
|
|
}
|
|
|
|
//________________________________________________________________________
|
|
|
|
// 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. In the (likely) event that multiple operations
|
|
// are queued at the same block height, they will execute in a FIFO pattern.
|
|
type FutureOperation struct {
|
|
BlockHeight int
|
|
BlockTime time.Time
|
|
Op Operation
|
|
}
|
|
|
|
//________________________________________________________________________
|
|
|
|
// WeightedOperation is an operation with associated weight.
|
|
// This is used to bias the selection operation within the simulator.
|
|
type WeightedOperation struct {
|
|
Weight int
|
|
Op Operation
|
|
}
|
|
|
|
// WeightedOperations is the group of all weighted operations to simulate.
|
|
type WeightedOperations []WeightedOperation
|
|
|
|
func (w WeightedOperations) totalWeight() int {
|
|
totalOpWeight := 0
|
|
for i := 0; i < len(w); i++ {
|
|
totalOpWeight += w[i].Weight
|
|
}
|
|
return totalOpWeight
|
|
}
|
|
|
|
type selectOpFn func(r *rand.Rand) Operation
|
|
|
|
func (w WeightedOperations) getSelectOpFn() selectOpFn {
|
|
totalOpWeight := ops.totalWeight()
|
|
return func(r *rand.Rand) Operation {
|
|
x := r.Intn(totalOpWeight)
|
|
for i := 0; i < len(ops); i++ {
|
|
if x <= ops[i].Weight {
|
|
return ops[i].Op
|
|
}
|
|
x -= ops[i].Weight
|
|
}
|
|
// shouldn't happen
|
|
return ops[0].Op
|
|
}
|
|
}
|