diff --git a/app/lanes/default.go b/app/lanes/default.go index ff2d0a31..69652afe 100644 --- a/app/lanes/default.go +++ b/app/lanes/default.go @@ -20,10 +20,21 @@ func NewDefaultLane(cfg blockbase.LaneConfig) block.Lane { lane := &blockbase.BaseLane{} proposalHandler := NewDefaultProposalHandler(lane) + mempool, err := NewMempool( + blockbase.NewDefaultTxPriority(), + cfg.SignerExtractor, + cfg.MaxTxs, + cfg.MaxBlockSpace, + cfg.TxEncoder, + ) + if err != nil { + panic(err) + } + _lane, err := blockbase.NewBaseLane( cfg, DefaultName, - blockbase.WithMempool(NewMempool(blockbase.NewDefaultTxPriority(), cfg.SignerExtractor, cfg.MaxTxs)), + blockbase.WithMempool(mempool), blockbase.WithPrepareLaneHandler(proposalHandler.PrepareLaneHandler()), blockbase.WithProcessLaneHandler(proposalHandler.ProcessLaneHandler()), ) diff --git a/app/lanes/free.go b/app/lanes/free.go index bf7cce31..3e34d218 100644 --- a/app/lanes/free.go +++ b/app/lanes/free.go @@ -42,11 +42,22 @@ func NewFreeLane( lane := &blockbase.BaseLane{} proposalHandler := NewDefaultProposalHandler(lane) + mempool, err := NewMempool( + blockbase.NewDefaultTxPriority(), + cfg.SignerExtractor, + cfg.MaxTxs, + cfg.MaxBlockSpace, + cfg.TxEncoder, + ) + if err != nil { + panic(err) + } + _lane, err := blockbase.NewBaseLane( cfg, FreeLaneName, blockbase.WithMatchHandler(matchFn), - blockbase.WithMempool(NewMempool(blockbase.NewDefaultTxPriority(), cfg.SignerExtractor, cfg.MaxTxs)), + blockbase.WithMempool(mempool), blockbase.WithPrepareLaneHandler(proposalHandler.PrepareLaneHandler()), blockbase.WithProcessLaneHandler(proposalHandler.ProcessLaneHandler()), ) diff --git a/app/lanes/free_test.go b/app/lanes/free_test.go index 294797ce..2eb8736c 100644 --- a/app/lanes/free_test.go +++ b/app/lanes/free_test.go @@ -41,7 +41,8 @@ var _ sdk.Tx = MockTx{} var _ sdk.FeeTx = &MockTx{} type MockTx struct { - msgs []sdk.Msg + msgs []sdk.Msg + gasLimit uint64 } func (tx MockTx) GetMsgsV2() ([]protov2.Message, error) { @@ -53,7 +54,7 @@ func (tx MockTx) GetMsgs() []sdk.Msg { } func (tx MockTx) GetGas() uint64 { - return 0 + return tx.gasLimit } func (tx MockTx) GetFee() sdk.Coins { diff --git a/app/lanes/mempool.go b/app/lanes/mempool.go index 97a3a254..d0abd619 100644 --- a/app/lanes/mempool.go +++ b/app/lanes/mempool.go @@ -5,10 +5,13 @@ import ( "errors" "fmt" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" + signer_extraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" blockbase "github.com/skip-mev/block-sdk/v2/block/base" + "github.com/skip-mev/block-sdk/v2/block/proposals" ) type ( @@ -33,11 +36,30 @@ type ( // txCache is a map of all transactions in the mempool. It is used // to quickly check if a transaction is already in the mempool. txCache map[txKey]struct{} + + // ratio defines the relative percentage of block space that can be + // used by this lane. + ratio math.LegacyDec + + // txEncoder defines tx encoder. + txEncoder sdk.TxEncoder } ) // NewMempool returns a new Mempool. -func NewMempool[C comparable](txPriority blockbase.TxPriority[C], extractor signer_extraction.Adapter, maxTx int) *Mempool[C] { +func NewMempool[C comparable]( + txPriority blockbase.TxPriority[C], extractor signer_extraction.Adapter, + maxTx int, ratio math.LegacyDec, txEncoder sdk.TxEncoder, +) (*Mempool[C], error) { + if !ratio.IsPositive() { + return nil, errors.New("mempool creation; ratio must be positive") + } else if ratio.GT(math.LegacyOneDec()) { + return nil, errors.New("mempool creation; ratio must be less than or equal to 1") + } + if txEncoder == nil { + return nil, errors.New("mempool creation; tx encoder is nil") + } + return &Mempool[C]{ index: blockbase.NewPriorityMempool( blockbase.PriorityNonceMempoolConfig[C]{ @@ -48,7 +70,9 @@ func NewMempool[C comparable](txPriority blockbase.TxPriority[C], extractor sign ), extractor: extractor, txCache: make(map[txKey]struct{}), - } + ratio: ratio, + txEncoder: txEncoder, + }, nil } // Priority returns the priority of the transaction. @@ -89,6 +113,10 @@ func (cm *Mempool[C]) Contains(tx sdk.Tx) bool { // Insert inserts a transaction into the mempool. func (cm *Mempool[C]) Insert(ctx context.Context, tx sdk.Tx) error { + if err := cm.AssertLaneLimits(sdk.UnwrapSDKContext(ctx), tx); err != nil { + return err + } + if err := cm.index.Insert(ctx, tx); err != nil { return fmt.Errorf("failed to insert tx into auction index: %w", err) } @@ -130,3 +158,33 @@ func (cm *Mempool[C]) getTxKey(tx sdk.Tx) (txKey, error) { nonce := sig.Sequence return txKey{nonce, sender}, nil } + +// AssertLaneLimits asserts that the transaction does not exceed the lane's max size and gas limit. +func (cm *Mempool[C]) AssertLaneLimits(ctx sdk.Context, tx sdk.Tx) error { + maxBlockSize, maxGasLimit := proposals.GetBlockLimits(ctx) + maxLaneTxSize := cm.ratio.MulInt64(maxBlockSize).TruncateInt().Int64() + maxLaneGasLimit := cm.ratio.MulInt(math.NewIntFromUint64(maxGasLimit)).TruncateInt().Uint64() + + txBytes, err := cm.txEncoder(tx) + if err != nil { + return fmt.Errorf("failed to encode transaction: %w", err) + } + + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return fmt.Errorf("failed to cast transaction to gas tx") + } + + txSize := int64(len(txBytes)) + txGasLimit := gasTx.GetGas() + + if txSize > maxLaneTxSize { + return fmt.Errorf("tx size %d exceeds max lane size %d", txSize, maxLaneTxSize) + } + + if txGasLimit > maxLaneGasLimit { + return fmt.Errorf("tx gas limit %d exceeds max lane gas limit %d", txGasLimit, maxLaneGasLimit) + } + + return nil +} diff --git a/app/lanes/mempool_test.go b/app/lanes/mempool_test.go new file mode 100644 index 00000000..fba227a3 --- /dev/null +++ b/app/lanes/mempool_test.go @@ -0,0 +1,100 @@ +package lanes_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + "cosmossdk.io/math" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsign "github.com/cosmos/cosmos-sdk/x/auth/signing" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + signer_extraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + blockbase "github.com/skip-mev/block-sdk/v2/block/base" + + lanes "github.com/initia-labs/initia/app/lanes" + "github.com/initia-labs/initia/app/params" +) + +func Test_MempoolInsert(t *testing.T) { + ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()).WithConsensusParams(cmtproto.ConsensusParams{ + Block: &cmtproto.BlockParams{ + MaxBytes: 1000000, + MaxGas: 1000000, + }, + }) + + signerExtractor := signer_extraction.NewDefaultAdapter() + encodingConfig := params.MakeEncodingConfig() + txEncoder := encodingConfig.TxConfig.TxEncoder() + + // cannot create mempool with negative ratio + _, err := lanes.NewMempool( + blockbase.NewDefaultTxPriority(), + signerExtractor, + 1, // max txs + math.LegacyNewDecFromInt(math.NewInt(-1)), // max block space + txEncoder, + ) + require.Error(t, err) + + // cannot create mempool with ratio greater than 1 + _, err = lanes.NewMempool( + blockbase.NewDefaultTxPriority(), + signerExtractor, + 1, // max txs + math.LegacyNewDecFromInt(math.NewInt(2)), // max block space + txEncoder, + ) + require.Error(t, err) + + // valid creation + mempool, err := lanes.NewMempool( + blockbase.NewDefaultTxPriority(), + signerExtractor, + 1, // max txs + math.LegacyMustNewDecFromStr("0.01"), // max block space + txEncoder, + ) + require.NoError(t, err) + + priv, _, addr := testdata.KeyTestPubAddr() + defaultSignMode, err := authsign.APISignModeToInternal(encodingConfig.TxConfig.SignModeHandler().DefaultMode()) + require.NoError(t, err) + + // valid gas limit + txBuilder := encodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetGasLimit(10000) + txBuilder.SetMemo("") + txBuilder.SetMsgs(&banktypes.MsgSend{FromAddress: addr.String(), ToAddress: addr.String(), Amount: sdk.Coins{}}) + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: defaultSignMode, + Signature: nil, + }, + Sequence: 1, + } + err = txBuilder.SetSignatures(sigV2) + require.NoError(t, err) + err = mempool.Insert(ctx, txBuilder.GetTx()) + require.NoError(t, err) + + // high gas limit than max gas + txBuilder.SetGasLimit(10001) + err = mempool.Insert(ctx, txBuilder.GetTx()) + require.ErrorContains(t, err, "exceeds max lane gas limit") + + // rollback gas limit and set memo to exceed max block space + txBuilder.SetGasLimit(10000) + txBuilder.SetMemo(string(make([]byte, 10000))) + err = mempool.Insert(ctx, txBuilder.GetTx()) + require.ErrorContains(t, err, "exceeds max lane size") +} diff --git a/app/lanes/system.go b/app/lanes/system.go index 252d4504..8b806985 100644 --- a/app/lanes/system.go +++ b/app/lanes/system.go @@ -26,11 +26,21 @@ func NewSystemLane( lane := &blockbase.BaseLane{} proposalHandler := NewDefaultProposalHandler(lane) + mempool, err := NewMempool( + blockbase.NewDefaultTxPriority(), + cfg.SignerExtractor, + cfg.MaxTxs, + cfg.MaxBlockSpace, + cfg.TxEncoder, + ) + if err != nil { + panic(err) + } _lane, err := blockbase.NewBaseLane( cfg, SystemLaneName, blockbase.WithMatchHandler(matchFn), - blockbase.WithMempool(NewMempool(blockbase.NewDefaultTxPriority(), cfg.SignerExtractor, cfg.MaxTxs)), + blockbase.WithMempool(mempool), blockbase.WithPrepareLaneHandler(proposalHandler.PrepareLaneHandler()), blockbase.WithProcessLaneHandler(proposalHandler.ProcessLaneHandler()), )