diff --git a/mempool/config.go b/mempool/config.go index cce200a..1932c12 100644 --- a/mempool/config.go +++ b/mempool/config.go @@ -4,6 +4,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" ) type ( @@ -94,11 +95,14 @@ func (config *DefaultConfig) GetTransactionSigners(tx []byte) (map[string]struct return nil, err } + sigTx, ok := sdkTx.(signing.SigVerifiableTx) + if !ok { + return nil, fmt.Errorf("transaction is not valid") + } + signers := make(map[string]struct{}) - for _, msg := range sdkTx.GetMsgs() { - for _, signer := range msg.GetSigners() { - signers[signer.String()] = struct{}{} - } + for _, signer := range sigTx.GetSigners() { + signers[signer.String()] = struct{}{} } return signers, nil diff --git a/mempool/config_test.go b/mempool/config_test.go new file mode 100644 index 0000000..d856e2a --- /dev/null +++ b/mempool/config_test.go @@ -0,0 +1,548 @@ +package mempool_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + testutils "github.com/skip-mev/pob/testutils" +) + +func (suite *IntegrationTestSuite) TestIsAuctionTx() { + testCases := []struct { + name string + createTx func() sdk.Tx + isAuctionTx bool + expectedError bool + }{ + { + "normal sdk tx", + func() sdk.Tx { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 2, 0) + suite.Require().NoError(err) + return tx + }, + false, + false, + }, + { + "malformed auction bid tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + msgs := testutils.CreateRandomMsgs(suite.accounts[0].Address, 2) + msgs = append(msgs, msgAuctionBid) + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + false, + true, + }, + { + "valid auction bid tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + true, + false, + }, + { + "tx with multiple MsgAuctionBid messages", + func() sdk.Tx { + bid1, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + bid2, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 1, 2) + suite.Require().NoError(err) + + msgs := []sdk.Msg{bid1, bid2} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + false, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx := tc.createTx() + + isAuctionTx, err := suite.config.IsAuctionTx(tx) + + suite.Require().Equal(tc.isAuctionTx, isAuctionTx) + if tc.expectedError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *IntegrationTestSuite) TestGetTransactionSigners() { + testCases := []struct { + name string + createTx func() []byte + expectedSigners []string + }{ + { + "normal sdk tx", + func() []byte { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + bz, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + return bz + }, + []string{suite.accounts[0].Address.String()}, + }, + { + "normal sdk tx with several messages", + func() []byte { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 10, 0) + suite.Require().NoError(err) + + bz, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + return bz + }, + []string{suite.accounts[0].Address.String()}, + }, + { + "multiple signers on tx", + func() []byte { + tx, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[0:3]) + suite.Require().NoError(err) + + bz, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + return bz + }, + []string{suite.accounts[0].Address.String(), suite.accounts[1].Address.String(), suite.accounts[2].Address.String()}, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx := tc.createTx() + + signers, err := suite.config.GetTransactionSigners(tx) + suite.Require().NoError(err) + suite.Require().Equal(len(tc.expectedSigners), len(signers)) + + for _, signer := range tc.expectedSigners { + suite.Require().Contains(signers, signer) + } + }) + } +} + +func (suite *IntegrationTestSuite) TestGetBundleSigners() { + testCases := []struct { + name string + createBundle func() [][]byte + expectedSigners [][]string + }{ + { + "single bundle with one signer", + func() [][]byte { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + bz, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + return [][]byte{bz} + }, + [][]string{{suite.accounts[0].Address.String()}}, + }, + { + "single bundle with multiple signers", + func() [][]byte { + tx, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[0:3]) + suite.Require().NoError(err) + + bz, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + return [][]byte{bz} + }, + [][]string{{suite.accounts[0].Address.String(), suite.accounts[1].Address.String(), suite.accounts[2].Address.String()}}, + }, + { + "multiple bundles with one signer", + func() [][]byte { + tx1, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + bz1, err := suite.encCfg.TxConfig.TxEncoder()(tx1) + suite.Require().NoError(err) + + tx2, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[1], 0, 1, 0) + suite.Require().NoError(err) + + bz2, err := suite.encCfg.TxConfig.TxEncoder()(tx2) + suite.Require().NoError(err) + + return [][]byte{bz1, bz2} + }, + [][]string{{suite.accounts[0].Address.String()}, {suite.accounts[1].Address.String()}}, + }, + { + "multiple bundles with multiple signers", + func() [][]byte { + tx1, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[0:3]) + suite.Require().NoError(err) + + bz1, err := suite.encCfg.TxConfig.TxEncoder()(tx1) + suite.Require().NoError(err) + + tx2, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[3:6]) + suite.Require().NoError(err) + + bz2, err := suite.encCfg.TxConfig.TxEncoder()(tx2) + suite.Require().NoError(err) + + return [][]byte{bz1, bz2} + }, + [][]string{{suite.accounts[0].Address.String(), suite.accounts[1].Address.String(), suite.accounts[2].Address.String()}, {suite.accounts[3].Address.String(), suite.accounts[4].Address.String(), suite.accounts[5].Address.String()}}, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + bundle := tc.createBundle() + + signers, err := suite.config.GetBundleSigners(bundle) + suite.Require().NoError(err) + suite.Require().Equal(len(tc.expectedSigners), len(signers)) + + for i, bundleSigners := range tc.expectedSigners { + suite.Require().Equal(len(bundleSigners), len(signers[i])) + + for _, signer := range bundleSigners { + suite.Require().Contains(signers[i], signer) + } + } + }) + } +} + +func (suite *IntegrationTestSuite) TestWrapBundleTransaction() { + testCases := []struct { + name string + createBundleTx func() (sdk.Tx, []byte) + }{ + { + "normal sdk tx", + func() (sdk.Tx, []byte) { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + bz, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + return tx, bz + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx, bz := tc.createBundleTx() + + wrappedTx, err := suite.config.WrapBundleTransaction(bz) + suite.Require().NoError(err) + + txBytes, err := suite.encCfg.TxConfig.TxEncoder()(tx) + suite.Require().NoError(err) + + wrappedTxBytes, err := suite.encCfg.TxConfig.TxEncoder()(wrappedTx) + suite.Require().NoError(err) + + suite.Require().Equal(txBytes, wrappedTxBytes) + }) + } +} + +func (suite *IntegrationTestSuite) TestGetBidder() { + testCases := []struct { + name string + createTx func() sdk.Tx + expectedBidder string + expectedError bool + }{ + { + "normal sdk tx", + func() sdk.Tx { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + return tx + }, + "", + true, + }, + { + "valid auction tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + suite.accounts[0].Address.String(), + false, + }, + { + "invalid auction tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + randomMsg := testutils.CreateRandomMsgs(suite.accounts[0].Address, 1)[0] + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid, randomMsg} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + "", + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx := tc.createTx() + + bidder, err := suite.config.GetBidder(tx) + if tc.expectedError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expectedBidder, bidder.String()) + } + }) + } +} + +func (suite *IntegrationTestSuite) TestGetBid() { + testCases := []struct { + name string + createTx func() sdk.Tx + expectedBid sdk.Coin + expectedError bool + }{ + { + "normal sdk tx", + func() sdk.Tx { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + return tx + }, + sdk.Coin{}, + true, + }, + { + "valid auction tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + sdk.NewInt64Coin("foo", 100), + false, + }, + { + "invalid auction tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + randomMsg := testutils.CreateRandomMsgs(suite.accounts[0].Address, 1)[0] + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid, randomMsg} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx + }, + sdk.Coin{}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx := tc.createTx() + + bid, err := suite.config.GetBid(tx) + if tc.expectedError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expectedBid, bid) + } + }) + } +} + +func (suite *IntegrationTestSuite) TestGetBundledTransactions() { + testCases := []struct { + name string + createTx func() (sdk.Tx, [][]byte) + expectedError bool + }{ + { + "normal sdk tx", + func() (sdk.Tx, [][]byte) { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0) + suite.Require().NoError(err) + + return tx, nil + }, + true, + }, + { + "valid auction tx", + func() (sdk.Tx, [][]byte) { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx, msgAuctionBid.Transactions + }, + false, + }, + { + "invalid auction tx", + func() (sdk.Tx, [][]byte) { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + randomMsg := testutils.CreateRandomMsgs(suite.accounts[0].Address, 1)[0] + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid, randomMsg} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 0, msgs) + suite.Require().NoError(err) + return tx, nil + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx, expectedBundledTxs := tc.createTx() + + bundledTxs, err := suite.config.GetBundledTransactions(tx) + if tc.expectedError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(expectedBundledTxs, bundledTxs) + } + }) + } +} + +func (suite *IntegrationTestSuite) TestGetTimeout() { + testCases := []struct { + name string + createTx func() sdk.Tx + expectedError bool + expectedTimeout uint64 + }{ + { + "normal sdk tx", + func() sdk.Tx { + tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 1) + suite.Require().NoError(err) + + return tx + }, + false, + 1, + }, + { + "valid auction tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 10, msgs) + suite.Require().NoError(err) + return tx + }, + false, + 10, + }, + { + "invalid auction tx", + func() sdk.Tx { + msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encCfg.TxConfig, suite.accounts[0], sdk.NewInt64Coin("foo", 100), 0, 2) + suite.Require().NoError(err) + + randomMsg := testutils.CreateRandomMsgs(suite.accounts[0].Address, 1)[0] + suite.Require().NoError(err) + + msgs := []sdk.Msg{msgAuctionBid, randomMsg} + + tx, err := testutils.CreateTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 10, msgs) + suite.Require().NoError(err) + return tx + }, + false, + 10, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tx := tc.createTx() + + timeout, err := suite.config.GetTimeout(tx) + if tc.expectedError { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expectedTimeout, timeout) + } + }) + } +} diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 33db26e..86ca9c9 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -19,6 +19,7 @@ type IntegrationTestSuite struct { suite.Suite encCfg testutils.EncodingConfig + config mempool.Config mempool *mempool.AuctionMempool ctx sdk.Context random *rand.Rand @@ -33,13 +34,13 @@ func TestMempoolTestSuite(t *testing.T) { func (suite *IntegrationTestSuite) SetupTest() { // Mempool setup suite.encCfg = testutils.CreateTestEncodingConfig() - config := mempool.NewDefaultConfig(suite.encCfg.TxConfig.TxDecoder()) - suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), suite.encCfg.TxConfig.TxEncoder(), 0, config) + suite.config = mempool.NewDefaultConfig(suite.encCfg.TxConfig.TxDecoder()) + suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), suite.encCfg.TxConfig.TxEncoder(), 0, suite.config) suite.ctx = sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger()) // Init accounts suite.random = rand.New(rand.NewSource(time.Now().Unix())) - suite.accounts = testutils.RandomAccounts(suite.random, 5) + suite.accounts = testutils.RandomAccounts(suite.random, 10) suite.nonces = make(map[string]uint64) for _, acc := range suite.accounts { diff --git a/testutils/utils.go b/testutils/utils.go index cea9413..446f8f4 100644 --- a/testutils/utils.go +++ b/testutils/utils.go @@ -125,6 +125,36 @@ func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, t return txBuilder.GetTx(), nil } +func CreateTxWithSigners(txCfg client.TxConfig, nonce, timeout uint64, signers []Account) (authsigning.Tx, error) { + msgs := []sdk.Msg{} + for _, signer := range signers { + msg := CreateRandomMsgs(signer.Address, 1) + msgs = append(msgs, msg...) + } + + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := signing.SignatureV2{ + PubKey: signers[0].PrivKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: txCfg.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: nonce, + } + + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + txBuilder.SetTimeoutHeight(timeout) + + return txBuilder.GetTx(), 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(),