[ENG-733]: Supporting generic auction transaction configurations (#66)

This commit is contained in:
David Terpay
2023-04-19 13:41:15 -04:00
committed by GitHub
parent a831b25025
commit 46c4dcc4df
11 changed files with 385 additions and 117 deletions
+36 -27
View File
@@ -51,41 +51,25 @@ func (ad BuilderDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
}
}
auctionMsg, err := mempool.GetMsgAuctionBidFromTx(tx)
isAuctionTx, err := ad.mempool.IsAuctionTx(tx)
if err != nil {
return ctx, err
}
// Validate the auction bid if one exists.
if auctionMsg != nil {
auctionTx, ok := tx.(TxWithTimeoutHeight)
if !ok {
return ctx, fmt.Errorf("transaction does not implement TxWithTimeoutHeight")
if isAuctionTx {
// Auction transactions must have a timeout set to a valid block height.
if err := ad.HasValidTimeout(ctx, tx); err != nil {
return ctx, err
}
timeout := auctionTx.GetTimeoutHeight()
if timeout == 0 {
return ctx, fmt.Errorf("timeout height cannot be zero")
}
bidder, err := sdk.AccAddressFromBech32(auctionMsg.Bidder)
bidInfo, err := ad.mempool.GetAuctionBidInfo(tx)
if err != nil {
return ctx, errors.Wrapf(err, "invalid bidder address (%s)", auctionMsg.Bidder)
return ctx, err
}
transactions := make([]sdk.Tx, len(auctionMsg.Transactions))
for i, tx := range auctionMsg.Transactions {
decodedTx, err := ad.txDecoder(tx)
if err != nil {
return ctx, errors.Wrapf(err, "failed to decode transaction (%s)", tx)
}
transactions[i] = decodedTx
}
topBid := sdk.Coin{}
// If the current transaction is the highest bidding transaction, then the highest bid is empty.
topBid := sdk.Coin{}
isTopBidTx, err := ad.IsTopBidTx(ctx, tx)
if err != nil {
return ctx, errors.Wrap(err, "failed to check if current transaction is highest bidding transaction")
@@ -99,7 +83,13 @@ func (ad BuilderDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
}
}
if err := ad.builderKeeper.ValidateAuctionMsg(ctx, bidder, auctionMsg.Bid, topBid, transactions); err != nil {
// Extract signers from bundle for verification.
signers, err := ad.mempool.GetBundleSigners(bidInfo.Transactions)
if err != nil {
return ctx, errors.Wrap(err, "failed to get bundle signers")
}
if err := ad.builderKeeper.ValidateBidInfo(ctx, topBid, bidInfo, signers); err != nil {
return ctx, errors.Wrap(err, "failed to validate auction bid")
}
}
@@ -114,12 +104,12 @@ func (ad BuilderDecorator) GetTopAuctionBid(ctx sdk.Context) (sdk.Coin, error) {
return sdk.Coin{}, nil
}
msgAuctionBid, err := mempool.GetMsgAuctionBidFromTx(auctionTx)
bid, err := ad.mempool.GetBid(auctionTx)
if err != nil {
return sdk.Coin{}, err
}
return msgAuctionBid.Bid, nil
return bid, nil
}
// IsTopBidTx returns true if the transaction inputted is the highest bidding auction transaction in the mempool.
@@ -141,3 +131,22 @@ func (ad BuilderDecorator) IsTopBidTx(ctx sdk.Context, tx sdk.Tx) (bool, error)
return bytes.Equal(topBidBz, currentTxBz), nil
}
// HasValidTimeout returns true if the transaction has a valid timeout height.
func (ad BuilderDecorator) HasValidTimeout(ctx sdk.Context, tx sdk.Tx) error {
auctionTx, ok := tx.(TxWithTimeoutHeight)
if !ok {
return fmt.Errorf("transaction does not implement TxWithTimeoutHeight")
}
timeout := auctionTx.GetTimeoutHeight()
if timeout == 0 {
return fmt.Errorf("timeout height cannot be zero")
}
if timeout < uint64(ctx.BlockHeight()) {
return fmt.Errorf("timeout height cannot be less than the current block height")
}
return nil
}
+2 -1
View File
@@ -244,7 +244,8 @@ func (suite *AnteTestSuite) TestAnteHandler() {
suite.Require().NoError(err)
// Insert the top bid into the mempool
mempool := mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0)
config := mempool.NewDefaultConfig(suite.encodingConfig.TxConfig.TxDecoder())
mempool := mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), 0, config)
if insertTopBid {
topAuctionTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, topBidder, topBid, 0, timeout, []testutils.Account{})
suite.Require().NoError(err)
+14 -35
View File
@@ -4,22 +4,23 @@ import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/mempool"
)
// ValidateAuctionMsg validates that the MsgAuctionBid can be included in the auction.
func (k Keeper) ValidateAuctionMsg(ctx sdk.Context, bidder sdk.AccAddress, bid, highestBid sdk.Coin, transactions []sdk.Tx) error {
// ValidateBidInfo validates that the bid can be included in the auction.
func (k Keeper) ValidateBidInfo(ctx sdk.Context, highestBid sdk.Coin, bidInfo mempool.AuctionBidInfo, signers []map[string]struct{}) error {
// Validate the bundle size.
maxBundleSize, err := k.GetMaxBundleSize(ctx)
if err != nil {
return err
}
if uint32(len(transactions)) > maxBundleSize {
return fmt.Errorf("bundle size (%d) exceeds max bundle size (%d)", len(transactions), maxBundleSize)
if uint32(len(bidInfo.Transactions)) > maxBundleSize {
return fmt.Errorf("bundle size (%d) exceeds max bundle size (%d)", len(bidInfo.Transactions), maxBundleSize)
}
// Validate the bid amount.
if err := k.ValidateAuctionBid(ctx, bidder, bid, highestBid); err != nil {
if err := k.ValidateAuctionBid(ctx, bidInfo.Bidder, bidInfo.Bid, highestBid); err != nil {
return err
}
@@ -30,7 +31,7 @@ func (k Keeper) ValidateAuctionMsg(ctx sdk.Context, bidder sdk.AccAddress, bid,
}
if protectionEnabled {
if err := k.ValidateAuctionBundle(bidder, transactions); err != nil {
if err := k.ValidateAuctionBundle(bidInfo.Bidder, signers); err != nil {
return err
}
}
@@ -100,27 +101,19 @@ func (k Keeper) ValidateAuctionBid(ctx sdk.Context, bidder sdk.AccAddress, bid,
// 2. valid: [tx1, tx2, tx3, tx4] where tx1 - tx4 are signed by the bidder.
// 3. invalid: [tx1, tx2, tx3] where tx1 and tx3 are signed by the bidder and tx2 is signed by some other signer. (possible sandwich attack)
// 4. invalid: [tx1, tx2, tx3] where tx1 is signed by the bidder, and tx2 - tx3 are signed by some other signer. (possible front-running attack)
func (k Keeper) ValidateAuctionBundle(bidder sdk.AccAddress, transactions []sdk.Tx) error {
if len(transactions) <= 1 {
func (k Keeper) ValidateAuctionBundle(bidder sdk.AccAddress, bundleSigners []map[string]struct{}) error {
if len(bundleSigners) <= 1 {
return nil
}
// prevSigners is used to track whether the signers of the current transaction overlap.
prevSigners, err := k.getTxSigners(transactions[0])
if err != nil {
return err
}
seenBidder := prevSigners[bidder.String()]
prevSigners := bundleSigners[0]
_, seenBidder := prevSigners[bidder.String()]
// Check that all subsequent transactions are signed by either
// 1. the same party as the first transaction
// 2. the same party for some arbitrary number of txs and then are all remaining txs are signed by the bidder.
for _, refTx := range transactions[1:] {
txSigners, err := k.getTxSigners(refTx)
if err != nil {
return err
}
for _, txSigners := range bundleSigners[1:] {
// Filter the signers to only those that signed the current transaction.
filterSigners(prevSigners, txSigners)
@@ -132,7 +125,7 @@ func (k Keeper) ValidateAuctionBundle(bidder sdk.AccAddress, transactions []sdk.
}
seenBidder = true
prevSigners = map[string]bool{bidder.String(): true}
prevSigners = map[string]struct{}{bidder.String(): {}}
filterSigners(prevSigners, txSigners)
if len(prevSigners) == 0 {
@@ -144,22 +137,8 @@ func (k Keeper) ValidateAuctionBundle(bidder sdk.AccAddress, transactions []sdk.
return nil
}
// getTxSigners returns the signers of a transaction.
func (k Keeper) getTxSigners(tx sdk.Tx) (map[string]bool, error) {
signers := make(map[string]bool, 0)
for _, msg := range tx.GetMsgs() {
for _, signer := range msg.GetSigners() {
// TODO: check for multi-sig accounts
// https://github.com/skip-mev/pob/issues/14
signers[signer.String()] = true
}
}
return signers, nil
}
// filterSigners removes any signers from the currentSigners map that are not in the txSigners map.
func filterSigners(currentSigners, txSigners map[string]bool) {
func filterSigners(currentSigners, txSigners map[string]struct{}) {
for signer := range currentSigners {
if _, ok := txSigners[signer]; !ok {
delete(currentSigners, signer)
+25 -6
View File
@@ -5,6 +5,7 @@ import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/mempool"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/keeper"
buildertypes "github.com/skip-mev/pob/x/builder/types"
@@ -181,14 +182,26 @@ func (suite *KeeperTestSuite) TestValidateAuctionMsg() {
suite.builderKeeper.SetParams(suite.ctx, params)
// Create the bundle of transactions ordered by accounts
bundle := make([]sdk.Tx, 0)
bundle := make([][]byte, 0)
for _, acc := range accounts {
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, acc, 0, 1, 100)
suite.Require().NoError(err)
bundle = append(bundle, tx)
txBz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
bundle = append(bundle, txBz)
}
err := suite.builderKeeper.ValidateAuctionMsg(suite.ctx, bidder.Address, bid, highestBid, bundle)
bidInfo := mempool.AuctionBidInfo{
Bidder: bidder.Address,
Bid: bid,
Transactions: bundle,
}
signers, err := suite.mempool.GetBundleSigners(bundle)
suite.Require().NoError(err)
err = suite.builderKeeper.ValidateBidInfo(suite.ctx, highestBid, bidInfo, signers)
if tc.pass {
suite.Require().NoError(err)
} else {
@@ -290,16 +303,22 @@ func (suite *KeeperTestSuite) TestValidateBundle() {
tc.malleate()
// Create the bundle of transactions ordered by accounts
bundle := make([]sdk.Tx, 0)
bundle := make([][]byte, 0)
for _, acc := range accounts {
// Create a random tx
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, acc, 0, 1, 1000)
suite.Require().NoError(err)
bundle = append(bundle, tx)
txBz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
suite.Require().NoError(err)
bundle = append(bundle, txBz)
}
signers, err := suite.mempool.GetBundleSigners(bundle)
suite.Require().NoError(err)
// Validate the bundle
err := suite.builderKeeper.ValidateAuctionBundle(bidder.Address, bundle)
err = suite.builderKeeper.ValidateAuctionBundle(bidder.Address, signers)
if tc.pass {
suite.Require().NoError(err)
} else {
+2 -1
View File
@@ -64,6 +64,7 @@ func (suite *KeeperTestSuite) SetupTest() {
err := suite.builderKeeper.SetParams(suite.ctx, types.DefaultParams())
suite.Require().NoError(err)
suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), suite.encCfg.TxConfig.TxEncoder(), 0)
config := mempool.NewDefaultConfig(suite.encCfg.TxConfig.TxDecoder())
suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), suite.encCfg.TxConfig.TxEncoder(), 0, config)
suite.msgServer = keeper.NewMsgServerImpl(suite.builderKeeper)
}