feat(bb): Multi-lane testing + docs (#174)

This commit is contained in:
David Terpay 2023-06-13 08:52:10 -04:00 committed by GitHub
parent cee39a9eb7
commit 04f3862ecb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 662 additions and 79 deletions

355
blockbuster/README.md Normal file
View File

@ -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 isnt 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 lanes
`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 lanes `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 lanes mempool. Developers can define their own custom ordering
by implementing a custom `TxPriority` struct that allows the lanes 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. Developers 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 lanes `VerifyTx` function. This logic can be
completely arbitrary. For example, the default lane verifies transactions
using base apps `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 lanes 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 users extend the functionality of the `Base` lane when first
exploring the code base.

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
},

View File

@ -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()