From 68588d93fb42ba7cadd7f70be2a6dafa0236253e Mon Sep 17 00:00:00 2001 From: David Terpay <35130517+davidterpay@users.noreply.github.com> Date: Wed, 15 Mar 2023 14:26:32 -0400 Subject: [PATCH] [ENG-539]: Mempool Testing (#25) Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Aleksandr Bezobchuk --- mempool/mempool_test.go | 292 +++++++++++++++++++------------------- mempool/priority_nonce.go | 4 +- mempool/utils_test.go | 80 +++++++++++ 3 files changed, 229 insertions(+), 147 deletions(-) diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 4241288..8d7e68b 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -7,169 +7,171 @@ import ( "github.com/cometbft/cometbft/libs/log" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/skip-mev/pob/mempool" auctiontypes "github.com/skip-mev/pob/x/auction/types" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) -func TestAuctionMempool(t *testing.T) { - encCfg := createTestEncodingConfig() - amp := mempool.NewAuctionMempool(encCfg.TxConfig.TxDecoder(), 0) - ctx := sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) - rng := rand.New(rand.NewSource(time.Now().Unix())) - accounts := RandomAccounts(rng, 5) +type IntegrationTestSuite struct { + suite.Suite - accNonces := map[string]uint64{} - for _, acc := range accounts { - accNonces[acc.Address.String()] = 0 + encCfg encodingConfig + mempool *mempool.AuctionMempool + ctx sdk.Context + random *rand.Rand + accounts []Account + nonces map[string]uint64 +} + +func TestMempoolTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +func (suite *IntegrationTestSuite) SetupTest() { + // Mempool setup + suite.encCfg = createTestEncodingConfig() + suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), 0) + suite.ctx = sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) + + // Init accounts + suite.random = rand.New(rand.NewSource(time.Now().Unix())) + suite.accounts = RandomAccounts(suite.random, 5) + + suite.nonces = make(map[string]uint64) + for _, acc := range suite.accounts { + suite.nonces[acc.Address.String()] = 0 + } +} + +// CreateFilledMempool creates a pre-filled mempool with numNormalTxs normal transactions, numAuctionTxs auction transactions, and numBundledTxs bundled +// transactions per auction transaction. If insertRefTxs is true, it will also insert a the referenced transactions into the mempool. This returns +// the total number of transactions inserted into the mempool. +func (suite *IntegrationTestSuite) CreateFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs int, insertRefTxs bool) int { + // Insert a bunch of normal transactions into the global mempool + for i := 0; i < numNormalTxs; i++ { + // create a few random msgs + randomMsgs := createRandomMsgs(3) + + // randomly select an account to create the tx + randomIndex := suite.random.Intn(len(suite.accounts)) + acc := suite.accounts[randomIndex] + nonce := suite.nonces[acc.Address.String()] + randomTx, err := createTx(suite.encCfg.TxConfig, acc, nonce, randomMsgs) + suite.Require().NoError(err) + + suite.nonces[acc.Address.String()]++ + priority := suite.random.Int63n(100) + 1 + suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), randomTx)) } - // insert a bunch of normal txs - for i := 0; i < 1000; i++ { - p := rng.Int63n(500-1) + 1 - j := rng.Intn(len(accounts)) - acc := accounts[j] - txBuilder := encCfg.TxConfig.NewTxBuilder() + suite.Require().Equal(numNormalTxs, suite.mempool.CountTx()) + suite.Require().Equal(0, suite.mempool.CountAuctionTx()) - msgs := []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: acc.Address.String(), - ToAddress: acc.Address.String(), - }, + // Insert a bunch of auction transactions into the global mempool and auction mempool + for i := 0; i < numAuctionTxs; i++ { + // randomly select a bidder to create the tx + acc := RandomAccounts(suite.random, 1)[0] + + // create a new auction bid msg with numBundledTxs bundled transactions + priority := suite.random.Int63n(100) + 1 + bid := sdk.NewCoins(sdk.NewInt64Coin("foo", priority)) + nonce := suite.nonces[acc.Address.String()] + bidMsg, err := createMsgAuctionBid(suite.encCfg.TxConfig, acc, bid, nonce, numBundledTxs) + suite.nonces[acc.Address.String()] += uint64(numBundledTxs) + suite.Require().NoError(err) + + // create the auction tx + nonce = suite.nonces[acc.Address.String()] + auctionTx, err := createTx(suite.encCfg.TxConfig, acc, nonce, []sdk.Msg{bidMsg}) + suite.Require().NoError(err) + + // insert the auction tx into the global mempool + suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), auctionTx)) + suite.nonces[acc.Address.String()]++ + + if insertRefTxs { + for _, refRawTx := range bidMsg.GetTransactions() { + refTx, err := suite.encCfg.TxConfig.TxDecoder()(refRawTx) + suite.Require().NoError(err) + suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), refTx)) + } } - err := txBuilder.SetMsgs(msgs...) - require.NoError(t, err) - - sigV2 := signing.SignatureV2{ - PubKey: acc.PrivKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: accNonces[acc.Address.String()], - } - err = txBuilder.SetSignatures(sigV2) - require.NoError(t, err) - - accNonces[acc.Address.String()]++ - - require.NoError(t, amp.Insert(ctx.WithPriority(p), txBuilder.GetTx())) } - require.Nil(t, amp.AuctionBidSelect(ctx)) + var totalNumTxs int + suite.Require().Equal(numAuctionTxs, suite.mempool.CountAuctionTx()) + if insertRefTxs { + totalNumTxs = numNormalTxs + numAuctionTxs*(numBundledTxs+1) + suite.Require().Equal(totalNumTxs, suite.mempool.CountTx()) + } else { + totalNumTxs = numNormalTxs + numAuctionTxs + suite.Require().Equal(totalNumTxs, suite.mempool.CountTx()) + } - // insert bid transactions + return totalNumTxs +} + +func (suite *IntegrationTestSuite) TestAuctionMempoolRemove() { + numberTotalTxs := 100 + numberAuctionTxs := 10 + numberBundledTxs := 5 + insertRefTxs := true + numMempoolTxs := suite.CreateFilledMempool(numberTotalTxs, numberAuctionTxs, numberBundledTxs, insertRefTxs) + + // Select the top bid tx from the auction mempool and do sanity checks + auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx) + suite.Require().NotNil(auctionIterator) + tx := auctionIterator.Tx() + suite.Require().Len(tx.GetMsgs(), 1) + suite.Require().NoError(suite.mempool.RemoveWithoutRefTx(tx)) + + // Ensure that the auction tx was removed from the auction and global mempool + suite.Require().Equal(numberAuctionTxs-1, suite.mempool.CountAuctionTx()) + suite.Require().Equal(numMempoolTxs-1, suite.mempool.CountTx()) + + // Attempt to remove again and ensure that the tx is not found + suite.Require().NoError(suite.mempool.RemoveWithoutRefTx(tx)) + suite.Require().Equal(numberAuctionTxs-1, suite.mempool.CountAuctionTx()) + suite.Require().Equal(numMempoolTxs-1, suite.mempool.CountTx()) + + // Attempt to remove with the bundled txs + suite.Require().NoError(suite.mempool.Remove(tx)) + suite.Require().Equal(numberAuctionTxs-1, suite.mempool.CountAuctionTx()) + suite.Require().Equal(numMempoolTxs-numberBundledTxs-1, suite.mempool.CountTx()) +} + +func (suite *IntegrationTestSuite) TestAuctionMempoolSelect() { + numberTotalTxs := 100 + numberAuctionTxs := 10 + numberBundledTxs := 5 + insertRefTxs := true + suite.CreateFilledMempool(numberTotalTxs, numberAuctionTxs, numberBundledTxs, insertRefTxs) + + // iterate through the entire auction mempool and ensure the bids are in order var highestBid sdk.Coins - biddingAccs := RandomAccounts(rng, 100) + var prevBid sdk.Coins + auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx) + numberTxsSeen := 0 + for auctionIterator != nil { + tx := auctionIterator.Tx() + suite.Require().Len(tx.GetMsgs(), 1) - for _, acc := range biddingAccs { - p := rng.Int63n(500-1) + 1 - txBuilder := encCfg.TxConfig.NewTxBuilder() - - // keep track of highest bid - bid := sdk.NewCoins(sdk.NewInt64Coin("foo", p)) - if bid.IsAllGT(highestBid) { - highestBid = bid + msgAuctionBid := tx.GetMsgs()[0].(*auctiontypes.MsgAuctionBid) + if highestBid == nil { + highestBid = msgAuctionBid.Bid + prevBid = msgAuctionBid.Bid + } else { + suite.Require().True(msgAuctionBid.Bid.IsAllLTE(highestBid)) + suite.Require().True(msgAuctionBid.Bid.IsAllLTE(prevBid)) + prevBid = msgAuctionBid.Bid } - bidMsg, err := createMsgAuctionBid(encCfg.TxConfig, acc, bid) - require.NoError(t, err) + suite.Require().Len(msgAuctionBid.GetTransactions(), numberBundledTxs) - msgs := []sdk.Msg{bidMsg} - err = txBuilder.SetMsgs(msgs...) - require.NoError(t, err) - - sigV2 := signing.SignatureV2{ - PubKey: acc.PrivKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: 0, - } - err = txBuilder.SetSignatures(sigV2) - require.NoError(t, err) - - require.NoError(t, amp.Insert(ctx.WithPriority(p), txBuilder.GetTx())) - - // Insert the referenced txs just to ensure that they are removed from the - // mempool in cases where they exist. - for _, refRawTx := range bidMsg.GetTransactions() { - refTx, err := encCfg.TxConfig.TxDecoder()(refRawTx) - require.NoError(t, err) - require.NoError(t, amp.Insert(ctx.WithPriority(0), refTx)) - } + auctionIterator = auctionIterator.Next() + numberTxsSeen++ } - expectedCount := 1000 + 100 + 200 - require.Equal(t, expectedCount, amp.CountTx()) - - // select the top bid and misc txs - bidTx := amp.AuctionBidSelect(ctx).Tx() - require.Len(t, bidTx.GetMsgs(), 1) - require.Equal(t, highestBid, bidTx.GetMsgs()[0].(*auctiontypes.MsgAuctionBid).Bid) - - // remove the top bid tx (without removing the referenced txs) - prevAuctionCount := amp.CountAuctionTx() - require.NoError(t, amp.RemoveWithoutRefTx(bidTx)) - require.Equal(t, expectedCount-1, amp.CountTx()) - require.Equal(t, prevAuctionCount-1, amp.CountAuctionTx()) - - // the next bid tx should be less than or equal to the previous highest bid - nextBidTx := amp.AuctionBidSelect(ctx).Tx() - require.Len(t, nextBidTx.GetMsgs(), 1) - msgAuctionBid := nextBidTx.GetMsgs()[0].(*auctiontypes.MsgAuctionBid) - require.True(t, highestBid.IsAllGTE(msgAuctionBid.Bid)) - - // remove the top bid tx (including the ref txs) - prevGlobalCount := amp.CountTx() - require.NoError(t, amp.Remove(nextBidTx)) - require.Equal(t, prevGlobalCount-1-2, amp.CountTx()) -} - -func createMsgAuctionBid(txCfg client.TxConfig, bidder Account, bid sdk.Coins) (*auctiontypes.MsgAuctionBid, error) { - bidMsg := &auctiontypes.MsgAuctionBid{ - Bidder: bidder.Address.String(), - Bid: bid, - Transactions: make([][]byte, 2), - } - - for i := 0; i < 2; i++ { - txBuilder := txCfg.NewTxBuilder() - - msgs := []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: bidder.Address.String(), - ToAddress: bidder.Address.String(), - }, - } - if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err - } - - sigV2 := signing.SignatureV2{ - PubKey: bidder.PrivKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: txCfg.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: uint64(i + 1), - } - if err := txBuilder.SetSignatures(sigV2); err != nil { - return nil, err - } - - bz, err := txCfg.TxEncoder()(txBuilder.GetTx()) - if err != nil { - return nil, err - } - - bidMsg.Transactions[i] = bz - } - - return bidMsg, nil + suite.Require().Equal(numberAuctionTxs, numberTxsSeen) } diff --git a/mempool/priority_nonce.go b/mempool/priority_nonce.go index ccbf3cd..6784f03 100644 --- a/mempool/priority_nonce.go +++ b/mempool/priority_nonce.go @@ -451,7 +451,7 @@ func IsEmpty[C comparable](mempool sdkmempool.Mempool) error { return fmt.Errorf("priorityIndex not empty") } - var countKeys []C + countKeys := make([]C, 0, len(mp.priorityCounts)) for k := range mp.priorityCounts { countKeys = append(countKeys, k) } @@ -462,7 +462,7 @@ func IsEmpty[C comparable](mempool sdkmempool.Mempool) error { } } - var senderKeys []string + senderKeys := make([]string, 0, len(mp.senderIndices)) for k := range mp.senderIndices { senderKeys = append(senderKeys, k) } diff --git a/mempool/utils_test.go b/mempool/utils_test.go index cd2548d..e03ac3a 100644 --- a/mempool/utils_test.go +++ b/mempool/utils_test.go @@ -11,8 +11,11 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/auth/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + auctiontypes "github.com/skip-mev/pob/x/auction/types" ) type encodingConfig struct { @@ -66,3 +69,80 @@ func RandomAccounts(r *rand.Rand, n int) []Account { return accs } + +func createTx(txCfg client.TxConfig, account Account, nonce uint64, msgs []sdk.Msg) (authsigning.Tx, error) { + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := signing.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: txCfg.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + return txBuilder.GetTx(), nil +} + +func createRandomMsgs(numberMsgs int) []sdk.Msg { + msgs := make([]sdk.Msg, numberMsgs) + for i := 0; i < numberMsgs; i++ { + msgs[i] = &banktypes.MsgSend{ + FromAddress: sdk.AccAddress([]byte("addr1_______________")).String(), + ToAddress: sdk.AccAddress([]byte("addr2_______________")).String(), + } + } + + return msgs +} + +// createMsgAuctionBid creates a new MsgAuctionBid with numberMsgs of referenced transactions embedded into the message and the inputted bid/bidder. +func createMsgAuctionBid(txCfg client.TxConfig, bidder Account, bid sdk.Coins, nonce uint64, numberMsgs int) (*auctiontypes.MsgAuctionBid, error) { + bidMsg := &auctiontypes.MsgAuctionBid{ + Bidder: bidder.Address.String(), + Bid: bid, + Transactions: make([][]byte, numberMsgs), + } + + for i := 0; i < numberMsgs; i++ { + txBuilder := txCfg.NewTxBuilder() + + msgs := []sdk.Msg{ + &banktypes.MsgSend{ + FromAddress: bidder.Address.String(), + ToAddress: bidder.Address.String(), + }, + } + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := signing.SignatureV2{ + PubKey: bidder.PrivKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: txCfg.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: nonce + uint64(i), + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + bz, err := txCfg.TxEncoder()(txBuilder.GetTx()) + if err != nil { + return nil, err + } + + bidMsg.Transactions[i] = bz + } + + return bidMsg, nil +}