The builder Keeper
is POB's gateway to processing special MsgAuctionBid
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.
type App struct {
...
// BuilderKeeper is the keeper that handles processing auction transactions
BuilderKeeper builderkeeper.Keeper
// Custom checkTx handler
checkTxHandler abci.CheckTx
}
b. Add the builder module to the list of module account permissions. This will
instantiate the builder module account on genesis.
maccPerms = map[string][]string{
builder.ModuleName: nil,
...
}
c. Instantiate the builder keeper, store keys, and module manager. Note, be
sure to do this after all the required keeper dependencies have been instantiated.
keys := storetypes.NewKVStoreKeys(
buildertypes.StoreKey,
...
)
...
app.BuilderKeeper := builderkeeper.NewKeeper(
appCodec,
keys[buildertypes.StoreKey],
app.AccountKeeper,
app.BankKeeper,
app.DistrKeeper,
app.StakingKeeper,
authtypes.NewModuleAddress(govv1.ModuleName).String(),
)
app.ModuleManager = module.NewManager(
builder.NewAppModule(appCodec, app.BuilderKeeper),
...
)
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
application’s mempool. As such, we have to instantiate POB’s custom
AuctionMempool
- a modified version of the SDK’s 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.
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
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
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
discrepencies between the auction transaction and the bundled transactions
are validated in CheckTx
and PrepareProposal
such that the auction can be
griefed. All other transactions will be executed with base app's CheckTx
.
// 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(),
)
app.SetPrepareProposal(handler.PrepareProposalHandler())
app.SetProcessProposal(handler.ProcessProposalHandler())
// Set the custom CheckTx handler on BaseApp.
checkTxHandler := pobabci.CheckTxHandler(
app.App,
app.TxDecoder,
mempool,
anteHandler,
chainID,
)
app.SetCheckTx(checkTxHandler)
...
// CheckTx will check the transaction with the provided checkTxHandler. We override the default
// handler so that we can verify bid transactions before they are inserted into the mempool.
// With the POB CheckTx, we can verify the bid transaction and all of the bundled transactions
// before inserting the bid transaction into the mempool.
func (app *TestApp) CheckTx(req cometabci.RequestCheckTx) cometabci.ResponseCheckTx {
return app.checkTxHandler(req)
}
// SetCheckTx sets the checkTxHandler for the app.
func (app *TestApp) SetCheckTx(handler abci.CheckTx) {
app.checkTxHandler = handler
}
f. Finally, update the app's InitGenesis
order and ante-handler chain.
genesisModuleOrder := []string{
buildertypes.ModuleName,
...,
}