init constructor docs

This commit is contained in:
David Terpay 2023-08-15 15:03:50 -04:00
parent aecb504ae8
commit 4072ad8cb8
No known key found for this signature in database
GPG Key ID: 627EFB00DADF0CD1
2 changed files with 274 additions and 417 deletions

View File

@ -1,355 +1,44 @@
# 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 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.
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: MEV 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/block/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 := block.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 MEV 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 MEV 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/block/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(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 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 MEV lane includes
an custom `TxPriority` that orders transactions in the mempool based on their
bid.
```go
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.
```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 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 lanes `VerifyTx` function. This logic can be
completely arbitrary. For example, the default lane verifies transactions
using base apps `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.
* `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.
## 🤔 How does it work
### Transaction Lifecycle
The best way to understand how lanes work is to first understand the lifecycle
of a transaction. When a transaction is submitted to the chain, it will be checked
in `CheckTx` by the base application. If the transaction is valid, it will be
inserted into the applications mempool. The transaction then waits in the mempool
until a new block needs to be proposed. When a new block needs to be proposed,
the application will call `PrepareProposal` (which is a new ABCI++ addition) to
request a new block from the current proposer. The proposer will look at what the
transactions currently waiting to be included in a block in their mempool and
will iterative select transactions until the block is full. The proposer will then
send the block to other validators in the network. When a validator receives a
proposed block, the validator will first want to verify the contents of the block
before signing off on it. The validator will call `ProcessProposal` to verify the
contents of the block. If the block is valid, the validator will sign off on the
block and broadcast their vote to the network. If the block is invalid, the validator
will reject the block. Once a block is accepted by the network, it is committed
and the transactions that were included in the block are removed from the mempool.
### Lane Lifecycle
The Lane Constructor implements the `Lane` interface. After transactions are
check in `CheckTx`, they will be added to this lane's mempool (data structure
responsible for storing transactions). When a new block is proposed, `PrepareLane`
will be called by the `PrepareProposalHandler` defined in `abci/abci.go`. This
will trigger the lane to reap transactions from its mempool and add them to the
proposal. By default, transactions are added to proposals in the order that they
are reaped from the mempool. Transactions will only be added to a proposal
if they are valid according to the lane's verification logic. The default implementation
determines whether a transaction is valid by running the transaction through the
lane's `AnteHandler`. If any transactions are invalid, they will be removed from
lane's mempool from further consideration.
When proposals need to be verified in `ProcessProposal`, the `ProcessProposalHandler`
defined in `abci/abci.go` will call `ProcessLane` on each lane. This will trigger
the lane to process all transactions that are included in the proposal. Lane's
should only verify transactions that belong to their lane. The default implementation
of `ProcessLane` will first check that transactions that should belong to the
current lane are ordered correctly in the proposal. If they are not, the proposal
will be rejected. If they are, the lane will run the transactions through its `ProcessLaneHandler`
which is responsible for verifying the transactions against the lane's verification
logic. If any transactions are invalid, the proposal will be rejected.

View File

