github.com/cosmos/cosmos-sdk@v0.50.10/baseapp/block_gas_test.go (about)

     1  package baseapp_test
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"testing"
     7  
     8  	abci "github.com/cometbft/cometbft/abci/types"
     9  	cmtjson "github.com/cometbft/cometbft/libs/json"
    10  	dbm "github.com/cosmos/cosmos-db"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"cosmossdk.io/depinject"
    14  	"cosmossdk.io/log"
    15  	sdkmath "cosmossdk.io/math"
    16  	store "cosmossdk.io/store/types"
    17  
    18  	baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil"
    19  	"github.com/cosmos/cosmos-sdk/client"
    20  	"github.com/cosmos/cosmos-sdk/client/tx"
    21  	"github.com/cosmos/cosmos-sdk/codec"
    22  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    23  	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    24  	"github.com/cosmos/cosmos-sdk/runtime"
    25  	"github.com/cosmos/cosmos-sdk/testutil/configurator"
    26  	simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
    27  	"github.com/cosmos/cosmos-sdk/testutil/testdata"
    28  	sdk "github.com/cosmos/cosmos-sdk/types"
    29  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    30  	txtypes "github.com/cosmos/cosmos-sdk/types/tx"
    31  	"github.com/cosmos/cosmos-sdk/types/tx/signing"
    32  	authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
    33  	xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
    34  	bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
    35  	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
    36  	minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
    37  )
    38  
    39  var blockMaxGas = uint64(simtestutil.DefaultConsensusParams.Block.MaxGas)
    40  
    41  type BlockGasImpl struct {
    42  	panicTx      bool
    43  	gasToConsume uint64
    44  	key          store.StoreKey
    45  }
    46  
    47  func (m BlockGasImpl) Set(ctx context.Context, msg *baseapptestutil.MsgKeyValue) (*baseapptestutil.MsgCreateKeyValueResponse, error) {
    48  	sdkCtx := sdk.UnwrapSDKContext(ctx)
    49  	sdkCtx.KVStore(m.key).Set(msg.Key, msg.Value)
    50  	sdkCtx.GasMeter().ConsumeGas(m.gasToConsume, "TestMsg")
    51  	if m.panicTx {
    52  		panic("panic in tx execution")
    53  	}
    54  	return &baseapptestutil.MsgCreateKeyValueResponse{}, nil
    55  }
    56  
    57  func TestBaseApp_BlockGas(t *testing.T) {
    58  	testcases := []struct {
    59  		name         string
    60  		gasToConsume uint64 // gas to consume in the msg execution
    61  		panicTx      bool   // panic explicitly in tx execution
    62  		expErr       bool
    63  	}{
    64  		{"less than block gas meter", 10, false, false},
    65  		{"more than block gas meter", blockMaxGas, false, true},
    66  		{"more than block gas meter", uint64(float64(blockMaxGas) * 1.2), false, true},
    67  		{"consume MaxUint64", math.MaxUint64, true, true},
    68  		{"consume MaxGasWanted", txtypes.MaxGasWanted, false, true},
    69  		{"consume block gas when panicked", 10, true, true},
    70  	}
    71  
    72  	for _, tc := range testcases {
    73  		var (
    74  			bankKeeper        bankkeeper.Keeper
    75  			accountKeeper     authkeeper.AccountKeeper
    76  			appBuilder        *runtime.AppBuilder
    77  			txConfig          client.TxConfig
    78  			cdc               codec.Codec
    79  			interfaceRegistry codectypes.InterfaceRegistry
    80  			err               error
    81  		)
    82  
    83  		err = depinject.Inject(
    84  			depinject.Configs(
    85  				configurator.NewAppConfig(
    86  					configurator.AuthModule(),
    87  					configurator.TxModule(),
    88  					configurator.ParamsModule(),
    89  					configurator.ConsensusModule(),
    90  					configurator.BankModule(),
    91  					configurator.StakingModule(),
    92  				),
    93  				depinject.Supply(log.NewNopLogger()),
    94  			),
    95  			&bankKeeper,
    96  			&accountKeeper,
    97  			&interfaceRegistry,
    98  			&txConfig,
    99  			&cdc,
   100  			&appBuilder)
   101  		require.NoError(t, err)
   102  
   103  		bapp := appBuilder.Build(dbm.NewMemDB(), nil)
   104  		err = bapp.Load(true)
   105  		require.NoError(t, err)
   106  
   107  		t.Run(tc.name, func(t *testing.T) {
   108  			baseapptestutil.RegisterInterfaces(interfaceRegistry)
   109  			baseapptestutil.RegisterKeyValueServer(bapp.MsgServiceRouter(), BlockGasImpl{
   110  				panicTx:      tc.panicTx,
   111  				gasToConsume: tc.gasToConsume,
   112  				key:          bapp.UnsafeFindStoreKey(banktypes.ModuleName),
   113  			})
   114  
   115  			genState := GenesisStateWithSingleValidator(t, cdc, appBuilder)
   116  			stateBytes, err := cmtjson.MarshalIndent(genState, "", " ")
   117  			require.NoError(t, err)
   118  			bapp.InitChain(&abci.RequestInitChain{
   119  				Validators:      []abci.ValidatorUpdate{},
   120  				ConsensusParams: simtestutil.DefaultConsensusParams,
   121  				AppStateBytes:   stateBytes,
   122  			})
   123  
   124  			ctx := bapp.NewContext(false)
   125  
   126  			// tx fee
   127  			feeCoin := sdk.NewCoin("atom", sdkmath.NewInt(150))
   128  			feeAmount := sdk.NewCoins(feeCoin)
   129  
   130  			// test account and fund
   131  			priv1, _, addr1 := testdata.KeyTestPubAddr()
   132  			err = bankKeeper.MintCoins(ctx, minttypes.ModuleName, feeAmount)
   133  			require.NoError(t, err)
   134  			err = bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr1, feeAmount)
   135  			require.NoError(t, err)
   136  			require.Equal(t, feeCoin.Amount, bankKeeper.GetBalance(ctx, addr1, feeCoin.Denom).Amount)
   137  			seq := accountKeeper.GetAccount(ctx, addr1).GetSequence()
   138  			require.Equal(t, uint64(0), seq)
   139  
   140  			// msg and signatures
   141  			msg := &baseapptestutil.MsgKeyValue{
   142  				Key:    []byte("ok"),
   143  				Value:  []byte("ok"),
   144  				Signer: addr1.String(),
   145  			}
   146  
   147  			txBuilder := txConfig.NewTxBuilder()
   148  
   149  			require.NoError(t, txBuilder.SetMsgs(msg))
   150  			txBuilder.SetFeeAmount(feeAmount)
   151  			txBuilder.SetGasLimit(uint64(simtestutil.DefaultConsensusParams.Block.MaxGas))
   152  
   153  			senderAccountNumber := accountKeeper.GetAccount(ctx, addr1).GetAccountNumber()
   154  			privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{senderAccountNumber}, []uint64{0}
   155  			_, txBytes, err := createTestTx(txConfig, txBuilder, privs, accNums, accSeqs, ctx.ChainID())
   156  			require.NoError(t, err)
   157  
   158  			rsp, err := bapp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}})
   159  			require.NoError(t, err)
   160  
   161  			// check result
   162  			ctx = bapp.GetContextForFinalizeBlock(txBytes)
   163  			okValue := ctx.KVStore(bapp.UnsafeFindStoreKey(banktypes.ModuleName)).Get([]byte("ok"))
   164  
   165  			if tc.expErr {
   166  				if tc.panicTx {
   167  					require.Equal(t, sdkerrors.ErrPanic.ABCICode(), rsp.TxResults[0].Code)
   168  				} else {
   169  					require.Equal(t, sdkerrors.ErrOutOfGas.ABCICode(), rsp.TxResults[0].Code)
   170  				}
   171  				require.Empty(t, okValue)
   172  			} else {
   173  				require.Equal(t, uint32(0), rsp.TxResults[0].Code)
   174  				require.Equal(t, []byte("ok"), okValue)
   175  			}
   176  			// check block gas is always consumed
   177  			baseGas := uint64(57504) // baseGas is the gas consumed before tx msg
   178  			expGasConsumed := addUint64Saturating(tc.gasToConsume, baseGas)
   179  			if expGasConsumed > uint64(simtestutil.DefaultConsensusParams.Block.MaxGas) {
   180  				// capped by gasLimit
   181  				expGasConsumed = uint64(simtestutil.DefaultConsensusParams.Block.MaxGas)
   182  			}
   183  			require.Equal(t, int(expGasConsumed), int(ctx.BlockGasMeter().GasConsumed()))
   184  			// tx fee is always deducted
   185  			require.Equal(t, int64(0), bankKeeper.GetBalance(ctx, addr1, feeCoin.Denom).Amount.Int64())
   186  			// sender's sequence is always increased
   187  			seq = accountKeeper.GetAccount(ctx, addr1).GetSequence()
   188  			require.NoError(t, err)
   189  			require.Equal(t, uint64(1), seq)
   190  		})
   191  	}
   192  }
   193  
   194  func createTestTx(txConfig client.TxConfig, txBuilder client.TxBuilder, privs []cryptotypes.PrivKey, accNums, accSeqs []uint64, chainID string) (xauthsigning.Tx, []byte, error) {
   195  	defaultSignMode, err := xauthsigning.APISignModeToInternal(txConfig.SignModeHandler().DefaultMode())
   196  	if err != nil {
   197  		return nil, nil, err
   198  	}
   199  	// First round: we gather all the signer infos. We use the "set empty
   200  	// signature" hack to do that.
   201  	var sigsV2 []signing.SignatureV2
   202  	for i, priv := range privs {
   203  		sigV2 := signing.SignatureV2{
   204  			PubKey: priv.PubKey(),
   205  			Data: &signing.SingleSignatureData{
   206  				SignMode:  defaultSignMode,
   207  				Signature: nil,
   208  			},
   209  			Sequence: accSeqs[i],
   210  		}
   211  
   212  		sigsV2 = append(sigsV2, sigV2)
   213  	}
   214  	err = txBuilder.SetSignatures(sigsV2...)
   215  	if err != nil {
   216  		return nil, nil, err
   217  	}
   218  
   219  	// Second round: all signer infos are set, so each signer can sign.
   220  	sigsV2 = []signing.SignatureV2{}
   221  	for i, priv := range privs {
   222  		signerData := xauthsigning.SignerData{
   223  			Address:       sdk.AccAddress(priv.PubKey().Bytes()).String(),
   224  			ChainID:       chainID,
   225  			AccountNumber: accNums[i],
   226  			Sequence:      accSeqs[i],
   227  			PubKey:        priv.PubKey(),
   228  		}
   229  		sigV2, err := tx.SignWithPrivKey(
   230  			context.TODO(), defaultSignMode, signerData,
   231  			txBuilder, priv, txConfig, accSeqs[i])
   232  		if err != nil {
   233  			return nil, nil, err
   234  		}
   235  
   236  		sigsV2 = append(sigsV2, sigV2)
   237  	}
   238  	err = txBuilder.SetSignatures(sigsV2...)
   239  	if err != nil {
   240  		return nil, nil, err
   241  	}
   242  
   243  	txBytes, err := txConfig.TxEncoder()(txBuilder.GetTx())
   244  	if err != nil {
   245  		return nil, nil, err
   246  	}
   247  
   248  	return txBuilder.GetTx(), txBytes, nil
   249  }
   250  
   251  func addUint64Saturating(a, b uint64) uint64 {
   252  	if math.MaxUint64-a < b {
   253  		return math.MaxUint64
   254  	}
   255  
   256  	return a + b
   257  }