diff --git a/.markdownlint.json b/.markdownlint.json index d5eabd2..118aaca 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 b8d6a81..0782468 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 ( @@ -78,25 +74,17 @@ 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) 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 +99,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { ) return &abci.ResponsePrepareProposal{ - Txs: txs, + Txs: finalProposal.Txs, }, nil } } @@ -119,9 +107,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 { @@ -138,10 +126,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 +140,21 @@ 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)) + // Verify the proposal. + processLanesHandler := ChainProcessLanes(registry) + 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 } - // Ensure block size and gas limit are correct. - if err := h.ValidateBlockLimits(finalProposal, proposalInfo); 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", len(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 bc63e2c..81f5332 100644 --- a/abci/abci_test.go +++ b/abci/abci_test.go @@ -16,7 +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/block/proposals" + "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" ) @@ -93,18 +93,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 +119,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 +159,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 +199,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 +212,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 +244,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 +283,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 +323,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 +359,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 +426,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 +478,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) }) } @@ -733,20 +546,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() { @@ -811,20 +612,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() { @@ -898,20 +687,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() { @@ -985,20 +762,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 +774,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 +821,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 +849,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 +907,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,67 +916,119 @@ func (s *ProposalsTestSuite) TestProcessProposal() { s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}, resp) }) - s.Run("rejects a proposal with mismatching block size", func() { + 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, - 100, + 1, sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), ) s.Require().NoError(err) - mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) + // 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(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 0}, tx) + proposal := s.createProposal(bidTx, bundleTxs[0], tx, freeTx) // tx and freeTx are out of order - // 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() + 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 mismatching gas limit", func() { + 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, - 100, + 1, sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), ) s.Require().NoError(err) - mevLane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), map[sdk.Tx]bool{}) + // 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(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 0}, tx) + proposal := s.createProposal(freeTx, tx, bidTx, bundleTxs[0]) - // 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() + 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) }) @@ -1229,19 +1038,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, - 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,37 +1060,16 @@ 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) 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( - s.encodingConfig.TxConfig, - s.accounts[0], - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - 0, - 1, - nil, - ) - s.Require().NoError(err) - - // Create a random transaction that will be inserted into the default lane - tx2, err := testutils.CreateRandomTx( + tx1, err := testutils.CreateRandomTx( s.encodingConfig.TxConfig, s.accounts[2], 0, @@ -1304,19 +1080,31 @@ func (s *ProposalsTestSuite) TestProcessProposal() { ) s.Require().NoError(err) + // Create a random transaction that will be inserted into the default lane + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 1, + 1, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2000000)), + ) + 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(map[string]uint64{defaultLane.Name(): 1, mevLane.Name(): 1}, 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() { @@ -1366,12 +1154,13 @@ 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}) 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() { @@ -1416,12 +1205,14 @@ 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}) 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() { @@ -1473,12 +1264,71 @@ 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}) s.Require().NotNil(resp) 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) }) } @@ -1573,29 +1423,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 +1534,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 +1543,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 6f411e4..73ad76e 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 @@ -162,8 +67,11 @@ 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. -func ChainProcessLanes(partialProposals [][][]byte, chain []block.Lane) block.ProcessLanesHandler { +// 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 } @@ -171,12 +79,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 453e674..a22815a 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" @@ -58,6 +55,29 @@ 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.NewNopLogger(), @@ -153,51 +173,8 @@ func (s *ProposalsTestSuite) setUpProposalHandlers(lanes []block.Lane) *abci.Pro ) } -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 +188,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 59b0b64..3ac2539 100644 --- a/block/base/abci.go +++ b/block/base/abci.go @@ -76,42 +76,39 @@ 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)) + 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 next(ctx, proposal, txs) } - // Verify the transactions that belong to this lane according to the verification logic of the lane. - if err := l.processLaneHandler(ctx, decodedTxs); err != nil { + // 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( "failed to process lane", "lane", l.Name(), "err", err, - "num_txs_to_verify", len(decodedTxs), ) 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(), + "num_txs_verified", len(txsFromLane), "err", err, - "num_txs_to_verify", len(decodedTxs), ) return proposal, err @@ -120,10 +117,12 @@ 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) + // Validate the remaining transactions with the next lane in the chain. + 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 167753f..702d038 100644 --- a/block/base/handlers.go +++ b/block/base/handlers.go @@ -111,35 +111,59 @@ 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. 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) 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 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 } // 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. There are no remaining transactions. + 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 when it should not") + } + } + + 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 c90d835..a5bb950 100644 --- a/block/base/types.go +++ b/block/base/types.go @@ -21,9 +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. - ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) 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. + 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 137cb85..d0a1a61 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 7100a21..50288a6 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/proposals/proposals.go b/block/proposals/proposals.go index e8bcca1..a966ce8 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/block/proposals/update.go b/block/proposals/update.go index a5896a7..0280586 100644 --- a/block/proposals/update.go +++ b/block/proposals/update.go @@ -1,7 +1,6 @@ package proposals import ( - "encoding/base64" "fmt" "cosmossdk.io/math" @@ -48,15 +47,13 @@ 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(), "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/types.go b/block/types.go index f08ab8a..e00f8df 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/block/utils/utils.go b/block/utils/utils.go index 9f2881d..a0826f2 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 c0636bc..02b1435 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" @@ -521,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() { @@ -560,8 +606,10 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -570,8 +618,13 @@ 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) + + 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() { @@ -623,8 +676,11 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + // + 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(), @@ -633,8 +689,13 @@ 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) + + 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() { @@ -699,8 +760,10 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -709,11 +772,16 @@ 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) + + 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 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,8 +803,10 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -745,8 +815,13 @@ 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) + + 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() { @@ -771,8 +846,10 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) + 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(), @@ -781,7 +858,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 +905,13 @@ func (s *BaseTestSuite) TestProcessLane() { tx1: true, tx2: false, tx3: true, - }) + }, + ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) + 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(), @@ -840,11 +920,11 @@ 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) }) - 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], @@ -863,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) @@ -877,10 +957,13 @@ func (s *BaseTestSuite) TestProcessLane() { map[sdk.Tx]bool{ tx1: true, tx2: true, - }) + }, + ) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -889,11 +972,16 @@ 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) + + 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", 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], @@ -926,10 +1014,13 @@ 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) + 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(), @@ -938,11 +1029,11 @@ 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) }) - 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], @@ -965,14 +1056,27 @@ 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(), map[sdk.Tx]bool{ tx1: true, - tx2: false, - }) + tx2: true, + }, + ) lane.SetIgnoreList([]block.Lane{otherLane}) proposal := []sdk.Tx{ @@ -980,8 +1084,10 @@ func (s *BaseTestSuite) TestProcessLane() { tx2, } - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) + 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(), @@ -990,7 +1096,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) }) @@ -1013,12 +1119,16 @@ func (s *BaseTestSuite) TestProcessLane() { math.LegacyOneDec(), map[sdk.Tx]bool{ tx1: true, - }) + }, + ) - maxSize := s.getTxSize(tx1) - 1 - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -1026,7 +1136,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) }) @@ -1052,10 +1162,12 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - maxSize := s.getTxSize(tx1) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -1063,7 +1175,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) }) @@ -1098,12 +1210,15 @@ func (s *BaseTestSuite) TestProcessLane() { map[sdk.Tx]bool{ tx1: true, tx2: true, - }) + }, + ) + + 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) - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) - s.Require().NoError(err) - emptyProposal := proposals.NewProposal( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1111,7 +1226,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) }) @@ -1149,10 +1264,12 @@ func (s *BaseTestSuite) TestProcessLane() { }, ) - maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1 - partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal) + 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(), @@ -1160,7 +1277,308 @@ 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}) + + 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(), + 1000, + 1000, + ) + + 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() { + 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}) + + 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(), + 1000, + 1000, + ) + + finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler()) + 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) }) } @@ -1213,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++ { @@ -1222,6 +1640,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 +1650,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,9 +1703,6 @@ 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(), s.encodingConfig.TxConfig.TxEncoder(), @@ -1292,9 +1710,9 @@ func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() { 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.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 diff --git a/lanes/mev/abci.go b/lanes/mev/abci.go index 574f01e..5b03a60 100644 --- a/lanes/mev/abci.go +++ b/lanes/mev/abci.go @@ -77,73 +77,84 @@ 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. 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 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 } 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. + // + // TODO: There is duplicate work being done in VerifyBidTx and here. 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 00366e7..d8ddbae 100644 --- a/lanes/mev/abci_test.go +++ b/lanes/mev/abci_test.go @@ -230,23 +230,31 @@ 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() { - 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) + 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)) }) s.Run("can process a proposal with bad bid tx", func() { @@ -261,12 +269,16 @@ 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) + 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) }) @@ -283,12 +295,16 @@ 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) + 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) }) @@ -305,12 +321,16 @@ 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) + 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) }) @@ -327,12 +347,16 @@ 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) + 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,12 +373,16 @@ 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) + 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) }) @@ -371,12 +399,16 @@ 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) + 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) }) @@ -393,12 +425,16 @@ 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) + 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) }) @@ -415,12 +451,80 @@ 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) + 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) }) diff --git a/lanes/terminator/lane.go b/lanes/terminator/lane.go index 0dd70d5..c8e3727 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/block_sdk_integration_test.go b/tests/integration/block_sdk_integration_test.go index 7d891f8..982d0ea 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 dcfdf62..d67ba52 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()) + s.NoError(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()) + s.NoError(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()) + s.NoError(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()) + s.NoError(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()) + s.NoError(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 a5aeeb5..c2929c3 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 } @@ -356,25 +423,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))