[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
+135
View File
@@ -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
View File
@@ -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
}
+2 -1
View File
@@ -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
+90
View File
@@ -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
}