From b91cfb617cdb5bac19fecedf265a58cb98688ae8 Mon Sep 17 00:00:00 2001 From: David Terpay <35130517+davidterpay@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:10:09 -0500 Subject: [PATCH] fix: Removing IgnoreList from Lane Interface (#245) * greedy approach to lane verification * docs * base lane testing * mev lane testing nits * abci top level testing done * network spamming in E2E * string rep of escrow address * nit * nit * nit v1.0.1 * removing logs from testing * query test * logging with tx info * nits * nit * nit * testing nit * adding readmes which i will fill out l8r * removing ignore list from convo, ur done * removing logs in testing * nits * eh ig we dont need it rn * removing ignore list from config * nit test --------- Co-authored-by: Alex Johnson --- abci/abci_test.go | 12 --- abci/utils_test.go | 6 +- block/base/config.go | 9 -- block/base/handlers.go | 16 +++ block/base/lane.go | 31 +----- block/lane.go | 6 -- block/mempool.go | 56 ----------- block/mempool_test.go | 72 ++------------ block/mocks/lane.go | 21 ---- block/proposals/proposals_test.go | 2 +- lanes/base/abci_test.go | 152 ++++++++++------------------ lanes/base/lane.go | 7 +- lanes/mev/lane.go | 3 +- lanes/mev/mev_test.go | 2 +- tests/app/README.md | 159 ++++++++++++++++++++++++++++++ tests/app/app.go | 85 ++++------------ tests/app/lanes.go | 93 +++++++++++++++++ 17 files changed, 358 insertions(+), 374 deletions(-) create mode 100644 tests/app/README.md create mode 100644 tests/app/lanes.go diff --git a/abci/abci_test.go b/abci/abci_test.go index 81f5332..b9dd7fb 100644 --- a/abci/abci_test.go +++ b/abci/abci_test.go @@ -532,8 +532,6 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { ) s.Require().NoError(err) - defaultLane.SetIgnoreList(nil) - proposalHandler := abci.NewProposalHandler( log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), @@ -598,8 +596,6 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { ) s.Require().NoError(err) - defaultLane.SetIgnoreList(nil) - proposalHandler := abci.NewProposalHandler( log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), @@ -671,10 +667,6 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { ) s.Require().NoError(err) - panicLane.SetIgnoreList(nil) - panicLane2.SetIgnoreList(nil) - defaultLane.SetIgnoreList(nil) - proposalHandler := abci.NewProposalHandler( log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), @@ -746,10 +738,6 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { ) s.Require().NoError(err) - panicLane.SetIgnoreList(nil) - panicLane2.SetIgnoreList(nil) - defaultLane.SetIgnoreList(nil) - proposalHandler := abci.NewProposalHandler( log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), diff --git a/abci/utils_test.go b/abci/utils_test.go index a22815a..8fcc527 100644 --- a/abci/utils_test.go +++ b/abci/utils_test.go @@ -85,11 +85,10 @@ func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, exp TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), MaxBlockSpace: maxBlockSpace, - IgnoreList: make([]block.Lane, 0), SignerExtractor: signeradaptors.NewDefaultAdapter(), } - return defaultlane.NewDefaultLane(cfg) + return defaultlane.NewDefaultLane(cfg, base.DefaultMatchHandler()) } func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *mev.MEVLane { @@ -102,7 +101,8 @@ func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expected SignerExtractor: signeradaptors.NewDefaultAdapter(), } - return mev.NewMEVLane(cfg, mev.NewDefaultAuctionFactory(cfg.TxDecoder, signeradaptors.NewDefaultAdapter())) + factory := mev.NewDefaultAuctionFactory(cfg.TxDecoder, signeradaptors.NewDefaultAdapter()) + return mev.NewMEVLane(cfg, factory, factory.MatchHandler()) } func (s *ProposalsTestSuite) setUpFreeLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *free.FreeLane { diff --git a/block/base/config.go b/block/base/config.go index 9275efe..2b0485a 100644 --- a/block/base/config.go +++ b/block/base/config.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" signer_extraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" - "github.com/skip-mev/block-sdk/block" ) // LaneConfig defines the basic configurations needed for a lane. @@ -28,14 +27,6 @@ type LaneConfig struct { // lane (up to maxTxBytes as provided by the request). This is useful for the default lane. MaxBlockSpace math.LegacyDec - // IgnoreList defines the list of lanes to ignore when processing transactions. This - // is useful for when you want lanes to exist after the default lane. For example, - // say there are two lanes: default and free. The free lane should be processed after - // the default lane. In this case, the free lane should be added to the ignore list - // of the default lane. Otherwise, the transactions that belong to the free lane - // will be processed by the default lane (which accepts all transactions by default). - IgnoreList []block.Lane - // MaxTxs sets the maximum number of transactions allowed in the mempool with // the semantics: // - if MaxTx == 0, there is no cap on the number of transactions in the mempool diff --git a/block/base/handlers.go b/block/base/handlers.go index 110a9a9..9308c52 100644 --- a/block/base/handlers.go +++ b/block/base/handlers.go @@ -170,3 +170,19 @@ func DefaultMatchHandler() MatchHandler { return true } } + +// NewMatchHandler returns a match handler that matches transactions +// that match the lane and do not match with any of the provided match handlers. +// In the context of building an application, you would want to use this to +// ignore the match handlers of other lanes in the application. +func NewMatchHandler(mh MatchHandler, ignoreMHs ...MatchHandler) MatchHandler { + return func(ctx sdk.Context, tx sdk.Tx) bool { + for _, ignoreMH := range ignoreMHs { + if ignoreMH(ctx, tx) { + return false + } + } + + return mh(ctx, tx) + } +} diff --git a/block/base/lane.go b/block/base/lane.go index e6b0568..0536957 100644 --- a/block/base/lane.go +++ b/block/base/lane.go @@ -121,24 +121,7 @@ func (l *BaseLane) SetProcessLaneHandler(processLaneHandler ProcessLaneHandler) // if the transaction is on the ignore list. If the transaction is on the ignore // list, it returns false. func (l *BaseLane) Match(ctx sdk.Context, tx sdk.Tx) bool { - return l.matchHandler(ctx, tx) && !l.CheckIgnoreList(ctx, tx) -} - -// CheckIgnoreList returns true if the transaction is on the ignore list. The ignore -// list is utilized to prevent transactions that should be considered in other lanes -// from being considered from this lane. -func (l *BaseLane) CheckIgnoreList(ctx sdk.Context, tx sdk.Tx) bool { - if l.cfg.IgnoreList == nil { - return false - } - - for _, lane := range l.cfg.IgnoreList { - if lane.Match(ctx, tx) { - return true - } - } - - return false + return l.matchHandler(ctx, tx) } // Name returns the name of the lane. @@ -146,18 +129,6 @@ func (l *BaseLane) Name() string { return l.laneName } -// SetIgnoreList sets the ignore list for the lane. The ignore list is a list -// of lanes that the lane should ignore when processing transactions. -func (l *BaseLane) SetIgnoreList(lanes []block.Lane) { - l.cfg.IgnoreList = lanes -} - -// GetIgnoreList returns the ignore list for the lane. The ignore list is a list -// of lanes that the lane should ignore when processing transactions. -func (l *BaseLane) GetIgnoreList() []block.Lane { - return l.cfg.IgnoreList -} - // SetAnteHandler sets the ante handler for the lane. func (l *BaseLane) SetAnteHandler(anteHandler sdk.AnteHandler) { l.cfg.AnteHandler = anteHandler diff --git a/block/lane.go b/block/lane.go index 7415547..56a88ca 100644 --- a/block/lane.go +++ b/block/lane.go @@ -70,12 +70,6 @@ type Lane interface { // SetAnteHandler sets the lane's antehandler. SetAnteHandler(antehander sdk.AnteHandler) - // SetIgnoreList sets the lanes that should be ignored by this lane. - SetIgnoreList(ignoreList []Lane) - - // GetIgnoreList returns the lanes that should be ignored by this lane. - GetIgnoreList() []Lane - // Match determines if a transaction belongs to this lane. Match(ctx sdk.Context, tx sdk.Tx) bool diff --git a/block/mempool.go b/block/mempool.go index e82faea..b18c31d 100644 --- a/block/mempool.go +++ b/block/mempool.go @@ -12,12 +12,6 @@ import ( blocksdkmoduletypes "github.com/skip-mev/block-sdk/x/blocksdk/types" ) -const ( - // DefaultLaneName is the default lane name. We enforce that a lane with the name - // "default" is provided when constructing the mempool. - DefaultLaneName = "default" -) - var _ Mempool = (*LanedMempool)(nil) // LaneFetcher defines the interface to get a lane stored in the x/blocksdk module. @@ -62,61 +56,11 @@ type ( // to its own selection logic. The lanes are ordered according to their priority. The // first lane in the registry has the highest priority. Proposals are verified according // to the order of the lanes in the registry. Each transaction SHOULD only belong in one lane. -// To enforce that transactions only belong to one lane, each lane has an ignore list. -// -// For example, say we have three lanes, MEV, default, and free. The ignore list of each -// lane will look like the following: -// - MEV: free -// - default: MEV, free -// - free: MEV. -// -// Note that a name with the value "default" MUST be provided. func NewLanedMempool( logger log.Logger, lanes []Lane, laneFetcher LaneFetcher, ) (*LanedMempool, error) { - laneCache := make(map[Lane]struct{}) - seenDefault := false - - // Ensure that each of the lanes are mutually exclusive. The default lane should - // ignore all other lanes, while all other lanes should ignore every lane except - // the default lane. - for index, lane := range lanes { - if lane.Name() == DefaultLaneName { - lowerIgnoreList := make([]Lane, index) - copy(lowerIgnoreList, lanes[:index]) - - upperIgnoreList := make([]Lane, len(lanes)-index-1) - copy(upperIgnoreList, lanes[index+1:]) - - lane.SetIgnoreList(append(lowerIgnoreList, upperIgnoreList...)) - seenDefault = true - } else { - laneCache[lane] = struct{}{} - } - } - - if !seenDefault { - return nil, fmt.Errorf("default lane not found. a lane with the name %s must be provided", DefaultLaneName) - } - - for _, lane := range lanes { - if lane.Name() == DefaultLaneName { - continue - } - - delete(laneCache, lane) - - ignoreList := make([]Lane, 0) - for otherLane := range laneCache { - ignoreList = append(ignoreList, otherLane) - } - - lane.SetIgnoreList(ignoreList) - laneCache[lane] = struct{}{} - } - mempool := &LanedMempool{ logger: logger, registry: lanes, diff --git a/block/mempool_test.go b/block/mempool_test.go index 46c2f36..08203c6 100644 --- a/block/mempool_test.go +++ b/block/mempool_test.go @@ -78,9 +78,11 @@ func (suite *BlockBusterTestSuite) SetupTest() { AnteHandler: nil, MaxBlockSpace: math.LegacyZeroDec(), } + factory := mev.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()) suite.mevLane = mev.NewMEVLane( mevConfig, - mev.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()), + factory, + factory.MatchHandler(), ) suite.mevSDKLane = blocksdkmoduletypes.Lane{ @@ -121,6 +123,7 @@ func (suite *BlockBusterTestSuite) SetupTest() { } suite.baseLane = defaultlane.NewDefaultLane( baseConfig, + base.DefaultMatchHandler(), ) suite.baseSDKLane = blocksdkmoduletypes.Lane{ @@ -169,10 +172,12 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { MaxBlockSpace: math.LegacyZeroDec(), } - defaultLane := defaultlane.NewDefaultLane(baseConfig) + defaultLane := defaultlane.NewDefaultLane(baseConfig, base.DefaultMatchHandler()) + factory := mev.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()) mevLane := mev.NewMEVLane( baseConfig, - mev.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()), + factory, + factory.MatchHandler(), ) freeLane := free.NewFreeLane( baseConfig, @@ -189,9 +194,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(0, len(ignoreList)) }) suite.Run("works mev and default lane", func() { @@ -203,12 +205,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - - ignoreList = mevLane.GetIgnoreList() - suite.Require().Equal(0, len(ignoreList)) }) suite.Run("works mev and default lane in reverse order", func() { @@ -220,12 +216,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - - ignoreList = mevLane.GetIgnoreList() - suite.Require().Equal(0, len(ignoreList)) }) suite.Run("works with mev, free, and default lane", func() { @@ -237,17 +227,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(2, len(ignoreList)) - - ignoreList = mevLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(freeLane, ignoreList[0]) - - ignoreList = freeLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(mevLane, ignoreList[0]) }) suite.Run("works with mev, default, free lane", func() { @@ -259,17 +238,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(2, len(ignoreList)) - - ignoreList = mevLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(freeLane, ignoreList[0]) - - ignoreList = freeLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(mevLane, ignoreList[0]) }) suite.Run("works with free, mev, and default lane", func() { @@ -281,17 +249,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(2, len(ignoreList)) - - ignoreList = mevLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(freeLane, ignoreList[0]) - - ignoreList = freeLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(mevLane, ignoreList[0]) }) suite.Run("works with default, free, mev lanes", func() { @@ -303,17 +260,6 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { fetcher, ) suite.Require().NoError(err) - - ignoreList := defaultLane.GetIgnoreList() - suite.Require().Equal(2, len(ignoreList)) - - ignoreList = mevLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(freeLane, ignoreList[0]) - - ignoreList = freeLane.GetIgnoreList() - suite.Require().Equal(1, len(ignoreList)) - suite.Require().Equal(mevLane, ignoreList[0]) }) suite.Run("default lane not included", func() { @@ -324,7 +270,7 @@ func (suite *BlockBusterTestSuite) TestNewMempool() { lanes, fetcher, ) - suite.Require().Error(err) + suite.Require().NoError(err) }) suite.Run("duplicate lanes", func() { diff --git a/block/mocks/lane.go b/block/mocks/lane.go index 8002e31..5f91edb 100644 --- a/block/mocks/lane.go +++ b/block/mocks/lane.go @@ -77,22 +77,6 @@ func (_m *Lane) CountTx() int { return r0 } -// GetIgnoreList provides a mock function with given fields: -func (_m *Lane) GetIgnoreList() []block.Lane { - ret := _m.Called() - - var r0 []block.Lane - if rf, ok := ret.Get(0).(func() []block.Lane); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]block.Lane) - } - } - - return r0 -} - // GetMaxBlockSpace provides a mock function with given fields: func (_m *Lane) GetMaxBlockSpace() math.LegacyDec { ret := _m.Called() @@ -272,11 +256,6 @@ func (_m *Lane) SetAnteHandler(antehander types.AnteHandler) { _m.Called(antehander) } -// SetIgnoreList provides a mock function with given fields: ignoreList -func (_m *Lane) SetIgnoreList(ignoreList []block.Lane) { - _m.Called(ignoreList) -} - // SetMaxBlockSpace provides a mock function with given fields: _a0 func (_m *Lane) SetMaxBlockSpace(_a0 math.LegacyDec) { _m.Called(_a0) diff --git a/block/proposals/proposals_test.go b/block/proposals/proposals_test.go index 61a8381..29c5408 100644 --- a/block/proposals/proposals_test.go +++ b/block/proposals/proposals_test.go @@ -591,7 +591,7 @@ func getTxsWithInfo(txs []sdk.Tx) ([]utils.TxWithInfo, error) { signerextraction.NewDefaultAdapter(), math.LegacyNewDec(1), ) - lane := defaultlane.NewDefaultLane(cfg) + lane := defaultlane.NewDefaultLane(cfg, base.DefaultMatchHandler()) txsWithInfo := make([]utils.TxWithInfo, len(txs)) for i, tx := range txs { diff --git a/lanes/base/abci_test.go b/lanes/base/abci_test.go index ab9c6c2..877fdb9 100644 --- a/lanes/base/abci_test.go +++ b/lanes/base/abci_test.go @@ -10,7 +10,6 @@ import ( "cosmossdk.io/log" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/mock" signer_extraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" "github.com/skip-mev/block-sdk/block" @@ -539,23 +538,19 @@ func (s *BaseTestSuite) TestPrepareLane() { ) s.Require().NoError(err) + mh := func(ctx sdk.Context, tx sdk.Tx) bool { + return true + } + // Create a lane with a max block space of 1 but a proposal that is smaller than the tx expectedExecution := map[sdk.Tx]bool{ tx: true, } - lane := s.initLane(math.LegacyOneDec(), expectedExecution) + lane := s.initLaneWithMatchHandlers(math.LegacyOneDec(), expectedExecution, []base.MatchHandler{mh}) // Insert the transaction into the lane s.Require().NoError(lane.Insert(s.ctx, tx)) - mockLane := mocks.NewLane(s.T()) - mockLane.On( - "Match", - mock.Anything, - tx, - ).Return(true, nil) - lane.SetIgnoreList([]block.Lane{mockLane}) - txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) s.Require().NoError(err) @@ -1060,27 +1055,18 @@ func (s *BaseTestSuite) TestProcessLane() { s.Require().NoError(err) // First lane matches this lane the other does not. - otherLane := mocks.NewLane(s.T()) - otherLane.On( - "Match", - mock.Anything, - tx1, - ).Return(true, nil) + mh := func(ctx sdk.Context, tx sdk.Tx) bool { + return tx == tx1 + } - otherLane.On( - "Match", - mock.Anything, - tx2, - ).Return(false, nil) - - lane := s.initLane( + lane := s.initLaneWithMatchHandlers( math.LegacyOneDec(), map[sdk.Tx]bool{ tx1: true, tx2: true, }, + []base.MatchHandler{mh}, ) - lane.SetIgnoreList([]block.Lane{otherLane}) proposal := []sdk.Tx{ tx1, @@ -1332,39 +1318,22 @@ func (s *BaseTestSuite) TestProcessLane() { tx4, } - otherLane := mocks.NewLane(s.T()) - otherLane.On( - "Match", - mock.Anything, - tx1, - ).Return(false, nil) + mh := func(ctx sdk.Context, tx sdk.Tx) bool { + if tx == tx1 || tx == tx2 { + return false + } - otherLane.On( - "Match", - mock.Anything, - tx2, - ).Return(false, nil) + return true + } - otherLane.On( - "Match", - mock.Anything, - tx3, - ).Return(true, nil) - - otherLane.On( - "Match", - mock.Anything, - tx4, - ).Return(true, nil) - - lane := s.initLane( + lane := s.initLaneWithMatchHandlers( math.LegacyOneDec(), map[sdk.Tx]bool{ tx1: true, tx2: true, }, + []base.MatchHandler{mh}, ) - lane.SetIgnoreList([]block.Lane{otherLane}) txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) s.Require().NoError(err) @@ -1436,36 +1405,15 @@ func (s *BaseTestSuite) TestProcessLane() { tx4, } - otherLane := mocks.NewLane(s.T()) - otherLane.On( - "Match", - mock.Anything, - tx1, - ).Return(true, nil) + mh := func(ctx sdk.Context, tx sdk.Tx) bool { + return true + } - otherLane.On( - "Match", - mock.Anything, - tx2, - ).Return(true, nil) - - otherLane.On( - "Match", - mock.Anything, - tx3, - ).Return(true, nil) - - otherLane.On( - "Match", - mock.Anything, - tx4, - ).Return(true, nil) - - lane := s.initLane( + lane := s.initLaneWithMatchHandlers( math.LegacyOneDec(), map[sdk.Tx]bool{}, + []base.MatchHandler{mh}, ) - lane.SetIgnoreList([]block.Lane{otherLane}) txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) s.Require().NoError(err) @@ -1533,32 +1481,15 @@ func (s *BaseTestSuite) TestProcessLane() { tx4, } - otherLane := mocks.NewLane(s.T()) - otherLane.On( - "Match", - mock.Anything, - tx1, - ).Return(false, nil) + mh := func(ctx sdk.Context, tx sdk.Tx) bool { + if tx == tx1 || tx == tx3 { + return false + } - otherLane.On( - "Match", - mock.Anything, - tx2, - ).Return(true, nil) + return true + } - otherLane.On( - "Match", - mock.Anything, - tx3, - ).Return(false, nil).Maybe() - - otherLane.On( - "Match", - mock.Anything, - tx4, - ).Return(true, nil).Maybe() - - lane := s.initLane( + lane := s.initLaneWithMatchHandlers( math.LegacyOneDec(), map[sdk.Tx]bool{ tx1: true, @@ -1566,8 +1497,8 @@ func (s *BaseTestSuite) TestProcessLane() { tx3: true, tx4: true, }, + []base.MatchHandler{mh}, ) - lane.SetIgnoreList([]block.Lane{otherLane}) txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) s.Require().Error(err) @@ -1739,7 +1670,26 @@ func (s *BaseTestSuite) initLane( maxBlockSpace, ) - return defaultlane.NewDefaultLane(config) + return defaultlane.NewDefaultLane(config, base.DefaultMatchHandler()) +} + +func (s *BaseTestSuite) initLaneWithMatchHandlers( + maxBlockSpace math.LegacyDec, + expectedExecution map[sdk.Tx]bool, + matchHandlers []base.MatchHandler, +) *defaultlane.DefaultLane { + config := base.NewLaneConfig( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + s.encodingConfig.TxConfig.TxDecoder(), + s.setUpAnteHandler(expectedExecution), + signer_extraction.NewDefaultAdapter(), + maxBlockSpace, + ) + + mh := base.NewMatchHandler(base.DefaultMatchHandler(), matchHandlers...) + + return defaultlane.NewDefaultLane(config, mh) } func (s *BaseTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) sdk.AnteHandler { diff --git a/lanes/base/lane.go b/lanes/base/lane.go index eb8f38d..ce28595 100644 --- a/lanes/base/lane.go +++ b/lanes/base/lane.go @@ -13,8 +13,7 @@ const ( var _ block.Lane = (*DefaultLane)(nil) // DefaultLane defines a default lane implementation. The default lane orders -// transactions by the transaction fees. The default lane accepts any transaction -// that should not be ignored (as defined by the IgnoreList in the LaneConfig). +// transactions by the transaction fees. The default lane accepts any transaction. // The default lane builds and verifies blocks in a similar fashion to how the // CometBFT/Tendermint consensus engine builds and verifies blocks pre SDK version // 0.47.0. @@ -23,7 +22,7 @@ type DefaultLane struct { } // NewDefaultLane returns a new default lane. -func NewDefaultLane(cfg base.LaneConfig) *DefaultLane { +func NewDefaultLane(cfg base.LaneConfig, matchHandler base.MatchHandler) *DefaultLane { lane := base.NewBaseLane( cfg, LaneName, @@ -33,7 +32,7 @@ func NewDefaultLane(cfg base.LaneConfig) *DefaultLane { cfg.SignerExtractor, cfg.MaxTxs, ), - base.DefaultMatchHandler(), + matchHandler, ) if err := lane.ValidateBasic(); err != nil { diff --git a/lanes/mev/lane.go b/lanes/mev/lane.go index 63ed327..c4a8d8c 100644 --- a/lanes/mev/lane.go +++ b/lanes/mev/lane.go @@ -45,6 +45,7 @@ type ( func NewMEVLane( cfg base.LaneConfig, factory Factory, + matchHandler base.MatchHandler, ) *MEVLane { lane := &MEVLane{ BaseLane: base.NewBaseLane( @@ -56,7 +57,7 @@ func NewMEVLane( cfg.SignerExtractor, cfg.MaxTxs, ), - factory.MatchHandler(), + matchHandler, ), Factory: factory, } diff --git a/lanes/mev/mev_test.go b/lanes/mev/mev_test.go index d9f37bc..ea500c2 100644 --- a/lanes/mev/mev_test.go +++ b/lanes/mev/mev_test.go @@ -64,7 +64,7 @@ func (s *MEVTestSuite) initLane( ) factory := mev.NewDefaultAuctionFactory(s.encCfg.TxConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()) - return mev.NewMEVLane(config, factory) + return mev.NewMEVLane(config, factory, factory.MatchHandler()) } func (s *MEVTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) sdk.AnteHandler { diff --git a/tests/app/README.md b/tests/app/README.md new file mode 100644 index 0000000..2a2eaa8 --- /dev/null +++ b/tests/app/README.md @@ -0,0 +1,159 @@ +# Block SDK Enabled Test App (10 min) + +## Overview + +This readme describes how to build a test app that uses the Block SDK. This assumes that you have already installed the Block SDK and have a working development environment. To install the Block SDK, please run the following: + +```bash +go get github.com/skip-mev/block-sdk +``` + +## Building the Test App + +There are fix critical steps to building a test app that uses the Block SDK: + +1. Set up the signer extractor. +2. Create the lane configurations for each individual lane i.e. `LaneConfig`. +3. Configure the match handlers for each lane i.e. `MatchHandler`. +4. Creating the Block SDK mempool i.e. `LanedMempool`. +5. Setting the antehandlers - used for transaction validation - for each lane. +6. Setting the proposal handlers - used for block creation and verification - for the application to utilize the Block SDK's Prepare and Process Proposal handlers. + +### 1. Signer Extractor + +The signer extractor is responsible for extracting signers and relevant information about who is signing the transaction. We recommend using the default implementation provided by the Block SDK. + +```go +signerAdapter := signerextraction.NewDefaultAdapter() +``` + +### 2. Lane Configurations + +This controls how many transactions can be stored by each lane, how much block space is allocated to each lane, how to extract transacation information such as signers, fees, and more. Each lane should have a separate `LaneConfig` object. + +For example, in [`lanes.go`](./lanes.go) we see the following: + +```go +mevConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: app.txConfig.TxEncoder(), + TxDecoder: app.txConfig.TxDecoder(), + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), + SignerExtractor: signerAdapter, + MaxTxs: 1000, +} +``` + +Following the example above: + +* `Logger`: This is the logger that will be utilized by the lane when outputting information as blocks are being processed and constructed. +* `TxEncoder`: This is the encoder that will be used to encode transactions. +* `TxDecoder`: This is the decoder that will be used to decode transactions. +* `MaxBlockSpace`: This is the maximum amount of block space that can be allocated to this lane. In this case, we allocate 20% of the block space to this lane. +* `SignerExtractor`: This is the signer extractor that will be used to extract signers from transactions. In this case, we utilize the default signer extractor provided by the Block SDK. **This is the recommended approach.** +* `MaxTxs`: This is the maximum number of transactions that can be stored in this lane. In this case, we allow up to 1000 transactions to be stored in this lane at any given time. + +### Match Handlers + +Match handlers are responsible for matching transactions to lanes. Each lane should have a unique match handler. By default, we recommend that the default lane be the last lane in your application. This is because the default lane matches all transactions that do not match to any of the other lanes. If you want to have a lane after the default lane, please see the section below. + +#### (OPTIONAL) Having Lanes after the Default Lane + +If you want to have lanes after the default lane, you will need to utilize the `base.NewMatchHandler` function. This function allows you to construct a match handler that can ignore other lane's match handlers. + +For example, if we wanted the free and MEV lanes to be processed after the default lane - default, MEV, free - we can do the following: + +```go +// Create the final match handler for the default lane. +defaultMatchHandler := base.NewMatchHandler( + base.DefaultMatchHandler(), + factory.MatchHandler(), + freelane.DefaultMatchHandler(), +) +``` + +Following the example, we can see the following: + +* `base.DefaultMatchHandler()`: This is the default match handler provided by the Block SDK. This matches all transactions to the lane. +* `factory.MatchHandler()`: This is the MEV lane's match handler. This is passed as a parameter to the `base.NewMatchHandler` function - which means that all transactions that match to the MEV lane will be ignored by the default match handler. +* `freelane.DefaultMatchHandler()`: This is the default match handler for the free lane. This is passed as a parameter to the `base.NewMatchHandler` function - which means that all transactions that match to the free lane will be ignored by the default match handler. + +**This will allow the default match handler to only match transactions that do not match to the MEV lane or the free lane.** + +### Block SDK Mempool + +After constructing the lanes, we can create the Block SDK mempool - `LanedMempool`. This object is responsible for managing the lanes and processing transactions. + +```go +// STEP 1: Create the Block SDK lanes. +mevLane, freeLane, defaultLane := CreateLanes(app) + +// STEP 2: Construct a mempool based off the lanes. +mempool, err := block.NewLanedMempool( + app.Logger(), + []block.Lane{mevLane, freeLane, defaultLane}, + &app.blocksdkKeeper, +) +if err != nil { + panic(err) +} + +// STEP 3: Set the mempool on the app. +app.App.SetMempool(mempool) +``` + +Note that we pass the lanes to the `block.NewLanedMempool` function. **The order of the lanes is important.** Proposals will be constructed based on the order of lanes passed to the `block.NewLanedMempool` function. In the example above, the MEV lane will be processed first, followed by the free lane, and finally the default lane. + +### AnteHandlers + +`AnteHandlers` are responsible for validating transactions. We recommend that developers utilize the same antehandler chain that is used by the application. In the example test app, we construct the `AnteHandler` with `NewBSDKAnteHandler`. In the case where the certain ante decorators should ignore certain lanes, we can wrap a `Decorator` with the `block.NewIgnoreDecorator` function as seen in `ante.go`. + +After constructing the `AnteHandler`, we can set it on the application and on the lanes. + +```go +// STEP 4: Create a global ante handler that will be called on each transaction when +// proposals are being built and verified. Note that this step must be done before +// setting the ante handler on the lanes. +handlerOptions := ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + SignModeHandler: app.txConfig.SignModeHandler(), +} +options := BSDKHandlerOptions{ + BaseOptions: handlerOptions, + auctionkeeper: app.auctionkeeper, + TxDecoder: app.txConfig.TxDecoder(), + TxEncoder: app.txConfig.TxEncoder(), + FreeLane: freeLane, + MEVLane: mevLane, +} +anteHandler := NewBSDKAnteHandler(options) +app.App.SetAnteHandler(anteHandler) + +// Set the AnteHandlers on the lanes. +mevLane.SetAnteHandler(anteHandler) +freeLane.SetAnteHandler(anteHandler) +defaultLane.SetAnteHandler(anteHandler) +``` + +### Proposal Handlers + +The proposal handlers - `PrepareProposal` and `ProcessProposal` - are responsible for building and verifying block proposals. To add it to your application, follow the example below: + +```go +// Step 5: Create the proposal handler and set it on the app. +proposalHandler := abci.NewProposalHandler( + app.Logger(), + app.TxConfig().TxDecoder(), + app.TxConfig().TxEncoder(), + mempool, +) +app.App.SetPrepareProposal(proposalHandler.PrepareProposalHandler()) +app.App.SetProcessProposal(proposalHandler.ProcessProposalHandler()) +``` + +## Conclusion + +Adding the Block SDK to your application is a simple 6 step process. If you have any questions, please feel free to reach out to the [Skip team](https://skip.money/contact). We are happy to help! diff --git a/tests/app/app.go b/tests/app/app.go index 5a3ab5b..c7edc79 100644 --- a/tests/app/app.go +++ b/tests/app/app.go @@ -6,7 +6,6 @@ import ( "path/filepath" "cosmossdk.io/log" - "cosmossdk.io/math" dbm "github.com/cosmos/cosmos-db" "cosmossdk.io/depinject" @@ -43,11 +42,7 @@ import ( stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/skip-mev/block-sdk/abci" - signer_extraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" "github.com/skip-mev/block-sdk/block" - "github.com/skip-mev/block-sdk/block/base" - defaultlane "github.com/skip-mev/block-sdk/lanes/base" - "github.com/skip-mev/block-sdk/lanes/free" "github.com/skip-mev/block-sdk/lanes/mev" auctionkeeper "github.com/skip-mev/block-sdk/x/auction/keeper" blocksdkkeeper "github.com/skip-mev/block-sdk/x/blocksdk/keeper" @@ -213,71 +208,27 @@ func New( // ---------------------------------------------------------------------------- // // ------------------------- Begin Custom Code -------------------------------- // // ---------------------------------------------------------------------------- // + // STEP 1-3: Create the Block SDK lanes. + mevLane, freeLane, defaultLane := CreateLanes(app) - // Set POB's mempool into the app. - // Create the lanes. - // - // NOTE: The lanes are ordered by priority. The first lane is the highest priority - // lane and the last lane is the lowest priority lane. - // MEV lane allows transactions to bid for inclusion at the top of the next block. - mevConfig := base.LaneConfig{ - Logger: app.Logger(), - TxEncoder: app.txConfig.TxEncoder(), - TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), - SignerExtractor: signer_extraction.NewDefaultAdapter(), - MaxTxs: 1000, - } - mevLane := mev.NewMEVLane( - mevConfig, - mev.NewDefaultAuctionFactory(app.txConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()), - ) - - // Free lane allows transactions to be included in the next block for free. - freeConfig := base.LaneConfig{ - Logger: app.Logger(), - TxEncoder: app.txConfig.TxEncoder(), - TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), - SignerExtractor: signer_extraction.NewDefaultAdapter(), - MaxTxs: 1000, - } - freeLane := free.NewFreeLane( - freeConfig, - base.DefaultTxPriority(), - free.DefaultMatchHandler(), - ) - - // Default lane accepts all other transactions. - defaultConfig := base.LaneConfig{ - Logger: app.Logger(), - TxEncoder: app.txConfig.TxEncoder(), - TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyMustNewDecFromStr("0.6"), - SignerExtractor: signer_extraction.NewDefaultAdapter(), - MaxTxs: 1000, - } - defaultLane := defaultlane.NewDefaultLane(defaultConfig) - - // Set the lanes into the mempool. - lanes := []block.Lane{ - mevLane, - freeLane, - defaultLane, - } + // STEP 4: Construct a mempool based off the lanes. Note that the order of the lanes + // matters. Blocks are constructed from the top lane to the bottom lane. The top lane + // is the first lane in the array and the bottom lane is the last lane in the array. mempool, err := block.NewLanedMempool( app.Logger(), - lanes, + []block.Lane{mevLane, freeLane, defaultLane}, &app.blocksdkKeeper, ) if err != nil { panic(err) } + // The application's mempool is now powered by the Block SDK! app.App.SetMempool(mempool) - // Create a global ante handler that will be called on each transaction when - // proposals are being built and verified. + // STEP 5: Create a global ante handler that will be called on each transaction when + // proposals are being built and verified. Note that this step must be done before + // setting the ante handler on the lanes. handlerOptions := ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, @@ -294,14 +245,15 @@ func New( MEVLane: mevLane, } anteHandler := NewBSDKAnteHandler(options) - - // Set the lane config on the lanes. - for _, lane := range lanes { - lane.SetAnteHandler(anteHandler) - } app.App.SetAnteHandler(anteHandler) - // Set the abci handlers on base app + // Set the ante handler on the lanes. + mevLane.SetAnteHandler(anteHandler) + freeLane.SetAnteHandler(anteHandler) + defaultLane.SetAnteHandler(anteHandler) + + // Step 6: Create the proposal handler and set it on the app. Now the application + // will build and verify proposals using the Block SDK! proposalHandler := abci.NewProposalHandler( app.Logger(), app.TxConfig().TxDecoder(), @@ -311,7 +263,8 @@ func New( app.App.SetPrepareProposal(proposalHandler.PrepareProposalHandler()) app.App.SetProcessProposal(proposalHandler.ProcessProposalHandler()) - // Set the custom CheckTx handler on BaseApp. + // Step 7: Set the custom CheckTx handler on BaseApp. This is only required if you + // use the MEV lane. checkTxHandler := mev.NewCheckTxHandler( app.App, app.txConfig.TxDecoder(), diff --git a/tests/app/lanes.go b/tests/app/lanes.go new file mode 100644 index 0000000..44e3423 --- /dev/null +++ b/tests/app/lanes.go @@ -0,0 +1,93 @@ +package app + +import ( + "cosmossdk.io/math" + signerextraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" + "github.com/skip-mev/block-sdk/block/base" + defaultlane "github.com/skip-mev/block-sdk/lanes/base" + freelane "github.com/skip-mev/block-sdk/lanes/free" + mevlane "github.com/skip-mev/block-sdk/lanes/mev" +) + +// CreateLanes walks through the process of creating the lanes for the block sdk. In this function +// we create three separate lanes - MEV, Free, and Default - and then return them. +// +// NOTE: Application Developers should closely replicate this function in their own application. +func CreateLanes(app *TestApp) (*mevlane.MEVLane, *freelane.FreeLane, *defaultlane.DefaultLane) { + // 1. Create the signer extractor. This is used to extract the expected signers from + // a transaction. Each lane can have a different signer extractor if needed. + signerAdapter := signerextraction.NewDefaultAdapter() + + // 2. Create the configurations for each lane. These configurations determine how many + // transactions the lane can store, the maximum block space the lane can consume, and + // the signer extractor used to extract the expected signers from a transaction. + // + // IMPORTANT NOTE: If the block sdk module is utilized to store lanes, than the maximum + // block space will be replaced with what is in state / in the genesis file. + + // Create a mev configuration that accepts 1000 transactions and consumes 20% of the + // block space. + mevConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: app.txConfig.TxEncoder(), + TxDecoder: app.txConfig.TxDecoder(), + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), + SignerExtractor: signerAdapter, + MaxTxs: 1000, + } + + // Create a free configuration that accepts 1000 transactions and consumes 20% of the + // block space. + freeConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: app.txConfig.TxEncoder(), + TxDecoder: app.txConfig.TxDecoder(), + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), + SignerExtractor: signerAdapter, + MaxTxs: 1000, + } + + // Create a default configuration that accepts 1000 transactions and consumes 60% of the + // block space. + defaultConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: app.txConfig.TxEncoder(), + TxDecoder: app.txConfig.TxDecoder(), + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.6"), + SignerExtractor: signerAdapter, + MaxTxs: 1000, + } + + // 3. Create the match handlers for each lane. These match handlers determine whether or not + // a transaction belongs in the lane. + + // Create the final match handler for the mev lane. + factory := mevlane.NewDefaultAuctionFactory(app.txConfig.TxDecoder(), signerAdapter) + mevMatchHandler := factory.MatchHandler() + + // Create the final match handler for the free lane. + freeMatchHandler := freelane.DefaultMatchHandler() + + // Create the final match handler for the default lane. + defaultMatchHandler := base.DefaultMatchHandler() + + // 4. Create the lanes. + mevLane := mevlane.NewMEVLane( + mevConfig, + factory, + mevMatchHandler, + ) + + freeLane := freelane.NewFreeLane( + freeConfig, + base.DefaultTxPriority(), + freeMatchHandler, + ) + + defaultLane := defaultlane.NewDefaultLane( + defaultConfig, + defaultMatchHandler, + ) + + return mevLane, freeLane, defaultLane +}