diff --git a/CHANGELOG.md b/CHANGELOG.md index c94336a07..e66e3a366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ - [\#442](https://github.com/cosmos/evm/pull/442) Prevent nil pointer by checking error in gov precompile FromResponse. - [\#387](https://github.com/cosmos/evm/pull/387) (Experimental) EVM-compatible appside mempool - [\#476](https://github.com/cosmos/evm/pull/476) Add revert error e2e tests for contract and precompile calls +- [\#534](https://github.com/cosmos/evm/pull/534) Align cosmos tx priority with priority setup of anteHandler ### FEATURES diff --git a/ante/cosmos/min_gas_price.go b/ante/cosmos/min_gas_price.go index 8303fdfcf..44b81ff82 100644 --- a/ante/cosmos/min_gas_price.go +++ b/ante/cosmos/min_gas_price.go @@ -42,11 +42,8 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate feeCoins := feeTx.GetFee() evmDenom := evmtypes.GetEVMCoinDenom() - // only allow user to pass in aatom and stake native token as transaction fees - // allow use stake native tokens for fees is just for unit tests to pass - // - // TODO: is the handling of stake necessary here? Why not adjust the tests to contain the correct denom? - validFees := len(feeCoins) == 0 || (len(feeCoins) == 1 && slices.Contains([]string{evmDenom, sdk.DefaultBondDenom}, feeCoins.GetDenomByIndex(0))) + // only allow user to pass in evm coin as transaction fee + validFees := IsValidFeeCoins(feeCoins, []string{evmDenom}) if !validFees && !simulate { return ctx, fmt.Errorf("expected only native token %s for fee, but got %s", evmDenom, feeCoins.String()) } @@ -94,3 +91,7 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate return next(ctx, tx, simulate) } + +func IsValidFeeCoins(feeCoins sdk.Coins, allowedDenoms []string) bool { + return len(feeCoins) == 0 || (len(feeCoins) == 1 && slices.Contains(allowedDenoms, feeCoins.GetDenomByIndex(0))) +} diff --git a/ante/cosmos/min_gas_price_test.go b/ante/cosmos/min_gas_price_test.go new file mode 100644 index 000000000..51b6f473c --- /dev/null +++ b/ante/cosmos/min_gas_price_test.go @@ -0,0 +1,96 @@ +package cosmos_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/evm/ante/cosmos" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestIsValidFeeCoins(t *testing.T) { + tests := []struct { + name string + feeCoins sdk.Coins + allowedDenoms []string + expectedResult bool + }{ + { + name: "empty fee coins should be valid", + feeCoins: sdk.Coins{}, + allowedDenoms: []string{"uatom"}, + expectedResult: true, + }, + { + name: "nil fee coins should be valid", + feeCoins: nil, + allowedDenoms: []string{"uatom"}, + expectedResult: true, + }, + { + name: "single allowed denom should be valid", + feeCoins: sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(1000))), + allowedDenoms: []string{"uatom"}, + expectedResult: true, + }, + { + name: "single allowed denom from multiple allowed should be valid", + feeCoins: sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(1000))), + allowedDenoms: []string{"uatom", "stake", "wei"}, + expectedResult: true, + }, + { + name: "single disallowed denom should be invalid", + feeCoins: sdk.NewCoins(sdk.NewCoin("forbidden", sdkmath.NewInt(1000))), + allowedDenoms: []string{"uatom"}, + expectedResult: false, + }, + { + name: "single disallowed denom with empty allowed list should be invalid", + feeCoins: sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(1000))), + allowedDenoms: []string{}, + expectedResult: false, + }, + { + name: "multiple fee coins should be invalid", + feeCoins: sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(1000)), sdk.NewCoin("stake", sdkmath.NewInt(500))), + allowedDenoms: []string{"uatom", "stake"}, + expectedResult: false, + }, + { + name: "multiple fee coins with one allowed should be invalid", + feeCoins: sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(1000)), sdk.NewCoin("forbidden", sdkmath.NewInt(500))), + allowedDenoms: []string{"uatom"}, + expectedResult: false, + }, + { + name: "empty allowed denoms with empty fee coins should be valid", + feeCoins: sdk.Coins{}, + allowedDenoms: []string{}, + expectedResult: true, + }, + { + name: "case sensitive denom matching", + feeCoins: sdk.NewCoins(sdk.NewCoin("UATOM", sdkmath.NewInt(1000))), + allowedDenoms: []string{"uatom"}, + expectedResult: false, + }, + { + name: "zero amount allowed denom should be valid", + feeCoins: sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(0))), + allowedDenoms: []string{"uatom"}, + expectedResult: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := cosmos.IsValidFeeCoins(tt.feeCoins, tt.allowedDenoms) + require.Equal(t, tt.expectedResult, result, "IsValidFeeCoins returned unexpected result") + }) + } +} diff --git a/mempool/iterator.go b/mempool/iterator.go index b7a75b91c..2860dbdda 100644 --- a/mempool/iterator.go +++ b/mempool/iterator.go @@ -1,6 +1,7 @@ package mempool import ( + "math" "math/big" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -8,14 +9,16 @@ import ( "github.com/cosmos/evm/mempool/miner" "github.com/cosmos/evm/mempool/txpool" + cosmosevmtypes "github.com/cosmos/evm/types" msgtypes "github.com/cosmos/evm/x/vm/types" "cosmossdk.io/log" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" ) var _ mempool.Iterator = &EVMMempoolIterator{} @@ -255,7 +258,29 @@ func (i *EVMMempoolIterator) extractCosmosEffectiveTip(tx sdk.Tx) *uint256.Int { return nil // Transaction doesn't implement FeeTx interface } - bondDenomFeeAmount := math.ZeroInt() + // default to `MaxInt64` when there's no extension option. + maxPriorityPrice := sdkmath.LegacyNewDec(math.MaxInt64) + + if hasExtOptsTx, ok := feeTx.(authante.HasExtensionOptionsTx); ok { + for _, opt := range hasExtOptsTx.GetExtensionOptions() { + if extOpt, ok := opt.GetCachedValue().(*cosmosevmtypes.ExtensionOptionDynamicFeeTx); ok { + maxPriorityPrice = extOpt.MaxPriorityPrice + if maxPriorityPrice.IsNil() { + maxPriorityPrice = sdkmath.LegacyZeroDec() + } + break + } + } + } + + // Convert gasTipCap (LegacyDec) to *uint256.Int for comparison + gasTipCap, overflow := uint256.FromBig(maxPriorityPrice.BigInt()) + if overflow { + i.logger.Debug("overflowed on gas tip cap calculation") + return nil + } + + bondDenomFeeAmount := sdkmath.ZeroInt() fees := feeTx.GetFee() for _, coin := range fees { if coin.Denom == i.bondDenom { @@ -264,8 +289,14 @@ func (i *EVMMempoolIterator) extractCosmosEffectiveTip(tx sdk.Tx) *uint256.Int { } } + gas := sdkmath.NewIntFromUint64(feeTx.GetGas()) + if gas.IsZero() { + i.logger.Debug("Cosmos transaction has zero gas") + return nil + } + // Calculate gas price: fee_amount / gas_limit - gasPrice, overflow := uint256.FromBig(bondDenomFeeAmount.Quo(math.NewIntFromUint64(feeTx.GetGas())).BigInt()) + gasPrice, overflow := uint256.FromBig(bondDenomFeeAmount.Quo(gas).BigInt()) if overflow { i.logger.Debug("overflowed on gas price calculation") return nil @@ -287,6 +318,10 @@ func (i *EVMMempoolIterator) extractCosmosEffectiveTip(tx sdk.Tx) *uint256.Int { } effectiveTip := new(uint256.Int).Sub(gasPrice, baseFee) + if effectiveTip.Cmp(gasTipCap) > 0 { + effectiveTip = gasTipCap + } + i.logger.Debug("calculated effective tip", "gas_price", gasPrice.String(), "base_fee", baseFee.String(), "effective_tip", effectiveTip.String()) return effectiveTip } diff --git a/mempool/mempool.go b/mempool/mempool.go index 24c3687a4..82f3c93f0 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -150,18 +150,12 @@ func NewExperimentalEVMMempool(getCtxCallback func(height int64, prove bool) (sd defaultConfig := sdkmempool.PriorityNonceMempoolConfig[math.Int]{} defaultConfig.TxPriority = sdkmempool.TxPriority[math.Int]{ GetTxPriority: func(goCtx context.Context, tx sdk.Tx) math.Int { - cosmosTxFee, ok := tx.(sdk.FeeTx) - if !ok { + ctx := sdk.UnwrapSDKContext(goCtx) + priority := ctx.Priority() + if priority < 0 { return math.ZeroInt() } - found, coin := cosmosTxFee.GetFee().Find(bondDenom) - if !found { - return math.ZeroInt() - } - - gasPrice := coin.Amount.Quo(math.NewIntFromUint64(cosmosTxFee.GetGas())) - - return gasPrice + return math.NewIntFromUint64(uint64(priority)) }, Compare: func(a, b math.Int) int { return a.BigInt().Cmp(b.BigInt()) diff --git a/tests/integration/mempool/test_helpers.go b/tests/integration/mempool/test_helpers.go index 69246265a..d32f15e87 100644 --- a/tests/integration/mempool/test_helpers.go +++ b/tests/integration/mempool/test_helpers.go @@ -3,6 +3,7 @@ package mempool import ( "encoding/hex" "fmt" + "math" "math/big" abci "github.com/cometbft/cometbft/abci/types" @@ -23,7 +24,9 @@ const ( TxGas = 100_000 ) -// createCosmosSendTransactionWithKey creates a simple bank send transaction with the specified key +var MaxGasTipCap = big.NewInt(math.MaxInt64) + +// createCosmosSendTx creates a simple bank send transaction with the provided key and gasPrice func (s *IntegrationTestSuite) createCosmosSendTx(key keyring.Key, gasPrice *big.Int) sdk.Tx { feeDenom := "aatom" @@ -42,10 +45,11 @@ func (s *IntegrationTestSuite) createCosmosSendTx(key keyring.Key, gasPrice *big tx, err := s.factory.BuildCosmosTx(key.Priv, txArgs) s.Require().NoError(err) + fmt.Printf("DEBUG: Created cosmos transaction successfully\n") return tx } -// createEVMTransaction creates an EVM transaction using the provided key +// createEVMValueTransferTx creates an EVM value transfer transaction using the provided key and gasPrice func (s *IntegrationTestSuite) createEVMValueTransferTx(key keyring.Key, nonce int, gasPrice *big.Int) sdk.Tx { to := s.keyring.GetKey(1).Addr @@ -67,7 +71,7 @@ func (s *IntegrationTestSuite) createEVMValueTransferTx(key keyring.Key, nonce i return tx } -// createEVMContractDeployTx creates an EVM transaction for contract deployment +// createEVMContractDeployTx creates an EVM contract deploy transaction using the provided key and gasPrice func (s *IntegrationTestSuite) createEVMContractDeployTx(key keyring.Key, gasPrice *big.Int, data []byte) sdk.Tx { ethTxArgs := evmtypes.EvmTxArgs{ Nonce: 0, @@ -93,7 +97,7 @@ func (s *IntegrationTestSuite) checkTxs(txs []sdk.Tx) error { return nil } -// checkTxs call abci CheckTx for a transaction +// checkTx calls abci CheckTx for a transaction func (s *IntegrationTestSuite) checkTx(tx sdk.Tx) error { txBytes, err := s.network.App.GetTxConfig().TxEncoder()(tx) if err != nil { @@ -138,7 +142,7 @@ func (s *IntegrationTestSuite) calculateCosmosGasPrice(feeAmount int64, gasLimit // calculateCosmosEffectiveTip calculates the effective tip for a Cosmos transaction // This aligns with EVM transaction prioritization: effective_tip = gas_price - base_fee -func (s *IntegrationTestSuite) calculateCosmosEffectiveTip(feeAmount int64, gasLimit uint64, baseFee *big.Int) *big.Int { +func (s *IntegrationTestSuite) calculateCosmosEffectiveTip(feeAmount int64, gasLimit uint64, gasTipCap, baseFee *big.Int) *big.Int { gasPrice := s.calculateCosmosGasPrice(feeAmount, gasLimit) if baseFee == nil || baseFee.Sign() == 0 { return gasPrice // No base fee, effective tip equals gas price @@ -148,5 +152,10 @@ func (s *IntegrationTestSuite) calculateCosmosEffectiveTip(feeAmount int64, gasL return big.NewInt(0) // Gas price lower than base fee, effective tip is zero } - return new(big.Int).Sub(gasPrice, baseFee) + effectiveTip := new(big.Int).Sub(gasPrice, baseFee) + if effectiveTip.Cmp(gasTipCap) > 0 { + effectiveTip = gasTipCap + } + + return effectiveTip } diff --git a/tests/integration/mempool/test_mempool_integration.go b/tests/integration/mempool/test_mempool_integration.go index 32e24a4a1..780a86b41 100644 --- a/tests/integration/mempool/test_mempool_integration.go +++ b/tests/integration/mempool/test_mempool_integration.go @@ -268,7 +268,12 @@ func (s *IntegrationTestSuite) TestMempoolSelect() { setupTxs: func() { cosmosTx := s.createCosmosSendTx(s.keyring.GetKey(0), big.NewInt(2000)) mpool := s.network.App.GetMempool() - err := mpool.Insert(s.network.GetContext(), cosmosTx) + + // NOTE: In normal tx flow, ctx.priority is set by anteHandler. + // However, in this test code, tx is directly inserted into mempool bypassing anteHandler. + // If ctx.priority were not set by anteHandler, ordering of cosmos txs can be non-deterministic. + // So we should set ctx.pritority only for testing. + err := mpool.Insert(s.network.GetContext().WithPriority(2000), cosmosTx) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -336,7 +341,7 @@ func (s *IntegrationTestSuite) TestMempoolIterator() { setupTxs: func() { cosmosTx := s.createCosmosSendTx(s.keyring.GetKey(0), big.NewInt(2000)) mpool := s.network.App.GetMempool() - err := mpool.Insert(s.network.GetContext(), cosmosTx) + err := mpool.Insert(s.network.GetContext().WithPriority(2000), cosmosTx) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -371,11 +376,11 @@ func (s *IntegrationTestSuite) TestMempoolIterator() { mpool := s.network.App.GetMempool() cosmosTx1 := s.createCosmosSendTx(s.keyring.GetKey(0), big.NewInt(1000)) - err := mpool.Insert(s.network.GetContext(), cosmosTx1) + err := mpool.Insert(s.network.GetContext().WithPriority(1000), cosmosTx1) s.Require().NoError(err) cosmosTx2 := s.createCosmosSendTx(s.keyring.GetKey(1), big.NewInt(2000)) - err = mpool.Insert(s.network.GetContext(), cosmosTx2) + err = mpool.Insert(s.network.GetContext().WithPriority(2000), cosmosTx2) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -402,7 +407,7 @@ func (s *IntegrationTestSuite) TestMempoolIterator() { // Add Cosmos transaction cosmosTx := s.createCosmosSendTx(s.keyring.GetKey(0), big.NewInt(2000)) - err = mpool.Insert(s.network.GetContext(), cosmosTx) + err = mpool.Insert(s.network.GetContext().WithPriority(2000), cosmosTx) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -452,13 +457,13 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { mpool := s.network.App.GetMempool() // Insert in non-priority order - err := mpool.Insert(s.network.GetContext(), lowFeeCosmosTx) + err := mpool.Insert(s.network.GetContext().WithPriority(1000000000), lowFeeCosmosTx) s.Require().NoError(err) err = mpool.Insert(s.network.GetContext(), highGasPriceEVMTx) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), mediumFeeCosmosTx) + err = mpool.Insert(s.network.GetContext().WithPriority(3000000000), mediumFeeCosmosTx) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), highFeeCosmosTx) + err = mpool.Insert(s.network.GetContext().WithPriority(5000000000), highFeeCosmosTx) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -560,11 +565,11 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { mpool := s.network.App.GetMempool() // Insert in random order - err := mpool.Insert(s.network.GetContext(), mediumFeeTx) + err := mpool.Insert(s.network.GetContext().WithPriority(1000000000), mediumFeeTx) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), lowFeeTx) + err = mpool.Insert(s.network.GetContext().WithPriority(3000000000), lowFeeTx) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), highFeeTx) + err = mpool.Insert(s.network.GetContext().WithPriority(5000000000), highFeeTx) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -593,7 +598,7 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { mpool := s.network.App.GetMempool() // Insert Cosmos first, then EVM - err := mpool.Insert(s.network.GetContext(), cosmosTx) + err := mpool.Insert(s.network.GetContext().WithPriority(1000000000), cosmosTx) s.Require().NoError(err) err = mpool.Insert(s.network.GetContext(), evmTx) s.Require().NoError(err) @@ -619,7 +624,7 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { s.Require().NotNil(tx2) feeTx := tx2.(sdk.FeeTx) - effectiveTip = s.calculateCosmosEffectiveTip(feeTx.GetFee().AmountOf("aatom").Int64(), feeTx.GetGas(), big.NewInt(0)) // base fee = 0 + effectiveTip = s.calculateCosmosEffectiveTip(feeTx.GetFee().AmountOf("aatom").Int64(), feeTx.GetGas(), MaxGasTipCap, big.NewInt(0)) // base fee = 0 s.Require().Equal(big.NewInt(1000000000), effectiveTip, "Second transaction should be Cosmos with 1000 aatom effective tip") }, }, @@ -635,7 +640,7 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { mpool := s.network.App.GetMempool() // Insert Cosmos first, then EVM - err := mpool.Insert(s.network.GetContext(), cosmosTx) + err := mpool.Insert(s.network.GetContext().WithPriority(2000000000), cosmosTx) s.Require().NoError(err) err = mpool.Insert(s.network.GetContext(), evmTx) s.Require().NoError(err) @@ -658,7 +663,7 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { s.Require().NotNil(tx2) feeTx := tx2.(sdk.FeeTx) - effectiveTip2 := s.calculateCosmosEffectiveTip(feeTx.GetFee().AmountOf("aatom").Int64(), feeTx.GetGas(), big.NewInt(0)) // base fee = 0 + effectiveTip2 := s.calculateCosmosEffectiveTip(feeTx.GetFee().AmountOf("aatom").Int64(), feeTx.GetGas(), MaxGasTipCap, big.NewInt(0)) // base fee = 0 s.Require().Equal(big.NewInt(2000000000), effectiveTip2, "Second transaction should be Cosmos with 2000 aatom effective tip") }, }, @@ -676,7 +681,7 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { // Insert EVM first, then Cosmos err := mpool.Insert(s.network.GetContext(), evmTx) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), cosmosTx) + err = mpool.Insert(s.network.GetContext().WithPriority(5000000000), cosmosTx) s.Require().NoError(err) }, verifyFunc: func(iterator mempool.Iterator) { @@ -685,7 +690,7 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { s.Require().NotNil(tx1) feeTx := tx1.(sdk.FeeTx) - effectiveTip := s.calculateCosmosEffectiveTip(feeTx.GetFee().AmountOf("aatom").Int64(), feeTx.GetGas(), big.NewInt(0)) // base fee = 0 + effectiveTip := s.calculateCosmosEffectiveTip(feeTx.GetFee().AmountOf("aatom").Int64(), feeTx.GetGas(), MaxGasTipCap, big.NewInt(0)) // base fee = 0 s.Require().Equal(big.NewInt(5000000000), effectiveTip, "First transaction should be Cosmos with 5000 aatom effective tip") // Second transaction should be EVM @@ -719,15 +724,15 @@ func (s *IntegrationTestSuite) TestTransactionOrdering() { mpool := s.network.App.GetMempool() // Insert in random order - err := mpool.Insert(s.network.GetContext(), cosmosLow) + err := mpool.Insert(s.network.GetContext().WithPriority(1000000000), cosmosLow) s.Require().NoError(err) err = mpool.Insert(s.network.GetContext(), evmMedium) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), cosmosHigh) + err = mpool.Insert(s.network.GetContext().WithPriority(6000000000), cosmosHigh) s.Require().NoError(err) err = mpool.Insert(s.network.GetContext(), evmLow) s.Require().NoError(err) - err = mpool.Insert(s.network.GetContext(), cosmosMedium) + err = mpool.Insert(s.network.GetContext().WithPriority(3000000000), cosmosMedium) s.Require().NoError(err) err = mpool.Insert(s.network.GetContext(), evmHigh) s.Require().NoError(err) @@ -840,7 +845,7 @@ func (s *IntegrationTestSuite) TestSelectBy() { setupTxs: func() { cosmosTx := s.createCosmosSendTx(s.keyring.GetKey(0), big.NewInt(2000)) mpool := s.network.App.GetMempool() - err := mpool.Insert(s.network.GetContext(), cosmosTx) + err := mpool.Insert(s.network.GetContext().WithPriority(2000), cosmosTx) s.Require().NoError(err) }, filterFunc: func(tx sdk.Tx) bool { @@ -869,7 +874,7 @@ func (s *IntegrationTestSuite) TestSelectBy() { // Add transactions with different fees for i := 1; i < 6; i++ { // Use different keys for different transactions cosmosTx := s.createCosmosSendTx(s.keyring.GetKey(i), big.NewInt(int64(i*1000))) // 5000, 4000, 3000, 2000, 1000 - err := mpool.Insert(s.network.GetContext(), cosmosTx) + err := mpool.Insert(s.network.GetContext().WithPriority(int64(i*1000)), cosmosTx) s.Require().NoError(err) } }, @@ -970,7 +975,7 @@ func (s *IntegrationTestSuite) TestMempoolHeightRequirement() { tx := s.createCosmosSendTx(s.keyring.GetKey(0), big.NewInt(1000)) // Should fail because mempool requires block height >= 2 - err = mpool.Insert(nw.GetContext(), tx) + err = mpool.Insert(nw.GetContext().WithPriority(1000), tx) // The mempool might not enforce height requirements in this context // Just check that the operation completes (either success or error) s.Require().True(err == nil || err != nil)