feat(bb): Defer in proposal handlers, more interfaces for lanes, clean up (#165)

This commit is contained in:
David Terpay
2023-06-07 13:47:34 +00:00
committed by GitHub
parent 61b6759e92
commit b7780a3140
41 changed files with 1798 additions and 3320 deletions
-285
View File
@@ -1,285 +0,0 @@
package abci
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/skip-mev/pob/mempool"
)
type (
Mempool interface {
sdkmempool.Mempool
// The AuctionFactory interface is utilized to retrieve, validate, and wrap bid
// information into the block proposal.
mempool.AuctionFactory
// AuctionBidSelect returns an iterator that iterates over the top bid
// transactions in the mempool.
AuctionBidSelect(ctx context.Context) sdkmempool.Iterator
}
ProposalHandler struct {
mempool Mempool
logger log.Logger
anteHandler sdk.AnteHandler
txEncoder sdk.TxEncoder
txDecoder sdk.TxDecoder
}
)
func NewProposalHandler(
mp Mempool,
logger log.Logger,
anteHandler sdk.AnteHandler,
txEncoder sdk.TxEncoder,
txDecoder sdk.TxDecoder,
) *ProposalHandler {
return &ProposalHandler{
mempool: mp,
logger: logger,
anteHandler: anteHandler,
txEncoder: txEncoder,
txDecoder: txDecoder,
}
}
// PrepareProposalHandler returns the PrepareProposal ABCI handler that performs
// top-of-block auctioning and general block proposal construction.
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
var (
selectedTxs [][]byte
totalTxBytes int64
)
bidTxIterator := h.mempool.AuctionBidSelect(ctx)
txsToRemove := make(map[sdk.Tx]struct{}, 0)
seenTxs := make(map[string]struct{}, 0)
// Attempt to select the highest bid transaction that is valid and whose
// bundled transactions are valid.
selectBidTxLoop:
for ; bidTxIterator != nil; bidTxIterator = bidTxIterator.Next() {
cacheCtx, write := ctx.CacheContext()
tmpBidTx := bidTxIterator.Tx()
bidTxBz, err := h.PrepareProposalVerifyTx(cacheCtx, tmpBidTx)
if err != nil {
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
bidTxSize := int64(len(bidTxBz))
if bidTxSize <= req.MaxTxBytes {
bidInfo, err := h.mempool.GetAuctionBidInfo(tmpBidTx)
if err != nil {
// Some transactions in the bundle may be malformatted 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
bundledTransactions := bidInfo.Transactions
sdkTxBytes := make([][]byte, len(bundledTransactions))
// Ensure that the bundled transactions are valid
for index, rawRefTx := range bundledTransactions {
refTx, err := h.mempool.WrapBundleTransaction(rawRefTx)
if err != nil {
// Malformed bundled transaction, so we remove the bid transaction
// and try the next top bid.
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
txBz, err := h.PrepareProposalVerifyTx(cacheCtx, refTx)
if err != nil {
// Invalid bundled transaction, so we remove the bid transaction
// and try the next top bid.
txsToRemove[tmpBidTx] = struct{}{}
continue selectBidTxLoop
}
sdkTxBytes[index] = txBz
}
// 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.
totalTxBytes += bidTxSize
selectedTxs = append(selectedTxs, bidTxBz)
selectedTxs = append(selectedTxs, sdkTxBytes...)
for _, refTxRaw := range sdkTxBytes {
hash := sha256.Sum256(refTxRaw)
txHash := hex.EncodeToString(hash[:])
seenTxs[txHash] = struct{}{}
}
// Write the cache context to the original context when we know we have a
// valid top of block bundle.
write()
break selectBidTxLoop
}
txsToRemove[tmpBidTx] = struct{}{}
h.logger.Info(
"failed to select auction bid tx; tx size is too large",
"tx_size", bidTxSize,
"max_size", req.MaxTxBytes,
)
}
// Remove all invalid transactions from the mempool.
for tx := range txsToRemove {
h.RemoveTx(tx)
}
iterator := h.mempool.Select(ctx, nil)
txsToRemove = map[sdk.Tx]struct{}{}
// Select remaining transactions for the block proposal until we've reached
// size capacity.
selectTxLoop:
for ; iterator != nil; iterator = iterator.Next() {
memTx := iterator.Tx()
// If the transaction is already included in the proposal, then we skip it.
txBz, err := h.txEncoder(memTx)
if err != nil {
txsToRemove[memTx] = struct{}{}
continue selectTxLoop
}
hash := sha256.Sum256(txBz)
txHash := hex.EncodeToString(hash[:])
if _, ok := seenTxs[txHash]; ok {
continue selectTxLoop
}
txBz, err = h.PrepareProposalVerifyTx(ctx, memTx)
if err != nil {
txsToRemove[memTx] = struct{}{}
continue selectTxLoop
}
txSize := int64(len(txBz))
if totalTxBytes += txSize; totalTxBytes <= req.MaxTxBytes {
selectedTxs = append(selectedTxs, txBz)
} else {
// We've reached capacity per req.MaxTxBytes so we cannot select any
// more transactions.
break selectTxLoop
}
}
// Remove all invalid transactions from the mempool.
for tx := range txsToRemove {
h.RemoveTx(tx)
}
return abci.ResponsePrepareProposal{Txs: selectedTxs}
}
}
// ProcessProposalHandler returns the ProcessProposal ABCI handler that performs
// block proposal verification.
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal {
for index, txBz := range req.Txs {
tx, err := h.ProcessProposalVerifyTx(ctx, txBz)
if err != nil {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
bidInfo, err := h.mempool.GetAuctionBidInfo(tx)
if err != nil {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
// If the transaction is an auction bid, then we need to ensure that it is
// the first transaction in the block proposal and that the order of
// transactions in the block proposal follows the order of transactions in
// the bid.
if bidInfo != nil {
if index != 0 {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
bundledTransactions := bidInfo.Transactions
if len(req.Txs) < len(bundledTransactions)+1 {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
for i, refTxRaw := range bundledTransactions {
// Wrap and then encode the bundled transaction to ensure that the underlying
// reference transaction can be processed as an sdk.Tx.
wrappedTx, err := h.mempool.WrapBundleTransaction(refTxRaw)
if err != nil {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
refTxBz, err := h.txEncoder(wrappedTx)
if err != nil {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
if !bytes.Equal(refTxBz, req.Txs[i+1]) {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
}
}
}
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
}
}
// PrepareProposalVerifyTx encodes a transaction and verifies it.
func (h *ProposalHandler) PrepareProposalVerifyTx(ctx sdk.Context, tx sdk.Tx) ([]byte, error) {
txBz, err := h.txEncoder(tx)
if err != nil {
return nil, err
}
return txBz, h.verifyTx(ctx, tx)
}
// ProcessProposalVerifyTx decodes a transaction and verifies it.
func (h *ProposalHandler) ProcessProposalVerifyTx(ctx sdk.Context, txBz []byte) (sdk.Tx, error) {
tx, err := h.txDecoder(txBz)
if err != nil {
return nil, err
}
return tx, h.verifyTx(ctx, tx)
}
// VerifyTx verifies a transaction against the application's state.
func (h *ProposalHandler) verifyTx(ctx sdk.Context, tx sdk.Tx) error {
if h.anteHandler != nil {
_, err := h.anteHandler(ctx, tx, false)
return err
}
return nil
}
func (h *ProposalHandler) RemoveTx(tx sdk.Tx) {
if err := h.mempool.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
panic(fmt.Errorf("failed to remove invalid transaction from the mempool: %w", err))
}
}
+146 -620
View File
@@ -1,22 +1,20 @@
package abci_test
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"testing"
"time"
abcitypes "github.com/cometbft/cometbft/abci/types"
comettypes "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/skip-mev/pob/abci"
"github.com/skip-mev/pob/mempool"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/lanes/base"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/ante"
"github.com/skip-mev/pob/x/builder/keeper"
@@ -28,17 +26,15 @@ type ABCITestSuite struct {
suite.Suite
ctx sdk.Context
// mempool setup
mempool *mempool.AuctionMempool
logger log.Logger
encodingConfig testutils.EncodingConfig
proposalHandler *abci.ProposalHandler
config mempool.AuctionFactory
txs map[string]struct{}
// mempool and lane set up
mempool blockbuster.Mempool
tobLane *auction.TOBLane
baseLane *base.DefaultLane
// auction bid setup
auctionBidAmount sdk.Coin
minBidIncrement sdk.Coin
logger log.Logger
encodingConfig testutils.EncodingConfig
proposalHandler *abci.ProposalHandler
voteExtensionHandler *abci.VoteExtensionHandler
// builder setup
builderKeeper keeper.Keeper
@@ -68,13 +64,34 @@ func (suite *ABCITestSuite) SetupTest() {
suite.key = storetypes.NewKVStoreKey(buildertypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(suite.T(), suite.key, storetypes.NewTransientStoreKey("transient_test"))
suite.ctx = testCtx.Ctx.WithBlockHeight(1)
suite.logger = log.NewNopLogger()
// Lanes configuration
//
// TOB lane set up
config := blockbuster.BaseLaneConfig{
Logger: suite.logger,
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: sdk.ZeroDec(),
}
suite.tobLane = auction.NewTOBLane(
config,
0, // No bound on the number of transactions in the lane
auction.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Base lane set up
suite.baseLane = base.NewDefaultLane(
config,
)
// Mempool set up
suite.config = mempool.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder())
suite.mempool = mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0, suite.config)
suite.txs = make(map[string]struct{})
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(1000000000))
suite.minBidIncrement = sdk.NewCoin("foo", sdk.NewInt(1000))
suite.mempool = blockbuster.NewMempool(
suite.tobLane,
suite.baseLane,
)
// Mock keepers set up
ctrl := gomock.NewController(suite.T())
@@ -97,10 +114,10 @@ func (suite *ABCITestSuite) SetupTest() {
)
err := suite.builderKeeper.SetParams(suite.ctx, buildertypes.DefaultParams())
suite.Require().NoError(err)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.tobLane, suite.mempool)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 1)
suite.accounts = testutils.RandomAccounts(suite.random, 10)
suite.balances = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000000000000000000)))
suite.nonces = make(map[string]uint64)
for _, acc := range suite.accounts {
@@ -108,663 +125,172 @@ func (suite *ABCITestSuite) SetupTest() {
}
// Proposal handler set up
suite.logger = log.NewNopLogger()
suite.proposalHandler = abci.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
suite.proposalHandler = abci.NewProposalHandler(
[]blockbuster.Lane{suite.baseLane}, // only the base lane is used for proposal handling
suite.tobLane,
suite.logger,
suite.encodingConfig.TxConfig.TxEncoder(),
suite.encodingConfig.TxConfig.TxDecoder(),
)
suite.voteExtensionHandler = abci.NewVoteExtensionHandler(
suite.tobLane,
suite.encodingConfig.TxConfig.TxDecoder(),
suite.encodingConfig.TxConfig.TxEncoder(),
)
}
func (suite *ABCITestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
func (suite *ABCITestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, _ bool) (sdk.Context, error) {
signer := tx.GetMsgs()[0].GetSigners()[0]
suite.bankKeeper.EXPECT().GetAllBalances(ctx, signer).AnyTimes().Return(suite.balances)
next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
next := func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}
ctx, err := suite.builderDecorator.AnteHandle(ctx, tx, false, next)
if err != nil {
return ctx, err
}
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
if err != nil {
return ctx, err
}
if !simulate {
hash := sha256.Sum256(bz)
txHash := hex.EncodeToString(hash[:])
if _, ok := suite.txs[txHash]; ok {
return ctx, fmt.Errorf("tx already in mempool")
}
suite.txs[txHash] = struct{}{}
}
return ctx, nil
return suite.builderDecorator.AnteHandle(ctx, tx, false, next)
}
func (suite *ABCITestSuite) createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs int, insertRefTxs bool) int {
// Insert a bunch of normal transactions into the global mempool
for i := 0; i < numNormalTxs; i++ {
// fillBaseLane fills the base lane with numTxs transactions that are randomly created.
func (suite *ABCITestSuite) fillBaseLane(numTxs int) {
for i := 0; i < numTxs; i++ {
// randomly select an account to create the tx
randomIndex := suite.random.Intn(len(suite.accounts))
acc := suite.accounts[randomIndex]
// create a few random msgs
randomMsgs := testutils.CreateRandomMsgs(acc.Address, 3)
// create a few random msgs and construct the tx
nonce := suite.nonces[acc.Address.String()]
randomTx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, randomMsgs)
randomMsgs := testutils.CreateRandomMsgs(acc.Address, 3)
tx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, randomMsgs)
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), randomTx))
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), tx))
}
}
suite.Require().Equal(numNormalTxs, suite.mempool.CountTx())
suite.Require().Equal(0, suite.mempool.CountAuctionTx())
// fillTOBLane fills the TOB lane with numTxs transactions that are randomly created.
func (suite *ABCITestSuite) fillTOBLane(numTxs int, numBundledTxs int) {
// Insert a bunch of auction transactions into the global mempool and auction mempool
for i := 0; i < numAuctionTxs; i++ {
for i := 0; i < numTxs; i++ {
// randomly select a bidder to create the tx
randomIndex := suite.random.Intn(len(suite.accounts))
acc := suite.accounts[randomIndex]
// create a new auction bid msg with numBundledTxs bundled transactions
// create a randomized auction transaction
nonce := suite.nonces[acc.Address.String()]
bidMsg, err := testutils.CreateMsgAuctionBid(suite.encodingConfig.TxConfig, acc, suite.auctionBidAmount, nonce, numBundledTxs)
suite.nonces[acc.Address.String()] += uint64(numBundledTxs)
suite.Require().NoError(err)
bidAmount := sdk.NewInt(int64(suite.random.Intn(1000) + 1))
bid := sdk.NewCoin("foo", bidAmount)
// create the auction tx
nonce = suite.nonces[acc.Address.String()]
auctionTx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, []sdk.Msg{bidMsg})
signers := []testutils.Account{}
for j := 0; j < numBundledTxs; j++ {
signers = append(signers, suite.accounts[0])
}
tx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, acc, bid, nonce, 1000, signers)
suite.Require().NoError(err)
// insert the auction tx into the global mempool
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), auctionTx))
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
suite.nonces[acc.Address.String()]++
if insertRefTxs {
for _, refRawTx := range bidMsg.GetTransactions() {
refTx, err := suite.encodingConfig.TxConfig.TxDecoder()(refRawTx)
suite.Require().NoError(err)
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), refTx))
}
}
// decrement the bid amount for the next auction tx
suite.auctionBidAmount = suite.auctionBidAmount.Sub(suite.minBidIncrement)
}
numSeenGlobalTxs := 0
for iterator := suite.mempool.Select(suite.ctx, nil); iterator != nil; iterator = iterator.Next() {
numSeenGlobalTxs++
}
numSeenAuctionTxs := 0
for iterator := suite.mempool.AuctionBidSelect(suite.ctx); iterator != nil; iterator = iterator.Next() {
numSeenAuctionTxs++
}
var totalNumTxs int
suite.Require().Equal(numAuctionTxs, suite.mempool.CountAuctionTx())
if insertRefTxs {
totalNumTxs = numNormalTxs + numAuctionTxs*(numBundledTxs)
suite.Require().Equal(totalNumTxs, suite.mempool.CountTx())
suite.Require().Equal(totalNumTxs, numSeenGlobalTxs)
} else {
totalNumTxs = numNormalTxs
suite.Require().Equal(totalNumTxs, suite.mempool.CountTx())
suite.Require().Equal(totalNumTxs, numSeenGlobalTxs)
}
suite.Require().Equal(numAuctionTxs, numSeenAuctionTxs)
return totalNumTxs
}
func (suite *ABCITestSuite) exportMempool(exportRefTxs bool) [][]byte {
txs := make([][]byte, 0)
seenTxs := make(map[string]bool)
func (suite *ABCITestSuite) createPrepareProposalRequest(maxBytes int64) comettypes.RequestPrepareProposal {
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx)
auctionIterator := suite.tobLane.Select(suite.ctx, nil)
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
auctionTx := auctionIterator.Tx()
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(auctionTx)
tx := auctionIterator.Tx()
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
txs = append(txs, txBz)
if exportRefTxs {
for _, refRawTx := range auctionTx.GetMsgs()[0].(*buildertypes.MsgAuctionBid).GetTransactions() {
txs = append(txs, refRawTx)
seenTxs[string(refRawTx)] = true
}
}
seenTxs[string(txBz)] = true
voteExtensions = append(voteExtensions, comettypes.ExtendedVoteInfo{
VoteExtension: txBz,
})
}
iterator := suite.mempool.Select(suite.ctx, nil)
for ; iterator != nil; iterator = iterator.Next() {
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(iterator.Tx())
return comettypes.RequestPrepareProposal{
MaxTxBytes: maxBytes,
LocalLastCommit: comettypes.ExtendedCommitInfo{
Votes: voteExtensions,
},
}
}
func (suite *ABCITestSuite) createExtendedCommitInfoFromTxs(txs []sdk.Tx) comettypes.ExtendedCommitInfo {
voteExtensions := make([][]byte, 0)
for _, tx := range txs {
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
if !seenTxs[string(txBz)] {
txs = append(txs, txBz)
voteExtensions = append(voteExtensions, bz)
}
return suite.createExtendedCommitInfo(voteExtensions)
}
func (suite *ABCITestSuite) createExtendedVoteInfo(voteExtensions [][]byte) []comettypes.ExtendedVoteInfo {
commitInfo := make([]comettypes.ExtendedVoteInfo, 0)
for _, voteExtension := range voteExtensions {
info := comettypes.ExtendedVoteInfo{
VoteExtension: voteExtension,
}
commitInfo = append(commitInfo, info)
}
return txs
return commitInfo
}
func (suite *ABCITestSuite) TestPrepareProposal() {
var (
// the modified transactions cannot exceed this size
maxTxBytes int64 = 1000000000000000000
// mempool configuration
numNormalTxs = 100
numAuctionTxs = 100
numBundledTxs = 3
insertRefTxs = false
// auction configuration
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
frontRunningProtection = true
)
cases := []struct {
name string
malleate func()
expectedNumberProposalTxs int
expectedNumberTxsInMempool int
isTopBidValid bool
}{
{
"single bundle in the mempool",
func() {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
},
4,
3,
true,
},
{
"single bundle in the mempool, no ref txs in mempool",
func() {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = false
},
4,
0,
true,
},
{
"single bundle in the mempool, not valid",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(100000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(10000)) // this will fail the ante handler
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
},
0,
0,
false,
},
{
"single bundle in the mempool, not valid with ref txs in mempool",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(100000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(10000)) // this will fail the ante handler
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
},
3,
3,
false,
},
{
"multiple bundles in the mempool, no normal txs + no ref txs in mempool",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(10000000))
numNormalTxs = 0
numAuctionTxs = 10
numBundledTxs = 3
insertRefTxs = false
},
4,
0,
true,
},
{
"multiple bundles in the mempool, no normal txs + ref txs in mempool",
func() {
numNormalTxs = 0
numAuctionTxs = 10
numBundledTxs = 3
insertRefTxs = true
},
31,
30,
true,
},
{
"normal txs only",
func() {
numNormalTxs = 1
numAuctionTxs = 0
numBundledTxs = 0
},
1,
1,
false,
},
{
"many normal txs only",
func() {
numNormalTxs = 100
numAuctionTxs = 0
numBundledTxs = 0
},
100,
100,
false,
},
{
"single normal tx, single auction tx",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 0
},
2,
1,
true,
},
{
"single normal tx, single auction tx with ref txs",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = false
},
5,
1,
true,
},
{
"single normal tx, single failing auction tx with ref txs",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(2000)) // this will fail the ante handler
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000000000))
},
4,
4,
false,
},
{
"many normal tx, single auction tx with no ref txs",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(2000000))
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 0
},
101,
100,
true,
},
{
"many normal tx, single auction tx with ref txs",
func() {
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
},
104,
103,
true,
},
{
"many normal tx, single auction tx with ref txs",
func() {
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = false
},
104,
100,
true,
},
{
"many normal tx, many auction tx with ref txs",
func() {
numNormalTxs = 100
numAuctionTxs = 100
numBundledTxs = 1
insertRefTxs = true
},
201,
200,
true,
},
func (suite *ABCITestSuite) createExtendedCommitInfo(voteExtensions [][]byte) comettypes.ExtendedCommitInfo {
commitInfo := comettypes.ExtendedCommitInfo{
Votes: suite.createExtendedVoteInfo(voteExtensions),
}
for _, tc := range cases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
tc.malleate()
return commitInfo
}
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
func (suite *ABCITestSuite) createExtendedCommitInfoFromTxBzs(txs [][]byte) []byte {
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
// create a new auction
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
MinBidIncrement: suite.minBidIncrement,
}
suite.builderKeeper.SetParams(suite.ctx, params)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
handler := suite.proposalHandler.PrepareProposalHandler()
res := handler(suite.ctx, abcitypes.RequestPrepareProposal{
MaxTxBytes: maxTxBytes,
})
// -------------------- Check Invariants -------------------- //
// 1. The auction tx must fail if we know it is invalid
suite.Require().Equal(tc.isTopBidValid, suite.isTopBidValid())
// 2. total bytes must be less than or equal to maxTxBytes
totalBytes := int64(0)
if suite.isTopBidValid() {
totalBytes += int64(len(res.Txs[0]))
for _, tx := range res.Txs[1+numBundledTxs:] {
totalBytes += int64(len(tx))
}
} else {
for _, tx := range res.Txs {
totalBytes += int64(len(tx))
}
}
suite.Require().LessOrEqual(totalBytes, maxTxBytes)
// 3. the number of transactions in the response must be equal to the number of expected transactions
suite.Require().Equal(tc.expectedNumberProposalTxs, len(res.Txs))
// 4. if there are auction transactions, the first transaction must be the top bid
// and the rest of the bundle must be in the response
if suite.isTopBidValid() {
auctionTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[0])
suite.Require().NoError(err)
bidInfo, err := suite.mempool.GetAuctionBidInfo(auctionTx)
suite.Require().NoError(err)
for index, tx := range bidInfo.Transactions {
suite.Require().Equal(tx, res.Txs[index+1])
}
}
// 5. All of the transactions must be unique
uniqueTxs := make(map[string]bool)
for _, tx := range res.Txs {
suite.Require().False(uniqueTxs[string(tx)])
uniqueTxs[string(tx)] = true
}
// 6. The number of transactions in the mempool must be correct
suite.Require().Equal(tc.expectedNumberTxsInMempool, suite.mempool.CountTx())
for _, txBz := range txs {
voteExtensions = append(voteExtensions, comettypes.ExtendedVoteInfo{
VoteExtension: txBz,
})
}
}
func (suite *ABCITestSuite) TestProcessProposal() {
var (
// mempool set up
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
exportRefTxs = true
frontRunningTx sdk.Tx
// auction set up
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
frontRunningProtection = true
)
cases := []struct {
name string
malleate func()
isTopBidValid bool
response abcitypes.ResponseProcessProposal_ProposalStatus
}{
{
"single normal tx, no auction tx",
func() {
numNormalTxs = 1
numAuctionTxs = 0
numBundledTxs = 0
},
false,
abcitypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx, no normal txs",
func() {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 0
},
true,
abcitypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx, single auction tx",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 0
},
true,
abcitypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx, single auction tx with ref txs",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 4
},
true,
abcitypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx, single auction tx with no ref txs",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 4
insertRefTxs = false
exportRefTxs = false
},
true,
abcitypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs, single normal tx",
func() {
numNormalTxs = 1
numAuctionTxs = 2
numBundledTxs = 4
insertRefTxs = true
exportRefTxs = true
},
true,
abcitypes.ResponseProcessProposal_REJECT,
},
{
"single auction txs, multiple normal tx",
func() {
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 4
},
true,
abcitypes.ResponseProcessProposal_ACCEPT,
},
{
"single invalid auction tx, multiple normal tx",
func() {
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 4
reserveFee = sdk.NewCoin("foo", sdk.NewInt(100000000000000000))
insertRefTxs = true
},
false,
abcitypes.ResponseProcessProposal_REJECT,
},
{
"single valid auction txs but missing ref txs",
func() {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 4
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
insertRefTxs = false
exportRefTxs = false
},
true,
abcitypes.ResponseProcessProposal_REJECT,
},
{
"single valid auction txs but missing ref txs, with many normal txs",
func() {
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 4
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
insertRefTxs = false
exportRefTxs = false
},
true,
abcitypes.ResponseProcessProposal_REJECT,
},
{
"auction tx with frontrunning",
func() {
randomAccount := testutils.RandomAccounts(suite.random, 1)[0]
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(696969696969))
nonce := suite.nonces[bidder.Address.String()]
frontRunningTx, _ = testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, suite.accounts[0], bid, nonce+1, 1000, []testutils.Account{bidder, randomAccount})
suite.Require().NotNil(frontRunningTx)
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 4
insertRefTxs = true
exportRefTxs = true
},
false,
abcitypes.ResponseProcessProposal_REJECT,
},
{
"auction tx with frontrunning, but frontrunning protection disabled",
func() {
randomAccount := testutils.RandomAccounts(suite.random, 1)[0]
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(696969696969))
nonce := suite.nonces[bidder.Address.String()]
frontRunningTx, _ = testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, suite.accounts[0], bid, nonce+1, 1000, []testutils.Account{bidder, randomAccount})
suite.Require().NotNil(frontRunningTx)
numAuctionTxs = 0
frontRunningProtection = false
},
true,
abcitypes.ResponseProcessProposal_ACCEPT,
},
commitInfo := comettypes.ExtendedCommitInfo{
Votes: voteExtensions,
}
for _, tc := range cases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
tc.malleate()
commitInfoBz, err := commitInfo.Marshal()
suite.Require().NoError(err)
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
if frontRunningTx != nil {
suite.Require().NoError(suite.mempool.Insert(suite.ctx, frontRunningTx))
}
// create a new auction
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
MinBidIncrement: suite.minBidIncrement,
}
suite.builderKeeper.SetParams(suite.ctx, params)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
suite.Require().Equal(tc.isTopBidValid, suite.isTopBidValid())
txs := suite.exportMempool(exportRefTxs)
if frontRunningTx != nil {
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(frontRunningTx)
suite.Require().NoError(err)
suite.Require().True(bytes.Equal(txs[0], txBz))
}
handler := suite.proposalHandler.ProcessProposalHandler()
res := handler(suite.ctx, abcitypes.RequestProcessProposal{
Txs: txs,
})
// Check if the response is valid
suite.Require().Equal(tc.response, res.Status)
})
}
return commitInfoBz
}
// isTopBidValid returns true if the top bid is valid. We purposefully insert invalid
// auction transactions into the mempool to test the handlers.
func (suite *ABCITestSuite) isTopBidValid() bool {
iterator := suite.mempool.AuctionBidSelect(suite.ctx)
if iterator == nil {
return false
func (suite *ABCITestSuite) createAuctionInfoFromTxBzs(txs [][]byte, numTxs uint64) []byte {
auctionInfo := abci.AuctionInfo{
ExtendedCommitInfo: suite.createExtendedCommitInfoFromTxBzs(txs),
NumTxs: numTxs,
MaxTxBytes: int64(len(txs[0])),
}
// check if the top bid is valid
_, err := suite.anteHandler(suite.ctx, iterator.Tx(), true)
return err == nil
auctionInfoBz, err := auctionInfo.Marshal()
suite.Require().NoError(err)
return auctionInfoBz
}
func (suite *ABCITestSuite) getAuctionBidInfoFromTxBz(txBz []byte) *buildertypes.BidInfo {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(txBz)
suite.Require().NoError(err)
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
return bidInfo
}
@@ -1,4 +1,4 @@
package v2_test
package abci_test
import (
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -481,17 +481,17 @@ func (suite *ABCITestSuite) TestBuildTOB() {
proposal := suite.proposalHandler.BuildTOB(suite.ctx, commitInfo, tc.maxBytes)
// Size of the proposal should be less than or equal to the max bytes
suite.Require().LessOrEqual(proposal.Size, tc.maxBytes)
suite.Require().LessOrEqual(proposal.TotalTxBytes, tc.maxBytes)
if winningBid == nil {
suite.Require().Len(proposal.Txs, 0)
suite.Require().Equal(proposal.Size, int64(0))
suite.Require().Equal(proposal.TotalTxBytes, int64(0))
} else {
// Get info about the winning bid
winningBidBz, err := suite.encodingConfig.TxConfig.TxEncoder()(winningBid)
suite.Require().NoError(err)
auctionBidInfo, err := suite.mempool.GetAuctionBidInfo(winningBid)
auctionBidInfo, err := suite.tobLane.GetAuctionBidInfo(winningBid)
suite.Require().NoError(err)
// Verify that the size of the proposal is the size of the winning bid
-210
View File
@@ -1,210 +0,0 @@
package abci
import (
"context"
"fmt"
cometabci "github.com/cometbft/cometbft/abci/types"
log "github.com/cometbft/cometbft/libs/log"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/skip-mev/pob/mempool"
)
type (
// CheckTxHandler is a wrapper around baseapp's CheckTx method that allows us to
// verify bid transactions against the latest committed state. All other transactions
// are executed normally using base app's CheckTx. This defines all of the
// dependencies that are required to verify a bid transaction.
CheckTxHandler struct {
// baseApp is utilized to retrieve the latest committed state and to call
// baseapp's CheckTx method.
baseApp BaseApp
// txDecoder is utilized to decode transactions to determine if they are
// bid transactions.
txDecoder sdk.TxDecoder
// mempool is utilized to retrieve the bid info of a transaction and to
// insert a transaction into the application-side mempool.
mempool CheckTxMempool
// anteHandler is utilized to verify the bid transaction against the latest
// committed state.
anteHandler sdk.AnteHandler
// chainID is the chain ID of the blockchain.
chainID string
}
// CheckTx is baseapp's CheckTx method that checks the validity of a
// transaction.
CheckTx func(cometabci.RequestCheckTx) cometabci.ResponseCheckTx
// CheckTxMempool is the interface that defines all of the dependencies that
// are required to interact with the application-side mempool.
CheckTxMempool interface {
// GetAuctionBidInfo is utilized to retrieve the bid info of a transaction.
GetAuctionBidInfo(tx sdk.Tx) (*mempool.AuctionBidInfo, error)
// Insert is utilized to insert a transaction into the application-side mempool.
Insert(ctx context.Context, tx sdk.Tx) error
// WrapBundleTransaction is utilized to wrap a transaction included in a bid transaction
// into an sdk.Tx.
WrapBundleTransaction(tx []byte) (sdk.Tx, error)
}
// BaseApp is an interface that allows us to call baseapp's CheckTx method
// as well as retrieve the latest committed state.
BaseApp interface {
// CommitMultiStore is utilized to retrieve the latest committed state.
CommitMultiStore() sdk.CommitMultiStore
// CheckTx is baseapp's CheckTx method that checks the validity of a
// transaction.
CheckTx(cometabci.RequestCheckTx) cometabci.ResponseCheckTx
// Logger is utilized to log errors.
Logger() log.Logger
// LastBlockHeight is utilized to retrieve the latest block height.
LastBlockHeight() int64
// GetConsensusParams is utilized to retrieve the consensus params.
GetConsensusParams(ctx sdk.Context) *tmproto.ConsensusParams
}
)
// NewCheckTxHandler is a constructor for CheckTxHandler.
func NewCheckTxHandler(baseApp BaseApp, txDecoder sdk.TxDecoder, mempool CheckTxMempool, anteHandler sdk.AnteHandler, chainID string) *CheckTxHandler {
return &CheckTxHandler{
baseApp: baseApp,
txDecoder: txDecoder,
mempool: mempool,
anteHandler: anteHandler,
chainID: chainID,
}
}
// CheckTxHandler is a wrapper around baseapp's CheckTx method that allows us to
// verify bid transactions against the latest committed state. All other transactions
// are executed normally. We must verify each bid tx and all of its bundled transactions
// before we can insert it into the mempool against the latest commit state because
// otherwise the auction can be griefed. No state changes are applied to the state
// during this process.
func (handler *CheckTxHandler) CheckTx() CheckTx {
return func(req cometabci.RequestCheckTx) (resp cometabci.ResponseCheckTx) {
defer func() {
if err := recover(); err != nil {
resp = sdkerrors.ResponseCheckTxWithEvents(fmt.Errorf("panic in check tx handler: %s", err), 0, 0, nil, false)
}
}()
tx, err := handler.txDecoder(req.Tx)
if err != nil {
return sdkerrors.ResponseCheckTxWithEvents(fmt.Errorf("failed to decode tx: %w", err), 0, 0, nil, false)
}
// Attempt to get the bid info of the transaction.
bidInfo, err := handler.mempool.GetAuctionBidInfo(tx)
if err != nil {
return sdkerrors.ResponseCheckTxWithEvents(fmt.Errorf("failed to get auction bid info: %w", err), 0, 0, nil, false)
}
// If this is not a bid transaction, we just execute it normally.
if bidInfo == nil {
return handler.baseApp.CheckTx(req)
}
// We attempt to get the latest committed state in order to verify transactions
// as if they were to be executed at the top of the block. After verification, this
// context will be discarded and will not apply any state changes.
ctx := handler.GetContextForBidTx(req)
// Verify the bid transaction.
gasInfo, err := handler.ValidateBidTx(ctx, tx, bidInfo)
if err != nil {
return sdkerrors.ResponseCheckTxWithEvents(fmt.Errorf("invalid bid tx: %w", err), gasInfo.GasWanted, gasInfo.GasUsed, nil, false)
}
// If the bid transaction is valid, we know we can insert it into the mempool for consideration in the next block.
if err := handler.mempool.Insert(ctx, tx); err != nil {
return sdkerrors.ResponseCheckTxWithEvents(fmt.Errorf("invalid bid tx; failed to insert bid transaction into mempool: %w", err), gasInfo.GasWanted, gasInfo.GasUsed, nil, false)
}
return cometabci.ResponseCheckTx{
Code: cometabci.CodeTypeOK,
GasWanted: int64(gasInfo.GasWanted),
GasUsed: int64(gasInfo.GasUsed),
}
}
}
// ValidateBidTx is utilized to verify the bid transaction against the latest committed state.
func (handler *CheckTxHandler) ValidateBidTx(ctx sdk.Context, bidTx sdk.Tx, bidInfo *mempool.AuctionBidInfo) (sdk.GasInfo, error) {
// Verify the bid transaction.
ctx, err := handler.anteHandler(ctx, bidTx, false)
if err != nil {
return sdk.GasInfo{}, fmt.Errorf("invalid bid tx; failed to execute ante handler: %w", err)
}
// Store the gas info and priority of the bid transaction before applying changes with other transactions.
gasInfo := sdk.GasInfo{
GasWanted: ctx.GasMeter().Limit(),
GasUsed: ctx.GasMeter().GasConsumed(),
}
// Verify all of the bundled transactions.
for _, tx := range bidInfo.Transactions {
bundledTx, err := handler.mempool.WrapBundleTransaction(tx)
if err != nil {
return gasInfo, fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err)
}
// bid txs cannot be included in bundled txs
bidInfo, _ := handler.mempool.GetAuctionBidInfo(bundledTx)
if bidInfo != nil {
return gasInfo, fmt.Errorf("invalid bid tx; bundled tx cannot be a bid tx")
}
if ctx, err = handler.anteHandler(ctx, bundledTx, false); err != nil {
return gasInfo, fmt.Errorf("invalid bid tx; failed to execute bundled transaction: %w", err)
}
}
return gasInfo, nil
}
// GetContextForBidTx is returns the latest committed state and sets the context given
// the checkTx request.
func (handler *CheckTxHandler) GetContextForBidTx(req cometabci.RequestCheckTx) sdk.Context {
// Retrieve the commit multi-store which is used to retrieve the latest committed state.
ms := handler.baseApp.CommitMultiStore().CacheMultiStore()
// Create a new context based off of the latest committed state.
header := tmproto.Header{
Height: handler.baseApp.LastBlockHeight(),
ChainID: handler.chainID, // TODO: Replace with actual chain ID. This is currently not exposed by the app.
}
ctx, _ := sdk.NewContext(ms, header, true, handler.baseApp.Logger()).CacheContext()
// Set the context to the correct checking mode.
switch req.Type {
case cometabci.CheckTxType_New:
ctx = ctx.WithIsCheckTx(true)
case cometabci.CheckTxType_Recheck:
ctx = ctx.WithIsReCheckTx(true)
default:
panic("unknown check tx type")
}
// Set the remaining important context values.
ctx = ctx.
WithTxBytes(req.Tx).
WithEventManager(sdk.NewEventManager()).
WithConsensusParams(handler.baseApp.GetConsensusParams(ctx))
return ctx
}
@@ -1,40 +1,20 @@
package v2
package abci
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"reflect"
"sort"
abci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
pobabci "github.com/skip-mev/pob/abci"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/utils"
)
// TopOfBlock contains information about how the top of block should be built.
type TopOfBlock struct {
// Txs contains the transactions that should be included in the top of block.
Txs [][]byte
// Size is the total size of the top of block.
Size int64
// Cache is the cache of transactions that were seen, stored in order to ignore them
// when building the rest of the block.
Cache map[string]struct{}
}
func NewTopOfBlock() TopOfBlock {
return TopOfBlock{
Cache: make(map[string]struct{}),
}
}
// BuildTOB inputs all of the vote extensions and outputs a top of block proposal
// that includes the highest bidding valid transaction along with all the bundled
// transactions.
func (h *ProposalHandler) BuildTOB(ctx sdk.Context, voteExtensionInfo abci.ExtendedCommitInfo, maxBytes int64) TopOfBlock {
func (h *ProposalHandler) BuildTOB(ctx sdk.Context, voteExtensionInfo abci.ExtendedCommitInfo, maxBytes int64) *blockbuster.Proposal {
// Get the bid transactions from the vote extensions.
sortedBidTxs := h.GetBidsFromVoteExtensions(voteExtensionInfo.Votes)
@@ -43,14 +23,14 @@ func (h *ProposalHandler) BuildTOB(ctx sdk.Context, voteExtensionInfo abci.Exten
// Attempt to select the highest bid transaction that is valid and whose
// bundled transactions are valid.
var topOfBlock TopOfBlock
topOfBlock := blockbuster.NewProposal(maxBytes)
for _, bidTx := range sortedBidTxs {
// Cache the context so that we can write it back to the original context
// when we know we have a valid top of block bundle.
cacheCtx, write := ctx.CacheContext()
// Attempt to build the top of block using the bid transaction.
proposal, err := h.buildTOB(cacheCtx, bidTx)
proposal, err := h.buildTOB(cacheCtx, bidTx, maxBytes)
if err != nil {
h.logger.Info(
"vote extension auction failed to verify auction tx",
@@ -60,27 +40,22 @@ func (h *ProposalHandler) BuildTOB(ctx sdk.Context, voteExtensionInfo abci.Exten
continue
}
if proposal.Size <= maxBytes {
// 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 and apply the state changes to the cache
// context.
topOfBlock = proposal
write()
// 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 and apply the state changes to the cache
// context.
topOfBlock = proposal
write()
break
}
h.logger.Info(
"failed to select auction bid tx; auction tx size is too large",
"tx_size", proposal.Size,
"max_size", maxBytes,
)
break
}
// Remove all of the transactions that were not valid.
for tx := range txsToRemove {
h.RemoveTx(tx)
if err := utils.RemoveTxsFromLane(txsToRemove, h.tobLane); err != nil {
h.logger.Error(
"failed to remove transactions from lane",
"err", err,
)
}
return topOfBlock
@@ -88,14 +63,14 @@ func (h *ProposalHandler) BuildTOB(ctx sdk.Context, voteExtensionInfo abci.Exten
// VerifyTOB verifies that the set of vote extensions used in prepare proposal deterministically
// produce the same top of block proposal.
func (h *ProposalHandler) VerifyTOB(ctx sdk.Context, proposalTxs [][]byte) (*pobabci.AuctionInfo, error) {
func (h *ProposalHandler) VerifyTOB(ctx sdk.Context, proposalTxs [][]byte) (*AuctionInfo, error) {
// Proposal must include at least the auction info.
if len(proposalTxs) < NumInjectedTxs {
return nil, fmt.Errorf("proposal is too small; expected at least %d slots", NumInjectedTxs)
}
// Extract the auction info from the proposal.
auctionInfo := &pobabci.AuctionInfo{}
auctionInfo := &AuctionInfo{}
if err := auctionInfo.Unmarshal(proposalTxs[AuctionInfoIndex]); err != nil {
return nil, fmt.Errorf("failed to unmarshal auction info: %w", err)
}
@@ -141,12 +116,12 @@ func (h *ProposalHandler) GetBidsFromVoteExtensions(voteExtensions []abci.Extend
// Sort the auction transactions by their bid amount in descending order.
sort.Slice(bidTxs, func(i, j int) bool {
// In the case of an error, we want to sort the transaction to the end of the list.
bidInfoI, err := h.mempool.GetAuctionBidInfo(bidTxs[i])
bidInfoI, err := h.tobLane.GetAuctionBidInfo(bidTxs[i])
if err != nil {
return false
}
bidInfoJ, err := h.mempool.GetAuctionBidInfo(bidTxs[j])
bidInfoJ, err := h.tobLane.GetAuctionBidInfo(bidTxs[j])
if err != nil {
return true
}
@@ -161,16 +136,29 @@ func (h *ProposalHandler) GetBidsFromVoteExtensions(voteExtensions []abci.Extend
// returns the transactions that should be included in the top of block, size
// of the auction transaction and bundle, and a cache of all transactions that
// should be ignored.
func (h *ProposalHandler) buildTOB(ctx sdk.Context, bidTx sdk.Tx) (TopOfBlock, error) {
proposal := NewTopOfBlock()
func (h *ProposalHandler) buildTOB(ctx sdk.Context, bidTx sdk.Tx, maxBytes int64) (*blockbuster.Proposal, error) {
proposal := blockbuster.NewProposal(maxBytes)
// Ensure that the bid transaction is valid
bidTxBz, err := h.PrepareProposalVerifyTx(ctx, bidTx)
// cache the bytes of the bid transaction
txBz, hash, err := utils.GetTxHashStr(h.txEncoder, bidTx)
if err != nil {
return proposal, err
}
bidInfo, err := h.mempool.GetAuctionBidInfo(bidTx)
proposal.Cache[hash] = struct{}{}
proposal.TotalTxBytes = int64(len(txBz))
proposal.Txs = append(proposal.Txs, txBz)
if int64(len(txBz)) > maxBytes {
return proposal, fmt.Errorf("bid transaction is too large; got %d, max %d", len(txBz), maxBytes)
}
// Ensure that the bid transaction is valid
if err := h.tobLane.VerifyTx(ctx, bidTx); err != nil {
return proposal, err
}
bidInfo, err := h.tobLane.GetAuctionBidInfo(bidTx)
if err != nil {
return proposal, err
}
@@ -181,34 +169,23 @@ func (h *ProposalHandler) buildTOB(ctx sdk.Context, bidTx sdk.Tx) (TopOfBlock, e
// Ensure that the bundled transactions are valid
for index, rawRefTx := range bidInfo.Transactions {
// convert the bundled raw transaction to a sdk.Tx
refTx, err := h.mempool.WrapBundleTransaction(rawRefTx)
refTx, err := h.tobLane.WrapBundleTransaction(rawRefTx)
if err != nil {
return TopOfBlock{}, err
return proposal, err
}
txBz, err := h.PrepareProposalVerifyTx(ctx, refTx)
// convert the sdk.Tx to a hash and bytes
txBz, hash, err := utils.GetTxHashStr(h.txEncoder, refTx)
if err != nil {
return TopOfBlock{}, err
return proposal, err
}
hashBz := sha256.Sum256(txBz)
hash := hex.EncodeToString(hashBz[:])
proposal.Cache[hash] = struct{}{}
sdkTxBytes[index] = txBz
}
// cache the bytes of the bid transaction
hashBz := sha256.Sum256(bidTxBz)
hash := hex.EncodeToString(hashBz[:])
proposal.Cache[hash] = struct{}{}
txs := [][]byte{bidTxBz}
txs = append(txs, sdkTxBytes...)
// Set the top of block transactions and size.
proposal.Txs = txs
proposal.Size = int64(len(bidTxBz))
// Add the bundled transactions to the proposal.
proposal.Txs = append(proposal.Txs, sdkTxBytes...)
return proposal, nil
}
@@ -227,7 +204,7 @@ func (h *ProposalHandler) getAuctionTxFromVoteExtension(voteExtension []byte) (s
}
// Verify the auction transaction has bid information.
if bidInfo, err := h.mempool.GetAuctionBidInfo(bidTx); err != nil || bidInfo == nil {
if bidInfo, err := h.tobLane.GetAuctionBidInfo(bidTx); err != nil || bidInfo == nil {
return nil, fmt.Errorf("vote extension does not contain an auction transaction")
}
+152
View File
@@ -0,0 +1,152 @@
package abci
import (
"errors"
"fmt"
cometabci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
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/abci"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
)
const (
// NumInjectedTxs is the minimum number of transactions that were injected into
// the proposal but are not actual transactions. In this case, the auction
// info is injected into the proposal but should be ignored by the application.ß
NumInjectedTxs = 1
// AuctionInfoIndex is the index of the auction info in the proposal.
AuctionInfoIndex = 0
)
type (
// TOBLaneProposal is the interface that defines all of the dependencies that
// are required to interact with the top of block lane.
TOBLaneProposal interface {
sdkmempool.Mempool
// 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.).
auction.Factory
// VerifyTx is utilized to verify a bid transaction according to the preferences
// of the top of block lane.
VerifyTx(ctx sdk.Context, tx sdk.Tx) error
// ProcessLaneBasic is utilized to verify the rest of the proposal according to
// the preferences of the top of block lane. This is used to verify that no
ProcessLaneBasic(txs [][]byte) error
}
// ProposalHandler contains the functionality and handlers required to\
// process, validate and build blocks.
ProposalHandler struct {
prepareLanesHandler blockbuster.PrepareLanesHandler
processLanesHandler blockbuster.ProcessLanesHandler
tobLane TOBLaneProposal
logger log.Logger
txEncoder sdk.TxEncoder
txDecoder sdk.TxDecoder
}
)
// NewProposalHandler returns a ProposalHandler that contains the functionality and handlers
// required to process, validate and build blocks.
func NewProposalHandler(
lanes []blockbuster.Lane,
tobLane TOBLaneProposal,
logger log.Logger,
txEncoder sdk.TxEncoder,
txDecoder sdk.TxDecoder,
) *ProposalHandler {
return &ProposalHandler{
prepareLanesHandler: abci.ChainPrepareLanes(lanes...),
processLanesHandler: abci.ChainProcessLanes(lanes...),
tobLane: tobLane,
logger: logger,
txEncoder: txEncoder,
txDecoder: txDecoder,
}
}
// PrepareProposalHandler returns the PrepareProposal ABCI handler that performs
// top-of-block auctioning and general block proposal construction.
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req cometabci.RequestPrepareProposal) cometabci.ResponsePrepareProposal {
// Build the top of block portion of the proposal given the vote extensions
// from the previous block.
topOfBlock := h.BuildTOB(ctx, req.LocalLastCommit, req.MaxTxBytes)
// If information is unable to be marshaled, we return an empty proposal. This will
// cause another proposal to be generated after it is rejected in ProcessProposal.
lastCommitInfo, err := req.LocalLastCommit.Marshal()
if err != nil {
h.logger.Error("failed to marshal last commit info", "err", err)
return cometabci.ResponsePrepareProposal{Txs: nil}
}
auctionInfo := &AuctionInfo{
ExtendedCommitInfo: lastCommitInfo,
MaxTxBytes: req.MaxTxBytes,
NumTxs: uint64(len(topOfBlock.Txs)),
}
// Add the auction info and top of block transactions into the proposal.
auctionInfoBz, err := auctionInfo.Marshal()
if err != nil {
h.logger.Error("failed to marshal auction info", "err", err)
return cometabci.ResponsePrepareProposal{Txs: nil}
}
topOfBlock.Txs = append([][]byte{auctionInfoBz}, topOfBlock.Txs...)
// Prepare the proposal by selecting transactions from each lane according to
// each lane's selection logic.
proposal := h.prepareLanesHandler(ctx, topOfBlock)
return cometabci.ResponsePrepareProposal{Txs: proposal.Txs}
}
}
// ProcessProposalHandler returns the ProcessProposal ABCI handler that performs
// block proposal verification.
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req cometabci.RequestProcessProposal) cometabci.ResponseProcessProposal {
proposal := req.Txs
// Verify that the same top of block transactions can be built from the vote
// extensions included in the proposal.
auctionInfo, err := h.VerifyTOB(ctx, proposal)
if err != nil {
h.logger.Error("failed to verify top of block transactions", "err", err)
return cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}
}
// Do a basic check of the rest of the proposal to make sure no auction transactions
// are included.
if err := h.tobLane.ProcessLaneBasic(proposal[NumInjectedTxs:]); err != nil {
h.logger.Error("failed to process proposal", "err", err)
return cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}
}
// Verify that the rest of the proposal is valid according to each lane's verification logic.
if _, err = h.processLanesHandler(ctx, proposal[auctionInfo.NumTxs:]); err != nil {
h.logger.Error("failed to process proposal", "err", err)
return cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}
}
return cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}
}
}
// RemoveTx removes a transaction from the application-side mempool.
func (h *ProposalHandler) RemoveTx(tx sdk.Tx) {
if err := h.tobLane.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
panic(fmt.Errorf("failed to remove invalid transaction from the mempool: %w", err))
}
}
+776
View File
@@ -0,0 +1,776 @@
package abci_test
import (
comettypes "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/abci"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/lanes/base"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/ante"
buildertypes "github.com/skip-mev/pob/x/builder/types"
)
func (suite *ABCITestSuite) TestPrepareProposal() {
var (
// the modified transactions cannot exceed this size
maxTxBytes int64 = 1000000000000000000
// mempool configuration
normalTxs []sdk.Tx
auctionTxs []sdk.Tx
winningBidTx sdk.Tx
insertBundledTxs = false
// auction configuration
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
frontRunningProtection = true
)
cases := []struct {
name string
malleate func()
expectedNumberProposalTxs int
expectedMempoolDistribution map[string]int
}{
{
"single valid tob transaction in the mempool",
func() {
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = false
},
2,
map[string]int{
base.LaneName: 0,
auction.LaneName: 1,
},
},
{
"single invalid tob transaction in the mempool",
func() {
bidder := suite.accounts[0]
bid := reserveFee.Sub(sdk.NewCoin("foo", sdk.NewInt(1))) // bid is less than the reserve fee
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = nil
insertBundledTxs = false
},
0,
map[string]int{
base.LaneName: 0,
auction.LaneName: 0,
},
},
{
"normal transactions in the mempool",
func() {
account := suite.accounts[0]
nonce := suite.nonces[account.Address.String()]
timeout := uint64(100)
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{normalTx}
auctionTxs = []sdk.Tx{}
winningBidTx = nil
insertBundledTxs = false
},
1,
map[string]int{
base.LaneName: 1,
auction.LaneName: 0,
},
},
{
"normal transactions and tob transactions in the mempool",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid default transaction
account := suite.accounts[1]
nonce = suite.nonces[account.Address.String()] + 1
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{normalTx}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = false
},
3,
map[string]int{
base.LaneName: 1,
auction.LaneName: 1,
},
},
{
"multiple tob transactions where the first is invalid",
func() {
// Create an invalid tob transaction (frontrunning)
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder, suite.accounts[1]}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("foo", sdk.NewInt(1000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTx2, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx, bidTx2}
winningBidTx = bidTx2
insertBundledTxs = false
},
2,
map[string]int{
base.LaneName: 0,
auction.LaneName: 1,
},
},
{
"multiple tob transactions where the first is valid",
func() {
// Create an valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("foo", sdk.NewInt(1000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTx2, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx, bidTx2}
winningBidTx = bidTx
insertBundledTxs = false
},
3,
map[string]int{
base.LaneName: 0,
auction.LaneName: 2,
},
},
{
"multiple tob transactions where the first is valid and bundle is inserted into mempool",
func() {
frontRunningProtection = false
// Create an valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], suite.accounts[1], bidder, suite.accounts[3], suite.accounts[4]}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = true
},
6,
map[string]int{
base.LaneName: 5,
auction.LaneName: 1,
},
},
{
"single tob transaction with other normal transactions in the mempool",
func() {
// Create an valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], suite.accounts[1], bidder, suite.accounts[3], suite.accounts[4]}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
account := suite.accounts[5]
nonce = suite.nonces[account.Address.String()]
timeout = uint64(100)
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{normalTx}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = true
},
7,
map[string]int{
base.LaneName: 6,
auction.LaneName: 1,
},
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
tc.malleate()
// Insert all of the normal transactions into the default lane
for _, tx := range normalTxs {
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
// Insert all of the auction transactions into the TOB lane
for _, tx := range auctionTxs {
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
// Insert all of the bundled transactions into the TOB lane if desired
if insertBundledTxs {
for _, tx := range auctionTxs {
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
for _, txBz := range bidInfo.Transactions {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(txBz)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
}
}
// create a new auction
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
}
suite.builderKeeper.SetParams(suite.ctx, params)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.tobLane, suite.mempool)
suite.proposalHandler = abci.NewProposalHandler(
[]blockbuster.Lane{suite.baseLane},
suite.tobLane,
suite.logger,
suite.encodingConfig.TxConfig.TxEncoder(),
suite.encodingConfig.TxConfig.TxDecoder(),
)
handler := suite.proposalHandler.PrepareProposalHandler()
req := suite.createPrepareProposalRequest(maxTxBytes)
res := handler(suite.ctx, req)
// -------------------- Check Invariants -------------------- //
// The first slot in the proposal must be the auction info
auctionInfo := abci.AuctionInfo{}
err := auctionInfo.Unmarshal(res.Txs[abci.AuctionInfoIndex])
suite.Require().NoError(err)
// Total bytes must be less than or equal to maxTxBytes
totalBytes := int64(0)
for _, tx := range res.Txs[abci.NumInjectedTxs:] {
totalBytes += int64(len(tx))
}
suite.Require().LessOrEqual(totalBytes, maxTxBytes)
// 2. the number of transactions in the response must be equal to the number of expected transactions
// NOTE: We add 1 to the expected number of transactions because the first transaction in the response
// is the auction transaction
suite.Require().Equal(tc.expectedNumberProposalTxs+1, len(res.Txs))
// 3. if there are auction transactions, the first transaction must be the top bid
// and the rest of the bundle must be in the response
if winningBidTx != nil {
auctionTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[1])
suite.Require().NoError(err)
bidInfo, err := suite.tobLane.GetAuctionBidInfo(auctionTx)
suite.Require().NoError(err)
for index, tx := range bidInfo.Transactions {
suite.Require().Equal(tx, res.Txs[index+1+abci.NumInjectedTxs])
}
} else if len(res.Txs) > 1 {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[1])
suite.Require().NoError(err)
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
suite.Require().Nil(bidInfo)
}
// 4. All of the transactions must be unique
uniqueTxs := make(map[string]bool)
for _, tx := range res.Txs {
suite.Require().False(uniqueTxs[string(tx)])
uniqueTxs[string(tx)] = true
}
// 5. The number of transactions in the mempool must be correct
suite.Require().Equal(tc.expectedMempoolDistribution, suite.mempool.GetTxDistribution())
})
}
}
func (suite *ABCITestSuite) TestProcessProposal() {
var (
// auction configuration
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
frontRunningProtection = true
// mempool configuration
proposal [][]byte
)
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
}
suite.builderKeeper.SetParams(suite.ctx, params)
cases := []struct {
name string
createTxs func()
response comettypes.ResponseProcessProposal_ProposalStatus
}{
{
"no transactions in mempool with no vote extension info",
func() {
proposal = nil
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"no transactions in mempool with empty vote extension info",
func() {
proposal = [][]byte{}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single normal tx, no vote extension info",
func() {
account := suite.accounts[0]
nonce := suite.nonces[account.Address.String()]
timeout := uint64(100)
numberMsgs := uint64(3)
normalTxBz, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
proposal = [][]byte{normalTxBz}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, single auction tx, no vote extension info",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid default transaction
account := suite.accounts[1]
nonce = suite.nonces[account.Address.String()] + 1
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
proposal = [][]byte{bidTx, normalTx}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs (no unwrapping)",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid default transaction
account := suite.accounts[1]
nonce = suite.nonces[account.Address.String()] + 1
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTx}, 2)
proposal = [][]byte{
auctionInfo,
bidTx,
normalTx,
}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs (with unwrapping)",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 2)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx with ref txs but misplaced in proposal",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[1], bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 3)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = [][]byte{
auctionInfo,
bidTxBz,
bidInfo.Transactions[1],
bidInfo.Transactions[0],
}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, but auction tx is not valid",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, suite.accounts[1]} // front-running
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 3)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs but wrong auction tx is at top of block",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create another valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("foo", sdk.NewInt(1000000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz, bidTxBz2}, 3)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs and correct auction tx is selected",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create another valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("foo", sdk.NewInt(1000000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz, bidTxBz2}, 2)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz2)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz2,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"multiple auction txs included in block",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create another valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("foo", sdk.NewInt(1000000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz, bidTxBz2}, 2)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz2)
bidInfo2 := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz2,
},
bidInfo.Transactions...,
)
proposal = append(proposal, bidTxBz)
proposal = append(proposal, bidInfo2.Transactions...)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, but rest of the mempool is invalid",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 2)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
proposal = append(proposal, []byte("invalid tx"))
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs with ref txs + normal transactions",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 2)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
normalTxBz, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[1], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
normalTxBz, err = testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[2], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"front-running protection disabled",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], suite.accounts[1], bidder, suite.accounts[3], suite.accounts[4]}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, uint64(len(signers)+1))
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
normalTxBz, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[5], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
normalTxBz, err = testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[6], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
// disable frontrunning protection
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: false,
}
suite.builderKeeper.SetParams(suite.ctx, params)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
// suite.SetupTest() // reset
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.tobLane, suite.mempool)
// reset the proposal handler with the new mempool
suite.proposalHandler = abci.NewProposalHandler(
[]blockbuster.Lane{suite.baseLane},
suite.tobLane, log.NewNopLogger(),
suite.encodingConfig.TxConfig.TxEncoder(),
suite.encodingConfig.TxConfig.TxDecoder(),
)
tc.createTxs()
handler := suite.proposalHandler.ProcessProposalHandler()
res := handler(suite.ctx, comettypes.RequestProcessProposal{
Txs: proposal,
})
// Check if the response is valid
suite.Require().Equal(tc.response, res.Status)
})
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ alpha/RC tag is released. These types are simply used to prototype and develop
against.
*/
//nolint
package v2
package abci
import (
sdk "github.com/cosmos/cosmos-sdk/types"
-336
View File
@@ -1,336 +0,0 @@
package v2_test
import (
"math/rand"
"testing"
"time"
comettypes "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/skip-mev/pob/abci"
v2 "github.com/skip-mev/pob/abci/v2"
"github.com/skip-mev/pob/mempool"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/ante"
"github.com/skip-mev/pob/x/builder/keeper"
buildertypes "github.com/skip-mev/pob/x/builder/types"
"github.com/stretchr/testify/suite"
)
type ABCITestSuite struct {
suite.Suite
ctx sdk.Context
// mempool setup
mempool *mempool.AuctionMempool
logger log.Logger
encodingConfig testutils.EncodingConfig
proposalHandler *v2.ProposalHandler
voteExtensionHandler *v2.VoteExtensionHandler
config mempool.AuctionFactory
txs map[string]struct{}
// auction bid setup
auctionBidAmount sdk.Coin
minBidIncrement sdk.Coin
// builder setup
builderKeeper keeper.Keeper
bankKeeper *testutils.MockBankKeeper
accountKeeper *testutils.MockAccountKeeper
distrKeeper *testutils.MockDistributionKeeper
stakingKeeper *testutils.MockStakingKeeper
builderDecorator ante.BuilderDecorator
key *storetypes.KVStoreKey
authorityAccount sdk.AccAddress
// account set up
accounts []testutils.Account
balances sdk.Coins
random *rand.Rand
nonces map[string]uint64
}
func TestABCISuite(t *testing.T) {
suite.Run(t, new(ABCITestSuite))
}
func (suite *ABCITestSuite) SetupTest() {
// General config
suite.encodingConfig = testutils.CreateTestEncodingConfig()
suite.random = rand.New(rand.NewSource(time.Now().Unix()))
suite.key = storetypes.NewKVStoreKey(buildertypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(suite.T(), suite.key, storetypes.NewTransientStoreKey("transient_test"))
suite.ctx = testCtx.Ctx.WithBlockHeight(1)
// Mempool set up
suite.config = mempool.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder())
suite.mempool = mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0, suite.config)
suite.txs = make(map[string]struct{})
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(1000000000))
suite.minBidIncrement = sdk.NewCoin("foo", sdk.NewInt(1000))
// Mock keepers set up
ctrl := gomock.NewController(suite.T())
suite.accountKeeper = testutils.NewMockAccountKeeper(ctrl)
suite.accountKeeper.EXPECT().GetModuleAddress(buildertypes.ModuleName).Return(sdk.AccAddress{}).AnyTimes()
suite.bankKeeper = testutils.NewMockBankKeeper(ctrl)
suite.distrKeeper = testutils.NewMockDistributionKeeper(ctrl)
suite.stakingKeeper = testutils.NewMockStakingKeeper(ctrl)
suite.authorityAccount = sdk.AccAddress([]byte("authority"))
// Builder keeper / decorator set up
suite.builderKeeper = keeper.NewKeeper(
suite.encodingConfig.Codec,
suite.key,
suite.accountKeeper,
suite.bankKeeper,
suite.distrKeeper,
suite.stakingKeeper,
suite.authorityAccount.String(),
)
err := suite.builderKeeper.SetParams(suite.ctx, buildertypes.DefaultParams())
suite.Require().NoError(err)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 10)
suite.balances = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000000000000000000)))
suite.nonces = make(map[string]uint64)
for _, acc := range suite.accounts {
suite.nonces[acc.Address.String()] = 0
}
// Proposal handler set up
suite.logger = log.NewNopLogger()
suite.proposalHandler = v2.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
suite.voteExtensionHandler = v2.NewVoteExtensionHandler(suite.mempool, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), suite.anteHandler)
}
func (suite *ABCITestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, _ bool) (sdk.Context, error) {
signer := tx.GetMsgs()[0].GetSigners()[0]
suite.bankKeeper.EXPECT().GetAllBalances(ctx, signer).AnyTimes().Return(suite.balances)
next := func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}
ctx, err := suite.builderDecorator.AnteHandle(ctx, tx, false, next)
if err != nil {
return ctx, err
}
return ctx, nil
}
func (suite *ABCITestSuite) createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs int, insertRefTxs bool) int {
suite.mempool = mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0, suite.config)
// Insert a bunch of normal transactions into the global mempool
for i := 0; i < numNormalTxs; i++ {
// randomly select an account to create the tx
randomIndex := suite.random.Intn(len(suite.accounts))
acc := suite.accounts[randomIndex]
// create a few random msgs
randomMsgs := testutils.CreateRandomMsgs(acc.Address, 3)
nonce := suite.nonces[acc.Address.String()]
randomTx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, randomMsgs)
suite.Require().NoError(err)
suite.nonces[acc.Address.String()]++
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), randomTx))
}
suite.Require().Equal(numNormalTxs, suite.mempool.CountTx())
suite.Require().Equal(0, suite.mempool.CountAuctionTx())
// Insert a bunch of auction transactions into the global mempool and auction mempool
for i := 0; i < numAuctionTxs; i++ {
// randomly select a bidder to create the tx
randomIndex := suite.random.Intn(len(suite.accounts))
acc := suite.accounts[randomIndex]
// create a new auction bid msg with numBundledTxs bundled transactions
nonce := suite.nonces[acc.Address.String()]
bidMsg, err := testutils.CreateMsgAuctionBid(suite.encodingConfig.TxConfig, acc, suite.auctionBidAmount, nonce, numBundledTxs)
suite.nonces[acc.Address.String()] += uint64(numBundledTxs)
suite.Require().NoError(err)
// create the auction tx
nonce = suite.nonces[acc.Address.String()]
auctionTx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, []sdk.Msg{bidMsg})
suite.Require().NoError(err)
// insert the auction tx into the global mempool
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), auctionTx))
suite.nonces[acc.Address.String()]++
if insertRefTxs {
for _, refRawTx := range bidMsg.GetTransactions() {
refTx, err := suite.encodingConfig.TxConfig.TxDecoder()(refRawTx)
suite.Require().NoError(err)
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), refTx))
}
}
// decrement the bid amount for the next auction tx
suite.auctionBidAmount = suite.auctionBidAmount.Sub(suite.minBidIncrement)
}
numSeenGlobalTxs := 0
for iterator := suite.mempool.Select(suite.ctx, nil); iterator != nil; iterator = iterator.Next() {
numSeenGlobalTxs++
}
numSeenAuctionTxs := 0
for iterator := suite.mempool.AuctionBidSelect(suite.ctx); iterator != nil; iterator = iterator.Next() {
numSeenAuctionTxs++
}
var totalNumTxs int
suite.Require().Equal(numAuctionTxs, suite.mempool.CountAuctionTx())
if insertRefTxs {
totalNumTxs = numNormalTxs + numAuctionTxs*(numBundledTxs)
suite.Require().Equal(totalNumTxs, suite.mempool.CountTx())
suite.Require().Equal(totalNumTxs, numSeenGlobalTxs)
} else {
totalNumTxs = numNormalTxs
suite.Require().Equal(totalNumTxs, suite.mempool.CountTx())
suite.Require().Equal(totalNumTxs, numSeenGlobalTxs)
}
suite.Require().Equal(numAuctionTxs, numSeenAuctionTxs)
return totalNumTxs
}
func (suite *ABCITestSuite) exportMempool() [][]byte {
txs := make([][]byte, 0)
seenTxs := make(map[string]bool)
iterator := suite.mempool.Select(suite.ctx, nil)
for ; iterator != nil; iterator = iterator.Next() {
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(iterator.Tx())
suite.Require().NoError(err)
if !seenTxs[string(txBz)] {
txs = append(txs, txBz)
}
}
return txs
}
func (suite *ABCITestSuite) createPrepareProposalRequest(maxBytes int64) comettypes.RequestPrepareProposal {
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx)
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
tx := auctionIterator.Tx()
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
voteExtensions = append(voteExtensions, comettypes.ExtendedVoteInfo{
VoteExtension: txBz,
})
}
return comettypes.RequestPrepareProposal{
MaxTxBytes: maxBytes,
LocalLastCommit: comettypes.ExtendedCommitInfo{
Votes: voteExtensions,
},
}
}
func (suite *ABCITestSuite) createExtendedCommitInfoFromTxBzs(txs [][]byte) []byte {
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
for _, txBz := range txs {
voteExtensions = append(voteExtensions, comettypes.ExtendedVoteInfo{
VoteExtension: txBz,
})
}
commitInfo := comettypes.ExtendedCommitInfo{
Votes: voteExtensions,
}
commitInfoBz, err := commitInfo.Marshal()
suite.Require().NoError(err)
return commitInfoBz
}
func (suite *ABCITestSuite) createAuctionInfoFromTxBzs(txs [][]byte, numTxs uint64) []byte {
auctionInfo := abci.AuctionInfo{
ExtendedCommitInfo: suite.createExtendedCommitInfoFromTxBzs(txs),
NumTxs: numTxs,
MaxTxBytes: int64(len(txs[0])),
}
auctionInfoBz, err := auctionInfo.Marshal()
suite.Require().NoError(err)
return auctionInfoBz
}
func (suite *ABCITestSuite) getAllAuctionTxs() ([]sdk.Tx, [][]byte) {
auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx)
txs := make([]sdk.Tx, 0)
txBzs := make([][]byte, 0)
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
txs = append(txs, auctionIterator.Tx())
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(auctionIterator.Tx())
suite.Require().NoError(err)
txBzs = append(txBzs, bz)
}
return txs, txBzs
}
func (suite *ABCITestSuite) createExtendedCommitInfoFromTxs(txs []sdk.Tx) comettypes.ExtendedCommitInfo {
voteExtensions := make([][]byte, 0)
for _, tx := range txs {
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
voteExtensions = append(voteExtensions, bz)
}
return suite.createExtendedCommitInfo(voteExtensions)
}
func (suite *ABCITestSuite) createExtendedVoteInfo(voteExtensions [][]byte) []comettypes.ExtendedVoteInfo {
commitInfo := make([]comettypes.ExtendedVoteInfo, 0)
for _, voteExtension := range voteExtensions {
info := comettypes.ExtendedVoteInfo{
VoteExtension: voteExtension,
}
commitInfo = append(commitInfo, info)
}
return commitInfo
}
func (suite *ABCITestSuite) createExtendedCommitInfo(voteExtensions [][]byte) comettypes.ExtendedCommitInfo {
commitInfo := comettypes.ExtendedCommitInfo{
Votes: suite.createExtendedVoteInfo(voteExtensions),
}
return commitInfo
}
-237
View File
@@ -1,237 +0,0 @@
package v2
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
pobabci "github.com/skip-mev/pob/abci"
mempool "github.com/skip-mev/pob/mempool"
)
const (
// NumInjectedTxs is the minimum number of transactions that were injected into
// the proposal but are not actual transactions. In this case, the auction
// info is injected into the proposal but should be ignored by the application.ß
NumInjectedTxs = 1
// AuctionInfoIndex is the index of the auction info in the proposal.
AuctionInfoIndex = 0
)
type (
// ProposalMempool contains the methods required by the ProposalHandler
// to interact with the local mempool.
ProposalMempool interface {
sdkmempool.Mempool
// The AuctionFactory interface is utilized to retrieve, validate, and wrap bid
// information into the block proposal.
mempool.AuctionFactory
// AuctionBidSelect returns an iterator that iterates over the top bid
// transactions in the mempool.
AuctionBidSelect(ctx context.Context) sdkmempool.Iterator
}
// ProposalHandler contains the functionality and handlers required to\
// process, validate and build blocks.
ProposalHandler struct {
mempool ProposalMempool
logger log.Logger
anteHandler sdk.AnteHandler
txEncoder sdk.TxEncoder
txDecoder sdk.TxDecoder
}
)
// NewProposalHandler returns a ProposalHandler that contains the functionality and handlers
// required to process, validate and build blocks.
func NewProposalHandler(
mp ProposalMempool,
logger log.Logger,
anteHandler sdk.AnteHandler,
txEncoder sdk.TxEncoder,
txDecoder sdk.TxDecoder,
) *ProposalHandler {
return &ProposalHandler{
mempool: mp,
logger: logger,
anteHandler: anteHandler,
txEncoder: txEncoder,
txDecoder: txDecoder,
}
}
// PrepareProposalHandler returns the PrepareProposal ABCI handler that performs
// top-of-block auctioning and general block proposal construction.
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
// Proposal includes all of the transactions that will be included in the
// block along with the vote extensions from the previous block included at
// the beginning of the proposal. Vote extensions must be included in the
// first slot of the proposal because they are inaccessible in ProcessProposal.
proposal := make([][]byte, 0)
// Build the top of block portion of the proposal given the vote extensions
// from the previous block.
topOfBlock := h.BuildTOB(ctx, req.LocalLastCommit, req.MaxTxBytes)
// If information is unable to be marshaled, we return an empty proposal. This will
// cause another proposal to be generated after it is rejected in ProcessProposal.
lastCommitInfo, err := req.LocalLastCommit.Marshal()
if err != nil {
return abci.ResponsePrepareProposal{Txs: proposal}
}
auctionInfo := pobabci.AuctionInfo{
ExtendedCommitInfo: lastCommitInfo,
MaxTxBytes: req.MaxTxBytes,
NumTxs: uint64(len(topOfBlock.Txs)),
}
// Add the auction info and top of block transactions into the proposal.
auctionInfoBz, err := auctionInfo.Marshal()
if err != nil {
return abci.ResponsePrepareProposal{Txs: proposal}
}
proposal = append(proposal, auctionInfoBz)
proposal = append(proposal, topOfBlock.Txs...)
// Select remaining transactions for the block proposal until we've reached
// size capacity.
totalTxBytes := topOfBlock.Size
txsToRemove := make(map[sdk.Tx]struct{}, 0)
for iterator := h.mempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() {
memTx := iterator.Tx()
// If the transaction has already been seen in the top of block, skip it.
txBz, err := h.txEncoder(memTx)
if err != nil {
txsToRemove[memTx] = struct{}{}
continue
}
hashBz := sha256.Sum256(txBz)
hash := hex.EncodeToString(hashBz[:])
if _, ok := topOfBlock.Cache[hash]; ok {
continue
}
// Verify that the transaction is valid.
txBz, err = h.PrepareProposalVerifyTx(ctx, memTx)
if err != nil {
txsToRemove[memTx] = struct{}{}
continue
}
txSize := int64(len(txBz))
if totalTxBytes += txSize; totalTxBytes <= req.MaxTxBytes {
proposal = append(proposal, txBz)
} else {
// We've reached capacity per req.MaxTxBytes so we cannot select any
// more transactions.
break
}
}
// Remove all invalid transactions from the mempool.
for tx := range txsToRemove {
h.RemoveTx(tx)
}
return abci.ResponsePrepareProposal{Txs: proposal}
}
}
// ProcessProposalHandler returns the ProcessProposal ABCI handler that performs
// block proposal verification.
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal {
proposal := req.Txs
// Verify that the same top of block transactions can be built from the vote
// extensions included in the proposal.
auctionInfo, err := h.VerifyTOB(ctx, proposal)
if err != nil {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
// Track the transactions that need to be removed from the mempool.
txsToRemove := make(map[sdk.Tx]struct{}, 0)
invalidProposal := false
// Verify that the remaining transactions in the proposal are valid.
for _, txBz := range proposal[auctionInfo.NumTxs+NumInjectedTxs:] {
tx, err := h.ProcessProposalVerifyTx(ctx, txBz)
if tx == nil || err != nil {
invalidProposal = true
if tx != nil {
txsToRemove[tx] = struct{}{}
}
continue
}
// The only auction transactions that should be included in the block proposal
// must be at the top of the block.
if bidInfo, err := h.mempool.GetAuctionBidInfo(tx); err != nil || bidInfo != nil {
invalidProposal = true
}
}
// Remove all invalid transactions from the mempool.
for tx := range txsToRemove {
h.RemoveTx(tx)
}
if invalidProposal {
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
}
}
// PrepareProposalVerifyTx encodes a transaction and verifies it.
func (h *ProposalHandler) PrepareProposalVerifyTx(ctx sdk.Context, tx sdk.Tx) ([]byte, error) {
txBz, err := h.txEncoder(tx)
if err != nil {
return nil, err
}
return txBz, h.verifyTx(ctx, tx)
}
// ProcessProposalVerifyTx decodes a transaction and verifies it.
func (h *ProposalHandler) ProcessProposalVerifyTx(ctx sdk.Context, txBz []byte) (sdk.Tx, error) {
tx, err := h.txDecoder(txBz)
if err != nil {
return nil, err
}
return tx, h.verifyTx(ctx, tx)
}
// RemoveTx removes a transaction from the application-side mempool.
func (h *ProposalHandler) RemoveTx(tx sdk.Tx) {
if err := h.mempool.Remove(tx); err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
panic(fmt.Errorf("failed to remove invalid transaction from the mempool: %w", err))
}
}
// VerifyTx verifies a transaction against the application's state.
func (h *ProposalHandler) verifyTx(ctx sdk.Context, tx sdk.Tx) error {
if h.anteHandler != nil {
_, err := h.anteHandler(ctx, tx, false)
return err
}
return nil
}
-766
View File
@@ -1,766 +0,0 @@
package v2_test
import (
comettypes "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/abci"
v2 "github.com/skip-mev/pob/abci/v2"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/ante"
buildertypes "github.com/skip-mev/pob/x/builder/types"
)
func (suite *ABCITestSuite) TestPrepareProposal() {
var (
// the modified transactions cannot exceed this size
maxTxBytes int64 = 1000000000000000000
// mempool configuration
numNormalTxs = 100
numAuctionTxs = 100
numBundledTxs = 3
insertRefTxs = false
expectedTopAuctionTx sdk.Tx
// auction configuration
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
frontRunningProtection = true
)
cases := []struct {
name string
malleate func()
expectedNumberProposalTxs int
expectedNumberTxsInMempool int
expectedNumberTxsInAuctionMempool int
}{
{
"single bundle in the mempool",
func() {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
5,
3,
1,
},
{
"single bundle in the mempool, no ref txs in mempool",
func() {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
5,
0,
1,
},
{
"single bundle in the mempool, not valid",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(100000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(10000)) // this will fail the ante handler
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = nil
},
1,
0,
0,
},
{
"single bundle in the mempool, not valid with ref txs in mempool",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(100000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(10000)) // this will fail the ante handler
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = nil
},
4,
3,
0,
},
{
"multiple bundles in the mempool, no normal txs + no ref txs in mempool",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(10000000))
numNormalTxs = 0
numAuctionTxs = 10
numBundledTxs = 3
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
5,
0,
10,
},
{
"multiple bundles in the mempool, normal txs + ref txs in mempool",
func() {
numNormalTxs = 0
numAuctionTxs = 10
numBundledTxs = 3
insertRefTxs = true
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
32,
30,
10,
},
{
"normal txs only",
func() {
numNormalTxs = 1
numAuctionTxs = 0
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
2,
1,
0,
},
{
"many normal txs only",
func() {
numNormalTxs = 100
numAuctionTxs = 0
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
101,
100,
0,
},
{
"single normal tx, single auction tx",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
3,
1,
1,
},
{
"single normal tx, single auction tx with ref txs",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
6,
4,
1,
},
{
"single normal tx, single failing auction tx with ref txs",
func() {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = true
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(2000)) // this will fail the ante handler
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000000000))
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = nil
},
5,
4,
0,
},
{
"many normal tx, single auction tx with no ref txs",
func() {
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(2000000))
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = nil
},
102,
100,
1,
},
{
"many normal tx, single auction tx with ref txs",
func() {
numNormalTxs = 100
numAuctionTxs = 100
numBundledTxs = 3
insertRefTxs = true
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
},
402,
400,
100,
},
{
"many normal tx, many auction tx with ref txs but top bid is invalid",
func() {
numNormalTxs = 100
numAuctionTxs = 100
numBundledTxs = 1
insertRefTxs = true
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
// create a new bid that is greater than the current top bid
bid := sdk.NewCoin("foo", sdk.NewInt(200000000000000000))
bidTx, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[0],
bid,
0,
0,
[]testutils.Account{suite.accounts[0], suite.accounts[1]},
)
suite.Require().NoError(err)
// add the new bid to the mempool
err = suite.mempool.Insert(suite.ctx, bidTx)
suite.Require().NoError(err)
suite.Require().Equal(suite.mempool.CountAuctionTx(), 101)
},
202,
200,
100,
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
tc.malleate()
// Create a new auction.
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
MinBidIncrement: suite.minBidIncrement,
}
suite.builderKeeper.SetParams(suite.ctx, params)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
// Reset the proposal handler with the new mempool.
suite.proposalHandler = v2.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
// Create a prepare proposal request based on the current state of the mempool.
handler := suite.proposalHandler.PrepareProposalHandler()
req := suite.createPrepareProposalRequest(maxTxBytes)
res := handler(suite.ctx, req)
// -------------------- Check Invariants -------------------- //
// The first slot in the proposal must be the auction info
auctionInfo := abci.AuctionInfo{}
err := auctionInfo.Unmarshal(res.Txs[v2.AuctionInfoIndex])
suite.Require().NoError(err)
// Total bytes must be less than or equal to maxTxBytes
totalBytes := int64(0)
for _, tx := range res.Txs[v2.NumInjectedTxs:] {
totalBytes += int64(len(tx))
}
suite.Require().LessOrEqual(totalBytes, maxTxBytes)
// The number of transactions in the response must be equal to the number of expected transactions
suite.Require().Equal(tc.expectedNumberProposalTxs, len(res.Txs))
// If there are auction transactions, the first transaction must be the top bid
// and the rest of the bundle must be in the response
if expectedTopAuctionTx != nil {
auctionTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[1])
suite.Require().NoError(err)
bidInfo, err := suite.mempool.GetAuctionBidInfo(auctionTx)
suite.Require().NoError(err)
for index, tx := range bidInfo.Transactions {
suite.Require().Equal(tx, res.Txs[v2.NumInjectedTxs+index+1])
}
}
// 5. All of the transactions must be unique
uniqueTxs := make(map[string]bool)
for _, tx := range res.Txs[v2.NumInjectedTxs:] {
suite.Require().False(uniqueTxs[string(tx)])
uniqueTxs[string(tx)] = true
}
// 6. The number of transactions in the mempool must be correct
suite.Require().Equal(tc.expectedNumberTxsInMempool, suite.mempool.CountTx())
suite.Require().Equal(tc.expectedNumberTxsInAuctionMempool, suite.mempool.CountAuctionTx())
})
}
}
func (suite *ABCITestSuite) TestProcessProposal() {
var (
// mempool set up
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 3
insertRefTxs = false
// auction set up
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
)
cases := []struct {
name string
createTxs func() [][]byte
response comettypes.ResponseProcessProposal_ProposalStatus
}{
{
"single normal tx, no vote extension info",
func() [][]byte {
numNormalTxs = 1
numAuctionTxs = 0
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
txs := suite.exportMempool()
return txs
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, no vote extension info",
func() [][]byte {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
return suite.exportMempool()
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, single auction tx, no vote extension info",
func() [][]byte {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 0
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
return suite.exportMempool()
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs (no unwrapping)",
func() [][]byte {
numNormalTxs = 1
numAuctionTxs = 1
numBundledTxs = 4
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(topAuctionTx)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 5)
proposal := append([][]byte{
auctionInfo,
txBz,
}, suite.exportMempool()...)
return proposal
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs (with unwrapping)",
func() [][]byte {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 4
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(topAuctionTx)
bidInfo, err := suite.mempool.GetAuctionBidInfo(topAuctionTx)
suite.Require().NoError(err)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 5)
proposal := append([][]byte{
auctionInfo,
txBz,
}, bidInfo.Transactions...)
return proposal
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx but no inclusion of ref txs",
func() [][]byte {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 4
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(topAuctionTx)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 5)
return [][]byte{
auctionInfo,
txBz,
}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, but auction tx is not valid",
func() [][]byte {
tx, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[0],
sdk.NewCoin("foo", sdk.NewInt(100)),
1,
0, // invalid timeout
[]testutils.Account{},
)
suite.Require().NoError(err)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
auctionInfoBz := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 1)
return [][]byte{
auctionInfoBz,
txBz,
}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs, but auction tx is not valid",
func() [][]byte {
tx, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[0],
sdk.NewCoin("foo", sdk.NewInt(100)),
1,
1,
[]testutils.Account{suite.accounts[1], suite.accounts[1], suite.accounts[0]},
)
suite.Require().NoError(err)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
auctionInfoBz := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 4)
bidInfo, err := suite.mempool.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
return append([][]byte{
auctionInfoBz,
txBz,
}, bidInfo.Transactions...)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs but wrong auction tx is at top of block",
func() [][]byte {
numNormalTxs = 0
numAuctionTxs = 2
numBundledTxs = 0
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
_, auctionTxBzs := suite.getAllAuctionTxs()
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 1)
proposal := [][]byte{
auctionInfo,
auctionTxBzs[1],
}
return proposal
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs included in block",
func() [][]byte {
numNormalTxs = 0
numAuctionTxs = 2
numBundledTxs = 0
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
_, auctionTxBzs := suite.getAllAuctionTxs()
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 1)
proposal := [][]byte{
auctionInfo,
auctionTxBzs[0],
auctionTxBzs[1],
}
return proposal
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, but rest of the mempool is invalid",
func() [][]byte {
numNormalTxs = 0
numAuctionTxs = 1
numBundledTxs = 0
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(topAuctionTx)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 1)
proposal := [][]byte{
auctionInfo,
txBz,
[]byte("invalid tx"),
}
return proposal
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with filled mempool, but rest of the mempool is invalid",
func() [][]byte {
numNormalTxs = 100
numAuctionTxs = 1
numBundledTxs = 0
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(topAuctionTx)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 1)
proposal := append([][]byte{
auctionInfo,
txBz,
}, suite.exportMempool()...)
proposal = append(proposal, []byte("invalid tx"))
return proposal
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs with filled mempool",
func() [][]byte {
numNormalTxs = 100
numAuctionTxs = 10
numBundledTxs = 0
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
_, auctionTxBzs := suite.getAllAuctionTxs()
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 1)
proposal := append([][]byte{
auctionInfo,
auctionTxBzs[0],
}, suite.exportMempool()...)
return proposal
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"multiple auction txs with ref txs + filled mempool",
func() [][]byte {
numNormalTxs = 100
numAuctionTxs = 10
numBundledTxs = 10
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
auctionTxs, auctionTxBzs := suite.getAllAuctionTxs()
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 11)
bidInfo, err := suite.mempool.GetAuctionBidInfo(auctionTxs[0])
suite.Require().NoError(err)
proposal := append([][]byte{
auctionInfo,
auctionTxBzs[0],
}, bidInfo.Transactions...)
proposal = append(proposal, suite.exportMempool()...)
return proposal
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"auction tx with front-running",
func() [][]byte {
numNormalTxs = 100
numAuctionTxs = 0
numBundledTxs = 0
insertRefTxs = false
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
topAuctionTx, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[0],
sdk.NewCoin("foo", sdk.NewInt(1000000)),
0,
1,
[]testutils.Account{suite.accounts[0], suite.accounts[1]}, // front-running
)
suite.Require().NoError(err)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
suite.Require().NoError(err)
bidInfo, err := suite.mempool.GetAuctionBidInfo(topAuctionTx)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 3)
proposal := append([][]byte{
auctionInfo,
txBz,
}, bidInfo.Transactions...)
proposal = append(proposal, suite.exportMempool()...)
return proposal
},
comettypes.ResponseProcessProposal_REJECT,
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
// create a new auction
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: true,
MinBidIncrement: suite.minBidIncrement,
}
suite.builderKeeper.SetParams(suite.ctx, params)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
// reset the proposal handler with the new mempool
suite.proposalHandler = v2.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
handler := suite.proposalHandler.ProcessProposalHandler()
res := handler(suite.ctx, comettypes.RequestProcessProposal{
Txs: tc.createTxs(),
})
// Check if the response is valid
suite.Require().Equal(tc.response, res.Status)
})
}
}
@@ -1,30 +1,34 @@
package v2
package abci
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/skip-mev/pob/mempool"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
)
type (
// VoteExtensionMempool contains the methods required by the VoteExtensionHandler
// to interact with the local mempool.
VoteExtensionMempool interface {
Remove(tx sdk.Tx) error
AuctionBidSelect(ctx context.Context) sdkmempool.Iterator
GetAuctionBidInfo(tx sdk.Tx) (*mempool.AuctionBidInfo, error)
WrapBundleTransaction(tx []byte) (sdk.Tx, error)
// TOBLaneVE contains the methods required by the VoteExtensionHandler
// to interact with the local mempool i.e. the top of block lane.
TOBLaneVE interface {
sdkmempool.Mempool
// 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.).
auction.Factory
// VerifyTx is utilized to verify a bid transaction according to the preferences
// of the top of block lane.
VerifyTx(ctx sdk.Context, tx sdk.Tx) error
}
// VoteExtensionHandler contains the functionality and handlers required to
// process, validate and build vote extensions.
VoteExtensionHandler struct {
mempool VoteExtensionMempool
tobLane TOBLaneVE
// txDecoder is used to decode the top bidding auction transaction
txDecoder sdk.TxDecoder
@@ -32,9 +36,6 @@ type (
// txEncoder is used to encode the top bidding auction transaction
txEncoder sdk.TxEncoder
// anteHandler is used to validate the vote extension
anteHandler sdk.AnteHandler
// cache is used to store the results of the vote extension verification
// for a given block height.
cache map[string]error
@@ -46,14 +47,11 @@ type (
// NewVoteExtensionHandler returns an VoteExtensionHandler that contains the functionality and handlers
// required to inject, process, and validate vote extensions.
func NewVoteExtensionHandler(mp VoteExtensionMempool, txDecoder sdk.TxDecoder,
txEncoder sdk.TxEncoder, ah sdk.AnteHandler,
) *VoteExtensionHandler {
func NewVoteExtensionHandler(lane TOBLaneVE, txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder) *VoteExtensionHandler {
return &VoteExtensionHandler{
mempool: mp,
tobLane: lane,
txDecoder: txDecoder,
txEncoder: txEncoder,
anteHandler: ah,
cache: make(map[string]error),
currentHeight: 0,
}
@@ -65,15 +63,17 @@ func NewVoteExtensionHandler(mp VoteExtensionMempool, txDecoder sdk.TxDecoder,
func (h *VoteExtensionHandler) ExtendVoteHandler() ExtendVoteHandler {
return func(ctx sdk.Context, req *RequestExtendVote) (*ResponseExtendVote, error) {
// Iterate through auction bids until we find a valid one
auctionIterator := h.mempool.AuctionBidSelect(ctx)
auctionIterator := h.tobLane.Select(ctx, nil)
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
bidTx := auctionIterator.Tx()
// Verify the bid tx can be encoded and included in vote extension
if bidBz, err := h.txEncoder(bidTx); err == nil {
// Validate the auction transaction
if err := h.verifyAuctionTx(ctx, bidTx); err == nil {
// Validate the auction transaction against a cache state
cacheCtx, _ := ctx.CacheContext()
if err := h.tobLane.VerifyTx(cacheCtx, bidTx); err == nil {
return &ResponseExtendVote{VoteExtension: bidBz}, nil
}
}
@@ -116,7 +116,7 @@ func (h *VoteExtensionHandler) VerifyVoteExtensionHandler() VerifyVoteExtensionH
}
// Verify the auction transaction and cache the result
if err = h.verifyAuctionTx(ctx, bidTx); err != nil {
if err = h.tobLane.VerifyTx(ctx, bidTx); err != nil {
h.cache[hash] = err
return &ResponseVerifyVoteExtension{Status: ResponseVerifyVoteExtension_REJECT}, err
}
@@ -136,40 +136,3 @@ func (h *VoteExtensionHandler) resetCache(blockHeight int64) {
h.currentHeight = blockHeight
}
}
// verifyAuctionTx verifies a transaction against the application's state.
func (h *VoteExtensionHandler) verifyAuctionTx(ctx sdk.Context, bidTx sdk.Tx) error {
// Verify the vote extension is a auction transaction
bidInfo, err := h.mempool.GetAuctionBidInfo(bidTx)
if err != nil {
return err
}
if bidInfo == nil {
return fmt.Errorf("vote extension is not a valid auction transaction")
}
if h.anteHandler == nil {
return nil
}
// Cache context is used to avoid state changes
cache, _ := ctx.CacheContext()
if _, err := h.anteHandler(cache, bidTx, false); err != nil {
return err
}
// Verify all bundled transactions
for _, tx := range bidInfo.Transactions {
wrappedTx, err := h.mempool.WrapBundleTransaction(tx)
if err != nil {
return err
}
if _, err := h.anteHandler(cache, wrappedTx, false); err != nil {
return err
}
}
return nil
}
@@ -1,9 +1,8 @@
package v2_test
package abci_test
import (
sdk "github.com/cosmos/cosmos-sdk/types"
v2 "github.com/skip-mev/pob/abci/v2"
"github.com/skip-mev/pob/mempool"
"github.com/skip-mev/pob/abci"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/types"
)
@@ -13,12 +12,8 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
MaxBundleSize: 5,
ReserveFee: sdk.NewCoin("foo", sdk.NewInt(10)),
FrontRunningProtection: true,
MinBidIncrement: suite.minBidIncrement,
}
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
testCases := []struct {
name string
getExpectedVE func() []byte
@@ -26,21 +21,20 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
{
"empty mempool",
func() []byte {
suite.createFilledMempool(0, 0, 0, false)
return []byte{}
},
},
{
"filled mempool with no auction transactions",
func() []byte {
suite.createFilledMempool(100, 0, 0, false)
suite.fillBaseLane(10)
return []byte{}
},
},
{
"mempool with invalid auction transaction (too many bundled transactions)",
func() []byte {
suite.createFilledMempool(0, 1, int(params.MaxBundleSize)+1, true)
suite.fillTOBLane(3, int(params.MaxBundleSize)+1)
return []byte{}
},
},
@@ -55,9 +49,7 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.mempool = mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0, suite.config)
err = suite.mempool.Insert(suite.ctx, bidTx)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx))
// this should return nothing since the top bid is not valid
return []byte{}
@@ -66,14 +58,12 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
{
"mempool contains only invalid auction bids (bid is too low)",
func() []byte {
params.ReserveFee = suite.auctionBidAmount
params.ReserveFee = sdk.NewCoin("foo", sdk.NewInt(10000000000000000))
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
// this way all of the bids will be too small
suite.auctionBidAmount = params.ReserveFee.Sub(sdk.NewCoin("foo", sdk.NewInt(1)))
suite.createFilledMempool(100, 100, 2, true)
suite.fillTOBLane(4, 1)
return []byte{}
},
@@ -88,10 +78,7 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.mempool = mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0, suite.config)
err = suite.mempool.Insert(suite.ctx, bidTx)
suite.Require().NoError(err)
suite.Require().NoError(suite.tobLane.Insert(suite.ctx, bidTx))
// this should return nothing since the top bid is not valid
return []byte{}
@@ -100,26 +87,26 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
{
"top bid is invalid but next best is valid",
func() []byte {
params.ReserveFee = sdk.NewCoin("foo", sdk.NewInt(100))
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
params.ReserveFee = sdk.NewCoin("foo", sdk.NewInt(10))
bidder := suite.accounts[0]
bid := suite.auctionBidAmount.Add(suite.minBidIncrement)
bid := params.ReserveFee.Add(params.ReserveFee)
signers := []testutils.Account{bidder}
timeout := 0
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx))
suite.createFilledMempool(100, 100, 2, true)
topBidTx := suite.mempool.GetTopAuctionTx(suite.ctx)
err = suite.mempool.Insert(suite.ctx, bidTx)
bidder = suite.accounts[1]
bid = params.ReserveFee
signers = []testutils.Account{bidder}
timeout = 100
bidTx2, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx2))
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(topBidTx)
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(bidTx2)
suite.Require().NoError(err)
return bz
@@ -129,10 +116,14 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
expectedVE := tc.getExpectedVE()
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
// Reset the handler with the new mempool
suite.voteExtensionHandler = v2.NewVoteExtensionHandler(suite.mempool, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), suite.anteHandler)
suite.voteExtensionHandler = abci.NewVoteExtensionHandler(suite.tobLane, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder())
handler := suite.voteExtensionHandler.ExtendVoteHandler()
resp, err := handler(suite.ctx, nil)
@@ -148,7 +139,6 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
MaxBundleSize: 5,
ReserveFee: sdk.NewCoin("foo", sdk.NewInt(100)),
FrontRunningProtection: true,
MinBidIncrement: sdk.NewCoin("foo", sdk.NewInt(10)), // can't be tested atm
}
err := suite.builderKeeper.SetParams(suite.ctx, params)
@@ -156,13 +146,13 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
testCases := []struct {
name string
req func() *v2.RequestVerifyVoteExtension
req func() *abci.RequestVerifyVoteExtension
expectedErr bool
}{
{
"invalid vote extension bytes",
func() *v2.RequestVerifyVoteExtension {
return &v2.RequestVerifyVoteExtension{
func() *abci.RequestVerifyVoteExtension {
return &abci.RequestVerifyVoteExtension{
VoteExtension: []byte("invalid vote extension"),
}
},
@@ -170,8 +160,8 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"empty vote extension bytes",
func() *v2.RequestVerifyVoteExtension {
return &v2.RequestVerifyVoteExtension{
func() *abci.RequestVerifyVoteExtension {
return &abci.RequestVerifyVoteExtension{
VoteExtension: []byte{},
}
},
@@ -179,8 +169,8 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"nil vote extension bytes",
func() *v2.RequestVerifyVoteExtension {
return &v2.RequestVerifyVoteExtension{
func() *abci.RequestVerifyVoteExtension {
return &abci.RequestVerifyVoteExtension{
VoteExtension: nil,
}
},
@@ -188,14 +178,14 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"invalid extension with bid tx with bad timeout",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(10))
signers := []testutils.Account{bidder}
timeout := 0
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
@@ -203,14 +193,14 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"invalid vote extension with bid tx with bad bid",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := sdk.NewCoin("foo", sdk.NewInt(0))
signers := []testutils.Account{bidder}
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
@@ -218,14 +208,14 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"valid vote extension",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
signers := []testutils.Account{bidder}
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
@@ -233,7 +223,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"invalid vote extension with front running bid tx",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
timeout := 10
@@ -242,7 +232,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
signers := []testutils.Account{bidder, bundlee}
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
@@ -250,7 +240,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"invalid vote extension with too many bundle txs",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
// disable front running protection
params.FrontRunningProtection = false
err := suite.builderKeeper.SetParams(suite.ctx, params)
@@ -262,7 +252,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
@@ -270,7 +260,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"invalid vote extension with a failing bundle tx",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
@@ -286,7 +276,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(bidTx)
suite.Require().NoError(err)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
@@ -294,7 +284,7 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
},
{
"valid vote extension + no comparison to local mempool",
func() *v2.RequestVerifyVoteExtension {
func() *abci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
signers := []testutils.Account{bidder}
@@ -303,17 +293,17 @@ func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
// Add a bid to the mempool that is greater than the one in the vote extension
bid = bid.Add(params.MinBidIncrement)
bid = bid.Add(params.ReserveFee)
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 10, 1, signers)
suite.Require().NoError(err)
err = suite.mempool.Insert(suite.ctx, bidTx)
suite.Require().NoError(err)
tx := suite.mempool.GetTopAuctionTx(suite.ctx)
tx := suite.tobLane.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(tx)
return &v2.RequestVerifyVoteExtension{
return &abci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},