feat(BB): Update match function to include sdk.Context (#234)

This commit is contained in:
David Terpay 2023-08-10 13:01:52 -04:00 committed by GitHub
parent 128cc26f1a
commit 14f83b5e25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 402 additions and 265 deletions

246
README.md
View File

@ -17,20 +17,17 @@ redistribute MEV.
Skip's POB provides developers with a set of a few core primitives:
* `BlockBuster`: BlockBuster is a generalized block-building and mempool SDK
that allows developers to define how their applications construct and validate blocks
on-chain in a transparent, enforceable way. At its core, BlockBuster is an app-side mempool + set
of proposal handlers (`PrepareProposal`/`ProcessProposal`) that allow developers to configure
modular lanes of transactions in their blocks with distinct validation/ordering logic. For more
information, see the [BlockBuster README](/blockbuster/README.md).
* `x/builder`: This Cosmos SDK module gives applications the ability to process
MEV bundled transactions in addition to having the ability to define how searchers
and block proposers are rewarded. In addition, the module defines a `AuctionDecorator`,
which is an AnteHandler decorator that enforces various chain configurable MEV
rules.
* `ProposalHandler`: This ABCI++ handler defines `PrepareProposal` and `ProcessProposal`
methods that give applications the ability to perform top-of-block auctions,
which enables recapturing, redistributing and control over MEV. These methods
are responsible for block proposal construction and validation.
* `AuctionMempool`: An MEV-aware mempool that enables searchers to submit bundled
transactions to the mempool and have them bundled into blocks via a top-of-block
auction. Searchers include a bid in their bundled transactions and the highest
bid wins the auction. Application devs have control over levers that control
aspects such as the bid floor and minimum bid increment.
## Releases
@ -39,6 +36,9 @@ Skip's POB provides developers with a set of a few core primitives:
| POB Version | Cosmos SDK |
| :---------: | :--------: |
| v1.x.x | v0.47.x |
| v1.x.x | v0.48.x |
| v1.x.x | v0.49.x |
| v1.x.x | v0.50.x |
## Install
@ -48,17 +48,33 @@ $ go install github.com/skip-mev/pob
## Setup
>This set up guide will walk you through the process of setting up a POB application. In particular, we will configure an application with the following features:
>
>* Top of block lane (auction lane). This will create an auction lane where users can bid to have their
> transactions executed at the top of the block.
>* Free lane. This will create a free lane where users can submit transactions that will be executed
> for free (no fees).
>* Default lane. This will create a default lane where users can submit transactions that will be executed
> with the default app logic.
>* Builder module that pairs with the auction lane to process auction transactions and distribute revenue
> to the auction house.
>
> To build your own custom BlockBuster Lane, please see the [BlockBuster README](/blockbuster/README.md).
1. Import the necessary dependencies into your application. This includes the
proposal handlers, mempool, keeper, builder types, and builder module. This
blockbuster proposal handlers +mempool, keeper, builder types, and builder module. This
tutorial will go into more detail into each of the dependencies.
```go
import (
proposalhandler "github.com/skip-mev/pob/abci"
"github.com/skip-mev/pob/mempool"
"github.com/skip-mev/pob/x/builder"
builderkeeper "github.com/skip-mev/pob/x/builder/keeper"
buildertypes "github.com/skip-mev/pob/x/builder/types"
...
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/abci"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/lanes/base"
"github.com/skip-mev/pob/blockbuster/lanes/free"
buildermodule "github.com/skip-mev/pob/x/builder"
builderkeeper "github.com/skip-mev/pob/x/builder/keeper"
...
)
```
@ -76,7 +92,7 @@ $ go install github.com/skip-mev/pob
var (
ModuleBasics = module.NewBasicManager(
...
builder.AppModuleBasic{},
buildermodule.AppModuleBasic{},
)
...
)
@ -86,7 +102,11 @@ $ go install github.com/skip-mev/pob
messages that allow users to participate in the top of block auction, distribute
revenue to the auction house, and ensure the validity of auction transactions.
a. First add the keeper to the app's struct definition.
a. First add the keeper to the app's struct definition. We also want to add POB's custom
checkTx handler to the app's struct definition. This will allow us to override the
default checkTx handler to process bid transactions before they are inserted into the mempool.
NOTE: The custom handler is required as otherwise the auction can be held hostage by a malicious
users.
```go
type App struct {
@ -109,7 +129,124 @@ $ go install github.com/skip-mev/pob
}
```
c. Instantiate the builder keeper, store keys, and module manager. Note, be
c. Instantiate the blockbuster mempool with the application's desired lanes.
```go
// Set the blockbuster mempool into the app.
// Create the lanes.
//
// NOTE: The lanes are ordered by priority. The first lane is the highest priority
// lane and the last lane is the lowest priority lane.
// Top of block lane allows transactions to bid for inclusion at the top of the next block.
//
// blockbuster.BaseLaneConfig is utilized for basic encoding/decoding of transactions.
tobConfig := blockbuster.BaseLaneConfig{
Logger: app.Logger(),
TxEncoder: app.txConfig.TxEncoder(),
TxDecoder: app.txConfig.TxDecoder(),
// the desired portion of total block space to be reserved for the lane. a value of 0
// indicates that the lane can use all available block space.
MaxBlockSpace: sdk.ZeroDec(),
}
tobLane := auction.NewTOBLane(
tobConfig,
// the maximum number of transactions that the mempool can store. a value of 0 indicates
// that the mempool can store an unlimited number of transactions.
0,
// AuctionFactory is responsible for determining what is an auction bid transaction and
// how to extract the bid information from the transaction. There is a default implementation
// that can be used or application developers can implement their own.
auction.NewDefaultAuctionFactory(app.txConfig.TxDecoder()),
)
// Free lane allows transactions to be included in the next block for free.
freeConfig := blockbuster.BaseLaneConfig{
Logger: app.Logger(),
TxEncoder: app.txConfig.TxEncoder(),
TxDecoder: app.txConfig.TxDecoder(),
MaxBlockSpace: sdk.ZeroDec(),
// IgnoreList is a list of lanes that if a transaction should be included in, it will be
// ignored by the lane. For example, if a transaction should belong to the tob lane, it
// will be ignored by the free lane.
IgnoreList: []blockbuster.Lane{
tobLane,
},
}
freeLane := free.NewFreeLane(
freeConfig,
free.NewDefaultFreeFactory(app.txConfig.TxDecoder()),
)
// Default lane accepts all other transactions.
defaultConfig := blockbuster.BaseLaneConfig{
Logger: app.Logger(),
TxEncoder: app.txConfig.TxEncoder(),
TxDecoder: app.txConfig.TxDecoder(),
MaxBlockSpace: sdk.ZeroDec(),
IgnoreList: []blockbuster.Lane{
tobLane,
freeLane,
},
}
defaultLane := base.NewDefaultLane(defaultConfig)
// Set the lanes into the mempool.
lanes := []blockbuster.Lane{
tobLane,
freeLane,
defaultLane,
}
mempool := blockbuster.NewMempool(lanes...)
app.App.SetMempool(mempool)
```
d. Instantiate the antehandler chain for the application with awareness of the
blockbuster mempool. This will allow the application to verify the validity
of a transaction respecting the desired logic of a given lane. In this walkthrough,
we want the `FeeDecorator` to be ignored for all transactions that should belong to the
free lane. Additionally, we want to add the `x/builder` module's `AuctionDecorator` to the
ante-handler chain. The `AuctionDecorator` is an AnteHandler decorator that enforces various
chain configurable MEV rules.
```go
import (
...
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/utils"
builderante "github.com/skip-mev/pob/x/builder/ante"
...
)
anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
...
// The IgnoreDecorator allows for certain decorators to be ignored for certain transactions. In
// this case, we want to ignore the FeeDecorator for all transactions that should belong to the
// free lane.
utils.NewIgnoreDecorator(
ante.NewDeductFeeDecorator(
options.BaseOptions.AccountKeeper,
options.BaseOptions.BankKeeper,
options.BaseOptions.FeegrantKeeper,
options.BaseOptions.TxFeeChecker,
),
options.FreeLane,
),
...
builderante.NewBuilderDecorator(options.BuilderKeeper, options.TxEncoder, options.TOBLane, options.Mempool),
}
anteHandler := sdk.ChainAnteDecorators(anteDecorators...)
app.SetAnteHandler(anteHandler)
// Set the antehandlers on the lanes.
for _, lane := range lanes {
lane.SetAnteHandler(anteHandler)
}
app.App.SetAnteHandler(anteHandler)
```
e. Instantiate the builder keeper, store keys, and module manager. Note, be
sure to do this after all the required keeper dependencies have been instantiated.
```go
@ -136,40 +273,17 @@ $ go install github.com/skip-mev/pob
)
```
d. Searchers bid to have their bundles executed at the top of the block
using `MsgAuctionBid` messages (by default). While the builder `Keeper` is capable of
tracking valid bids, it is unable to correctly sequence the auction
transactions alongside the normal transactions without having access to the
applications mempool. As such, we have to instantiate POBs custom
`AuctionMempool` - a modified version of the SDKs priority sender-nonce
mempool - into the application. Note, this should be done after `BaseApp` is
instantiated.
d.1. Application developers can choose to implement their own `AuctionFactory` implementation
or use the default implementation provided by POB. The `AuctionFactory` is responsible
for determining what is an auction bid transaction and how to extract the bid information
from the transaction. The default implementation provided by POB is `DefaultAuctionFactory`
which uses the `MsgAuctionBid` message to determine if a transaction is an auction bid
transaction and extracts the bid information from the message.
```go
config := mempool.NewDefaultAuctionFactory(txDecoder)
mempool := mempool.NewAuctionMempool(txDecoder, txEncoder, maxTx, config)
bApp.SetMempool(mempool)
```
e. With Cosmos SDK version 0.47.0, the process of building blocks has been
updated and moved from the consensus layer, CometBFT, to the application layer.
When a new block is requested, the proposer for that height will utilize the
`PrepareProposal` handler to build a block while the `ProcessProposal` handler
will verify the contents of the block proposal by all validators. The
combination of the `AuctionMempool`, `PrepareProposal` and `ProcessProposal`
combination of the `BlockBuster` mempool + `PrepareProposal`/`ProcessProposal`
handlers allows the application to verifiably build valid blocks with
top-of-block block space reserved for auctions. Additionally, we override the
`BaseApp`'s `CheckTx` handler with our own custom `CheckTx` handler that will
be responsible for checking the validity of transactions. We override the
`CheckTx` handler so that we can verify auction transactions before they are
top-of-block block space reserved for auctions and partial block for free transactions.
Additionally, we override the `BaseApp`'s `CheckTx` handler with our own custom
`CheckTx` handler that will be responsible for checking the validity of transactions.
We override the `CheckTx` handler so that we can verify auction transactions before they are
inserted into the mempool. With the POB `CheckTx`, we can verify the auction
transaction and all of the bundled transactions before inserting the auction
transaction into the mempool. This is important because we otherwise there may be
@ -178,42 +292,26 @@ $ go install github.com/skip-mev/pob
griefed. All other transactions will be executed with base app's `CheckTx`.
```go
// Create the entire chain of AnteDecorators for the application.
anteDecorators := []sdk.AnteDecorator{
auction.NewAuctionDecorator(
app.BuilderKeeper,
txConfig.TxEncoder(),
mempool,
),
...,
}
// Create the antehandler that will be used to check transactions throughout the lifecycle
// of the application.
anteHandler := sdk.ChainAnteDecorators(anteDecorators...)
app.SetAnteHandler(anteHandler)
// Create the proposal handler that will be used to build and validate blocks.
handler := proposalhandler.NewProposalHandler(
mempool,
bApp.Logger(),
anteHandler,
txConfig.TxEncoder(),
txConfig.TxDecoder(),
proposalHandler := abci.NewProposalHandler(
app.Logger(),
app.txConfig.TxDecoder(),
mempool,
)
app.SetPrepareProposal(handler.PrepareProposalHandler())
app.SetProcessProposal(handler.ProcessProposalHandler())
app.App.SetPrepareProposal(proposalHandler.PrepareProposalHandler())
app.App.SetProcessProposal(proposalHandler.ProcessProposalHandler())
// Set the custom CheckTx handler on BaseApp.
checkTxHandler := pobabci.CheckTxHandler(
checkTxHandler := abci.NewCheckTxHandler(
app.App,
app.TxDecoder,
mempool,
app.txConfig.TxDecoder(),
tobLane,
anteHandler,
chainID,
app.ChainID(),
)
app.SetCheckTx(checkTxHandler)
app.SetCheckTx(checkTxHandler.CheckTx())
...
// CheckTx will check the transaction with the provided checkTxHandler. We override the default

View File

@ -77,7 +77,7 @@ func (suite *ABCITestSuite) SetupTest() {
// Lanes configuration
//
// TOB lane set up
config := blockbuster.BaseLaneConfig{
tobConfig := blockbuster.BaseLaneConfig{
Logger: suite.logger,
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
@ -85,18 +85,27 @@ func (suite *ABCITestSuite) SetupTest() {
MaxBlockSpace: math.LegacyZeroDec(),
}
suite.tobLane = auction.NewTOBLane(
config,
tobConfig,
0, // No bound on the number of transactions in the lane
auction.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Base lane set up
baseConfig := blockbuster.BaseLaneConfig{
Logger: suite.logger,
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{suite.tobLane},
}
suite.baseLane = base.NewDefaultLane(
config,
baseConfig,
)
// Mempool set up
suite.mempool = blockbuster.NewMempool(
suite.logger,
suite.tobLane,
suite.baseLane,
)

View File

@ -169,33 +169,33 @@ Lane interface {
Name() string
// Match determines if a transaction belongs to this lane.
Match(tx sdk.Tx) bool
Match(ctx sdk.Context, 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)
// Contains returns true if the mempool/lane contains the given transaction.
Contains(tx sdk.Tx) bool
// 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.
// 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,
next PrepareLanesHandler
) (BlockProposal, error)
// ProcessLaneBasic validates that transactions belonging to this lane are
// not misplaced in the block proposal.
ProcessLaneBasic(txs []sdk.Tx) error
// ProcessLaneBasic validates that transactions belonging to this lane
// are not misplaced in the block proposal.
ProcessLaneBasic(ctx sdk.Context, 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 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,
@ -209,7 +209,7 @@ Lane interface {
Logger() log.Logger
// GetMaxBlockSpace returns the max block space for the lane as a relative percentage.
GetMaxBlockSpace() sdk.Dec
GetMaxBlockSpace() math.LegacyDec
}
```

View File

@ -198,7 +198,7 @@ func ChainProcessLanes(chain ...blockbuster.Lane) blockbuster.ProcessLanesHandle
chain[0].Logger().Info("processing lane", "lane", chain[0].Name())
if err := chain[0].ProcessLaneBasic(proposalTxs); err != nil {
if err := chain[0].ProcessLaneBasic(ctx, proposalTxs); err != nil {
chain[0].Logger().Error("failed to process lane", "lane", chain[0].Name(), "err", err)
return ctx, err
}

View File

@ -75,16 +75,14 @@ func (suite *ABCITestSuite) SetupTest() {
suite.ctx = testCtx.Ctx.WithBlockHeight(1)
// Lanes configuration
config := blockbuster.BaseLaneConfig{
// Top of block lane set up
suite.tobConfig = blockbuster.BaseLaneConfig{
Logger: log.NewTestLogger(suite.T()),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(), // It can be as big as it wants (up to maxTxBytes)
}
// Top of block lane set up
suite.tobConfig = config
suite.tobLane = auction.NewTOBLane(
suite.tobConfig,
0, // No bound on the number of transactions in the lane
@ -92,21 +90,35 @@ func (suite *ABCITestSuite) SetupTest() {
)
// Free lane set up
suite.freeConfig = config
suite.freeConfig = blockbuster.BaseLaneConfig{
Logger: log.NewTestLogger(suite.T()),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(), // It can be as big as it wants (up to maxTxBytes)
IgnoreList: []blockbuster.Lane{suite.tobLane},
}
suite.freeLane = free.NewFreeLane(
suite.freeConfig,
free.NewDefaultFreeFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Base lane set up
suite.baseConfig = config
suite.baseConfig = blockbuster.BaseLaneConfig{
Logger: log.NewTestLogger(suite.T()),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(), // It can be as big as it wants (up to maxTxBytes)
IgnoreList: []blockbuster.Lane{suite.tobLane, suite.freeLane},
}
suite.baseLane = base.NewDefaultLane(
suite.baseConfig,
)
// Mempool set up
suite.lanes = []blockbuster.Lane{suite.tobLane, suite.freeLane, suite.baseLane}
suite.mempool = blockbuster.NewMempool(suite.lanes...)
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), suite.lanes...)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 10)
@ -179,7 +191,7 @@ func (suite *ABCITestSuite) resetLanesWithNewConfig() {
suite.lanes = []blockbuster.Lane{suite.tobLane, suite.freeLane, suite.baseLane}
suite.mempool = blockbuster.NewMempool(suite.lanes...)
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), suite.lanes...)
}
func (suite *ABCITestSuite) TestPrepareProposal() {
@ -711,13 +723,12 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[txIndex])
suite.Require().NoError(err)
lane, err := suite.mempool.Match(tx)
suite.Require().NoError(err)
suite.Require().Equal(true, suite.mempool.Contains(tx))
// In the case where we have a tob tx, we skip the other transactions in the bundle
// in order to not double count
switch {
case lane.Name() == suite.tobLane.Name():
case suite.tobLane.Match(suite.ctx, tx):
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
@ -767,7 +778,7 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
sdkTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[txIndex])
suite.Require().NoError(err)
if suite.lanes[laneIndex].Match(sdkTx) {
if suite.lanes[laneIndex].Match(suite.ctx, sdkTx) {
switch suite.lanes[laneIndex].Name() {
case suite.tobLane.Name():
bidInfo, err := suite.tobLane.GetAuctionBidInfo(sdkTx)

View File

@ -50,13 +50,13 @@ type (
Name() string
// Match determines if a transaction belongs to this lane.
Match(tx sdk.Tx) bool
Match(ctx sdk.Context, 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)
// Contains returns true if the mempool/lane contains the given transaction.
Contains(tx sdk.Tx) bool
// 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
@ -66,7 +66,7 @@ type (
// ProcessLaneBasic validates that transactions belonging to this lane are not misplaced
// in the block proposal.
ProcessLaneBasic(txs []sdk.Tx) error
ProcessLaneBasic(ctx sdk.Context, 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

View File

@ -172,7 +172,7 @@ func (l *TOBLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next blockbuster.Pr
}
bidTx := txs[0]
if !l.Match(bidTx) {
if !l.Match(ctx, bidTx) {
return next(ctx, txs)
}
@ -193,7 +193,7 @@ func (l *TOBLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next blockbuster.Pr
// - all of the bundled transactions are included after the bid transaction in the order
// they were included in the bid transaction.
// - there are no other bid transactions in the proposal
func (l *TOBLane) ProcessLaneBasic(txs []sdk.Tx) error {
func (l *TOBLane) ProcessLaneBasic(ctx sdk.Context, txs []sdk.Tx) error {
if len(txs) == 0 {
return nil
}
@ -201,9 +201,9 @@ func (l *TOBLane) ProcessLaneBasic(txs []sdk.Tx) error {
bidTx := txs[0]
// If there is a bid transaction, it must be the first transaction in the block proposal.
if !l.Match(bidTx) {
if !l.Match(ctx, bidTx) {
for _, tx := range txs[1:] {
if l.Match(tx) {
if l.Match(ctx, tx) {
return fmt.Errorf("misplaced bid transactions in lane %s", l.Name())
}
}
@ -222,7 +222,7 @@ func (l *TOBLane) ProcessLaneBasic(txs []sdk.Tx) error {
// Ensure that the order of transactions in the bundle is preserved.
for i, bundleTx := range txs[1 : len(bidInfo.Transactions)+1] {
if l.Match(bundleTx) {
if l.Match(ctx, bundleTx) {
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
}
@ -238,7 +238,7 @@ func (l *TOBLane) ProcessLaneBasic(txs []sdk.Tx) error {
// Ensure that there are no more bid transactions in the block proposal.
for _, tx := range txs[len(bidInfo.Transactions)+1:] {
if l.Match(tx) {
if l.Match(ctx, tx) {
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
}
}

View File

@ -47,19 +47,18 @@ func NewTOBLane(
return &TOBLane{
Mempool: NewMempool(cfg.TxEncoder, maxTx, af),
DefaultLane: base.NewDefaultLane(cfg),
DefaultLane: base.NewDefaultLane(cfg).WithName(LaneName),
Factory: af,
}
}
// Match returns true if the transaction is a bid transaction. This is determined
// by the AuctionFactory.
func (l *TOBLane) Match(tx sdk.Tx) bool {
func (l *TOBLane) Match(ctx sdk.Context, tx sdk.Tx) bool {
if l.MatchIgnoreList(ctx, tx) {
return false
}
bidInfo, err := l.GetAuctionBidInfo(tx)
return bidInfo != nil && err == nil
}
// Name returns the name of the lane.
func (l *TOBLane) Name() string {
return LaneName
}

View File

@ -22,7 +22,7 @@ type (
GetTopAuctionTx(ctx context.Context) sdk.Tx
// Contains returns true if the transaction is contained in the mempool.
Contains(tx sdk.Tx) (bool, error)
Contains(tx sdk.Tx) bool
}
// TOBMempool defines an auction mempool. It can be seen as an extension of
@ -106,16 +106,6 @@ func NewMempool(txEncoder sdk.TxEncoder, maxTx int, config Factory) *TOBMempool
// Insert inserts a transaction into the auction mempool.
func (am *TOBMempool) Insert(ctx context.Context, tx sdk.Tx) error {
bidInfo, err := am.GetAuctionBidInfo(tx)
if err != nil {
return err
}
// This mempool only supports auction bid transactions.
if bidInfo == nil {
return fmt.Errorf("invalid transaction type")
}
if err := am.index.Insert(ctx, tx); err != nil {
return fmt.Errorf("failed to insert tx into auction index: %w", err)
}
@ -132,17 +122,16 @@ func (am *TOBMempool) Insert(ctx context.Context, tx sdk.Tx) error {
// Remove removes a transaction from the mempool based.
func (am *TOBMempool) Remove(tx sdk.Tx) error {
bidInfo, err := am.GetAuctionBidInfo(tx)
if err := am.index.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
return fmt.Errorf("failed to remove invalid transaction from the mempool: %w", err)
}
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
return err
return fmt.Errorf("failed to get tx hash string: %w", err)
}
// This mempool only supports auction bid transactions.
if bidInfo == nil {
return fmt.Errorf("invalid transaction type")
}
am.removeTx(am.index, tx)
delete(am.txIndex, txHashStr)
return nil
}
@ -166,25 +155,12 @@ func (am *TOBMempool) CountTx() int {
}
// Contains returns true if the transaction is contained in the mempool.
func (am *TOBMempool) Contains(tx sdk.Tx) (bool, error) {
func (am *TOBMempool) Contains(tx sdk.Tx) bool {
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
return false, fmt.Errorf("failed to get tx hash string: %w", err)
return false
}
_, ok := am.txIndex[txHashStr]
return ok, nil
}
func (am *TOBMempool) removeTx(mp sdkmempool.Mempool, tx sdk.Tx) {
if err := mp.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
panic(fmt.Errorf("failed to remove invalid transaction from the mempool: %w", err))
}
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
panic(fmt.Errorf("failed to get tx hash string: %w", err))
}
delete(am.txIndex, txHashStr)
return ok
}

View File

@ -95,7 +95,7 @@ func (l *DefaultLane) PrepareLane(
// we only need to verify the contiguous set of transactions that match to the default lane.
func (l *DefaultLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next blockbuster.ProcessLanesHandler) (sdk.Context, error) {
for index, tx := range txs {
if l.Match(tx) {
if l.Match(ctx, tx) {
if err := l.VerifyTx(ctx, tx); err != nil {
return ctx, fmt.Errorf("failed to verify tx: %w", err)
}
@ -111,12 +111,12 @@ func (l *DefaultLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next blockbuste
// transactions that belong to this lane are not misplaced in the block proposal i.e.
// the proposal only contains contiguous transactions that belong to this lane - there
// can be no interleaving of transactions from other lanes.
func (l *DefaultLane) ProcessLaneBasic(txs []sdk.Tx) error {
func (l *DefaultLane) ProcessLaneBasic(ctx sdk.Context, txs []sdk.Tx) error {
seenOtherLaneTx := false
lastSeenIndex := 0
for _, tx := range txs {
if l.Match(tx) {
if l.Match(ctx, tx) {
if seenOtherLaneTx {
return fmt.Errorf("the %s lane contains a transaction that belongs to another lane", l.Name())
}

View File

@ -55,14 +55,20 @@ func (l *DefaultLane) WithName(name string) *DefaultLane {
// Match returns true if the transaction belongs to this lane. Since
// this is the default lane, it always returns true except for transactions
// that belong to lanes in the ignore list.
func (l *DefaultLane) Match(tx sdk.Tx) bool {
func (l *DefaultLane) Match(ctx sdk.Context, tx sdk.Tx) bool {
return !l.MatchIgnoreList(ctx, tx)
}
// MatchIgnoreList returns true if any of the lanes that are in the ignore list
// match the current transaction.
func (l *DefaultLane) MatchIgnoreList(ctx sdk.Context, tx sdk.Tx) bool {
for _, lane := range l.Cfg.IgnoreList {
if lane.Match(tx) {
return false
if lane.Match(ctx, tx) {
return true
}
}
return true
return false
}
// Name returns the name of the lane.

View File

@ -19,7 +19,7 @@ type (
sdkmempool.Mempool
// Contains returns true if the transaction is contained in the mempool.
Contains(tx sdk.Tx) (bool, error)
Contains(tx sdk.Tx) bool
}
// DefaultMempool defines the most basic mempool. It can be seen as an extension of
@ -60,6 +60,7 @@ func (am *DefaultMempool) Insert(ctx context.Context, tx sdk.Tx) error {
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
am.Remove(tx)
return err
}
@ -70,7 +71,17 @@ func (am *DefaultMempool) Insert(ctx context.Context, tx sdk.Tx) error {
// Remove removes a transaction from the mempool based on the transaction type (normal or auction).
func (am *DefaultMempool) Remove(tx sdk.Tx) error {
am.removeTx(am.index, tx)
if err := am.index.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
return fmt.Errorf("failed to remove transaction from the mempool: %w", err)
}
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
return fmt.Errorf("failed to get tx hash string: %w", err)
}
delete(am.txIndex, txHashStr)
return nil
}
@ -83,26 +94,12 @@ func (am *DefaultMempool) CountTx() int {
}
// Contains returns true if the transaction is contained in the mempool.
func (am *DefaultMempool) Contains(tx sdk.Tx) (bool, error) {
func (am *DefaultMempool) Contains(tx sdk.Tx) bool {
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
return false, fmt.Errorf("failed to get tx hash string: %w", err)
return false
}
_, ok := am.txIndex[txHashStr]
return ok, nil
}
func (am *DefaultMempool) removeTx(mp sdkmempool.Mempool, tx sdk.Tx) {
err := mp.Remove(tx)
if err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
panic(fmt.Errorf("failed to remove invalid transaction from the mempool: %w", err))
}
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
panic(fmt.Errorf("failed to get tx hash string: %w", err))
}
delete(am.txIndex, txHashStr)
return ok
}

View File

@ -32,11 +32,10 @@ func NewFreeLane(cfg blockbuster.BaseLaneConfig, factory Factory) *Lane {
}
// Match returns true if the transaction is a free transaction.
func (l *Lane) Match(tx sdk.Tx) bool {
func (l *Lane) Match(ctx sdk.Context, tx sdk.Tx) bool {
if l.MatchIgnoreList(ctx, tx) {
return false
}
return l.IsFreeTx(tx)
}
// Name returns the name of the free lane.
func (l *Lane) Name() string {
return LaneName
}

View File

@ -51,7 +51,7 @@ func (t Terminator) Name() string {
}
// Match is a no-op
func (t Terminator) Match(sdk.Tx) bool {
func (t Terminator) Match(sdk.Context, sdk.Tx) bool {
return false
}
@ -61,8 +61,8 @@ func (t Terminator) VerifyTx(sdk.Context, sdk.Tx) error {
}
// Contains is a no-op
func (t Terminator) Contains(sdk.Tx) (bool, error) {
return false, nil
func (t Terminator) Contains(sdk.Tx) bool {
return false
}
// CountTx is a no-op
@ -86,7 +86,7 @@ func (t Terminator) Select(context.Context, [][]byte) sdkmempool.Iterator {
}
// ValidateLaneBasic is a no-op
func (t Terminator) ProcessLaneBasic([]sdk.Tx) error {
func (t Terminator) ProcessLaneBasic(sdk.Context, []sdk.Tx) error {
return nil
}

View File

@ -3,7 +3,9 @@ package blockbuster
import (
"context"
"fmt"
"strings"
"cosmossdk.io/log"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
@ -19,15 +21,12 @@ type (
// Registry returns the mempool's lane registry.
Registry() []Lane
// Contains returns true if the transaction is contained in the mempool.
Contains(tx sdk.Tx) (bool, error)
// Contains returns the any of the lanes currently contain the transaction.
Contains(tx sdk.Tx) bool
// GetTxDistribution returns the number of transactions in each lane.
GetTxDistribution() map[string]int
// Match will return the lane that the transaction belongs to.
Match(tx sdk.Tx) (Lane, error)
// GetLane returns the lane with the given name.
GetLane(name string) (Lane, error)
}
@ -36,6 +35,7 @@ type (
// of lanes, which allows for customizable block proposal construction.
BBMempool struct {
registry []Lane
logger log.Logger
}
)
@ -44,11 +44,13 @@ type (
// transactions according to its own selection logic. The lanes are ordered
// according to their priority. The first lane in the registry has the highest
// priority. Proposals are verified according to the order of the lanes in the
// registry. Basic mempool API, such as insertion, removal, and contains, are
// delegated to the first lane that matches the transaction. Each transaction
// should only belong in one lane.
func NewMempool(lanes ...Lane) *BBMempool {
// registry. Each transaction should only belong in one lane but this is NOT enforced.
// To enforce that each transaction belong to a single lane, you must configure the
// ignore list of each lane to include all preceding lanes. Basic mempool API will
// attempt to insert, remove transactions from all lanes it belongs to.
func NewMempool(logger log.Logger, lanes ...Lane) *BBMempool {
mempool := &BBMempool{
logger: logger,
registry: lanes,
}
@ -81,27 +83,28 @@ func (m *BBMempool) GetTxDistribution() map[string]int {
return counts
}
// Match will return the lane that the transaction belongs to. It matches to
// the first lane where lane.Match(tx) is true.
func (m *BBMempool) Match(tx sdk.Tx) (Lane, error) {
for _, lane := range m.registry {
if lane.Match(tx) {
return lane, nil
}
}
return nil, fmt.Errorf("no lane matched transaction")
}
// Insert will insert a transaction into the mempool. It inserts the transaction
// into the first lane that it matches.
func (m *BBMempool) Insert(ctx context.Context, tx sdk.Tx) error {
lane, err := m.Match(tx)
if err != nil {
return err
var errors []string
unwrappedCtx := sdk.UnwrapSDKContext(ctx)
for _, lane := range m.registry {
if !lane.Match(unwrappedCtx, tx) {
continue
}
if err := lane.Insert(ctx, tx); err != nil {
m.logger.Debug("failed to insert tx into lane", "lane", lane.Name(), "err", err)
errors = append(errors, fmt.Sprintf("failed to insert tx into lane %s: %s", lane.Name(), err.Error()))
}
}
return lane.Insert(ctx, tx)
if len(errors) == 0 {
return nil
}
return fmt.Errorf(strings.Join(errors, ";"))
}
// Insert returns a nil iterator.
@ -114,26 +117,46 @@ func (m *BBMempool) Select(_ context.Context, _ [][]byte) sdkmempool.Iterator {
return nil
}
// Remove removes a transaction from the mempool based on the first lane that
// it matches.
// Remove removes a transaction from all of the lanes it is currently in.
func (m *BBMempool) Remove(tx sdk.Tx) error {
lane, err := m.Match(tx)
if err != nil {
return err
var errors []string
for _, lane := range m.registry {
if !lane.Contains(tx) {
continue
}
if err := lane.Remove(tx); err != nil {
m.logger.Debug("failed to remove tx from lane", "lane", lane.Name(), "err", err)
// We only care about errors that are not "tx not found" errors.
//
// TODO: Figure out whether we should be erroring in the mempool if
// the tx is not found in the lane. Downstream, if the removal fails runTx will
// error out and will NOT execute runMsgs (which is where the tx is actually
// executed).
if err != sdkmempool.ErrTxNotFound {
errors = append(errors, fmt.Sprintf("failed to remove tx from lane %s: %s;", lane.Name(), err.Error()))
}
}
}
return lane.Remove(tx)
if len(errors) == 0 {
return nil
}
return fmt.Errorf(strings.Join(errors, ";"))
}
// Contains returns true if the transaction is contained in the mempool. It
// checks the first lane that it matches to.
func (m *BBMempool) Contains(tx sdk.Tx) (bool, error) {
lane, err := m.Match(tx)
if err != nil {
return false, err
// Contains returns true if the transaction is contained in any of the lanes.
func (m *BBMempool) Contains(tx sdk.Tx) bool {
for _, lane := range m.registry {
if lane.Contains(tx) {
return true
}
}
return lane.Contains(tx)
return false
}
// Registry returns the mempool's lane registry.

View File

@ -55,35 +55,54 @@ func (suite *BlockBusterTestSuite) SetupTest() {
// Lanes configuration
//
// TOB lane set up
config := blockbuster.BaseLaneConfig{
tobConfig := blockbuster.BaseLaneConfig{
Logger: log.NewNopLogger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: nil,
MaxBlockSpace: math.LegacyZeroDec(),
}
// Top of block lane set up
suite.tobLane = auction.NewTOBLane(
config,
tobConfig,
0, // No bound on the number of transactions in the lane
auction.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Free lane set up
freeConfig := blockbuster.BaseLaneConfig{
Logger: log.NewNopLogger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: nil,
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{
suite.tobLane,
},
}
suite.freeLane = free.NewFreeLane(
config,
freeConfig,
free.NewDefaultFreeFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Base lane set up
baseConfig := blockbuster.BaseLaneConfig{
Logger: log.NewNopLogger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: nil,
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{
suite.tobLane,
suite.freeLane,
},
}
suite.baseLane = base.NewDefaultLane(
config,
baseConfig,
)
// Mempool set up
suite.lanes = []blockbuster.Lane{suite.tobLane, suite.freeLane, suite.baseLane}
suite.mempool = blockbuster.NewMempool(suite.lanes...)
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), suite.lanes...)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 10)
@ -179,20 +198,20 @@ func (suite *BlockBusterTestSuite) TestInsert() {
}
// Validate the mempool
suite.Require().Equal(suite.mempool.CountTx(), sum)
suite.Require().Equal(sum, suite.mempool.CountTx())
// Validate the lanes
suite.Require().Equal(suite.baseLane.CountTx(), tc.insertDistribution[suite.baseLane.Name()])
suite.Require().Equal(suite.tobLane.CountTx(), tc.insertDistribution[suite.tobLane.Name()])
suite.Require().Equal(suite.freeLane.CountTx(), tc.insertDistribution[suite.freeLane.Name()])
suite.Require().Equal(tc.insertDistribution[suite.tobLane.Name()], suite.tobLane.CountTx())
suite.Require().Equal(tc.insertDistribution[suite.baseLane.Name()], suite.baseLane.CountTx())
suite.Require().Equal(tc.insertDistribution[suite.freeLane.Name()], suite.freeLane.CountTx())
// Validate the lane counts
laneCounts := suite.mempool.GetTxDistribution()
// Ensure that the lane counts are correct
suite.Require().Equal(laneCounts[suite.tobLane.Name()], tc.insertDistribution[suite.tobLane.Name()])
suite.Require().Equal(laneCounts[suite.baseLane.Name()], tc.insertDistribution[suite.baseLane.Name()])
suite.Require().Equal(laneCounts[suite.freeLane.Name()], tc.insertDistribution[suite.freeLane.Name()])
suite.Require().Equal(tc.insertDistribution[suite.tobLane.Name()], laneCounts[suite.tobLane.Name()])
suite.Require().Equal(tc.insertDistribution[suite.baseLane.Name()], laneCounts[suite.baseLane.Name()])
suite.Require().Equal(tc.insertDistribution[suite.freeLane.Name()], laneCounts[suite.freeLane.Name()])
})
}
}
@ -250,9 +269,7 @@ func (suite *BlockBusterTestSuite) TestRemove() {
suite.Require().NoError(suite.mempool.Remove(tx))
// Ensure that the transaction is no longer in the mempool
contains, err := suite.mempool.Contains(tx)
suite.Require().NoError(err)
suite.Require().False(contains)
suite.Require().Equal(false, suite.mempool.Contains(tx))
// Ensure the number of transactions in the lane is correct
baseCount--
@ -275,9 +292,7 @@ func (suite *BlockBusterTestSuite) TestRemove() {
suite.Require().NoError(suite.mempool.Remove(tx))
// Ensure that the transaction is no longer in the mempool
contains, err := suite.mempool.Contains(tx)
suite.Require().NoError(err)
suite.Require().False(contains)
suite.Require().Equal(false, suite.mempool.Contains(tx))
// Ensure the number of transactions in the lane is correct
tobCount--

View File

@ -8,7 +8,7 @@ type (
// Lane defines the required functionality for a lane. The ignore decorator
// will check if a transaction belongs to a lane by calling the Match function.
Lane interface {
Match(tx sdk.Tx) bool
Match(ctx sdk.Context, tx sdk.Tx) bool
}
// IgnoreDecorator is an AnteDecorator that wraps an existing AnteDecorator. It allows
@ -34,7 +34,7 @@ func (sd IgnoreDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler,
) (sdk.Context, error) {
for _, lane := range sd.lanes {
if lane.Match(tx) {
if lane.Match(ctx, tx) {
return next(ctx, tx, simulate)
}
}

View File

@ -310,7 +310,7 @@ func New(
freeLane,
defaultLane,
}
mempool := blockbuster.NewMempool(lanes...)
mempool := blockbuster.NewMempool(app.Logger(), lanes...)
app.App.SetMempool(mempool)
// Create a global ante handler that will be called on each transaction when

View File

@ -23,7 +23,7 @@ type (
// Mempool is an interface that defines the methods required to interact with the application-side mempool.
Mempool interface {
Contains(tx sdk.Tx) (bool, error)
Contains(tx sdk.Tx) bool
}
// BuilderDecorator is an AnteDecorator that validates the auction bid and bundled transactions.
@ -49,12 +49,7 @@ func NewBuilderDecorator(ak keeper.Keeper, txEncoder sdk.TxEncoder, lane TOBLane
func (bd BuilderDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// If comet is re-checking a transaction, we only need to check if the transaction is in the application-side mempool.
if ctx.IsReCheckTx() {
contains, err := bd.mempool.Contains(tx)
if err != nil {
return ctx, err
}
if !contains {
if !bd.mempool.Contains(tx) {
return ctx, fmt.Errorf("transaction not found in application-side mempool")
}
}

View File

@ -5,6 +5,7 @@ import (
"testing"
"time"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/testutil"
@ -82,7 +83,7 @@ func (suite *AnteTestSuite) SetupTest() {
// Lanes configuration
//
// TOB lane set up
config := blockbuster.BaseLaneConfig{
tobConfig := blockbuster.BaseLaneConfig{
Logger: suite.ctx.Logger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
@ -90,17 +91,25 @@ func (suite *AnteTestSuite) SetupTest() {
MaxBlockSpace: math.LegacyZeroDec(),
}
suite.tobLane = auction.NewTOBLane(
config,
tobConfig,
0, // No bound on the number of transactions in the lane
auction.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Base lane set up
suite.baseLane = base.NewDefaultLane(config)
baseConfig := blockbuster.BaseLaneConfig{
Logger: suite.ctx.Logger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{suite.tobLane},
}
suite.baseLane = base.NewDefaultLane(baseConfig)
// Mempool set up
suite.lanes = []blockbuster.Lane{suite.tobLane, suite.baseLane}
suite.mempool = blockbuster.NewMempool(suite.lanes...)
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), suite.lanes...)
}
func (suite *AnteTestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, _ bool) (sdk.Context, error) {