block-sdk/tests/e2e/e2e_test.go
2023-05-23 22:09:37 +00:00

1005 lines
37 KiB
Go

//go:build e2e
package e2e
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/tests/app"
)
// TestGetBuilderParams tests the query for the builder parameters.
func (s *IntegrationTestSuite) TestGetBuilderParams() {
params := s.queryBuilderParams()
s.Require().NotNil(params)
}
// TestValidBids tests the execution of various valid auction bids. There are a few
// invariants that are tested:
//
// 1. The order of transactions in a bundle is preserved when bids are valid.
// 2. All transactions execute as expected.
// 3. The balance of the escrow account should be updated correctly.
// 4. Top of block bids will be included in block proposals before other transactions
// that are included in the same block.
func (s *IntegrationTestSuite) TestValidBids() {
// Create the accounts that will create transactions to be included in bundles
initBalance := sdk.NewInt64Coin(app.BondDenom, 10000000000)
numAccounts := 4
accounts := s.createTestAccounts(numAccounts, initBalance)
// basic send amount
defaultSendAmount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))
// auction parameters
params := s.queryBuilderParams()
reserveFee := params.ReserveFee
minBidIncrement := params.MinBidIncrement
maxBundleSize := params.MaxBundleSize
escrowAddress := params.EscrowAccountAddress
testCases := []struct {
name string
test func()
}{
{
name: "Valid auction bid",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a single transaction
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000),
}
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("Valid auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
bundleHashes[1]: true,
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
{
name: "Valid bid with multiple other transactions",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a multiple transaction that is valid
bundle := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("gud auction bid", bidTx, bundle)
// Execute a few other messages to be included in the block after the bid and bundle
normalTxs := make([][]byte, 3)
normalTxs[0] = s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000)
normalTxs[1] = s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 1, 1000)
normalTxs[2] = s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 2, 1000)
for _, tx := range normalTxs {
s.broadcastTx(tx, 0)
}
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
}
for _, hash := range bundleHashes[1:] {
expectedExecution[hash] = true
}
for _, hash := range s.normalTxsToTxHashes(normalTxs) {
expectedExecution[hash] = true
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
{
name: "iterative bidding from the same account",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a multiple transaction that is valid
bundle := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("gud auction bid 1", bidTx, bundle)
// Create another bid transaction that includes the bundle and is valid from the same account
// to verify that user can bid with the same account multiple times in the same block
bid2 := bid.Add(minBidIncrement)
bidTx2 := s.createAuctionBidTx(accounts[1], bid2, bundle, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("gud auction bid 2", bidTx2, bundle)
// Create a third bid
bid3 := bid2.Add(minBidIncrement)
bidTx3 := s.createAuctionBidTx(accounts[1], bid3, bundle, 0, height+1)
s.broadcastTx(bidTx3, 0)
s.displayExpectedBundle("gud auction bid 3", bidTx3, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle)
bundleHashes3 := s.bundleToTxHashes(bidTx3, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes2[0]: false,
bundleHashes3[0]: true,
}
for _, hash := range bundleHashes3[1:] {
expectedExecution[hash] = true
}
s.verifyBlock(height+1, bundleHashes3, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid3)
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
{
name: "bid with a bundle with transactions that are already in the mempool",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a multiple transaction that is valid
bundle := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+1)
s.displayExpectedBundle("gud auction bid", bidTx, bundle)
// Broadcast all of the transactions in the bundle to the mempool
for _, tx := range bundle {
s.broadcastTx(tx, 0)
}
// Broadcast the bid transaction
s.broadcastTx(bidTx, 0)
// Broadcast some other transactions to the mempool
normalTxs := make([][]byte, 10)
for i := 0; i < 10; i++ {
normalTxs[i] = s.createMsgSendTx(accounts[1], accounts[3].Address.String(), defaultSendAmount, uint64(i), 1000)
s.broadcastTx(normalTxs[i], 0)
}
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
}
for _, hash := range bundleHashes[1:] {
expectedExecution[hash] = true
}
for _, hash := range s.normalTxsToTxHashes(normalTxs) {
expectedExecution[hash] = true
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
{
name: "searcher attempts to include several txs in the same block to invalidate auction (we extract bid regardless)",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a multiple transaction that is valid
bundle := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
// Execute a few other messages to be included in the block after the bid and bundle
normalTxs := make([][]byte, 3)
normalTxs[0] = s.createMsgSendTx(accounts[1], accounts[1].Address.String(), defaultSendAmount, 0, 1000)
normalTxs[1] = s.createMsgSendTx(accounts[1], accounts[1].Address.String(), defaultSendAmount, 1, 1000)
normalTxs[2] = s.createMsgSendTx(accounts[1], accounts[1].Address.String(), defaultSendAmount, 2, 1000)
for _, tx := range normalTxs {
s.broadcastTx(tx, 0)
}
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
}
// The entire bundle should land irrespective of the transactions submitted by the searcher
for _, hash := range bundleHashes[1:] {
expectedExecution[hash] = true
}
// We expect only the first normal transaction to not be executed (due to a sequence number mismatch)
normalHashes := s.normalTxsToTxHashes(normalTxs)
expectedExecution[normalHashes[0]] = false
for _, hash := range normalHashes[1:] {
expectedExecution[hash] = true
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
}
for _, tc := range testCases {
s.waitForABlock()
s.Run(tc.name, tc.test)
}
}
// TestMultipleBids tests the execution of various valid auction bids in the same block. There are a few
// invariants that are tested:
//
// 1. The order of transactions in a bundle is preserved when bids are valid.
// 2. All transactions execute as expected.
// 3. The balance of the escrow account should be updated correctly.
// 4. Top of block bids will be included in block proposals before other transactions
// that are included in the same block.
// 5. If there is a block that has multiple valid bids with timeouts that are sufficiently far apart,
// the bids should be executed respecting the highest bids until the timeout is reached.
func (s *IntegrationTestSuite) TestMultipleBids() {
// Create the accounts that will create transactions to be included in bundles
initBalance := sdk.NewInt64Coin(app.BondDenom, 10000000000)
numAccounts := 4
accounts := s.createTestAccounts(numAccounts, initBalance)
// basic send amount
defaultSendAmount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))
// auction parameters
params := s.queryBuilderParams()
reserveFee := params.ReserveFee
minBidIncrement := params.MinBidIncrement
maxBundleSize := params.MaxBundleSize
escrowAddress := params.EscrowAccountAddress
testCases := []struct {
name string
test func()
}{
{
name: "broadcasting bids to two different validators (both should execute over several blocks) with same bid",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a multiple transaction that is valid
bundle := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
bundle2 := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle2[i] = s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5)
// Createa a second bid transaction that includes the bundle and is valid
bid2 := reserveFee.Add(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))
bidTx2 := s.createAuctionBidTx(accounts[3], bid2, bundle2, 0, height+5)
// Broadcast the transactions to different validators
s.broadcastTx(bidTx, 0)
s.broadcastTx(bidTx2, 1)
s.displayExpectedBundle("gud auction bid 1", bidTx, bundle)
s.displayExpectedBundle("gud auction bid 2", bidTx2, bundle2)
// Wait for both blocks to be created to verify that both bids were executed
s.waitForABlock()
s.waitForABlock()
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle2)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
bundleHashes2[0]: true,
}
for _, hash := range bundleHashes[1:] {
expectedExecution[hash] = true
}
for _, hash := range bundleHashes2[1:] {
expectedExecution[hash] = true
}
// Pass in nil since we don't know the order of transactions that ill be executed
s.verifyBlock(height+2, nil, expectedExecution)
// Ensure that the escrow account has the correct balance (both bids should have been extracted by this point)
expectedEscrowFee := s.calculateProposerEscrowSplit(bid).Add(s.calculateProposerEscrowSplit(bid2))
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
{
name: "multi-block auction bids with different bids",
test: func() {
// Get escrow account balance to ensure that it is updated correctly
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a multiple transaction that is valid
bundle := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
bundle2 := make([][]byte, maxBundleSize)
for i := 0; i < int(maxBundleSize); i++ {
bundle2[i] = s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+2)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("gud auction bid 1", bidTx, bundle)
// Create another bid transaction that includes the bundle and is valid from a different account
bid2 := bid.Add(minBidIncrement)
bidTx2 := s.createAuctionBidTx(accounts[3], bid2, bundle2, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("gud auction bid 2", bidTx2, bundle2)
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle2)
expectedExecution := map[string]bool{
bundleHashes2[0]: true,
}
for _, hash := range bundleHashes2[1:] {
expectedExecution[hash] = true
}
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
// Wait for a block to be created
s.waitForABlock()
// Ensure that the block was correctly created and executed in the order expected
expectedExecution = map[string]bool{
bundleHashes[0]: true,
}
for _, hash := range bundleHashes[1:] {
expectedExecution[hash] = true
}
s.verifyBlock(height+2, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance (both bids should have been extracted by this point)
expectedEscrowFee := s.calculateProposerEscrowSplit(bid).Add(s.calculateProposerEscrowSplit(bid2))
s.Require().Equal(escrowBalance.Add(expectedEscrowFee), s.queryBalanceOf(escrowAddress, app.BondDenom))
},
},
{
name: "Multiple bid transactions with second bid being smaller than min bid increment (same account)",
test: func() {
// Get escrow account balance
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a single transaction
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000),
}
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bid 1", bidTx, bundle)
// Create a second bid transaction that includes the bundle and is valid (but smaller than the min bid increment)
badBid := reserveFee.Add(sdk.NewInt64Coin(app.BondDenom, 10))
bidTx2 := s.createAuctionBidTx(accounts[0], badBid, bundle, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("bid 2", bidTx2, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure only the first bid was executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
bundleHashes[1]: true,
bundleHashes2[0]: false,
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
s.Require().Equal(expectedEscrowFee.Add(escrowBalance), s.queryBalanceOf(escrowAddress, app.BondDenom))
// Wait another block to make sure the second bid is not executed
s.waitForABlock()
s.verifyBlock(height+2, bundleHashes2, expectedExecution)
},
},
{
name: "Multiple transactions with second bid being smaller than min bid increment (different account)",
test: func() {
// Get escrow account balance
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a single transaction
bundle := [][]byte{
s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bid 1", bidTx, bundle)
// Create a second bid transaction that includes the bundle and is valid (but smaller than the min bid increment)
badBid := reserveFee.Add(sdk.NewInt64Coin(app.BondDenom, 10))
bidTx2 := s.createAuctionBidTx(accounts[1], badBid, bundle, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("bid 2", bidTx2, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure only the first bid was executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: true,
bundleHashes[1]: true,
bundleHashes2[0]: false,
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
s.Require().Equal(expectedEscrowFee.Add(escrowBalance), s.queryBalanceOf(escrowAddress, app.BondDenom))
// Wait another block to make sure the second bid is not executed
s.waitForABlock()
s.verifyBlock(height+2, bundleHashes2, expectedExecution)
},
},
{
name: "Multiple transactions with increasing bids but first bid has same bundle so it should fail in later block (same account)",
test: func() {
// Get escrow account balance
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a single transaction
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000),
}
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+2)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bid 1", bidTx, bundle)
// Create a second bid transaction that includes the bundle and is valid
bid2 := reserveFee.Add(minBidIncrement)
bidTx2 := s.createAuctionBidTx(accounts[0], bid2, bundle, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("bid 2", bidTx2, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure only the second bid was executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes2[0]: true,
bundleHashes2[1]: true,
}
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid2)
s.Require().Equal(expectedEscrowFee.Add(escrowBalance), s.queryBalanceOf(escrowAddress, app.BondDenom))
// Wait for a block to be created and ensure that the first bid was not executed
s.waitForABlock()
s.verifyBlock(height+2, bundleHashes, expectedExecution)
},
},
{
name: "Multiple transactions with increasing bids but first bid has same bundle so it should fail in later block (different account)",
test: func() {
// Get escrow account balance
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a single transaction
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+2)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bid 1", bidTx, bundle)
// Create a second bid transaction that includes the bundle and is valid
bid2 := reserveFee.Add(minBidIncrement)
bidTx2 := s.createAuctionBidTx(accounts[1], bid2, bundle, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("bid 2", bidTx2, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure only the second bid was executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes2[0]: true,
bundleHashes2[1]: true,
}
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid2)
s.Require().Equal(expectedEscrowFee.Add(escrowBalance), s.queryBalanceOf(escrowAddress, app.BondDenom))
// Wait for a block to be created and ensure that the first bid was not executed
s.waitForABlock()
s.verifyBlock(height+2, bundleHashes, expectedExecution)
},
},
{
name: "Multiple transactions with increasing bids and different bundles (one should execute)",
test: func() {
// Get escrow account balance
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Create a bundle with a single transaction
firstBundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bundle with a single transaction
secondBundle := [][]byte{
s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bid transaction that includes the bundle and is valid
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[2], bid, firstBundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bid 1", bidTx, firstBundle)
// Create a second bid transaction that includes the bundle and is valid
bid2 := reserveFee.Add(minBidIncrement)
bidTx2 := s.createAuctionBidTx(accounts[3], bid2, secondBundle, 0, height+1)
s.broadcastTx(bidTx2, 0)
s.displayExpectedBundle("bid 2", bidTx2, secondBundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure only the second bid was executed
bundleHashes := s.bundleToTxHashes(bidTx, firstBundle)
bundleHashes2 := s.bundleToTxHashes(bidTx2, secondBundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
bundleHashes2[0]: true,
bundleHashes2[1]: true,
}
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
// Ensure that the escrow account has the correct balance
expectedEscrowFee := s.calculateProposerEscrowSplit(bid2)
s.Require().Equal(expectedEscrowFee.Add(escrowBalance), s.queryBalanceOf(escrowAddress, app.BondDenom))
// Wait for a block to be created and ensure that the second bid is executed
s.waitForABlock()
s.verifyBlock(height+2, bundleHashes, expectedExecution)
},
},
}
for _, tc := range testCases {
s.waitForABlock()
s.Run(tc.name, tc.test)
}
}
// TestInvalidBundles tests that the application correctly rejects invalid bundles. The balance of the escrow
// account should not be updated and bid + transactions in the bundle should not be executed unless if the transactions
// in the bundle were already in the mempool.
func (s *IntegrationTestSuite) TestInvalidBids() {
// Create the accounts that will create transactions to be included in bundles
initBalance := sdk.NewInt64Coin(app.BondDenom, 10000000000)
numAccounts := 4
accounts := s.createTestAccounts(numAccounts, initBalance)
// basic send amount
defaultSendAmount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))
// auction parameters
params := s.queryBuilderParams()
reserveFee := params.ReserveFee
maxBundleSize := params.MaxBundleSize
escrowAddress := params.EscrowAccountAddress
testCases := []struct {
name string
test func()
}{
{
name: "searcher is attempting to submit a bundle that includes another bid tx",
test: func() {
// Create a bundle with a multiple transaction that is valid
bundle := [][]byte{
s.createAuctionBidTx(accounts[0], reserveFee, nil, 0, 1000),
}
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Create a bid transaction that includes the bundle
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bad auction bid", bidTx, bundle)
// Ensure that the block was built correctly and that the bid was not executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "Invalid bid that is attempting to bid more than their balance",
test: func() {
// Create a bundle with a single transaction that is valid
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bid transaction that includes the bundle that is attempting to bid more than their balance
bid := sdk.NewCoin(app.BondDenom, sdk.NewInt(999999999999999999))
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("bad auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
}
// Ensure that the block was built correctly and that the bid was not executed
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "Invalid bid that is attempting to front-run/sandwich",
test: func() {
// Create a front-running bundle
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, 0, 1000),
s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bid transaction that includes the bundle
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("front-running auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
bundleHashes[2]: false,
bundleHashes[3]: false,
}
// Ensure that the block was built correctly and that the bid was not executed
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "Invalid bid that includes an invalid bundle tx",
test: func() {
// Create a bundle with a single transaction that is invalid (sequence number is wrong)
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1000, 1000),
}
// Create a bid transaction that includes the bundle
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("invalid auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
}
// Ensure that the block was built correctly and that the bid was not executed
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "invalid auction bid with a bid smaller than the reserve fee",
test: func() {
// Create a bundle with a single transaction (this should not be included in the block proposal)
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000),
}
// Create a bid transaction that includes a bid that is smaller than the reserve fee
bid := reserveFee.Sub(sdk.NewInt64Coin(app.BondDenom, 1))
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("invalid auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure that no transactions were executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "invalid auction bid with too many transactions in the bundle",
test: func() {
// Create a bundle with too many transactions
bundle := [][]byte{}
for i := 0; i < int(maxBundleSize)+1; i++ {
bundle = append(bundle, s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i+1), 1000))
}
// Create a bid transaction that includes the bundle
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("invalid auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure that no transactions were executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := make(map[string]bool)
for _, hash := range bundleHashes {
expectedExecution[hash] = false
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "invalid auction bid that has an invalid timeout",
test: func() {
// Create a bundle with a single transaction
bundle := [][]byte{
s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000),
}
// Create a bid transaction that includes the bundle and has a bad timeout
bid := reserveFee
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height)
s.broadcastTx(bidTx, 0)
s.displayExpectedBundle("invalid auction bid", bidTx, bundle)
// Wait for a block to be created
s.waitForABlock()
// Ensure that no transactions were executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := map[string]bool{
bundleHashes[0]: false,
bundleHashes[1]: false,
}
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
{
name: "invalid bid that includes valid transactions that are in the mempool (only bundled txs should execute)",
test: func() {
// Create a bundle with multiple transactions
bundle := make([][]byte, 3)
for i := 0; i < 3; i++ {
bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000)
}
// Create a bid transaction that includes the bundle and is invalid
bid := reserveFee.Sub(sdk.NewInt64Coin(app.BondDenom, 1))
height := s.queryCurrentHeight()
bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1)
s.displayExpectedBundle("invalid auction bid", bidTx, bundle)
// Wait for a block to ensure all transactions are included in the same block
s.waitForABlock()
// Broadcast all of the transactions in the bundle
for _, tx := range bundle {
s.broadcastTx(tx, 0)
}
// Broadcast the bid transaction
s.broadcastTx(bidTx, 0)
// Wait for a block to be created
s.waitForABlock()
// Ensure that only the transactions in the bundle were executed
bundleHashes := s.bundleToTxHashes(bidTx, bundle)
expectedExecution := make(map[string]bool)
for _, hash := range bundleHashes {
expectedExecution[hash] = true
}
expectedExecution[bundleHashes[0]] = false
s.verifyBlock(height+1, bundleHashes, expectedExecution)
},
},
}
for _, tc := range testCases {
escrowBalance := s.queryBalanceOf(escrowAddress, app.BondDenom)
// Wait for a block to be created and run the test
s.waitForABlock()
s.Run(tc.name, tc.test)
// Get escrow account balance to ensure that it is not changed
s.Require().Equal(escrowBalance, s.queryBalanceOf(escrowAddress, app.BondDenom))
}
}