@ -1,6 +1,6 @@
# 🎨 Lane Constructor
> 🏗️ Build your own lane in less than 30 minutes using the Lane Constructor
> 🏗️ Build your own lane in less than 10 minutes using the Lane Constructor
## 💡 Overview
@ -8,52 +8,7 @@ The Lane Constructor is a generic implementation of a lane. It comes out of the
box with default implementations for all the required interfaces. It is meant to
be used as a starting point for building your own lane.
## 🤔 How does it work
### Transaction Lifecycle
The best way to understand how lanes work is to first understand the lifecycle
of a transaction. When a transaction is submitted to the chain, it will be checked
in `CheckTx` by the base application. If the transaction is valid, it will be
inserted into the applications mempool. The transaction then waits in the mempool
until a new block needs to be proposed. When a new block needs to be proposed,
the application will call `PrepareProposal` (which is a new ABCI++ addition) to
request a new block from the current proposer. The proposer will look at what the
transactions currently waiting to be included in a block in their mempool and
will iterative select transactions until the block is full. The proposer will then
send the block to other validators in the network. When a validator receives a
proposed block, the validator will first want to verify the contents of the block
before signing off on it. The validator will call `ProcessProposal` to verify the
contents of the block. If the block is valid, the validator will sign off on the
block and broadcast their vote to the network. If the block is invalid, the validator
will reject the block. Once a block is accepted by the network, it is committed
and the transactions that were included in the block are removed from the mempool.
### Lane Lifecycle
The Lane Constructor implements the `Lane` interface. After transactions are
check in `CheckTx`, they will be added to this lane's mempool (data structure
responsible for storing transactions). When a new block is proposed, `PrepareLane`
will be called by the `PrepareProposalHandler` defined in `abci/abci.go`. This
will trigger the lane to reap transactions from its mempool and add them to the
proposal. By default, transactions are added to proposals in the order that they
are reaped from the mempool. Transactions will only be added to a proposal
if they are valid according to the lane's verification logic. The default implementation
determines whether a transaction is valid by running the transaction through the
lane's `AnteHandler`. If any transactions are invalid, they will be removed from
lane's mempool from further consideration.
When proposals need to be verified in `ProcessProposal`, the `ProcessProposalHandler`
defined in `abci/abci.go` will call `ProcessLane` on each lane. This will trigger
the lane to process all transactions that are included in the proposal. Lane's
should only verify transactions that belong to their lane. The default implementation
of `ProcessLane` will first check that transactions that should belong to the
current lane are ordered correctly in the proposal. If they are not, the proposal
will be rejected. If they are, the lane will run the transactions through its `ProcessLaneHandler`
which is responsible for verifying the transactions against the lane's verification
logic. If any transactions are invalid, the proposal will be rejected.
## How to use it
## 🤔 How to use it
There are **three** critical
components to the Lane Constructor:
@ -68,15 +23,15 @@ be accepted to this lane.
is responsible for reaping transactions from its mempool and adding them to a proposal.
This allows users to customize the order/how transactions are added to a proposal
if any custom block building logic is required.
5. [**OPTIONAL**] Users can optionally define their own `ProcessLaneHandler`, which
5. [**OPTIONAL**] Users can optionally define their own `CheckOrderHandler`, which
is responsible for determining whether transactions that are included in a proposal
and belong to a given lane are ordered correctly in a block proposal. This is useful
for lanes that require a specific ordering of transactions in a proposal.
6. [**OPTIONAL**] Users can optionally define their own `ProcessLaneHandler`, which
is responsible for processing transactions that are included in block proposals.
In the case where a custom `PrepareLaneHandler` is defined, a custom `ProcessLaneHandler`
will likely follow. This will allow a proposal to be verified against the custom
block building logic.
6. [**OPTIONAL**] Users can optionally define their own `CheckOrderHandler`, which
is responsible for determining whether transactions that are included in a proposal
and belong to a given lane are ordered correctly in a block proposal. This is useful
for lanes that require a specific ordering of transactions in a proposal.
### 1. Lane Config
@ -143,7 +98,7 @@ This is the data structure that is responsible for storing transactions
as they are being verified and are waiting to be included in proposals. `block/constructor/mempool.go`
provides an out-of-the-box implementation that should be used as a starting
point for building out the mempool and should cover most use cases. To
utilize the mempool, you must define a `TxPriority[C]` struct that does the
utilize the mempool, you must implement a `TxPriority[C]` struct that does the
following:
- Implements a `GetTxPriority` method that returns the priority (as defined
@ -155,20 +110,29 @@ should return 1, otherwise the method should return 0.
- Implements a `MinValue` method that returns the minimum priority value
that a transaction can have.
The default implementation can be found in `block/constructor/mempool.go`.
The default implementation can be found in `block/constructor/mempool.go`. What if
we wanted to prioritize transactions by the amount they have staked on chain? Well
we could do something like the following:
```golang
// DefaultTxPriority returns a default implementation of the TxPriority. It prioritizes
// transactions by their fee.
func DefaultTxPriority() TxPriority[string] {
// CustomTxPriority returns a TxPriority that prioritizes transactions by the
// amount they have staked on chain. This means that transactions with a higher
// amount staked will be prioritized over transactions with a lower amount staked.
func (p *CustomTxPriority) CustomTxPriority() TxPriority[string] {
return TxPriority[string]{
GetTxPriority: func(goCtx context.Context, tx sdk.Tx) string {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
GetTxPriority: func(ctx context.Context, tx sdk.Tx) string {
// Get the signer of the transaction.
signer := p.getTransactionSigner(tx)
// Get the total amount staked by the signer on chain.
// This is abstracted away in the example, but you can
// implement this using the staking keeper.
totalStake, err := p.getTotalStake(ctx, signer)
if err != nil {
return ""
}
return feeTx.GetFee().String()
return totalStake.String()
},
Compare: func(a, b string) int {
aCoins, _ := sdk.ParseCoinsNormalized(a)
@ -202,4 +166,208 @@ func DefaultTxPriority() TxPriority[string] {
}
```
### LaneHandlers
To utilize this mempool in a lane, all you have to then do is pass in the
`TxPriority[C]` to the `NewLaneMempool` function.
```golang
// Pseudocode for creating the custom tx priority
priorityCfg := NewPriorityConfig(
stakingKeeper,
accountKeeper,
...
)
// define your mempool that orders transactions by on chain stake
mempool := constructor.NewMempool[string](
priorityCfg.CustomTxPriority(),
cfg.TxEncoder,
cfg.MaxTxs,
)
// Initialize your lane with the mempool
lane := constructor.NewLaneConstructor(
cfg,
LaneName,
mempool,
constructor.DefaultMatchHandler(),
)
```
### 3. MatchHandler
> 🔒 `MatchHandler` Invarients
>
> The handler assumes that the transactions passed into the function are already
> ordered respecting the lane's ordering rules and respecting the ordering rules
> of the mempool relative to the lanes it has. This means that the transactions
> should already be in contiguous order.
MatchHandler is utilized to determine if a transaction should be included in
the lane. This function can be a stateless or stateful check on the transaction.
The default implementation can be found in `block/constructor/handlers.go`.
The match handler can be as custom as desired. Following the example above, if
we wanted to make a lane that only accepts transactions if they have a large
amount staked, we could do the following:
```golang
// CustomMatchHandler returns a custom implementation of the MatchHandler. It
// matches transactions that have a large amount staked. These transactions
// will then be charged no fees at execution time.
//
// NOTE: This is a stateful check on the transaction. The details of how to
// implement this are abstracted away in the example, but you can implement
// this using the staking keeper.
func (h *Handler) CustomMatchHandler() block.MatchHandler {
return func(ctx sdk.Context, tx sdk.Tx) bool {
if !h.IsStakingTx(tx) {
return false
}
signer, err := getTxSigner(tx)
if err != nil {
return false
}
stakedAmount, err := h.GetStakedAmount(signer)
if err != nil {
return false
}
return stakeAmount.GT(h.Threshold)
}
}
```
If we wanted to create the lane using the custom match handler along with the
custom mempool, we could do the following:
```golang
// Pseudocode for creating the custom match handler
handler := NewHandler(
stakingKeeper,
accountKeeper,
...
)
// define your mempool that orders transactions by on chain stake
mempool := constructor.NewMempool[string](
priorityCfg.CustomTxPriority(),
cfg.TxEncoder,
cfg.MaxTxs,
)
// Initialize your lane with the mempool
lane := constructor.NewLaneConstructor(
cfg,
LaneName,
mempool,
handler.CustomMatchHandler(),
)
```
### Notes on Steps 4-6
Although not required, if you implement any single custom handler, whether it's
the `PrepareLaneHandler`, `ProcessLaneHandler`, or `CheckOrderHandler`, you must
implement all of them. This is because the default implementation of the lane
constructor will call all of these handlers. If you do not implement all of them,
the lane may have unintended behavior.
### 4. [OPTIONAL] PrepareLaneHandler
> 🔒 `PrepareLaneHandler` Invarients
>
> Transactions should be reaped respecting the priority mechanism of the lane.
> By default this is the TxPriority object used to initialize the lane's mempool.
The `PrepareLaneHandler` is an optional field you can set on the lane constructor.
This handler is responsible for the transaction selection logic when a new proposal
is requested. The default implementation should fit most use cases and can be found
in `block/constructor/handlers.go`.
The handler should return the following for a given lane:
1. The transactions to be included in the block proposal.
2. The transactions to be removed from the mempool.
3. An error if the lane is unable to prepare a block proposal.
```golang
// PrepareLaneHandler is responsible for preparing transactions to be included
// in the block from a given lane. Given a lane, this function should return
// the transactions to include in the block, the transactions that must be
// removed from the lane, and an error if one occurred.
PrepareLaneHandler func(
ctx sdk.Context,
proposal BlockProposal,
maxTxBytes int64,
) (txsToInclude [][]byte, txsToRemove []sdk.Tx, err error)
```
The default implementation is simple. It will continue to select transactions
from its mempool under the following criteria:
1. The transactions is not already included in the block proposal.
2. The transaction is valid and passes the AnteHandler check.
3. The transaction is not too large to be included in the block.
If a more involved selection process is required, you can implement your own
`PrepareLaneHandler` and and set it after creating the lane constructor.
```golang
customLane := constructor.NewLaneConstructor(
cfg,
LaneName,
mempool,
handler.CustomMatchHandler(),
)
customLane.SetPrepareLaneHandler(customlane.PrepareLaneHandler())
```
### 5. [OPTIONAL] CheckOrderHandler
> 🔒 `CheckOrderHandler` Invarients
>
> The CheckOrderHandler must ensure that transactions included in block proposals
> only include transactions that are in contiguous order respecting the lane's
> ordering rules and respecting the ordering rules of the mempool relative to the
> lanes it has. This means that all transactions that belong to the same lane, must
> be right next to each other in the block proposal. Additionally, the relative priority
> of each transaction belonging to the lane must be respected.
The `CheckOrderHandler` is an optional field you can set on the lane constructor.
### 6. [OPTIONAL] ProcessLaneHandler
> 🔒 `ProcessLaneHandler` Invarients
>
> The handler assumes that the transactions passed into the function are already
> ordered respecting the lane's ordering rules and respecting the ordering rules
> of the mempool relative to the lanes it has. This means that the transactions
> should already be in contiguous order.
The `ProcessLaneHandler` is an optional field you can set on the lane constructor.
This handler is responsible for verifying the transactions in the block proposal
that belong to the lane. The default implementation should fit most use cases and
can be found in `block/constructor/handlers.go`.
```golang
// ProcessLaneHandler is responsible for processing transactions that are
// included in a block and belong to a given lane. ProcessLaneHandler is
// executed after CheckOrderHandler so the transactions passed into this
// function SHOULD already be in order respecting the ordering rules of the
// lane and respecting the ordering rules of mempool relative to the lanes it has.
ProcessLaneHandler func(ctx sdk.Context, txs []sdk.Tx) ([]sdk.Tx, error)
```
Given the invarients above, the default implementation is simple. It will
continue to verify transactions in the block proposal under the following
criteria:
1. If a transaction matches to this lane, verify it and continue. If it is not
valid, return an error.
2. If a transaction does not match to this lane, return the remaining transactions.