[ENG-733]: Supporting generic auction transaction configurations (#66)
This commit is contained in:
+36
-27
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user