From e61327abf3f9dd759a9825d82c09c3b25847555b Mon Sep 17 00:00:00 2001 From: David Terpay Date: Thu, 12 Oct 2023 16:56:56 -0400 Subject: [PATCH] validate bundler txs --- lanes/mev/check_tx.go | 2 +- lanes/mev/factory.go | 57 +- tests/app/app.go | 6 +- .../integration/block_sdk_integration_test.go | 8 +- tests/integration/block_sdk_suite.go | 873 +++++++----------- tests/integration/chain_setup.go | 47 +- x/auction/ante/utils.go | 4 +- x/auction/keeper/auction.go | 30 + x/auction/keeper/auction_test.go | 56 ++ x/auction/types/bid_info.go | 15 +- 10 files changed, 538 insertions(+), 560 deletions(-) diff --git a/lanes/mev/check_tx.go b/lanes/mev/check_tx.go index 787343f9..1d530a55 100644 --- a/lanes/mev/check_tx.go +++ b/lanes/mev/check_tx.go @@ -166,7 +166,7 @@ func (handler *CheckTxHandler) CheckTx() CheckTx { "bid_height", bidInfo.Timeout, "bidder", bidInfo.Bidder, "bid", bidInfo.Bid, - "removing tx from mempool", true, + "is_recheck_tx", ctx.IsReCheckTx(), ) // attempt to remove the bid from the MEVLane (if it exists) diff --git a/lanes/mev/factory.go b/lanes/mev/factory.go index 41cf6f17..6e58099a 100644 --- a/lanes/mev/factory.go +++ b/lanes/mev/factory.go @@ -79,25 +79,37 @@ func (config *DefaultAuctionFactory) GetAuctionBidInfo(tx sdk.Tx) (*types.BidInf return nil, fmt.Errorf("invalid bidder address (%s): %w", msg.Bidder, err) } - timeoutTx, ok := tx.(TxWithTimeoutHeight) - if !ok { - return nil, fmt.Errorf("cannot extract timeout; transaction does not implement TxWithTimeoutHeight") + height, err := config.GetTimeoutHeight(tx) + if err != nil { + return nil, err } - signers, err := config.getBundleSigners(msg.Transactions) + signers, timeouts, err := config.getBundleInfo(msg.Transactions) if err != nil { return nil, err } return &types.BidInfo{ - Bid: msg.Bid, - Bidder: bidder, - Transactions: msg.Transactions, - Timeout: timeoutTx.GetTimeoutHeight(), - Signers: signers, + Bid: msg.Bid, + Bidder: bidder, + Transactions: msg.Transactions, + TransactionTimeouts: timeouts, + Timeout: height, + Signers: signers, }, nil } +// GetTimeoutHeight returns the timeout height of the transaction. +func (config *DefaultAuctionFactory) GetTimeoutHeight(tx sdk.Tx) (uint64, error) { + timeoutTx, ok := tx.(TxWithTimeoutHeight) + if !ok { + return 0, fmt.Errorf("cannot extract timeout; transaction does not implement TxWithTimeoutHeight") + } + + return timeoutTx.GetTimeoutHeight(), nil +} + +// MatchHandler defines a default function that checks if a transaction matches the mev lane. func (config *DefaultAuctionFactory) MatchHandler() base.MatchHandler { return func(ctx sdk.Context, tx sdk.Tx) bool { bidInfo, err := config.GetAuctionBidInfo(tx) @@ -105,31 +117,38 @@ func (config *DefaultAuctionFactory) MatchHandler() base.MatchHandler { } } -// getBundleSigners defines a default function that returns the signers of all transactions in -// a bundle. In the default case, each bundle transaction will be an sdk.Tx and the -// signers are the signers of each sdk.Msg in the transaction. -func (config *DefaultAuctionFactory) getBundleSigners(bundle [][]byte) ([]map[string]struct{}, error) { - bundleSigners := make([]map[string]struct{}, 0) +// getBundleInfo defines a default function that returns the signers of all transactions in +// a bundle as well as each bundled txs timeout. In the default case, each bundle transaction +// will be an sdk.Tx and the signers are the signers of each sdk.Msg in the transaction. +func (config *DefaultAuctionFactory) getBundleInfo(bundle [][]byte) ([]map[string]struct{}, []uint64, error) { + bundleSigners := make([]map[string]struct{}, len(bundle)) + timeouts := make([]uint64, len(bundle)) - for _, tx := range bundle { + for index, tx := range bundle { sdkTx, err := config.txDecoder(tx) if err != nil { - return nil, err + return nil, nil, err } txSigners := make(map[string]struct{}) signers, err := config.signerExtractor.GetSigners(sdkTx) if err != nil { - return nil, err + return nil, nil, err } for _, signer := range signers { txSigners[signer.Signer.String()] = struct{}{} } - bundleSigners = append(bundleSigners, txSigners) + timeout, err := config.GetTimeoutHeight(sdkTx) + if err != nil { + return nil, nil, err + } + + bundleSigners[index] = txSigners + timeouts[index] = timeout } - return bundleSigners, nil + return bundleSigners, timeouts, nil } diff --git a/tests/app/app.go b/tests/app/app.go index d671907b..8949ba6e 100644 --- a/tests/app/app.go +++ b/tests/app/app.go @@ -268,7 +268,7 @@ func New( Logger: app.Logger(), TxEncoder: app.txConfig.TxEncoder(), TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyZeroDec(), // This means the lane has no limit on block space. + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), // This means the lane has no limit on block space. SignerExtractor: signer_extraction.NewDefaultAdapter(), MaxTxs: 0, // This means the lane has no limit on the number of transactions it can store. } @@ -282,7 +282,7 @@ func New( Logger: app.Logger(), TxEncoder: app.txConfig.TxEncoder(), TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyZeroDec(), + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.2"), SignerExtractor: signer_extraction.NewDefaultAdapter(), MaxTxs: 0, } @@ -297,7 +297,7 @@ func New( Logger: app.Logger(), TxEncoder: app.txConfig.TxEncoder(), TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyZeroDec(), + MaxBlockSpace: math.LegacyMustNewDecFromStr("0.6"), SignerExtractor: signer_extraction.NewDefaultAdapter(), MaxTxs: 0, } diff --git a/tests/integration/block_sdk_integration_test.go b/tests/integration/block_sdk_integration_test.go index e72296bd..59937d8e 100644 --- a/tests/integration/block_sdk_integration_test.go +++ b/tests/integration/block_sdk_integration_test.go @@ -8,6 +8,7 @@ import ( "github.com/strangelove-ventures/interchaintest/v7" "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v7/ibc" + ictestutil "github.com/strangelove-ventures/interchaintest/v7/testutil" "github.com/stretchr/testify/suite" "github.com/skip-mev/block-sdk/tests/integration" @@ -16,7 +17,7 @@ import ( var ( // config params - numValidators = 4 + numValidators = 1 numFullNodes = 0 denom = "stake" @@ -36,6 +37,10 @@ var ( }, } + consensusParams = ictestutil.Toml{ + "timeout_commit": "3500ms", + } + // interchain specification spec = &interchaintest.ChainSpec{ ChainName: "block-sdk", @@ -63,6 +68,7 @@ var ( NoHostMount: noHostMount, UsingNewGenesisCommand: true, ModifyGenesis: cosmos.ModifyGenesis(genesisKV), + ConfigFileOverrides: map[string]any{"config/config.toml": ictestutil.Toml{"consensus": consensusParams}}, }, } ) diff --git a/tests/integration/block_sdk_suite.go b/tests/integration/block_sdk_suite.go index 3a0c0d6e..ee2506cc 100644 --- a/tests/integration/block_sdk_suite.go +++ b/tests/integration/block_sdk_suite.go @@ -3,8 +3,6 @@ package integration import ( "context" - "bytes" - "cosmossdk.io/math" rpctypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/cosmos/cosmos-sdk/codec" @@ -23,6 +21,11 @@ const ( initBalance = 1000000000000 ) +type committedTx struct { + tx []byte + res *rpctypes.ResultTx +} + // IntegrationTestSuite runs the Block SDK integration test-suite against a given interchaintest specification type IntegrationTestSuite struct { suite.Suite @@ -128,6 +131,10 @@ func (s *IntegrationTestSuite) TestValidBids() { // create message send tx tx := banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100)))) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 1 + // create the MsgAuctioBid bidAmt := params.ReserveFee bid, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{ @@ -137,28 +144,26 @@ func (s *IntegrationTestSuite) TestValidBids() { tx, }, SequenceIncrement: 1, + Height: nextBlockHeight, }, }) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast + wait for the tx to be included in a block res := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid}, - Height: height + 1, + Height: nextBlockHeight, }, }) - height = height + 1 - - // wait for next height - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) - // query + verify the block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), int64(height)) - VerifyBlock(s.T(), block, 0, TxHash(res[0]), bundledTxs) + // verify the block + expectedBlock := [][]byte{ + res[0], + bundledTxs[0], + } + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, expectedBlock) // ensure that the escrow account has the correct balance escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) @@ -176,6 +181,10 @@ func (s *IntegrationTestSuite) TestValidBids() { msgs[0] = banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100)))) msgs[1] = banktypes.NewMsgSend(s.user2.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100)))) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 1 + // create the MsgAuctionBid bidAmt := params.ReserveFee bid, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{ @@ -183,63 +192,34 @@ func (s *IntegrationTestSuite) TestValidBids() { User: s.user1, Msgs: msgs[0:1], SequenceIncrement: 1, + Height: nextBlockHeight, }, }) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // create the messages to be broadcast - msgsToBcast := make([]Tx, 0) - msgsToBcast = append(msgsToBcast, Tx{ + txs := make([]Tx, 0) + txs = append(txs, Tx{ User: s.user1, Msgs: []sdk.Msg{bid}, - Height: height + 3, + Height: nextBlockHeight, }) - msgsToBcast = append(msgsToBcast, Tx{ - User: s.user2, - Msgs: msgs[1:2], - Height: height + 3, + txs = append(txs, Tx{ + User: s.user2, + Msgs: msgs[1:2], }) - expTxs := make(chan committedTx, 2) - - regular_txs := s.BroadcastTxsWithCallback( - context.Background(), - s.chain.(*cosmos.CosmosChain), - msgsToBcast, - func(tx []byte, resp *rpctypes.ResultTx) { - expTxs <- committedTx{tx, resp} - }, - ) - close(expTxs) - s.Require().Len(expTxs, 2) - - // get the height of the block that the bid was included in - var commitHeight int64 - - tx1 := <-expTxs - tx2 := <-expTxs - - // determine which tx is the bid - if bytes.Equal(tx1.tx, regular_txs[0]) { - commitHeight = tx1.res.Height - } else { - commitHeight = tx2.res.Height - } - - // if they were committed in the same height - if tx1.res.Height == tx2.res.Height { - bundledTxs = append(bundledTxs, regular_txs[1:]...) - } - - // get the block at the next height - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), commitHeight) + // broadcast + wait for the tx to be included in a block + res := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), txs) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) // verify the block - bidTxHash := TxHash(regular_txs[0]) - VerifyBlock(s.T(), block, 0, bidTxHash, bundledTxs) + expectedBlock := [][]byte{ + res[0], + bundledTxs[0], + res[1], + } + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, expectedBlock) // ensure that escrow account has the correct balance escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) @@ -251,6 +231,12 @@ func (s *IntegrationTestSuite) TestValidBids() { // get escrow account balance before bid escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 2 + + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + // create multi-tx valid bundle // bank-send msg txs := make([]Tx, 2) @@ -258,11 +244,13 @@ func (s *IntegrationTestSuite) TestValidBids() { User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 1, + Height: nextBlockHeight, } txs[1] = Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 2, + Height: nextBlockHeight, } // create bundle bidAmt := params.ReserveFee @@ -271,38 +259,35 @@ func (s *IntegrationTestSuite) TestValidBids() { bid2, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt.Add(params.MinBidIncrement), txs) bid3, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt.Add(params.MinBidIncrement).Add(params.MinBidIncrement), txs) - // query height - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - // wait for the next height to broadcast - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - height++ - // broadcast all bids broadcastedTxs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid}, - Height: height + 1, + Height: nextBlockHeight, SkipInclusionCheck: true, }, { User: s.user1, Msgs: []sdk.Msg{bid2}, - Height: height + 1, + Height: nextBlockHeight, SkipInclusionCheck: true, }, { User: s.user1, Msgs: []sdk.Msg{bid3}, - Height: height + 1, + Height: nextBlockHeight, }, }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) - // Verify the block - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - VerifyBlock(s.T(), Block(s.T(), s.chain.(*cosmos.CosmosChain), int64(height+1)), 0, TxHash(broadcastedTxs[2]), bundledTxs) + // verify the block + expectedBlock := [][]byte{ + broadcastedTxs[2], + bundledTxs[0], + bundledTxs[1], + } + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, expectedBlock) // check escrow account balance escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) @@ -314,13 +299,14 @@ func (s *IntegrationTestSuite) TestValidBids() { // reset height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) require.NoError(s.T(), err) - - // wait for the next height - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + nextBlockHeight := height + 2 // escrow account balance escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) + // wait for the next height + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + // create valid bundle // bank-send msg txs := make([]Tx, 2) @@ -338,39 +324,30 @@ func (s *IntegrationTestSuite) TestValidBids() { bidAmt := params.ReserveFee bid, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt, txs) - // get chain height - height, err = s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - expTxs := make(chan committedTx, 4) - - // broadcast txs in the bundle to network + bundle + extra - broadcastedTxs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + txsToBroadcast := []Tx{ { User: s.user2, Msgs: []sdk.Msg{bid}, - Height: height + 3, - }, { - User: s.user3, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user3.Address(), s.user1.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - Height: height + 3, - }}, - func(tx []byte, resp *rpctypes.ResultTx) { - expTxs <- committedTx{tx, resp} - }) - close(expTxs) - - var bidTxHeight int64 - for tx := range expTxs { - if bytes.Equal(tx.tx, broadcastedTxs[0]) { - bidTxHeight = tx.res.Height - } + Height: nextBlockHeight, + }, + { + User: s.user3, + Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user3.Address(), s.user1.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, + }, } - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), bidTxHeight) + // broadcast txs in the bundle to network + bundle + extra + broadcastedTxs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), append(txsToBroadcast, txs...)) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) - // check block - VerifyBlock(s.T(), block, 0, TxHash(broadcastedTxs[0]), bundledTxs) + // Verify the block + expectedBlock := [][]byte{ + broadcastedTxs[0], + bundledTxs[0], + bundledTxs[1], + broadcastedTxs[1], + } + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, expectedBlock) // check escrow account balance escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) @@ -379,11 +356,6 @@ func (s *IntegrationTestSuite) TestValidBids() { }) } -type committedTx struct { - tx []byte - res *rpctypes.ResultTx -} - // TestMultipleBids tests the execution of various valid auction bids in the same block. There are a few // invariants that are tested: // @@ -398,194 +370,62 @@ func (s *IntegrationTestSuite) TestMultipleBids() { params := QueryAuctionParams(s.T(), s.chain) escrowAddr := sdk.AccAddress(params.EscrowAccountAddress).String() - s.Run("broadcasting bids to two different validators (both should execute over several blocks) with same bid", func() { - // escrow account balance - escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - - // create bid 1 - // bank-send msg - msg := Tx{ - User: s.user1, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, - } - // create bid1 - bidAmt := params.ReserveFee - bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - - // create bid 2 - msg2 := Tx{ - User: s.user2, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user2.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, - } - // create bid2 w/ higher bid than bid1 - bid2, bundledTxs2 := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt.Add(params.MinBidIncrement), []Tx{msg2}) + s.Run("Multiple bid transactions with second bid being smaller than min bid increment", func() { // get chain height height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) require.NoError(s.T(), err) + nextBlockHeight := height + 2 - // create channel to receive txs - txsCh := make(chan committedTx, 2) - - // broadcast both bids (with ample time to be committed (instead of timing out)) - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ - { - User: s.user1, - Msgs: []sdk.Msg{bid1}, - Height: height + 4, - }, - { - User: s.user2, - Msgs: []sdk.Msg{bid2}, - Height: height + 3, - }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - txsCh <- committedTx{tx, resp} - }) - - // check txs were committed - require.Len(s.T(), txsCh, 2) - close(txsCh) - - tx1 := <-txsCh - tx2 := <-txsCh - - // query next block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), tx1.res.Height) - - // check bid2 was included first - VerifyBlock(s.T(), block, 0, TxHash(txs[1]), bundledTxs2) - - // check next block - block = Block(s.T(), s.chain.(*cosmos.CosmosChain), tx2.res.Height) - - // check bid1 was included second - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), bundledTxs) - - // check escrow balance - escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - expectedIncrement := escrowAddressIncrement(bidAmt.Add(params.MinBidIncrement.Add(bidAmt)).Amount, params.ProposerFee) - require.Equal(s.T(), escrowAcctBalanceBeforeBid+expectedIncrement, escrowAcctBalanceAfterBid) - }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - s.Run("Multiple bid transactions with second bid being smaller than min bid increment", func() { // escrow account balance escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) // create bid 1 // bank-send msg - tx := Tx{ + bidAmt := params.ReserveFee + bundleTx1 := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 1, + Height: nextBlockHeight, } // create bid1 - bidAmt := params.ReserveFee - bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{tx}) + bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{bundleTx1}) // create bid 2 - tx2 := Tx{ + bundleTx2 := Tx{ User: s.user2, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user2.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 1, + Height: nextBlockHeight, } - // create bid2 w/ higher bid than bid1 - bid2, _ := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{tx2}) - - // get chain height - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - expTx := make(chan committedTx, 1) + bid2, _ := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{bundleTx2}) - // broadcast both bids (wait for the first to be committed) - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + txs := []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid1}, - Height: height + 4, + Height: nextBlockHeight, }, { User: s.user2, Msgs: []sdk.Msg{bid2}, - Height: height + 3, + Height: nextBlockHeight, ExpectFail: true, }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - expTx <- committedTx{tx, resp} - }) - - close(expTx) - commitTx := <-expTx - - // query next block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), commitTx.res.Height) - - // check bid2 was included first - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), bundledTxs) - - // check escrow balance - escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - expectedIncrement := escrowAddressIncrement(bidAmt.Amount, params.ProposerFee) - require.Equal(s.T(), escrowAcctBalanceBeforeBid+expectedIncrement, escrowAcctBalanceAfterBid) - }) - - s.Run("Multiple bid transactions from diff accounts with second bid being smaller than min bid increment", func() { - // escrow account balance - escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - - // create bid 1 - // bank-send msg - msg := Tx{ - User: s.user1, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, - } - // create bid1 - bidAmt := params.ReserveFee - bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - - // create bid 2 - msg2 := Tx{ - User: s.user2, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user2.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, } - // create bid2 w/ higher bid than bid1 - bid2, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg2}) - // get chain height - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - expTx := make(chan committedTx, 1) - - // broadcast both bids - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ - { - User: s.user1, - Msgs: []sdk.Msg{bid1}, - Height: height + 4, - }, - { - User: s.user1, - Msgs: []sdk.Msg{bid2}, - Height: height + 4, - ExpectFail: true, - }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - expTx <- committedTx{tx, resp} - }) - close(expTx) - - commitTx := <-expTx - - // query next block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), commitTx.res.Height) + // broadcast both bids (wait for the first to be committed) + resp := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), txs) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) - // check bid1 was included first - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), bundledTxs) + // verify the block + expectedBlock := [][]byte{ + resp[0], + bundledTxs[0], + } + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, expectedBlock) // check escrow balance escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) @@ -593,214 +433,88 @@ func (s *IntegrationTestSuite) TestMultipleBids() { require.Equal(s.T(), escrowAcctBalanceBeforeBid+expectedIncrement, escrowAcctBalanceAfterBid) }) - s.Run("Multiple transactions with increasing bids but first bid has same bundle so it should fail in later block", func() { - // escrow account balance - escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - - // create bid 1 - // bank-send msg - msg := Tx{ - User: s.user1, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, - } - // create bid1 - bidAmt := params.ReserveFee - bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - - // create bid2 w/ higher bid than bid1 - bid2, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt.Add(params.MinBidIncrement), []Tx{msg}) + s.Run("Multiple transactions from diff. account with increasing bids and first bid should fail in later block", func() { // get chain height height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) require.NoError(s.T(), err) + nextBlockHeight := height + 2 - commitTx := make(chan committedTx, 1) - - // broadcast both bids - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ - { - User: s.user1, - Msgs: []sdk.Msg{bid1}, - Height: height + 4, - SkipInclusionCheck: true, - }, - { - User: s.user1, - Msgs: []sdk.Msg{bid2}, - Height: height + 3, - }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - commitTx <- committedTx{tx, resp} - }) - close(commitTx) - - expTx := <-commitTx - - // query next block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), expTx.res.Height) - - // check bid2 was included first - VerifyBlock(s.T(), block, 0, TxHash(txs[1]), bundledTxs) - - // check escrow balance - escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - expectedIncrement := escrowAddressIncrement(bidAmt.Add(params.MinBidIncrement).Amount, params.ProposerFee) - require.Equal(s.T(), escrowAcctBalanceBeforeBid+expectedIncrement, escrowAcctBalanceAfterBid) - - // wait for next block for mempool to clear - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+3) - }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - s.Run("Multiple transactions from diff. account with increasing bids but first bid has same bundle so it should fail in later block", func() { // escrow account balance escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) // create bid 1 - // bank-send msg - msg := Tx{ + bidAmt := params.ReserveFee + bundleTx1 := Tx{ User: s.user3, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user3.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, } - - // create bid1 - bidAmt := params.ReserveFee - bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) + bid1, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{bundleTx1}) // create bid2 w/ higher bid than bid1 - bid2, _ := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt.Add(params.MinBidIncrement), []Tx{msg}) - // get chain height - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - commitTx := make(chan committedTx, 1) + bidAmt = params.ReserveFee.Add(params.MinBidIncrement) + bundleTx2 := Tx{ + User: s.user2, + Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user2.Address(), s.user1.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, + Height: nextBlockHeight, + SequenceIncrement: 1, + } + bid2, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{bundleTx2}) - // broadcast both bids - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + txs := []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid1}, - Height: height + 4, + Height: nextBlockHeight, SkipInclusionCheck: true, }, { User: s.user2, Msgs: []sdk.Msg{bid2}, - Height: height + 3, + Height: nextBlockHeight, }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - commitTx <- committedTx{tx, resp} - }) - close(commitTx) - - expTx := <-commitTx - - // query next block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), expTx.res.Height) - - // check bid2 was included first - VerifyBlock(s.T(), block, 0, TxHash(txs[1]), bundledTxs) - - // check escrow balance - escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - expectedIncrement := escrowAddressIncrement(bidAmt.Add(params.MinBidIncrement).Amount, params.ProposerFee) - require.Equal(s.T(), escrowAcctBalanceBeforeBid+expectedIncrement, escrowAcctBalanceAfterBid) - - // wait for next block for mempool to clear - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+3) - }) - - s.Run("Multiple transactions with increasing bids and different bundles", func() { - // escrow account balance - escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - - // create bid 1 - // bank-send msg - msg := Tx{ - User: s.user1, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, - } - // create bid1 - bidAmt := params.ReserveFee - bid1, bundledTxs := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - - // create bid2 - // create a second message - msg2 := Tx{ - User: s.user2, - Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user2.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 1, } - // create bid2 w/ higher bid than bid1 - bid2, bundledTxs2 := s.CreateAuctionBidMsg(context.Background(), s.user2, s.chain.(*cosmos.CosmosChain), bidAmt.Add(params.MinBidIncrement), []Tx{msg2}) - // get chain height - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - // make channel for committedTxs (expect 2 txs to be committed) - committedTxs := make(chan committedTx, 2) + // broadcast both bids (wait for the second to be committed) + resp := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), txs) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) - // broadcast both bids - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ - { - User: s.user1, - Msgs: []sdk.Msg{bid1}, - Height: height + 4, - }, - { - User: s.user2, - Msgs: []sdk.Msg{bid2}, - Height: height + 3, - }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - committedTxs <- committedTx{ - tx: tx, - res: resp, - } - }) - - // close the channel when finished - close(committedTxs) - - // expect 2 txs - tx1 := <-committedTxs - tx2 := <-committedTxs - - // query next block - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), tx1.res.Height) - - // check bid2 was included first - VerifyBlock(s.T(), block, 0, TxHash(txs[1]), bundledTxs2) - - // query next block and check tx inclusion - block = Block(s.T(), s.chain.(*cosmos.CosmosChain), tx2.res.Height) + // verify the block + expectedBlock := [][]byte{ + resp[1], + bundledTxs[0], + } + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, expectedBlock) - // check bid1 was included second - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), bundledTxs) + // Wait for next block and ensure other bid did not get included + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight+1) + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight+1, nil) // check escrow balance escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - expectedIncrement := escrowAddressIncrement(bidAmt.Add(params.MinBidIncrement.Add(bidAmt)).Amount, params.ProposerFee) + expectedIncrement := escrowAddressIncrement(bidAmt.Amount, params.ProposerFee) require.Equal(s.T(), escrowAcctBalanceBeforeBid+expectedIncrement, escrowAcctBalanceAfterBid) }) } func (s *IntegrationTestSuite) TestInvalidBids() { params := QueryAuctionParams(s.T(), s.chain) - escrowAddr := sdk.AccAddress(params.EscrowAccountAddress).String() s.Run("searcher is attempting to submit a bundle that includes another bid tx", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create bid tx msg := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 2, + Height: height + 1, } bidAmt := params.ReserveFee bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) // wrap bidTx in another tx wrappedBid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{ { @@ -811,8 +525,6 @@ func (s *IntegrationTestSuite) TestInvalidBids() { }, }) - require.NoError(s.T(), err) - // broadcast wrapped bid, and expect a failure s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { @@ -825,28 +537,45 @@ func (s *IntegrationTestSuite) TestInvalidBids() { }) s.Run("Invalid bid that is attempting to bid more than their balance", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create bid tx msg := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, - SequenceIncrement: 2, + SequenceIncrement: 1, + Height: height + 1, } bidAmt := sdk.NewCoin(s.denom, math.NewInt(1000000000000000000)) bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast wrapped bid, and expect a failure - s.SimulateTx(context.Background(), s.chain.(*cosmos.CosmosChain), s.user1, height+1, true, []sdk.Msg{bid}...) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + { + User: s.user1, + Msgs: []sdk.Msg{bid}, + Height: height + 1, + ExpectFail: true, + }, + }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) s.Run("Invalid bid that is attempting to front-run/sandwich", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 1 + // create bid tx msg := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 1, + Height: nextBlockHeight, } msg2 := Tx{ User: s.user2, @@ -856,70 +585,99 @@ func (s *IntegrationTestSuite) TestInvalidBids() { User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 2, + Height: nextBlockHeight, } bidAmt := params.ReserveFee bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg, msg2, msg3}) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast wrapped bid, and expect a failure - s.SimulateTx(context.Background(), s.chain.(*cosmos.CosmosChain), s.user1, height+1, true, []sdk.Msg{bid}...) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + { + User: s.user1, + Msgs: []sdk.Msg{bid}, + Height: height + 1, + ExpectFail: true, + }, + }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) s.Run("Invalid bid that includes an invalid bundle tx", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create bid tx msg := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 2, + Height: height + 1, } bidAmt := params.ReserveFee bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast wrapped bid, and expect a failure s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid}, - ExpectFail: true, Height: height + 1, + ExpectFail: true, }, }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) s.Run("Invalid auction bid with a bid smaller than the reserve fee", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create bid tx msg := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 1, + Height: height + 1, } // create bid smaller than reserve bidAmt := sdk.NewCoin(s.denom, math.NewInt(0)) bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast wrapped bid, and expect a failure - s.SimulateTx(context.Background(), s.chain.(*cosmos.CosmosChain), s.user1, height+1, true, []sdk.Msg{bid}...) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + { + User: s.user1, + Msgs: []sdk.Msg{bid}, + Height: height + 1, + ExpectFail: true, + }, + }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) s.Run("Invalid auction bid with too many transactions in the bundle", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create bid tx msgs := make([]Tx, 4) - for i := range msgs { msgs[i] = Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: uint64(i + 1), + Height: height + 1, } } @@ -927,55 +685,71 @@ func (s *IntegrationTestSuite) TestInvalidBids() { bidAmt := sdk.NewCoin(s.denom, math.NewInt(0)) bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, msgs) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast wrapped bid, and expect a failure - s.SimulateTx(context.Background(), s.chain.(*cosmos.CosmosChain), s.user1, height+1, true, []sdk.Msg{bid}...) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + { + User: s.user1, + Msgs: []sdk.Msg{bid}, + Height: height + 1, + ExpectFail: true, + }, + }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) s.Run("invalid auction bid that has an invalid timeout", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create bid tx msg := Tx{ User: s.user1, Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, SequenceIncrement: 1, + Height: height + 1, } - // create bid smaller than reserve - bidAmt := sdk.NewCoin(s.denom, math.NewInt(0)) + bidAmt := sdk.NewCoin(s.denom, params.ReserveFee.Amount) bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) // broadcast wrapped bid, and expect a failure - s.SimulateTx(context.Background(), s.chain.(*cosmos.CosmosChain), s.user1, 0, true, []sdk.Msg{bid}...) + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + { + User: s.user1, + Msgs: []sdk.Msg{bid}, + Height: height + 2, + ExpectFail: true, + }, + }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) s.Run("Invalid bid that includes valid transactions that are in the mempool", func() { - // get escrow account balance before bid - escrowAcctBalanceBeforeBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) - // create bundle w/ a single tx // create message send tx - tx := banktypes.NewMsgSend(s.user2.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100)))) + msgSend := banktypes.NewMsgSend(s.user2.Address(), s.user3.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100)))) // create the MsgAuctioBid (this should fail b.c same tx is repeated twice) bidAmt := params.ReserveFee bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{ { User: s.user2, - Msgs: []sdk.Msg{ - tx, - }, + Msgs: []sdk.Msg{msgSend}, }, { User: s.user2, - Msgs: []sdk.Msg{tx}, + Msgs: []sdk.Msg{msgSend}, }, }) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - // broadcast + wait for the tx to be included in a block txs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { @@ -986,7 +760,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() { }, { User: s.user2, - Msgs: []sdk.Msg{tx}, + Msgs: []sdk.Msg{msgSend}, Height: height + 1, }, }) @@ -994,18 +768,68 @@ func (s *IntegrationTestSuite) TestInvalidBids() { // wait for next height WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - // query + verify the block expect no bid - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), int64(height+1)) - VerifyBlock(s.T(), block, 0, "", txs[1:]) + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, [][]byte{txs[1]}) + }) - // ensure that the escrow account has the correct balance (same as before) - escrowAcctBalanceAfterBid := QueryAccountBalance(s.T(), s.chain, escrowAddr, params.ReserveFee.Denom) - require.Equal(s.T(), escrowAcctBalanceAfterBid, escrowAcctBalanceBeforeBid) + s.Run("searcher does not set the timeout height on their transactions", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + + // create bid tx + msg := Tx{ + User: s.user1, + Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, + SequenceIncrement: 1, + } + + bidAmt := sdk.NewCoin(s.denom, params.ReserveFee.Amount) + bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) + + // broadcast wrapped bid, and expect a failure + bidTx := Tx{ + User: s.user1, + Msgs: []sdk.Msg{bid}, + ExpectFail: true, + Height: height + 1, + } + + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{bidTx}) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) }) -} -func escrowAddressIncrement(bid math.Int, proposerFee math.LegacyDec) int64 { - return int64(bid.Sub(math.Int(math.LegacyNewDecFromInt(bid).Mul(proposerFee).RoundInt())).Int64()) + s.Run("timeout height on searchers txs in bundle do not match bid timeout", func() { + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + + // create bid tx + msg := Tx{ + User: s.user1, + Msgs: []sdk.Msg{banktypes.NewMsgSend(s.user1.Address(), s.user2.Address(), sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100))))}, + SequenceIncrement: 1, + Height: height + 3, + } + + bidAmt := sdk.NewCoin(s.denom, params.ReserveFee.Amount) + bid, _ := s.CreateAuctionBidMsg(context.Background(), s.user1, s.chain.(*cosmos.CosmosChain), bidAmt, []Tx{msg}) + + // broadcast wrapped bid, and expect a failure + bidTx := Tx{ + User: s.user1, + Msgs: []sdk.Msg{bid}, + ExpectFail: true, + Height: height + 1, + } + + s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{bidTx}) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), height+1, nil) + }) } // TestFreeLane tests that the application correctly handles free lanes. There are a few invariants that are tested: @@ -1022,6 +846,9 @@ func (s *IntegrationTestSuite) TestFreeLane() { // query balance of account before tx submission balanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // create a free tx (MsgDelegate), broadcast and wait for commit s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { @@ -1037,6 +864,9 @@ func (s *IntegrationTestSuite) TestFreeLane() { }, }) + // wait for next block + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + // check balance of account balanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) require.Equal(s.T(), balanceBefore, balanceAfter+delegation.Amount.Int64()) @@ -1046,6 +876,9 @@ func (s *IntegrationTestSuite) TestFreeLane() { user1BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) user2BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // user1 submits a free-tx, user2 submits a normal tx s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { @@ -1072,6 +905,9 @@ func (s *IntegrationTestSuite) TestFreeLane() { }, }) + // wait for next block + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + // check balance after, user1 balance only diff by delegation user1BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) user2BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) @@ -1085,6 +921,9 @@ func (s *IntegrationTestSuite) TestFreeLane() { user1BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) user2BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + // user1 submits a free-tx, user2 submits a free tx s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { @@ -1109,6 +948,9 @@ func (s *IntegrationTestSuite) TestFreeLane() { }, }) + // wait for next block + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) + // check balance after, user1 balance only diff by delegation user1BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) user2BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) @@ -1129,6 +971,10 @@ func (s *IntegrationTestSuite) TestLanes() { s.Run("block with mev, free, and normal tx", func() { user2BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 2 + // create free-tx, bid-tx, and normal-tx\ bid, bundledTx := s.CreateAuctionBidMsg( context.Background(), @@ -1146,20 +992,16 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, SequenceIncrement: 1, + Height: nextBlockHeight, }, }, ) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - committedTxs := make(chan committedTx, 3) - - txs := s.BroadcastTxsWithCallback(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + txsToBroadcast := []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid}, - Height: height + 3, + Height: nextBlockHeight, }, { User: s.user2, @@ -1182,23 +1024,15 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, }, - }, func(tx []byte, resp *rpctypes.ResultTx) { - committedTxs <- committedTx{tx: tx, res: resp} - }) - close(committedTxs) - - // find height of committed tx - var committedHeight int64 - for tx := range committedTxs { - if bytes.Equal(tx.tx, txs[0]) { - committedHeight = tx.res.Height - break - } } - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), committedHeight) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight-1) - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), bundledTx) + txs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), txsToBroadcast) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, [][]byte{txs[0], bundledTx[0], txs[1], txs[2]}) // check user2 balance expect no fee deduction user2BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) @@ -1208,6 +1042,11 @@ func (s *IntegrationTestSuite) TestLanes() { s.Run("failing MEV transaction, free, and normal tx", func() { user2BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) user1Balance := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user1.FormattedAddress(), s.denom) + + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 2 + // create free-tx, bid-tx, and normal-tx\ bid, _ := s.CreateAuctionBidMsg( context.Background(), @@ -1225,6 +1064,7 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, SequenceIncrement: 2, + Height: nextBlockHeight, }, { User: s.user1, @@ -1236,18 +1076,17 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, SequenceIncrement: 2, + Height: nextBlockHeight, }, }, ) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - - s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight-1) + txs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { User: s.user1, Msgs: []sdk.Msg{bid}, - Height: height + 1, + Height: nextBlockHeight, ExpectFail: true, }, { @@ -1272,6 +1111,10 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, [][]byte{txs[1], txs[2]}) // check user2 balance expect no fee deduction user2BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) @@ -1281,6 +1124,10 @@ func (s *IntegrationTestSuite) TestLanes() { s.Run("MEV transaction that includes transactions from the free lane", func() { user2BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 2 + delegateTx := Tx{ User: s.user2, Msgs: []sdk.Msg{ @@ -1310,37 +1157,36 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, SequenceIncrement: 1, + Height: nextBlockHeight, }, }, ) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight-1) txs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { User: s.user3, Msgs: []sdk.Msg{bid}, - Height: height + 1, + Height: nextBlockHeight, }, - delegateTx, }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) + + // verify the block + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, [][]byte{txs[0], bundledTx[0], bundledTx[1]}) // query balance after, expect no fees paid user2BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) s.Require().Equal(user2BalanceBefore, user2BalanceAfter+delegation.Amount.Int64()) - - // check block - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), int64(height+1)) - - // verify - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), bundledTx) }) s.Run("MEV transaction that includes transaction from free lane + other free lane txs + normal txs", func() { user2BalanceBefore := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) + height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) + require.NoError(s.T(), err) + nextBlockHeight := height + 2 + // create free-txs signed by user2 / 3 user2DelegateTx := Tx{ User: s.user2, @@ -1365,6 +1211,7 @@ func (s *IntegrationTestSuite) TestLanes() { }, GasPrice: 10, SequenceIncrement: 1, + Height: nextBlockHeight, } // create bid-tx w/ user3 DelegateTx @@ -1386,27 +1233,25 @@ func (s *IntegrationTestSuite) TestLanes() { }, }, SequenceIncrement: 2, + Height: nextBlockHeight, }, }, ) - height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background()) - require.NoError(s.T(), err) - + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight-1) txs := s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{ { User: s.user3, Msgs: []sdk.Msg{bid}, - Height: height + 1, + Height: nextBlockHeight, }, // already included above user2DelegateTx, }) + WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight) // verify block - WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1) - block := Block(s.T(), s.chain.(*cosmos.CosmosChain), int64(height+1)) - VerifyBlock(s.T(), block, 0, TxHash(txs[0]), append(bundledTx, txs[1:]...)) + VerifyBlockWithExpectedBlock(s.T(), s.chain.(*cosmos.CosmosChain), nextBlockHeight, [][]byte{txs[0], bundledTx[0], bundledTx[1], txs[1]}) // check user2 balance expect no fee deduction user2BalanceAfter := QueryAccountBalance(s.T(), s.chain.(*cosmos.CosmosChain), s.user2.FormattedAddress(), s.denom) diff --git a/tests/integration/chain_setup.go b/tests/integration/chain_setup.go index 7b10bf6a..4f6af6ee 100644 --- a/tests/integration/chain_setup.go +++ b/tests/integration/chain_setup.go @@ -47,7 +47,7 @@ type KeyringOverride struct { // and returns the associated chain func ChainBuilderFromChainSpec(t *testing.T, spec *interchaintest.ChainSpec) ibc.Chain { // require that NumFullNodes == NumValidators == 4 - require.Equal(t, *spec.NumValidators, 4) + require.Equal(t, *spec.NumValidators, 1) cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{spec}) @@ -106,9 +106,7 @@ func (s *IntegrationTestSuite) CreateTx(ctx context.Context, chain *cosmos.Cosmo } // get gas for tx - _, gas, err := tx.CalculateGas(cc, txf, msgs...) - s.Require().NoError(err) - txf.WithGas(gas) + txf.WithGas(25000000) // update sequence number txf = txf.WithSequence(txf.Sequence() + seqIncrement) @@ -181,8 +179,8 @@ func (s *IntegrationTestSuite) CreateAuctionBidMsg(ctx context.Context, searcher // BroadcastTxs broadcasts the given messages for each user. This function returns the broadcasted txs. If a message // is not expected to be included in a block, set SkipInclusionCheck to true and the method // will not block on the tx's inclusion in a block, otherwise this method will block on the tx's inclusion -func (s *IntegrationTestSuite) BroadcastTxs(ctx context.Context, chain *cosmos.CosmosChain, msgsPerUser []Tx) [][]byte { - return s.BroadcastTxsWithCallback(ctx, chain, msgsPerUser, nil) +func (s *IntegrationTestSuite) BroadcastTxs(ctx context.Context, chain *cosmos.CosmosChain, txs []Tx) [][]byte { + return s.BroadcastTxsWithCallback(ctx, chain, txs, nil) } // BroadcastTxs broadcasts the given messages for each user. This function returns the broadcasted txs. If a message @@ -192,13 +190,13 @@ func (s *IntegrationTestSuite) BroadcastTxs(ctx context.Context, chain *cosmos.C func (s *IntegrationTestSuite) BroadcastTxsWithCallback( ctx context.Context, chain *cosmos.CosmosChain, - msgsPerUser []Tx, + txs []Tx, cb func(tx []byte, resp *rpctypes.ResultTx), ) [][]byte { - txs := make([][]byte, len(msgsPerUser)) + rawTxs := make([][]byte, len(txs)) - for i, msg := range msgsPerUser { - txs[i] = s.CreateTx(ctx, chain, msg.User, msg.SequenceIncrement, msg.Height, msg.GasPrice, msg.Msgs...) + for i, msg := range txs { + rawTxs[i] = s.CreateTx(ctx, chain, msg.User, msg.SequenceIncrement, msg.Height, msg.GasPrice, msg.Msgs...) } // broadcast each tx @@ -210,12 +208,12 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback( s.T().Logf("broadcasting transactions at latest height of %d", statusResp.SyncInfo.LatestBlockHeight) - for i, tx := range txs { + for i, tx := range rawTxs { // broadcast tx resp, err := client.BroadcastTxSync(ctx, tx) // check execution was successful - if !msgsPerUser[i].ExpectFail { + if !txs[i].ExpectFail { s.Require().Equal(resp.Code, uint32(0)) } else { if resp != nil { @@ -224,14 +222,13 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback( s.Require().Error(err) } } - } // block on all txs being included in block eg := errgroup.Group{} - for i, tx := range txs { + for i, tx := range rawTxs { // if we don't expect this tx to be included.. skip it - if msgsPerUser[i].SkipInclusionCheck || msgsPerUser[i].ExpectFail { + if txs[i].SkipInclusionCheck || txs[i].ExpectFail { continue } @@ -254,7 +251,7 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback( s.Require().NoError(eg.Wait()) - return txs + return rawTxs } // QueryAuctionParams queries the x/auction module's params @@ -373,6 +370,20 @@ func VerifyBlock(t *testing.T, block *rpctypes.ResultBlock, offset int, bidTxHas } } +// 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:] + + t.Logf("verifying block %d", height) + require.Equal(t, len(txs), len(blockTxs)) + for i, tx := range txs { + t.Logf("verifying tx %d; expected %s, got %s", i, TxHash(tx), TxHash(blockTxs[i])) + require.Equal(t, TxHash(tx), TxHash(blockTxs[i])) + } +} + func TxHash(tx []byte) string { return strings.ToUpper(hex.EncodeToString(comettypes.Tx(tx).Hash())) } @@ -446,3 +457,7 @@ func (s *IntegrationTestSuite) keyringDirFromNode() string { return localDir } + +func escrowAddressIncrement(bid math.Int, proposerFee math.LegacyDec) int64 { + return int64(bid.Sub(math.Int(math.LegacyNewDecFromInt(bid).Mul(proposerFee).RoundInt())).Int64()) +} diff --git a/x/auction/ante/utils.go b/x/auction/ante/utils.go index b13e780e..e59aedb0 100644 --- a/x/auction/ante/utils.go +++ b/x/auction/ante/utils.go @@ -41,9 +41,9 @@ func ValidateTimeout(ctx sdk.Context, timeout int64) error { height++ } - if height > timeout { + if height != timeout { return fmt.Errorf( - "timeout height cannot be less than the current block height (timeout: %d, current block height: %d)", + "you must set the timeout height to be the next block height got %d, expected %d", timeout, height, ) diff --git a/x/auction/keeper/auction.go b/x/auction/keeper/auction.go index 6a70a16d..e02e068f 100644 --- a/x/auction/keeper/auction.go +++ b/x/auction/keeper/auction.go @@ -37,6 +37,11 @@ func (k Keeper) ValidateBidInfo(ctx sdk.Context, highestBid sdk.Coin, bidInfo *t } } + // Validate the timeouts of the transactions in the bundle. + if err := k.ValidateBundleTimeouts(bidInfo); err != nil { + return err + } + return nil } @@ -180,6 +185,31 @@ func (k Keeper) ValidateAuctionBundle(bidder sdk.AccAddress, bundleSigners []map return nil } +// ValidateBundleTimeouts validates that the timeouts of the transactions in the bundle are valid. We consider +// the timeouts valid iff all of the bidders txs in the bundle are all the same and are equal to the timeout of the bid. +// This is mandatory as otherwise other searchers can potentially include other searcher's transactions in their bundles. +func (k Keeper) ValidateBundleTimeouts(bidInfo *types.BidInfo) error { + bidder := bidInfo.Bidder.String() + bidTimeout := bidInfo.Timeout + + for index, bundleTimeout := range bidInfo.TransactionTimeouts { + signers := bidInfo.Signers[index] + if _, ok := signers[bidder]; !ok { + continue + } + + if bundleTimeout != bidTimeout { + return fmt.Errorf( + "searchers must set the timeout of all of their transactions in the bundle to be the same as the bid timeout; got %d, expected %d", + bundleTimeout, + bidTimeout, + ) + } + } + + return nil +} + // filterSigners removes any signers from the currentSigners map that are not in the txSigners map. func filterSigners(currentSigners, txSigners map[string]struct{}) { for signer := range currentSigners { diff --git a/x/auction/keeper/auction_test.go b/x/auction/keeper/auction_test.go index 069fae63..2ae65224 100644 --- a/x/auction/keeper/auction_test.go +++ b/x/auction/keeper/auction_test.go @@ -226,3 +226,59 @@ func (s *KeeperTestSuite) TestValidateBundle() { }) } } + +func (s *KeeperTestSuite) TestValidateBundleTimeouts() { + s.Run("can validate valid bundle timeouts", func() { + bidder := sdk.AccAddress([]byte("bidder")) + other := sdk.AccAddress([]byte("other")) + + bidInfo := &types.BidInfo{ + Bidder: sdk.AccAddress([]byte("bidder")), + Timeout: 10, + TransactionTimeouts: []uint64{0, 10, 0}, + Signers: []map[string]struct{}{ + {other.String(): {}}, + {bidder.String(): {}}, + {other.String(): {}}, + }, + } + + err := s.auctionkeeper.ValidateBundleTimeouts(bidInfo) + s.Require().NoError(err) + }) + + s.Run("can invalidate invalid bundle timeouts", func() { + bidder := sdk.AccAddress([]byte("bidder")) + other := sdk.AccAddress([]byte("other")) + + bidInfo := &types.BidInfo{ + Bidder: sdk.AccAddress([]byte("bidder")), + Timeout: 10, + TransactionTimeouts: []uint64{0, 10, 0}, + Signers: []map[string]struct{}{ + {other.String(): {}}, + {bidder.String(): {}}, + {bidder.String(): {}}, + }, + } + + err := s.auctionkeeper.ValidateBundleTimeouts(bidInfo) + s.Require().Error(err) + }) + + s.Run("can invalidate invalid bundle timeout with single tx", func() { + bidder := sdk.AccAddress([]byte("bidder")) + + bidInfo := &types.BidInfo{ + Bidder: sdk.AccAddress([]byte("bidder")), + Timeout: 10, + TransactionTimeouts: []uint64{0}, + Signers: []map[string]struct{}{ + {bidder.String(): {}}, + }, + } + + err := s.auctionkeeper.ValidateBundleTimeouts(bidInfo) + s.Require().Error(err) + }) +} diff --git a/x/auction/types/bid_info.go b/x/auction/types/bid_info.go index 54d2627c..9a007a08 100644 --- a/x/auction/types/bid_info.go +++ b/x/auction/types/bid_info.go @@ -4,9 +4,16 @@ import sdk "github.com/cosmos/cosmos-sdk/types" // BidInfo defines the information about a bid to the auction house. type BidInfo struct { - Bidder sdk.AccAddress - Bid sdk.Coin + // Bidder is the address of the bidder. + Bidder sdk.AccAddress + // Bid is the amount of coins that the bidder is bidding. + Bid sdk.Coin + // Transactions is the bundle of transactions that the bidder is committing to. Transactions [][]byte - Timeout uint64 - Signers []map[string]struct{} + // Timeout is the block height at which the bid transaction will be executed. This must be the next block height. + Timeout uint64 + // Signers is the list of signers for each transaction in the bundle. + Signers []map[string]struct{} + // TransactionTimeouts is the list of timeouts for each transaction in the bundle. + TransactionTimeouts []uint64 }