[ENG-733]: Supporting generic auction transaction configurations (#66)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// AuctionBidInfo defines the information about a bid to the auction house.
|
||||
AuctionBidInfo struct {
|
||||
Bidder sdk.AccAddress
|
||||
Bid sdk.Coin
|
||||
Transactions [][]byte
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// DefaultConfig defines a default configuration for processing auction transactions.
|
||||
DefaultConfig struct {
|
||||
txDecoder sdk.TxDecoder
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
signers := make(map[string]struct{})
|
||||
for _, msg := range sdkTx.GetMsgs() {
|
||||
for _, signer := range msg.GetSigners() {
|
||||
signers[signer.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msg.Transactions, nil
|
||||
}
|
||||
+37
-28
@@ -21,10 +21,10 @@ type AuctionMempool struct {
|
||||
// globalIndex defines the index of all transactions in the mempool. It uses
|
||||
// the SDK's builtin PriorityNonceMempool. Once a bid is selected for top-of-block,
|
||||
// all subsequent transactions in the mempool will be selected from this index.
|
||||
globalIndex *PriorityNonceMempool[int64]
|
||||
globalIndex sdkmempool.Mempool
|
||||
|
||||
// auctionIndex defines an index of auction bids.
|
||||
auctionIndex *PriorityNonceMempool[string]
|
||||
auctionIndex sdkmempool.Mempool
|
||||
|
||||
// txDecoder defines the sdk.Tx decoder that allows us to decode transactions
|
||||
// and construct sdk.Txs from the bundled transactions.
|
||||
@@ -37,6 +37,9 @@ type AuctionMempool struct {
|
||||
// txIndex is a map of all transactions in the mempool. It is used
|
||||
// to quickly check if a transaction is already in the mempool.
|
||||
txIndex map[string]struct{}
|
||||
|
||||
// config defines the transaction configuration for processing auction transactions.
|
||||
config Config
|
||||
}
|
||||
|
||||
// AuctionTxPriority returns a TxPriority over auction bid transactions only. It
|
||||
@@ -82,7 +85,7 @@ func AuctionTxPriority() TxPriority[string] {
|
||||
}
|
||||
}
|
||||
|
||||
func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx int) *AuctionMempool {
|
||||
func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx int, config Config) *AuctionMempool {
|
||||
return &AuctionMempool{
|
||||
globalIndex: NewPriorityMempool(
|
||||
PriorityNonceMempoolConfig[int64]{
|
||||
@@ -99,6 +102,7 @@ func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx i
|
||||
txDecoder: txDecoder,
|
||||
txEncoder: txEncoder,
|
||||
txIndex: make(map[string]struct{}),
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,18 +110,18 @@ func NewAuctionMempool(txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder, maxTx i
|
||||
// auction tx (tx that contains a single MsgAuctionBid), it will also insert the
|
||||
// transaction into the auction index.
|
||||
func (am *AuctionMempool) Insert(ctx context.Context, tx sdk.Tx) error {
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
isAuctionTx, err := am.IsAuctionTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert the transactions into the appropriate index.
|
||||
switch {
|
||||
case msg == nil:
|
||||
case !isAuctionTx:
|
||||
if err := am.globalIndex.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to insert tx into global index: %w", err)
|
||||
}
|
||||
case msg != nil:
|
||||
case isAuctionTx:
|
||||
if err := am.auctionIndex.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("failed to insert tx into auction index: %w", err)
|
||||
}
|
||||
@@ -137,26 +141,31 @@ func (am *AuctionMempool) Insert(ctx context.Context, tx sdk.Tx) error {
|
||||
// auction tx (tx that contains a single MsgAuctionBid), it will also remove all
|
||||
// referenced transactions from the global mempool.
|
||||
func (am *AuctionMempool) Remove(tx sdk.Tx) error {
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
isAuctionTx, err := am.IsAuctionTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the transactions from the appropriate index.
|
||||
switch {
|
||||
case msg == nil:
|
||||
case !isAuctionTx:
|
||||
am.removeTx(am.globalIndex, tx)
|
||||
case msg != nil:
|
||||
case isAuctionTx:
|
||||
am.removeTx(am.auctionIndex, tx)
|
||||
|
||||
// Remove all referenced transactions from the global mempool.
|
||||
for _, refRawTx := range msg.GetTransactions() {
|
||||
refTx, err := am.txDecoder(refRawTx)
|
||||
bundleTxs, err := am.GetBundledTransactions(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, refTx := range bundleTxs {
|
||||
wrappedRefTx, err := am.WrapBundleTransaction(refTx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode referenced tx: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
am.removeTx(am.globalIndex, refTx)
|
||||
am.removeTx(am.globalIndex, wrappedRefTx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,12 +178,12 @@ func (am *AuctionMempool) Remove(tx sdk.Tx) error {
|
||||
// API is used to ensure that searchers are unable to remove valid transactions
|
||||
// from the global mempool.
|
||||
func (am *AuctionMempool) RemoveWithoutRefTx(tx sdk.Tx) error {
|
||||
msg, err := GetMsgAuctionBidFromTx(tx)
|
||||
isAuctionTx, err := am.IsAuctionTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg != nil {
|
||||
if isAuctionTx {
|
||||
am.removeTx(am.auctionIndex, tx)
|
||||
}
|
||||
|
||||
@@ -219,19 +228,6 @@ func (am *AuctionMempool) Contains(tx sdk.Tx) (bool, error) {
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// getTxHashStr returns the transaction hash string for a given transaction.
|
||||
func (am *AuctionMempool) getTxHashStr(tx sdk.Tx) (string, error) {
|
||||
txBz, err := am.txEncoder(tx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode transaction: %w", err)
|
||||
}
|
||||
|
||||
txHash := sha256.Sum256(txBz)
|
||||
txHashStr := hex.EncodeToString(txHash[:])
|
||||
|
||||
return txHashStr, nil
|
||||
}
|
||||
|
||||
func (am *AuctionMempool) removeTx(mp sdkmempool.Mempool, tx sdk.Tx) {
|
||||
err := mp.Remove(tx)
|
||||
if err != nil && !errors.Is(err, sdkmempool.ErrTxNotFound) {
|
||||
@@ -245,3 +241,16 @@ func (am *AuctionMempool) removeTx(mp sdkmempool.Mempool, tx sdk.Tx) {
|
||||
|
||||
delete(am.txIndex, txHashStr)
|
||||
}
|
||||
|
||||
// getTxHashStr returns the transaction hash string for a given transaction.
|
||||
func (am *AuctionMempool) getTxHashStr(tx sdk.Tx) (string, error) {
|
||||
txBz, err := am.txEncoder(tx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode transaction: %w", err)
|
||||
}
|
||||
|
||||
txHash := sha256.Sum256(txBz)
|
||||
txHashStr := hex.EncodeToString(txHash[:])
|
||||
|
||||
return txHashStr, nil
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ func TestMempoolTestSuite(t *testing.T) {
|
||||
func (suite *IntegrationTestSuite) SetupTest() {
|
||||
// Mempool setup
|
||||
suite.encCfg = testutils.CreateTestEncodingConfig()
|
||||
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.ctx = sdk.NewContext(nil, cmtproto.Header{}, false, log.NewNopLogger())
|
||||
|
||||
// Init accounts
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// IsAuctionTx returns true if the transaction is a transaction that is attempting to
|
||||
// bid to the auction.
|
||||
func (am *AuctionMempool) IsAuctionTx(tx sdk.Tx) (bool, error) {
|
||||
return am.config.IsAuctionTx(tx)
|
||||
}
|
||||
|
||||
// GetTransactionSigners returns the signers of the bundle transaction.
|
||||
func (am *AuctionMempool) GetTransactionSigners(tx []byte) (map[string]struct{}, error) {
|
||||
return am.config.GetTransactionSigners(tx)
|
||||
}
|
||||
|
||||
// WrapBundleTransaction wraps a bundle transaction into sdk.Tx transaction.
|
||||
func (am *AuctionMempool) WrapBundleTransaction(tx []byte) (sdk.Tx, error) {
|
||||
return am.config.WrapBundleTransaction(tx)
|
||||
}
|
||||
|
||||
// GetAuctionBidInfo returns the bid info from an auction transaction.
|
||||
func (am *AuctionMempool) GetAuctionBidInfo(tx sdk.Tx) (AuctionBidInfo, error) {
|
||||
bidder, err := am.GetBidder(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
bid, err := am.GetBid(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
transactions, err := am.GetBundledTransactions(tx)
|
||||
if err != nil {
|
||||
return AuctionBidInfo{}, err
|
||||
}
|
||||
|
||||
return AuctionBidInfo{
|
||||
Bidder: bidder,
|
||||
Bid: bid,
|
||||
Transactions: transactions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBidder returns the bidder from an auction transaction.
|
||||
func (am *AuctionMempool) GetBidder(tx sdk.Tx) (sdk.AccAddress, error) {
|
||||
if isAuctionTx, err := am.IsAuctionTx(tx); err != nil || !isAuctionTx {
|
||||
return nil, fmt.Errorf("transaction is not an auction transaction")
|
||||
}
|
||||
|
||||
return am.config.GetBidder(tx)
|
||||
}
|
||||
|
||||
// GetBid returns the bid from an auction transaction.
|
||||
func (am *AuctionMempool) GetBid(tx sdk.Tx) (sdk.Coin, error) {
|
||||
if isAuctionTx, err := am.IsAuctionTx(tx); err != nil || !isAuctionTx {
|
||||
return sdk.Coin{}, fmt.Errorf("transaction is not an auction transaction")
|
||||
}
|
||||
|
||||
return am.config.GetBid(tx)
|
||||
}
|
||||
|
||||
// GetBundledTransactions returns the transactions that are bundled in an auction transaction.
|
||||
func (am *AuctionMempool) GetBundledTransactions(tx sdk.Tx) ([][]byte, error) {
|
||||
if isAuctionTx, err := am.IsAuctionTx(tx); err != nil || !isAuctionTx {
|
||||
return nil, fmt.Errorf("transaction is not an auction transaction")
|
||||
}
|
||||
|
||||
return am.config.GetBundledTransactions(tx)
|
||||
}
|
||||
|
||||
// GetBundleSigners returns all of the signers for each transaction in the bundle.
|
||||
func (am *AuctionMempool) GetBundleSigners(txs [][]byte) ([]map[string]struct{}, error) {
|
||||
signers := make([]map[string]struct{}, len(txs))
|
||||
|
||||
for index, tx := range txs {
|
||||
txSigners, err := am.GetTransactionSigners(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signers[index] = txSigners
|
||||
}
|
||||
|
||||
return signers, nil
|
||||
}
|
||||
Reference in New Issue
Block a user