From 4abf08eefdaca1422e9a8ef8b32b93e956ea7cda Mon Sep 17 00:00:00 2001 From: David Terpay Date: Tue, 21 Nov 2023 17:46:39 -0600 Subject: [PATCH 01/17] greedy approach to lane verification --- abci/abci.go | 41 +- abci/abci_test.go | 641 +++---------------------------- abci/utils.go | 103 +---- abci/utils_test.go | 77 +--- block/base/abci.go | 32 +- block/base/handlers.go | 42 +- block/base/types.go | 12 +- block/lane.go | 2 +- block/mocks/lane.go | 18 +- block/types.go | 4 +- lanes/base/abci_test.go | 290 +++++++++++--- lanes/mev/abci.go | 41 +- lanes/mev/abci_test.go | 31 +- lanes/terminator/lane.go | 7 +- tests/integration/chain_setup.go | 16 +- 15 files changed, 413 insertions(+), 944 deletions(-) diff --git a/abci/abci.go b/abci/abci.go index b8d6a813..b9836da7 100644 --- a/abci/abci.go +++ b/abci/abci.go @@ -9,11 +9,7 @@ import ( "github.com/skip-mev/block-sdk/block" "github.com/skip-mev/block-sdk/block/proposals" -) - -const ( - // ProposalInfoIndex is the index of the proposal metadata in the proposal. - ProposalInfoIndex = 0 + "github.com/skip-mev/block-sdk/block/utils" ) type ( @@ -87,16 +83,9 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err } - // Retrieve the proposal with metadata and transactions. - txs, err := finalProposal.GetProposalWithInfo() - if err != nil { - h.logger.Error("failed to get proposal with metadata", "err", err) - return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err - } - h.logger.Info( "prepared proposal", - "num_txs", len(txs), + "num_txs", len(finalProposal.Txs), "total_tx_bytes", finalProposal.Info.BlockSize, "max_tx_bytes", finalProposal.Info.MaxBlockSize, "total_gas_limit", finalProposal.Info.GasLimit, @@ -111,7 +100,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { ) return &abci.ResponsePrepareProposal{ - Txs: txs, + Txs: finalProposal.Txs, }, nil } } @@ -138,10 +127,10 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { } }() - // Extract all of the lanes and their corresponding transactions from the proposal. - proposalInfo, partialProposals, err := h.ExtractLanes(ctx, req.Txs) + // Decode the transactions in the proposal. These will be verified by each lane in a greedy fashion. + decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs) if err != nil { - h.logger.Error("failed to validate proposal", "err", err) + h.logger.Error("failed to decode txs", "err", err) return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err } @@ -152,22 +141,22 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err } - processLanesHandler := ChainProcessLanes(partialProposals, registry) - finalProposal, err := processLanesHandler(ctx, proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder)) - if err != nil { - h.logger.Error("failed to validate the proposal", "err", err) - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err - } + processLanesHandler := ChainProcessLanes(registry) - // Ensure block size and gas limit are correct. - if err := h.ValidateBlockLimits(finalProposal, proposalInfo); err != nil { + // Verify the proposal. + finalProposal, err := processLanesHandler( + ctx, + proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder), + decodedTxs, + ) + if err != nil { h.logger.Error("failed to validate the proposal", "err", err) return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err } h.logger.Info( "processed proposal", - "num_txs", len(req.Txs), + "num_txs", finalProposal.Txs, "total_tx_bytes", finalProposal.Info.BlockSize, "max_tx_bytes", finalProposal.Info.MaxBlockSize, "total_gas_limit", finalProposal.Info.GasLimit, diff --git a/abci/abci_test.go b/abci/abci_test.go index bc63e2c3..e5c4fda9 100644 --- a/abci/abci_test.go +++ b/abci/abci_test.go @@ -16,7 +16,6 @@ import ( "github.com/skip-mev/block-sdk/abci" "github.com/skip-mev/block-sdk/block" "github.com/skip-mev/block-sdk/block/mocks" - "github.com/skip-mev/block-sdk/block/proposals" testutils "github.com/skip-mev/block-sdk/testutils" blocksdkmoduletypes "github.com/skip-mev/block-sdk/x/blocksdk/types" ) @@ -93,18 +92,7 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2}) s.Require().NoError(err) s.Require().NotNil(resp) - s.Require().Equal(1, len(resp.Txs)) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(0, len(info.TxsByLane)) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(0, len(resp.Txs)) }) s.Run("can build a proposal with a single tx from the lane", func() { @@ -130,23 +118,11 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NoError(err) proposal := s.getTxBytes(tx) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) - s.Run("can build a proposal with multiple txs from the lane", func() { + s.Run("can build a proposal with multiple txs from the default lane", func() { // Create a random transaction that will be inserted into the default lane tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, @@ -182,20 +158,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NoError(err) proposal := s.getTxBytes(tx2, tx1) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(2), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(2, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal with single tx with other that fails", func() { @@ -234,20 +198,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NoError(err) proposal := s.getTxBytes(tx1) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal an empty proposal with multiple lanes", func() { @@ -259,19 +211,7 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2}) s.Require().NoError(err) s.Require().NotNil(resp) - - s.Require().Equal(1, len(resp.Txs)) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(0, len(info.TxsByLane)) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(0, len(resp.Txs)) }) s.Run("can build a proposal with transactions from a single lane given multiple lanes", func() { @@ -303,20 +243,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NotNil(resp) proposal := s.getTxBytes(tx, bundleTxs[0]) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(2), info.TxsByLane[mevLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(2, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can ignore txs that are already included in a proposal", func() { @@ -354,20 +282,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NotNil(resp) proposal := s.getTxBytes(tx, bundleTxs[0]) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(2), info.TxsByLane[mevLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(2, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal where first lane has failing tx and second lane has a valid tx", func() { @@ -406,74 +322,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NotNil(resp) proposal := s.getTxBytes(bundleTxs[0]) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) - }) - - s.Run("can build a proposal where first lane cannot fit txs but second lane can", func() { - // Create a bid tx that includes a single bundled tx - tx, bundleTxs, err := testutils.CreateAuctionTx( - s.encodingConfig.TxConfig, - s.accounts[0], - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - 0, - 0, - s.accounts[0:1], - 100, - ) - s.Require().NoError(err) - - // Set up the TOB lane with the bid tx and the bundled tx - mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.5"), map[sdk.Tx]bool{ - tx: true, - bundleTxs[0]: true, - }) - s.Require().NoError(mevLane.Insert(sdk.Context{}, tx)) - - // Set up the default lane with the bid tx and the bundled tx - defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{ - // Even though this passes it should not include it in the proposal because it is in the ignore list - tx: true, - bundleTxs[0]: true, - }) - s.Require().NoError(defaultLane.Insert(sdk.Context{}, tx)) - s.Require().NoError(defaultLane.Insert(sdk.Context{}, bundleTxs[0])) - - proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).PrepareProposalHandler() - proposal := s.getTxBytes(tx, bundleTxs[0]) - size := int64(len(proposal[0]) - 1) - - s.setBlockParams(10000000, size) - resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2}) - s.Require().NoError(err) - s.Require().NotNil(resp) - - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal[1:], resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal with single tx from middle lane", func() { @@ -508,20 +358,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NoError(err) s.Require().NotNil(resp) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[freeLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("transaction from every lane", func() { @@ -587,22 +425,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NoError(err) s.Require().NotNil(resp) - s.Require().Equal(8, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(3, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[freeLane.Name()]) - s.Require().Equal(uint64(5), info.TxsByLane[mevLane.Name()]) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(7, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal where first lane does not have enough gas but second lane does", func() { @@ -653,20 +477,8 @@ func (s *ProposalsTestSuite) TestPrepareProposal() { s.Require().NoError(err) s.Require().NotNil(resp) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal[2:], resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal[2:], resp.Txs) }) } @@ -709,7 +521,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewNopLogger(), + log.NewTestLogger(s.T()), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -722,7 +534,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -733,20 +545,8 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { s.Require().NotNil(resp) proposal := s.getTxBytes(tx) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal if second lane panics", func() { @@ -787,7 +587,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewNopLogger(), + log.NewTestLogger(s.T()), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -800,7 +600,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -811,20 +611,8 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { s.Require().NotNil(resp) proposal := s.getTxBytes(tx) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal if multiple consecutive lanes panic", func() { @@ -872,7 +660,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewNopLogger(), + log.NewTestLogger(s.T()), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -887,7 +675,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -898,20 +686,8 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { s.Require().NotNil(resp) proposal := s.getTxBytes(tx) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) s.Run("can build a proposal if the last few lanes panic", func() { @@ -959,7 +735,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewNopLogger(), + log.NewTestLogger(s.T()), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -974,7 +750,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -985,20 +761,8 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { s.Require().NotNil(resp) proposal := s.getTxBytes(tx) - s.Require().Equal(2, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs[1:]) - - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(1, len(info.TxsByLane)) - s.Require().Equal(uint64(1), info.TxsByLane[defaultLane.Name()]) - - maxBlockSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - s.Require().Equal(maxBlockSize, info.MaxBlockSize) - s.Require().Equal(maxGasLimit, info.MaxGasLimit) - - s.Require().LessOrEqual(info.BlockSize, info.MaxBlockSize) - s.Require().LessOrEqual(info.GasLimit, info.MaxGasLimit) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) }) } @@ -1009,15 +773,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{}) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, freeLane, defaultLane}).ProcessProposalHandler() - - info := s.createProposalInfoBytes( - 0, - 0, - 0, - 0, - nil, - ) - proposal := [][]byte{info} + proposal := [][]byte{} resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().NoError(err) @@ -1064,7 +820,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().Equal(2, len(txs)) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 2}, tx1, tx2) + proposal := s.createProposal(tx1, tx2) proposalHandler := s.setUpProposalHandlers([]block.Lane{defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1092,7 +848,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { tx: true, }) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 1}, tx) + proposal := s.createProposal(tx) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1150,7 +906,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { tx: true, }) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 2, freeLane.Name(): 1}, bidTx, bundleTxs[0], freeTx, tx) + proposal := s.createProposal(bidTx, bundleTxs[0], freeTx, tx) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, freeLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1159,89 +915,13 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}, resp) }) - s.Run("rejects a proposal with mismatching block size", func() { - tx, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 0, - 100, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) - defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{ - tx: true, - }) - - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 0}, tx) - - // modify the block size to be 1 - info := s.getProposalInfo(proposal[0]) - info.BlockSize-- - infoBz, err := info.Marshal() - s.Require().NoError(err) - proposal[0] = infoBz - - proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() - resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) - s.Require().Error(err) - s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) - }) - - s.Run("rejects a proposal with mismatching gas limit", func() { - tx, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 0, - 100, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) - defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{ - tx: true, - }) - - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 0}, tx) - - // modify the block size to be 1 - info := s.getProposalInfo(proposal[0]) - info.GasLimit-- - infoBz, err := info.Marshal() - s.Require().NoError(err) - proposal[0] = infoBz - - proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() - resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) - s.Require().Error(err) - s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) - }) - s.Run("rejects a proposal with bad txs", func() { mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) freeLane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{}) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, freeLane, defaultLane}).ProcessProposalHandler() - - info := s.createProposalInfoBytes( - 0, - 0, - 0, - 0, - map[string]uint64{ - mevLane.Name(): 0, - freeLane.Name(): 0, - defaultLane.Name(): 1, - }, - ) - proposal := [][]byte{info, {0x01, 0x02, 0x03}} + proposal := [][]byte{{0x01, 0x02, 0x03}} resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().Error(err) @@ -1263,17 +943,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().NoError(err) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, panicLane}).ProcessProposalHandler() - - info := s.createProposalInfoBytes( - 0, - 0, - 0, - 0, - map[string]uint64{ - panicLane.Name(): 1, - }, - ) - proposal := [][]byte{info, txbz} + proposal := [][]byte{txbz} resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().Error(err) @@ -1311,7 +981,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{tx2: true}) s.Require().NoError(defaultLane.Insert(sdk.Context{}, tx)) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 1}, tx2, tx) + proposal := s.createProposal(tx2, tx) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1366,7 +1036,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { bundle[1]: true, }) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 2, mevLane.Name(): 3}, bidTx, bundle[0], bundle[1], normalTx, normalTx2) + proposal := s.createProposal(bidTx, bundle[0], bundle[1], normalTx, normalTx2) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1416,7 +1086,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { // Set up the TOB lane mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.1"), nil) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 2, mevLane.Name(): 1}, bidTx, normalTx, normalTx2) + proposal := s.createProposal(bidTx, normalTx, normalTx2) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1473,7 +1143,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { bidTx: true, }) - proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 2, mevLane.Name(): 1}, bidTx, normalTx, normalTx2) + proposal := s.createProposal(bidTx, normalTx, normalTx2) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) @@ -1573,29 +1243,21 @@ func (s *ProposalsTestSuite) TestPrepareProcessParity() { s.Require().NoError(err) s.Require().NotNil(resp) - info := s.getProposalInfo(resp.Txs[0]) - s.Require().NotNil(info) - s.Require().Equal(2, len(info.TxsByLane)) - s.Require().Equal(numTxsPerLane, info.TxsByLane[defaultLane.Name()]) - s.Require().Equal(numTxsPerLane, info.TxsByLane[freelane.Name()]) - s.Require().Equal(numTxsPerLane*2, uint64(len(resp.Txs)-1)) - // Ensure the transactions are in the correct order for the free lane for i := 0; i < int(numTxsPerLane); i++ { bz, err := s.encodingConfig.TxConfig.TxEncoder()(freeRetrievedTxs[i]) s.Require().NoError(err) - s.Require().Equal(bz, resp.Txs[i+1]) + s.Require().Equal(bz, resp.Txs[i]) } // Ensure the transactions are in the correct order for the default lane for i := 0; i < int(numTxsPerLane); i++ { bz, err := s.encodingConfig.TxConfig.TxEncoder()(retrievedTxs[i]) s.Require().NoError(err) - s.Require().Equal(bz, resp.Txs[i+1+int(numTxsPerLane)]) + s.Require().Equal(bz, resp.Txs[i+int(numTxsPerLane)]) } proposal := s.createProposal( - map[string]uint64{defaultLane.Name(): numTxsPerLane, freelane.Name(): numTxsPerLane}, append(freeRetrievedTxs, retrievedTxs...)..., ) @@ -1692,7 +1354,6 @@ func (s *ProposalsTestSuite) TestIterateMempoolAndProcessProposalParity() { s.Require().Equal(numTxsPerLane, uint64(len(freeRetrievedTxs))) proposal := s.createProposal( - map[string]uint64{defaultLane.Name(): numTxsPerLane, freelane.Name(): numTxsPerLane}, append(freeRetrievedTxs, retrievedTxs...)..., ) @@ -1702,207 +1363,3 @@ func (s *ProposalsTestSuite) TestIterateMempoolAndProcessProposalParity() { s.Require().NotNil(resp) s.Require().NoError(err) } - -func (s *ProposalsTestSuite) TestValidateBasic() { - // Set up the default lane with no transactions - mevlane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), nil) - freelane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.25"), nil) - defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), nil) - - proposalHandlers := s.setUpProposalHandlers([]block.Lane{ - mevlane, - freelane, - defaultLane, - }) - - s.Run("can validate an empty proposal", func() { - info := s.createProposalInfoBytes(0, 0, 0, 0, nil) - proposal := [][]byte{info} - - _, partialProposals, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().NoError(err) - s.Require().Equal(3, len(partialProposals)) - - for _, partialProposal := range partialProposals { - s.Require().Equal(0, len(partialProposal)) - } - }) - - s.Run("should invalidate proposal with mismatch in transactions and proposal info", func() { - info := s.createProposalInfoBytes(0, 0, 0, 0, nil) - proposal := [][]byte{info, {0x01, 0x02, 0x03}} - - _, _, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().Error(err) - }) - - s.Run("should invalidate proposal without info", func() { - proposal := [][]byte{{0x01, 0x02, 0x03}} - - _, _, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().Error(err) - }) - - s.Run("should invalidate completely empty proposal", func() { - proposal := [][]byte{} - - _, _, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().Error(err) - }) - - s.Run("should invalidate proposal with mismatch txs count with proposal info", func() { - info := s.createProposalInfoBytes(0, 0, 0, 0, nil) - proposal := [][]byte{info, {0x01, 0x02, 0x03}, {0x01, 0x02, 0x03}} - - _, _, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().Error(err) - }) - - s.Run("can validate a proposal with a single tx", func() { - tx, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 0, - 1, - ) - s.Require().NoError(err) - proposal := s.getTxBytes(tx) - - size, limit := s.getTxInfos(tx) - maxSize, maxLimit := proposals.GetBlockLimits(s.ctx) - info := s.createProposalInfoBytes( - maxLimit, - limit, - maxSize, - size, - map[string]uint64{ - defaultLane.Name(): 1, - }, - ) - - proposal = append([][]byte{info}, proposal...) - - _, partialProposals, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().NoError(err) - - s.Require().Equal(3, len(partialProposals)) - s.Require().Equal(0, len(partialProposals[0])) - s.Require().Equal(0, len(partialProposals[1])) - s.Require().Equal(1, len(partialProposals[2])) - s.Require().Equal(proposal[1], partialProposals[2][0]) - }) - - s.Run("can validate a proposal with multiple txs from single lane", func() { - tx, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 0, - 1, - ) - s.Require().NoError(err) - - tx2, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 0, - 1, - ) - s.Require().NoError(err) - - proposal := s.getTxBytes(tx, tx2) - - size, limit := s.getTxInfos(tx, tx2) - maxSize, maxLimit := proposals.GetBlockLimits(s.ctx) - info := s.createProposalInfoBytes( - maxLimit, - limit, - maxSize, - size, - map[string]uint64{ - defaultLane.Name(): 2, - }, - ) - - proposal = append([][]byte{info}, proposal...) - - _, partialProposals, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().NoError(err) - - s.Require().Equal(3, len(partialProposals)) - s.Require().Equal(0, len(partialProposals[0])) - s.Require().Equal(0, len(partialProposals[1])) - s.Require().Equal(2, len(partialProposals[2])) - s.Require().Equal(proposal[1], partialProposals[2][0]) - s.Require().Equal(proposal[2], partialProposals[2][1]) - }) - - s.Run("can validate a proposal with 1 tx from each lane", func() { - tx, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 0, - 1, - ) - s.Require().NoError(err) - - tx2, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 0, - 1, - ) - s.Require().NoError(err) - - tx3, err := testutils.CreateRandomTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 0, - 1, - ) - s.Require().NoError(err) - - proposal := s.getTxBytes(tx, tx2, tx3) - - size, limit := s.getTxInfos(tx, tx2, tx3) - maxSize, maxLimit := proposals.GetBlockLimits(s.ctx) - - info := s.createProposalInfoBytes( - maxLimit, - limit, - maxSize, - size, - map[string]uint64{ - defaultLane.Name(): 1, - mevlane.Name(): 1, - freelane.Name(): 1, - }, - ) - - proposal = append([][]byte{info}, proposal...) - - _, partialProposals, err := proposalHandlers.ExtractLanes(s.ctx, proposal) - s.Require().NoError(err) - - s.Require().Equal(3, len(partialProposals)) - s.Require().Equal(1, len(partialProposals[0])) - s.Require().Equal(proposal[1], partialProposals[0][0]) - - s.Require().Equal(1, len(partialProposals[1])) - s.Require().Equal(proposal[2], partialProposals[1][0]) - - s.Require().Equal(1, len(partialProposals[2])) - s.Require().Equal(proposal[3], partialProposals[2][0]) - }) -} diff --git a/abci/utils.go b/abci/utils.go index 6f411e4d..9c91e1c2 100644 --- a/abci/utils.go +++ b/abci/utils.go @@ -1,108 +1,13 @@ package abci import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/skip-mev/block-sdk/block" "github.com/skip-mev/block-sdk/block/proposals" - "github.com/skip-mev/block-sdk/block/proposals/types" "github.com/skip-mev/block-sdk/lanes/terminator" ) -// ExtractLanes validates the proposal against the basic invariants that are required -// for the proposal to be valid. This includes: -// 1. The proposal must contain the proposal information and must be valid. -// 2. The proposal must contain the correct number of transactions for each lane. -func (h *ProposalHandler) ExtractLanes(ctx sdk.Context, proposal [][]byte) (types.ProposalInfo, [][][]byte, error) { - // If the proposal is empty, then the metadata was not included. - if len(proposal) == 0 { - return types.ProposalInfo{}, nil, fmt.Errorf("proposal does not contain proposal metadata") - } - - metaDataBz, txs := proposal[ProposalInfoIndex], proposal[ProposalInfoIndex+1:] - - // Retrieve the metadata from the proposal. - var metaData types.ProposalInfo - if err := metaData.Unmarshal(metaDataBz); err != nil { - return types.ProposalInfo{}, nil, fmt.Errorf("failed to unmarshal proposal metadata: %w", err) - } - - lanes, err := h.mempool.Registry(ctx) - if err != nil { - return types.ProposalInfo{}, nil, fmt.Errorf("failed to get mempool registry: %w", err) - } - partialProposals := make([][][]byte, len(lanes)) - - if metaData.TxsByLane == nil { - if len(txs) > 0 { - return types.ProposalInfo{}, nil, fmt.Errorf("proposal contains invalid number of transactions") - } - - return types.ProposalInfo{}, partialProposals, nil - } - - h.logger.Info( - "received proposal with metadata", - "max_block_size", metaData.MaxBlockSize, - "max_gas_limit", metaData.MaxGasLimit, - "gas_limit", metaData.GasLimit, - "block_size", metaData.BlockSize, - "lanes_with_txs", metaData.TxsByLane, - ) - - // Iterate through all of the lanes and match the corresponding transactions to the lane. - for index, lane := range lanes { - numTxs := metaData.TxsByLane[lane.Name()] - if numTxs > uint64(len(txs)) { - return types.ProposalInfo{}, nil, fmt.Errorf( - "proposal metadata contains invalid number of transactions for lane %s; got %d, expected %d", - lane.Name(), - len(txs), - numTxs, - ) - } - - partialProposals[index] = txs[:numTxs] - txs = txs[numTxs:] - } - - // If there are any transactions remaining in the proposal, then the proposal is invalid. - if len(txs) > 0 { - return types.ProposalInfo{}, nil, fmt.Errorf("proposal contains invalid number of transactions") - } - - return metaData, partialProposals, nil -} - -// ValidateBlockLimits validates the block limits of the proposal against the block limits -// of the chain. -func (h *ProposalHandler) ValidateBlockLimits(finalProposal proposals.Proposal, proposalInfo types.ProposalInfo) error { - // Conduct final checks on block size and gas limit. - if finalProposal.Info.BlockSize != proposalInfo.BlockSize { - h.logger.Error( - "proposal block size does not match", - "expected", proposalInfo.BlockSize, - "got", finalProposal.Info.BlockSize, - ) - - return fmt.Errorf("proposal block size does not match") - } - - if finalProposal.Info.GasLimit != proposalInfo.GasLimit { - h.logger.Error( - "proposal gas limit does not match", - "expected", proposalInfo.GasLimit, - "got", finalProposal.Info.GasLimit, - ) - - return fmt.Errorf("proposal gas limit does not match") - } - - return nil -} - // ChainPrepareLanes chains together the proposal preparation logic from each lane into a // single function. The first lane in the chain is the first lane to be prepared and the // last lane in the chain is the last lane to be prepared. In the case where any of the lanes @@ -163,7 +68,7 @@ func ChainPrepareLanes(chain []block.Lane) block.PrepareLanesHandler { // into a single function. The first lane in the chain is the first lane to be verified and // the last lane in the chain is the last lane to be verified. Each lane will validate // the transactions that it selected in the prepare phase. -func ChainProcessLanes(partialProposals [][][]byte, chain []block.Lane) block.ProcessLanesHandler { +func ChainProcessLanes(chain []block.Lane) block.ProcessLanesHandler { if len(chain) == 0 { return nil } @@ -171,12 +76,10 @@ func ChainProcessLanes(partialProposals [][][]byte, chain []block.Lane) block.Pr // Handle non-terminated decorators chain if (chain[len(chain)-1] != terminator.Terminator{}) { chain = append(chain, terminator.Terminator{}) - partialProposals = append(partialProposals, nil) } - return func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error) { + return func(ctx sdk.Context, proposal proposals.Proposal, txs []sdk.Tx) (proposals.Proposal, error) { lane := chain[0] - partialProposal := partialProposals[0] - return lane.ProcessLane(ctx, proposal, partialProposal, ChainProcessLanes(partialProposals[1:], chain[1:])) + return lane.ProcessLane(ctx, proposal, txs, ChainProcessLanes(chain[1:])) } } diff --git a/abci/utils_test.go b/abci/utils_test.go index 453e6749..75e5332d 100644 --- a/abci/utils_test.go +++ b/abci/utils_test.go @@ -17,9 +17,6 @@ import ( signeradaptors "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" - "github.com/skip-mev/block-sdk/block/proposals" - "github.com/skip-mev/block-sdk/block/proposals/types" - "github.com/skip-mev/block-sdk/block/utils" 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" @@ -60,7 +57,7 @@ func (s *ProposalsTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *defaultlane.DefaultLane { cfg := base.LaneConfig{ - Logger: log.NewNopLogger(), + Logger: log.NewTestLogger(s.T()), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), @@ -74,7 +71,7 @@ func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, exp func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *mev.MEVLane { cfg := base.LaneConfig{ - Logger: log.NewNopLogger(), + Logger: log.NewTestLogger(s.T()), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), @@ -87,7 +84,7 @@ func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expected func (s *ProposalsTestSuite) setUpFreeLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *free.FreeLane { cfg := base.LaneConfig{ - Logger: log.NewNopLogger(), + Logger: log.NewTestLogger(s.T()), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), @@ -100,7 +97,7 @@ func (s *ProposalsTestSuite) setUpFreeLane(maxBlockSpace math.LegacyDec, expecte func (s *ProposalsTestSuite) setUpPanicLane(name string, maxBlockSpace math.LegacyDec) *base.BaseLane { cfg := base.LaneConfig{ - Logger: log.NewNopLogger(), + Logger: log.NewTestLogger(s.T()), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), MaxBlockSpace: maxBlockSpace, @@ -139,65 +136,22 @@ func (s *ProposalsTestSuite) setUpProposalHandlers(lanes []block.Lane) *abci.Pro }) mempool, err := block.NewLanedMempool( - log.NewNopLogger(), + log.NewTestLogger(s.T()), lanes, laneFetcher, ) s.Require().NoError(err) return abci.NewProposalHandler( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, ) } -func (s *ProposalsTestSuite) createProposal(distribution map[string]uint64, txs ...sdk.Tx) [][]byte { - maxSize, maxGasLimit := proposals.GetBlockLimits(s.ctx) - size, limit := s.getTxInfos(txs...) - - info := s.createProposalInfoBytes( - maxGasLimit, - limit, - maxSize, - size, - distribution, - ) - - proposal := s.getTxBytes(txs...) - return append([][]byte{info}, proposal...) -} - -func (s *ProposalsTestSuite) getProposalInfo(bz []byte) types.ProposalInfo { - var info types.ProposalInfo - s.Require().NoError(info.Unmarshal(bz)) - return info -} - -func (s *ProposalsTestSuite) createProposalInfo( - maxGasLimit, gasLimit uint64, - maxBlockSize, blockSize int64, - txsByLane map[string]uint64, -) types.ProposalInfo { - return types.ProposalInfo{ - MaxGasLimit: maxGasLimit, - GasLimit: gasLimit, - MaxBlockSize: maxBlockSize, - BlockSize: blockSize, - TxsByLane: txsByLane, - } -} - -func (s *ProposalsTestSuite) createProposalInfoBytes( - maxGasLimit, gasLimit uint64, - maxBlockSize, blockSize int64, - txsByLane map[string]uint64, -) []byte { - info := s.createProposalInfo(maxGasLimit, gasLimit, maxBlockSize, blockSize, txsByLane) - bz, err := info.Marshal() - s.Require().NoError(err) - return bz +func (s *ProposalsTestSuite) createProposal(txs ...sdk.Tx) [][]byte { + return s.getTxBytes(txs...) } func (s *ProposalsTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { @@ -211,21 +165,6 @@ func (s *ProposalsTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { return txBytes } -func (s *ProposalsTestSuite) getTxInfos(txs ...sdk.Tx) (int64, uint64) { - totalSize := int64(0) - totalGasLimit := uint64(0) - - for _, tx := range txs { - info, err := utils.GetTxInfo(s.encodingConfig.TxConfig.TxEncoder(), tx) - s.Require().NoError(err) - - totalSize += info.Size - totalGasLimit += info.GasLimit - } - - return totalSize, totalGasLimit -} - func (s *ProposalsTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { s.ctx = s.ctx.WithConsensusParams( tmprototypes.ConsensusParams{ diff --git a/block/base/abci.go b/block/base/abci.go index 59b0b648..ab0184c5 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -76,42 +76,35 @@ func (l *BaseLane) PrepareLane( func (l *BaseLane) ProcessLane( ctx sdk.Context, proposal proposals.Proposal, - txs [][]byte, + txs []sdk.Tx, next block.ProcessLanesHandler, ) (proposals.Proposal, error) { - l.Logger().Info("processing lane", "lane", l.Name(), "num_txs_to_verify", len(txs)) - - // Assume that this lane is processing sdk.Tx's and decode the transactions. - decodedTxs, err := utils.GetDecodedTxs(l.TxDecoder(), txs) - if err != nil { - l.Logger().Error( - "failed to decode transactions", - "lane", l.Name(), - "err", err, - ) - - return proposal, err + if len(txs) == 0 { + return proposal, nil } + l.Logger().Info("processing lane", "lane", l.Name()) + // Verify the transactions that belong to this lane according to the verification logic of the lane. - if err := l.processLaneHandler(ctx, decodedTxs); err != nil { + txsFromLane, remainingTxs, err := l.processLaneHandler(ctx, txs) + if err != nil { l.Logger().Error( "failed to process lane", "lane", l.Name(), "err", err, - "num_txs_to_verify", len(decodedTxs), + "num_txs_to_verify", len(txs), ) return proposal, err } // Optimistically update the proposal with the partial proposal. - if err := proposal.UpdateProposal(l, decodedTxs); err != nil { + if err := proposal.UpdateProposal(l, txsFromLane); err != nil { l.Logger().Error( "failed to update proposal", "lane", l.Name(), "err", err, - "num_txs_to_verify", len(decodedTxs), + "num_txs_to_verify", len(txsFromLane), ) return proposal, err @@ -120,10 +113,11 @@ func (l *BaseLane) ProcessLane( l.Logger().Info( "lane processed", "lane", l.Name(), - "num_txs_verified", len(decodedTxs), + "num_txs_verified", len(txsFromLane), + "num_txs_remaining", len(remainingTxs), ) - return next(ctx, proposal) + return next(ctx, proposal, remainingTxs) } // VerifyTx verifies that the transaction is valid respecting the ante verification logic of diff --git a/block/base/handlers.go b/block/base/handlers.go index 167753f0..e8a457d8 100644 --- a/block/base/handlers.go +++ b/block/base/handlers.go @@ -111,35 +111,57 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler { // DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It verifies // the following invariants: -// 1. All transactions belong to this lane. -// 2. All transactions respect the priority defined by the mempool. -// 3. All transactions are valid respecting the verification logic of the lane. +// 1. All transactions belong to this lane. If a transaction does not belong to this lane, we +// return the remain transactions to the next lane. +// 2. All transactions respect the priority defined by the mempool. +// 3. All transactions are valid respecting the verification logic of the lane. func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler { - return func(ctx sdk.Context, partialProposal []sdk.Tx) error { - // Process all transactions that match the lane's matcher. + return func(ctx sdk.Context, partialProposal []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) { + if len(partialProposal) == 0 { + return nil, nil, nil + } + for index, tx := range partialProposal { if !l.Match(ctx, tx) { - return fmt.Errorf("the %s lane contains a transaction that belongs to another lane", l.Name()) + // If the transaction does not belong to this lane, we return the remaining transactions + // iff there are no matches in the remaining transactions after this index. + if err := l.VerifyNoMatches(ctx, partialProposal[index:]); err != nil { + return nil, nil, fmt.Errorf("failed to verify no matches: %w", err) + } + + return partialProposal[:index], partialProposal[index:], nil } // If the transactions do not respect the priority defined by the mempool, we consider the proposal // to be invalid if index > 0 { if v, err := l.Compare(ctx, partialProposal[index-1], tx); v == -1 || err != nil { - return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1) + return nil, nil, fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1) } } if err := l.VerifyTx(ctx, tx, false); err != nil { - return fmt.Errorf("failed to verify tx: %w", err) + return nil, nil, fmt.Errorf("failed to verify tx: %w", err) } } - // This means we have processed all transactions in the partial proposal. - return nil + // This means we have processed all transactions in the partial proposal i.e. + // all of the transactions belong to this lane. + return partialProposal, nil, nil } } +// VerifyNoMatches returns an error if any of the transactions match the lane. +func (l *BaseLane) VerifyNoMatches(ctx sdk.Context, txs []sdk.Tx) error { + for _, tx := range txs { + if l.Match(ctx, tx) { + return fmt.Errorf("transaction belongs to lane") + } + } + + return nil +} + // DefaultMatchHandler returns a default implementation of the MatchHandler. It matches all // transactions. func DefaultMatchHandler() MatchHandler { diff --git a/block/base/types.go b/block/base/types.go index c90d835d..f346b49c 100644 --- a/block/base/types.go +++ b/block/base/types.go @@ -23,7 +23,11 @@ type ( // ProcessLaneHandler is responsible for processing transactions that are included in a block and // belong to a given lane. This handler must return an error if the transactions are not correctly // ordered, do not belong to this lane, or any other relevant error. - ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) error + ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) ( + txsFromLane []sdk.Tx, + remainingTxs []sdk.Tx, + err error, + ) ) // NoOpPrepareLaneHandler returns a no-op prepare lane handler. @@ -45,15 +49,15 @@ func PanicPrepareLaneHandler() PrepareLaneHandler { // NoOpProcessLaneHandler returns a no-op process lane handler. // This should only be used for testing. func NoOpProcessLaneHandler() ProcessLaneHandler { - return func(sdk.Context, []sdk.Tx) error { - return nil + return func(sdk.Context, []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) { + return nil, nil, nil } } // PanicProcessLanesHandler returns a process lanes handler that panics. // This should only be used for testing. func PanicProcessLaneHandler() ProcessLaneHandler { - return func(sdk.Context, []sdk.Tx) error { + return func(sdk.Context, []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) { panic("panic process lanes handler") } } diff --git a/block/lane.go b/block/lane.go index 137cb85e..d0a1a619 100644 --- a/block/lane.go +++ b/block/lane.go @@ -50,7 +50,7 @@ type Lane interface { ProcessLane( ctx sdk.Context, proposal proposals.Proposal, - partialProposal [][]byte, + txs []sdk.Tx, next ProcessLanesHandler, ) (proposals.Proposal, error) diff --git a/block/mocks/lane.go b/block/mocks/lane.go index 7100a21a..50288a67 100644 --- a/block/mocks/lane.go +++ b/block/mocks/lane.go @@ -171,23 +171,23 @@ func (_m *Lane) PrepareLane(ctx types.Context, proposal proposals.Proposal, next return r0, r1 } -// ProcessLane provides a mock function with given fields: ctx, proposal, partialProposal, next -func (_m *Lane) ProcessLane(ctx types.Context, proposal proposals.Proposal, partialProposal [][]byte, next block.ProcessLanesHandler) (proposals.Proposal, error) { - ret := _m.Called(ctx, proposal, partialProposal, next) +// ProcessLane provides a mock function with given fields: ctx, proposal, txs, next +func (_m *Lane) ProcessLane(ctx types.Context, proposal proposals.Proposal, txs []types.Tx, next block.ProcessLanesHandler) (proposals.Proposal, error) { + ret := _m.Called(ctx, proposal, txs, next) var r0 proposals.Proposal var r1 error - if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) (proposals.Proposal, error)); ok { - return rf(ctx, proposal, partialProposal, next) + if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, []types.Tx, block.ProcessLanesHandler) (proposals.Proposal, error)); ok { + return rf(ctx, proposal, txs, next) } - if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) proposals.Proposal); ok { - r0 = rf(ctx, proposal, partialProposal, next) + if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, []types.Tx, block.ProcessLanesHandler) proposals.Proposal); ok { + r0 = rf(ctx, proposal, txs, next) } else { r0 = ret.Get(0).(proposals.Proposal) } - if rf, ok := ret.Get(1).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) error); ok { - r1 = rf(ctx, proposal, partialProposal, next) + if rf, ok := ret.Get(1).(func(types.Context, proposals.Proposal, []types.Tx, block.ProcessLanesHandler) error); ok { + r1 = rf(ctx, proposal, txs, next) } else { r1 = ret.Error(1) } diff --git a/block/types.go b/block/types.go index f08ab8a4..e00f8df9 100644 --- a/block/types.go +++ b/block/types.go @@ -15,7 +15,7 @@ type ( // ProcessLanesHandler wraps all of the lanes' ProcessLane functions into a single chained // function. You can think of it like an AnteHandler, but for processing proposals in the // context of lanes instead of modules. - ProcessLanesHandler func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error) + ProcessLanesHandler func(ctx sdk.Context, proposal proposals.Proposal, txs []sdk.Tx) (proposals.Proposal, error) ) // NoOpPrepareLanesHandler returns a no-op prepare lanes handler. @@ -29,7 +29,7 @@ func NoOpPrepareLanesHandler() PrepareLanesHandler { // NoOpProcessLanesHandler returns a no-op process lanes handler. // This should only be used for testing. func NoOpProcessLanesHandler() ProcessLanesHandler { - return func(_ sdk.Context, p proposals.Proposal) (proposals.Proposal, error) { + return func(_ sdk.Context, p proposals.Proposal, _ []sdk.Tx) (proposals.Proposal, error) { return p, nil } } diff --git a/lanes/base/abci_test.go b/lanes/base/abci_test.go index c0636bc1..9f3f9eec 100644 --- a/lanes/base/abci_test.go +++ b/lanes/base/abci_test.go @@ -10,6 +10,7 @@ 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" @@ -560,9 +561,6 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -570,7 +568,8 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().Len(finalProposal.Txs, 2) s.Require().NoError(err) }) @@ -623,9 +622,6 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -633,7 +629,8 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().Len(finalProposal.Txs, 3) s.Require().NoError(err) }) @@ -699,9 +696,6 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -709,11 +703,12 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().Len(finalProposal.Txs, 4) s.Require().NoError(err) }) - s.Run("should accept a proposal with valid transactions", func() { + s.Run("should accept a proposal with a single valid transaction", func() { tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, s.accounts[0], @@ -735,9 +730,6 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -745,7 +737,8 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().Len(finalProposal.Txs, 1) s.Require().NoError(err) }) @@ -771,9 +764,6 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -781,7 +771,7 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -828,10 +818,8 @@ func (s *BaseTestSuite) TestProcessLane() { tx1: true, tx2: false, tx3: true, - }) - - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) + }, + ) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -840,7 +828,7 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -877,10 +865,8 @@ func (s *BaseTestSuite) TestProcessLane() { map[sdk.Tx]bool{ tx1: true, tx2: true, - }) - - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) + }, + ) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -889,11 +875,12 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().Len(finalProposal.Txs, 2) s.Require().NoError(err) }) - s.Run("should not accept a proposal with transactions that are not in the correct order", func() { + s.Run("should not accept a proposal with transactions that are not in the correct order fee wise", func() { tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, s.accounts[0], @@ -928,9 +915,6 @@ func (s *BaseTestSuite) TestProcessLane() { tx2: true, }) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -938,7 +922,7 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -965,7 +949,19 @@ func (s *BaseTestSuite) TestProcessLane() { ) s.Require().NoError(err) - otherLane := s.initLane(math.LegacyOneDec(), nil) + // First lane matches this lane the other does not. + otherLane := mocks.NewLane(s.T()) + otherLane.On( + "Match", + mock.Anything, + tx1, + ).Return(true, nil) + + otherLane.On( + "Match", + mock.Anything, + tx2, + ).Return(false, nil) lane := s.initLane( math.LegacyOneDec(), @@ -980,9 +976,6 @@ func (s *BaseTestSuite) TestProcessLane() { tx2, } - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -990,7 +983,7 @@ func (s *BaseTestSuite) TestProcessLane() { 100000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -1016,8 +1009,6 @@ func (s *BaseTestSuite) TestProcessLane() { }) maxSize := s.getTxSize(tx1) - 1 - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -1026,7 +1017,7 @@ func (s *BaseTestSuite) TestProcessLane() { 1000000, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -1053,8 +1044,6 @@ func (s *BaseTestSuite) TestProcessLane() { ) maxSize := s.getTxSize(tx1) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -1063,7 +1052,7 @@ func (s *BaseTestSuite) TestProcessLane() { 9, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -1101,8 +1090,6 @@ func (s *BaseTestSuite) TestProcessLane() { }) maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -1111,7 +1098,7 @@ func (s *BaseTestSuite) TestProcessLane() { 19, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -1150,8 +1137,6 @@ func (s *BaseTestSuite) TestProcessLane() { ) maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1 - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -1160,9 +1145,194 @@ func (s *BaseTestSuite) TestProcessLane() { 20, ) - _, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) + + s.Run("contiguous set of transactions should be accepted with other transactions that do not match", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 1, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 2, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx3, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 3, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx4, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 4, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + tx2, + tx3, + tx4, + } + + otherLane := mocks.NewLane(s.T()) + otherLane.On( + "Match", + mock.Anything, + tx1, + ).Return(false, nil) + + otherLane.On( + "Match", + mock.Anything, + tx2, + ).Return(false, nil) + + otherLane.On( + "Match", + mock.Anything, + tx3, + ).Return(true, nil) + + otherLane.On( + "Match", + mock.Anything, + tx4, + ).Return(true, nil) + + lane := s.initLane( + math.LegacyOneDec(), + map[sdk.Tx]bool{ + tx1: true, + tx2: true, + }, + ) + lane.SetIgnoreList([]block.Lane{otherLane}) + + emptyProposal := proposals.NewProposal( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + 1000, + 1000, + ) + + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().NoError(err) + s.Require().Len(finalProposal.Txs, 2) + }) + + s.Run("returns no error if transactions belong to a different lane", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 1, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 2, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx3, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 3, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx4, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 4, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + tx2, + tx3, + tx4, + } + + otherLane := mocks.NewLane(s.T()) + otherLane.On( + "Match", + mock.Anything, + tx1, + ).Return(true, nil) + + 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( + math.LegacyOneDec(), + map[sdk.Tx]bool{}, + ) + lane.SetIgnoreList([]block.Lane{otherLane}) + + emptyProposal := proposals.NewProposal( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + 1000, + 1000, + ) + + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().NoError(err) + s.Require().Len(finalProposal.Txs, 0) + }) } func (s *BaseTestSuite) TestPrepareProcessParity() { @@ -1222,6 +1392,9 @@ func (s *BaseTestSuite) TestPrepareProcessParity() { s.Require().Equal(bz, proposal.Txs[i]) } + decodedTxs, err := utils.GetDecodedTxs(s.encodingConfig.TxConfig.TxDecoder(), proposal.Txs) + s.Require().NoError(err) + // Verify the same proposal with the process lanes handler emptyProposal = proposals.NewProposal( log.NewNopLogger(), @@ -1229,7 +1402,7 @@ func (s *BaseTestSuite) TestPrepareProcessParity() { 1000000000000000, 1000000000000000, ) - proposal, err = lane.ProcessLane(s.ctx, emptyProposal, proposal.Txs, block.NoOpProcessLanesHandler()) + proposal, err = lane.ProcessLane(s.ctx, emptyProposal, decodedTxs, block.NoOpProcessLanesHandler()) s.Require().NoError(err) s.Require().Equal(len(txsToInsert), len(proposal.Txs)) s.T().Logf("proposal num txs: %d", len(proposal.Txs)) @@ -1282,17 +1455,14 @@ func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() { s.Require().Equal(len(txsToInsert), len(retrievedTxs)) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), retrievedTxs) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), 1000000000000000, 1000000000000000, ) - proposal, err := lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler()) + proposal, err := lane.ProcessLane(s.ctx, emptyProposal, retrievedTxs, block.NoOpProcessLanesHandler()) s.Require().NoError(err) s.Require().Equal(len(txsToInsert), len(proposal.Txs)) s.T().Logf("proposal num txs: %d", len(proposal.Txs)) @@ -1310,7 +1480,7 @@ func (s *BaseTestSuite) initLane( expectedExecution map[sdk.Tx]bool, ) *defaultlane.DefaultLane { config := base.NewLaneConfig( - log.NewNopLogger(), + log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), s.encodingConfig.TxConfig.TxDecoder(), s.setUpAnteHandler(expectedExecution), diff --git a/lanes/mev/abci.go b/lanes/mev/abci.go index 574f01ef..e8b29da3 100644 --- a/lanes/mev/abci.go +++ b/lanes/mev/abci.go @@ -77,73 +77,80 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler { // ProcessLaneHandler will ensure that block proposals that include transactions from // the mev lane are valid. In particular, the invariant checks that we perform are: -// 1. The first transaction in the partial block proposal must be a bid transaction. +// 1. If the first transaction does not match the lane, no other MEV transactions +// should be included in the proposal. // 2. The bid transaction must be valid. // 3. The bundled transactions must be valid. // 4. The bundled transactions must match the transactions in the block proposal in the // same order they were defined in the bid transaction. // 5. The bundled transactions must not be bid transactions. func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler { - return func(ctx sdk.Context, partialProposal []sdk.Tx) error { + return func(ctx sdk.Context, partialProposal []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) { if len(partialProposal) == 0 { - return nil + return nil, nil, nil } - // If the first transaction does not match the lane, then we return an error. + // If the first transaction does not match the lane, no other MEV transactions + // should be included in the proposal. bidTx := partialProposal[0] if !l.Match(ctx, bidTx) { - return fmt.Errorf("expected first transaction in lane %s to be a bid transaction", l.Name()) + if err := l.VerifyNoMatches(ctx, partialProposal[1:]); err != nil { + return nil, nil, fmt.Errorf("failed to verify no matches: %w", err) + } + + return nil, partialProposal, nil } bidInfo, err := l.GetAuctionBidInfo(bidTx) if err != nil { - return fmt.Errorf("failed to get bid info from auction bid tx for lane %s: %w", l.Name(), err) + return nil, nil, fmt.Errorf("failed to get bid info from auction bid tx for lane %s: %w", l.Name(), err) } if bidInfo == nil { - return fmt.Errorf("bid info is nil") + return nil, nil, fmt.Errorf("bid info is nil") } // Check that all bundled transactions were included. - if len(bidInfo.Transactions)+1 != len(partialProposal) { - return fmt.Errorf( + bundleSize := len(bidInfo.Transactions) + 1 + if bundleSize > len(partialProposal) { + return nil, nil, fmt.Errorf( "expected %d transactions in lane %s but got %d", - len(bidInfo.Transactions)+1, + bundleSize, l.Name(), len(partialProposal), ) } // Ensure the transactions in the proposal match the bundled transactions in the bid transaction. - bundle := partialProposal[1:] + bundle := partialProposal[1:bundleSize] for index, bundledTxBz := range bidInfo.Transactions { bundledTx, err := l.WrapBundleTransaction(bundledTxBz) if err != nil { - return fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err) + return nil, nil, fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err) } expectedTxBz, err := l.TxEncoder()(bundledTx) if err != nil { - return fmt.Errorf("invalid bid tx; failed to encode bundled tx: %w", err) + return nil, nil, fmt.Errorf("invalid bid tx; failed to encode bundled tx: %w", err) } actualTxBz, err := l.TxEncoder()(bundle[index]) if err != nil { - return fmt.Errorf("invalid bid tx; failed to encode tx: %w", err) + return nil, nil, fmt.Errorf("invalid bid tx; failed to encode tx: %w", err) } // Verify that the bundled transaction matches the transaction in the block proposal. if !bytes.Equal(actualTxBz, expectedTxBz) { - return fmt.Errorf("invalid bid tx; bundled tx does not match tx in block proposal") + return nil, nil, fmt.Errorf("invalid bid tx; bundled tx does not match tx in block proposal") } } // Verify the top-level bid transaction. if err := l.VerifyBidTx(ctx, bidTx, bundle); err != nil { - return fmt.Errorf("invalid bid tx; failed to verify bid tx: %w", err) + return nil, nil, fmt.Errorf("invalid bid tx; failed to verify bid tx: %w", err) } - return nil + return partialProposal[:bundleSize], partialProposal[bundleSize:], nil } } diff --git a/lanes/mev/abci_test.go b/lanes/mev/abci_test.go index 00366e7c..463af5d9 100644 --- a/lanes/mev/abci_test.go +++ b/lanes/mev/abci_test.go @@ -239,14 +239,15 @@ func (s *MEVTestSuite) TestProcessLane() { }) s.Run("can process a proposal with tx that does not belong to this lane", func() { - txBz, err := testutils.CreateRandomTxBz(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100) + tx, err := testutils.CreateRandomTx(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100) s.Require().NoError(err) lane := s.initLane(math.LegacyOneDec(), nil) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100) - _, err = lane.ProcessLane(s.ctx, proposal, [][]byte{txBz}, block.NoOpProcessLanesHandler()) - s.Require().Error(err) + finalProposal, err := lane.ProcessLane(s.ctx, proposal, []sdk.Tx{tx}, block.NoOpProcessLanesHandler()) + s.Require().NoError(err) + s.Require().Equal(0, len(finalProposal.Txs)) }) s.Run("can process a proposal with bad bid tx", func() { @@ -261,8 +262,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: false}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) @@ -283,8 +283,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: false}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) @@ -305,8 +304,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[1], bundle[0]}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx, bundle[1], bundle[0]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) @@ -327,8 +325,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0]}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx, bundle[0]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) @@ -349,8 +346,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) @@ -371,8 +367,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) @@ -393,8 +388,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 20000, 99) @@ -415,8 +409,7 @@ func (s *MEVTestSuite) TestProcessLane() { ) s.Require().NoError(err) - partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]}) - s.Require().NoError(err) + partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100) diff --git a/lanes/terminator/lane.go b/lanes/terminator/lane.go index 0dd70d53..c8e37273 100644 --- a/lanes/terminator/lane.go +++ b/lanes/terminator/lane.go @@ -2,6 +2,7 @@ package terminator import ( "context" + "fmt" "cosmossdk.io/log" "cosmossdk.io/math" @@ -46,7 +47,11 @@ func (t Terminator) PrepareLane(_ sdk.Context, proposal proposals.Proposal, _ bl } // ProcessLane is a no-op -func (t Terminator) ProcessLane(_ sdk.Context, p proposals.Proposal, _ [][]byte, _ block.ProcessLanesHandler) (proposals.Proposal, error) { +func (t Terminator) ProcessLane(_ sdk.Context, p proposals.Proposal, txs []sdk.Tx, _ block.ProcessLanesHandler) (proposals.Proposal, error) { + if len(txs) > 0 { + return p, fmt.Errorf("terminator lane should not have any transactions") + } + return p, nil } diff --git a/tests/integration/chain_setup.go b/tests/integration/chain_setup.go index a5aeeb57..2144bfad 100644 --- a/tests/integration/chain_setup.go +++ b/tests/integration/chain_setup.go @@ -356,25 +356,11 @@ func WaitForHeight(t *testing.T, chain *cosmos.CosmosChain, height uint64) { require.NoError(t, err) } -// VerifyBlock takes a Block and verifies that it contains the given bid at the 0-th index, and the bundled txs immediately after -func VerifyBlock(t *testing.T, block *rpctypes.ResultBlock, offset int, bidTxHash string, txs [][]byte) { - // verify the block - if bidTxHash != "" { - require.Equal(t, bidTxHash, TxHash(block.Block.Data.Txs[offset+1])) - offset += 1 - } - - // verify the txs in sequence - for i, tx := range txs { - require.Equal(t, TxHash(tx), TxHash(block.Block.Data.Txs[i+offset+1])) - } -} - // VerifyBlockWithExpectedBlock takes in a list of raw tx bytes and compares each tx hash to the tx hashes in the block. // The expected block is the block that should be returned by the chain at the given height. func VerifyBlockWithExpectedBlock(t *testing.T, chain *cosmos.CosmosChain, height uint64, txs [][]byte) { block := Block(t, chain, int64(height)) - blockTxs := block.Block.Data.Txs[1:] + blockTxs := block.Block.Data.Txs t.Logf("verifying block %d", height) require.Equal(t, len(txs), len(blockTxs)) From b9fc721412f4fca4e6b48152cdb5c8fd33e1cb36 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Tue, 21 Nov 2023 19:00:57 -0600 Subject: [PATCH 02/17] docs --- .markdownlint.json | 1 + abci/abci.go | 6 +++--- abci/utils.go | 5 ++++- block/base/abci.go | 14 ++++++++++---- block/base/handlers.go | 14 ++++++++------ block/base/types.go | 9 +++++++-- block/proposals/proposals.go | 3 +++ lanes/mev/abci.go | 10 ++++++++-- 8 files changed, 44 insertions(+), 18 deletions(-) diff --git a/.markdownlint.json b/.markdownlint.json index d5eabd25..118aaca5 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -7,6 +7,7 @@ "MD033": false, "MD034": false, "MD014": false, + "MD013": false, "no-hard-tabs": false, "whitespace": false } diff --git a/abci/abci.go b/abci/abci.go index b9836da7..59a833dc 100644 --- a/abci/abci.go +++ b/abci/abci.go @@ -108,9 +108,9 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { // ProcessProposalHandler processes the proposal by verifying all transactions in the proposal // according to each lane's verification logic. Proposals are verified similar to how they are // constructed. After a proposal is processed, it should amount to the same proposal that was prepared. -// Each proposal will first be broken down by the lanes that prepared each partial proposal. Then, each -// lane will iteratively verify the transactions that it belong to it. If any lane fails to verify the -// transactions, then the proposal is rejected. +// The proposal is verified in a greedy fashion, respecting the ordering of lanes. A lane will +// verify all transactions in the proposal that belong to the lane and pass any remaining transactions +// to the next lane in the chain. func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { if req.Height <= 1 { diff --git a/abci/utils.go b/abci/utils.go index 9c91e1c2..73ad76e3 100644 --- a/abci/utils.go +++ b/abci/utils.go @@ -67,7 +67,10 @@ func ChainPrepareLanes(chain []block.Lane) block.PrepareLanesHandler { // ChainProcessLanes chains together the proposal verification logic from each lane // into a single function. The first lane in the chain is the first lane to be verified and // the last lane in the chain is the last lane to be verified. Each lane will validate -// the transactions that it selected in the prepare phase. +// the transactions that belong to the lane and pass any remaining transactions to the next +// lane in the chain. If any of the lanes fail to verify the transactions, the proposal will +// be rejected. If there are any remaining transactions after all lanes have been processed, +// the proposal will be rejected. func ChainProcessLanes(chain []block.Lane) block.ProcessLanesHandler { if len(chain) == 0 { return nil diff --git a/block/base/abci.go b/block/base/abci.go index ab0184c5..e13eb852 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -79,13 +79,18 @@ func (l *BaseLane) ProcessLane( txs []sdk.Tx, next block.ProcessLanesHandler, ) (proposals.Proposal, error) { + l.Logger().Info( + "processing lane", + "lane", l.Name(), + "num_txs_to_verify", len(txs), + ) + if len(txs) == 0 { - return proposal, nil + return next(ctx, proposal, txs) } - l.Logger().Info("processing lane", "lane", l.Name()) - - // Verify the transactions that belong to this lane according to the verification logic of the lane. + // Verify the transactions that belong to the lane and return any transactions that must be + // validated by the next lane in the chain. txsFromLane, remainingTxs, err := l.processLaneHandler(ctx, txs) if err != nil { l.Logger().Error( @@ -117,6 +122,7 @@ func (l *BaseLane) ProcessLane( "num_txs_remaining", len(remainingTxs), ) + // Validate the remaining transactions with the next lane in the chain. return next(ctx, proposal, remainingTxs) } diff --git a/block/base/handlers.go b/block/base/handlers.go index e8a457d8..814f5872 100644 --- a/block/base/handlers.go +++ b/block/base/handlers.go @@ -111,8 +111,8 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler { // DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It verifies // the following invariants: -// 1. All transactions belong to this lane. If a transaction does not belong to this lane, we -// return the remain transactions to the next lane. +// 1. All transactions belong to this lane. If a transaction does not belong to this lane, we return +// the remaining transactions iff there are no matches in the remaining transactions after this index. // 2. All transactions respect the priority defined by the mempool. // 3. All transactions are valid respecting the verification logic of the lane. func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler { @@ -125,8 +125,10 @@ func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler { if !l.Match(ctx, tx) { // If the transaction does not belong to this lane, we return the remaining transactions // iff there are no matches in the remaining transactions after this index. - if err := l.VerifyNoMatches(ctx, partialProposal[index:]); err != nil { - return nil, nil, fmt.Errorf("failed to verify no matches: %w", err) + if index+1 < len(partialProposal) { + if err := l.VerifyNoMatches(ctx, partialProposal[index+1:]); err != nil { + return nil, nil, fmt.Errorf("failed to verify no matches: %w", err) + } } return partialProposal[:index], partialProposal[index:], nil @@ -146,7 +148,7 @@ func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler { } // This means we have processed all transactions in the partial proposal i.e. - // all of the transactions belong to this lane. + // all of the transactions belong to this lane. There are no remaining transactions. return partialProposal, nil, nil } } @@ -155,7 +157,7 @@ func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler { func (l *BaseLane) VerifyNoMatches(ctx sdk.Context, txs []sdk.Tx) error { for _, tx := range txs { if l.Match(ctx, tx) { - return fmt.Errorf("transaction belongs to lane") + return fmt.Errorf("transaction belongs to lane when it should not") } } diff --git a/block/base/types.go b/block/base/types.go index f346b49c..dcc0c66c 100644 --- a/block/base/types.go +++ b/block/base/types.go @@ -21,8 +21,13 @@ type ( ) (txsToInclude []sdk.Tx, txsToRemove []sdk.Tx, err error) // ProcessLaneHandler is responsible for processing transactions that are included in a block and - // belong to a given lane. This handler must return an error if the transactions are not correctly - // ordered, do not belong to this lane, or any other relevant error. + // belong to a given lane. The handler must return the transactions that were successfully processed + // and the transactions that it cannot process because they belong to a different lane. Basic invariant + // checks that are required: + // 1. Transactions belonging to the lane must be contiguous from the beginning of the partial proposal. + // 2. Transactions that do not belong to the lane must be contiguous from the end of the partial proposal. + // 3. Transactions must be ordered respecting the priority defined by the lane (e.g. gas price). + // 4. Transactions must be valid according to the verification logic of the lane. ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) ( txsFromLane []sdk.Tx, remainingTxs []sdk.Tx, diff --git a/block/proposals/proposals.go b/block/proposals/proposals.go index e8bcca1a..a966ce81 100644 --- a/block/proposals/proposals.go +++ b/block/proposals/proposals.go @@ -48,6 +48,9 @@ func NewProposal(logger log.Logger, txEncoder sdk.TxEncoder, maxBlockSize int64, // GetProposalWithInfo returns all of the transactions in the proposal along with information // about the lanes that built the proposal. +// +// NOTE: This is currently not used in production but likely will be once +// ABCI 3.0 is released. func (p *Proposal) GetProposalWithInfo() ([][]byte, error) { // Marshall the proposal info into the first slot of the proposal. infoBz, err := p.Info.Marshal() diff --git a/lanes/mev/abci.go b/lanes/mev/abci.go index e8b29da3..b2d0abc6 100644 --- a/lanes/mev/abci.go +++ b/lanes/mev/abci.go @@ -94,8 +94,12 @@ func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler { // should be included in the proposal. bidTx := partialProposal[0] if !l.Match(ctx, bidTx) { - if err := l.VerifyNoMatches(ctx, partialProposal[1:]); err != nil { - return nil, nil, fmt.Errorf("failed to verify no matches: %w", err) + // If the transaction does not belong to this lane, we return the remaining transactions + // iff there are no matches in the remaining transactions after this index. + if len(partialProposal) > 1 { + if err := l.VerifyNoMatches(ctx, partialProposal[1:]); err != nil { + return nil, nil, fmt.Errorf("failed to verify no matches: %w", err) + } } return nil, partialProposal, nil @@ -146,6 +150,8 @@ func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler { } // Verify the top-level bid transaction. + // + // TODO: There is duplicate work being done in VerifyBidTx and here. if err := l.VerifyBidTx(ctx, bidTx, bundle); err != nil { return nil, nil, fmt.Errorf("invalid bid tx; failed to verify bid tx: %w", err) } From 580f4878271377a005cd653e469147a00e6b0d9e Mon Sep 17 00:00:00 2001 From: David Terpay Date: Tue, 21 Nov 2023 20:21:10 -0600 Subject: [PATCH 03/17] base lane testing --- block/base/abci.go | 3 +- block/proposals/update.go | 3 - block/utils/utils.go | 7 +- lanes/base/abci_test.go | 280 +++++++++++++++++++++++++++++++++++--- 4 files changed, 269 insertions(+), 24 deletions(-) diff --git a/block/base/abci.go b/block/base/abci.go index e13eb852..3ac25394 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -97,7 +97,6 @@ func (l *BaseLane) ProcessLane( "failed to process lane", "lane", l.Name(), "err", err, - "num_txs_to_verify", len(txs), ) return proposal, err @@ -108,8 +107,8 @@ func (l *BaseLane) ProcessLane( l.Logger().Error( "failed to update proposal", "lane", l.Name(), + "num_txs_verified", len(txsFromLane), "err", err, - "num_txs_to_verify", len(txsFromLane), ) return proposal, err diff --git a/block/proposals/update.go b/block/proposals/update.go index a5896a79..632b1c71 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -1,7 +1,6 @@ package proposals import ( - "encoding/base64" "fmt" "cosmossdk.io/math" @@ -55,8 +54,6 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error { "tx_hash", txInfo.Hash, "tx_size", txInfo.Size, "tx_gas_limit", txInfo.GasLimit, - "tx_bytes", txInfo.TxBytes, - "raw_tx", base64.StdEncoding.EncodeToString(txInfo.TxBytes), ) // invariant check: Ensure that the transaction is not already in the proposal. diff --git a/block/utils/utils.go b/block/utils/utils.go index 9f2881d0..a0826f2e 100644 --- a/block/utils/utils.go +++ b/block/utils/utils.go @@ -1,9 +1,11 @@ package utils import ( - "crypto/sha256" "encoding/hex" "fmt" + "strings" + + comettypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" @@ -31,8 +33,7 @@ func GetTxInfo(txEncoder sdk.TxEncoder, tx sdk.Tx) (TxInfo, error) { return TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err) } - txHash := sha256.Sum256(txBz) - txHashStr := hex.EncodeToString(txHash[:]) + txHashStr := strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBz).Hash())) // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. gasTx, ok := tx.(sdk.FeeTx) diff --git a/lanes/base/abci_test.go b/lanes/base/abci_test.go index 9f3f9eec..02b1435c 100644 --- a/lanes/base/abci_test.go +++ b/lanes/base/abci_test.go @@ -522,6 +522,51 @@ func (s *BaseTestSuite) TestPrepareLane() { s.Require().Equal(uint64(2), finalProposal.Info.GasLimit) s.Require().Equal([][]byte{txBz}, finalProposal.Txs) }) + + s.Run("should not attempt to include transaction that matches to a different lane", func() { + // Create a basic transaction that should not in the proposal + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + 2, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + ) + s.Require().NoError(err) + + // 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) + + // 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) + + emptyProposal := proposals.NewProposal( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + int64(len(txBz))*10, + 1000000, + ) + + finalProposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + s.Require().Len(finalProposal.Txs, 0) + }) } func (s *BaseTestSuite) TestProcessLane() { @@ -561,6 +606,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 2) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -571,6 +621,10 @@ func (s *BaseTestSuite) TestProcessLane() { finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Len(finalProposal.Txs, 2) s.Require().NoError(err) + + encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, finalProposal.Txs) }) s.Run("should accept a proposal where transaction fees are not in order bc of sequence numbers with other txs", func() { @@ -622,6 +676,12 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + // + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 3) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -632,6 +692,10 @@ func (s *BaseTestSuite) TestProcessLane() { finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Len(finalProposal.Txs, 3) s.Require().NoError(err) + + encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, finalProposal.Txs) }) s.Run("accepts proposal with multiple senders and seq nums", func() { @@ -696,6 +760,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 4) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -706,6 +775,10 @@ func (s *BaseTestSuite) TestProcessLane() { finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Len(finalProposal.Txs, 4) s.Require().NoError(err) + + encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, finalProposal.Txs) }) s.Run("should accept a proposal with a single valid transaction", func() { @@ -730,6 +803,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 1) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -740,6 +818,10 @@ func (s *BaseTestSuite) TestProcessLane() { finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Len(finalProposal.Txs, 1) s.Require().NoError(err) + + encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, finalProposal.Txs) }) s.Run("should not accept a proposal with invalid transactions", func() { @@ -764,6 +846,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().Error(err) + s.Require().Len(txsFromLane, 0) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -821,6 +908,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().Error(err) + s.Require().Len(txsFromLane, 0) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -832,7 +924,7 @@ func (s *BaseTestSuite) TestProcessLane() { s.Require().Error(err) }) - s.Run("should accept proposal with transactions in correct order", func() { + s.Run("should accept proposal with transactions in correct order with same fees", func() { tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, s.accounts[0], @@ -851,7 +943,7 @@ func (s *BaseTestSuite) TestProcessLane() { 1, 0, 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)), ) s.Require().NoError(err) @@ -868,6 +960,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 2) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -878,6 +975,10 @@ func (s *BaseTestSuite) TestProcessLane() { finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().Len(finalProposal.Txs, 2) s.Require().NoError(err) + + encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, finalProposal.Txs) }) s.Run("should not accept a proposal with transactions that are not in the correct order fee wise", func() { @@ -913,7 +1014,13 @@ func (s *BaseTestSuite) TestProcessLane() { map[sdk.Tx]bool{ tx1: true, tx2: true, - }) + }, + ) + + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().Error(err) + s.Require().Len(txsFromLane, 0) + s.Require().Len(remainingTxs, 0) emptyProposal := proposals.NewProposal( log.NewNopLogger(), @@ -926,7 +1033,7 @@ func (s *BaseTestSuite) TestProcessLane() { s.Require().Error(err) }) - s.Run("should not accept a proposal where transactions are out of order relative to other lanes", func() { + s.Run("should not accept proposal where transactions from lane are not contiguous from the start", func() { tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, s.accounts[0], @@ -967,8 +1074,9 @@ func (s *BaseTestSuite) TestProcessLane() { math.LegacyOneDec(), map[sdk.Tx]bool{ tx1: true, - tx2: false, - }) + tx2: true, + }, + ) lane.SetIgnoreList([]block.Lane{otherLane}) proposal := []sdk.Tx{ @@ -976,6 +1084,11 @@ func (s *BaseTestSuite) TestProcessLane() { tx2, } + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().Error(err) + s.Require().Len(txsFromLane, 0) + s.Require().Len(remainingTxs, 0) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1006,10 +1119,16 @@ func (s *BaseTestSuite) TestProcessLane() { math.LegacyOneDec(), map[sdk.Tx]bool{ tx1: true, - }) + }, + ) - maxSize := s.getTxSize(tx1) - 1 + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 1) + s.Require().Len(remainingTxs, 0) + // Set the size to be 1 less than the size of the transaction + maxSize := s.getTxSize(tx1) - 1 emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1043,8 +1162,12 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - maxSize := s.getTxSize(tx1) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 1) + s.Require().Len(remainingTxs, 0) + maxSize := s.getTxSize(tx1) emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1087,10 +1210,15 @@ func (s *BaseTestSuite) TestProcessLane() { map[sdk.Tx]bool{ tx1: true, tx2: true, - }) + }, + ) - maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 2) + s.Require().Len(remainingTxs, 0) + maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1136,8 +1264,12 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1 + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 2) + s.Require().Len(remainingTxs, 0) + maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1 emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1231,6 +1363,12 @@ func (s *BaseTestSuite) TestProcessLane() { ) lane.SetIgnoreList([]block.Lane{otherLane}) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 2) + s.Require().Len(remainingTxs, 2) + s.Require().Equal([]sdk.Tx{tx3, tx4}, remainingTxs) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1241,6 +1379,10 @@ func (s *BaseTestSuite) TestProcessLane() { finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) s.Require().NoError(err) s.Require().Len(finalProposal.Txs, 2) + + encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx1, tx2}) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, finalProposal.Txs) }) s.Run("returns no error if transactions belong to a different lane", func() { @@ -1322,6 +1464,12 @@ func (s *BaseTestSuite) TestProcessLane() { ) lane.SetIgnoreList([]block.Lane{otherLane}) + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().NoError(err) + s.Require().Len(txsFromLane, 0) + s.Require().Len(remainingTxs, 4) + s.Require().Equal(proposal, remainingTxs) + emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1333,6 +1481,106 @@ func (s *BaseTestSuite) TestProcessLane() { s.Require().NoError(err) s.Require().Len(finalProposal.Txs, 0) }) + + s.Run("returns an error if transactions are interleaved with other lanes", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 1, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 2, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx3, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 3, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + tx4, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 4, + 1, + 0, + 1, + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + tx2, + tx3, + tx4, + } + + otherLane := mocks.NewLane(s.T()) + otherLane.On( + "Match", + mock.Anything, + tx1, + ).Return(false, nil) + + otherLane.On( + "Match", + mock.Anything, + tx2, + ).Return(true, nil) + + otherLane.On( + "Match", + mock.Anything, + tx3, + ).Return(false, nil).Maybe() + + otherLane.On( + "Match", + mock.Anything, + tx4, + ).Return(true, nil).Maybe() + + lane := s.initLane( + math.LegacyOneDec(), + map[sdk.Tx]bool{ + tx1: true, + tx2: true, + tx3: true, + tx4: true, + }, + ) + lane.SetIgnoreList([]block.Lane{otherLane}) + + txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal) + s.Require().Error(err) + s.Require().Len(txsFromLane, 0) + s.Require().Len(remainingTxs, 0) + + emptyProposal := proposals.NewProposal( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + 1000, + 1000, + ) + + _, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + s.Require().Error(err) + }) } func (s *BaseTestSuite) TestPrepareProcessParity() { @@ -1383,7 +1631,7 @@ func (s *BaseTestSuite) TestPrepareProcessParity() { ) proposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler()) s.Require().NoError(err) - s.Require().Equal(len(txsToInsert), len(proposal.Txs)) + s.Require().Equal(len(retrievedTxs), len(proposal.Txs)) // Ensure that the transactions are in the same order for i := 0; i < len(retrievedTxs); i++ { @@ -1456,7 +1704,7 @@ func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() { s.Require().Equal(len(txsToInsert), len(retrievedTxs)) emptyProposal := proposals.NewProposal( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), 1000000000000000, 1000000000000000, @@ -1464,7 +1712,7 @@ func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() { proposal, err := lane.ProcessLane(s.ctx, emptyProposal, retrievedTxs, block.NoOpProcessLanesHandler()) s.Require().NoError(err) - s.Require().Equal(len(txsToInsert), len(proposal.Txs)) + s.Require().Equal(len(retrievedTxs), len(proposal.Txs)) s.T().Logf("proposal num txs: %d", len(proposal.Txs)) // Ensure that the transactions are in the same order @@ -1480,7 +1728,7 @@ func (s *BaseTestSuite) initLane( expectedExecution map[sdk.Tx]bool, ) *defaultlane.DefaultLane { config := base.NewLaneConfig( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), s.encodingConfig.TxConfig.TxDecoder(), s.setUpAnteHandler(expectedExecution), From 0a44f1ff0fb377d5782e52edbe4f4876e00bc044 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Tue, 21 Nov 2023 20:58:31 -0600 Subject: [PATCH 04/17] mev lane testing nits --- block/base/handlers.go | 8 +-- block/base/types.go | 7 +-- lanes/mev/abci.go | 2 - lanes/mev/abci_test.go | 133 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 127 insertions(+), 23 deletions(-) diff --git a/block/base/handlers.go b/block/base/handlers.go index 814f5872..702d038b 100644 --- a/block/base/handlers.go +++ b/block/base/handlers.go @@ -111,10 +111,10 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler { // DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It verifies // the following invariants: -// 1. All transactions belong to this lane. If a transaction does not belong to this lane, we return -// the remaining transactions iff there are no matches in the remaining transactions after this index. -// 2. All transactions respect the priority defined by the mempool. -// 3. All transactions are valid respecting the verification logic of the lane. +// 1. Transactions belonging to the lane must be contiguous from the beginning of the partial proposal. +// 2. Transactions that do not belong to the lane must be contiguous from the end of the partial proposal. +// 3. Transactions must be ordered respecting the priority defined by the lane (e.g. gas price). +// 4. Transactions must be valid according to the verification logic of the lane. func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler { return func(ctx sdk.Context, partialProposal []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) { if len(partialProposal) == 0 { diff --git a/block/base/types.go b/block/base/types.go index dcc0c66c..a5bb9507 100644 --- a/block/base/types.go +++ b/block/base/types.go @@ -22,12 +22,7 @@ type ( // ProcessLaneHandler is responsible for processing transactions that are included in a block and // belong to a given lane. The handler must return the transactions that were successfully processed - // and the transactions that it cannot process because they belong to a different lane. Basic invariant - // checks that are required: - // 1. Transactions belonging to the lane must be contiguous from the beginning of the partial proposal. - // 2. Transactions that do not belong to the lane must be contiguous from the end of the partial proposal. - // 3. Transactions must be ordered respecting the priority defined by the lane (e.g. gas price). - // 4. Transactions must be valid according to the verification logic of the lane. + // and the transactions that it cannot process because they belong to a different lane. ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) ( txsFromLane []sdk.Tx, remainingTxs []sdk.Tx, diff --git a/lanes/mev/abci.go b/lanes/mev/abci.go index b2d0abc6..5b03a605 100644 --- a/lanes/mev/abci.go +++ b/lanes/mev/abci.go @@ -90,8 +90,6 @@ func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler { return nil, nil, nil } - // If the first transaction does not match the lane, no other MEV transactions - // should be included in the proposal. bidTx := partialProposal[0] if !l.Match(ctx, bidTx) { // If the transaction does not belong to this lane, we return the remaining transactions diff --git a/lanes/mev/abci_test.go b/lanes/mev/abci_test.go index 463af5d9..d8ddbaee 100644 --- a/lanes/mev/abci_test.go +++ b/lanes/mev/abci_test.go @@ -230,12 +230,14 @@ func (s *MEVTestSuite) TestProcessLane() { lane := s.initLane(math.LegacyOneDec(), nil) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100) - proposal, err := lane.ProcessLane(s.ctx, proposal, nil, block.NoOpProcessLanesHandler()) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, nil) + s.Require().NoError(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal, err = lane.ProcessLane(s.ctx, proposal, nil, block.NoOpProcessLanesHandler()) s.Require().NoError(err) s.Require().Equal(0, len(proposal.Txs)) - s.Require().Equal(0, len(proposal.Info.TxsByLane)) - s.Require().Equal(int64(0), proposal.Info.BlockSize) - s.Require().Equal(uint64(0), proposal.Info.GasLimit) }) s.Run("can process a proposal with tx that does not belong to this lane", func() { @@ -245,6 +247,11 @@ func (s *MEVTestSuite) TestProcessLane() { lane := s.initLane(math.LegacyOneDec(), nil) proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, []sdk.Tx{tx}) + s.Require().NoError(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(1, len(remainingTxs)) + finalProposal, err := lane.ProcessLane(s.ctx, proposal, []sdk.Tx{tx}, block.NoOpProcessLanesHandler()) s.Require().NoError(err) s.Require().Equal(0, len(finalProposal.Txs)) @@ -265,8 +272,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: false}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().Error(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -286,8 +298,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: false}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().Error(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -307,8 +324,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx, bundle[1], bundle[0]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().Error(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -328,8 +350,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx, bundle[0]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().Error(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -349,8 +376,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().NoError(err) + s.Require().Equal(3, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().NoError(err) }) @@ -370,8 +402,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().NoError(err) + s.Require().Equal(1, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().NoError(err) }) @@ -391,8 +428,13 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) - proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 20000, 99) + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().NoError(err) + s.Require().Equal(3, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 20000, 99) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) @@ -412,8 +454,77 @@ func (s *MEVTestSuite) TestProcessLane() { partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]} lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) + + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().NoError(err) + s.Require().Equal(3, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100) + _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) + s.Require().Error(err) + }) + + s.Run("can accept a block proposal with bid and other txs", func() { + bidTx, bundle, err := testutils.CreateAuctionTx( + s.encCfg.TxConfig, + s.accounts[0], + sdk.NewCoin("stake", math.NewInt(100)), + 0, + 0, + s.accounts[0:2], + 100, + ) + s.Require().NoError(err) + + otherTx, err := testutils.CreateRandomTx(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100) + s.Require().NoError(err) + + partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1], otherTx} + + lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) + + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().NoError(err) + s.Require().Equal(3, len(txsFromLane)) + s.Require().Equal(1, len(remainingTxs)) + s.Require().Equal(otherTx, remainingTxs[0]) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) + proposal, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) + s.Require().NoError(err) + s.Require().Len(proposal.Txs, 3) + + encodedTxs, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]}) + s.Require().NoError(err) + s.Require().Equal(encodedTxs, proposal.Txs) + }) + s.Run("rejects a block where the bid tx is not the first tx", func() { + bidTx, bundle, err := testutils.CreateAuctionTx( + s.encCfg.TxConfig, + s.accounts[0], + sdk.NewCoin("stake", math.NewInt(100)), + 0, + 0, + s.accounts[0:2], + 100, + ) + s.Require().NoError(err) + + otherTx, err := testutils.CreateRandomTx(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100) + s.Require().NoError(err) + + partialProposal := []sdk.Tx{otherTx, bidTx, bundle[0], bundle[1]} + + lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true}) + + txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal) + s.Require().Error(err) + s.Require().Equal(0, len(txsFromLane)) + s.Require().Equal(0, len(remainingTxs)) + + proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000) _, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler()) s.Require().Error(err) }) From 03990697bbec12ec83fa0f4c5a88613fa7bb44e9 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Tue, 21 Nov 2023 21:15:58 -0600 Subject: [PATCH 05/17] abci top level testing done --- abci/abci.go | 6 +- abci/abci_test.go | 141 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 133 insertions(+), 14 deletions(-) diff --git a/abci/abci.go b/abci/abci.go index 59a833dc..f0c24a22 100644 --- a/abci/abci.go +++ b/abci/abci.go @@ -74,9 +74,8 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err } - prepareLanesHandler := ChainPrepareLanes(registry) - // Fill the proposal with transactions from each lane. + prepareLanesHandler := ChainPrepareLanes(registry) finalProposal, err := prepareLanesHandler(ctx, proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder)) if err != nil { h.logger.Error("failed to prepare proposal", "err", err) @@ -141,9 +140,8 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err } - processLanesHandler := ChainProcessLanes(registry) - // Verify the proposal. + processLanesHandler := ChainProcessLanes(registry) finalProposal, err := processLanesHandler( ctx, proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder), diff --git a/abci/abci_test.go b/abci/abci_test.go index e5c4fda9..6ab3b848 100644 --- a/abci/abci_test.go +++ b/abci/abci_test.go @@ -915,6 +915,122 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}, resp) }) + s.Run("can reject a proposal with txs from multiple lanes", func() { + // Create a random transaction that will be inserted into the default lane + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // create a bid tx that will be inserted into the mev lane + bidTx, bundleTxs, err := testutils.CreateAuctionTx( + s.encodingConfig.TxConfig, + s.accounts[1], + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + 0, + 0, + s.accounts[1:2], + 100, + ) + s.Require().NoError(err) + + // create a free tx that will be inserted into the free lane + freeTx, err := testutils.CreateFreeTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + "test", + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Mev lane + mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{ + bidTx: true, + bundleTxs[0]: true, + }) + freeLane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{ + freeTx: true, + }) + defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{ + tx: true, + }) + + proposal := s.createProposal(bidTx, bundleTxs[0], tx, freeTx) // tx and freeTx are out of order + + proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, freeLane, defaultLane}).ProcessProposalHandler() + resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) + s.Require().Error(err) + s.Require().NotNil(resp) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) + }) + + s.Run("can reject a proposal with txs from multiple lanes (mev is mixed up)", func() { + // Create a random transaction that will be inserted into the default lane + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // create a bid tx that will be inserted into the mev lane + bidTx, bundleTxs, err := testutils.CreateAuctionTx( + s.encodingConfig.TxConfig, + s.accounts[1], + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + 0, + 0, + s.accounts[1:2], + 100, + ) + s.Require().NoError(err) + + // create a free tx that will be inserted into the free lane + freeTx, err := testutils.CreateFreeTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + "test", + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Mev lane + mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{ + bidTx: true, + bundleTxs[0]: true, + }) + freeLane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{ + freeTx: true, + }) + defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{ + tx: true, + }) + + proposal := s.createProposal(freeTx, tx, bidTx, bundleTxs[0]) + + proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, freeLane, defaultLane}).ProcessProposalHandler() + resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) + s.Require().Error(err) + s.Require().NotNil(resp) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) + }) + s.Run("rejects a proposal with bad txs", func() { mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) freeLane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) @@ -950,15 +1066,16 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) }) - s.Run("can process a invalid proposal (out of order)", func() { + s.Run("can process a invalid proposal (default lane out of order)", func() { // Create a random transaction that will be inserted into the default lane - tx, err := testutils.CreateAuctionTxWithSigners( + tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, - s.accounts[0], - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + s.accounts[2], 0, 1, - nil, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2000000)), ) s.Require().NoError(err) @@ -966,7 +1083,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { tx2, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, s.accounts[2], - 0, + 1, 1, 0, 1, @@ -975,18 +1092,18 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().NoError(err) // Mev lane - mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{tx: true}) + mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{}) // Set up the default lane - defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{tx2: true}) - s.Require().NoError(defaultLane.Insert(sdk.Context{}, tx)) + defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{tx2: true, tx1: true}) - proposal := s.createProposal(tx2, tx) + proposal := s.createProposal(tx2, tx1) proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, defaultLane}).ProcessProposalHandler() resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().NotNil(resp) s.Require().Error(err) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) }) s.Run("can process a invalid proposal where first lane is valid second is not", func() { @@ -1042,6 +1159,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().NotNil(resp) s.Require().Error(err) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) }) s.Run("can process a invalid proposal where a lane consumes too much gas", func() { @@ -1092,6 +1210,8 @@ func (s *ProposalsTestSuite) TestProcessProposal() { resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().NotNil(resp) s.Require().Error(err) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) }) s.Run("can process a invalid proposal where a lane consumes too much block space", func() { @@ -1149,6 +1269,7 @@ func (s *ProposalsTestSuite) TestProcessProposal() { resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) s.Require().NotNil(resp) s.Require().Error(err) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) }) } From 8b25b0364f1c26b46d4d6797fe2abc4e1a6c5486 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Tue, 21 Nov 2023 22:08:27 -0600 Subject: [PATCH 06/17] network spamming in E2E --- abci/abci.go | 2 +- block/proposals/update.go | 2 +- .../integration/block_sdk_integration_test.go | 2 +- tests/integration/block_sdk_suite.go | 166 ++++++++++++++++++ tests/integration/chain_setup.go | 79 ++++++++- 5 files changed, 242 insertions(+), 9 deletions(-) diff --git a/abci/abci.go b/abci/abci.go index f0c24a22..0782468c 100644 --- a/abci/abci.go +++ b/abci/abci.go @@ -154,7 +154,7 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { h.logger.Info( "processed proposal", - "num_txs", finalProposal.Txs, + "num_txs", len(finalProposal.Txs), "total_tx_bytes", finalProposal.Info.BlockSize, "max_tx_bytes", finalProposal.Info.MaxBlockSize, "total_gas_limit", finalProposal.Info.GasLimit, diff --git a/block/proposals/update.go b/block/proposals/update.go index 632b1c71..0280586a 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -47,7 +47,7 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error { return fmt.Errorf("err retrieving transaction info: %s", err) } - p.Logger.Debug( + p.Logger.Info( "updating proposal with tx", "index", index, "lane", lane.Name(), diff --git a/tests/integration/block_sdk_integration_test.go b/tests/integration/block_sdk_integration_test.go index 7d891f83..982d0eab 100644 --- a/tests/integration/block_sdk_integration_test.go +++ b/tests/integration/block_sdk_integration_test.go @@ -64,7 +64,7 @@ var ( } consensusParams = ictestutil.Toml{ - "timeout_commit": "3500ms", + "timeout_commit": "5000ms", } // interchain specification diff --git a/tests/integration/block_sdk_suite.go b/tests/integration/block_sdk_suite.go index dcfdf625..ada82367 100644 --- a/tests/integration/block_sdk_suite.go +++ b/tests/integration/block_sdk_suite.go @@ -2,6 +2,8 @@ package integration import ( "context" + "math/rand" + "time" "cosmossdk.io/math" rpctypes "github.com/cometbft/cometbft/rpc/core/types" @@ -39,6 +41,8 @@ type IntegrationTestSuite struct { user1, user2, user3 ibc.Wallet // denom denom string + // fuzzusers + fuzzusers []ibc.Wallet // overrides for key-ring configuration of the broadcaster broadcasterOverrides *KeyringOverride @@ -86,6 +90,10 @@ func (s *IntegrationTestSuite) SetupSuite() { s.user2 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0] s.user3 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0] + for i := 0; i < 10; i++ { + s.fuzzusers = append(s.fuzzusers, interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0]) + } + // create the broadcaster s.T().Log("creating broadcaster") s.setupBroadcaster() @@ -1258,3 +1266,161 @@ func (s *IntegrationTestSuite) TestLanes() { require.Equal(s.T(), user2BalanceBefore, user2BalanceAfter+delegation.Amount.Int64()) }) } + +func (s *IntegrationTestSuite) TestNetwork() { + amountToTest := time.NewTicker(time.Second * 45) + defer amountToTest.Stop() + + numTxs := 10 + sendAmount := sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))) + delegation := sdk.NewCoin(s.denom, math.NewInt(100)) + validators := QueryValidators(s.T(), s.chain.(*cosmos.CosmosChain)) + + s.Run("can produce blocks with only default transactions", func() { + for { + select { + case <-amountToTest.C: + return + default: + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + s.T().Logf("height: %d", height+1) + + for i := 0; i < numTxs; i++ { + for _, user := range s.fuzzusers { + fee := rand.Int63n(100000) + sequenceOffset := uint64(i) + + normalTx := s.CreateDummyNormalTx(user, s.user1, sendAmount, sequenceOffset, fee) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{normalTx}) + } + } + } + } + }) + + s.Run("can produce blocks with only free transactions", func() { + for { + select { + case <-amountToTest.C: + return + default: + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + s.T().Logf("height: %d", height+1) + + for i := 0; i < numTxs; i++ { + for _, user := range s.fuzzusers { + sequenceOffset := uint64(i) + + freeTx := s.CreateDummyFreeTx(user, validators[0], delegation, sequenceOffset) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{freeTx}) + } + } + } + } + }) + + s.Run("can produce blocks with only MEV transactions", func() { + for { + select { + case <-amountToTest.C: + return + default: + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + s.T().Logf("height: %d", height+1) + + for i := 0; i < numTxs; i++ { + for _, user := range s.fuzzusers { + bid := rand.Int63n(1000000) + bidAmount := sdk.NewCoin(s.denom, math.NewInt(bid)) + + mevTx := s.CreateDummyAuctionBidTx( + height+2, + user, + bidAmount, + ) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{mevTx}) + } + } + } + } + }) + + amountToTest.Reset(5 * time.Minute) + s.Run("can produce blocks with all types of transactions", func() { + for { + select { + case <-amountToTest.C: + return + default: + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + s.T().Logf("height: %d", height+1) + + txs := []Tx{} + + for i := 0; i < numTxs; i++ { + for _, user := range s.fuzzusers[0:3] { + bid := rand.Int63n(1000000) + bidAmount := sdk.NewCoin(s.denom, math.NewInt(bid)) + + bidTx := s.CreateDummyAuctionBidTx( + height+2, + user, + bidAmount, + ) + txs = append(txs, bidTx) + } + } + + for i := 0; i < numTxs; i++ { + for _, user := range s.fuzzusers[3:6] { + sequenceOffset := uint64(i) + + freeTx := s.CreateDummyFreeTx(user, validators[0], delegation, sequenceOffset) + txs = append(txs, freeTx) + + } + } + + for i := 0; i < numTxs; i++ { + for _, user := range s.fuzzusers[6:10] { + fee := rand.Int63n(100000) + sequenceOffset := uint64(i) + normalTx := s.CreateDummyNormalTx(user, s.user1, sendAmount, sequenceOffset, fee) + txs = append(txs, normalTx) + } + } + + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), txs) + } + } + }) + + // Wait for 1 minute for the network to stabilize + amountToTest.Reset(1 * time.Minute) + s.Run("can produce empty blocks", func() { + for { + select { + case <-amountToTest.C: + return + default: + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + s.T().Logf("height: %d", height+1) + } + } + }) +} diff --git a/tests/integration/chain_setup.go b/tests/integration/chain_setup.go index 2144bfad..c2929c38 100644 --- a/tests/integration/chain_setup.go +++ b/tests/integration/chain_setup.go @@ -24,6 +24,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" interchaintest "github.com/strangelove-ventures/interchaintest/v8" "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" @@ -124,6 +125,71 @@ func (s *IntegrationTestSuite) CreateTx(ctx context.Context, chain *cosmos.Cosmo return bz } +func (s *IntegrationTestSuite) CreateDummyAuctionBidTx( + height uint64, + searcher ibc.Wallet, + bid sdk.Coin, +) Tx { + msgAuctionBid := auctiontypes.NewMsgAuctionBid( + searcher.Address(), + bid, + nil, + ) + + return Tx{ + User: searcher, + Msgs: []sdk.Msg{msgAuctionBid}, + GasPrice: 1000, + Height: height + 1, + SkipInclusionCheck: true, + IgnoreChecks: true, + } +} + +func (s *IntegrationTestSuite) CreateDummyNormalTx( + from, to ibc.Wallet, + coins sdk.Coins, + sequenceOffset uint64, + gasPrice int64, +) Tx { + msgSend := banktypes.NewMsgSend( + sdk.AccAddress(from.Address()), + sdk.AccAddress(to.Address()), + coins, + ) + + return Tx{ + User: from, + Msgs: []sdk.Msg{msgSend}, + GasPrice: gasPrice, + SequenceIncrement: sequenceOffset, + SkipInclusionCheck: true, + IgnoreChecks: true, + } +} + +func (s *IntegrationTestSuite) CreateDummyFreeTx( + user ibc.Wallet, + validator sdk.ValAddress, + delegation sdk.Coin, + sequenceOffset uint64, +) Tx { + delegateMsg := stakingtypes.NewMsgDelegate( + sdk.AccAddress(user.Address()).String(), + sdk.ValAddress(validator).String(), + delegation, + ) + + return Tx{ + User: user, + Msgs: []sdk.Msg{delegateMsg}, + GasPrice: 1000, + SequenceIncrement: sequenceOffset, + SkipInclusionCheck: true, + IgnoreChecks: true, + } +} + // SimulateTx simulates the provided messages, and checks whether the provided failure condition is met func (s *IntegrationTestSuite) SimulateTx(ctx context.Context, chain *cosmos.CosmosChain, user cosmos.User, height uint64, expectFail bool, msgs ...sdk.Msg) { // create tx factory + Client Context @@ -154,6 +220,7 @@ type Tx struct { Height uint64 SkipInclusionCheck bool ExpectFail bool + IgnoreChecks bool } // CreateAuctionBidMsg creates a new AuctionBid tx signed by the given user, the order of txs in the MsgAuctionBid will be determined by the contents + order of the MessageForUsers @@ -203,13 +270,13 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback( s.Require().True(len(chain.Nodes()) > 0) client := chain.Nodes()[0].Client - statusResp, err := client.Status(context.Background()) - s.Require().NoError(err) - - s.T().Logf("broadcasting transactions at latest height of %d", statusResp.SyncInfo.LatestBlockHeight) - for i, tx := range rawTxs { // broadcast tx + if txs[i].IgnoreChecks { + client.BroadcastTxAsync(ctx, tx) + continue + } + resp, err := client.BroadcastTxSync(ctx, tx) // check execution was successful @@ -228,7 +295,7 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback( eg := errgroup.Group{} for i, tx := range rawTxs { // if we don't expect this tx to be included.. skip it - if txs[i].SkipInclusionCheck || txs[i].ExpectFail { + if txs[i].SkipInclusionCheck || txs[i].ExpectFail || txs[i].IgnoreChecks { continue } From b1a14cd66abb02305a1cad6ebd76782723ca73d3 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Wed, 22 Nov 2023 12:28:14 -0600 Subject: [PATCH 07/17] string rep of escrow address --- block/proposals/update.go | 2 +- proto/sdk/auction/v1/query.proto | 5 +- x/auction/keeper/grpc_query.go | 3 +- x/auction/types/query.pb.go | 98 +++++++++++++++++++++++++------- 4 files changed, 84 insertions(+), 24 deletions(-) diff --git a/block/proposals/update.go b/block/proposals/update.go index 0280586a..ee8dc44b 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -49,7 +49,7 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error { p.Logger.Info( "updating proposal with tx", - "index", index, + "index", index+len(p.Txs), "lane", lane.Name(), "tx_hash", txInfo.Hash, "tx_size", txInfo.Size, diff --git a/proto/sdk/auction/v1/query.proto b/proto/sdk/auction/v1/query.proto index 2eaa41c8..652a3195 100644 --- a/proto/sdk/auction/v1/query.proto +++ b/proto/sdk/auction/v1/query.proto @@ -22,6 +22,9 @@ message QueryParamsRequest {} // QueryParamsResponse is the response type for the Query/Params RPC method. message QueryParamsResponse { - // params defines the parameters of the module. + // Params defines the parameters of the module. Params params = 1 [ (gogoproto.nullable) = false ]; + // EscrowAddressString is the string representation of the escrow address stored + // in params. + string escrow_address_string = 2; } \ No newline at end of file diff --git a/x/auction/keeper/grpc_query.go b/x/auction/keeper/grpc_query.go index 95a25123..58891910 100644 --- a/x/auction/keeper/grpc_query.go +++ b/x/auction/keeper/grpc_query.go @@ -29,5 +29,6 @@ func (q QueryServer) Params(c context.Context, _ *types.QueryParamsRequest) (*ty return nil, err } - return &types.QueryParamsResponse{Params: params}, nil + escrowAddress := sdk.AccAddress(params.EscrowAccountAddress) + return &types.QueryParamsResponse{Params: params, EscrowAddressString: escrowAddress.String()}, nil } diff --git a/x/auction/types/query.pb.go b/x/auction/types/query.pb.go index 439aab98..a2b2b4a4 100644 --- a/x/auction/types/query.pb.go +++ b/x/auction/types/query.pb.go @@ -69,8 +69,11 @@ var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo // QueryParamsResponse is the response type for the Query/Params RPC method. type QueryParamsResponse struct { - // params defines the parameters of the module. + // Params defines the parameters of the module. Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + // EscrowAddressString is the string representation of the escrow address stored + // in params. + EscrowAddressString string `protobuf:"bytes,2,opt,name=escrow_address_string,json=escrowAddressString,proto3" json:"escrow_address_string,omitempty"` } func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } @@ -113,6 +116,13 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } +func (m *QueryParamsResponse) GetEscrowAddressString() string { + if m != nil { + return m.EscrowAddressString + } + return "" +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "sdk.auction.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "sdk.auction.v1.QueryParamsResponse") @@ -121,26 +131,29 @@ func init() { func init() { proto.RegisterFile("sdk/auction/v1/query.proto", fileDescriptor_47f76418be29a6d6) } var fileDescriptor_47f76418be29a6d6 = []byte{ - // 303 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2a, 0x4e, 0xc9, 0xd6, - 0x4f, 0x2c, 0x4d, 0x2e, 0xc9, 0xcc, 0xcf, 0xd3, 0x2f, 0x33, 0xd4, 0x2f, 0x2c, 0x4d, 0x2d, 0xaa, - 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2b, 0x4e, 0xc9, 0xd6, 0x83, 0xca, 0xe9, 0x95, - 0x19, 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0xa5, 0xf4, 0x41, 0x2c, 0x88, 0x2a, 0x29, 0x99, - 0xf4, 0xfc, 0xfc, 0xf4, 0x9c, 0x54, 0xfd, 0xc4, 0x82, 0x4c, 0xfd, 0xc4, 0xbc, 0xbc, 0xfc, 0x92, - 0x44, 0x90, 0xfa, 0x62, 0xa8, 0xac, 0x74, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x31, 0xc4, 0x5c, 0x34, - 0x0b, 0xa4, 0x64, 0xd0, 0x2c, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x84, 0x6a, 0x55, 0x12, 0xe1, - 0x12, 0x0a, 0x04, 0x29, 0x0e, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, - 0x2e, 0x51, 0xf2, 0xe6, 0x12, 0x46, 0x11, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0x32, 0xe1, - 0x62, 0x2b, 0x00, 0x8b, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x89, 0xe9, 0xa1, 0x3a, 0x5e, - 0x0f, 0xa2, 0xde, 0x89, 0xe5, 0xc4, 0x3d, 0x79, 0x86, 0x20, 0xa8, 0x5a, 0xa3, 0x56, 0x46, 0x2e, - 0x56, 0xb0, 0x69, 0x42, 0x35, 0x5c, 0x6c, 0x10, 0x15, 0x42, 0x4a, 0xe8, 0x3a, 0x31, 0x1d, 0x21, - 0xa5, 0x8c, 0x57, 0x0d, 0xc4, 0x49, 0x4a, 0x9a, 0x1d, 0xcf, 0x37, 0x68, 0x31, 0x36, 0x5d, 0x7e, - 0x32, 0x99, 0x49, 0x4e, 0x48, 0x46, 0x3f, 0x29, 0x27, 0x3f, 0x39, 0x5b, 0x17, 0xcd, 0xc7, 0x10, - 0x77, 0x38, 0xb9, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, - 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x6e, 0x7a, - 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x71, 0x76, 0x66, 0x81, 0x6e, 0x6e, - 0x6a, 0x19, 0x92, 0x51, 0x15, 0x70, 0xc3, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x41, - 0x67, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x23, 0x86, 0x8c, 0xe6, 0xd7, 0x01, 0x00, 0x00, + // 341 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xc1, 0x4a, 0xc3, 0x40, + 0x10, 0x86, 0xb3, 0x45, 0x0b, 0x46, 0xf0, 0x90, 0x56, 0x29, 0xb1, 0xc4, 0x12, 0x2f, 0x55, 0x68, + 0x96, 0x56, 0x5f, 0xc0, 0x5e, 0xbc, 0x6a, 0xbd, 0x79, 0x29, 0xdb, 0x64, 0x59, 0x43, 0x9a, 0x4c, + 0x9a, 0xd9, 0x54, 0x0b, 0x82, 0x20, 0x08, 0x1e, 0x05, 0x5f, 0xc4, 0xc7, 0xe8, 0xb1, 0xe0, 0xc5, + 0x93, 0x48, 0x2b, 0xf8, 0x1a, 0xd2, 0x6c, 0x10, 0x1b, 0xc1, 0x5b, 0x98, 0xef, 0xcb, 0x3f, 0xff, + 0x8e, 0x6e, 0xa2, 0x17, 0x50, 0x96, 0xba, 0xd2, 0x87, 0x88, 0x8e, 0xdb, 0x74, 0x94, 0xf2, 0x64, + 0xe2, 0xc4, 0x09, 0x48, 0x30, 0xb6, 0xd0, 0x0b, 0x9c, 0x9c, 0x39, 0xe3, 0xb6, 0x59, 0x15, 0x20, + 0x20, 0x43, 0x74, 0xf9, 0xa5, 0x2c, 0xb3, 0x2e, 0x00, 0xc4, 0x90, 0x53, 0x16, 0xfb, 0x94, 0x45, + 0x11, 0x48, 0xb6, 0xf4, 0x31, 0xa7, 0xbb, 0x2e, 0x60, 0x08, 0xa8, 0x72, 0x0b, 0x0b, 0xcc, 0x7a, + 0x61, 0xb9, 0xe0, 0x11, 0x47, 0x3f, 0xff, 0xd5, 0xae, 0xea, 0xc6, 0xf9, 0x52, 0x3e, 0x63, 0x09, + 0x0b, 0xb1, 0xc7, 0x47, 0x29, 0x47, 0x69, 0xdf, 0xe9, 0x95, 0x95, 0x29, 0xc6, 0x10, 0x21, 0x37, + 0x8e, 0xf5, 0x72, 0x9c, 0x4d, 0x6a, 0xa4, 0x41, 0x9a, 0x9b, 0x9d, 0x1d, 0x67, 0xb5, 0xbc, 0xa3, + 0xfc, 0xee, 0xda, 0xf4, 0x7d, 0x4f, 0xeb, 0xe5, 0xae, 0xd1, 0xd1, 0xb7, 0x39, 0xba, 0x09, 0x5c, + 0xf7, 0x99, 0xe7, 0x25, 0x1c, 0xb1, 0x8f, 0x32, 0xf1, 0x23, 0x51, 0x2b, 0x35, 0x48, 0x73, 0xa3, + 0x57, 0x51, 0xf0, 0x44, 0xb1, 0x8b, 0x0c, 0x75, 0x1e, 0x88, 0xbe, 0x9e, 0x35, 0x30, 0x6e, 0xf5, + 0xb2, 0x4a, 0x35, 0xec, 0xe2, 0xb6, 0xbf, 0xc5, 0xcd, 0xfd, 0x7f, 0x1d, 0xf5, 0x0c, 0xfb, 0xe0, + 0xf1, 0xeb, 0xe5, 0x90, 0xdc, 0xbf, 0x7e, 0x3e, 0x97, 0x2c, 0xa3, 0x4e, 0x07, 0x43, 0x70, 0x83, + 0x56, 0xe1, 0x4a, 0xaa, 0x7b, 0xf7, 0x74, 0x3a, 0xb7, 0xc8, 0x6c, 0x6e, 0x91, 0x8f, 0xb9, 0x45, + 0x9e, 0x16, 0x96, 0x36, 0x5b, 0x58, 0xda, 0xdb, 0xc2, 0xd2, 0x2e, 0x5b, 0xc2, 0x97, 0x57, 0xe9, + 0xc0, 0x71, 0x21, 0xa4, 0x18, 0xf8, 0x71, 0x2b, 0xe4, 0xe3, 0x5f, 0x51, 0x37, 0x3f, 0x61, 0x72, + 0x12, 0x73, 0x1c, 0x94, 0xb3, 0x73, 0x1f, 0x7d, 0x07, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x3e, 0x57, + 0xb0, 0x0b, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -268,6 +281,13 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.EscrowAddressString) > 0 { + i -= len(m.EscrowAddressString) + copy(dAtA[i:], m.EscrowAddressString) + i = encodeVarintQuery(dAtA, i, uint64(len(m.EscrowAddressString))) + i-- + dAtA[i] = 0x12 + } { size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -309,6 +329,10 @@ func (m *QueryParamsResponse) Size() (n int) { _ = l l = m.Params.Size() n += 1 + l + sovQuery(uint64(l)) + l = len(m.EscrowAddressString) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } return n } @@ -430,6 +454,38 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EscrowAddressString", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EscrowAddressString = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) From 6266d605d8bd5b6ade2b17389e0dc64a4150d726 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Wed, 22 Nov 2023 12:30:20 -0600 Subject: [PATCH 08/17] nit --- block/proposals/update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/proposals/update.go b/block/proposals/update.go index ee8dc44b..0280586a 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -49,7 +49,7 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error { p.Logger.Info( "updating proposal with tx", - "index", index+len(p.Txs), + "index", index, "lane", lane.Name(), "tx_hash", txInfo.Hash, "tx_size", txInfo.Size, From ad9df3df61acc0581ae8af316cf9f87d31717285 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 10:41:41 -0500 Subject: [PATCH 09/17] nit --- block/base/abci.go | 4 +-- block/base/handlers.go | 3 +-- block/base/mempool.go | 17 +++++++----- block/base/tx_info.go | 41 ++++++++++++++++++++++++++++ block/lane.go | 9 +++++++ block/mocks/lane.go | 42 +++++++++++++++++++++++++++++ block/mocks/lane_mempool.go | 16 +++++++++++ block/proposals/update.go | 17 +++++++----- block/utils/utils.go | 54 +++++++++++++++---------------------- 9 files changed, 154 insertions(+), 49 deletions(-) create mode 100644 block/base/tx_info.go diff --git a/block/base/abci.go b/block/base/abci.go index 3ac25394..2ef37dcb 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -44,7 +44,7 @@ func (l *BaseLane) PrepareLane( // Update the proposal with the selected transactions. This fails if the lane attempted to add // more transactions than the allocated max block space for the lane. - if err := proposal.UpdateProposal(l, txsToInclude); err != nil { + if err := proposal.UpdateProposal(ctx, l, txsToInclude); err != nil { l.Logger().Error( "failed to update proposal", "lane", l.Name(), @@ -103,7 +103,7 @@ func (l *BaseLane) ProcessLane( } // Optimistically update the proposal with the partial proposal. - if err := proposal.UpdateProposal(l, txsFromLane); err != nil { + if err := proposal.UpdateProposal(ctx, l, txsFromLane); err != nil { l.Logger().Error( "failed to update proposal", "lane", l.Name(), diff --git a/block/base/handlers.go b/block/base/handlers.go index 702d038b..110a9a94 100644 --- a/block/base/handlers.go +++ b/block/base/handlers.go @@ -6,7 +6,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/skip-mev/block-sdk/block/proposals" - "github.com/skip-mev/block-sdk/block/utils" ) // DefaultPrepareLaneHandler returns a default implementation of the PrepareLaneHandler. It @@ -27,7 +26,7 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler { for iterator := l.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { tx := iterator.Tx() - txInfo, err := utils.GetTxInfo(l.TxEncoder(), tx) + txInfo, err := l.GetTxInfo(ctx, tx) if err != nil { l.Logger().Info("failed to get hash of tx", "err", err) diff --git a/block/base/mempool.go b/block/base/mempool.go index 0ec52675..17cf56d6 100644 --- a/block/base/mempool.go +++ b/block/base/mempool.go @@ -102,19 +102,24 @@ func NewMempool[C comparable](txPriority TxPriority[C], txEncoder sdk.TxEncoder, } } +// Priority returns the priority of the transaction. +func (cm *Mempool[C]) Priority(ctx sdk.Context, tx sdk.Tx) any { + return cm.txPriority.GetTxPriority(ctx, tx) +} + // Insert inserts a transaction into the mempool. func (cm *Mempool[C]) Insert(ctx context.Context, tx sdk.Tx) error { if err := cm.index.Insert(ctx, tx); err != nil { return fmt.Errorf("failed to insert tx into auction index: %w", err) } - txInfo, err := utils.GetTxInfo(cm.txEncoder, tx) + hash, err := utils.GetTxHash(cm.txEncoder, tx) if err != nil { cm.Remove(tx) return err } - cm.txCache[txInfo.Hash] = struct{}{} + cm.txCache[hash] = struct{}{} return nil } @@ -125,12 +130,12 @@ func (cm *Mempool[C]) Remove(tx sdk.Tx) error { return fmt.Errorf("failed to remove transaction from the mempool: %w", err) } - txInfo, err := utils.GetTxInfo(cm.txEncoder, tx) + hash, err := utils.GetTxHash(cm.txEncoder, tx) if err != nil { return fmt.Errorf("failed to get tx hash string: %w", err) } - delete(cm.txCache, txInfo.Hash) + delete(cm.txCache, hash) return nil } @@ -150,12 +155,12 @@ func (cm *Mempool[C]) CountTx() int { // Contains returns true if the transaction is contained in the mempool. func (cm *Mempool[C]) Contains(tx sdk.Tx) bool { - txInfo, err := utils.GetTxInfo(cm.txEncoder, tx) + hash, err := utils.GetTxHash(cm.txEncoder, tx) if err != nil { return false } - _, ok := cm.txCache[txInfo.Hash] + _, ok := cm.txCache[hash] return ok } diff --git a/block/base/tx_info.go b/block/base/tx_info.go new file mode 100644 index 00000000..47bff385 --- /dev/null +++ b/block/base/tx_info.go @@ -0,0 +1,41 @@ +package base + +import ( + "encoding/hex" + "fmt" + "strings" + + comettypes "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/block-sdk/block/utils" +) + +// GetTxInfo returns various information about the transaction that +// belongs to the lane including its priority, signer's, sequence number +// size and more. +func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxInfo, error) { + txBytes, err := l.cfg.TxEncoder(tx) + if err != nil { + return utils.TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err) + } + + // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return utils.TxInfo{}, fmt.Errorf("failed to cast transaction to GasTx") + } + + signers, err := l.cfg.SignerExtractor.GetSigners(tx) + if err != nil { + return utils.TxInfo{}, err + } + + return utils.TxInfo{ + Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), + Size: int64(len(txBytes)), + GasLimit: gasTx.GetGas(), + TxBytes: txBytes, + Priority: l.LaneMempool.Priority(ctx, tx), + Signers: signers, + }, nil +} diff --git a/block/lane.go b/block/lane.go index d0a1a619..83ab014e 100644 --- a/block/lane.go +++ b/block/lane.go @@ -6,6 +6,7 @@ import ( sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/skip-mev/block-sdk/block/proposals" + "github.com/skip-mev/block-sdk/block/utils" ) // LaneMempool defines the interface a lane's mempool should implement. The basic API @@ -23,6 +24,9 @@ type LaneMempool interface { // Contains returns true if the transaction is contained in the mempool. Contains(tx sdk.Tx) bool + + // Priority returns the priority of a transaction that belongs to this lane. + Priority(ctx sdk.Context, tx sdk.Tx) any } // Lane defines an interface used for matching transactions to lanes, storing transactions, @@ -74,6 +78,11 @@ type Lane interface { // Match determines if a transaction belongs to this lane. Match(ctx sdk.Context, tx sdk.Tx) bool + + // GetTxInfo returns various information about the transaction that + // belongs to the lane including its priority, signer's, sequence number + // size and more. + GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxInfo, error) } // FindLane finds a Lanes from in an array of Lanes and returns it and its index if found. diff --git a/block/mocks/lane.go b/block/mocks/lane.go index 50288a67..a02d35a0 100644 --- a/block/mocks/lane.go +++ b/block/mocks/lane.go @@ -16,6 +16,8 @@ import ( proposals "github.com/skip-mev/block-sdk/block/proposals" types "github.com/cosmos/cosmos-sdk/types" + + utils "github.com/skip-mev/block-sdk/block/utils" ) // Lane is an autogenerated mock type for the Lane type @@ -105,6 +107,30 @@ func (_m *Lane) GetMaxBlockSpace() math.LegacyDec { return r0 } +// GetTxInfo provides a mock function with given fields: ctx, tx +func (_m *Lane) GetTxInfo(ctx types.Context, tx types.Tx) (utils.TxInfo, error) { + ret := _m.Called(ctx, tx) + + var r0 utils.TxInfo + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) (utils.TxInfo, error)); ok { + return rf(ctx, tx) + } + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) utils.TxInfo); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Get(0).(utils.TxInfo) + } + + if rf, ok := ret.Get(1).(func(types.Context, types.Tx) error); ok { + r1 = rf(ctx, tx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Insert provides a mock function with given fields: _a0, _a1 func (_m *Lane) Insert(_a0 context.Context, _a1 types.Tx) error { ret := _m.Called(_a0, _a1) @@ -171,6 +197,22 @@ func (_m *Lane) PrepareLane(ctx types.Context, proposal proposals.Proposal, next return r0, r1 } +// Priority provides a mock function with given fields: ctx, tx +func (_m *Lane) Priority(ctx types.Context, tx types.Tx) interface{} { + ret := _m.Called(ctx, tx) + + var r0 interface{} + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) interface{}); ok { + r0 = rf(ctx, tx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + return r0 +} + // ProcessLane provides a mock function with given fields: ctx, proposal, txs, next func (_m *Lane) ProcessLane(ctx types.Context, proposal proposals.Proposal, txs []types.Tx, next block.ProcessLanesHandler) (proposals.Proposal, error) { ret := _m.Called(ctx, proposal, txs, next) diff --git a/block/mocks/lane_mempool.go b/block/mocks/lane_mempool.go index cecf96f4..7e0ca4ce 100644 --- a/block/mocks/lane_mempool.go +++ b/block/mocks/lane_mempool.go @@ -82,6 +82,22 @@ func (_m *LaneMempool) Insert(_a0 context.Context, _a1 types.Tx) error { return r0 } +// Priority provides a mock function with given fields: ctx, tx +func (_m *LaneMempool) Priority(ctx types.Context, tx types.Tx) interface{} { + ret := _m.Called(ctx, tx) + + var r0 interface{} + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) interface{}); ok { + r0 = rf(ctx, tx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + return r0 +} + // Remove provides a mock function with given fields: _a0 func (_m *LaneMempool) Remove(_a0 types.Tx) error { ret := _m.Called(_a0) diff --git a/block/proposals/update.go b/block/proposals/update.go index 0280586a..e93d6c13 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -11,8 +11,9 @@ import ( // Lane defines the contract interface for a lane. type Lane interface { - GetMaxBlockSpace() math.LegacyDec Name() string + GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxInfo, error) + GetMaxBlockSpace() math.LegacyDec } // UpdateProposal updates the proposal with the given transactions and lane limits. There are a @@ -25,7 +26,7 @@ type Lane interface { // the lane. // 5. The lane must not have already prepared a partial proposal. // 6. The transaction must not already be in the proposal. -func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error { +func (p *Proposal) UpdateProposal(ctx sdk.Context, lane Lane, partialProposal []sdk.Tx) error { if len(partialProposal) == 0 { return nil } @@ -42,18 +43,20 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error { partialProposalGasLimit := uint64(0) for index, tx := range partialProposal { - txInfo, err := utils.GetTxInfo(p.TxEncoder, tx) + txInfo, err := lane.GetTxInfo(ctx, tx) if err != nil { return fmt.Errorf("err retrieving transaction info: %s", err) } p.Logger.Info( "updating proposal with tx", - "index", index, + "index", index+len(p.Txs), "lane", lane.Name(), - "tx_hash", txInfo.Hash, - "tx_size", txInfo.Size, - "tx_gas_limit", txInfo.GasLimit, + "hash", txInfo.Hash, + "size", txInfo.Size, + "gas_limit", txInfo.GasLimit, + "signers", txInfo.Signers, + "priority", txInfo.Priority, ) // invariant check: Ensure that the transaction is not already in the proposal. diff --git a/block/utils/utils.go b/block/utils/utils.go index a0826f2e..02d1d796 100644 --- a/block/utils/utils.go +++ b/block/utils/utils.go @@ -6,47 +6,37 @@ import ( "strings" comettypes "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" + signerextraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" ) -type ( - // TxInfo contains the information required for a transaction to be - // included in a proposal. - TxInfo struct { - // Hash is the hex-encoded hash of the transaction. - Hash string - // Size is the size of the transaction in bytes. - Size int64 - // GasLimit is the gas limit of the transaction. - GasLimit uint64 - // TxBytes is the bytes of the transaction. - TxBytes []byte - } -) +// TxInfo contains the information required for a transaction to be +// included in a proposal. +type TxInfo struct { + // Hash is the hex-encoded hash of the transaction. + Hash string + // Size is the size of the transaction in bytes. + Size int64 + // GasLimit is the gas limit of the transaction. + GasLimit uint64 + // TxBytes is the bytes of the transaction. + TxBytes []byte + // Priority defines the priority of the transaction. + Priority any + // signerData defines the signers of a transaction. + Signers []signerextraction.SignerData +} -// GetTxHashStr returns the TxInfo of a given transaction. -func GetTxInfo(txEncoder sdk.TxEncoder, tx sdk.Tx) (TxInfo, error) { - txBz, err := txEncoder(tx) +// GetTxHash returns the string hash representation of a transaction. +func GetTxHash(encoder sdk.TxEncoder, tx sdk.Tx) (string, error) { + txBz, err := encoder(tx) if err != nil { - return TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err) + return "", fmt.Errorf("failed to encode transaction: %w", err) } txHashStr := strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBz).Hash())) - - // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. - gasTx, ok := tx.(sdk.FeeTx) - if !ok { - return TxInfo{}, fmt.Errorf("failed to cast transaction to GasTx") - } - - return TxInfo{ - Hash: txHashStr, - Size: int64(len(txBz)), - GasLimit: gasTx.GetGas(), - TxBytes: txBz, - }, nil + return txHashStr, nil } // GetDecodedTxs returns the decoded transactions from the given bytes. From 2084ddf8aa7d223acb93133920f638a680ffc4a1 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 10:41:51 -0500 Subject: [PATCH 10/17] nit v1.0.1 --- lanes/mev/abci.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lanes/mev/abci.go b/lanes/mev/abci.go index 5b03a605..5910f214 100644 --- a/lanes/mev/abci.go +++ b/lanes/mev/abci.go @@ -8,7 +8,6 @@ import ( "github.com/skip-mev/block-sdk/block/base" "github.com/skip-mev/block-sdk/block/proposals" - "github.com/skip-mev/block-sdk/block/utils" ) // PrepareLaneHandler will attempt to select the highest bid transaction that is valid @@ -35,7 +34,9 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler { continue } - bundle, err := l.VerifyBidBasic(bidTx, proposal, limit) + cacheCtx, write := ctx.CacheContext() + + bundle, err := l.VerifyBidBasic(cacheCtx, bidTx, proposal, limit) if err != nil { l.Logger().Info( "failed to select auction bid tx for lane; tx is invalid", @@ -46,7 +47,6 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler { continue } - cacheCtx, write := ctx.CacheContext() if err := l.VerifyBidTx(cacheCtx, bidTx, bundle); err != nil { l.Logger().Info( "failed to select auction bid tx for lane; tx is invalid", @@ -161,6 +161,7 @@ func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler { // VerifyBidBasic will verify that the bid transaction and all of its bundled // transactions respect the basic invariants of the lane (e.g. size, gas limit). func (l *MEVLane) VerifyBidBasic( + ctx sdk.Context, bidTx sdk.Tx, proposal proposals.Proposal, limit proposals.LaneLimits, @@ -175,7 +176,7 @@ func (l *MEVLane) VerifyBidBasic( return nil, fmt.Errorf("bid info is nil") } - txInfo, err := utils.GetTxInfo(l.TxEncoder(), bidTx) + txInfo, err := l.GetTxInfo(ctx, bidTx) if err != nil { return nil, fmt.Errorf("err retrieving transaction info: %s", err) } @@ -196,7 +197,7 @@ func (l *MEVLane) VerifyBidBasic( return nil, fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err) } - bundledTxInfo, err := utils.GetTxInfo(l.TxEncoder(), bundledTx) + bundledTxInfo, err := l.GetTxInfo(ctx, bundledTx) if err != nil { return nil, fmt.Errorf("err retrieving transaction info: %s", err) } From b337085bbb58141d7f50d09348c198ca7d961e0f Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 11:09:12 -0500 Subject: [PATCH 11/17] removing logs from testing --- abci/abci_test.go | 75 +++++++++++++++++++++++++++++++++++++++++----- abci/utils_test.go | 35 ++++++++++++++++++---- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/abci/abci_test.go b/abci/abci_test.go index 6ab3b848..81f53323 100644 --- a/abci/abci_test.go +++ b/abci/abci_test.go @@ -16,6 +16,7 @@ import ( "github.com/skip-mev/block-sdk/abci" "github.com/skip-mev/block-sdk/block" "github.com/skip-mev/block-sdk/block/mocks" + "github.com/skip-mev/block-sdk/lanes/free" testutils "github.com/skip-mev/block-sdk/testutils" blocksdkmoduletypes "github.com/skip-mev/block-sdk/x/blocksdk/types" ) @@ -521,7 +522,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewTestLogger(s.T()), + log.NewNopLogger(), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -534,7 +535,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -587,7 +588,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewTestLogger(s.T()), + log.NewNopLogger(), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -600,7 +601,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -660,7 +661,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewTestLogger(s.T()), + log.NewNopLogger(), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -675,7 +676,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -735,7 +736,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { } mempool, err := block.NewLanedMempool( - log.NewTestLogger(s.T()), + log.NewNopLogger(), lanes, mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) { return blocksdkmoduletypes.Lane{}, nil @@ -750,7 +751,7 @@ func (s *ProposalsTestSuite) TestPrepareProposalEdgeCases() { defaultLane.SetIgnoreList(nil) proposalHandler := abci.NewProposalHandler( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, @@ -1271,6 +1272,64 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().Error(err) s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) }) + + s.Run("rejects a proposal where there are transactions remaining that have been unverified", func() { + bidTx, _, err := testutils.CreateAuctionTx( + s.encodingConfig.TxConfig, + s.accounts[0], + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + 0, + 1, + s.accounts[0:0], + 1, + ) + s.Require().NoError(err) + + freeTx, err := testutils.CreateFreeTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 1, + "test", + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2000000)), + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2000000)), + ) + s.Require().NoError(err) + + normalTx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(3000000)), + ) + s.Require().NoError(err) + + // Set up the top of block lane + mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.5"), map[sdk.Tx]bool{ + bidTx: true, + }) + + // Set up the default lane + freeLane := s.setUpCustomMatchHandlerLane( + math.LegacyMustNewDecFromStr("0.0"), + map[sdk.Tx]bool{ + freeTx: true, + }, + free.DefaultMatchHandler(), + "default", + ) + + proposal := s.createProposal(bidTx, freeTx, normalTx) + + proposalHandler := s.setUpProposalHandlers([]block.Lane{mevLane, freeLane}).ProcessProposalHandler() + resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2}) + s.Require().NotNil(resp) + s.Require().Error(err) + s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, resp) + }) } func (s *ProposalsTestSuite) TestPrepareProcessParity() { diff --git a/abci/utils_test.go b/abci/utils_test.go index 75e5332d..a22815a4 100644 --- a/abci/utils_test.go +++ b/abci/utils_test.go @@ -55,9 +55,32 @@ func (s *ProposalsTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) return anteHandler } +func (s *ProposalsTestSuite) setUpCustomMatchHandlerLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool, mh base.MatchHandler, name string) block.Lane { + cfg := base.LaneConfig{ + Logger: log.NewNopLogger(), + TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), + TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), + AnteHandler: s.setUpAnteHandler(expectedExecution), + MaxBlockSpace: maxBlockSpace, + SignerExtractor: signeradaptors.NewDefaultAdapter(), + } + + lane := base.NewBaseLane( + cfg, + name, + base.NewMempool[string](base.DefaultTxPriority(), cfg.TxEncoder, cfg.SignerExtractor, 0), + mh, + ) + + lane.SetPrepareLaneHandler(lane.DefaultPrepareLaneHandler()) + lane.SetProcessLaneHandler(lane.DefaultProcessLaneHandler()) + + return lane +} + func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *defaultlane.DefaultLane { cfg := base.LaneConfig{ - Logger: log.NewTestLogger(s.T()), + Logger: log.NewNopLogger(), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), @@ -71,7 +94,7 @@ func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, exp func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *mev.MEVLane { cfg := base.LaneConfig{ - Logger: log.NewTestLogger(s.T()), + Logger: log.NewNopLogger(), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), @@ -84,7 +107,7 @@ func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expected func (s *ProposalsTestSuite) setUpFreeLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *free.FreeLane { cfg := base.LaneConfig{ - Logger: log.NewTestLogger(s.T()), + Logger: log.NewNopLogger(), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), AnteHandler: s.setUpAnteHandler(expectedExecution), @@ -97,7 +120,7 @@ func (s *ProposalsTestSuite) setUpFreeLane(maxBlockSpace math.LegacyDec, expecte func (s *ProposalsTestSuite) setUpPanicLane(name string, maxBlockSpace math.LegacyDec) *base.BaseLane { cfg := base.LaneConfig{ - Logger: log.NewTestLogger(s.T()), + Logger: log.NewNopLogger(), TxEncoder: s.encodingConfig.TxConfig.TxEncoder(), TxDecoder: s.encodingConfig.TxConfig.TxDecoder(), MaxBlockSpace: maxBlockSpace, @@ -136,14 +159,14 @@ func (s *ProposalsTestSuite) setUpProposalHandlers(lanes []block.Lane) *abci.Pro }) mempool, err := block.NewLanedMempool( - log.NewTestLogger(s.T()), + log.NewNopLogger(), lanes, laneFetcher, ) s.Require().NoError(err) return abci.NewProposalHandler( - log.NewTestLogger(s.T()), + log.NewNopLogger(), s.encodingConfig.TxConfig.TxDecoder(), s.encodingConfig.TxConfig.TxEncoder(), mempool, From 0f8c6f87a64b2d24b14d9ad11cbeb03b64251b6f Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 11:34:08 -0500 Subject: [PATCH 12/17] query test --- x/auction/keeper/grpc_query_test.go | 21 +++++++++++++++++++++ x/auction/keeper/keeper_test.go | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 x/auction/keeper/grpc_query_test.go diff --git a/x/auction/keeper/grpc_query_test.go b/x/auction/keeper/grpc_query_test.go new file mode 100644 index 00000000..a124a228 --- /dev/null +++ b/x/auction/keeper/grpc_query_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/skip-mev/block-sdk/x/auction/types" +) + +func (s *KeeperTestSuite) TestQueryParams() { + s.Run("can query module params", func() { + params, err := s.auctionkeeper.GetParams(s.ctx) + s.Require().NoError(err) + + escrowAddress := sdk.AccAddress(params.EscrowAccountAddress) + + res, err := s.queryServer.Params(s.ctx, &types.QueryParamsRequest{}) + s.Require().NoError(err) + s.Require().Equal(params, res.Params) + s.Require().Equal(escrowAddress.String(), res.EscrowAddressString) + }) +} diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index f77fc70d..37967021 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -25,9 +25,11 @@ type KeeperTestSuite struct { stakingKeeper *mocks.StakingKeeper encCfg testutils.EncodingConfig ctx sdk.Context - msgServer types.MsgServer key *storetypes.KVStoreKey authorityAccount sdk.AccAddress + + msgServer types.MsgServer + queryServer types.QueryServer } func TestKeeperTestSuite(t *testing.T) { @@ -61,4 +63,5 @@ func (s *KeeperTestSuite) SetupTest() { s.Require().NoError(err) s.msgServer = keeper.NewMsgServerImpl(s.auctionkeeper) + s.queryServer = keeper.NewQueryServer(s.auctionkeeper) } From a562fcd6cf245bfb63161bb0b8943adb204ec811 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 14:39:06 -0500 Subject: [PATCH 13/17] logging with tx info --- block/base/abci.go | 39 ++++++++++- block/base/tx_info.go | 11 +-- block/base/tx_info_test.go | 1 + block/lane.go | 2 +- block/mocks/lane.go | 10 +-- block/proposals/proposals_test.go | 109 ++++++++++++++++++++++++------ block/proposals/update.go | 31 ++++----- block/utils/utils.go | 8 ++- lanes/base/abci_test.go | 5 +- lanes/mev/abci_test.go | 10 +-- lanes/terminator/lane.go | 13 +++- tests/integration/chain_setup.go | 4 ++ 12 files changed, 179 insertions(+), 64 deletions(-) create mode 100644 block/base/tx_info_test.go diff --git a/block/base/abci.go b/block/base/abci.go index 2ef37dcb..9f0d8825 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -42,9 +42,27 @@ func (l *BaseLane) PrepareLane( ) } + // Get the transaction info for each transaction that was selected to be included in the + // partial proposal. + txsWithInfo := make([]utils.TxWithInfo, len(txsToInclude)) + for i, tx := range txsToInclude { + txInfo, err := l.GetTxInfo(ctx, tx) + if err != nil { + l.Logger().Error( + "failed to get tx info", + "lane", l.Name(), + "err", err, + ) + + return proposal, err + } + + txsWithInfo[i] = txInfo + } + // Update the proposal with the selected transactions. This fails if the lane attempted to add // more transactions than the allocated max block space for the lane. - if err := proposal.UpdateProposal(ctx, l, txsToInclude); err != nil { + if err := proposal.UpdateProposal(l, txsWithInfo); err != nil { l.Logger().Error( "failed to update proposal", "lane", l.Name(), @@ -102,8 +120,25 @@ func (l *BaseLane) ProcessLane( return proposal, err } + // Retrieve the transaction info for each transaction that belongs to the lane. + txsWithInfo := make([]utils.TxWithInfo, len(txsFromLane)) + for i, tx := range txsFromLane { + txInfo, err := l.GetTxInfo(ctx, tx) + if err != nil { + l.Logger().Error( + "failed to get tx info", + "lane", l.Name(), + "err", err, + ) + + return proposal, err + } + + txsWithInfo[i] = txInfo + } + // Optimistically update the proposal with the partial proposal. - if err := proposal.UpdateProposal(ctx, l, txsFromLane); err != nil { + if err := proposal.UpdateProposal(l, txsWithInfo); err != nil { l.Logger().Error( "failed to update proposal", "lane", l.Name(), diff --git a/block/base/tx_info.go b/block/base/tx_info.go index 47bff385..fed1cc0a 100644 --- a/block/base/tx_info.go +++ b/block/base/tx_info.go @@ -13,29 +13,30 @@ import ( // GetTxInfo returns various information about the transaction that // belongs to the lane including its priority, signer's, sequence number // size and more. -func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxInfo, error) { +func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, error) { txBytes, err := l.cfg.TxEncoder(tx) if err != nil { - return utils.TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err) + return utils.TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) } // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. gasTx, ok := tx.(sdk.FeeTx) if !ok { - return utils.TxInfo{}, fmt.Errorf("failed to cast transaction to GasTx") + return utils.TxWithInfo{}, fmt.Errorf("failed to cast transaction to GasTx") } signers, err := l.cfg.SignerExtractor.GetSigners(tx) if err != nil { - return utils.TxInfo{}, err + return utils.TxWithInfo{}, err } - return utils.TxInfo{ + return utils.TxWithInfo{ Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), Size: int64(len(txBytes)), GasLimit: gasTx.GetGas(), TxBytes: txBytes, Priority: l.LaneMempool.Priority(ctx, tx), Signers: signers, + Tx: tx, }, nil } diff --git a/block/base/tx_info_test.go b/block/base/tx_info_test.go new file mode 100644 index 00000000..265c95ae --- /dev/null +++ b/block/base/tx_info_test.go @@ -0,0 +1 @@ +package base_test diff --git a/block/lane.go b/block/lane.go index 83ab014e..9f75abdb 100644 --- a/block/lane.go +++ b/block/lane.go @@ -82,7 +82,7 @@ type Lane interface { // GetTxInfo returns various information about the transaction that // belongs to the lane including its priority, signer's, sequence number // size and more. - GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxInfo, error) + GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, error) } // FindLane finds a Lanes from in an array of Lanes and returns it and its index if found. diff --git a/block/mocks/lane.go b/block/mocks/lane.go index a02d35a0..8002e310 100644 --- a/block/mocks/lane.go +++ b/block/mocks/lane.go @@ -108,18 +108,18 @@ func (_m *Lane) GetMaxBlockSpace() math.LegacyDec { } // GetTxInfo provides a mock function with given fields: ctx, tx -func (_m *Lane) GetTxInfo(ctx types.Context, tx types.Tx) (utils.TxInfo, error) { +func (_m *Lane) GetTxInfo(ctx types.Context, tx types.Tx) (utils.TxWithInfo, error) { ret := _m.Called(ctx, tx) - var r0 utils.TxInfo + var r0 utils.TxWithInfo var r1 error - if rf, ok := ret.Get(0).(func(types.Context, types.Tx) (utils.TxInfo, error)); ok { + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) (utils.TxWithInfo, error)); ok { return rf(ctx, tx) } - if rf, ok := ret.Get(0).(func(types.Context, types.Tx) utils.TxInfo); ok { + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) utils.TxWithInfo); ok { r0 = rf(ctx, tx) } else { - r0 = ret.Get(0).(utils.TxInfo) + r0 = ret.Get(0).(utils.TxWithInfo) } if rf, ok := ret.Get(1).(func(types.Context, types.Tx) error); ok { diff --git a/block/proposals/proposals_test.go b/block/proposals/proposals_test.go index 3b0655cc..61a83810 100644 --- a/block/proposals/proposals_test.go +++ b/block/proposals/proposals_test.go @@ -9,10 +9,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + signerextraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" + "github.com/skip-mev/block-sdk/block/base" "github.com/skip-mev/block-sdk/block/mocks" "github.com/skip-mev/block-sdk/block/proposals" "github.com/skip-mev/block-sdk/block/proposals/types" "github.com/skip-mev/block-sdk/block/utils" + defaultlane "github.com/skip-mev/block-sdk/lanes/base" "github.com/skip-mev/block-sdk/testutils" ) @@ -29,7 +32,7 @@ func TestUpdateProposal(t *testing.T) { lane.On("GetMaxBlockSpace").Return(math.LegacyNewDec(1)).Maybe() t.Run("can update with no transactions", func(t *testing.T) { - proposal := proposals.NewProposal(log.NewNopLogger(), nil, 100, 100) + proposal := proposals.NewProposal(log.NewTestLogger(t), nil, 100, 100) err := proposal.UpdateProposal(lane, nil) require.NoError(t, err) @@ -61,9 +64,12 @@ func TestUpdateProposal(t *testing.T) { size := len(txBzs[0]) gasLimit := 100 - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) + + err = proposal.UpdateProposal(lane, txsWithInfo) require.NoError(t, err) // Ensure that the proposal is not empty. @@ -107,9 +113,12 @@ func TestUpdateProposal(t *testing.T) { gasLimit += 100 } - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit) + + txsWithInfo, err := getTxsWithInfo(txs) + require.NoError(t, err) - err = proposal.UpdateProposal(lane, txs) + err = proposal.UpdateProposal(lane, txsWithInfo) require.NoError(t, err) // Ensure that the proposal is not empty. @@ -144,9 +153,12 @@ func TestUpdateProposal(t *testing.T) { size := int64(len(txBzs[0])) gasLimit := uint64(100) - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), size, gasLimit) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), size, gasLimit) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) + + err = proposal.UpdateProposal(lane, txsWithInfo) require.NoError(t, err) // Ensure that the proposal is empty. @@ -161,8 +173,11 @@ func TestUpdateProposal(t *testing.T) { otherlane.On("Name").Return("test").Maybe() otherlane.On("GetMaxBlockSpace").Return(math.LegacyNewDec(1)).Maybe() + txsWithInfo, err = getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) + // Attempt to add the same transaction again. - err = proposal.UpdateProposal(otherlane, []sdk.Tx{tx}) + err = proposal.UpdateProposal(otherlane, txsWithInfo) require.Error(t, err) require.Equal(t, 1, len(proposal.Txs)) @@ -204,12 +219,18 @@ func TestUpdateProposal(t *testing.T) { size := len(txBzs[0]) + len(txBzs[1]) gasLimit := 200 - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) require.NoError(t, err) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx2}) + err = proposal.UpdateProposal(lane, txsWithInfo) + require.NoError(t, err) + + txsWithInfo, err = getTxsWithInfo([]sdk.Tx{tx2}) + require.NoError(t, err) + + err = proposal.UpdateProposal(lane, txsWithInfo) require.Error(t, err) // Ensure that the proposal is not empty. @@ -242,14 +263,17 @@ func TestUpdateProposal(t *testing.T) { size := len(txBzs[0]) gasLimit := 100 - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) lane := mocks.NewLane(t) lane.On("Name").Return("test").Maybe() lane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("0.5")).Maybe() - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) + + err = proposal.UpdateProposal(lane, txsWithInfo) require.Error(t, err) // Ensure that the proposal is empty. @@ -280,14 +304,17 @@ func TestUpdateProposal(t *testing.T) { size := len(txBzs[0]) gasLimit := 100 - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)) lane := mocks.NewLane(t) lane.On("Name").Return("test").Maybe() lane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("0.5")).Maybe() - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) + + err = proposal.UpdateProposal(lane, txsWithInfo) require.Error(t, err) // Ensure that the proposal is empty. @@ -318,9 +345,12 @@ func TestUpdateProposal(t *testing.T) { size := len(txBzs[0]) gasLimit := 100 - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size)-1, uint64(gasLimit)) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size)-1, uint64(gasLimit)) + + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + err = proposal.UpdateProposal(lane, txsWithInfo) require.Error(t, err) // Ensure that the proposal is empty. @@ -351,9 +381,12 @@ func TestUpdateProposal(t *testing.T) { size := len(txBzs[0]) gasLimit := 100 - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)-1) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)-1) + + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + err = proposal.UpdateProposal(lane, txsWithInfo) require.Error(t, err) // Ensure that the proposal is empty. @@ -392,16 +425,22 @@ func TestUpdateProposal(t *testing.T) { txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx, tx2}) require.NoError(t, err) - proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), 10000, 10000) + proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), 10000, 10000) + + txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx}) + require.NoError(t, err) - err = proposal.UpdateProposal(lane, []sdk.Tx{tx}) + err = proposal.UpdateProposal(lane, txsWithInfo) require.NoError(t, err) otherlane := mocks.NewLane(t) otherlane.On("Name").Return("test2") otherlane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("1.0")) - err = proposal.UpdateProposal(otherlane, []sdk.Tx{tx2}) + txsWithInfo, err = getTxsWithInfo([]sdk.Tx{tx2}) + require.NoError(t, err) + + err = proposal.UpdateProposal(otherlane, txsWithInfo) require.NoError(t, err) size := len(txBzs[0]) + len(txBzs[1]) @@ -540,3 +579,29 @@ func TestGetLaneLimits(t *testing.T) { }) } } + +func getTxsWithInfo(txs []sdk.Tx) ([]utils.TxWithInfo, error) { + encoding := testutils.CreateTestEncodingConfig() + + cfg := base.NewLaneConfig( + log.NewNopLogger(), + encoding.TxConfig.TxEncoder(), + encoding.TxConfig.TxDecoder(), + nil, + signerextraction.NewDefaultAdapter(), + math.LegacyNewDec(1), + ) + lane := defaultlane.NewDefaultLane(cfg) + + txsWithInfo := make([]utils.TxWithInfo, len(txs)) + for i, tx := range txs { + txInfo, err := lane.GetTxInfo(sdk.Context{}, tx) + if err != nil { + return nil, err + } + + txsWithInfo[i] = txInfo + } + + return txsWithInfo, nil +} diff --git a/block/proposals/update.go b/block/proposals/update.go index e93d6c13..dfaf1858 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -4,7 +4,6 @@ import ( "fmt" "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/skip-mev/block-sdk/block/utils" ) @@ -12,7 +11,6 @@ import ( // Lane defines the contract interface for a lane. type Lane interface { Name() string - GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxInfo, error) GetMaxBlockSpace() math.LegacyDec } @@ -26,7 +24,7 @@ type Lane interface { // the lane. // 5. The lane must not have already prepared a partial proposal. // 6. The transaction must not already be in the proposal. -func (p *Proposal) UpdateProposal(ctx sdk.Context, lane Lane, partialProposal []sdk.Tx) error { +func (p *Proposal) UpdateProposal(lane Lane, partialProposal []utils.TxWithInfo) error { if len(partialProposal) == 0 { return nil } @@ -43,31 +41,26 @@ func (p *Proposal) UpdateProposal(ctx sdk.Context, lane Lane, partialProposal [] partialProposalGasLimit := uint64(0) for index, tx := range partialProposal { - txInfo, err := lane.GetTxInfo(ctx, tx) - if err != nil { - return fmt.Errorf("err retrieving transaction info: %s", err) - } - p.Logger.Info( "updating proposal with tx", "index", index+len(p.Txs), "lane", lane.Name(), - "hash", txInfo.Hash, - "size", txInfo.Size, - "gas_limit", txInfo.GasLimit, - "signers", txInfo.Signers, - "priority", txInfo.Priority, + "hash", tx.Hash, + "size", tx.Size, + "gas_limit", tx.GasLimit, + "signers", tx.Signers, + "priority", tx.Priority, ) // invariant check: Ensure that the transaction is not already in the proposal. - if _, ok := p.Cache[txInfo.Hash]; ok { - return fmt.Errorf("transaction %s is already in the proposal", txInfo.Hash) + if _, ok := p.Cache[tx.Hash]; ok { + return fmt.Errorf("transaction %s is already in the proposal", tx.Hash) } - hashes[txInfo.Hash] = struct{}{} - partialProposalSize += txInfo.Size - partialProposalGasLimit += txInfo.GasLimit - txs[index] = txInfo.TxBytes + hashes[tx.Hash] = struct{}{} + partialProposalSize += tx.Size + partialProposalGasLimit += tx.GasLimit + txs[index] = tx.TxBytes } // invariant check: Ensure that the partial proposal is not too large. diff --git a/block/utils/utils.go b/block/utils/utils.go index 02d1d796..aec8f062 100644 --- a/block/utils/utils.go +++ b/block/utils/utils.go @@ -11,9 +11,9 @@ import ( signerextraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter" ) -// TxInfo contains the information required for a transaction to be +// TxWithInfo contains the information required for a transaction to be // included in a proposal. -type TxInfo struct { +type TxWithInfo struct { // Hash is the hex-encoded hash of the transaction. Hash string // Size is the size of the transaction in bytes. @@ -24,8 +24,10 @@ type TxInfo struct { TxBytes []byte // Priority defines the priority of the transaction. Priority any - // signerData defines the signers of a transaction. + // Signers defines the signers of a transaction. Signers []signerextraction.SignerData + // Tx is the sdk.Tx representation of the transaction. + Tx sdk.Tx } // GetTxHash returns the string hash representation of a transaction. diff --git a/lanes/base/abci_test.go b/lanes/base/abci_test.go index 02b1435c..ab9c6c28 100644 --- a/lanes/base/abci_test.go +++ b/lanes/base/abci_test.go @@ -510,7 +510,10 @@ func (s *BaseTestSuite) TestPrepareLane() { mockLane.On("Name").Return("test") mockLane.On("GetMaxBlockSpace").Return(math.LegacyOneDec()) - err = emptyProposal.UpdateProposal(mockLane, []sdk.Tx{tx}) + txWithInfo, err := lane.GetTxInfo(s.ctx, tx) + s.Require().NoError(err) + + err = emptyProposal.UpdateProposal(mockLane, []utils.TxWithInfo{txWithInfo}) s.Require().NoError(err) finalProposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler()) diff --git a/lanes/mev/abci_test.go b/lanes/mev/abci_test.go index d8ddbaee..fee3c515 100644 --- a/lanes/mev/abci_test.go +++ b/lanes/mev/abci_test.go @@ -547,7 +547,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() { ) s.Require().NoError(err) - bundle, err := lane.VerifyBidBasic(bidTx, proposal, limits) + bundle, err := lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits) s.Require().NoError(err) s.compare(bundle, expectedBundle) }) @@ -563,7 +563,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() { ) s.Require().NoError(err) - _, err = lane.VerifyBidBasic(tx, proposal, limits) + _, err = lane.VerifyBidBasic(s.ctx, tx, proposal, limits) s.Require().Error(err) }) @@ -579,7 +579,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() { ) s.Require().NoError(err) - _, err = lane.VerifyBidBasic(bidTx, proposal, limits) + _, err = lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits) s.Require().Error(err) }) @@ -599,7 +599,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() { proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), size-1, 100) limits := proposal.GetLaneLimits(lane.GetMaxBlockSpace()) - _, err = lane.VerifyBidBasic(bidTx, proposal, limits) + _, err = lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits) s.Require().Error(err) }) @@ -624,7 +624,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() { ) s.Require().NoError(err) - _, err = lane.VerifyBidBasic(bidTx, proposal, limits) + _, err = lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits) s.Require().Error(err) }) } diff --git a/lanes/terminator/lane.go b/lanes/terminator/lane.go index c8e37273..e7d59a0c 100644 --- a/lanes/terminator/lane.go +++ b/lanes/terminator/lane.go @@ -11,6 +11,7 @@ import ( "github.com/skip-mev/block-sdk/block" "github.com/skip-mev/block-sdk/block/proposals" + "github.com/skip-mev/block-sdk/block/utils" ) const ( @@ -73,6 +74,11 @@ func (t Terminator) Name() string { return LaneName } +// GetTxInfo is a no-op +func (t Terminator) GetTxInfo(_ sdk.Context, _ sdk.Tx) (utils.TxWithInfo, error) { + return utils.TxWithInfo{}, fmt.Errorf("terminator lane should not have any transactions") +} + // SetAnteHandler is a no-op func (t Terminator) SetAnteHandler(sdk.AnteHandler) {} @@ -114,7 +120,12 @@ func (t Terminator) Select(context.Context, [][]byte) sdkmempool.Iterator { return nil } -// HasHigherPriority is a no-op +// Compare is a no-op func (t Terminator) Compare(sdk.Context, sdk.Tx, sdk.Tx) (int, error) { return 0, nil } + +// Priority is a no-op +func (t Terminator) Priority(sdk.Context, sdk.Tx) any { + return 0 +} diff --git a/tests/integration/chain_setup.go b/tests/integration/chain_setup.go index c2929c38..b35aea16 100644 --- a/tests/integration/chain_setup.go +++ b/tests/integration/chain_setup.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "context" + "crypto/rand" "encoding/hex" "encoding/json" "io" @@ -180,6 +181,9 @@ func (s *IntegrationTestSuite) CreateDummyFreeTx( delegation, ) + // Create a random gas price + gasPrice := rand.Int63n(1000) + return Tx{ User: user, Msgs: []sdk.Msg{delegateMsg}, From c95343b353dce52f831b63eed132f60b42433415 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 16:03:03 -0500 Subject: [PATCH 14/17] nits --- .../signer_extraction_adapter.go | 13 +++ block/base/abci.go | 3 +- block/base/tx_info.go | 3 +- block/base/tx_info_test.go | 1 - block/lane.go | 2 +- block/utils/utils.go | 27 +++++- lanes/base/tx_info_test.go | 94 +++++++++++++++++++ tests/integration/chain_setup.go | 7 +- 8 files changed, 137 insertions(+), 13 deletions(-) delete mode 100644 block/base/tx_info_test.go create mode 100644 lanes/base/tx_info_test.go diff --git a/adapters/signer_extraction_adapter/signer_extraction_adapter.go b/adapters/signer_extraction_adapter/signer_extraction_adapter.go index e905c5b3..98ae04f9 100644 --- a/adapters/signer_extraction_adapter/signer_extraction_adapter.go +++ b/adapters/signer_extraction_adapter/signer_extraction_adapter.go @@ -12,6 +12,19 @@ type SignerData struct { Sequence uint64 } +// NewSignerData returns a new SignerData instance. +func NewSignerData(signer sdk.AccAddress, sequence uint64) SignerData { + return SignerData{ + Signer: signer, + Sequence: sequence, + } +} + +// String implements the fmt.Stringer interface. +func (s SignerData) String() string { + return fmt.Sprintf("SignerData{Signer: %s, Sequence: %d}", s.Signer, s.Sequence) +} + // SignerExtractionAdapter is an interface used to determine how the signers of a transaction should be extracted // from the transaction. type Adapter interface { diff --git a/block/base/abci.go b/block/base/abci.go index 9f0d8825..1da77d0f 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -42,8 +42,7 @@ func (l *BaseLane) PrepareLane( ) } - // Get the transaction info for each transaction that was selected to be included in the - // partial proposal. + // Get the transaction info for each transaction that was selected. txsWithInfo := make([]utils.TxWithInfo, len(txsToInclude)) for i, tx := range txsToInclude { txInfo, err := l.GetTxInfo(ctx, tx) diff --git a/block/base/tx_info.go b/block/base/tx_info.go index fed1cc0a..3b000ed0 100644 --- a/block/base/tx_info.go +++ b/block/base/tx_info.go @@ -11,7 +11,7 @@ import ( ) // GetTxInfo returns various information about the transaction that -// belongs to the lane including its priority, signer's, sequence number +// belongs to the lane including its priority, signer's, sequence number, // size and more. func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, error) { txBytes, err := l.cfg.TxEncoder(tx) @@ -37,6 +37,5 @@ func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, erro TxBytes: txBytes, Priority: l.LaneMempool.Priority(ctx, tx), Signers: signers, - Tx: tx, }, nil } diff --git a/block/base/tx_info_test.go b/block/base/tx_info_test.go deleted file mode 100644 index 265c95ae..00000000 --- a/block/base/tx_info_test.go +++ /dev/null @@ -1 +0,0 @@ -package base_test diff --git a/block/lane.go b/block/lane.go index 9f75abdb..7415547a 100644 --- a/block/lane.go +++ b/block/lane.go @@ -80,7 +80,7 @@ type Lane interface { Match(ctx sdk.Context, tx sdk.Tx) bool // GetTxInfo returns various information about the transaction that - // belongs to the lane including its priority, signer's, sequence number + // belongs to the lane including its priority, signer's, sequence number, // size and more. GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, error) } diff --git a/block/utils/utils.go b/block/utils/utils.go index aec8f062..c0ecd187 100644 --- a/block/utils/utils.go +++ b/block/utils/utils.go @@ -26,8 +26,31 @@ type TxWithInfo struct { Priority any // Signers defines the signers of a transaction. Signers []signerextraction.SignerData - // Tx is the sdk.Tx representation of the transaction. - Tx sdk.Tx +} + +// NewTxInfo returns a new TxInfo instance. +func NewTxInfo( + hash string, + size int64, + gasLimit uint64, + txBytes []byte, + priority any, + signers []signerextraction.SignerData, +) TxWithInfo { + return TxWithInfo{ + Hash: hash, + Size: size, + GasLimit: gasLimit, + TxBytes: txBytes, + Priority: priority, + Signers: signers, + } +} + +// String implements the fmt.Stringer interface. +func (t TxWithInfo) String() string { + return fmt.Sprintf("TxWithInfo{Hash: %s, Size: %d, GasLimit: %d, Priority: %s, Signers: %v}", + t.Hash, t.Size, t.GasLimit, t.Priority, t.Signers) } // GetTxHash returns the string hash representation of a transaction. diff --git a/lanes/base/tx_info_test.go b/lanes/base/tx_info_test.go new file mode 100644 index 00000000..a5abb069 --- /dev/null +++ b/lanes/base/tx_info_test.go @@ -0,0 +1,94 @@ +package base_test + +import ( + "math/rand" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/block-sdk/testutils" +) + +func (s *BaseTestSuite) TestGetTxInfo() { + accounts := testutils.RandomAccounts(rand.New(rand.NewSource(1)), 3) + lane := s.initLane(math.LegacyOneDec(), nil) + + s.Run("can retrieve information for a default tx", func() { + signer := accounts[0] + nonce := uint64(1) + fee := sdk.NewCoins(sdk.NewCoin(s.gasTokenDenom, math.NewInt(100))) + gasLimit := uint64(100) + + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + signer, + nonce, + 1, + 0, + gasLimit, + fee..., + ) + s.Require().NoError(err) + + txInfo, err := lane.GetTxInfo(s.ctx, tx) + s.Require().NoError(err) + s.Require().NotEmpty(txInfo.Hash) + + // Verify the signers + s.Require().Len(txInfo.Signers, 1) + s.Require().Equal(signer.Address.String(), txInfo.Signers[0].Signer.String()) + s.Require().Equal(nonce, txInfo.Signers[0].Sequence) + + // Verify the priority + actualfee, err := sdk.ParseCoinsNormalized(txInfo.Priority.(string)) + s.Require().NoError(err) + s.Require().Equal(fee, actualfee) + + // Verify the gas limit + s.Require().Equal(gasLimit, txInfo.GasLimit) + + // Verify the bytes + txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + s.Require().Equal(txBz, txInfo.TxBytes) + + // Verify the size + s.Require().Equal(int64(len(txBz)), txInfo.Size) + }) + + s.Run("can retrieve information with different fees", func() { + signer := accounts[1] + nonce := uint64(10) + fee := sdk.NewCoins(sdk.NewCoin(s.gasTokenDenom, math.NewInt(20000))) + gasLimit := uint64(10000000) + + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + signer, + nonce, + 10, + 0, + gasLimit, + fee..., + ) + s.Require().NoError(err) + + txInfo, err := lane.GetTxInfo(s.ctx, tx) + s.Require().NoError(err) + s.Require().NotEmpty(txInfo.Hash) + + // Verify the signers + s.Require().Len(txInfo.Signers, 1) + s.Require().Equal(signer.Address.String(), txInfo.Signers[0].Signer.String()) + s.Require().Equal(nonce, txInfo.Signers[0].Sequence) + + // Verify the priority + actualfee, err := sdk.ParseCoinsNormalized(txInfo.Priority.(string)) + s.Require().NoError(err) + s.Require().Equal(fee, actualfee) + + // Verify the bytes + txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + s.Require().Equal(txBz, txInfo.TxBytes) + }) +} diff --git a/tests/integration/chain_setup.go b/tests/integration/chain_setup.go index b35aea16..f57f6575 100644 --- a/tests/integration/chain_setup.go +++ b/tests/integration/chain_setup.go @@ -4,10 +4,10 @@ import ( "archive/tar" "bytes" "context" - "crypto/rand" "encoding/hex" "encoding/json" "io" + "math/rand" "os" "path" "strings" @@ -181,13 +181,10 @@ func (s *IntegrationTestSuite) CreateDummyFreeTx( delegation, ) - // Create a random gas price - gasPrice := rand.Int63n(1000) - return Tx{ User: user, Msgs: []sdk.Msg{delegateMsg}, - GasPrice: 1000, + GasPrice: rand.Int63n(150000), SequenceIncrement: sequenceOffset, SkipInclusionCheck: true, IgnoreChecks: true, From 765de40197cd2938a90a4796a24fe9c3233ef89d Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 17:11:20 -0500 Subject: [PATCH 15/17] nit --- block/base/tx_info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/base/tx_info.go b/block/base/tx_info.go index 3b000ed0..3de609ec 100644 --- a/block/base/tx_info.go +++ b/block/base/tx_info.go @@ -22,7 +22,7 @@ func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, erro // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. gasTx, ok := tx.(sdk.FeeTx) if !ok { - return utils.TxWithInfo{}, fmt.Errorf("failed to cast transaction to GasTx") + return utils.TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") } signers, err := l.cfg.SignerExtractor.GetSigners(tx) From 36ec06af65163921d28e2ccadabda48e6cbb162a Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 17:48:51 -0500 Subject: [PATCH 16/17] nit --- lanes/mev/mempool.go | 4 ++-- tests/integration/block_sdk_suite.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lanes/mev/mempool.go b/lanes/mev/mempool.go index cae04e62..a7e8cdcc 100644 --- a/lanes/mev/mempool.go +++ b/lanes/mev/mempool.go @@ -14,8 +14,8 @@ func TxPriority(config Factory) base.TxPriority[string] { return base.TxPriority[string]{ GetTxPriority: func(goCtx context.Context, tx sdk.Tx) string { bidInfo, err := config.GetAuctionBidInfo(tx) - if err != nil { - panic(err) + if err != nil || bidInfo == nil || bidInfo.Bid.IsNil() { + return "" } return bidInfo.Bid.String() diff --git a/tests/integration/block_sdk_suite.go b/tests/integration/block_sdk_suite.go index d67ba527..b561dca3 100644 --- a/tests/integration/block_sdk_suite.go +++ b/tests/integration/block_sdk_suite.go @@ -109,7 +109,8 @@ func (s *IntegrationTestSuite) SetupSubTest() { // query height height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) require.NoError(s.T(), err) - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+3) + s.T().Logf("reached height %d", height+3) } func (s *IntegrationTestSuite) TestQueryParams() { From f20b15b42d486be93f79a45fb514e964b7808abe Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 27 Nov 2023 18:21:46 -0500 Subject: [PATCH 17/17] testing nit --- tests/integration/block_sdk_suite.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/block_sdk_suite.go b/tests/integration/block_sdk_suite.go index b561dca3..4e401abe 100644 --- a/tests/integration/block_sdk_suite.go +++ b/tests/integration/block_sdk_suite.go @@ -110,7 +110,7 @@ func (s *IntegrationTestSuite) SetupSubTest() { height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) require.NoError(s.T(), err) WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+3) - s.T().Logf("reached height %d", height+3) + s.T().Logf("reached height %d", height+2) } func (s *IntegrationTestSuite) TestQueryParams() { @@ -1355,7 +1355,7 @@ func (s *IntegrationTestSuite) TestNetwork() { } }) - amountToTest.Reset(5 * time.Minute) + amountToTest.Reset(3 * time.Minute) s.Run("can produce blocks with all types of transactions", func() { for { select {