github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/simulation/operation.go (about)

     1  package simulation
     2  
     3  import (
     4  	"encoding/json"
     5  	"math/rand"
     6  	"sort"
     7  	"time"
     8  
     9  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/baseapp"
    10  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    11  )
    12  
    13  // Operation runs a state machine transition, and ensures the transition
    14  // happened as expected.  The operation could be running and testing a fuzzed
    15  // transaction, or doing the same for a message.
    16  //
    17  // For ease of debugging, an operation returns a descriptive message "action",
    18  // which details what this fuzzed state machine transition actually did.
    19  //
    20  // Operations can optionally provide a list of "FutureOperations" to run later
    21  // These will be ran at the beginning of the corresponding block.
    22  type Operation func(r *rand.Rand, app *baseapp.BaseApp,
    23  	ctx sdk.Context, accounts []Account, chainID string) (
    24  	OperationMsg OperationMsg, futureOps []FutureOperation, err error)
    25  
    26  // entry kinds for use within OperationEntry
    27  const (
    28  	BeginBlockEntryKind = "begin_block"
    29  	EndBlockEntryKind   = "end_block"
    30  	MsgEntryKind        = "msg"
    31  	QueuedMsgEntryKind  = "queued_msg"
    32  )
    33  
    34  // OperationEntry - an operation entry for logging (ex. BeginBlock, EndBlock, XxxMsg, etc)
    35  type OperationEntry struct {
    36  	EntryKind string          `json:"entry_kind" yaml:"entry_kind"`
    37  	Height    int64           `json:"height" yaml:"height"`
    38  	Order     int64           `json:"order" yaml:"order"`
    39  	Operation json.RawMessage `json:"operation" yaml:"operation"`
    40  }
    41  
    42  // NewOperationEntry creates a new OperationEntry instance
    43  func NewOperationEntry(entry string, height, order int64, op json.RawMessage) OperationEntry {
    44  	return OperationEntry{
    45  		EntryKind: entry,
    46  		Height:    height,
    47  		Order:     order,
    48  		Operation: op,
    49  	}
    50  }
    51  
    52  // BeginBlockEntry - operation entry for begin block
    53  func BeginBlockEntry(height int64) OperationEntry {
    54  	return NewOperationEntry(BeginBlockEntryKind, height, -1, nil)
    55  }
    56  
    57  // EndBlockEntry - operation entry for end block
    58  func EndBlockEntry(height int64) OperationEntry {
    59  	return NewOperationEntry(EndBlockEntryKind, height, -1, nil)
    60  }
    61  
    62  // MsgEntry - operation entry for standard msg
    63  func MsgEntry(height, order int64, opMsg OperationMsg) OperationEntry {
    64  	return NewOperationEntry(MsgEntryKind, height, order, opMsg.MustMarshal())
    65  }
    66  
    67  // QueuedMsgEntry creates an operation entry for a given queued message.
    68  func QueuedMsgEntry(height int64, opMsg OperationMsg) OperationEntry {
    69  	return NewOperationEntry(QueuedMsgEntryKind, height, -1, opMsg.MustMarshal())
    70  }
    71  
    72  // MustMarshal marshals the operation entry, panic on error.
    73  func (oe OperationEntry) MustMarshal() json.RawMessage {
    74  	out, err := json.Marshal(oe)
    75  	if err != nil {
    76  		panic(err)
    77  	}
    78  	return out
    79  }
    80  
    81  //_____________________________________________________________________
    82  
    83  // OperationMsg - structure for operation output
    84  type OperationMsg struct {
    85  	Route   string          `json:"route" yaml:"route"`     // msg route (i.e module name)
    86  	Name    string          `json:"name" yaml:"name"`       // operation name (msg Type or "no-operation")
    87  	Comment string          `json:"comment" yaml:"comment"` // additional comment
    88  	OK      bool            `json:"ok" yaml:"ok"`           // success
    89  	Msg     json.RawMessage `json:"msg" yaml:"msg"`         // JSON encoded msg
    90  }
    91  
    92  // NewOperationMsgBasic creates a new operation message from raw input.
    93  func NewOperationMsgBasic(route, name, comment string, ok bool, msg []byte) OperationMsg {
    94  	return OperationMsg{
    95  		Route:   route,
    96  		Name:    name,
    97  		Comment: comment,
    98  		OK:      ok,
    99  		Msg:     msg,
   100  	}
   101  }
   102  
   103  // NewOperationMsg - create a new operation message from sdk.Msg
   104  func NewOperationMsg(msg sdk.Msg, ok bool, comment string) OperationMsg {
   105  	return NewOperationMsgBasic(msg.Route(), msg.Type(), comment, ok, msg.GetSignBytes())
   106  }
   107  
   108  // NoOpMsg - create a no-operation message
   109  func NoOpMsg(route string) OperationMsg {
   110  	return NewOperationMsgBasic(route, "no-operation", "", false, nil)
   111  }
   112  
   113  // log entry text for this operation msg
   114  func (om OperationMsg) String() string {
   115  	out, err := json.Marshal(om)
   116  	if err != nil {
   117  		panic(err)
   118  	}
   119  	return string(out)
   120  }
   121  
   122  // MustMarshal Marshals the operation msg, panic on error
   123  func (om OperationMsg) MustMarshal() json.RawMessage {
   124  	out, err := json.Marshal(om)
   125  	if err != nil {
   126  		panic(err)
   127  	}
   128  	return out
   129  }
   130  
   131  // LogEvent adds an event for the events stats
   132  func (om OperationMsg) LogEvent(eventLogger func(route, op, evResult string)) {
   133  	pass := "ok"
   134  	if !om.OK {
   135  		pass = "failure"
   136  	}
   137  	eventLogger(om.Route, om.Name, pass)
   138  }
   139  
   140  // OperationQueue defines an object for a queue of operations
   141  type OperationQueue map[int][]Operation
   142  
   143  // NewOperationQueue creates a new OperationQueue instance.
   144  func NewOperationQueue() OperationQueue {
   145  	return make(OperationQueue)
   146  }
   147  
   148  // queueOperations adds all future operations into the operation queue.
   149  func queueOperations(queuedOps OperationQueue,
   150  	queuedTimeOps []FutureOperation, futureOps []FutureOperation) {
   151  
   152  	if futureOps == nil {
   153  		return
   154  	}
   155  
   156  	for _, futureOp := range futureOps {
   157  		futureOp := futureOp
   158  		if futureOp.BlockHeight != 0 {
   159  			if val, ok := queuedOps[futureOp.BlockHeight]; ok {
   160  				queuedOps[futureOp.BlockHeight] = append(val, futureOp.Op)
   161  			} else {
   162  				queuedOps[futureOp.BlockHeight] = []Operation{futureOp.Op}
   163  			}
   164  			continue
   165  		}
   166  
   167  		// TODO: Replace with proper sorted data structure, so don't have the
   168  		// copy entire slice
   169  		index := sort.Search(
   170  			len(queuedTimeOps),
   171  			func(i int) bool {
   172  				return queuedTimeOps[i].BlockTime.After(futureOp.BlockTime)
   173  			},
   174  		)
   175  		queuedTimeOps = append(queuedTimeOps, FutureOperation{})
   176  		copy(queuedTimeOps[index+1:], queuedTimeOps[index:])
   177  		queuedTimeOps[index] = futureOp
   178  	}
   179  }
   180  
   181  //________________________________________________________________________
   182  
   183  // FutureOperation is an operation which will be ran at the beginning of the
   184  // provided BlockHeight. If both a BlockHeight and BlockTime are specified, it
   185  // will use the BlockHeight. In the (likely) event that multiple operations
   186  // are queued at the same block height, they will execute in a FIFO pattern.
   187  type FutureOperation struct {
   188  	BlockHeight int
   189  	BlockTime   time.Time
   190  	Op          Operation
   191  }
   192  
   193  //________________________________________________________________________
   194  
   195  // WeightedOperation is an operation with associated weight.
   196  // This is used to bias the selection operation within the simulator.
   197  type WeightedOperation struct {
   198  	Weight int
   199  	Op     Operation
   200  }
   201  
   202  // NewWeightedOperation creates a new WeightedOperation instance
   203  func NewWeightedOperation(weight int, op Operation) WeightedOperation {
   204  	return WeightedOperation{
   205  		Weight: weight,
   206  		Op:     op,
   207  	}
   208  }
   209  
   210  // WeightedOperations is the group of all weighted operations to simulate.
   211  type WeightedOperations []WeightedOperation
   212  
   213  func (ops WeightedOperations) totalWeight() int {
   214  	totalOpWeight := 0
   215  	for _, op := range ops {
   216  		totalOpWeight += op.Weight
   217  	}
   218  	return totalOpWeight
   219  }
   220  
   221  type selectOpFn func(r *rand.Rand) Operation
   222  
   223  func (ops WeightedOperations) getSelectOpFn() selectOpFn {
   224  	totalOpWeight := ops.totalWeight()
   225  	return func(r *rand.Rand) Operation {
   226  		x := r.Intn(totalOpWeight)
   227  		for i := 0; i < len(ops); i++ {
   228  			if x <= ops[i].Weight {
   229  				return ops[i].Op
   230  			}
   231  			x -= ops[i].Weight
   232  		}
   233  		// shouldn't happen
   234  		return ops[0].Op
   235  	}
   236  }