init constructor docs
This commit is contained in:
parent
aecb504ae8
commit
4072ad8cb8
399
block/README.md
399
block/README.md
@ -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 isn’t enforced).
|
||||
|
||||
### Proposals
|
||||
|
||||
The ordering of lanes when initializing BlockBuster in base app will determine
|
||||
the ordering of how proposals are built. For example, say that we instantiate
|
||||
three lanes:
|
||||
|
||||
1. Top of block lane
|
||||
2. Free lane
|
||||
3. Default lane
|
||||
|
||||
#### Preparing Proposals
|
||||
|
||||
When the current proposer starts building a block, it will first populate the
|
||||
proposal with transactions from the 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:
|
||||
|
||||
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 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.
|
||||
|
||||
```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. 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.
|
||||
|
||||
* `PrepareLane` will be in charge of building a partial block given the
|
||||
transactions in the lane.
|
||||
* `ProcessLaneBasic` ensures that transactions that should be included in the
|
||||
current lane are not interleaved with other lanes i.e. transactions in
|
||||
proposals are ordered respecting the ordering of lanes.
|
||||
* `ProcessLane` will be in charge of verifying the lane’s partial block.
|
||||
|
||||
### Inheritance
|
||||
|
||||
Lanes can inherit the underlying implementation of other lanes and overwrite
|
||||
any part of the implementation with their own custom functionality. We
|
||||
recommend that user’s extend the functionality of the `Base` lane when first
|
||||
exploring the code base.
|
||||
|
||||
## 🤔 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.
|
||||
@ -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.
|
||||
Loading…
Reference in New Issue
Block a user