fix(Config): Simplifying config interface (#104)
This commit is contained in:
parent
07c76b8330
commit
d58b36bf3d
@ -32,7 +32,7 @@ type ABCITestSuite struct {
|
||||
encodingConfig testutils.EncodingConfig
|
||||
proposalHandler *abci.ProposalHandler
|
||||
voteExtensionHandler *abci.VoteExtensionHandler
|
||||
config mempool.Config
|
||||
config mempool.AuctionFactory
|
||||
txs map[string]struct{}
|
||||
|
||||
// auction bid setup
|
||||
@ -66,10 +66,10 @@ func (suite *ABCITestSuite) SetupTest() {
|
||||
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
|
||||
suite.ctx = testCtx.Ctx.WithBlockHeight(1)
|
||||
|
||||
// Mempool set up
|
||||
suite.config = mempool.NewDefaultConfig(suite.encodingConfig.TxConfig.TxDecoder())
|
||||
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))
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"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 (
|
||||
@ -19,10 +20,14 @@ type (
|
||||
// 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
|
||||
GetBundledTransactions(tx sdk.Tx) ([][]byte, error)
|
||||
WrapBundleTransaction(tx []byte) (sdk.Tx, error)
|
||||
IsAuctionTx(tx sdk.Tx) (bool, error)
|
||||
}
|
||||
|
||||
// ProposalHandler contains the functionality and handlers required to\
|
||||
@ -82,7 +87,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
|
||||
bidTxSize := int64(len(bidTxBz))
|
||||
if bidTxSize <= req.MaxTxBytes {
|
||||
bundledTransactions, err := h.mempool.GetBundledTransactions(tmpBidTx)
|
||||
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.
|
||||
@ -91,6 +96,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -205,23 +211,21 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
isAuctionTx, err := h.mempool.IsAuctionTx(tx)
|
||||
bidInfo, err := h.mempool.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
if isAuctionTx {
|
||||
// Only the first transaction can be an auction bid tx
|
||||
// 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, err := h.mempool.GetBundledTransactions(tx)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
// The order of transactions in the block proposal must follow the order of transactions in the bid.
|
||||
bundledTransactions := bidInfo.Transactions
|
||||
if len(req.Txs) < len(bundledTransactions)+1 {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
|
||||
"github.com/skip-mev/pob/mempool"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -16,8 +17,7 @@ type (
|
||||
VoteExtensionMempool interface {
|
||||
Remove(tx sdk.Tx) error
|
||||
AuctionBidSelect(ctx context.Context) sdkmempool.Iterator
|
||||
IsAuctionTx(tx sdk.Tx) (bool, error)
|
||||
GetBundledTransactions(tx sdk.Tx) ([][]byte, error)
|
||||
GetAuctionBidInfo(tx sdk.Tx) (*mempool.AuctionBidInfo, error)
|
||||
WrapBundleTransaction(tx []byte) (sdk.Tx, error)
|
||||
}
|
||||
|
||||
@ -140,12 +140,12 @@ func (h *VoteExtensionHandler) resetCache(blockHeight int64) {
|
||||
// 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
|
||||
isAuctionTx, err := h.mempool.IsAuctionTx(bidTx)
|
||||
bidInfo, err := h.mempool.GetAuctionBidInfo(bidTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isAuctionTx {
|
||||
if bidInfo == nil {
|
||||
return fmt.Errorf("vote extension is not a valid auction transaction")
|
||||
}
|
||||
|
||||
@ -159,13 +159,8 @@ func (h *VoteExtensionHandler) verifyAuctionTx(ctx sdk.Context, bidTx sdk.Tx) er
|
||||
return err
|
||||
}
|
||||
|
||||
bundledTxs, err := h.mempool.GetBundledTransactions(bidTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify all bundled transactions
|
||||
for _, tx := range bundledTxs {
|
||||
for _, tx := range bidInfo.Transactions {
|
||||
wrappedTx, err := h.mempool.WrapBundleTransaction(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -79,31 +79,6 @@ func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
"mempool contains bid that has bundled txs that are invalid",
|
||||
func() []byte {
|
||||
params.ReserveFee = sdk.NewCoin("foo", sdk.NewInt(10))
|
||||
err := suite.builderKeeper.SetParams(suite.ctx, params)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidder := suite.accounts[0]
|
||||
bid := params.ReserveFee.Sub(sdk.NewCoin("foo", sdk.NewInt(1)))
|
||||
|
||||
msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encodingConfig.TxConfig, bidder, bid, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
msgAuctionBid.Transactions = [][]byte{[]byte("invalid tx")}
|
||||
|
||||
bidTx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, bidder, 0, 10, []sdk.Msg{msgAuctionBid})
|
||||
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)
|
||||
|
||||
// this should return nothing since the top bid is not valid
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
"mempool contains bid that has an invalid timeout",
|
||||
func() []byte {
|
||||
@ -126,6 +101,10 @@ 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)
|
||||
|
||||
bidder := suite.accounts[0]
|
||||
bid := suite.auctionBidAmount.Add(suite.minBidIncrement)
|
||||
signers := []testutils.Account{bidder}
|
||||
|
||||
128
mempool/auction_bid.go
Normal file
128
mempool/auction_bid.go
Normal file
@ -0,0 +1,128 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
)
|
||||
|
||||
type (
|
||||
// AuctionBidInfo defines the information about a bid to the auction house.
|
||||
AuctionBidInfo struct {
|
||||
Bidder sdk.AccAddress
|
||||
Bid sdk.Coin
|
||||
Transactions [][]byte
|
||||
Timeout uint64
|
||||
Signers []map[string]struct{}
|
||||
}
|
||||
|
||||
// AuctionFactory defines the interface for processing auction transactions. It is
|
||||
// a wrapper around all of the functionality that each application chain must implement
|
||||
// in order for auction processing to work.
|
||||
AuctionFactory interface {
|
||||
// WrapBundleTransaction defines a function that wraps a bundle transaction into a sdk.Tx. Since
|
||||
// this is a potentially expensive operation, we allow each application chain to define how
|
||||
// they want to wrap the transaction such that it is only called when necessary (i.e. when the
|
||||
// transaction is being considered in the proposal handlers).
|
||||
WrapBundleTransaction(tx []byte) (sdk.Tx, error)
|
||||
|
||||
// GetAuctionBidInfo defines a function that returns the bid info from an auction transaction.
|
||||
GetAuctionBidInfo(tx sdk.Tx) (*AuctionBidInfo, error)
|
||||
}
|
||||
|
||||
// DefaultAuctionFactory defines a default implmentation for the auction factory interface for processing auction transactions.
|
||||
DefaultAuctionFactory struct {
|
||||
txDecoder sdk.TxDecoder
|
||||
}
|
||||
|
||||
// TxWithTimeoutHeight is used to extract timeouts from sdk.Tx transactions. In the case where,
|
||||
// timeouts are explicitly set on the sdk.Tx, we can use this interface to extract the timeout.
|
||||
TxWithTimeoutHeight interface {
|
||||
sdk.Tx
|
||||
|
||||
GetTimeoutHeight() uint64
|
||||
}
|
||||
)
|
||||
|
||||
var _ AuctionFactory = (*DefaultAuctionFactory)(nil)
|
||||
|
||||
// NewDefaultAuctionFactory returns a default auction factory interface implementation.
|
||||
func NewDefaultAuctionFactory(txDecoder sdk.TxDecoder) AuctionFactory {
|
||||
return &DefaultAuctionFactory{
|
||||
txDecoder: txDecoder,
|
||||
}
|
||||
}
|
||||
|
||||
// WrapBundleTransaction defines a default function that wraps a transaction
|
||||
// that is included in the bundle into a sdk.Tx. In the default case, the transaction
|
||||
// that is included in the bundle will be the raw bytes of an sdk.Tx so we can just
|
||||
// decode it.
|
||||
func (config *DefaultAuctionFactory) WrapBundleTransaction(tx []byte) (sdk.Tx, error) {
|
||||
return config.txDecoder(tx)
|
||||
}
|
||||
|
||||
// GetAuctionBidInfo defines a default function that returns the auction bid info from
|
||||
// an auction transaction. In the default case, the auction bid info is stored in the
|
||||
// MsgAuctionBid message.
|
||||
func (config *DefaultAuctionFactory) GetAuctionBidInfo(tx sdk.Tx) (*AuctionBidInfo, error) {
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
bidder, err := sdk.AccAddressFromBech32(msg.Bidder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid bidder address (%s): %w", msg.Bidder, err)
|
||||
}
|
||||
|
||||
timeoutTx, ok := tx.(TxWithTimeoutHeight)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot extract timeout; transaction does not implement TxWithTimeoutHeight")
|
||||
}
|
||||
|
||||
signers, err := config.getBundleSigners(msg.Transactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AuctionBidInfo{
|
||||
Bid: msg.Bid,
|
||||
Bidder: bidder,
|
||||
Transactions: msg.Transactions,
|
||||
Timeout: timeoutTx.GetTimeoutHeight(),
|
||||
Signers: signers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getBundleSigners defines a default function that returns the signers of all transactions in
|
||||
// a bundle. In the default case, each bundle transaction will be an sdk.Tx and the
|
||||
// signers are the signers of each sdk.Msg in the transaction.
|
||||
func (config *DefaultAuctionFactory) getBundleSigners(bundle [][]byte) ([]map[string]struct{}, error) {
|
||||
signers := make([]map[string]struct{}, 0)
|
||||
|
||||
for _, tx := range bundle {
|
||||
sdkTx, err := config.txDecoder(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigTx, ok := sdkTx.(signing.SigVerifiableTx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("transaction is not valid")
|
||||
}
|
||||
|
||||
txSigners := make(map[string]struct{})
|
||||
for _, signer := range sigTx.GetSigners() {
|
||||
txSigners[signer.String()] = struct{}{}
|
||||
}
|
||||
|
||||
signers = append(signers, txSigners)
|
||||
}
|
||||
|
||||
return signers, nil
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
package mempool_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
testutils "github.com/skip-mev/pob/testutils"
|
||||
)
|
||||
@ -77,9 +79,9 @@ func (suite *IntegrationTestSuite) TestIsAuctionTx() {
|
||||
suite.Run(tc.name, func() {
|
||||
tx := tc.createTx()
|
||||
|
||||
isAuctionTx, err := suite.config.IsAuctionTx(tx)
|
||||
bidInfo, err := suite.config.GetAuctionBidInfo(tx)
|
||||
|
||||
suite.Require().Equal(tc.isAuctionTx, isAuctionTx)
|
||||
suite.Require().Equal(tc.isAuctionTx, bidInfo != nil)
|
||||
if tc.expectedError {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
@ -92,47 +94,70 @@ func (suite *IntegrationTestSuite) TestIsAuctionTx() {
|
||||
func (suite *IntegrationTestSuite) TestGetTransactionSigners() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
createTx func() []byte
|
||||
expectedSigners []string
|
||||
createTx func() sdk.Tx
|
||||
expectedSigners []map[string]struct{}
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() []byte {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
"normal auction tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encCfg.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
1,
|
||||
0,
|
||||
suite.accounts[0:1],
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return bz
|
||||
return tx
|
||||
},
|
||||
[]string{suite.accounts[0].Address.String()},
|
||||
[]map[string]struct{}{
|
||||
{
|
||||
suite.accounts[0].Address.String(): {},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"normal sdk tx with several messages",
|
||||
func() []byte {
|
||||
"normal sdk tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 10, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return bz
|
||||
return tx
|
||||
},
|
||||
[]string{suite.accounts[0].Address.String()},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"multiple signers on tx",
|
||||
func() []byte {
|
||||
tx, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[0:3])
|
||||
"multiple signers on auction tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encCfg.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
1,
|
||||
0,
|
||||
suite.accounts[0:3],
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return bz
|
||||
return tx
|
||||
},
|
||||
[]string{suite.accounts[0].Address.String(), suite.accounts[1].Address.String(), suite.accounts[2].Address.String()},
|
||||
[]map[string]struct{}{
|
||||
{
|
||||
suite.accounts[0].Address.String(): {},
|
||||
},
|
||||
{
|
||||
suite.accounts[1].Address.String(): {},
|
||||
},
|
||||
{
|
||||
suite.accounts[2].Address.String(): {},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -140,103 +165,11 @@ func (suite *IntegrationTestSuite) TestGetTransactionSigners() {
|
||||
suite.Run(tc.name, func() {
|
||||
tx := tc.createTx()
|
||||
|
||||
signers, err := suite.config.GetTransactionSigners(tx)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(len(tc.expectedSigners), len(signers))
|
||||
|
||||
for _, signer := range tc.expectedSigners {
|
||||
suite.Require().Contains(signers, signer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) TestGetBundleSigners() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
createBundle func() [][]byte
|
||||
expectedSigners [][]string
|
||||
}{
|
||||
{
|
||||
"single bundle with one signer",
|
||||
func() [][]byte {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return [][]byte{bz}
|
||||
},
|
||||
[][]string{{suite.accounts[0].Address.String()}},
|
||||
},
|
||||
{
|
||||
"single bundle with multiple signers",
|
||||
func() [][]byte {
|
||||
tx, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[0:3])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return [][]byte{bz}
|
||||
},
|
||||
[][]string{{suite.accounts[0].Address.String(), suite.accounts[1].Address.String(), suite.accounts[2].Address.String()}},
|
||||
},
|
||||
{
|
||||
"multiple bundles with one signer",
|
||||
func() [][]byte {
|
||||
tx1, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz1, err := suite.encCfg.TxConfig.TxEncoder()(tx1)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[1], 0, 1, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz2, err := suite.encCfg.TxConfig.TxEncoder()(tx2)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return [][]byte{bz1, bz2}
|
||||
},
|
||||
[][]string{{suite.accounts[0].Address.String()}, {suite.accounts[1].Address.String()}},
|
||||
},
|
||||
{
|
||||
"multiple bundles with multiple signers",
|
||||
func() [][]byte {
|
||||
tx1, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[0:3])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz1, err := suite.encCfg.TxConfig.TxEncoder()(tx1)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateTxWithSigners(suite.encCfg.TxConfig, 0, 0, suite.accounts[3:6])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz2, err := suite.encCfg.TxConfig.TxEncoder()(tx2)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return [][]byte{bz1, bz2}
|
||||
},
|
||||
[][]string{{suite.accounts[0].Address.String(), suite.accounts[1].Address.String(), suite.accounts[2].Address.String()}, {suite.accounts[3].Address.String(), suite.accounts[4].Address.String(), suite.accounts[5].Address.String()}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
bundle := tc.createBundle()
|
||||
|
||||
signers, err := suite.config.GetBundleSigners(bundle)
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(len(tc.expectedSigners), len(signers))
|
||||
|
||||
for i, bundleSigners := range tc.expectedSigners {
|
||||
suite.Require().Equal(len(bundleSigners), len(signers[i]))
|
||||
|
||||
for _, signer := range bundleSigners {
|
||||
suite.Require().Contains(signers[i], signer)
|
||||
}
|
||||
bidInfo, _ := suite.config.GetAuctionBidInfo(tx)
|
||||
if tc.expectedError {
|
||||
suite.Require().Nil(bidInfo)
|
||||
} else {
|
||||
suite.Require().Equal(tc.expectedSigners, bidInfo.Signers)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -246,6 +179,7 @@ func (suite *IntegrationTestSuite) TestWrapBundleTransaction() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
createBundleTx func() (sdk.Tx, []byte)
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
"normal sdk tx",
|
||||
@ -258,6 +192,17 @@ func (suite *IntegrationTestSuite) TestWrapBundleTransaction() {
|
||||
|
||||
return tx, bz
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"random bytes with expected failure",
|
||||
func() (sdk.Tx, []byte) {
|
||||
bz := make([]byte, 100)
|
||||
rand.Read(bz)
|
||||
|
||||
return nil, bz
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -266,15 +211,19 @@ func (suite *IntegrationTestSuite) TestWrapBundleTransaction() {
|
||||
tx, bz := tc.createBundleTx()
|
||||
|
||||
wrappedTx, err := suite.config.WrapBundleTransaction(bz)
|
||||
suite.Require().NoError(err)
|
||||
if tc.expectedError {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBytes, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
txBytes, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
wrappedTxBytes, err := suite.encCfg.TxConfig.TxEncoder()(wrappedTx)
|
||||
suite.Require().NoError(err)
|
||||
wrappedTxBytes, err := suite.encCfg.TxConfig.TxEncoder()(wrappedTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().Equal(txBytes, wrappedTxBytes)
|
||||
suite.Require().Equal(txBytes, wrappedTxBytes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -285,6 +234,7 @@ func (suite *IntegrationTestSuite) TestGetBidder() {
|
||||
createTx func() sdk.Tx
|
||||
expectedBidder string
|
||||
expectedError bool
|
||||
isAuctionTx bool
|
||||
}{
|
||||
{
|
||||
"normal sdk tx",
|
||||
@ -295,7 +245,8 @@ func (suite *IntegrationTestSuite) TestGetBidder() {
|
||||
return tx
|
||||
},
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid auction tx",
|
||||
@ -311,6 +262,7 @@ func (suite *IntegrationTestSuite) TestGetBidder() {
|
||||
},
|
||||
suite.accounts[0].Address.String(),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid auction tx",
|
||||
@ -329,6 +281,7 @@ func (suite *IntegrationTestSuite) TestGetBidder() {
|
||||
},
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -336,12 +289,15 @@ func (suite *IntegrationTestSuite) TestGetBidder() {
|
||||
suite.Run(tc.name, func() {
|
||||
tx := tc.createTx()
|
||||
|
||||
bidder, err := suite.config.GetBidder(tx)
|
||||
bidInfo, err := suite.config.GetAuctionBidInfo(tx)
|
||||
if tc.expectedError {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(tc.expectedBidder, bidder.String())
|
||||
|
||||
if tc.isAuctionTx {
|
||||
suite.Require().Equal(tc.expectedBidder, bidInfo.Bidder.String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -353,6 +309,7 @@ func (suite *IntegrationTestSuite) TestGetBid() {
|
||||
createTx func() sdk.Tx
|
||||
expectedBid sdk.Coin
|
||||
expectedError bool
|
||||
isAuctionTx bool
|
||||
}{
|
||||
{
|
||||
"normal sdk tx",
|
||||
@ -363,7 +320,8 @@ func (suite *IntegrationTestSuite) TestGetBid() {
|
||||
return tx
|
||||
},
|
||||
sdk.Coin{},
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid auction tx",
|
||||
@ -379,6 +337,7 @@ func (suite *IntegrationTestSuite) TestGetBid() {
|
||||
},
|
||||
sdk.NewInt64Coin("foo", 100),
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid auction tx",
|
||||
@ -397,6 +356,7 @@ func (suite *IntegrationTestSuite) TestGetBid() {
|
||||
},
|
||||
sdk.Coin{},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -404,12 +364,15 @@ func (suite *IntegrationTestSuite) TestGetBid() {
|
||||
suite.Run(tc.name, func() {
|
||||
tx := tc.createTx()
|
||||
|
||||
bid, err := suite.config.GetBid(tx)
|
||||
bidInfo, err := suite.config.GetAuctionBidInfo(tx)
|
||||
if tc.expectedError {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(tc.expectedBid, bid)
|
||||
|
||||
if tc.isAuctionTx {
|
||||
suite.Require().Equal(tc.expectedBid, bidInfo.Bid)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -420,6 +383,7 @@ func (suite *IntegrationTestSuite) TestGetBundledTransactions() {
|
||||
name string
|
||||
createTx func() (sdk.Tx, [][]byte)
|
||||
expectedError bool
|
||||
isAuctionTx bool
|
||||
}{
|
||||
{
|
||||
"normal sdk tx",
|
||||
@ -429,7 +393,8 @@ func (suite *IntegrationTestSuite) TestGetBundledTransactions() {
|
||||
|
||||
return tx, nil
|
||||
},
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid auction tx",
|
||||
@ -444,6 +409,7 @@ func (suite *IntegrationTestSuite) TestGetBundledTransactions() {
|
||||
return tx, msgAuctionBid.Transactions
|
||||
},
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid auction tx",
|
||||
@ -461,6 +427,7 @@ func (suite *IntegrationTestSuite) TestGetBundledTransactions() {
|
||||
return tx, nil
|
||||
},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -468,12 +435,15 @@ func (suite *IntegrationTestSuite) TestGetBundledTransactions() {
|
||||
suite.Run(tc.name, func() {
|
||||
tx, expectedBundledTxs := tc.createTx()
|
||||
|
||||
bundledTxs, err := suite.config.GetBundledTransactions(tx)
|
||||
bidInfo, err := suite.config.GetAuctionBidInfo(tx)
|
||||
if tc.expectedError {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(expectedBundledTxs, bundledTxs)
|
||||
|
||||
if tc.isAuctionTx {
|
||||
suite.Require().Equal(expectedBundledTxs, bidInfo.Transactions)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -484,6 +454,7 @@ func (suite *IntegrationTestSuite) TestGetTimeout() {
|
||||
name string
|
||||
createTx func() sdk.Tx
|
||||
expectedError bool
|
||||
isAuctionTx bool
|
||||
expectedTimeout uint64
|
||||
}{
|
||||
{
|
||||
@ -495,6 +466,7 @@ func (suite *IntegrationTestSuite) TestGetTimeout() {
|
||||
return tx
|
||||
},
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
},
|
||||
{
|
||||
@ -510,6 +482,7 @@ func (suite *IntegrationTestSuite) TestGetTimeout() {
|
||||
return tx
|
||||
},
|
||||
false,
|
||||
true,
|
||||
10,
|
||||
},
|
||||
{
|
||||
@ -527,6 +500,7 @@ func (suite *IntegrationTestSuite) TestGetTimeout() {
|
||||
suite.Require().NoError(err)
|
||||
return tx
|
||||
},
|
||||
true,
|
||||
false,
|
||||
10,
|
||||
},
|
||||
@ -536,12 +510,15 @@ func (suite *IntegrationTestSuite) TestGetTimeout() {
|
||||
suite.Run(tc.name, func() {
|
||||
tx := tc.createTx()
|
||||
|
||||
timeout, err := suite.config.GetTimeout(tx)
|
||||
bidInfo, err := suite.config.GetAuctionBidInfo(tx)
|
||||
if tc.expectedError {
|
||||
suite.Require().Error(err)
|
||||
} else {
|
||||
suite.Require().NoError(err)
|
||||
suite.Require().Equal(tc.expectedTimeout, timeout)
|
||||
|
||||
if tc.isAuctionTx {
|
||||
suite.Require().Equal(tc.expectedTimeout, bidInfo.Timeout)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1,242 +0,0 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
)
|
||||
|
||||
type (
|
||||
// AuctionBidInfo defines the information about a bid to the auction house.
|
||||
AuctionBidInfo struct {
|
||||
Bidder sdk.AccAddress
|
||||
Bid sdk.Coin
|
||||
Transactions [][]byte
|
||||
Timeout uint64
|
||||
}
|
||||
|
||||
// Config defines the configuration for processing auction transactions. It is
|
||||
// a wrapper around all of the functionality that each application chain must implement
|
||||
// in order for auction processing to work.
|
||||
Config interface {
|
||||
// IsAuctionTx defines a function that returns true iff a transaction is an
|
||||
// auction bid transaction.
|
||||
IsAuctionTx(tx sdk.Tx) (bool, error)
|
||||
|
||||
// GetTransactionSigners defines a function that returns the signers of a
|
||||
// bundle transaction i.e. transaction that was included in the auction transaction's bundle.
|
||||
GetTransactionSigners(tx []byte) (map[string]struct{}, error)
|
||||
|
||||
// GetBundleSigners defines a function that returns the signers of every transaction in a bundle.
|
||||
GetBundleSigners(tx [][]byte) ([]map[string]struct{}, error)
|
||||
|
||||
// WrapBundleTransaction defines a function that wraps a bundle transaction into a sdk.Tx.
|
||||
WrapBundleTransaction(tx []byte) (sdk.Tx, error)
|
||||
|
||||
// GetBidder defines a function that returns the bidder of an auction transaction transaction.
|
||||
GetBidder(tx sdk.Tx) (sdk.AccAddress, error)
|
||||
|
||||
// GetBid defines a function that returns the bid of an auction transaction.
|
||||
GetBid(tx sdk.Tx) (sdk.Coin, error)
|
||||
|
||||
// GetBundledTransactions defines a function that returns the bundled transactions
|
||||
// that the user wants to execute at the top of the block given an auction transaction.
|
||||
GetBundledTransactions(tx sdk.Tx) ([][]byte, error)
|
||||
|
||||
// GetTimeout defines a function that returns the timeout of an auction transaction.
|
||||
GetTimeout(tx sdk.Tx) (uint64, error)
|
||||
|
||||
// GetAuctionBidInfo defines a function that returns the bid info from an auction transaction.
|
||||
GetAuctionBidInfo(tx sdk.Tx) (AuctionBidInfo, error)
|
||||
}
|
||||
|
||||
// DefaultConfig defines a default configuration for processing auction transactions.
|
||||
DefaultConfig struct {
|
||||
txDecoder sdk.TxDecoder
|
||||
}
|
||||
|
||||
// TxWithTimeoutHeight is used to extract timeouts from sdk.Tx transactions. In the case where,
|
||||
// timeouts are explicitly set on the sdk.Tx, we can use this interface to extract the timeout.
|
||||
TxWithTimeoutHeight interface {
|
||||
sdk.Tx
|
||||
|
||||
GetTimeoutHeight() uint64
|
||||
}
|
||||
)
|
||||
|
||||
var _ Config = (*DefaultConfig)(nil)
|
||||
|
||||
// NewDefaultConfig returns a default transaction configuration.
|
||||
func NewDefaultConfig(txDecoder sdk.TxDecoder) Config {
|
||||
return &DefaultConfig{
|
||||
txDecoder: txDecoder,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultIsAuctionTx defines a default function that returns true iff a transaction
|
||||
// is an auction bid transaction. In the default case, the transaction must contain a single
|
||||
// MsgAuctionBid message.
|
||||
func (config *DefaultConfig) IsAuctionTx(tx sdk.Tx) (bool, error) {
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return msg != nil, nil
|
||||
}
|
||||
|
||||
// GetTransactionSigners defines a default function that returns the signers
|
||||
// of a transaction. In the default case, each bundle transaction will be an sdk.Tx and the
|
||||
// signers are the signers of each sdk.Msg in the transaction.
|
||||
func (config *DefaultConfig) GetTransactionSigners(tx []byte) (map[string]struct{}, error) {
|
||||
sdkTx, err := config.txDecoder(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigTx, ok := sdkTx.(signing.SigVerifiableTx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("transaction is not valid")
|
||||
}
|
||||
|
||||
signers := make(map[string]struct{})
|
||||
for _, signer := range sigTx.GetSigners() {
|
||||
signers[signer.String()] = struct{}{}
|
||||
}
|
||||
|
||||
return signers, nil
|
||||
}
|
||||
|
||||
// GetBundleSigners defines a default function that returns the signers of every transaction
|
||||
// in a bundle. In the default case, each bundle transaction will be an sdk.Tx and the
|
||||
// signers are the signers of each sdk.Msg in the transaction.
|
||||
func (config *DefaultConfig) GetBundleSigners(txs [][]byte) ([]map[string]struct{}, error) {
|
||||
signers := make([]map[string]struct{}, len(txs))
|
||||
|
||||
for index, tx := range txs {
|
||||
txSigners, err := config.GetTransactionSigners(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signers[index] = txSigners
|
||||
}
|
||||
|
||||
return signers, nil
|
||||
}
|
||||
|
||||
// WrapBundleTransaction defines a default function that wraps a transaction
|
||||
// that is included in the bundle into a sdk.Tx. In the default case, the transaction
|
||||
// that is included in the bundle will be the raw bytes of an sdk.Tx so we can just
|
||||
// decode it.
|
||||
func (config *DefaultConfig) WrapBundleTransaction(tx []byte) (sdk.Tx, error) {
|
||||
return config.txDecoder(tx)
|
||||
}
|
||||
|
||||
// GetBidder defines a default function that returns the bidder of an auction transaction.
|
||||
// In the default case, the bidder is the address defined in MsgAuctionBid.
|
||||
func (config *DefaultConfig) GetBidder(tx sdk.Tx) (sdk.AccAddress, error) {
|
||||
isAuctionTx, err := config.IsAuctionTx(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isAuctionTx {
|
||||
return nil, fmt.Errorf("transaction is not an auction transaction")
|
||||
}
|
||||
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bidder, err := sdk.AccAddressFromBech32(msg.Bidder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid bidder address (%s): %w", msg.Bidder, err)
|
||||
}
|
||||
|
||||
return bidder, nil
|
||||
}
|
||||
|
||||
// GetBid defines a default function that returns the bid of an auction transaction.
|
||||
// In the default case, the bid is the amount defined in MsgAuctionBid.
|
||||
func (config *DefaultConfig) GetBid(tx sdk.Tx) (sdk.Coin, error) {
|
||||
isAuctionTx, err := config.IsAuctionTx(tx)
|
||||
if err != nil {
|
||||
return sdk.Coin{}, err
|
||||
}
|
||||
|
||||
if !isAuctionTx {
|
||||
return sdk.Coin{}, fmt.Errorf("transaction is not an auction transaction")
|
||||
}
|
||||
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
if err != nil {
|
||||
return sdk.Coin{}, err
|
||||
}
|
||||
|
||||
return msg.Bid, nil
|
||||
}
|
||||
|
||||
// GetBundledTransactions defines a default function that returns the bundled
|
||||
// transactions that the user wants to execute at the top of the block. In the default case,
|
||||
// the bundled transactions will be the raw bytes of sdk.Tx's that are included in the
|
||||
// MsgAuctionBid.
|
||||
func (config *DefaultConfig) GetBundledTransactions(tx sdk.Tx) ([][]byte, error) {
|
||||
isAuctionTx, err := config.IsAuctionTx(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isAuctionTx {
|
||||
return nil, fmt.Errorf("transaction is not an auction transaction")
|
||||
}
|
||||
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msg.Transactions, nil
|
||||
}
|
||||
|
||||
// GetTimeout defines a default function that returns the timeout of an auction transaction.
|
||||
func (config *DefaultConfig) GetTimeout(tx sdk.Tx) (uint64, error) {
|
||||
timeoutTx, ok := tx.(TxWithTimeoutHeight)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("transaction does not implement TxWithTimeoutHeight")
|
||||
}
|
||||
|
||||
return timeoutTx.GetTimeoutHeight(), nil
|
||||
}
|
||||
|
||||
// GetAuctionBidInfo returns the auction bid info from an auction transaction.
|
||||
func (config *DefaultConfig) GetAuctionBidInfo(tx sdk.Tx) (AuctionBidInfo, error) {
|
||||
bid, err := config.GetBid(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
bidder, err := config.GetBidder(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
bundle, err := config.GetBundledTransactions(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
timeout, err := config.GetTimeout(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
return AuctionBidInfo{
|
||||
Bid: bid,
|
||||
Bidder: bidder,
|
||||
Transactions: bundle,
|
||||
Timeout: timeout,
|
||||
}, nil
|
||||
}
|
||||
@ -38,21 +38,21 @@ type AuctionMempool struct {
|
||||
// to quickly check if a transaction is already in the mempool.
|
||||
txIndex map[string]struct{}
|
||||
|
||||
// Config defines the transaction configuration for processing auction transactions.
|
||||
Config
|
||||
// AuctionFactory implements the functionality required to process auction transactions.
|
||||
AuctionFactory
|
||||
}
|
||||
|
||||
// AuctionTxPriority returns a TxPriority over auction bid transactions only. It
|
||||
// is to be used in the auction index only.
|
||||
func AuctionTxPriority(config Config) TxPriority[string] {
|
||||
func AuctionTxPriority(config AuctionFactory) TxPriority[string] {
|
||||
return TxPriority[string]{
|
||||
GetTxPriority: func(goCtx context.Context, tx sdk.Tx) string {
|
||||
bid, err := config.GetBid(tx)
|
||||
bidInfo, err := config.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return bid.String()
|
||||
return bidInfo.Bid.String()
|
||||
},
|
||||
Compare: func(a, b string) int {
|
||||
aCoins, _ := sdk.ParseCoinsNormalized(a)
|
||||
@ -85,7 +85,7 @@ func AuctionTxPriority(config Config) TxPriority[string] {
|
||||
}
|
||||
}
|
||||
|
||||
func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx int, config Config) *AuctionMempool {
|
||||
func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx int, config AuctionFactory) *AuctionMempool {
|
||||
return &AuctionMempool{
|
||||
globalIndex: NewPriorityMempool(
|
||||
PriorityNonceMempoolConfig[int64]{
|
||||
@ -99,27 +99,26 @@ func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx i
|
||||
MaxTx: maxTx,
|
||||
},
|
||||
),
|
||||
txDecoder: txDecoder,
|
||||
txEncoder: txEncoder,
|
||||
txIndex: make(map[string]struct{}),
|
||||
Config: config,
|
||||
txDecoder: txDecoder,
|
||||
txEncoder: txEncoder,
|
||||
txIndex: make(map[string]struct{}),
|
||||
AuctionFactory: config,
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts a transaction into the mempool based on the transaction type (normal or auction).
|
||||
func (am *AuctionMempool) Insert(ctx context.Context, tx sdk.Tx) error {
|
||||
isAuctionTx, err := am.IsAuctionTx(tx)
|
||||
bidInfo, err := am.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert the transactions into the appropriate index.
|
||||
switch {
|
||||
case !isAuctionTx:
|
||||
if bidInfo == nil {
|
||||
if err := am.globalIndex.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to insert tx into global index: %w", err)
|
||||
}
|
||||
case isAuctionTx:
|
||||
} else {
|
||||
if err := am.auctionIndex.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to insert tx into auction index: %w", err)
|
||||
}
|
||||
@ -137,16 +136,15 @@ func (am *AuctionMempool) Insert(ctx context.Context, tx sdk.Tx) error {
|
||||
|
||||
// Remove removes a transaction from the mempool based on the transaction type (normal or auction).
|
||||
func (am *AuctionMempool) Remove(tx sdk.Tx) error {
|
||||
isAuctionTx, err := am.IsAuctionTx(tx)
|
||||
bidInfo, err := am.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the transactions from the appropriate index.
|
||||
switch {
|
||||
case !isAuctionTx:
|
||||
if bidInfo == nil {
|
||||
am.removeTx(am.globalIndex, tx)
|
||||
case isAuctionTx:
|
||||
} else {
|
||||
am.removeTx(am.auctionIndex, tx)
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ type IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
encCfg testutils.EncodingConfig
|
||||
config mempool.Config
|
||||
config mempool.AuctionFactory
|
||||
mempool *mempool.AuctionMempool
|
||||
ctx sdk.Context
|
||||
random *rand.Rand
|
||||
@ -34,7 +34,7 @@ func TestMempoolTestSuite(t *testing.T) {
|
||||
func (suite *IntegrationTestSuite) SetupTest() {
|
||||
// Mempool setup
|
||||
suite.encCfg = testutils.CreateTestEncodingConfig()
|
||||
suite.config = mempool.NewDefaultConfig(suite.encCfg.TxConfig.TxDecoder())
|
||||
suite.config = mempool.NewDefaultAuctionFactory(suite.encCfg.TxConfig.TxDecoder())
|
||||
suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), suite.encCfg.TxConfig.TxEncoder(), 0, suite.config)
|
||||
suite.ctx = sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger())
|
||||
|
||||
|
||||
@ -16,11 +16,8 @@ var _ sdk.AnteDecorator = BuilderDecorator{}
|
||||
type (
|
||||
Mempool interface {
|
||||
Contains(tx sdk.Tx) (bool, error)
|
||||
IsAuctionTx(tx sdk.Tx) (bool, error)
|
||||
GetAuctionBidInfo(tx sdk.Tx) (mempool.AuctionBidInfo, error)
|
||||
GetBundleSigners(txs [][]byte) ([]map[string]struct{}, error)
|
||||
GetAuctionBidInfo(tx sdk.Tx) (*mempool.AuctionBidInfo, error)
|
||||
GetTopAuctionTx(ctx context.Context) sdk.Tx
|
||||
GetTimeout(tx sdk.Tx) (uint64, error)
|
||||
}
|
||||
|
||||
BuilderDecorator struct {
|
||||
@ -55,108 +52,51 @@ func (ad BuilderDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
|
||||
}
|
||||
}
|
||||
|
||||
isAuctionTx, err := ad.mempool.IsAuctionTx(tx)
|
||||
bidInfo, err := ad.mempool.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// Validate the auction bid if one exists.
|
||||
if isAuctionTx {
|
||||
if bidInfo != nil {
|
||||
// Auction transactions must have a timeout set to a valid block height.
|
||||
if err := ad.HasValidTimeout(ctx, tx); err != nil {
|
||||
return ctx, err
|
||||
if int64(bidInfo.Timeout) < ctx.BlockHeight() {
|
||||
return ctx, fmt.Errorf("timeout height cannot be less than the current block height")
|
||||
}
|
||||
|
||||
bidInfo, err := ad.mempool.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// If the current transaction is the highest bidding transaction, then the highest bid is empty.
|
||||
topBid := sdk.Coin{}
|
||||
|
||||
// We only need to verify the auction bid relative to the local validator's mempool if the mode
|
||||
// is checkTx or recheckTx. Otherwise, the ABCI handlers (VerifyVoteExtension, ExtendVoteExtension, etc.)
|
||||
// will always compare the auction bid to the highest bidding transaction in the mempool leading to
|
||||
// poor liveness guarantees.
|
||||
topBid := sdk.Coin{}
|
||||
if ctx.IsCheckTx() || ctx.IsReCheckTx() {
|
||||
isTopBidTx, err := ad.IsTopBidTx(ctx, tx)
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "failed to check if current transaction is highest bidding transaction")
|
||||
}
|
||||
|
||||
if !isTopBidTx {
|
||||
// Set the top bid to the highest bidding transaction.
|
||||
topBid, err = ad.GetTopAuctionBid(ctx)
|
||||
if topBidTx := ad.mempool.GetTopAuctionTx(ctx); topBidTx != nil {
|
||||
topBidBz, err := ad.txEncoder(topBidTx)
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "failed to get highest auction bid")
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
currentTxBz, err := ad.txEncoder(tx)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// Compare the bytes to see if the current transaction is the highest bidding transaction.
|
||||
if !bytes.Equal(topBidBz, currentTxBz) {
|
||||
topBidInfo, err := ad.mempool.GetAuctionBidInfo(topBidTx)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
topBid = topBidInfo.Bid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if err := ad.builderKeeper.ValidateBidInfo(ctx, topBid, bidInfo); err != nil {
|
||||
return ctx, errors.Wrap(err, "failed to validate auction bid")
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// GetTopAuctionBid returns the highest auction bid if one exists.
|
||||
func (ad BuilderDecorator) GetTopAuctionBid(ctx sdk.Context) (sdk.Coin, error) {
|
||||
auctionTx := ad.mempool.GetTopAuctionTx(ctx)
|
||||
if auctionTx == nil {
|
||||
return sdk.Coin{}, nil
|
||||
}
|
||||
|
||||
auctionBidInfo, err := ad.mempool.GetAuctionBidInfo(auctionTx)
|
||||
if err != nil {
|
||||
return sdk.Coin{}, err
|
||||
}
|
||||
|
||||
return auctionBidInfo.Bid, nil
|
||||
}
|
||||
|
||||
// IsTopBidTx returns true if the transaction inputted is the highest bidding auction transaction in the mempool.
|
||||
func (ad BuilderDecorator) IsTopBidTx(ctx sdk.Context, tx sdk.Tx) (bool, error) {
|
||||
auctionTx := ad.mempool.GetTopAuctionTx(ctx)
|
||||
if auctionTx == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
topBidBz, err := ad.txEncoder(auctionTx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
currentTxBz, err := ad.txEncoder(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
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 {
|
||||
timeout, err := ad.mempool.GetTimeout(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -46,8 +46,7 @@ func (suite *AnteTestSuite) SetupTest() {
|
||||
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
|
||||
suite.ctx = suite.ctx.WithIsCheckTx(true)
|
||||
suite.ctx = testCtx.Ctx.WithIsCheckTx(true)
|
||||
|
||||
// Keepers set up
|
||||
ctrl := gomock.NewController(suite.T())
|
||||
@ -234,6 +233,8 @@ func (suite *AnteTestSuite) TestAnteHandler() {
|
||||
suite.SetupTest()
|
||||
tc.malleate()
|
||||
|
||||
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||
|
||||
// Set the auction params
|
||||
err := suite.builderKeeper.SetParams(suite.ctx, buildertypes.Params{
|
||||
MaxBundleSize: maxBundleSize,
|
||||
@ -245,7 +246,7 @@ func (suite *AnteTestSuite) TestAnteHandler() {
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Insert the top bid into the mempool
|
||||
config := mempool.NewDefaultConfig(suite.encodingConfig.TxConfig.TxDecoder())
|
||||
config := mempool.NewDefaultAuctionFactory(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{})
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// 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 {
|
||||
func (k Keeper) ValidateBidInfo(ctx sdk.Context, highestBid sdk.Coin, bidInfo *mempool.AuctionBidInfo) error {
|
||||
// Validate the bundle size.
|
||||
maxBundleSize, err := k.GetMaxBundleSize(ctx)
|
||||
if err != nil {
|
||||
@ -31,7 +31,7 @@ func (k Keeper) ValidateBidInfo(ctx sdk.Context, highestBid sdk.Coin, bidInfo me
|
||||
}
|
||||
|
||||
if protectionEnabled {
|
||||
if err := k.ValidateAuctionBundle(bidInfo.Bidder, signers); err != nil {
|
||||
if err := k.ValidateAuctionBundle(bidInfo.Bidder, bidInfo.Signers); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
)
|
||||
|
||||
func (suite *KeeperTestSuite) TestValidateAuctionMsg() {
|
||||
func (suite *KeeperTestSuite) TestValidateBidInfo() {
|
||||
var (
|
||||
// Tx building variables
|
||||
accounts = []testutils.Account{} // tracks the order of signers in the bundle
|
||||
@ -192,16 +192,23 @@ func (suite *KeeperTestSuite) TestValidateAuctionMsg() {
|
||||
bundle = append(bundle, txBz)
|
||||
}
|
||||
|
||||
bidInfo := mempool.AuctionBidInfo{
|
||||
signers := make([]map[string]struct{}, len(accounts))
|
||||
for index, acc := range accounts {
|
||||
txSigners := map[string]struct{}{
|
||||
acc.Address.String(): {},
|
||||
}
|
||||
|
||||
signers[index] = txSigners
|
||||
}
|
||||
|
||||
bidInfo := &mempool.AuctionBidInfo{
|
||||
Bidder: bidder.Address,
|
||||
Bid: bid,
|
||||
Transactions: bundle,
|
||||
Signers: signers,
|
||||
}
|
||||
|
||||
signers, err := suite.mempool.GetBundleSigners(bundle)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = suite.builderKeeper.ValidateBidInfo(suite.ctx, highestBid, bidInfo, signers)
|
||||
err := suite.builderKeeper.ValidateBidInfo(suite.ctx, highestBid, bidInfo)
|
||||
if tc.pass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
@ -302,23 +309,17 @@ func (suite *KeeperTestSuite) TestValidateBundle() {
|
||||
// Malleate the test case
|
||||
tc.malleate()
|
||||
|
||||
// Create the bundle of transactions ordered by accounts
|
||||
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)
|
||||
signers := make([]map[string]struct{}, len(accounts))
|
||||
for index, acc := range accounts {
|
||||
txSigners := map[string]struct{}{
|
||||
acc.Address.String(): {},
|
||||
}
|
||||
|
||||
txBz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
bundle = append(bundle, txBz)
|
||||
signers[index] = txSigners
|
||||
}
|
||||
|
||||
signers, err := suite.mempool.GetBundleSigners(bundle)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Validate the bundle
|
||||
err = suite.builderKeeper.ValidateAuctionBundle(bidder.Address, signers)
|
||||
err := suite.builderKeeper.ValidateAuctionBundle(bidder.Address, signers)
|
||||
if tc.pass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
|
||||
@ -64,7 +64,7 @@ func (suite *KeeperTestSuite) SetupTest() {
|
||||
err := suite.builderKeeper.SetParams(suite.ctx, types.DefaultParams())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
config := mempool.NewDefaultConfig(suite.encCfg.TxConfig.TxDecoder())
|
||||
config := mempool.NewDefaultAuctionFactory(suite.encCfg.TxConfig.TxDecoder())
|
||||
suite.mempool = mempool.NewAuctionMempool(suite.encCfg.TxConfig.TxDecoder(), suite.encCfg.TxConfig.TxEncoder(), 0, config)
|
||||
suite.msgServer = keeper.NewMsgServerImpl(suite.builderKeeper)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user