This commit is contained in:
David Terpay 2023-08-14 18:18:23 -04:00
parent 7005d4c8a3
commit 605d94f201
No known key found for this signature in database
GPG Key ID: 627EFB00DADF0CD1
11 changed files with 1081 additions and 1432 deletions

View File

@ -7,257 +7,247 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/utils"
"github.com/skip-mev/pob/x/builder/types"
)
// PrepareLane will attempt to select the highest bid transaction that is valid
// PrepareLaneHandler will attempt to select the highest bid transaction that is valid
// and whose bundled transactions are valid and include them in the proposal. It
// will return an empty partial proposal if no valid bids are found.
func (l *TOBLane) PrepareLane(
ctx sdk.Context,
proposal blockbuster.BlockProposal,
maxTxBytes int64,
next blockbuster.PrepareLanesHandler,
) (blockbuster.BlockProposal, error) {
// Define all of the info we need to select transactions for the partial proposal.
var (
txs [][]byte
txsToRemove = make(map[sdk.Tx]struct{}, 0)
)
// will return no transactions if no valid bids are found. If any of the bids are invalid,
// it will return them and will only remove the bids and not the bundled transactions.
func (l *TOBLane) PrepareLaneHandler() blockbuster.PrepareLaneHandler {
return func(ctx sdk.Context, proposal blockbuster.BlockProposal, maxTxBytes int64) ([][]byte, []sdk.Tx, error) {
// Define all of the info we need to select transactions for the partial proposal.
var (
txs [][]byte
txsToRemove []sdk.Tx
)
// Attempt to select the highest bid transaction that is valid and whose
// bundled transactions are valid.
bidTxIterator := l.Select(ctx, nil)
selectBidTxLoop:
for ; bidTxIterator != nil; bidTxIterator = bidTxIterator.Next() {
cacheCtx, write := ctx.CacheContext()
tmpBidTx := bidTxIterator.Tx()
// Attempt to select the highest bid transaction that is valid and whose
// bundled transactions are valid.
bidTxIterator := l.Select(ctx, nil)
selectBidTxLoop:
for ; bidTxIterator != nil; bidTxIterator = bidTxIterator.Next() {
cacheCtx, write := ctx.CacheContext()
tmpBidTx := bidTxIterator.Tx()
bidTxBz, hash, err := utils.GetTxHashStr(l.Cfg.TxEncoder, tmpBidTx)
if err != nil {
l.Logger().Info("failed to get hash of auction bid tx", "err", err)
bidTxBz, hash, err := utils.GetTxHashStr(l.TxEncoder(), tmpBidTx)
if err != nil {
l.Logger().Info("failed to get hash of auction bid tx", "err", err)
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
txsToRemove = append(txsToRemove, tmpBidTx)
continue selectBidTxLoop
}
// if the transaction is already in the (partial) block proposal, we skip it.
if proposal.Contains(bidTxBz) {
l.Logger().Info(
"failed to select auction bid tx for lane; tx is already in proposal",
"tx_hash", hash,
)
continue selectBidTxLoop
}
bidTxSize := int64(len(bidTxBz))
if bidTxSize <= maxTxBytes {
// Build the partial proposal by selecting the bid transaction and all of
// its bundled transactions.
bidInfo, err := l.GetAuctionBidInfo(tmpBidTx)
if err != nil {
l.Logger().Info(
"failed to get auction bid info",
"tx_hash", hash,
"err", err,
)
// Some transactions in the bundle may be malformed or invalid, so we
// remove the bid transaction and try the next top bid.
txsToRemove = append(txsToRemove, tmpBidTx)
continue selectBidTxLoop
}
// Verify the bid transaction and all of its bundled transactions.
if err := l.VerifyTx(cacheCtx, tmpBidTx, bidInfo); err != nil {
l.Logger().Info(
"failed to verify auction bid tx",
"tx_hash", hash,
"err", err,
)
txsToRemove = append(txsToRemove, tmpBidTx)
continue selectBidTxLoop
}
// store the bytes of each ref tx as sdk.Tx bytes in order to build a valid proposal
bundledTxBz := make([][]byte, len(bidInfo.Transactions))
for index, rawRefTx := range bidInfo.Transactions {
sdkTx, err := l.WrapBundleTransaction(rawRefTx)
if err != nil {
l.Logger().Info(
"failed to wrap bundled tx",
"tx_hash", hash,
"err", err,
)
txsToRemove = append(txsToRemove, tmpBidTx)
continue selectBidTxLoop
}
sdkTxBz, _, err := utils.GetTxHashStr(l.TxEncoder(), sdkTx)
if err != nil {
l.Logger().Info(
"failed to get hash of bundled tx",
"tx_hash", hash,
"err", err,
)
txsToRemove = append(txsToRemove, tmpBidTx)
continue selectBidTxLoop
}
// if the transaction is already in the (partial) block proposal, we skip it.
if proposal.Contains(sdkTxBz) {
l.Logger().Info(
"failed to select auction bid tx for lane; tx is already in proposal",
"tx_hash", hash,
)
continue selectBidTxLoop
}
bundleTxBz := make([]byte, len(sdkTxBz))
copy(bundleTxBz, sdkTxBz)
bundledTxBz[index] = sdkTxBz
}
// At this point, both the bid transaction itself and all the bundled
// transactions are valid. So we select the bid transaction along with
// all the bundled transactions. We also mark these transactions as seen and
// update the total size selected thus far.
txs = append(txs, bidTxBz)
txs = append(txs, bundledTxBz...)
// Write the cache context to the original context when we know we have a
// valid top of block bundle.
write()
break selectBidTxLoop
}
// if the transaction is already in the (partial) block proposal, we skip it.
if proposal.Contains(bidTxBz) {
l.Logger().Info(
"failed to select auction bid tx for lane; tx is already in proposal",
"tx_hash", hash,
"failed to select auction bid tx for lane; tx size is too large",
"tx_size", bidTxSize,
"max_size", maxTxBytes,
)
continue selectBidTxLoop
}
bidTxSize := int64(len(bidTxBz))
if bidTxSize <= maxTxBytes {
// Verify the bid transaction and all of its bundled transactions.
if err := l.VerifyTx(cacheCtx, tmpBidTx); err != nil {
l.Logger().Info(
"failed to verify auction bid tx",
"tx_hash", hash,
"err", err,
)
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
// Build the partial proposal by selecting the bid transaction and all of
// its bundled transactions.
bidInfo, err := l.GetAuctionBidInfo(tmpBidTx)
if bidInfo == nil || err != nil {
l.Logger().Info(
"failed to get auction bid info",
"tx_hash", hash,
"err", err,
)
// Some transactions in the bundle may be malformed or invalid, so we
// remove the bid transaction and try the next top bid.
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
// store the bytes of each ref tx as sdk.Tx bytes in order to build a valid proposal
bundledTxBz := make([][]byte, len(bidInfo.Transactions))
for index, rawRefTx := range bidInfo.Transactions {
sdkTx, err := l.WrapBundleTransaction(rawRefTx)
if err != nil {
l.Logger().Info(
"failed to wrap bundled tx",
"tx_hash", hash,
"err", err,
)
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
sdkTxBz, _, err := utils.GetTxHashStr(l.Cfg.TxEncoder, sdkTx)
if err != nil {
l.Logger().Info(
"failed to get hash of bundled tx",
"tx_hash", hash,
"err", err,
)
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
// if the transaction is already in the (partial) block proposal, we skip it.
if proposal.Contains(sdkTxBz) {
l.Logger().Info(
"failed to select auction bid tx for lane; tx is already in proposal",
"tx_hash", hash,
)
continue selectBidTxLoop
}
bundleTxBz := make([]byte, len(sdkTxBz))
copy(bundleTxBz, sdkTxBz)
bundledTxBz[index] = sdkTxBz
}
// At this point, both the bid transaction itself and all the bundled
// transactions are valid. So we select the bid transaction along with
// all the bundled transactions. We also mark these transactions as seen and
// update the total size selected thus far.
txs = append(txs, bidTxBz)
txs = append(txs, bundledTxBz...)
// Write the cache context to the original context when we know we have a
// valid top of block bundle.
write()
break selectBidTxLoop
}
l.Logger().Info(
"failed to select auction bid tx for lane; tx size is too large",
"tx_size", bidTxSize,
"max_size", maxTxBytes,
)
return txs, txsToRemove, nil
}
// Remove all transactions that were invalid during the creation of the partial proposal.
if err := utils.RemoveTxsFromLane(txsToRemove, l.Mempool); err != nil {
l.Logger().Error(
"failed to remove transactions from lane",
"lane", l.Name(),
"err", err,
)
return proposal, err
}
// Update the proposal with the selected transactions. This will only return an error
// if the invarient checks are not passed. In the case when this errors, the original proposal
// will be returned (without the selected transactions from this lane).
if err := proposal.UpdateProposal(l, txs); err != nil {
return proposal, err
}
return next(ctx, proposal)
}
// ProcessLane will ensure that block proposals that include transactions from
// ProcessLaneHandler will ensure that block proposals that include transactions from
// the top-of-block auction lane are valid.
func (l *TOBLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next blockbuster.ProcessLanesHandler) (sdk.Context, error) {
if len(txs) == 0 {
return next(ctx, txs)
}
func (l *TOBLane) ProcessLaneHandler() blockbuster.ProcessLaneHandler {
return func(ctx sdk.Context, txs []sdk.Tx) ([]sdk.Tx, error) {
if len(txs) == 0 {
return txs, nil
}
bidTx := txs[0]
if !l.Match(ctx, bidTx) {
return next(ctx, txs)
}
bidTx := txs[0]
if !l.Match(ctx, bidTx) {
return txs, nil
}
bidInfo, err := l.GetAuctionBidInfo(bidTx)
if err != nil {
return ctx, fmt.Errorf("failed to get bid info for lane %s: %w", l.Name(), err)
}
bidInfo, err := l.GetAuctionBidInfo(bidTx)
if err != nil {
return nil, fmt.Errorf("failed to get bid info for lane %s: %w", l.Name(), err)
}
if err := l.VerifyTx(ctx, bidTx); err != nil {
return ctx, fmt.Errorf("invalid bid tx: %w", err)
}
if err := l.VerifyTx(ctx, bidTx, bidInfo); err != nil {
return nil, fmt.Errorf("invalid bid tx: %w", err)
}
return next(ctx, txs[len(bidInfo.Transactions)+1:])
return txs[len(bidInfo.Transactions)+1:], nil
}
}
// ProcessLaneBasic ensures that if a bid transaction is present in a proposal,
// CheckOrderHandler ensures that if a bid transaction is present in a proposal,
// - it is the first transaction in the partial proposal
// - 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(ctx sdk.Context, txs []sdk.Tx) error {
if len(txs) == 0 {
return nil
}
// - transactions from other lanes are not interleaved with transactions from the bid
// transaction.
func (l *TOBLane) CheckOrderHandler() blockbuster.CheckOrderHandler {
return func(ctx sdk.Context, txs []sdk.Tx) error {
if len(txs) == 0 {
return nil
}
bidTx := txs[0]
bidTx := txs[0]
// If there is a bid transaction, it must be the first transaction in the block proposal.
if !l.Match(ctx, bidTx) {
for _, tx := range txs[1:] {
// If there is a bid transaction, it must be the first transaction in the block proposal.
if !l.Match(ctx, bidTx) {
for _, tx := range txs[1:] {
if l.Match(ctx, tx) {
return fmt.Errorf("misplaced bid transactions in lane %s", l.Name())
}
}
return nil
}
bidInfo, err := l.GetAuctionBidInfo(bidTx)
if err != nil {
return fmt.Errorf("failed to get bid info for lane %s: %w", l.Name(), err)
}
if len(txs) < len(bidInfo.Transactions)+1 {
return fmt.Errorf(
"invalid number of transactions in lane %s; expected at least %d, got %d",
l.Name(),
len(bidInfo.Transactions)+1,
len(txs),
)
}
// Ensure that the order of transactions in the bundle is preserved.
for i, bundleTx := range txs[1 : len(bidInfo.Transactions)+1] {
if l.Match(ctx, bundleTx) {
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
}
txBz, err := l.TxEncoder()(bundleTx)
if err != nil {
return fmt.Errorf("failed to encode bundled tx in lane %s: %w", l.Name(), err)
}
if !bytes.Equal(txBz, bidInfo.Transactions[i]) {
return fmt.Errorf("invalid order of transactions in lane %s", l.Name())
}
}
// Ensure that there are no more bid transactions in the block proposal.
for _, tx := range txs[len(bidInfo.Transactions)+1:] {
if l.Match(ctx, tx) {
return fmt.Errorf("misplaced bid transactions in lane %s", l.Name())
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
}
}
return nil
}
bidInfo, err := l.GetAuctionBidInfo(bidTx)
if err != nil {
return fmt.Errorf("failed to get bid info for lane %s: %w", l.Name(), err)
}
if len(txs) < len(bidInfo.Transactions)+1 {
return fmt.Errorf("invalid number of transactions in lane %s; expected at least %d, got %d", l.Name(), len(bidInfo.Transactions)+1, len(txs))
}
// Ensure that the order of transactions in the bundle is preserved.
for i, bundleTx := range txs[1 : len(bidInfo.Transactions)+1] {
if l.Match(ctx, bundleTx) {
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
}
txBz, err := l.Cfg.TxEncoder(bundleTx)
if err != nil {
return fmt.Errorf("failed to encode bundled tx in lane %s: %w", l.Name(), err)
}
if !bytes.Equal(txBz, bidInfo.Transactions[i]) {
return fmt.Errorf("invalid order of transactions in lane %s", l.Name())
}
}
// Ensure that there are no more bid transactions in the block proposal.
for _, tx := range txs[len(bidInfo.Transactions)+1:] {
if l.Match(ctx, tx) {
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
}
}
return nil
}
// VerifyTx will verify that the bid transaction and all of its bundled
// transactions are valid. It will return an error if any of the transactions
// are invalid.
func (l *TOBLane) VerifyTx(ctx sdk.Context, bidTx sdk.Tx) error {
bidInfo, err := l.GetAuctionBidInfo(bidTx)
if err != nil {
return fmt.Errorf("failed to get auction bid info: %w", err)
func (l *TOBLane) VerifyTx(ctx sdk.Context, bidTx sdk.Tx, bidInfo *types.BidInfo) (err error) {
if bidInfo == nil {
return fmt.Errorf("bid info is nil")
}
// verify the top-level bid transaction
ctx, err = l.verifyTx(ctx, bidTx)
if err != nil {
if ctx, err = l.AnteVerifyTx(ctx, bidTx, false); err != nil {
return fmt.Errorf("invalid bid tx; failed to execute ante handler: %w", err)
}
@ -268,27 +258,10 @@ func (l *TOBLane) VerifyTx(ctx sdk.Context, bidTx sdk.Tx) error {
return fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err)
}
// bid txs cannot be included in bundled txs
bidInfo, _ := l.GetAuctionBidInfo(bundledTx)
if bidInfo != nil {
return fmt.Errorf("invalid bid tx; bundled tx cannot be a bid tx")
}
if ctx, err = l.verifyTx(ctx, bundledTx); err != nil {
if ctx, err = l.AnteVerifyTx(ctx, bundledTx, false); err != nil {
return fmt.Errorf("invalid bid tx; failed to execute bundled transaction: %w", err)
}
}
return nil
}
// verifyTx will execute the ante handler on the transaction and return the
// resulting context and error.
func (l *TOBLane) verifyTx(ctx sdk.Context, tx sdk.Tx) (sdk.Context, error) {
if l.Cfg.AnteHandler != nil {
newCtx, err := l.Cfg.AnteHandler(ctx, tx, false)
return newCtx, err
}
return ctx, nil
}

View File

@ -18,7 +18,6 @@ type IntegrationTestSuite struct {
encCfg testutils.EncodingConfig
config auction.Factory
mempool auction.Mempool
ctx sdk.Context
random *rand.Rand
accounts []testutils.Account
@ -33,7 +32,6 @@ func (suite *IntegrationTestSuite) SetupTest() {
// Mempool setup
suite.encCfg = testutils.CreateTestEncodingConfig()
suite.config = auction.NewDefaultAuctionFactory(suite.encCfg.TxConfig.TxDecoder())
suite.mempool = auction.NewMempool(suite.encCfg.TxConfig.TxEncoder(), 0, suite.config)
suite.ctx = sdk.NewContext(nil, cmtproto.Header{}, false, log.NewTestLogger(suite.T()))
// Init accounts

View File

@ -5,6 +5,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/x/builder/types"
)
@ -21,6 +22,9 @@ type (
// GetAuctionBidInfo defines a function that returns the bid info from an auction transaction.
GetAuctionBidInfo(tx sdk.Tx) (*types.BidInfo, error)
// MatchHandler defines a function that checks if a transaction matches the auction lane.
MatchHandler() blockbuster.MatchHandler
}
// DefaultAuctionFactory defines a default implmentation for the auction factory interface for processing auction transactions.
@ -91,6 +95,13 @@ func (config *DefaultAuctionFactory) GetAuctionBidInfo(tx sdk.Tx) (*types.BidInf
}, nil
}
func (config *DefaultAuctionFactory) MatchHandler() blockbuster.MatchHandler {
return func(ctx sdk.Context, tx sdk.Tx) bool {
bidInfo, err := config.GetAuctionBidInfo(tx)
return bidInfo != nil && err == nil
}
}
// getBundleSigners defines a default function that returns the signers of all transactions in
// a bundle. In the default case, each bundle transaction will be an sdk.Tx and the
// signers are the signers of each sdk.Msg in the transaction.

View File

@ -1,9 +1,10 @@
package auction
import (
"context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/lanes/base"
)
const (
@ -12,8 +13,7 @@ const (
)
var (
_ blockbuster.Lane = (*TOBLane)(nil)
_ Factory = (*TOBLane)(nil)
_ TOBLaneI = (*TOBLane)(nil)
)
// TOBLane defines a top-of-block auction lane. The top of block auction lane
@ -22,43 +22,53 @@ var (
// their bid price. The highest valid bid transaction is selected for inclusion in the
// next block. The bundled transactions of the selected bid transaction are also
// included in the next block.
type TOBLane struct {
// Mempool defines the mempool for the lane.
Mempool
type (
// TOBLaneI defines the interface for the top-of-block auction lane. This interface
// is utilized by both the x/builder module and the checkTx handler.
TOBLaneI interface {
blockbuster.Lane
Factory
GetTopAuctionTx(ctx context.Context) sdk.Tx
}
// LaneConfig defines the base lane configuration.
*base.DefaultLane
TOBLane struct {
// LaneConfig defines the base lane configuration.
*blockbuster.LaneConstructor[string]
// Factory defines the API/functionality which is responsible for determining
// if a transaction is a bid transaction and how to extract relevant
// information from the transaction (bid, timeout, bidder, etc.).
Factory
}
// Factory defines the API/functionality which is responsible for determining
// if a transaction is a bid transaction and how to extract relevant
// information from the transaction (bid, timeout, bidder, etc.).
Factory
}
)
// NewTOBLane returns a new TOB lane.
func NewTOBLane(
cfg blockbuster.BaseLaneConfig,
maxTx int,
af Factory,
cfg blockbuster.LaneConfig,
factory Factory,
) *TOBLane {
if err := cfg.ValidateBasic(); err != nil {
panic(err)
lane := &TOBLane{
LaneConstructor: blockbuster.NewLaneConstructor[string](
cfg,
LaneName,
blockbuster.NewConstructorMempool[string](
TxPriority(factory),
cfg.TxEncoder,
cfg.MaxTxs,
),
factory.MatchHandler(),
),
Factory: factory,
}
return &TOBLane{
Mempool: NewMempool(cfg.TxEncoder, maxTx, af),
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(ctx sdk.Context, tx sdk.Tx) bool {
if l.MatchIgnoreList(ctx, tx) {
return false
}
bidInfo, err := l.GetAuctionBidInfo(tx)
return bidInfo != nil && err == nil
// Set the prepare lane handler to the TOB one
lane.SetPrepareLaneHandler(lane.PrepareLaneHandler())
// Set the process lane handler to the TOB one
lane.SetProcessLaneHandler(lane.ProcessLaneHandler())
// Set the check order handler to the TOB one
lane.SetCheckOrderHandler(lane.CheckOrderHandler())
return lane
}

View File

@ -2,48 +2,9 @@ package auction
import (
"context"
"errors"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/utils"
)
var _ Mempool = (*TOBMempool)(nil)
type (
// Mempool defines the interface of the auction mempool.
Mempool interface {
sdkmempool.Mempool
// GetTopAuctionTx returns the highest bidding transaction in the auction mempool.
GetTopAuctionTx(ctx context.Context) sdk.Tx
// Contains returns true if the transaction is contained in the mempool.
Contains(tx sdk.Tx) bool
}
// TOBMempool defines an auction mempool. It can be seen as an extension of
// an SDK PriorityNonceMempool, i.e. a mempool that supports <sender, nonce>
// two-dimensional priority ordering, with the additional support of prioritizing
// and indexing auction bids.
TOBMempool struct {
// index defines an index of auction bids.
index sdkmempool.Mempool
// txEncoder defines the sdk.Tx encoder that allows us to encode transactions
// to bytes.
txEncoder sdk.TxEncoder
// txIndex is a map of all transactions in the mempool. It is used
// to quickly check if a transaction is already in the mempool.
txIndex map[string]struct{}
// Factory implements the functionality required to process auction transactions.
Factory
}
)
// TxPriority returns a TxPriority over auction bid transactions only. It
@ -89,78 +50,13 @@ func TxPriority(config Factory) blockbuster.TxPriority[string] {
}
}
// NewMempool returns a new auction mempool.
func NewMempool(txEncoder sdk.TxEncoder, maxTx int, config Factory) *TOBMempool {
return &TOBMempool{
index: blockbuster.NewPriorityMempool(
blockbuster.PriorityNonceMempoolConfig[string]{
TxPriority: TxPriority(config),
MaxTx: maxTx,
},
),
txEncoder: txEncoder,
txIndex: make(map[string]struct{}),
Factory: config,
}
}
// Insert inserts a transaction into the auction mempool.
func (am *TOBMempool) Insert(ctx context.Context, tx sdk.Tx) error {
if err := am.index.Insert(ctx, tx); err != nil {
return fmt.Errorf("failed to insert tx into auction index: %w", err)
}
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
return err
}
am.txIndex[txHashStr] = struct{}{}
return nil
}
// Remove removes a transaction from the mempool based.
func (am *TOBMempool) Remove(tx sdk.Tx) error {
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 fmt.Errorf("failed to get tx hash string: %w", err)
}
delete(am.txIndex, txHashStr)
return nil
}
// GetTopAuctionTx returns the highest bidding transaction in the auction mempool.
func (am *TOBMempool) GetTopAuctionTx(ctx context.Context) sdk.Tx {
iterator := am.index.Select(ctx, nil)
// This is primarily a helper function for the x/builder module.
func (l *TOBLane) GetTopAuctionTx(ctx context.Context) sdk.Tx {
iterator := l.Select(ctx, nil)
if iterator == nil {
return nil
}
return iterator.Tx()
}
func (am *TOBMempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator {
return am.index.Select(ctx, txs)
}
func (am *TOBMempool) CountTx() int {
return am.index.CountTx()
}
// Contains returns true if the transaction is contained in the mempool.
func (am *TOBMempool) Contains(tx sdk.Tx) bool {
_, txHashStr, err := utils.GetTxHashStr(am.txEncoder, tx)
if err != nil {
return false
}
_, ok := am.txIndex[txHashStr]
return ok
}

View File

@ -31,11 +31,15 @@ type (
GetLane(name string) (Lane, error)
}
// Mempool defines the Blockbuster mempool implement. It contains a registry
// BBMempool defines the Blockbuster mempool implementation. It contains a registry
// of lanes, which allows for customizable block proposal construction.
BBMempool struct {
logger log.Logger
// registry contains the lanes in the mempool. The lanes are ordered
// according to their priority. The first lane in the registry has the
// highest priority and the last lane has the lowest priority.
registry []Lane
logger log.Logger
}
)
@ -47,8 +51,10 @@ type (
// 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 {
// attempt to insert, remove transactions from all lanes it belongs to. It is recommended,
// that mutex is set to true when creating the mempool. This will ensure that each
// transaction cannot be inserted into the lanes before it.
func NewMempool(logger log.Logger, mutex bool, lanes ...Lane) *BBMempool {
mempool := &BBMempool{
logger: logger,
registry: lanes,
@ -58,6 +64,16 @@ func NewMempool(logger log.Logger, lanes ...Lane) *BBMempool {
panic(err)
}
// Set the ignore list for each lane
if mutex {
registry := mempool.registry
for index, lane := range mempool.registry {
if index > 0 {
lane.SetIgnoreList(registry[:index])
}
}
}
return mempool
}
@ -85,7 +101,14 @@ func (m *BBMempool) GetTxDistribution() map[string]int {
// 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 {
func (m *BBMempool) Insert(ctx context.Context, tx sdk.Tx) (err error) {
defer func() {
if r := recover(); r != nil {
m.logger.Error("panic in Insert", "err", r)
err = fmt.Errorf("panic in Insert: %v", r)
}
}()
var errors []string
unwrappedCtx := sdk.UnwrapSDKContext(ctx)
@ -118,7 +141,14 @@ func (m *BBMempool) Select(_ context.Context, _ [][]byte) sdkmempool.Iterator {
}
// Remove removes a transaction from all of the lanes it is currently in.
func (m *BBMempool) Remove(tx sdk.Tx) error {
func (m *BBMempool) Remove(tx sdk.Tx) (err error) {
defer func() {
if r := recover(); r != nil {
m.logger.Error("panic in Remove", "err", r)
err = fmt.Errorf("panic in Remove: %v", r)
}
}()
var errors []string
for _, lane := range m.registry {
@ -149,7 +179,14 @@ func (m *BBMempool) Remove(tx sdk.Tx) error {
}
// Contains returns true if the transaction is contained in any of the lanes.
func (m *BBMempool) Contains(tx sdk.Tx) bool {
func (m *BBMempool) Contains(tx sdk.Tx) (contains bool) {
defer func() {
if r := recover(); r != nil {
m.logger.Error("panic in Contains", "err", r)
contains = false
}
}()
for _, lane := range m.registry {
if lane.Contains(tx) {
return true
@ -164,7 +201,10 @@ func (m *BBMempool) Registry() []Lane {
return m.registry
}
// ValidateBasic validates the mempools configuration.
// ValidateBasic validates the mempools configuration. ValidateBasic ensures
// the following:
// - The sum of the lane max block space percentages is less than or equal to 1.
// - There is no unused block space.
func (m *BBMempool) ValidateBasic() error {
sum := math.LegacyZeroDec()
seenZeroMaxBlockSpace := false

View File

@ -27,9 +27,10 @@ type BlockBusterTestSuite struct {
encodingConfig testutils.EncodingConfig
// Define all of the lanes utilized in the test suite
tobLane *auction.TOBLane
baseLane *base.DefaultLane
freeLane *free.Lane
tobLane *auction.TOBLane
baseLane *base.DefaultLane
freeLane *free.FreeLane
gasTokenDenom string
lanes []blockbuster.Lane
mempool blockbuster.Mempool
@ -55,7 +56,8 @@ func (suite *BlockBusterTestSuite) SetupTest() {
// Lanes configuration
//
// TOB lane set up
tobConfig := blockbuster.BaseLaneConfig{
suite.gasTokenDenom = "stake"
tobConfig := blockbuster.LaneConfig{
Logger: log.NewNopLogger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
@ -64,37 +66,30 @@ func (suite *BlockBusterTestSuite) SetupTest() {
}
suite.tobLane = auction.NewTOBLane(
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{
freeConfig := blockbuster.LaneConfig{
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(
freeConfig,
free.NewDefaultFreeFactory(suite.encodingConfig.TxConfig.TxDecoder()),
blockbuster.DefaultTxPriority(),
free.DefaultMatchHandler(),
)
// Base lane set up
baseConfig := blockbuster.BaseLaneConfig{
baseConfig := blockbuster.LaneConfig{
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(
baseConfig,
@ -102,7 +97,7 @@ func (suite *BlockBusterTestSuite) SetupTest() {
// Mempool set up
suite.lanes = []blockbuster.Lane{suite.tobLane, suite.freeLane, suite.baseLane}
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), suite.lanes...)
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), true, suite.lanes...)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 10)
@ -328,12 +323,12 @@ func (suite *BlockBusterTestSuite) fillBaseLane(numTxs int) {
// create a few random msgs and construct the tx
nonce := suite.nonces[acc.Address.String()]
randomMsgs := testutils.CreateRandomMsgs(acc.Address, 3)
tx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, randomMsgs)
priority := suite.random.Int63n(100) + 1
tx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, randomMsgs, sdk.NewCoin(suite.gasTokenDenom, math.NewInt(priority)))
suite.Require().NoError(err)
// insert the tx into the lane and update the account
suite.nonces[acc.Address.String()]++
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), tx))
}
}
@ -348,7 +343,7 @@ func (suite *BlockBusterTestSuite) fillTOBLane(numTxs int) {
// create a randomized auction transaction
nonce := suite.nonces[acc.Address.String()]
bidAmount := math.NewInt(int64(suite.random.Intn(1000) + 1))
bid := sdk.NewCoin("stake", bidAmount)
bid := sdk.NewCoin(suite.gasTokenDenom, bidAmount)
tx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, acc, bid, nonce, 1000, nil)
suite.Require().NoError(err)
@ -367,7 +362,7 @@ func (suite *BlockBusterTestSuite) fillFreeLane(numTxs int) {
// create a few random msgs and construct the tx
nonce := suite.nonces[acc.Address.String()]
tx, err := testutils.CreateFreeTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, "val1", sdk.NewCoin("stake", math.NewInt(100)))
tx, err := testutils.CreateFreeTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, "val1", sdk.NewCoin(suite.gasTokenDenom, math.NewInt(100)), sdk.NewCoin(suite.gasTokenDenom, math.NewInt(100)))
suite.Require().NoError(err)
// insert the tx into the lane and update the account

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import (
)
type (
// Lane defines the required functionality for a lane. The ignore decorator
// Lane defines the required API dependencies for the IgnoreDecorator. The ignore decorator
// will check if a transaction belongs to a lane by calling the Match function.
Lane interface {
Match(ctx sdk.Context, tx sdk.Tx) bool

View File

@ -62,10 +62,10 @@ import (
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
"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"
"github.com/skip-mev/pob/blockbuster/proposals"
buildermodule "github.com/skip-mev/pob/x/builder"
builderkeeper "github.com/skip-mev/pob/x/builder/keeper"
)
@ -139,7 +139,7 @@ type TestApp struct {
FeeGrantKeeper feegrantkeeper.Keeper
// custom checkTx handler
checkTxHandler abci.CheckTx
checkTxHandler proposals.CheckTx
}
func init() {
@ -262,43 +262,39 @@ func New(
// 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.
tobConfig := blockbuster.BaseLaneConfig{
tobConfig := blockbuster.LaneConfig{
Logger: app.Logger(),
TxEncoder: app.txConfig.TxEncoder(),
TxDecoder: app.txConfig.TxDecoder(),
MaxBlockSpace: math.LegacyZeroDec(),
MaxBlockSpace: math.LegacyZeroDec(), // This means the lane has no limit on block space.
MaxTxs: 0, // This means the lane has no limit on the number of transactions it can store.
}
tobLane := auction.NewTOBLane(
tobConfig,
0,
auction.NewDefaultAuctionFactory(app.txConfig.TxDecoder()),
)
// Free lane allows transactions to be included in the next block for free.
freeConfig := blockbuster.BaseLaneConfig{
freeConfig := blockbuster.LaneConfig{
Logger: app.Logger(),
TxEncoder: app.txConfig.TxEncoder(),
TxDecoder: app.txConfig.TxDecoder(),
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{
tobLane,
},
MaxTxs: 0,
}
freeLane := free.NewFreeLane(
freeConfig,
free.NewDefaultFreeFactory(app.txConfig.TxDecoder()),
blockbuster.DefaultTxPriority(),
free.DefaultMatchHandler(),
)
// Default lane accepts all other transactions.
defaultConfig := blockbuster.BaseLaneConfig{
defaultConfig := blockbuster.LaneConfig{
Logger: app.Logger(),
TxEncoder: app.txConfig.TxEncoder(),
TxDecoder: app.txConfig.TxDecoder(),
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{
tobLane,
freeLane,
},
MaxTxs: 0,
}
defaultLane := base.NewDefaultLane(defaultConfig)
@ -308,7 +304,7 @@ func New(
freeLane,
defaultLane,
}
mempool := blockbuster.NewMempool(app.Logger(), lanes...)
mempool := blockbuster.NewMempool(app.Logger(), true, lanes...)
app.App.SetMempool(mempool)
// Create a global ante handler that will be called on each transaction when
@ -338,16 +334,16 @@ func New(
app.App.SetAnteHandler(anteHandler)
// Set the proposal handlers on base app
proposalHandler := abci.NewProposalHandler(
proposalHandler := proposals.NewProposalHandler(
app.Logger(),
app.TxConfig().TxDecoder(),
mempool,
lanes,
)
app.App.SetPrepareProposal(proposalHandler.PrepareProposalHandler())
app.App.SetProcessProposal(proposalHandler.ProcessProposalHandler())
// Set the custom CheckTx handler on BaseApp.
checkTxHandler := abci.NewCheckTxHandler(
checkTxHandler := proposals.NewCheckTxHandler(
app.App,
app.txConfig.TxDecoder(),
tobLane,
@ -396,7 +392,7 @@ func (app *TestApp) CheckTx(req *cometabci.RequestCheckTx) (*cometabci.ResponseC
}
// SetCheckTx sets the checkTxHandler for the app.
func (app *TestApp) SetCheckTx(handler abci.CheckTx) {
func (app *TestApp) SetCheckTx(handler proposals.CheckTx) {
app.checkTxHandler = handler
}

View File

@ -83,7 +83,7 @@ func (suite *AnteTestSuite) SetupTest() {
// Lanes configuration
//
// TOB lane set up
tobConfig := blockbuster.BaseLaneConfig{
tobConfig := blockbuster.LaneConfig{
Logger: suite.ctx.Logger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
@ -92,12 +92,11 @@ func (suite *AnteTestSuite) SetupTest() {
}
suite.tobLane = auction.NewTOBLane(
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{
baseConfig := blockbuster.LaneConfig{
Logger: suite.ctx.Logger(),
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
@ -109,7 +108,7 @@ func (suite *AnteTestSuite) SetupTest() {
// Mempool set up
suite.lanes = []blockbuster.Lane{suite.tobLane, suite.baseLane}
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), suite.lanes...)
suite.mempool = blockbuster.NewMempool(log.NewTestLogger(suite.T()), true, suite.lanes...)
}
func (suite *AnteTestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, _ bool) (sdk.Context, error) {