176 lines
6.4 KiB
Go
176 lines
6.4 KiB
Go
package simsx
|
|
|
|
import (
|
|
"context"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// SimMsgFactoryX is an interface for creating and handling fuzz test-like simulation messages in the system.
|
|
type SimMsgFactoryX interface {
|
|
// MsgType returns an empty instance of the concrete message type that the factory provides.
|
|
// This instance is primarily used for deduplication and reporting purposes.
|
|
// The result must not be nil
|
|
MsgType() sdk.Msg
|
|
|
|
// Create returns a FactoryMethod implementation which is responsible for constructing new instances of the message
|
|
// on each invocation.
|
|
Create() FactoryMethod
|
|
|
|
// DeliveryResultHandler returns a SimDeliveryResultHandler instance which processes the delivery
|
|
// response error object. While most simulation factories anticipate successful message delivery,
|
|
// certain factories employ this handler to validate execution errors, thereby covering negative
|
|
// test scenarios.
|
|
DeliveryResultHandler() SimDeliveryResultHandler
|
|
}
|
|
|
|
type (
|
|
// FactoryMethod is a method signature implemented by concrete message factories for SimMsgFactoryX
|
|
//
|
|
// This factory method is responsible for creating a new `sdk.Msg` instance and determining the
|
|
// proposed signers who are expected to successfully sign the message for delivery.
|
|
//
|
|
// Parameters:
|
|
// - ctx: The context for the operation
|
|
// - testData: A pointer to a `ChainDataSource` which provides helper methods and simple access to accounts
|
|
// and balances within the chain.
|
|
// - reporter: An instance of `SimulationReporter` used to report the results of the simulation.
|
|
// If no valid message can be provided, the factory method should call `reporter.Skip("some comment")`
|
|
// with both `signer` and `msg` set to nil.
|
|
//
|
|
// Returns:
|
|
// - signer: A slice of `SimAccount` representing the proposed signers.
|
|
// - msg: An instance of `sdk.Msg` representing the message to be delivered.
|
|
FactoryMethod func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg)
|
|
|
|
// FactoryMethodWithFutureOps extended message factory method for the gov module or others that have to schedule operations for a future block.
|
|
FactoryMethodWithFutureOps[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter, fOpsReg FutureOpsRegistry) ([]SimAccount, T)
|
|
|
|
// FactoryMethodWithDeliveryResultHandler extended factory method that can return a result handler, that is executed on the delivery tx error result.
|
|
// This is used in staking for example to validate negative execution results.
|
|
FactoryMethodWithDeliveryResultHandler[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T, handler SimDeliveryResultHandler)
|
|
)
|
|
|
|
var _ SimMsgFactoryX = &ResultHandlingSimMsgFactory[sdk.Msg]{}
|
|
|
|
// ResultHandlingSimMsgFactory message factory with a delivery error result handler configured.
|
|
type ResultHandlingSimMsgFactory[T sdk.Msg] struct {
|
|
SimMsgFactoryFn[T]
|
|
resultHandler SimDeliveryResultHandler
|
|
}
|
|
|
|
// NewSimMsgFactoryWithDeliveryResultHandler constructor
|
|
func NewSimMsgFactoryWithDeliveryResultHandler[T sdk.Msg](f FactoryMethodWithDeliveryResultHandler[T]) *ResultHandlingSimMsgFactory[T] {
|
|
r := &ResultHandlingSimMsgFactory[T]{
|
|
resultHandler: expectNoError,
|
|
}
|
|
r.SimMsgFactoryFn = func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T) {
|
|
signer, msg, r.resultHandler = f(ctx, testData, reporter)
|
|
if r.resultHandler == nil {
|
|
r.resultHandler = expectNoError
|
|
}
|
|
return signer, msg
|
|
}
|
|
return r
|
|
}
|
|
|
|
// DeliveryResultHandler result handler of the last msg factory invocation
|
|
func (f ResultHandlingSimMsgFactory[T]) DeliveryResultHandler() SimDeliveryResultHandler {
|
|
return f.resultHandler
|
|
}
|
|
|
|
var (
|
|
_ SimMsgFactoryX = &LazyStateSimMsgFactory[sdk.Msg]{}
|
|
_ HasFutureOpsRegistry = &LazyStateSimMsgFactory[sdk.Msg]{}
|
|
)
|
|
|
|
// LazyStateSimMsgFactory stateful message factory with weighted proposals and future operation
|
|
// registry initialized lazy before execution.
|
|
type LazyStateSimMsgFactory[T sdk.Msg] struct {
|
|
SimMsgFactoryFn[T]
|
|
fsOpsReg FutureOpsRegistry
|
|
}
|
|
|
|
func NewSimMsgFactoryWithFutureOps[T sdk.Msg](f FactoryMethodWithFutureOps[T]) *LazyStateSimMsgFactory[T] {
|
|
r := &LazyStateSimMsgFactory[T]{}
|
|
r.SimMsgFactoryFn = func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T) {
|
|
signer, msg = f(ctx, testData, reporter, r.fsOpsReg)
|
|
return signer, msg
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (c *LazyStateSimMsgFactory[T]) SetFutureOpsRegistry(registry FutureOpsRegistry) {
|
|
c.fsOpsReg = registry
|
|
}
|
|
|
|
// pass errors through and don't handle them
|
|
func expectNoError(err error) error {
|
|
return err
|
|
}
|
|
|
|
var _ SimMsgFactoryX = SimMsgFactoryFn[sdk.Msg](nil)
|
|
|
|
// SimMsgFactoryFn is the default factory for most cases. It does not create future operations but ensures successful message delivery.
|
|
type SimMsgFactoryFn[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T)
|
|
|
|
// MsgType returns an empty instance of type T, which implements `sdk.Msg`.
|
|
func (f SimMsgFactoryFn[T]) MsgType() sdk.Msg {
|
|
var x T
|
|
return x
|
|
}
|
|
|
|
func (f SimMsgFactoryFn[T]) Create() FactoryMethod {
|
|
// adapter to return sdk.Msg instead of typed result to match FactoryMethod signature
|
|
return func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) ([]SimAccount, sdk.Msg) {
|
|
return f(ctx, testData, reporter)
|
|
}
|
|
}
|
|
|
|
func (f SimMsgFactoryFn[T]) DeliveryResultHandler() SimDeliveryResultHandler {
|
|
return expectNoError
|
|
}
|
|
|
|
func (f SimMsgFactoryFn[T]) Cast(msg sdk.Msg) T {
|
|
return msg.(T)
|
|
}
|
|
|
|
type tuple struct {
|
|
signer []SimAccount
|
|
msg sdk.Msg
|
|
}
|
|
|
|
// SafeRunFactoryMethod runs the factory method on a separate goroutine to abort early when the context is canceled via reporter skip
|
|
func SafeRunFactoryMethod(
|
|
ctx context.Context,
|
|
data *ChainDataSource,
|
|
reporter SimulationReporter,
|
|
f FactoryMethod,
|
|
) (signer []SimAccount, msg sdk.Msg) {
|
|
r := make(chan tuple)
|
|
go func() {
|
|
defer recoverPanicForSkipped(reporter, r)
|
|
signer, msg := f(ctx, data, reporter)
|
|
r <- tuple{signer: signer, msg: msg}
|
|
}()
|
|
select {
|
|
case t, ok := <-r:
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return t.signer, t.msg
|
|
case <-ctx.Done():
|
|
reporter.Skip("context closed")
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func recoverPanicForSkipped(reporter SimulationReporter, resultChan chan tuple) {
|
|
if r := recover(); r != nil {
|
|
if !reporter.IsSkipped() {
|
|
panic(r)
|
|
}
|
|
close(resultChan)
|
|
}
|
|
}
|