From 04f3862ecbb70122236064c185d715465924a817 Mon Sep 17 00:00:00 2001 From: David Terpay <35130517+davidterpay@users.noreply.github.com> Date: Tue, 13 Jun 2023 08:52:10 -0400 Subject: [PATCH] feat(bb): Multi-lane testing + docs (#174) --- blockbuster/README.md | 355 +++++++++++++++++++++++++++++ blockbuster/lanes/auction/abci.go | 7 +- blockbuster/lanes/base/abci.go | 7 +- tests/e2e/e2e_test.go | 358 ++++++++++++++++++++++++------ tests/e2e/e2e_tx_test.go | 14 +- 5 files changed, 662 insertions(+), 79 deletions(-) create mode 100644 blockbuster/README.md diff --git a/blockbuster/README.md b/blockbuster/README.md new file mode 100644 index 0000000..fb2236f --- /dev/null +++ b/blockbuster/README.md @@ -0,0 +1,355 @@ +# BlockBuster + +> 📕 BlockBuster is an app-side mempool + set of proposal handlers that allows +developers to configure modular lanes of transactions in their blocks with +distinct validation/ordering logic. **BlockBuster is the ultimate highway +system for transactions.** + +## High Level Overview + +**`BlockBuster`** is a framework for creating modular, application specific +mempools by **separating transactions into “lanes” with custom transaction handling.** + +You can think of BlockBuster as a **transaction highway system**, where each +lane on the highway serves a specific purpose and has its own set of rules and +traffic flow. + +Similarly, **BlockBuster** redefines block-space into **`lanes`** - where each +`lane` has its own set of rules and transaction flow management systems. + +* A lane is what we might traditionally consider to be a standard mempool +where transaction ***validation***, ***ordering*** and ***prioritization*** for +contained transactions are shared. +* Lanes implement a **standard interface** that allows each individual lane to +propose and validate a portion of a block. +* Lanes are ordered with each other, configurable by developers. All lanes +together define the desired block structure of a chain. + +## BlockBuster Use Cases + +A mempool with separate `lanes` can be used for: + +1. **MEV mitigation**: a top of block lane could be designed to create an +in-protocol top-of-block auction (as we are doing with POB) to recapture MEV +in a transparent and governable way. +2. **Free/reduced fee txs**: transactions with certain properties (e.g. +from trusted accounts or performing encouraged actions) could leverage a +free lane to reward behavior. +3. **Dedicated oracle space** Oracles could be included before other kinds +of transactions to ensure that price updates occur first, and are not able +to be sandwiched or manipulated. +4. **Orderflow auctions**: an OFA lane could be constructed such that order +flow providers can have their submitted transactions bundled with specific +backrunners, to guarantee MEV rewards are attributed back to users. +Imagine MEV-share but in protocol. +5. **Enhanced and customizable privacy**: privacy-enhancing features could +be introduced, such as threshold encrypted lanes, to protect user data and + maintain privacy for specific use cases. +6. **Fee market improvements**: one or many fee markets - such as EIP-1559 - +could be easily adopted for different lanes (potentially custom for certain +dApps). Each smart contract/exchange could have its own fee market or auction +for transaction ordering. +7. **Congestion management**: segmentation of transactions to lanes can help +mitigate network congestion by capping usage of certain applications and +tailoring fee markets. + +## BlockBuster Design + +BlockBuster is a mempool composed of sub-mempools called **lanes**. All +lanes together define the transaction highway system and BlockBuster mempool. +When instantiating the BlockBuster mempool, developers will define all of the +desired lanes and their configurations (including lane ordering). + +Utilizing BlockBuster is a simple three step process: + +* Determine the lanes desired. Currently, POB supports three different +implementations of lanes: top of block lane, free lane, and a default lane. + 1. Top of block lane allows the top of every block to be auctioned off + and constructed using logic defined by the `x/builder` module. + 2. Free lane allows base app to not charge certain types of transactions + any fees. For example, delegations and/or re-delegations might be charged no + fees. What qualifies as a free transaction is determined + [here](https://github.com/skip-mev/pob/blob/main/blockbuster/lanes/free/factory.go). + 3. Default lane accepts all other transactions and is considered to be + analogous to how mempools and proposals are constructed today. +* Instantiate the mempool in base app. + +```go +mempool := blockbuster.NewMempool(lanes...) +app.App.SetMempool(mempool) +``` + +* Instantiate the BlockBuster proposal handlers in base app. + +```go +proposalHandlers := abci.NewProposalHandler( + app.Logger(), + app.txConfig.TxDecoder(), + mempool, // BlockBuster mempool +) +app.App.SetPrepareProposal(proposalHandlers.PrepareProposalHandler()) +app.App.SetProcessProposal(proposalHandlers.ProcessProposalHandler()) +``` + +***Note: BlockBuster should configure a `DefaultLane` that accepts transactions +that do not belong to any other lane.*** + +Transactions are inserted into the first lane that the transaction matches to. +This means that a given transaction should really only belong to one lane +(but this isn’t enforced). + +### Proposals + +The ordering of lanes when initializing BlockBuster in base app will determine +the ordering of how proposals are built. For example, say that we instantiate +three lanes: + +1. Top of block lane +2. Free lane +3. Default lane + +#### Preparing Proposals + +When the current proposer starts building a block, it will first populate the +proposal with transactions from the top of block lane, followed by free and +default lane. Each lane proposes its own set of transactions using the lane’s +`PrepareLane` (analogous to `PrepareProposal`). Each lane has a limit on the +relative percentage of total block space that the lane can consume. +For example, the free lane might be configured to only make up 10% of any +block. This is defined on each lane’s `Config` when it is instantiated. + +In the case when any lane fails to propose its portion of the block, it will +be skipped and the next lane in the set of lanes will propose its portion of +the block. Failures of partial block proposals are independent of one another. + +#### Processing Proposals + +Block proposals are validated iteratively following the exact ordering of lanes +defined on base app. Transactions included in block proposals must respect the +ordering of lanes. Any proposal that includes transactions that are out of +order relative to the ordering of lanes will be rejected. Following the +example defined above, if a proposal contains the following transactions: + +1. Default transaction (belonging to the default lane) +2. Top of block transaction (belonging to the top of block lane) +3. Free transaction (belonging to the free lane) + +It will be rejected because it does not respect the lane ordering. + +The BlockBuster `ProcessProposalHandler` processes the proposal by verifying +all transactions in the proposal according to each lane's verification logic +in a greedy fashion. If a lane's portion of the proposal is invalid, we +reject the proposal. After a lane's portion of the proposal is verified, we +pass the remaining transactions to the next lane in the chain. + +#### Coming Soon + +BlockBuster will have its own dedicated gRPC service for searchers, wallets, +and users that allows them to query what lane their transaction might belong +in, what fees they might have to pay for a given transaction, and the general +state of the BlockBuster mempool. + +### Lanes + +Each lane will define its own: + +1. Unique prioritization/ordering mechanism i.e. how will transactions from a +given lane be ordered in a block / mempool. +2. Inclusion function to determine what types of transactions belong in the lane. +3. Unique block building/verification mechanism. + +The general interface that each lane must implement can be found [here](https://github.com/skip-mev/pob/blob/main/blockbuster/lane.go): + +```go +// Lane defines an interface used for block construction +Lane interface { + sdkmempool.Mempool + + // Name returns the name of the lane. + Name() string + + // Match determines if a transaction belongs to this lane. + Match(tx sdk.Tx) bool + + // VerifyTx verifies the transaction belonging to this lane. + VerifyTx(ctx sdk.Context, tx sdk.Tx) error + + // Contains returns true if the mempool contains the given transaction. + Contains(tx sdk.Tx) (bool, error) + + // PrepareLane builds a portion of the block. It inputs the maxTxBytes that + // can be included in the proposal for the given lane, the partial + // proposal, and a function to call the next lane in the chain. The + // next lane in the chain will be called with the updated proposal and context. + PrepareLane( + ctx sdk.Context, + proposal BlockProposal, + maxTxBytes int64, + next PrepareLanesHandler, + ) (BlockProposal, error) + + // ProcessLaneBasic validates that transactions belonging to this lane are + // not misplaced in the block proposal. + ProcessLaneBasic(txs []sdk.Tx) error + + // ProcessLane verifies this lane's portion of a proposed block. It inputs + // the transactions that may belong to this lane and a function to call + // the next lane in the chain. The next lane in the chain will be + // called with the updated context and filtered down transactions. + ProcessLane( + ctx sdk.Context, + proposalTxs []sdk.Tx, + next ProcessLanesHandler, + ) (sdk.Context, error) + + // SetAnteHandler sets the lane's antehandler. + SetAnteHandler(antehander sdk.AnteHandler) + + // Logger returns the lane's logger. + Logger() log.Logger + + // GetMaxBlockSpace returns the max block space for the lane as a relative percentage. + GetMaxBlockSpace() sdk.Dec +} +``` + +### 1. Intra-lane Transaction Ordering + +**Note: Lanes must implement the `sdk.Mempool` interface.** + +Transactions within a lane are ordered in a proposal respecting the ordering +defined on the lane’s mempool. Developers can define their own custom ordering +by implementing a custom `TxPriority` struct that allows the lane’s mempool to +determine the priority of a transaction `GetTxPriority` and relatively order +two transactions given the priority `Compare`. The top of block lane includes +an custom `TxPriority` that orders transactions in the mempool based on their +bid. + +```go +func TxPriority(config Factory) blockbuster.TxPriority[string] { + return blockbuster.TxPriority[string]{ + GetTxPriority: func(goCtx context.Context, tx sdk.Tx) string { + bidInfo, err := config.GetAuctionBidInfo(tx) + if err != nil { + panic(err) + } + + return bidInfo.Bid.String() + }, + Compare: func(a, b string) int { + aCoins, _ := sdk.ParseCoinsNormalized(a) + bCoins, _ := sdk.ParseCoinsNormalized(b) + + switch { + case aCoins == nil && bCoins == nil: + return 0 + + case aCoins == nil: + return -1 + + case bCoins == nil: + return 1 + + default: + switch { + case aCoins.IsAllGT(bCoins): + return 1 + + case aCoins.IsAllLT(bCoins): + return -1 + + default: + return 0 + } + } + }, + MinValue: "", + } +} + +// NewMempool returns a new auction mempool. +func NewMempool(txEncoder sdk.TxEncoder, maxTx int, config Factory) *TOBMempool { + return &TOBMempool{ + index: blockbuster.NewPriorityMempool( + blockbuster.PriorityNonceMempoolConfig[string]{ + TxPriority: TxPriority(config), + MaxTx: maxTx, + }, + ), + txEncoder: txEncoder, + txIndex: make(map[string]struct{}), + Factory: config, + } +} +``` + +### 2. [Optional] Transaction Information Retrieval + +Each lane can define a factory that configures the necessary set of interfaces +required for transaction processing, ordering, and validation. Lanes are +designed such that any given chain can adopt upstream `POB` lanes as long as +developers implement the specified interface(s) associated with transaction +information retrieval for that lane. + +***A standard cosmos chain or EVM chain can then implement their own versions +of these interfaces and automatically utilize the lane with no changes upstream!*** + +For example, the free lane defines an `Factory` that includes a single +`IsFreeTx` function that allows developers to configure what is a free +transaction. The default implementation categorizes free transactions as any +transaction that includes a delegate type message. + +```go +// IsFreeTx defines a default function that checks if a transaction is free. In +// this case, any transaction that is a delegation/redelegation transaction is free. +func (config *DefaultFreeFactory) IsFreeTx(tx sdk.Tx) bool { + for _, msg := range tx.GetMsgs() { + switch msg.(type) { + case *types.MsgDelegate: + return true + case *types.MsgBeginRedelegate: + return true + case *types.MsgCancelUnbondingDelegation: + return true + } + } + + return false +} +``` + +### 3. Lane Inclusion Functionality + +Lanes must implement a `Match` interface which determines whether a transaction +should be considered for a given lane. Developer’s are encouraged to utilize the + same interfaces defined in the `Factory` to match transactions to lanes. For + example, developers might configure a top of block auction lane to accept + transactions if they contain a single `MsgAuctionBid` message in the transaction. + +### 4.1. [Optional] Transaction Validation + +Transactions will be verified the lane’s `VerifyTx` function. This logic can be +completely arbitrary. For example, the default lane verifies transactions +using base app’s `AnteHandler` while the top of block lane verifies transactions +by extracting all bundled transactions included in the bid transaction and then +verifying the transaction iteratively given the bundle. + +### 4.2. Block Building/Verification Logic + +Each lane will implement block building and verification logic - analogous to +`Prepare` and `Process` proposal - that is unique to itself. + +* `PrepareLane` will be in charge of building a partial block given the +transactions in the lane. +* `ProcessLaneBasic` ensures that transactions that should be included in the +current lane are not interleaved with other lanes i.e. transactions in +proposals are ordered respecting the ordering of lanes. +* `ProcessLane` will be in charge of verifying the lane’s partial block. + +### Inheritance + +Lanes can inherit the underlying implementation of other lanes and overwrite +any part of the implementation with their own custom functionality. We +recommend that user’s extend the functionality of the `Base` lane when first +exploring the code base. + diff --git a/blockbuster/lanes/auction/abci.go b/blockbuster/lanes/auction/abci.go index fc876c3..45d8d2b 100644 --- a/blockbuster/lanes/auction/abci.go +++ b/blockbuster/lanes/auction/abci.go @@ -32,7 +32,7 @@ selectBidTxLoop: cacheCtx, write := ctx.CacheContext() tmpBidTx := bidTxIterator.Tx() - bidTxBz, _, err := utils.GetTxHashStr(l.Cfg.TxEncoder, tmpBidTx) + bidTxBz, hash, err := utils.GetTxHashStr(l.Cfg.TxEncoder, tmpBidTx) if err != nil { txsToRemove[tmpBidTx] = struct{}{} continue selectBidTxLoop @@ -47,6 +47,11 @@ selectBidTxLoop: if bidTxSize <= maxTxBytes { // Verify the bid transaction and all of its bundled transactions. if err := l.VerifyTx(cacheCtx, tmpBidTx); err != nil { + l.Logger().Info( + "failed to verify auction bid tx", + "tx_hash", hash, + "err", err, + ) txsToRemove[tmpBidTx] = struct{}{} continue selectBidTxLoop } diff --git a/blockbuster/lanes/base/abci.go b/blockbuster/lanes/base/abci.go index 08a6db2..825be1e 100644 --- a/blockbuster/lanes/base/abci.go +++ b/blockbuster/lanes/base/abci.go @@ -29,7 +29,7 @@ func (l *DefaultLane) PrepareLane( for iterator := l.Mempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { tx := iterator.Tx() - txBytes, _, err := utils.GetTxHashStr(l.Cfg.TxEncoder, tx) + txBytes, hash, err := utils.GetTxHashStr(l.Cfg.TxEncoder, tx) if err != nil { txsToRemove[tx] = struct{}{} continue @@ -48,6 +48,11 @@ func (l *DefaultLane) PrepareLane( // Verify the transaction. if err := l.VerifyTx(ctx, tx); err != nil { + l.Logger().Info( + "failed to verify tx", + "tx_hash", hash, + "err", err, + ) txsToRemove[tx] = struct{}{} continue } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index b5284d8..61abcae 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -37,6 +37,10 @@ func (s *IntegrationTestSuite) TestValidBids() { maxBundleSize := params.MaxBundleSize escrowAddress := params.EscrowAccountAddress + // standard tx params + gasLimit := uint64(5000000) + fees := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(150000))) + testCases := []struct { name string test func() @@ -49,13 +53,13 @@ func (s *IntegrationTestSuite) TestValidBids() { // Create a bundle with a single transaction bundle := [][]byte{ - s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("Valid auction bid", bidTx, bundle) @@ -84,7 +88,7 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // Wait for a block to ensure all transactions are included in the same block @@ -93,15 +97,15 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) 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) + normalTxs[0] = s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees) + normalTxs[1] = s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 1, 1000, gasLimit, fees) + normalTxs[2] = s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 2, 1000, gasLimit, fees) for _, tx := range normalTxs { s.broadcastTx(tx, 0) @@ -140,7 +144,7 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // Wait for a block to ensure all transactions are included in the same block @@ -149,20 +153,20 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[1], bid2, bundle, 0, height+1, gasLimit, fees) 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) + bidTx3 := s.createAuctionBidTx(accounts[1], bid3, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx3, 0) s.displayExpectedBundle("gud auction bid 3", bidTx3, bundle) @@ -199,7 +203,7 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // Wait for a block to ensure all transactions are included in the same block @@ -208,7 +212,7 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+1, gasLimit, fees) s.displayExpectedBundle("gud auction bid", bidTx, bundle) // Broadcast all of the transactions in the bundle to the mempool @@ -222,7 +226,7 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + normalTxs[i] = s.createMsgSendTx(accounts[1], accounts[3].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) s.broadcastTx(normalTxs[i], 0) } @@ -259,7 +263,7 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // Wait for a block to ensure all transactions are included in the same block @@ -268,14 +272,14 @@ func (s *IntegrationTestSuite) TestValidBids() { // 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) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) 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) + normalTxs[0] = s.createMsgSendTx(accounts[1], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees) + normalTxs[1] = s.createMsgSendTx(accounts[1], accounts[1].Address.String(), defaultSendAmount, 1, 1000, gasLimit, fees) + normalTxs[2] = s.createMsgSendTx(accounts[1], accounts[1].Address.String(), defaultSendAmount, 2, 1000, gasLimit, fees) for _, tx := range normalTxs { s.broadcastTx(tx, 0) @@ -343,6 +347,10 @@ func (s *IntegrationTestSuite) TestMultipleBids() { maxBundleSize := params.MaxBundleSize escrowAddress := params.EscrowAccountAddress + // standard tx params + gasLimit := uint64(5000000) + fees := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(150000))) + testCases := []struct { name string test func() @@ -356,22 +364,22 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } 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) + bundle2[i] = s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // 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) + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5, gasLimit, fees) // 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) + bidTx2 := s.createAuctionBidTx(accounts[3], bid2, bundle2, 0, height+5, gasLimit, fees) // Wait for a block to ensure all transactions are included in the same block s.waitForABlock() @@ -421,12 +429,12 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } 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) + bundle2[i] = s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // Wait for a block to ensure all transactions are included in the same block @@ -435,13 +443,13 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // 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) + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+2, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[3], bid2, bundle2, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx2, 0) s.displayExpectedBundle("gud auction bid 2", bidTx2, bundle2) @@ -488,19 +496,19 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // Create a bundle with a single transaction bundle := [][]byte{ - s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[0], badBid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx2, 0) s.displayExpectedBundle("bid 2", bidTx2, bundle) @@ -534,19 +542,19 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // Create a bundle with a single transaction bundle := [][]byte{ - s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000), + s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[1], badBid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx2, 0) s.displayExpectedBundle("bid 2", bidTx2, bundle) @@ -580,19 +588,19 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // Create a bundle with a single transaction bundle := [][]byte{ - s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+2, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[0], bid2, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx2, 0) s.displayExpectedBundle("bid 2", bidTx2, bundle) @@ -626,19 +634,19 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // Create a bundle with a single transaction bundle := [][]byte{ - s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+2, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[1], bid2, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx2, 0) s.displayExpectedBundle("bid 2", bidTx2, bundle) @@ -672,24 +680,24 @@ func (s *IntegrationTestSuite) TestMultipleBids() { // Create a bundle with a single transaction firstBundle := [][]byte{ - s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // Create a bundle with a single transaction secondBundle := [][]byte{ - s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, 0, 1000), + s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[2], bid, firstBundle, 0, height+1, gasLimit, fees) 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) + bidTx2 := s.createAuctionBidTx(accounts[3], bid2, secondBundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx2, 0) s.displayExpectedBundle("bid 2", bidTx2, secondBundle) @@ -742,6 +750,10 @@ func (s *IntegrationTestSuite) TestInvalidBids() { maxBundleSize := params.MaxBundleSize escrowAddress := params.EscrowAccountAddress + // standard tx params + gasLimit := uint64(5000000) + fees := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(150000))) + testCases := []struct { name string test func() @@ -751,7 +763,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() { test: func() { // Create a bundle with a multiple transaction that is valid bundle := [][]byte{ - s.createAuctionBidTx(accounts[0], reserveFee, nil, 0, 1000), + s.createAuctionBidTx(accounts[0], reserveFee, nil, 0, 1000, gasLimit, fees), } // Wait for a block to ensure all transactions are included in the same block @@ -760,7 +772,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() { // Create a bid transaction that includes the bundle bid := reserveFee height := s.queryCurrentHeight() - bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("bad auction bid", bidTx, bundle) @@ -779,13 +791,13 @@ func (s *IntegrationTestSuite) TestInvalidBids() { 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), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("bad auction bid", bidTx, bundle) @@ -807,15 +819,15 @@ func (s *IntegrationTestSuite) TestInvalidBids() { 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), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), + s.createMsgSendTx(accounts[1], accounts[0].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), + s.createMsgSendTx(accounts[2], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // Create a bid transaction that includes the bundle bid := reserveFee height := s.queryCurrentHeight() - bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("front-running auction bid", bidTx, bundle) @@ -839,13 +851,13 @@ func (s *IntegrationTestSuite) TestInvalidBids() { 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), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1000, 1000, gasLimit, fees), } // Create a bid transaction that includes the bundle bid := reserveFee height := s.queryCurrentHeight() - bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("invalid auction bid", bidTx, bundle) @@ -867,13 +879,13 @@ func (s *IntegrationTestSuite) TestInvalidBids() { 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), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 1, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("invalid auction bid", bidTx, bundle) @@ -896,13 +908,13 @@ func (s *IntegrationTestSuite) TestInvalidBids() { // 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)) + bundle = append(bundle, s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i+1), 1000, gasLimit, fees)) } // Create a bid transaction that includes the bundle bid := reserveFee height := s.queryCurrentHeight() - bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height+1, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("invalid auction bid", bidTx, bundle) @@ -924,13 +936,13 @@ func (s *IntegrationTestSuite) TestInvalidBids() { test: func() { // Create a bundle with a single transaction bundle := [][]byte{ - s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000), + s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, 0, 1000, gasLimit, fees), } // 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) + bidTx := s.createAuctionBidTx(accounts[0], bid, bundle, 0, height, gasLimit, fees) s.broadcastTx(bidTx, 0) s.displayExpectedBundle("invalid auction bid", bidTx, bundle) @@ -953,13 +965,13 @@ func (s *IntegrationTestSuite) TestInvalidBids() { // 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) + bundle[i] = s.createMsgSendTx(accounts[0], accounts[1].Address.String(), defaultSendAmount, uint64(i), 1000, gasLimit, fees) } // 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) + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+1, gasLimit, fees) s.displayExpectedBundle("invalid auction bid", bidTx, bundle) // Wait for a block to ensure all transactions are included in the same block @@ -1017,6 +1029,10 @@ func (s *IntegrationTestSuite) TestFreeLane() { defaultStakeAmount := sdk.NewCoin(app.BondDenom, sdk.NewInt(10)) defaultSendAmountCoins := sdk.NewCoins(defaultSendAmount) + // standard tx params + gasLimit := uint64(5000000) + fees := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(150000))) + testCases := []struct { name string test func() @@ -1029,7 +1045,7 @@ func (s *IntegrationTestSuite) TestFreeLane() { // basic stake amount validators := s.queryValidators() validator := validators[0] - tx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000) + tx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, fees) // Broadcast the transaction s.waitForABlock() @@ -1052,10 +1068,10 @@ func (s *IntegrationTestSuite) TestFreeLane() { // basic free transaction validators := s.queryValidators() validator := validators[0] - freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000) + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, fees) // other normal transaction - normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000) + normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees) // Broadcast the transactions s.waitForABlock() @@ -1093,10 +1109,10 @@ func (s *IntegrationTestSuite) TestFreeLane() { // basic free transaction validators := s.queryValidators() validator := validators[0] - freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000) + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, fees) // other normal transaction - freeTx2 := s.createMsgDelegateTx(accounts[1], validator.OperatorAddress, defaultStakeAmount, 0, 1000) + freeTx2 := s.createMsgDelegateTx(accounts[1], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, fees) // Broadcast the transactions s.waitForABlock() @@ -1138,28 +1154,84 @@ func (s *IntegrationTestSuite) TestLanes() { params := s.queryBuilderParams() reserveFee := params.ReserveFee + // standard tx params + gasLimit := uint64(5000000) + fees := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(150000))) + testCases := []struct { name string test func() }{ + { + "block with tob, free, and normal tx (free tx delegates entire balances)", + func() { + // basic free transaction + validators := s.queryValidators() + validator := validators[0] + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, initBalance, 0, 1000, gasLimit, sdk.NewCoins()) + + // other normal transaction + normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees) + + // Create a bid transaction that includes the bundle and is valid + bundle := [][]byte{ + s.createMsgSendTx(accounts[3], accounts[1].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees), + } + bid := reserveFee + height := s.queryCurrentHeight() + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5, gasLimit, fees) + s.displayExpectedBundle("Valid auction bid", bidTx, bundle) + + // Broadcast the transactions + s.waitForABlock() + s.broadcastTx(freeTx, 0) + s.broadcastTx(normalTx, 0) + s.broadcastTx(bidTx, 0) + + // Wait for a block to be created + s.waitForABlock() + + // Ensure that the transaction was executed + height = s.queryCurrentHeight() + hashes := s.normalTxsToTxHashes([][]byte{ + bidTx, + bundle[0], + freeTx, + normalTx, + }) + + expectedExecution := map[string]bool{ + hashes[0]: true, + hashes[1]: true, + hashes[2]: true, + hashes[3]: true, + } + + // Ensure that the block was built correctly + s.verifyBlock(height, hashes, expectedExecution) + + // Reset the balances + accounts = s.createTestAccounts(numAccounts, initBalance) + }, + }, { name: "block with tob, free, and normal tx", test: func() { // basic free transaction validators := s.queryValidators() validator := validators[0] - freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000) + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, fees) // other normal transaction - normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000) + normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees) // Create a bid transaction that includes the bundle and is valid bundle := [][]byte{ - s.createMsgSendTx(accounts[3], accounts[1].Address.String(), defaultSendAmountCoins, 0, 1000), + s.createMsgSendTx(accounts[3], accounts[1].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees), } bid := reserveFee height := s.queryCurrentHeight() - bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5) + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5, gasLimit, fees) s.displayExpectedBundle("Valid auction bid", bidTx, bundle) // Broadcast the transactions @@ -1186,6 +1258,152 @@ func (s *IntegrationTestSuite) TestLanes() { hashes[3]: true, } + // Ensure that the block was built correctly + s.verifyBlock(height, hashes, expectedExecution) + }, + }, + { + "failing top of block transaction, free, and normal tx", + func() { + // basic free transaction + validators := s.queryValidators() + validator := validators[0] + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, fees) + + // other normal transaction + normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees) + + // Create a bid transaction that includes the bundle and is invalid (out of sequence number) + bundle := [][]byte{ + s.createMsgSendTx(accounts[3], accounts[1].Address.String(), defaultSendAmountCoins, 3, 1000, gasLimit, fees), + } + bid := reserveFee + height := s.queryCurrentHeight() + bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5, gasLimit, fees) + s.displayExpectedBundle("Valid auction bid", bidTx, bundle) + + // Broadcast the transactions + s.waitForABlock() + s.broadcastTx(freeTx, 0) + s.broadcastTx(normalTx, 0) + s.broadcastTx(bidTx, 0) + + // Wait for a block to be created + s.waitForABlock() + height = s.queryCurrentHeight() + + // Ensure that the transaction was executed + hashes := s.normalTxsToTxHashes([][]byte{ + bidTx, + bundle[0], + freeTx, + normalTx, + }) + expectedExecution := map[string]bool{ + hashes[0]: false, + hashes[1]: false, + hashes[2]: true, + hashes[3]: true, + } + + // Ensure that the block was built correctly + s.verifyBlock(height, hashes[2:], expectedExecution) + }, + }, + { + "top of block transaction that includes transactions from the free lane (no fees paid)", + func() { + // basic free transaction + validators := s.queryValidators() + validator := validators[0] + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(0)))) // Free transaction with no fees + + // Create a bid transaction that includes the bundle and is invalid (out of sequence number) + bundle := [][]byte{ + freeTx, + s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 1, 1000, gasLimit, fees), + } + bid := reserveFee + height := s.queryCurrentHeight() + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+5, gasLimit, fees) + s.displayExpectedBundle("Valid auction bid", bidTx, bundle) + + // Broadcast the transactions + s.waitForABlock() + s.broadcastTx(freeTx, 0) + s.broadcastTx(bidTx, 0) + + // Wait for a block to be created + s.waitForABlock() + height = s.queryCurrentHeight() + + // Ensure that the transaction was executed + hashes := s.normalTxsToTxHashes([][]byte{ + bidTx, + freeTx, + bundle[1], + }) + expectedExecution := map[string]bool{ + hashes[0]: true, + hashes[1]: true, + hashes[2]: true, + } + + // Ensure that the block was built correctly + s.verifyBlock(height, hashes, expectedExecution) + }, + }, + { + "top of block transaction that includes transaction from free lane + other free lane txs + normal txs", + func() { + // basic free transaction + validators := s.queryValidators() + validator := validators[0] + freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000, gasLimit, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(0)))) // Free transaction with no fees + + // Another free transaction that should be included in the block + freeTx2 := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 1, 1000, gasLimit, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(0)))) // Free transaction with no fees + + // Create a bid transaction that includes the bundle and is invalid (out of sequence number) + bundle := [][]byte{ + freeTx, + s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 1, 1000, gasLimit, fees), + } + bid := reserveFee + height := s.queryCurrentHeight() + bidTx := s.createAuctionBidTx(accounts[1], bid, bundle, 0, height+5, gasLimit, fees) + s.displayExpectedBundle("Valid auction bid", bidTx, bundle) + + normalTx := s.createMsgSendTx(accounts[3], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000, gasLimit, fees) + + // Broadcast the transactions (including the ones in the bundle) + s.waitForABlock() + s.broadcastTx(bidTx, 0) + s.broadcastTx(freeTx, 0) + s.broadcastTx(bundle[1], 0) + s.broadcastTx(freeTx2, 0) + s.broadcastTx(normalTx, 0) + + // Wait for a block to be created + s.waitForABlock() + height = s.queryCurrentHeight() + + // Ensure that the transaction was executed + hashes := s.normalTxsToTxHashes([][]byte{ + bidTx, + freeTx, + bundle[1], + freeTx2, + normalTx, + }) + expectedExecution := map[string]bool{ + hashes[0]: true, + hashes[1]: true, + hashes[2]: true, + hashes[3]: true, + hashes[4]: true, + } + // Ensure that the block was built correctly s.verifyBlock(height, hashes, expectedExecution) }, diff --git a/tests/e2e/e2e_tx_test.go b/tests/e2e/e2e_tx_test.go index 726ea02..069351e 100644 --- a/tests/e2e/e2e_tx_test.go +++ b/tests/e2e/e2e_tx_test.go @@ -78,7 +78,7 @@ func (s *IntegrationTestSuite) execMsgSendTx(valIdx int, to sdk.AccAddress, amou } // createAuctionBidTx creates a transaction that bids on an auction given the provided bidder, bid, and transactions. -func (s *IntegrationTestSuite) createAuctionBidTx(account TestAccount, bid sdk.Coin, transactions [][]byte, sequenceOffset, height uint64) []byte { +func (s *IntegrationTestSuite) createAuctionBidTx(account TestAccount, bid sdk.Coin, transactions [][]byte, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte { msgs := []sdk.Msg{ &buildertypes.MsgAuctionBid{ Bidder: account.Address.String(), @@ -87,12 +87,12 @@ func (s *IntegrationTestSuite) createAuctionBidTx(account TestAccount, bid sdk.C }, } - return s.createTx(account, msgs, sequenceOffset, height) + return s.createTx(account, msgs, sequenceOffset, height, gasLimit, fees) } // createMsgSendTx creates a send transaction given the provided signer, recipient, amount, sequence number offset, and block height timeout. // This function is primarily used to create bundles of transactions. -func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress string, amount sdk.Coins, sequenceOffset, height uint64) []byte { +func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress string, amount sdk.Coins, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte { msgs := []sdk.Msg{ &banktypes.MsgSend{ FromAddress: account.Address.String(), @@ -101,12 +101,12 @@ func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress st }, } - return s.createTx(account, msgs, sequenceOffset, height) + return s.createTx(account, msgs, sequenceOffset, height, gasLimit, fees) } // createMsgDelegateTx creates a delegate transaction given the provided signer, validator, amount, sequence number offset // and block height timeout. -func (s *IntegrationTestSuite) createMsgDelegateTx(account TestAccount, validator string, amount sdk.Coin, sequenceOffset, height uint64) []byte { +func (s *IntegrationTestSuite) createMsgDelegateTx(account TestAccount, validator string, amount sdk.Coin, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte { msgs := []sdk.Msg{ &stakingtypes.MsgDelegate{ DelegatorAddress: account.Address.String(), @@ -115,11 +115,11 @@ func (s *IntegrationTestSuite) createMsgDelegateTx(account TestAccount, validato }, } - return s.createTx(account, msgs, sequenceOffset, height) + return s.createTx(account, msgs, sequenceOffset, height, gasLimit, fees) } // createTx creates a transaction given the provided messages, sequence number offset, and block height timeout. -func (s *IntegrationTestSuite) createTx(account TestAccount, msgs []sdk.Msg, sequenceOffset, height uint64) []byte { +func (s *IntegrationTestSuite) createTx(account TestAccount, msgs []sdk.Msg, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte { txConfig := encodingConfig.TxConfig txBuilder := txConfig.NewTxBuilder()