github.com/cosmos/cosmos-sdk@v0.50.10/testutil/integration/router.go (about)

     1  package integration
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	cmtabcitypes "github.com/cometbft/cometbft/abci/types"
     8  	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
     9  	dbm "github.com/cosmos/cosmos-db"
    10  
    11  	"cosmossdk.io/core/appmodule"
    12  	"cosmossdk.io/log"
    13  	"cosmossdk.io/store"
    14  	"cosmossdk.io/store/metrics"
    15  	storetypes "cosmossdk.io/store/types"
    16  
    17  	"github.com/cosmos/cosmos-sdk/baseapp"
    18  	"github.com/cosmos/cosmos-sdk/codec"
    19  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    20  	"github.com/cosmos/cosmos-sdk/runtime"
    21  	simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
    22  	sdk "github.com/cosmos/cosmos-sdk/types"
    23  	"github.com/cosmos/cosmos-sdk/types/module"
    24  	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
    25  	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
    26  	consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
    27  	consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
    28  )
    29  
    30  const appName = "integration-app"
    31  
    32  // App is a test application that can be used to test the integration of modules.
    33  type App struct {
    34  	*baseapp.BaseApp
    35  
    36  	ctx           sdk.Context
    37  	logger        log.Logger
    38  	moduleManager module.Manager
    39  	queryHelper   *baseapp.QueryServiceTestHelper
    40  }
    41  
    42  // NewIntegrationApp creates an application for testing purposes. This application
    43  // is able to route messages to their respective handlers.
    44  func NewIntegrationApp(
    45  	sdkCtx sdk.Context,
    46  	logger log.Logger,
    47  	keys map[string]*storetypes.KVStoreKey,
    48  	appCodec codec.Codec,
    49  	modules map[string]appmodule.AppModule,
    50  	baseAppOptions ...func(*baseapp.BaseApp),
    51  ) *App {
    52  	db := dbm.NewMemDB()
    53  
    54  	interfaceRegistry := codectypes.NewInterfaceRegistry()
    55  	moduleManager := module.NewManagerFromMap(modules)
    56  	basicModuleManager := module.NewBasicManagerFromManager(moduleManager, nil)
    57  	basicModuleManager.RegisterInterfaces(interfaceRegistry)
    58  
    59  	txConfig := authtx.NewTxConfig(codec.NewProtoCodec(interfaceRegistry), authtx.DefaultSignModes)
    60  	bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), append(baseAppOptions, baseapp.SetChainID(appName))...)
    61  	bApp.MountKVStores(keys)
    62  
    63  	bApp.SetInitChainer(func(_ sdk.Context, _ *cmtabcitypes.RequestInitChain) (*cmtabcitypes.ResponseInitChain, error) {
    64  		for _, mod := range modules {
    65  			if m, ok := mod.(module.HasGenesis); ok {
    66  				m.InitGenesis(sdkCtx, appCodec, m.DefaultGenesis(appCodec))
    67  			}
    68  		}
    69  
    70  		return &cmtabcitypes.ResponseInitChain{}, nil
    71  	})
    72  
    73  	bApp.SetBeginBlocker(func(_ sdk.Context) (sdk.BeginBlock, error) {
    74  		return moduleManager.BeginBlock(sdkCtx)
    75  	})
    76  	bApp.SetEndBlocker(func(_ sdk.Context) (sdk.EndBlock, error) {
    77  		return moduleManager.EndBlock(sdkCtx)
    78  	})
    79  
    80  	router := baseapp.NewMsgServiceRouter()
    81  	router.SetInterfaceRegistry(interfaceRegistry)
    82  	bApp.SetMsgServiceRouter(router)
    83  
    84  	if keys[consensusparamtypes.StoreKey] != nil {
    85  		// set baseApp param store
    86  		consensusParamsKeeper := consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress("gov").String(), runtime.EventService{})
    87  		bApp.SetParamStore(consensusParamsKeeper.ParamsStore)
    88  
    89  		if err := bApp.LoadLatestVersion(); err != nil {
    90  			panic(fmt.Errorf("failed to load application version from store: %w", err))
    91  		}
    92  
    93  		if _, err := bApp.InitChain(&cmtabcitypes.RequestInitChain{ChainId: appName, ConsensusParams: simtestutil.DefaultConsensusParams}); err != nil {
    94  			panic(fmt.Errorf("failed to initialize application: %w", err))
    95  		}
    96  	} else {
    97  		if err := bApp.LoadLatestVersion(); err != nil {
    98  			panic(fmt.Errorf("failed to load application version from store: %w", err))
    99  		}
   100  
   101  		if _, err := bApp.InitChain(&cmtabcitypes.RequestInitChain{ChainId: appName}); err != nil {
   102  			panic(fmt.Errorf("failed to initialize application: %w", err))
   103  		}
   104  	}
   105  
   106  	bApp.Commit()
   107  
   108  	ctx := sdkCtx.WithBlockHeader(cmtproto.Header{ChainID: appName}).WithIsCheckTx(true)
   109  
   110  	return &App{
   111  		BaseApp:       bApp,
   112  		logger:        logger,
   113  		ctx:           ctx,
   114  		moduleManager: *moduleManager,
   115  		queryHelper:   baseapp.NewQueryServerTestHelper(ctx, interfaceRegistry),
   116  	}
   117  }
   118  
   119  // RunMsg provides the ability to run a message and return the response.
   120  // In order to run a message, the application must have a handler for it.
   121  // These handlers are registered on the application message service router.
   122  // The result of the message execution is returned as an Any type.
   123  // That any type can be unmarshaled to the expected response type.
   124  // If the message execution fails, an error is returned.
   125  func (app *App) RunMsg(msg sdk.Msg, option ...Option) (*codectypes.Any, error) {
   126  	// set options
   127  	cfg := &Config{}
   128  	for _, opt := range option {
   129  		opt(cfg)
   130  	}
   131  
   132  	if cfg.AutomaticCommit {
   133  		defer app.Commit()
   134  	}
   135  
   136  	if cfg.AutomaticFinalizeBlock {
   137  		height := app.LastBlockHeight() + 1
   138  		if _, err := app.FinalizeBlock(&cmtabcitypes.RequestFinalizeBlock{Height: height}); err != nil {
   139  			return nil, fmt.Errorf("failed to run finalize block: %w", err)
   140  		}
   141  	}
   142  
   143  	app.logger.Info("Running msg", "msg", msg.String())
   144  
   145  	handler := app.MsgServiceRouter().Handler(msg)
   146  	if handler == nil {
   147  		return nil, fmt.Errorf("handler is nil, can't route message %s: %+v", sdk.MsgTypeURL(msg), msg)
   148  	}
   149  
   150  	msgResult, err := handler(app.ctx, msg)
   151  	if err != nil {
   152  		return nil, fmt.Errorf("failed to execute message %s: %w", sdk.MsgTypeURL(msg), err)
   153  	}
   154  
   155  	var response *codectypes.Any
   156  	if len(msgResult.MsgResponses) > 0 {
   157  		msgResponse := msgResult.MsgResponses[0]
   158  		if msgResponse == nil {
   159  			return nil, fmt.Errorf("got nil msg response %s in message result: %s", sdk.MsgTypeURL(msg), msgResult.String())
   160  		}
   161  
   162  		response = msgResponse
   163  	}
   164  
   165  	return response, nil
   166  }
   167  
   168  // Context returns the application context. It can be unwrapped to a sdk.Context,
   169  // with the sdk.UnwrapSDKContext function.
   170  func (app *App) Context() context.Context {
   171  	return app.ctx
   172  }
   173  
   174  // QueryHelper returns the application query helper.
   175  // It can be used when registering query services.
   176  func (app *App) QueryHelper() *baseapp.QueryServiceTestHelper {
   177  	return app.queryHelper
   178  }
   179  
   180  // CreateMultiStore is a helper for setting up multiple stores for provided modules.
   181  func CreateMultiStore(keys map[string]*storetypes.KVStoreKey, logger log.Logger) storetypes.CommitMultiStore {
   182  	db := dbm.NewMemDB()
   183  	cms := store.NewCommitMultiStore(db, logger, metrics.NewNoOpMetrics())
   184  
   185  	for key := range keys {
   186  		cms.MountStoreWithDB(keys[key], storetypes.StoreTypeIAVL, db)
   187  	}
   188  
   189  	_ = cms.LoadLatestVersion()
   190  	return cms
   191  }