| .. | ||
| constructor | ||
| utils | ||
| lane.go | ||
| mempool_test.go | ||
| mempool.go | ||
| proposals.go | ||
| README.md | ||
| types.go | ||
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:
- MEV mitigation: a MEV lane could be designed to create an in-protocol mev auction (as we are doing with POB) to recapture MEV in a transparent and governable way.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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: MEV lane, free lane, and a default lane.
- Top of block lane allows the top of every block to be auctioned off
and constructed using logic defined by the
x/buildermodule. - 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.
- Default lane accepts all other transactions and is considered to be analogous to how mempools and proposals are constructed today.
- Top of block lane allows the top of every block to be auctioned off
and constructed using logic defined by the
- Instantiate the mempool in base app.
mempool := block.NewMempool(lanes...)
app.App.SetMempool(mempool)
- Instantiate the BlockBuster proposal handlers in base app.
proposalHandlers := abci.NewProposalHandler(
app.Logger(),
app.txConfig.TxDecoder(),
mempool, // BlockBuster mempool
)
app.App.SetPrepareProposal(proposalHandlers.PrepareProposalHandler())
app.App.SetProcessProposal(proposalHandlers.ProcessProposalHandler())
Note: BlockBuster should configure a DefaultLane that accepts transactions
that do not belong to any other lane.
Transactions are inserted into the first lane that the transaction matches to. This means that a given transaction should really only belong to one lane (but this isn’t enforced).
Proposals
The ordering of lanes when initializing BlockBuster in base app will determine the ordering of how proposals are built. For example, say that we instantiate three lanes:
- Top of block lane
- Free lane
- Default lane
Preparing Proposals
When the current proposer starts building a block, it will first populate the
proposal with transactions from the MEV lane, followed by free and
default lane. Each lane proposes its own set of transactions using the lane’s
PrepareLane (analogous to PrepareProposal). Each lane has a limit on the
relative percentage of total block space that the lane can consume.
For example, the free lane might be configured to only make up 10% of any
block. This is defined on each lane’s Config when it is instantiated.
In the case when any lane fails to propose its portion of the block, it will be skipped and the next lane in the set of lanes will propose its portion of the block. Failures of partial block proposals are independent of one another.
Processing Proposals
Block proposals are validated iteratively following the exact ordering of lanes defined on base app. Transactions included in block proposals must respect the ordering of lanes. Any proposal that includes transactions that are out of order relative to the ordering of lanes will be rejected. Following the example defined above, if a proposal contains the following transactions:
- Default transaction (belonging to the default lane)
- Top of block transaction (belonging to the MEV lane)
- 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:
- Unique prioritization/ordering mechanism i.e. how will transactions from a given lane be ordered in a block / mempool.
- Inclusion function to determine what types of transactions belong in the lane.
- Unique block building/verification mechanism.
The general interface that each lane must implement can be found here:
// 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(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/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.
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(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(
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() math.LegacyDec
}
1. Intra-lane Transaction Ordering
Note: Lanes must implement the sdk.Mempool interface.
Transactions within a lane are ordered in a proposal respecting the ordering
defined on the lane’s mempool. Developers can define their own custom ordering
by implementing a custom TxPriority struct that allows the lane’s mempool to
determine the priority of a transaction GetTxPriority and relatively order
two transactions given the priority Compare. The MEV lane includes
an custom TxPriority that orders transactions in the mempool based on their
bid.
func TxPriority(config Factory) block.TxPriority[string] {
return block.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: block.NewPriorityMempool(
block.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.
// IsFreeTx defines a default function that checks if a transaction is free. In
// this case, any transaction that is a delegation/redelegation transaction is free.
func (config *DefaultFreeFactory) IsFreeTx(tx sdk.Tx) bool {
for _, msg := range tx.GetMsgs() {
switch msg.(type) {
case *types.MsgDelegate:
return true
case *types.MsgBeginRedelegate:
return true
case *types.MsgCancelUnbondingDelegation:
return true
}
}
return false
}
3. Lane Inclusion Functionality
Lanes must implement a Match interface which determines whether a transaction
should be considered for a given lane. Developer’s are encouraged to utilize the
same interfaces defined in the Factory to match transactions to lanes. For
example, developers might configure a MEV auction lane to accept
transactions if they contain a single MsgAuctionBid message in the transaction.
4.1. [Optional] Transaction Validation
Transactions will be verified the lane’s VerifyTx function. This logic can be
completely arbitrary. For example, the default lane verifies transactions
using base app’s AnteHandler while the MEV 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.
PrepareLanewill be in charge of building a partial block given the transactions in the lane.ProcessLaneBasicensures 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.ProcessLanewill be in charge of verifying the lane’s partial block.
Inheritance
Lanes can inherit the underlying implementation of other lanes and overwrite
any part of the implementation with their own custom functionality. We
recommend that user’s extend the functionality of the Base lane when first
exploring the code base.