Skip to content

Commit

Permalink
feat: return mempool error, if a tx exceeds lane limits (#297)
Browse files Browse the repository at this point in the history
* return mempool error; if a tx exceeds lane limits

* add more assertions
  • Loading branch information
beer-1 authored Nov 6, 2024
1 parent 4de1d33 commit 9141977
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 7 deletions.
13 changes: 12 additions & 1 deletion app/lanes/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
)
Expand Down
13 changes: 12 additions & 1 deletion app/lanes/free.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
)
Expand Down
5 changes: 3 additions & 2 deletions app/lanes/free_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down
62 changes: 60 additions & 2 deletions app/lanes/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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]{
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
100 changes: 100 additions & 0 deletions app/lanes/mempool_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
12 changes: 11 additions & 1 deletion app/lanes/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
)
Expand Down

0 comments on commit 9141977

Please sign in to comment.