From 7005d4c8a35002e0e77b0b2c68e7198a8f78d563 Mon Sep 17 00:00:00 2001 From: David Terpay Date: Mon, 14 Aug 2023 18:02:08 -0400 Subject: [PATCH] default re-factor with constructor --- blockbuster/lanes/base/abci.go | 142 ------ blockbuster/lanes/base/abci_test.go | 547 ++++++++++++++++++++++++ blockbuster/lanes/base/base_test.go | 32 ++ blockbuster/lanes/base/lane.go | 97 +---- blockbuster/lanes/base/mempool.go | 105 ----- blockbuster/lanes/base/mempool_test.go | 240 +++++++++++ blockbuster/lanes/free/factory.go | 47 -- blockbuster/lanes/free/lane.go | 55 ++- blockbuster/utils/mocks/lane.go | 256 +++++++++++ blockbuster/utils/mocks/lane_mempool.go | 117 +++++ testutils/utils.go | 59 ++- 11 files changed, 1305 insertions(+), 392 deletions(-) delete mode 100644 blockbuster/lanes/base/abci.go create mode 100644 blockbuster/lanes/base/abci_test.go create mode 100644 blockbuster/lanes/base/base_test.go delete mode 100644 blockbuster/lanes/base/mempool.go create mode 100644 blockbuster/lanes/base/mempool_test.go delete mode 100644 blockbuster/lanes/free/factory.go create mode 100644 blockbuster/utils/mocks/lane.go create mode 100644 blockbuster/utils/mocks/lane_mempool.go diff --git a/blockbuster/lanes/base/abci.go b/blockbuster/lanes/base/abci.go deleted file mode 100644 index b8ada51..0000000 --- a/blockbuster/lanes/base/abci.go +++ /dev/null @@ -1,142 +0,0 @@ -package base - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/skip-mev/pob/blockbuster" - "github.com/skip-mev/pob/blockbuster/utils" -) - -// PrepareLane will prepare a partial proposal for the default lane. It will select and include -// all valid transactions in the mempool that are not already in the partial proposal. -// The default lane orders transactions by the sdk.Context priority. -func (l *DefaultLane) PrepareLane( - ctx sdk.Context, - proposal blockbuster.BlockProposal, - maxTxBytes int64, - next blockbuster.PrepareLanesHandler, -) (blockbuster.BlockProposal, error) { - // Define all of the info we need to select transactions for the partial proposal. - var ( - totalSize int64 - txs [][]byte - txsToRemove = make(map[sdk.Tx]struct{}, 0) - ) - - // Select all transactions in the mempool that are valid and not already in the - // partial proposal. - for iterator := l.Mempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { - tx := iterator.Tx() - - txBytes, hash, err := utils.GetTxHashStr(l.Cfg.TxEncoder, tx) - if err != nil { - l.Logger().Info("failed to get hash of tx", "err", err) - - txsToRemove[tx] = struct{}{} - continue - } - - // if the transaction is already in the (partial) block proposal, we skip it. - if proposal.Contains(txBytes) { - l.Logger().Info( - "failed to select tx for lane; tx is already in proposal", - "tx_hash", hash, - "lane", l.Name(), - ) - - continue - } - - // If the transaction is too large, we break and do not attempt to include more txs. - txSize := int64(len(txBytes)) - if updatedSize := totalSize + txSize; updatedSize > maxTxBytes { - break - } - - // Verify the transaction. - if err := l.VerifyTx(ctx, tx); err != nil { - l.Logger().Info( - "failed to verify tx", - "tx_hash", hash, - "err", err, - ) - - txsToRemove[tx] = struct{}{} - continue - } - - totalSize += txSize - txs = append(txs, txBytes) - } - - // Remove all transactions that were invalid during the creation of the partial proposal. - if err := utils.RemoveTxsFromLane(txsToRemove, l.Mempool); err != nil { - l.Logger().Error( - "failed to remove transactions from lane", - "err", err, - ) - - return proposal, err - } - - // Update the partial proposal with the selected transactions. If the proposal is unable to - // be updated, we return an error. The proposal will only be modified if it passes all - // of the invarient checks. - if err := proposal.UpdateProposal(l, txs); err != nil { - return proposal, err - } - - return next(ctx, proposal) -} - -// ProcessLane verifies the default lane's portion of a block proposal. Since the default lane's -// ProcessLaneBasic function ensures that all of the default transactions are in the correct order, -// we only need to verify the contiguous set of transactions that match to the default lane. -func (l *DefaultLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next blockbuster.ProcessLanesHandler) (sdk.Context, error) { - for index, tx := range txs { - if l.Match(ctx, tx) { - if err := l.VerifyTx(ctx, tx); err != nil { - return ctx, fmt.Errorf("failed to verify tx: %w", err) - } - } else { - return next(ctx, txs[index:]) - } - } - - // This means we have processed all transactions in the proposal. - return ctx, nil -} - -// transactions that belong to this lane are not misplaced in the block proposal i.e. -// the proposal only contains contiguous transactions that belong to this lane - there -// can be no interleaving of transactions from other lanes. -func (l *DefaultLane) ProcessLaneBasic(ctx sdk.Context, txs []sdk.Tx) error { - seenOtherLaneTx := false - lastSeenIndex := 0 - - for _, tx := range txs { - if l.Match(ctx, tx) { - if seenOtherLaneTx { - return fmt.Errorf("the %s lane contains a transaction that belongs to another lane", l.Name()) - } - - lastSeenIndex++ - continue - } - - seenOtherLaneTx = true - } - - return nil -} - -// VerifyTx does basic verification of the transaction using the ante handler. -func (l *DefaultLane) VerifyTx(ctx sdk.Context, tx sdk.Tx) error { - if l.Cfg.AnteHandler != nil { - _, err := l.Cfg.AnteHandler(ctx, tx, false) - return err - } - - return nil -} diff --git a/blockbuster/lanes/base/abci_test.go b/blockbuster/lanes/base/abci_test.go new file mode 100644 index 0000000..d5c054d --- /dev/null +++ b/blockbuster/lanes/base/abci_test.go @@ -0,0 +1,547 @@ +package base_test + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + + "cosmossdk.io/log" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/blockbuster" + "github.com/skip-mev/pob/blockbuster/lanes/base" + "github.com/skip-mev/pob/blockbuster/utils/mocks" + testutils "github.com/skip-mev/pob/testutils" +) + +func (s *BaseTestSuite) TestPrepareLane() { + s.Run("should not build a proposal when amount configured to lane is too small", func() { + // Create a basic transaction that should not in the proposal + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + 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.LegacyMustNewDecFromStr("1"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}, tx)) + + txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + // Create a proposal + maxTxBytes := int64(len(txBz) - 1) + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + + // Ensure the proposal is empty + s.Require().Equal(0, proposal.GetNumTxs()) + s.Require().Equal(int64(0), proposal.GetTotalTxBytes()) + }) + + s.Run("should not build a proposal when box space configured to lane is too small", func() { + // Create a basic transaction that should not in the proposal + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + 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.LegacyMustNewDecFromStr("0.000001"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}, tx)) + + txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + // Create a proposal + maxTxBytes := int64(len(txBz)) + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().Error(err) + + // Ensure the proposal is empty + s.Require().Equal(0, proposal.GetNumTxs()) + s.Require().Equal(int64(0), proposal.GetTotalTxBytes()) + }) + + s.Run("should be able to build a proposal with a tx that just fits in", func() { + // Create a basic transaction that should not in the proposal + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + 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.LegacyMustNewDecFromStr("1"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}, tx)) + + txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + // Create a proposal + maxTxBytes := int64(len(txBz)) + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + + // Ensure the proposal is not empty and contains the transaction + s.Require().Equal(1, proposal.GetNumTxs()) + s.Require().Equal(maxTxBytes, proposal.GetTotalTxBytes()) + s.Require().Equal(txBz, proposal.GetTxs()[0]) + }) + + s.Run("should not build a proposal with a that fails verify tx", func() { + // Create a basic transaction that should not in the proposal + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + 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: false, + } + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}, tx)) + + // Create a proposal + txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + maxTxBytes := int64(len(txBz)) + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + + // Ensure the proposal is empty + s.Require().Equal(0, proposal.GetNumTxs()) + s.Require().Equal(int64(0), proposal.GetTotalTxBytes()) + + // Ensure the transaction is removed from the lane + s.Require().False(lane.Contains(tx)) + s.Require().Equal(0, lane.CountTx()) + }) + + s.Run("should order transactions correctly in the proposal", func() { + // Create a basic transaction that should not in the proposal + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + 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{ + tx1: true, + tx2: true, + } + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}, tx1)) + s.Require().NoError(lane.Insert(sdk.Context{}, tx2)) + + txBz1, err := s.encodingConfig.TxConfig.TxEncoder()(tx1) + s.Require().NoError(err) + + txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2) + s.Require().NoError(err) + + maxTxBytes := int64(len(txBz1)) + int64(len(txBz2)) + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + + // Ensure the proposal is ordered correctly + s.Require().Equal(2, proposal.GetNumTxs()) + s.Require().Equal(maxTxBytes, proposal.GetTotalTxBytes()) + s.Require().Equal([][]byte{txBz1, txBz2}, proposal.GetTxs()) + }) + + s.Run("should order transactions correctly in the proposal (with different insertion)", func() { + // Create a basic transaction that should not in the proposal + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)), + ) + 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{ + tx1: true, + tx2: true, + } + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}, tx1)) + s.Require().NoError(lane.Insert(sdk.Context{}, tx2)) + + txBz1, err := s.encodingConfig.TxConfig.TxEncoder()(tx1) + s.Require().NoError(err) + + txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2) + s.Require().NoError(err) + + maxTxBytes := int64(len(txBz1)) + int64(len(txBz2)) + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + + // Ensure the proposal is ordered correctly + s.Require().Equal(2, proposal.GetNumTxs()) + s.Require().Equal(maxTxBytes, proposal.GetTotalTxBytes()) + s.Require().Equal([][]byte{txBz2, txBz1}, proposal.GetTxs()) + }) + + s.Run("should include tx that fits in proposal when other does not", func() { + // Create a basic transaction that should not in the proposal + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 10, // This tx is too large to fit in the proposal + 0, + 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{ + tx1: true, + tx2: true, + } + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution) + + // Insert the transaction into the lane + s.Require().NoError(lane.Insert(sdk.Context{}.WithPriority(10), tx1)) + s.Require().NoError(lane.Insert(sdk.Context{}.WithPriority(5), tx2)) + + txBz1, err := s.encodingConfig.TxConfig.TxEncoder()(tx1) + s.Require().NoError(err) + + txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2) + s.Require().NoError(err) + + maxTxBytes := int64(len(txBz1)) + int64(len(txBz2)) - 1 + proposal, err := lane.PrepareLane(sdk.Context{}, blockbuster.NewProposal(maxTxBytes), maxTxBytes, blockbuster.NoOpPrepareLanesHandler()) + s.Require().NoError(err) + + // Ensure the proposal is ordered correctly + s.Require().Equal(1, proposal.GetNumTxs()) + s.Require().Equal(int64(len(txBz1)), proposal.GetTotalTxBytes()) + s.Require().Equal([][]byte{txBz1}, proposal.GetTxs()) + }) +} + +func (s *BaseTestSuite) TestProcessLane() { + s.Run("should accept a proposal with valid transactions", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + } + + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{ + tx1: true, + }) + + _, err = lane.ProcessLane(sdk.Context{}, proposal, blockbuster.NoOpProcessLanesHandler()) + s.Require().NoError(err) + }) + + s.Run("should not accept a proposal with invalid transactions", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + } + + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{ + tx1: false, + }) + + _, err = lane.ProcessLane(sdk.Context{}, proposal, blockbuster.NoOpProcessLanesHandler()) + s.Require().Error(err) + }) + + s.Run("should not accept a proposal with some invalid transactions", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + ) + s.Require().NoError(err) + + tx3, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 1, + 0, + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + tx2, + tx3, + } + + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{ + tx1: true, + tx2: false, + tx3: true, + }) + + _, err = lane.ProcessLane(sdk.Context{}, proposal, blockbuster.NoOpProcessLanesHandler()) + s.Require().Error(err) + }) +} + +func (s *BaseTestSuite) TestCheckOrder() { + s.Run("should accept proposal with transactions in correct order", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + tx2, + } + + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{ + tx1: true, + tx2: true, + }) + s.Require().NoError(lane.CheckOrder(sdk.Context{}, proposal)) + }) + + s.Run("should not accept a proposal with transactions that are not in the correct order", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)), + ) + s.Require().NoError(err) + + proposal := []sdk.Tx{ + tx1, + tx2, + } + + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{ + tx1: true, + tx2: true, + }) + s.Require().Error(lane.CheckOrder(sdk.Context{}, proposal)) + }) + + s.Run("should not accept a proposal where transactions are out of order relative to other lanes", func() { + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 2, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 1, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)), + ) + s.Require().NoError(err) + + mocklane := mocks.NewLane(s.T()) + mocklane.On("Match", sdk.Context{}, tx1).Return(true) + mocklane.On("Match", sdk.Context{}, tx2).Return(false) + + lane := s.initLane(math.LegacyMustNewDecFromStr("1"), nil) + lane.SetIgnoreList([]blockbuster.Lane{mocklane}) + + proposal := []sdk.Tx{ + tx1, + tx2, + } + + s.Require().Error(lane.CheckOrder(sdk.Context{}, proposal)) + }) +} + +func (s *BaseTestSuite) initLane( + maxBlockSpace math.LegacyDec, + expectedExecution map[sdk.Tx]bool, +) *base.DefaultLane { + config := blockbuster.NewBaseLaneConfig( + log.NewTestLogger(s.T()), + s.encodingConfig.TxConfig.TxEncoder(), + s.encodingConfig.TxConfig.TxDecoder(), + s.setUpAnteHandler(expectedExecution), + maxBlockSpace, + ) + + return base.NewDefaultLane(config) +} + +func (s *BaseTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) sdk.AnteHandler { + txCache := make(map[string]bool) + for tx, pass := range expectedExecution { + bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + hash := sha256.Sum256(bz) + hashStr := hex.EncodeToString(hash[:]) + txCache[hashStr] = pass + } + + anteHandler := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { + bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + hash := sha256.Sum256(bz) + hashStr := hex.EncodeToString(hash[:]) + + pass, found := txCache[hashStr] + if !found { + return ctx, fmt.Errorf("tx not found") + } + + if pass { + return ctx, nil + } + + return ctx, fmt.Errorf("tx failed") + } + + return anteHandler +} diff --git a/blockbuster/lanes/base/base_test.go b/blockbuster/lanes/base/base_test.go new file mode 100644 index 0000000..7852dac --- /dev/null +++ b/blockbuster/lanes/base/base_test.go @@ -0,0 +1,32 @@ +package base_test + +import ( + "math/rand" + "testing" + + testutils "github.com/skip-mev/pob/testutils" + "github.com/stretchr/testify/suite" +) + +type BaseTestSuite struct { + suite.Suite + + encodingConfig testutils.EncodingConfig + random *rand.Rand + accounts []testutils.Account + gasTokenDenom string +} + +func TestBaseTestSuite(t *testing.T) { + suite.Run(t, new(BaseTestSuite)) +} + +func (s *BaseTestSuite) SetupTest() { + // Set up basic TX encoding config. + s.encodingConfig = testutils.CreateTestEncodingConfig() + + // Create a few random accounts + s.random = rand.New(rand.NewSource(1)) + s.accounts = testutils.RandomAccounts(s.random, 5) + s.gasTokenDenom = "stake" +} diff --git a/blockbuster/lanes/base/lane.go b/blockbuster/lanes/base/lane.go index a1c4b9b..05b1640 100644 --- a/blockbuster/lanes/base/lane.go +++ b/blockbuster/lanes/base/lane.go @@ -1,9 +1,6 @@ package base import ( - "cosmossdk.io/log" - "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/skip-mev/pob/blockbuster" ) @@ -15,83 +12,29 @@ const ( var _ blockbuster.Lane = (*DefaultLane)(nil) // DefaultLane defines a default lane implementation. The default lane orders -// transactions by the sdk.Context priority. The default lane will accept any -// transaction that is not a part of the lane's IgnoreList. By default, the IgnoreList -// is empty and the default lane will accept any transaction. The default lane on its -// own implements the same functionality as the pre v0.47.0 tendermint mempool and proposal -// handlers. +// transactions by the transaction fees. The default lane accepts any transaction +// that is should not be ignored (as defined by the IgnoreList in the LaneConfig). +// The default lane builds and verifies blocks in a similiar fashion to how the +// CometBFT/Tendermint consensus engine builds and verifies blocks pre SDK version +// 0.47.0. type DefaultLane struct { - // Mempool defines the mempool for the lane. - Mempool - - // LaneConfig defines the base lane configuration. - Cfg blockbuster.BaseLaneConfig - - // Name defines the name of the lane. - laneName string + *blockbuster.LaneConstructor[string] } // NewDefaultLane returns a new default lane. -func NewDefaultLane(cfg blockbuster.BaseLaneConfig) *DefaultLane { - if err := cfg.ValidateBasic(); err != nil { - panic(err) +func NewDefaultLane(cfg blockbuster.LaneConfig) *DefaultLane { + lane := blockbuster.NewLaneConstructor[string]( + cfg, + LaneName, + blockbuster.NewConstructorMempool[string]( + blockbuster.DefaultTxPriority(), + cfg.TxEncoder, + cfg.MaxTxs, + ), + blockbuster.DefaultMatchHandler(), + ) + + return &DefaultLane{ + LaneConstructor: lane, } - - lane := &DefaultLane{ - Mempool: NewDefaultMempool(cfg.TxEncoder), - Cfg: cfg, - laneName: LaneName, - } - - return lane -} - -// WithName returns a lane option that sets the lane's name. -func (l *DefaultLane) WithName(name string) *DefaultLane { - l.laneName = name - return l -} - -// Match returns true if the transaction belongs to this lane. Since -// this is the default lane, it always returns true except for transactions -// that belong to lanes in the ignore list. -func (l *DefaultLane) Match(ctx sdk.Context, tx sdk.Tx) bool { - return !l.MatchIgnoreList(ctx, tx) -} - -// MatchIgnoreList returns true if any of the lanes that are in the ignore list -// match the current transaction. -func (l *DefaultLane) MatchIgnoreList(ctx sdk.Context, tx sdk.Tx) bool { - for _, lane := range l.Cfg.IgnoreList { - if lane.Match(ctx, tx) { - return true - } - } - - return false -} - -// Name returns the name of the lane. -func (l *DefaultLane) Name() string { - return l.laneName -} - -// Logger returns the lane's logger. -func (l *DefaultLane) Logger() log.Logger { - return l.Cfg.Logger -} - -// SetAnteHandler sets the lane's antehandler. -func (l *DefaultLane) SetAnteHandler(anteHandler sdk.AnteHandler) { - l.Cfg.AnteHandler = anteHandler -} - -// GetMaxBlockSpace returns the maximum block space for the lane as a relative percentage. -func (l *DefaultLane) GetMaxBlockSpace() math.LegacyDec { - return l.Cfg.MaxBlockSpace -} - -// GetIgnoreList returns the lane's ignore list. -func (l *DefaultLane) GetIgnoreList() []blockbuster.Lane { - return l.Cfg.IgnoreList } diff --git a/blockbuster/lanes/base/mempool.go b/blockbuster/lanes/base/mempool.go deleted file mode 100644 index 54b2388..0000000 --- a/blockbuster/lanes/base/mempool.go +++ /dev/null @@ -1,105 +0,0 @@ -package base - -import ( - "context" - "errors" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" - "github.com/skip-mev/pob/blockbuster" - "github.com/skip-mev/pob/blockbuster/utils" -) - -var _ sdkmempool.Mempool = (*DefaultMempool)(nil) - -type ( - // Mempool defines the interface of the default mempool. - Mempool interface { - sdkmempool.Mempool - - // Contains returns true if the transaction is contained in the mempool. - Contains(tx sdk.Tx) bool - } - - // DefaultMempool defines the most basic mempool. It can be seen as an extension of - // an SDK PriorityNonceMempool, i.e. a mempool that supports - // two-dimensional priority ordering, with the additional support of prioritizing - // and indexing auction bids. - DefaultMempool struct { - // index defines an index transactions. - index sdkmempool.Mempool - - // txEncoder defines the sdk.Tx encoder that allows us to encode transactions - // to bytes. - txEncoder sdk.TxEncoder - - // txIndex is a map of all transactions in the mempool. It is used - // to quickly check if a transaction is already in the mempool. - txIndex map[string]struct{} - } -) - -// NewDefaultMempool returns a new default mempool instance. The default mempool -// orders transactions by the sdk.Context priority. -func NewDefaultMempool(txEncoder sdk.TxEncoder) *DefaultMempool { - return &DefaultMempool{ - index: blockbuster.NewPriorityMempool( - blockbuster.DefaultPriorityNonceMempoolConfig(), - ), - txEncoder: txEncoder, - txIndex: make(map[string]struct{}), - } -} - -// Insert inserts a transaction into the mempool based on the transaction type (normal or auction). -func (am *DefaultMempool) Insert(ctx context.Context, tx sdk.Tx) error { - if err := am.index.Insert(ctx, tx); err != nil { - return fmt.Errorf("failed to insert tx into auction index: %w", err) - } - - _, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx) - if err != nil { - am.Remove(tx) - return err - } - - am.txIndex[txHashStr] = struct{}{} - - return nil -} - -// Remove removes a transaction from the mempool based on the transaction type (normal or auction). -func (am *DefaultMempool) Remove(tx sdk.Tx) error { - if err := am.index.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) { - return fmt.Errorf("failed to remove transaction from the mempool: %w", err) - } - - _, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx) - if err != nil { - return fmt.Errorf("failed to get tx hash string: %w", err) - } - - delete(am.txIndex, txHashStr) - - return nil -} - -func (am *DefaultMempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator { - return am.index.Select(ctx, txs) -} - -func (am *DefaultMempool) CountTx() int { - return am.index.CountTx() -} - -// Contains returns true if the transaction is contained in the mempool. -func (am *DefaultMempool) Contains(tx sdk.Tx) bool { - _, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx) - if err != nil { - return false - } - - _, ok := am.txIndex[txHashStr] - return ok -} diff --git a/blockbuster/lanes/base/mempool_test.go b/blockbuster/lanes/base/mempool_test.go new file mode 100644 index 0000000..4f12e73 --- /dev/null +++ b/blockbuster/lanes/base/mempool_test.go @@ -0,0 +1,240 @@ +package base_test + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/blockbuster" + testutils "github.com/skip-mev/pob/testutils" +) + +func (s *BaseTestSuite) TestGetTxPriority() { + txPriority := blockbuster.DefaultTxPriority() + + s.Run("should be able to get the priority off a normal transaction with fees", func() { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + priority := txPriority.GetTxPriority(sdk.Context{}, tx) + s.Require().Equal(sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)).String(), priority) + }) + + s.Run("should not get a priority when the transaction does not have a fee", func() { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + ) + s.Require().NoError(err) + + priority := txPriority.GetTxPriority(sdk.Context{}, tx) + s.Require().Equal("", priority) + }) + + s.Run("should get a priority when the gas token is different", func() { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin("random", math.NewInt(100)), + ) + s.Require().NoError(err) + + priority := txPriority.GetTxPriority(sdk.Context{}, tx) + s.Require().Equal(sdk.NewCoin("random", math.NewInt(100)).String(), priority) + }) +} + +func (s *BaseTestSuite) TestCompareTxPriority() { + txPriority := blockbuster.DefaultTxPriority() + + s.Run("should return 0 when both priorities are nil", func() { + a := sdk.NewCoin(s.gasTokenDenom, math.NewInt(0)).String() + b := sdk.NewCoin(s.gasTokenDenom, math.NewInt(0)).String() + s.Require().Equal(0, txPriority.Compare(a, b)) + }) + + s.Run("should return 1 when the first priority is greater", func() { + a := sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)).String() + b := sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)).String() + s.Require().Equal(1, txPriority.Compare(a, b)) + }) + + s.Run("should return -1 when the second priority is greater", func() { + a := sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)).String() + b := sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)).String() + s.Require().Equal(-1, txPriority.Compare(a, b)) + }) + + s.Run("should return 0 when both priorities are equal", func() { + a := sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)).String() + b := sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)).String() + s.Require().Equal(0, txPriority.Compare(a, b)) + }) +} + +func (s *BaseTestSuite) TestInsert() { + mempool := blockbuster.NewConstructorMempool[string](blockbuster.DefaultTxPriority(), s.encodingConfig.TxConfig.TxEncoder(), 3) + + s.Run("should be able to insert a transaction", func() { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + err = mempool.Insert(sdk.Context{}, tx) + s.Require().NoError(err) + s.Require().True(mempool.Contains(tx)) + }) + + s.Run("cannot insert more transactions than the max", func() { + for i := 0; i < 3; i++ { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + uint64(i), + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(int64(100*i))), + ) + s.Require().NoError(err) + + err = mempool.Insert(sdk.Context{}, tx) + s.Require().NoError(err) + s.Require().True(mempool.Contains(tx)) + } + + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 10, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + err = mempool.Insert(sdk.Context{}, tx) + s.Require().Error(err) + s.Require().False(mempool.Contains(tx)) + }) +} + +func (s *BaseTestSuite) TestRemove() { + mempool := blockbuster.NewConstructorMempool[string](blockbuster.DefaultTxPriority(), s.encodingConfig.TxConfig.TxEncoder(), 3) + + s.Run("should be able to remove a transaction", func() { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + err = mempool.Insert(sdk.Context{}, tx) + s.Require().NoError(err) + s.Require().True(mempool.Contains(tx)) + + mempool.Remove(tx) + s.Require().False(mempool.Contains(tx)) + }) + + s.Run("should not error when removing a transaction that does not exist", func() { + tx, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + mempool.Remove(tx) + }) +} + +func (s *BaseTestSuite) TestSelect() { + s.Run("should be able to select transactions in the correct order", func() { + mempool := blockbuster.NewConstructorMempool[string](blockbuster.DefaultTxPriority(), s.encodingConfig.TxConfig.TxEncoder(), 3) + + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(200)), + ) + s.Require().NoError(err) + + // Insert the transactions into the mempool + s.Require().NoError(mempool.Insert(sdk.Context{}, tx1)) + s.Require().NoError(mempool.Insert(sdk.Context{}, tx2)) + s.Require().Equal(2, mempool.CountTx()) + + // Check that the transactions are in the correct order + iterator := mempool.Select(sdk.Context{}, nil) + s.Require().NotNil(iterator) + s.Require().Equal(tx2, iterator.Tx()) + + // Check the second transaction + iterator = iterator.Next() + s.Require().NotNil(iterator) + s.Require().Equal(tx1, iterator.Tx()) + }) + + s.Run("should be able to select a single transaction", func() { + mempool := blockbuster.NewConstructorMempool[string](blockbuster.DefaultTxPriority(), s.encodingConfig.TxConfig.TxEncoder(), 3) + + tx1, err := testutils.CreateRandomTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 0, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)), + ) + s.Require().NoError(err) + + // Insert the transactions into the mempool + s.Require().NoError(mempool.Insert(sdk.Context{}, tx1)) + s.Require().Equal(1, mempool.CountTx()) + + // Check that the transactions are in the correct order + iterator := mempool.Select(sdk.Context{}, nil) + s.Require().NotNil(iterator) + s.Require().Equal(tx1, iterator.Tx()) + + iterator = iterator.Next() + s.Require().Nil(iterator) + }) +} diff --git a/blockbuster/lanes/free/factory.go b/blockbuster/lanes/free/factory.go deleted file mode 100644 index c9bd0fa..0000000 --- a/blockbuster/lanes/free/factory.go +++ /dev/null @@ -1,47 +0,0 @@ -package free - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -type ( - // Factory defines the interface for processing free transactions. It is - // a wrapper around all of the functionality that each application chain must implement - // in order for free processing to work. - Factory interface { - // IsFreeTx defines a function that checks if a transaction qualifies as free. - IsFreeTx(tx sdk.Tx) bool - } - - // DefaultFreeFactory defines a default implmentation for the free factory interface for processing free transactions. - DefaultFreeFactory struct { - txDecoder sdk.TxDecoder - } -) - -var _ Factory = (*DefaultFreeFactory)(nil) - -// NewDefaultFreeFactory returns a default free factory interface implementation. -func NewDefaultFreeFactory(txDecoder sdk.TxDecoder) Factory { - return &DefaultFreeFactory{ - txDecoder: txDecoder, - } -} - -// IsFreeTx defines a default function that checks if a transaction is free. In this case, -// any transaction that is a delegation/redelegation transaction is free. -func (config *DefaultFreeFactory) IsFreeTx(tx sdk.Tx) bool { - for _, msg := range tx.GetMsgs() { - switch msg.(type) { - case *types.MsgDelegate: - return true - case *types.MsgBeginRedelegate: - return true - case *types.MsgCancelUnbondingDelegation: - return true - } - } - - return false -} diff --git a/blockbuster/lanes/free/lane.go b/blockbuster/lanes/free/lane.go index 6413d1e..312c0d7 100644 --- a/blockbuster/lanes/free/lane.go +++ b/blockbuster/lanes/free/lane.go @@ -2,8 +2,8 @@ package free import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/skip-mev/pob/blockbuster" - "github.com/skip-mev/pob/blockbuster/lanes/base" ) const ( @@ -11,31 +11,52 @@ const ( LaneName = "free" ) -var _ blockbuster.Lane = (*Lane)(nil) +var _ blockbuster.Lane = (*FreeLane)(nil) // FreeLane defines the lane that is responsible for processing free transactions. -type Lane struct { - *base.DefaultLane - Factory +// By default, transactions that are staking related are considered free. +type FreeLane struct { + *blockbuster.LaneConstructor[string] } // NewFreeLane returns a new free lane. -func NewFreeLane(cfg blockbuster.BaseLaneConfig, factory Factory) *Lane { - if err := cfg.ValidateBasic(); err != nil { - panic(err) - } +func NewFreeLane( + cfg blockbuster.LaneConfig, + txPriority blockbuster.TxPriority[string], + matchFn blockbuster.MatchHandler, +) *FreeLane { + lane := blockbuster.NewLaneConstructor[string]( + cfg, + LaneName, + blockbuster.NewConstructorMempool[string]( + txPriority, + cfg.TxEncoder, + cfg.MaxTxs, + ), + matchFn, + ) - return &Lane{ - DefaultLane: base.NewDefaultLane(cfg).WithName(LaneName), - Factory: factory, + return &FreeLane{ + LaneConstructor: lane, } } -// Match returns true if the transaction is a free transaction. -func (l *Lane) Match(ctx sdk.Context, tx sdk.Tx) bool { - if l.MatchIgnoreList(ctx, tx) { +// DefaultMatchHandler returns the default match handler for the free lane. The +// default implementation matches transactions that are staking related. In particular, +// any transaction that is a MsgDelegate, MsgBeginRedelegate, or MsgCancelUnbondingDelegation. +func DefaultMatchHandler() blockbuster.MatchHandler { + return func(ctx sdk.Context, tx sdk.Tx) bool { + for _, msg := range tx.GetMsgs() { + switch msg.(type) { + case *types.MsgDelegate: + return true + case *types.MsgBeginRedelegate: + return true + case *types.MsgCancelUnbondingDelegation: + return true + } + } + return false } - - return l.IsFreeTx(tx) } diff --git a/blockbuster/utils/mocks/lane.go b/blockbuster/utils/mocks/lane.go new file mode 100644 index 0000000..16faa82 --- /dev/null +++ b/blockbuster/utils/mocks/lane.go @@ -0,0 +1,256 @@ +// Code generated by mockery v2.30.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + blockbuster "github.com/skip-mev/pob/blockbuster" + + log "cosmossdk.io/log" + + math "cosmossdk.io/math" + + mempool "github.com/cosmos/cosmos-sdk/types/mempool" + + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// Lane is an autogenerated mock type for the Lane type +type Lane struct { + mock.Mock +} + +// CheckOrder provides a mock function with given fields: ctx, txs +func (_m *Lane) CheckOrder(ctx types.Context, txs []types.Tx) error { + ret := _m.Called(ctx, txs) + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, []types.Tx) error); ok { + r0 = rf(ctx, txs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Compare provides a mock function with given fields: ctx, this, other +func (_m *Lane) Compare(ctx types.Context, this types.Tx, other types.Tx) int { + ret := _m.Called(ctx, this, other) + + var r0 int + if rf, ok := ret.Get(0).(func(types.Context, types.Tx, types.Tx) int); ok { + r0 = rf(ctx, this, other) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// Contains provides a mock function with given fields: tx +func (_m *Lane) Contains(tx types.Tx) bool { + ret := _m.Called(tx) + + var r0 bool + if rf, ok := ret.Get(0).(func(types.Tx) bool); ok { + r0 = rf(tx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// CountTx provides a mock function with given fields: +func (_m *Lane) CountTx() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// GetMaxBlockSpace provides a mock function with given fields: +func (_m *Lane) GetMaxBlockSpace() math.LegacyDec { + ret := _m.Called() + + var r0 math.LegacyDec + if rf, ok := ret.Get(0).(func() math.LegacyDec); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(math.LegacyDec) + } + + return r0 +} + +// Insert provides a mock function with given fields: _a0, _a1 +func (_m *Lane) Insert(_a0 context.Context, _a1 types.Tx) error { + ret := _m.Called(_a0, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Logger provides a mock function with given fields: +func (_m *Lane) Logger() log.Logger { + ret := _m.Called() + + var r0 log.Logger + if rf, ok := ret.Get(0).(func() log.Logger); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(log.Logger) + } + } + + return r0 +} + +// Match provides a mock function with given fields: ctx, tx +func (_m *Lane) Match(ctx types.Context, tx types.Tx) bool { + ret := _m.Called(ctx, tx) + + var r0 bool + if rf, ok := ret.Get(0).(func(types.Context, types.Tx) bool); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Name provides a mock function with given fields: +func (_m *Lane) Name() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// PrepareLane provides a mock function with given fields: ctx, proposal, maxTxBytes, next +func (_m *Lane) PrepareLane(ctx types.Context, proposal blockbuster.BlockProposal, maxTxBytes int64, next blockbuster.PrepareLanesHandler) (blockbuster.BlockProposal, error) { + ret := _m.Called(ctx, proposal, maxTxBytes, next) + + var r0 blockbuster.BlockProposal + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, blockbuster.BlockProposal, int64, blockbuster.PrepareLanesHandler) (blockbuster.BlockProposal, error)); ok { + return rf(ctx, proposal, maxTxBytes, next) + } + if rf, ok := ret.Get(0).(func(types.Context, blockbuster.BlockProposal, int64, blockbuster.PrepareLanesHandler) blockbuster.BlockProposal); ok { + r0 = rf(ctx, proposal, maxTxBytes, next) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(blockbuster.BlockProposal) + } + } + + if rf, ok := ret.Get(1).(func(types.Context, blockbuster.BlockProposal, int64, blockbuster.PrepareLanesHandler) error); ok { + r1 = rf(ctx, proposal, maxTxBytes, next) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProcessLane provides a mock function with given fields: ctx, proposalTxs, next +func (_m *Lane) ProcessLane(ctx types.Context, proposalTxs []types.Tx, next blockbuster.ProcessLanesHandler) (types.Context, error) { + ret := _m.Called(ctx, proposalTxs, next) + + var r0 types.Context + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, []types.Tx, blockbuster.ProcessLanesHandler) (types.Context, error)); ok { + return rf(ctx, proposalTxs, next) + } + if rf, ok := ret.Get(0).(func(types.Context, []types.Tx, blockbuster.ProcessLanesHandler) types.Context); ok { + r0 = rf(ctx, proposalTxs, next) + } else { + r0 = ret.Get(0).(types.Context) + } + + if rf, ok := ret.Get(1).(func(types.Context, []types.Tx, blockbuster.ProcessLanesHandler) error); ok { + r1 = rf(ctx, proposalTxs, next) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Remove provides a mock function with given fields: _a0 +func (_m *Lane) Remove(_a0 types.Tx) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(types.Tx) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Select provides a mock function with given fields: _a0, _a1 +func (_m *Lane) Select(_a0 context.Context, _a1 [][]byte) mempool.Iterator { + ret := _m.Called(_a0, _a1) + + var r0 mempool.Iterator + if rf, ok := ret.Get(0).(func(context.Context, [][]byte) mempool.Iterator); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(mempool.Iterator) + } + } + + return r0 +} + +// SetAnteHandler provides a mock function with given fields: antehander +func (_m *Lane) SetAnteHandler(antehander types.AnteHandler) { + _m.Called(antehander) +} + +// SetIgnoreList provides a mock function with given fields: ignoreList +func (_m *Lane) SetIgnoreList(ignoreList []blockbuster.Lane) { + _m.Called(ignoreList) +} + +// NewLane creates a new instance of Lane. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLane(t interface { + mock.TestingT + Cleanup(func()) +}) *Lane { + mock := &Lane{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/blockbuster/utils/mocks/lane_mempool.go b/blockbuster/utils/mocks/lane_mempool.go new file mode 100644 index 0000000..d4116e0 --- /dev/null +++ b/blockbuster/utils/mocks/lane_mempool.go @@ -0,0 +1,117 @@ +// Code generated by mockery v2.30.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mempool "github.com/cosmos/cosmos-sdk/types/mempool" + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// LaneMempool is an autogenerated mock type for the LaneMempool type +type LaneMempool struct { + mock.Mock +} + +// Compare provides a mock function with given fields: ctx, this, other +func (_m *LaneMempool) Compare(ctx types.Context, this types.Tx, other types.Tx) int { + ret := _m.Called(ctx, this, other) + + var r0 int + if rf, ok := ret.Get(0).(func(types.Context, types.Tx, types.Tx) int); ok { + r0 = rf(ctx, this, other) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// Contains provides a mock function with given fields: tx +func (_m *LaneMempool) Contains(tx types.Tx) bool { + ret := _m.Called(tx) + + var r0 bool + if rf, ok := ret.Get(0).(func(types.Tx) bool); ok { + r0 = rf(tx) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// CountTx provides a mock function with given fields: +func (_m *LaneMempool) CountTx() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// Insert provides a mock function with given fields: _a0, _a1 +func (_m *LaneMempool) Insert(_a0 context.Context, _a1 types.Tx) error { + ret := _m.Called(_a0, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.Tx) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Remove provides a mock function with given fields: _a0 +func (_m *LaneMempool) Remove(_a0 types.Tx) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(types.Tx) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Select provides a mock function with given fields: _a0, _a1 +func (_m *LaneMempool) Select(_a0 context.Context, _a1 [][]byte) mempool.Iterator { + ret := _m.Called(_a0, _a1) + + var r0 mempool.Iterator + if rf, ok := ret.Get(0).(func(context.Context, [][]byte) mempool.Iterator); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(mempool.Iterator) + } + } + + return r0 +} + +// NewLaneMempool creates a new instance of LaneMempool. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLaneMempool(t interface { + mock.TestingT + Cleanup(func()) +}) *LaneMempool { + mock := &LaneMempool{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutils/utils.go b/testutils/utils.go index 2a77768..9dd0c5b 100644 --- a/testutils/utils.go +++ b/testutils/utils.go @@ -84,7 +84,7 @@ func RandomAccounts(r *rand.Rand, n int) []Account { return accs } -func CreateTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, msgs []sdk.Msg) (authsigning.Tx, error) { +func CreateTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, msgs []sdk.Msg, fees ...sdk.Coin) (authsigning.Tx, error) { txBuilder := txCfg.NewTxBuilder() if err := txBuilder.SetMsgs(msgs...); err != nil { return nil, err @@ -104,10 +104,12 @@ func CreateTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, msg txBuilder.SetTimeoutHeight(timeout) + txBuilder.SetFeeAmount(fees) + return txBuilder.GetTx(), nil } -func CreateFreeTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, validator string, amount sdk.Coin) (authsigning.Tx, error) { +func CreateFreeTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, validator string, amount sdk.Coin, fees ...sdk.Coin) (authsigning.Tx, error) { msgs := []sdk.Msg{ &stakingtypes.MsgDelegate{ DelegatorAddress: account.Address.String(), @@ -116,10 +118,10 @@ func CreateFreeTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, }, } - return CreateTx(txCfg, account, nonce, timeout, msgs) + return CreateTx(txCfg, account, nonce, timeout, msgs, fees...) } -func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout uint64) (authsigning.Tx, error) { +func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout uint64, fees ...sdk.Coin) (authsigning.Tx, error) { msgs := make([]sdk.Msg, numberMsgs) for i := 0; i < int(numberMsgs); i++ { msgs[i] = &banktypes.MsgSend{ @@ -147,6 +149,8 @@ func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, t txBuilder.SetTimeoutHeight(timeout) + txBuilder.SetFeeAmount(fees) + return txBuilder.GetTx(), nil } @@ -189,6 +193,53 @@ func CreateTxWithSigners(txCfg client.TxConfig, nonce, timeout uint64, signers [ return txBuilder.GetTx(), nil } +func CreateAuctionTx(txCfg client.TxConfig, bidder Account, bid sdk.Coin, nonce, timeout uint64, signers []Account) (authsigning.Tx, []authsigning.Tx, error) { + bidMsg := &buildertypes.MsgAuctionBid{ + Bidder: bidder.Address.String(), + Bid: bid, + Transactions: make([][]byte, len(signers)), + } + + txs := []authsigning.Tx{} + + for i := 0; i < len(signers); i++ { + randomMsg := CreateRandomMsgs(signers[i].Address, 1) + randomTx, err := CreateTx(txCfg, signers[i], 0, timeout, randomMsg) + if err != nil { + return nil, nil, err + } + + bz, err := txCfg.TxEncoder()(randomTx) + if err != nil { + return nil, nil, err + } + + bidMsg.Transactions[i] = bz + txs = append(txs, randomTx) + } + + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(bidMsg); err != nil { + return nil, nil, err + } + + sigV2 := signing.SignatureV2{ + PubKey: bidder.PrivKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, nil, err + } + + txBuilder.SetTimeoutHeight(timeout) + + return txBuilder.GetTx(), txs, nil +} + func CreateAuctionTxWithSigners(txCfg client.TxConfig, bidder Account, bid sdk.Coin, nonce, timeout uint64, signers []Account) (authsigning.Tx, error) { bidMsg := &buildertypes.MsgAuctionBid{ Bidder: bidder.Address.String(),