feat(v1): Vote Extension Auction (#106)
This commit is contained in:
parent
cc74b9e027
commit
7797110514
@ -1,13 +1,11 @@
|
||||
package abci_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
comettypes "github.com/cometbft/cometbft/abci/types"
|
||||
"github.com/cometbft/cometbft/libs/log"
|
||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||
"github.com/cosmos/cosmos-sdk/testutil"
|
||||
@ -99,7 +97,7 @@ func (suite *ABCITestSuite) SetupTest() {
|
||||
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
|
||||
|
||||
// Accounts set up
|
||||
suite.accounts = testutils.RandomAccounts(suite.random, 1)
|
||||
suite.accounts = testutils.RandomAccounts(suite.random, 10)
|
||||
suite.balances = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000000000000000000)))
|
||||
suite.nonces = make(map[string]uint64)
|
||||
for _, acc := range suite.accounts {
|
||||
@ -125,20 +123,6 @@ func (suite *ABCITestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, simulate boo
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
if !simulate {
|
||||
hash := sha256.Sum256(bz)
|
||||
txHash := hex.EncodeToString(hash[:])
|
||||
if _, ok := suite.txs[txHash]; ok {
|
||||
return ctx, fmt.Errorf("tx already in mempool")
|
||||
}
|
||||
suite.txs[txHash] = struct{}{}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
@ -228,28 +212,10 @@ func (suite *ABCITestSuite) createFilledMempool(numNormalTxs, numAuctionTxs, num
|
||||
return totalNumTxs
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) exportMempool(exportRefTxs bool) [][]byte {
|
||||
func (suite *ABCITestSuite) exportMempool() [][]byte {
|
||||
txs := make([][]byte, 0)
|
||||
seenTxs := make(map[string]bool)
|
||||
|
||||
auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx)
|
||||
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
|
||||
auctionTx := auctionIterator.Tx()
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(auctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txs = append(txs, txBz)
|
||||
|
||||
if exportRefTxs {
|
||||
for _, refRawTx := range auctionTx.GetMsgs()[0].(*buildertypes.MsgAuctionBid).GetTransactions() {
|
||||
txs = append(txs, refRawTx)
|
||||
seenTxs[string(refRawTx)] = true
|
||||
}
|
||||
}
|
||||
|
||||
seenTxs[string(txBz)] = true
|
||||
}
|
||||
|
||||
iterator := suite.mempool.Select(suite.ctx, nil)
|
||||
for ; iterator != nil; iterator = iterator.Next() {
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(iterator.Tx())
|
||||
@ -262,3 +228,108 @@ func (suite *ABCITestSuite) exportMempool(exportRefTxs bool) [][]byte {
|
||||
|
||||
return txs
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) createPrepareProposalRequest(maxBytes int64) comettypes.RequestPrepareProposal {
|
||||
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
|
||||
|
||||
auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx)
|
||||
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
|
||||
tx := auctionIterator.Tx()
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions = append(voteExtensions, comettypes.ExtendedVoteInfo{
|
||||
VoteExtension: txBz,
|
||||
})
|
||||
}
|
||||
|
||||
return comettypes.RequestPrepareProposal{
|
||||
MaxTxBytes: maxBytes,
|
||||
LocalLastCommit: comettypes.ExtendedCommitInfo{
|
||||
Votes: voteExtensions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) createExtendedCommitInfoFromTxBzs(txs [][]byte) []byte {
|
||||
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
|
||||
|
||||
for _, txBz := range txs {
|
||||
voteExtensions = append(voteExtensions, comettypes.ExtendedVoteInfo{
|
||||
VoteExtension: txBz,
|
||||
})
|
||||
}
|
||||
|
||||
commitInfo := comettypes.ExtendedCommitInfo{
|
||||
Votes: voteExtensions,
|
||||
}
|
||||
|
||||
commitInfoBz, err := commitInfo.Marshal()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return commitInfoBz
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) createAuctionInfoFromTxBzs(txs [][]byte, numTxs uint64) []byte {
|
||||
auctionInfo := abci.AuctionInfo{
|
||||
ExtendedCommitInfo: suite.createExtendedCommitInfoFromTxBzs(txs),
|
||||
NumTxs: numTxs,
|
||||
MaxTxBytes: int64(len(txs[0])),
|
||||
}
|
||||
|
||||
auctionInfoBz, err := auctionInfo.Marshal()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return auctionInfoBz
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) getAllAuctionTxs() ([]sdk.Tx, [][]byte) {
|
||||
auctionIterator := suite.mempool.AuctionBidSelect(suite.ctx)
|
||||
txs := make([]sdk.Tx, 0)
|
||||
txBzs := make([][]byte, 0)
|
||||
|
||||
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
|
||||
txs = append(txs, auctionIterator.Tx())
|
||||
|
||||
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(auctionIterator.Tx())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBzs = append(txBzs, bz)
|
||||
}
|
||||
|
||||
return txs, txBzs
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) createExtendedCommitInfoFromTxs(txs []sdk.Tx) comettypes.ExtendedCommitInfo {
|
||||
voteExtensions := make([][]byte, 0)
|
||||
for _, tx := range txs {
|
||||
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions = append(voteExtensions, bz)
|
||||
}
|
||||
|
||||
return suite.createExtendedCommitInfo(voteExtensions)
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) createExtendedVoteInfo(voteExtensions [][]byte) []comettypes.ExtendedVoteInfo {
|
||||
commitInfo := make([]comettypes.ExtendedVoteInfo, 0)
|
||||
for _, voteExtension := range voteExtensions {
|
||||
info := comettypes.ExtendedVoteInfo{
|
||||
VoteExtension: voteExtension,
|
||||
}
|
||||
|
||||
commitInfo = append(commitInfo, info)
|
||||
}
|
||||
|
||||
return commitInfo
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) createExtendedCommitInfo(voteExtensions [][]byte) comettypes.ExtendedCommitInfo {
|
||||
commitInfo := comettypes.ExtendedCommitInfo{
|
||||
Votes: suite.createExtendedVoteInfo(voteExtensions),
|
||||
}
|
||||
|
||||
return commitInfo
|
||||
}
|
||||
|
||||
231
abci/auction.go
Normal file
231
abci/auction.go
Normal file
@ -0,0 +1,231 @@
|
||||
package abci
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
abci "github.com/cometbft/cometbft/abci/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// TopOfBlock contains information about how the top of block should be built.
|
||||
type TopOfBlock struct {
|
||||
// Txs contains the transactions that should be included in the top of block.
|
||||
Txs [][]byte
|
||||
|
||||
// Size is the total size of the top of block.
|
||||
Size int64
|
||||
|
||||
// Cache is the cache of transactions that were seen, stored in order to ignore them
|
||||
// when building the rest of the block.
|
||||
Cache map[string]struct{}
|
||||
}
|
||||
|
||||
func NewTopOfBlock() TopOfBlock {
|
||||
return TopOfBlock{
|
||||
Cache: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildTOB inputs all of the vote extensions and outputs a top of block proposal that includes
|
||||
// the highest bidding valid transaction along with all the bundled transactions.
|
||||
func (h *ProposalHandler) BuildTOB(ctx sdk.Context, voteExtensionInfo abci.ExtendedCommitInfo, maxBytes int64) TopOfBlock {
|
||||
// Get the bid transactions from the vote extensions.
|
||||
sortedBidTxs := h.GetBidsFromVoteExtensions(voteExtensionInfo.Votes)
|
||||
|
||||
// Track the transactions we can remove from the mempool
|
||||
txsToRemove := make(map[sdk.Tx]struct{})
|
||||
|
||||
// Attempt to select the highest bid transaction that is valid and whose
|
||||
// bundled transactions are valid.
|
||||
var topOfBlock TopOfBlock
|
||||
for _, bidTx := range sortedBidTxs {
|
||||
// Cache the context so that we can write it back to the original context
|
||||
// when we know we have a valid top of block bundle.
|
||||
cacheCtx, write := ctx.CacheContext()
|
||||
|
||||
// Attempt to build the top of block using the bid transaction.
|
||||
proposal, err := h.buildTOB(cacheCtx, bidTx)
|
||||
if err != nil {
|
||||
h.logger.Info(
|
||||
"vote extension auction failed to verify auction tx",
|
||||
"err", err,
|
||||
)
|
||||
txsToRemove[bidTx] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if proposal.Size <= maxBytes {
|
||||
// At this point, both the bid transaction itself and all the bundled
|
||||
// transactions are valid. So we select the bid transaction along with
|
||||
// all the bundled transactions and apply the state changes to the cache
|
||||
// context.
|
||||
topOfBlock = proposal
|
||||
write()
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
h.logger.Info(
|
||||
"failed to select auction bid tx; auction tx size is too large",
|
||||
"tx_size", proposal.Size,
|
||||
"max_size", maxBytes,
|
||||
)
|
||||
}
|
||||
|
||||
// Remove all of the transactions that were not valid.
|
||||
for tx := range txsToRemove {
|
||||
h.RemoveTx(tx)
|
||||
}
|
||||
|
||||
return topOfBlock
|
||||
}
|
||||
|
||||
// VerifyTOB verifies that the set of vote extensions used in prepare proposal deterministically
|
||||
// produce the same top of block proposal.
|
||||
func (h *ProposalHandler) VerifyTOB(ctx sdk.Context, proposalTxs [][]byte) (*AuctionInfo, error) {
|
||||
// Proposal must include at least the auction info.
|
||||
if len(proposalTxs) < NumInjectedTxs {
|
||||
return nil, fmt.Errorf("proposal is too small; expected at least %d slots", NumInjectedTxs)
|
||||
}
|
||||
|
||||
// Extract the auction info from the proposal.
|
||||
auctionInfo := &AuctionInfo{}
|
||||
if err := auctionInfo.Unmarshal(proposalTxs[AuctionInfoIndex]); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal auction info: %w", err)
|
||||
}
|
||||
|
||||
// Verify that the proposal contains the expected number of top of block transactions.
|
||||
if len(proposalTxs) < int(auctionInfo.NumTxs)+NumInjectedTxs {
|
||||
return nil, fmt.Errorf("number of txs in proposal do not match expected in auction info; expected at least %d slots", auctionInfo.NumTxs+NumInjectedTxs)
|
||||
}
|
||||
|
||||
// Unmarshall the vote extension information from the auction info.
|
||||
lastCommitInfo := abci.ExtendedCommitInfo{}
|
||||
if err := lastCommitInfo.Unmarshal(auctionInfo.ExtendedCommitInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal last commit info from auction info: %w", err)
|
||||
}
|
||||
|
||||
// Build the top of block proposal from the auction info.
|
||||
expectedTOB := h.BuildTOB(ctx, lastCommitInfo, auctionInfo.MaxTxBytes)
|
||||
|
||||
// Verify that the top of block txs matches the top of block proposal txs.
|
||||
actualTOBTxs := proposalTxs[NumInjectedTxs : auctionInfo.NumTxs+NumInjectedTxs]
|
||||
if !reflect.DeepEqual(actualTOBTxs, expectedTOB.Txs) {
|
||||
return nil, fmt.Errorf("expected top of block txs does not match top of block proposal")
|
||||
}
|
||||
|
||||
return auctionInfo, nil
|
||||
}
|
||||
|
||||
// GetBidsFromVoteExtensions returns all of the auction bid transactions from
|
||||
// the vote extensions in sorted descending order.
|
||||
func (h *ProposalHandler) GetBidsFromVoteExtensions(voteExtensions []abci.ExtendedVoteInfo) []sdk.Tx {
|
||||
bidTxs := make([]sdk.Tx, 0)
|
||||
|
||||
// Iterate through all vote extensions and extract the auction transactions.
|
||||
for _, voteInfo := range voteExtensions {
|
||||
voteExtension := voteInfo.VoteExtension
|
||||
|
||||
// Check if the vote extension contains an auction transaction.
|
||||
if bidTx, err := h.getAuctionTxFromVoteExtension(voteExtension); err == nil {
|
||||
bidTxs = append(bidTxs, bidTx)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the auction transactions by their bid amount in descending order.
|
||||
sort.Slice(bidTxs, func(i, j int) bool {
|
||||
// In the case of an error, we want to sort the transaction to the end of the list.
|
||||
bidInfoI, err := h.mempool.GetAuctionBidInfo(bidTxs[i])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
bidInfoJ, err := h.mempool.GetAuctionBidInfo(bidTxs[j])
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return bidInfoI.Bid.IsGTE(bidInfoJ.Bid)
|
||||
})
|
||||
|
||||
return bidTxs
|
||||
}
|
||||
|
||||
// buildTOB verifies that the auction and bundled transactions are valid and returns the transactions that
|
||||
// should be included in the top of block, size of the auction transaction and bundle, and a cache
|
||||
// of all transactions that should be ignored.
|
||||
func (h *ProposalHandler) buildTOB(ctx sdk.Context, bidTx sdk.Tx) (TopOfBlock, error) {
|
||||
proposal := NewTopOfBlock()
|
||||
|
||||
// Ensure that the bid transaction is valid
|
||||
bidTxBz, err := h.PrepareProposalVerifyTx(ctx, bidTx)
|
||||
if err != nil {
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
bidInfo, err := h.mempool.GetAuctionBidInfo(bidTx)
|
||||
if err != nil {
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
// store the bytes of each ref tx as sdk.Tx bytes in order to build a valid proposal
|
||||
sdkTxBytes := make([][]byte, len(bidInfo.Transactions))
|
||||
|
||||
// Ensure that the bundled transactions are valid
|
||||
for index, rawRefTx := range bidInfo.Transactions {
|
||||
// convert the bundled raw transaction to a sdk.Tx
|
||||
refTx, err := h.mempool.WrapBundleTransaction(rawRefTx)
|
||||
if err != nil {
|
||||
return TopOfBlock{}, err
|
||||
}
|
||||
|
||||
txBz, err := h.PrepareProposalVerifyTx(ctx, refTx)
|
||||
if err != nil {
|
||||
return TopOfBlock{}, err
|
||||
}
|
||||
|
||||
hashBz := sha256.Sum256(txBz)
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
|
||||
proposal.Cache[hash] = struct{}{}
|
||||
sdkTxBytes[index] = txBz
|
||||
}
|
||||
|
||||
// cache the bytes of the bid transaction
|
||||
hashBz := sha256.Sum256(bidTxBz)
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
proposal.Cache[hash] = struct{}{}
|
||||
|
||||
txs := [][]byte{bidTxBz}
|
||||
txs = append(txs, sdkTxBytes...)
|
||||
|
||||
// Set the top of block transactions and size.
|
||||
proposal.Txs = txs
|
||||
proposal.Size = int64(len(bidTxBz))
|
||||
|
||||
return proposal, nil
|
||||
}
|
||||
|
||||
// getAuctionTxFromVoteExtension extracts the auction transaction from the vote extension.
|
||||
func (h *ProposalHandler) getAuctionTxFromVoteExtension(voteExtension []byte) (sdk.Tx, error) {
|
||||
if len(voteExtension) == 0 {
|
||||
return nil, fmt.Errorf("vote extension is empty")
|
||||
}
|
||||
|
||||
// Attempt to unmarshal the auction transaction.
|
||||
bidTx, err := h.txDecoder(voteExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify the auction transaction has bid information.
|
||||
if bidInfo, err := h.mempool.GetAuctionBidInfo(bidTx); err != nil || bidInfo == nil {
|
||||
return nil, fmt.Errorf("vote extension does not contain an auction transaction")
|
||||
}
|
||||
|
||||
return bidTx, nil
|
||||
}
|
||||
395
abci/auction.pb.go
Normal file
395
abci/auction.pb.go
Normal file
@ -0,0 +1,395 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: pob/abci/v1/auction.proto
|
||||
|
||||
package abci
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/cosmos/gogoproto/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// AuctionInfo contains information about the top of block auction
|
||||
// that was run in PrepareProposal using vote extensions.
|
||||
type AuctionInfo struct {
|
||||
// extended_commit_info contains the vote extensions that were used to run the auction.
|
||||
ExtendedCommitInfo []byte `protobuf:"bytes,1,opt,name=extended_commit_info,json=extendedCommitInfo,proto3" json:"extended_commit_info,omitempty"`
|
||||
// max_tx_bytes is the maximum number of bytes that were allowed for the proposal.
|
||||
MaxTxBytes int64 `protobuf:"varint,2,opt,name=max_tx_bytes,json=maxTxBytes,proto3" json:"max_tx_bytes,omitempty"`
|
||||
// num_txs is the number of transactions that were included in the proposal.
|
||||
NumTxs uint64 `protobuf:"varint,3,opt,name=num_txs,json=numTxs,proto3" json:"num_txs,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AuctionInfo) Reset() { *m = AuctionInfo{} }
|
||||
func (m *AuctionInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*AuctionInfo) ProtoMessage() {}
|
||||
func (*AuctionInfo) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_ea32f9b647554bf5, []int{0}
|
||||
}
|
||||
func (m *AuctionInfo) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *AuctionInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_AuctionInfo.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *AuctionInfo) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AuctionInfo.Merge(m, src)
|
||||
}
|
||||
func (m *AuctionInfo) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *AuctionInfo) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AuctionInfo.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AuctionInfo proto.InternalMessageInfo
|
||||
|
||||
func (m *AuctionInfo) GetExtendedCommitInfo() []byte {
|
||||
if m != nil {
|
||||
return m.ExtendedCommitInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AuctionInfo) GetMaxTxBytes() int64 {
|
||||
if m != nil {
|
||||
return m.MaxTxBytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *AuctionInfo) GetNumTxs() uint64 {
|
||||
if m != nil {
|
||||
return m.NumTxs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AuctionInfo)(nil), "pob.abci.v1.AuctionInfo")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("pob/abci/v1/auction.proto", fileDescriptor_ea32f9b647554bf5) }
|
||||
|
||||
var fileDescriptor_ea32f9b647554bf5 = []byte{
|
||||
// 224 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2c, 0xc8, 0x4f, 0xd2,
|
||||
0x4f, 0x4c, 0x4a, 0xce, 0xd4, 0x2f, 0x33, 0xd4, 0x4f, 0x2c, 0x4d, 0x2e, 0xc9, 0xcc, 0xcf, 0xd3,
|
||||
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2e, 0xc8, 0x4f, 0xd2, 0x03, 0x49, 0xe9, 0x95, 0x19,
|
||||
0x2a, 0x55, 0x71, 0x71, 0x3b, 0x42, 0x64, 0x3d, 0xf3, 0xd2, 0xf2, 0x85, 0x0c, 0xb8, 0x44, 0x52,
|
||||
0x2b, 0x4a, 0x52, 0xf3, 0x52, 0x52, 0x53, 0xe2, 0x93, 0xf3, 0x73, 0x73, 0x33, 0x4b, 0xe2, 0x33,
|
||||
0xf3, 0xd2, 0xf2, 0x25, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0x84, 0x60, 0x72, 0xce, 0x60, 0x29,
|
||||
0xb0, 0x0e, 0x05, 0x2e, 0x9e, 0xdc, 0xc4, 0x8a, 0xf8, 0x92, 0x8a, 0xf8, 0xa4, 0xca, 0x92, 0xd4,
|
||||
0x62, 0x09, 0x26, 0x05, 0x46, 0x0d, 0xe6, 0x20, 0xae, 0xdc, 0xc4, 0x8a, 0x90, 0x0a, 0x27, 0x90,
|
||||
0x88, 0x90, 0x38, 0x17, 0x7b, 0x5e, 0x69, 0x6e, 0x7c, 0x49, 0x45, 0xb1, 0x04, 0xb3, 0x02, 0xa3,
|
||||
0x06, 0x4b, 0x10, 0x5b, 0x5e, 0x69, 0x6e, 0x48, 0x45, 0xb1, 0x93, 0xd9, 0x89, 0x47, 0x72, 0x8c,
|
||||
0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72,
|
||||
0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0xc9, 0xa4, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7,
|
||||
0xe7, 0xea, 0x17, 0x67, 0x67, 0x16, 0xe8, 0xe6, 0xa6, 0x96, 0xe9, 0xc3, 0x7c, 0x94, 0xc4, 0x06,
|
||||
0xf6, 0x87, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xce, 0x6f, 0x84, 0x78, 0xe4, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *AuctionInfo) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *AuctionInfo) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *AuctionInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.NumTxs != 0 {
|
||||
i = encodeVarintAuction(dAtA, i, uint64(m.NumTxs))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if m.MaxTxBytes != 0 {
|
||||
i = encodeVarintAuction(dAtA, i, uint64(m.MaxTxBytes))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if len(m.ExtendedCommitInfo) > 0 {
|
||||
i -= len(m.ExtendedCommitInfo)
|
||||
copy(dAtA[i:], m.ExtendedCommitInfo)
|
||||
i = encodeVarintAuction(dAtA, i, uint64(len(m.ExtendedCommitInfo)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintAuction(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovAuction(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *AuctionInfo) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.ExtendedCommitInfo)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAuction(uint64(l))
|
||||
}
|
||||
if m.MaxTxBytes != 0 {
|
||||
n += 1 + sovAuction(uint64(m.MaxTxBytes))
|
||||
}
|
||||
if m.NumTxs != 0 {
|
||||
n += 1 + sovAuction(uint64(m.NumTxs))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovAuction(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozAuction(x uint64) (n int) {
|
||||
return sovAuction(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *AuctionInfo) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: AuctionInfo: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: AuctionInfo: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ExtendedCommitInfo", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthAuction
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAuction
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.ExtendedCommitInfo = append(m.ExtendedCommitInfo[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.ExtendedCommitInfo == nil {
|
||||
m.ExtendedCommitInfo = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MaxTxBytes", wireType)
|
||||
}
|
||||
m.MaxTxBytes = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.MaxTxBytes |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field NumTxs", wireType)
|
||||
}
|
||||
m.NumTxs = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.NumTxs |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAuction(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthAuction
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipAuction(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuction
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthAuction
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupAuction
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthAuction
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthAuction = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowAuction = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupAuction = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
512
abci/auction_test.go
Normal file
512
abci/auction_test.go
Normal file
@ -0,0 +1,512 @@
|
||||
package abci_test
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
testutils "github.com/skip-mev/pob/testutils"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
)
|
||||
|
||||
func (suite *ABCITestSuite) TestGetBidsFromVoteExtensions() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
createVoteExtensions func() ([][]byte, [][]byte) // returns (vote extensions, expected bids)
|
||||
}{
|
||||
{
|
||||
"no vote extensions",
|
||||
func() ([][]byte, [][]byte) {
|
||||
return nil, [][]byte{}
|
||||
},
|
||||
},
|
||||
{
|
||||
"no vote extensions",
|
||||
func() ([][]byte, [][]byte) {
|
||||
return [][]byte{}, [][]byte{}
|
||||
},
|
||||
},
|
||||
{
|
||||
"single vote extension",
|
||||
func() ([][]byte, [][]byte) {
|
||||
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions := [][]byte{
|
||||
bidTxBz,
|
||||
}
|
||||
|
||||
expectedBids := [][]byte{
|
||||
bidTxBz,
|
||||
}
|
||||
|
||||
return voteExtensions, expectedBids
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple vote extensions",
|
||||
func() ([][]byte, [][]byte) {
|
||||
bidTxBz1, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions := [][]byte{
|
||||
bidTxBz1,
|
||||
bidTxBz2,
|
||||
}
|
||||
|
||||
expectedBids := [][]byte{
|
||||
bidTxBz1,
|
||||
bidTxBz2,
|
||||
}
|
||||
|
||||
return voteExtensions, expectedBids
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple vote extensions with some noise",
|
||||
func() ([][]byte, [][]byte) {
|
||||
bidTxBz1, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions := [][]byte{
|
||||
bidTxBz1,
|
||||
nil,
|
||||
bidTxBz2,
|
||||
[]byte("noise"),
|
||||
[]byte("noise p2"),
|
||||
}
|
||||
|
||||
expectedBids := [][]byte{
|
||||
bidTxBz1,
|
||||
bidTxBz2,
|
||||
}
|
||||
|
||||
return voteExtensions, expectedBids
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple vote extensions with some normal txs",
|
||||
func() ([][]byte, [][]byte) {
|
||||
bidTxBz1, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
randomBz, err := testutils.CreateRandomTxBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions := [][]byte{
|
||||
bidTxBz1,
|
||||
bidTxBz2,
|
||||
nil,
|
||||
randomBz,
|
||||
[]byte("noise p2"),
|
||||
}
|
||||
|
||||
expectedBids := [][]byte{
|
||||
bidTxBz1,
|
||||
bidTxBz2,
|
||||
}
|
||||
|
||||
return voteExtensions, expectedBids
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple vote extensions with some normal txs in unsorted order",
|
||||
func() ([][]byte, [][]byte) {
|
||||
bidTxBz1, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(1001)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
randomBz, err := testutils.CreateRandomTxBz(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
voteExtensions := [][]byte{
|
||||
bidTxBz2,
|
||||
bidTxBz1,
|
||||
nil,
|
||||
randomBz,
|
||||
[]byte("noise p2"),
|
||||
}
|
||||
|
||||
expectedBids := [][]byte{
|
||||
bidTxBz1,
|
||||
bidTxBz2,
|
||||
}
|
||||
|
||||
return voteExtensions, expectedBids
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
voteExtensions, expectedBids := tc.createVoteExtensions()
|
||||
|
||||
commitInfo := suite.createExtendedVoteInfo(voteExtensions)
|
||||
|
||||
// get the bids from the vote extensions
|
||||
bids := suite.proposalHandler.GetBidsFromVoteExtensions(commitInfo)
|
||||
|
||||
// Check invarients
|
||||
suite.Require().Equal(len(expectedBids), len(bids))
|
||||
for i, bid := range expectedBids {
|
||||
actualBz, err := suite.encodingConfig.TxConfig.TxEncoder()(bids[i])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().Equal(bid, actualBz)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ABCITestSuite) TestBuildTOB() {
|
||||
params := buildertypes.Params{
|
||||
MaxBundleSize: 4,
|
||||
MinBuyInFee: sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
ReserveFee: sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
MinBidIncrement: sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
FrontRunningProtection: true,
|
||||
}
|
||||
suite.builderKeeper.SetParams(suite.ctx, params)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
getBidTxs func() ([]sdk.Tx, sdk.Tx) // returns the bids and the winning bid
|
||||
maxBytes int64
|
||||
}{
|
||||
{
|
||||
"no bids",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
return []sdk.Tx{}, nil
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"single bid",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx}, bidTx
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"single invalid bid (bid is too small)",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(1)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx}, nil
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"single invalid bid with front-running",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(1000)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0], suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx}, nil
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"single invalid bid with too many transactions in the bundle",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx}, nil
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"single bid but max bytes is too small",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx}, nil
|
||||
},
|
||||
1,
|
||||
},
|
||||
{
|
||||
"multiple bids",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx1, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTx2, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[1],
|
||||
sdk.NewCoin("foo", sdk.NewInt(102)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx2, bidTx1}, bidTx2
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"multiple bids with front-running",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx1, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(1000)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0], suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTx2, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[1],
|
||||
sdk.NewCoin("foo", sdk.NewInt(200)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx1, bidTx2}, bidTx2
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"multiple bids with too many transactions in the bundle",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx1, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
suite.accounts[0],
|
||||
},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTx2, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[1],
|
||||
sdk.NewCoin("foo", sdk.NewInt(102)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx1, bidTx2}, bidTx2
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
{
|
||||
"multiple bids unsorted",
|
||||
func() ([]sdk.Tx, sdk.Tx) {
|
||||
bidTx1, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(101)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidTx2, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[1],
|
||||
sdk.NewCoin("foo", sdk.NewInt(102)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return []sdk.Tx{bidTx1, bidTx2}, bidTx2
|
||||
},
|
||||
1000000000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
bidTxs, winningBid := tc.getBidTxs()
|
||||
|
||||
commitInfo := suite.createExtendedCommitInfoFromTxs(bidTxs)
|
||||
|
||||
// Host the auction
|
||||
proposal := suite.proposalHandler.BuildTOB(suite.ctx, commitInfo, tc.maxBytes)
|
||||
|
||||
// Size of the proposal should be less than or equal to the max bytes
|
||||
suite.Require().LessOrEqual(proposal.Size, tc.maxBytes)
|
||||
|
||||
if winningBid == nil {
|
||||
suite.Require().Len(proposal.Txs, 0)
|
||||
suite.Require().Equal(proposal.Size, int64(0))
|
||||
} else {
|
||||
// Get info about the winning bid
|
||||
winningBidBz, err := suite.encodingConfig.TxConfig.TxEncoder()(winningBid)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionBidInfo, err := suite.mempool.GetAuctionBidInfo(winningBid)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Verify that the size of the proposal is the size of the winning bid
|
||||
// plus the size of the bundle
|
||||
suite.Require().Equal(len(proposal.Txs), len(auctionBidInfo.Transactions)+1)
|
||||
|
||||
// Verify that the winning bid is the first transaction in the proposal
|
||||
suite.Require().Equal(proposal.Txs[0], winningBidBz)
|
||||
|
||||
// Verify the ordering of transactions in the proposal
|
||||
for index, tx := range proposal.Txs[1:] {
|
||||
suite.Equal(tx, auctionBidInfo.Transactions[index])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
package abci
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@ -12,7 +11,17 @@ 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"
|
||||
mempool "github.com/skip-mev/pob/mempool"
|
||||
)
|
||||
|
||||
const (
|
||||
// NumInjectedTxs is the minimum number of transactions that were injected into
|
||||
// the proposal but are not actual transactions. In this case, the auction
|
||||
// info is injected into the proposal but should be ignored by the application.ß
|
||||
NumInjectedTxs = 1
|
||||
|
||||
// AuctionInfoIndex is the index of the auction info in the proposal.
|
||||
AuctionInfoIndex = 0
|
||||
)
|
||||
|
||||
type (
|
||||
@ -63,132 +72,72 @@ func NewProposalHandler(
|
||||
// top-of-block auctioning and general block proposal construction.
|
||||
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
return func(ctx sdk.Context, req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
|
||||
var (
|
||||
selectedTxs [][]byte
|
||||
totalTxBytes int64
|
||||
)
|
||||
// Proposal includes all of the transactions that will be included in the
|
||||
// block along with the vote extensions from the previous block included at
|
||||
// the beginning of the proposal. Vote extensions must be included in the
|
||||
// first slot of the proposal because they are inaccessible in ProcessProposal.
|
||||
proposal := make([][]byte, 0)
|
||||
|
||||
bidTxIterator := h.mempool.AuctionBidSelect(ctx)
|
||||
txsToRemove := make(map[sdk.Tx]struct{}, 0)
|
||||
seenTxs := make(map[string]struct{}, 0)
|
||||
// Build the top of block portion of the proposal given the vote extensions
|
||||
// from the previous block.
|
||||
topOfBlock := h.BuildTOB(ctx, req.LocalLastCommit, req.MaxTxBytes)
|
||||
|
||||
// Attempt to select the highest bid transaction that is valid and whose
|
||||
// bundled transactions are valid.
|
||||
selectBidTxLoop:
|
||||
for ; bidTxIterator != nil; bidTxIterator = bidTxIterator.Next() {
|
||||
cacheCtx, write := ctx.CacheContext()
|
||||
tmpBidTx := bidTxIterator.Tx()
|
||||
|
||||
bidTxBz, err := h.PrepareProposalVerifyTx(cacheCtx, tmpBidTx)
|
||||
if err != nil {
|
||||
txsToRemove[tmpBidTx] = struct{}{}
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
bidTxSize := int64(len(bidTxBz))
|
||||
if bidTxSize <= req.MaxTxBytes {
|
||||
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.
|
||||
txsToRemove[tmpBidTx] = struct{}{}
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// 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
|
||||
for index, rawRefTx := range bundledTransactions {
|
||||
refTx, err := h.mempool.WrapBundleTransaction(rawRefTx)
|
||||
if err != nil {
|
||||
// Malformed bundled transaction, so we remove the bid transaction
|
||||
// and try the next top bid.
|
||||
txsToRemove[tmpBidTx] = struct{}{}
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
txBz, err := h.PrepareProposalVerifyTx(cacheCtx, refTx)
|
||||
if err != nil {
|
||||
// Invalid bundled transaction, so we remove the bid transaction
|
||||
// and try the next top bid.
|
||||
txsToRemove[tmpBidTx] = struct{}{}
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
sdkTxBytes[index] = txBz
|
||||
}
|
||||
|
||||
// At this point, both the bid transaction itself and all the bundled
|
||||
// transactions are valid. So we select the bid transaction along with
|
||||
// all the bundled transactions. We also mark these transactions as seen and
|
||||
// update the total size selected thus far.
|
||||
totalTxBytes += bidTxSize
|
||||
selectedTxs = append(selectedTxs, bidTxBz)
|
||||
selectedTxs = append(selectedTxs, sdkTxBytes...)
|
||||
|
||||
for _, refTxRaw := range sdkTxBytes {
|
||||
hash := sha256.Sum256(refTxRaw)
|
||||
txHash := hex.EncodeToString(hash[:])
|
||||
seenTxs[txHash] = struct{}{}
|
||||
}
|
||||
|
||||
// Write the cache context to the original context when we know we have a
|
||||
// valid top of block bundle.
|
||||
write()
|
||||
|
||||
break selectBidTxLoop
|
||||
}
|
||||
|
||||
txsToRemove[tmpBidTx] = struct{}{}
|
||||
h.logger.Info(
|
||||
"failed to select auction bid tx; tx size is too large",
|
||||
"tx_size", bidTxSize,
|
||||
"max_size", req.MaxTxBytes,
|
||||
)
|
||||
// If information is unable to be marshaled, we return an empty proposal. This will
|
||||
// cause another proposal to be generated after it is rejected in ProcessProposal.
|
||||
lastCommitInfo, err := req.LocalLastCommit.Marshal()
|
||||
if err != nil {
|
||||
return abci.ResponsePrepareProposal{Txs: proposal}
|
||||
}
|
||||
|
||||
// Remove all invalid transactions from the mempool.
|
||||
for tx := range txsToRemove {
|
||||
h.RemoveTx(tx)
|
||||
auctionInfo := AuctionInfo{
|
||||
ExtendedCommitInfo: lastCommitInfo,
|
||||
MaxTxBytes: req.MaxTxBytes,
|
||||
NumTxs: uint64(len(topOfBlock.Txs)),
|
||||
}
|
||||
|
||||
iterator := h.mempool.Select(ctx, nil)
|
||||
txsToRemove = map[sdk.Tx]struct{}{}
|
||||
// Add the auction info and top of block transactions into the proposal.
|
||||
auctionInfoBz, err := auctionInfo.Marshal()
|
||||
if err != nil {
|
||||
return abci.ResponsePrepareProposal{Txs: proposal}
|
||||
}
|
||||
|
||||
proposal = append(proposal, auctionInfoBz)
|
||||
proposal = append(proposal, topOfBlock.Txs...)
|
||||
|
||||
// Select remaining transactions for the block proposal until we've reached
|
||||
// size capacity.
|
||||
selectTxLoop:
|
||||
for ; iterator != nil; iterator = iterator.Next() {
|
||||
totalTxBytes := topOfBlock.Size
|
||||
txsToRemove := make(map[sdk.Tx]struct{}, 0)
|
||||
for iterator := h.mempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() {
|
||||
memTx := iterator.Tx()
|
||||
|
||||
// If the transaction is already included in the proposal, then we skip it.
|
||||
// If the transaction has already been seen in the top of block, skip it.
|
||||
txBz, err := h.txEncoder(memTx)
|
||||
if err != nil {
|
||||
txsToRemove[memTx] = struct{}{}
|
||||
continue selectTxLoop
|
||||
continue
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(txBz)
|
||||
txHash := hex.EncodeToString(hash[:])
|
||||
if _, ok := seenTxs[txHash]; ok {
|
||||
continue selectTxLoop
|
||||
hashBz := sha256.Sum256(txBz)
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
if _, ok := topOfBlock.Cache[hash]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify that the transaction is valid.
|
||||
txBz, err = h.PrepareProposalVerifyTx(ctx, memTx)
|
||||
if err != nil {
|
||||
txsToRemove[memTx] = struct{}{}
|
||||
continue selectTxLoop
|
||||
continue
|
||||
}
|
||||
|
||||
txSize := int64(len(txBz))
|
||||
if totalTxBytes += txSize; totalTxBytes <= req.MaxTxBytes {
|
||||
selectedTxs = append(selectedTxs, txBz)
|
||||
proposal = append(proposal, txBz)
|
||||
} else {
|
||||
// We've reached capacity per req.MaxTxBytes so we cannot select any
|
||||
// more transactions.
|
||||
break selectTxLoop
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +146,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
h.RemoveTx(tx)
|
||||
}
|
||||
|
||||
return abci.ResponsePrepareProposal{Txs: selectedTxs}
|
||||
return abci.ResponsePrepareProposal{Txs: proposal}
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,50 +154,44 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
// block proposal verification.
|
||||
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
|
||||
return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal {
|
||||
for index, txBz := range req.Txs {
|
||||
proposal := req.Txs
|
||||
|
||||
// Verify that the same top of block transactions can be built from the vote
|
||||
// extensions included in the proposal.
|
||||
auctionInfo, err := h.VerifyTOB(ctx, proposal)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
// Track the transactions that need to be removed from the mempool.
|
||||
txsToRemove := make(map[sdk.Tx]struct{}, 0)
|
||||
invalidProposal := false
|
||||
|
||||
// Verify that the remaining transactions in the proposal are valid.
|
||||
for _, txBz := range proposal[auctionInfo.NumTxs+NumInjectedTxs:] {
|
||||
tx, err := h.ProcessProposalVerifyTx(ctx, txBz)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
bidInfo, err := h.mempool.GetAuctionBidInfo(tx)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
// 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}
|
||||
if tx == nil || err != nil {
|
||||
invalidProposal = true
|
||||
if tx != nil {
|
||||
txsToRemove[tx] = struct{}{}
|
||||
}
|
||||
|
||||
bundledTransactions := bidInfo.Transactions
|
||||
if len(req.Txs) < len(bundledTransactions)+1 {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
for i, refTxRaw := range bundledTransactions {
|
||||
// Wrap and then encode the bundled transaction to ensure that the underlying
|
||||
// reference transaction can be processed as an sdk.Tx.
|
||||
wrappedTx, err := h.mempool.WrapBundleTransaction(refTxRaw)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
refTxBz, err := h.txEncoder(wrappedTx)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
if !bytes.Equal(refTxBz, req.Txs[i+1]) {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// The only auction transactions that should be included in the block proposal
|
||||
// must be at the top of the block.
|
||||
if bidInfo, err := h.mempool.GetAuctionBidInfo(tx); err != nil || bidInfo != nil {
|
||||
invalidProposal = true
|
||||
}
|
||||
}
|
||||
// Remove all invalid transactions from the mempool.
|
||||
for tx := range txsToRemove {
|
||||
h.RemoveTx(tx)
|
||||
}
|
||||
|
||||
if invalidProposal {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
package abci_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
abcitypes "github.com/cometbft/cometbft/abci/types"
|
||||
comettypes "github.com/cometbft/cometbft/abci/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/pob/abci"
|
||||
testutils "github.com/skip-mev/pob/testutils"
|
||||
@ -17,10 +15,11 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
maxTxBytes int64 = 1000000000000000000
|
||||
|
||||
// mempool configuration
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 100
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 100
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
expectedTopAuctionTx sdk.Tx
|
||||
|
||||
// auction configuration
|
||||
maxBundleSize uint32 = 10
|
||||
@ -30,11 +29,11 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
malleate func()
|
||||
expectedNumberProposalTxs int
|
||||
expectedNumberTxsInMempool int
|
||||
isTopBidValid bool
|
||||
name string
|
||||
malleate func()
|
||||
expectedNumberProposalTxs int
|
||||
expectedNumberTxsInMempool int
|
||||
expectedNumberTxsInAuctionMempool int
|
||||
}{
|
||||
{
|
||||
"single bundle in the mempool",
|
||||
@ -43,10 +42,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = true
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
4,
|
||||
5,
|
||||
3,
|
||||
true,
|
||||
1,
|
||||
},
|
||||
{
|
||||
"single bundle in the mempool, no ref txs in mempool",
|
||||
@ -55,10 +58,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
true,
|
||||
1,
|
||||
},
|
||||
{
|
||||
"single bundle in the mempool, not valid",
|
||||
@ -68,10 +75,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = nil
|
||||
},
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"single bundle in the mempool, not valid with ref txs in mempool",
|
||||
@ -82,10 +93,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = true
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = nil
|
||||
},
|
||||
4,
|
||||
3,
|
||||
3,
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"multiple bundles in the mempool, no normal txs + no ref txs in mempool",
|
||||
@ -96,22 +111,30 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numAuctionTxs = 10
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
true,
|
||||
10,
|
||||
},
|
||||
{
|
||||
"multiple bundles in the mempool, no normal txs + ref txs in mempool",
|
||||
"multiple bundles in the mempool, normal txs + ref txs in mempool",
|
||||
func() {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 10
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = true
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
31,
|
||||
32,
|
||||
30,
|
||||
true,
|
||||
10,
|
||||
},
|
||||
{
|
||||
"normal txs only",
|
||||
@ -119,10 +142,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numNormalTxs = 1
|
||||
numAuctionTxs = 0
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"many normal txs only",
|
||||
@ -130,10 +157,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 0
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
101,
|
||||
100,
|
||||
100,
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"single normal tx, single auction tx",
|
||||
@ -141,10 +172,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numNormalTxs = 1
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
2,
|
||||
3,
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"single normal tx, single auction tx with ref txs",
|
||||
@ -152,11 +187,15 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numNormalTxs = 1
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
insertRefTxs = true
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
5,
|
||||
6,
|
||||
4,
|
||||
1,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"single normal tx, single failing auction tx with ref txs",
|
||||
@ -167,10 +206,14 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
insertRefTxs = true
|
||||
suite.auctionBidAmount = sdk.NewCoin("foo", sdk.NewInt(2000)) // this will fail the ante handler
|
||||
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000000000))
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = nil
|
||||
},
|
||||
5,
|
||||
4,
|
||||
4,
|
||||
false,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"many normal tx, single auction tx with no ref txs",
|
||||
@ -180,57 +223,72 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = nil
|
||||
},
|
||||
101,
|
||||
102,
|
||||
100,
|
||||
true,
|
||||
1,
|
||||
},
|
||||
{
|
||||
"many normal tx, single auction tx with ref txs",
|
||||
func() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numAuctionTxs = 100
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = true
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
},
|
||||
104,
|
||||
103,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"many normal tx, single auction tx with ref txs",
|
||||
func() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
},
|
||||
104,
|
||||
402,
|
||||
400,
|
||||
100,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"many normal tx, many auction tx with ref txs",
|
||||
"many normal tx, many auction tx with ref txs but top bid is invalid",
|
||||
func() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 100
|
||||
numBundledTxs = 1
|
||||
insertRefTxs = true
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
expectedTopAuctionTx = suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
|
||||
// create a new bid that is greater than the current top bid
|
||||
bid := sdk.NewCoin("foo", sdk.NewInt(200000000000000000))
|
||||
bidTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
bid,
|
||||
0,
|
||||
0,
|
||||
[]testutils.Account{suite.accounts[0], suite.accounts[1]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// add the new bid to the mempool
|
||||
err = suite.mempool.Insert(suite.ctx, bidTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().Equal(suite.mempool.CountAuctionTx(), 101)
|
||||
},
|
||||
201,
|
||||
202,
|
||||
200,
|
||||
true,
|
||||
100,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest() // reset
|
||||
tc.malleate()
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
// create a new auction
|
||||
// Create a new auction.
|
||||
params := buildertypes.Params{
|
||||
MaxBundleSize: maxBundleSize,
|
||||
ReserveFee: reserveFee,
|
||||
@ -241,59 +299,54 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
suite.builderKeeper.SetParams(suite.ctx, params)
|
||||
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
|
||||
|
||||
// reset the proposal handler with the new mempool
|
||||
// Reset the proposal handler with the new mempool.
|
||||
suite.proposalHandler = abci.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
|
||||
|
||||
// Create a prepare proposal request based on the current state of the mempool.
|
||||
handler := suite.proposalHandler.PrepareProposalHandler()
|
||||
res := handler(suite.ctx, abcitypes.RequestPrepareProposal{
|
||||
MaxTxBytes: maxTxBytes,
|
||||
})
|
||||
req := suite.createPrepareProposalRequest(maxTxBytes)
|
||||
res := handler(suite.ctx, req)
|
||||
|
||||
// -------------------- Check Invariants -------------------- //
|
||||
// 1. The auction tx must fail if we know it is invalid
|
||||
suite.Require().Equal(tc.isTopBidValid, suite.isTopBidValid())
|
||||
// The first slot in the proposal must be the auction info
|
||||
auctionInfo := abci.AuctionInfo{}
|
||||
err := auctionInfo.Unmarshal(res.Txs[abci.AuctionInfoIndex])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// 2. total bytes must be less than or equal to maxTxBytes
|
||||
// Total bytes must be less than or equal to maxTxBytes
|
||||
totalBytes := int64(0)
|
||||
if suite.isTopBidValid() {
|
||||
totalBytes += int64(len(res.Txs[0]))
|
||||
|
||||
for _, tx := range res.Txs[1+numBundledTxs:] {
|
||||
totalBytes += int64(len(tx))
|
||||
}
|
||||
} else {
|
||||
for _, tx := range res.Txs {
|
||||
totalBytes += int64(len(tx))
|
||||
}
|
||||
for _, tx := range res.Txs[abci.NumInjectedTxs:] {
|
||||
totalBytes += int64(len(tx))
|
||||
}
|
||||
suite.Require().LessOrEqual(totalBytes, maxTxBytes)
|
||||
|
||||
// 3. the number of transactions in the response must be equal to the number of expected transactions
|
||||
// The number of transactions in the response must be equal to the number of expected transactions
|
||||
suite.Require().Equal(tc.expectedNumberProposalTxs, len(res.Txs))
|
||||
|
||||
// 4. if there are auction transactions, the first transaction must be the top bid
|
||||
// If there are auction transactions, the first transaction must be the top bid
|
||||
// and the rest of the bundle must be in the response
|
||||
if suite.isTopBidValid() {
|
||||
auctionTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[0])
|
||||
if expectedTopAuctionTx != nil {
|
||||
auctionTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[1])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidInfo, err := suite.mempool.GetAuctionBidInfo(auctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
for index, tx := range bidInfo.Transactions {
|
||||
suite.Require().Equal(tx, res.Txs[index+1])
|
||||
suite.Require().Equal(tx, res.Txs[abci.NumInjectedTxs+index+1])
|
||||
}
|
||||
}
|
||||
|
||||
// 5. All of the transactions must be unique
|
||||
uniqueTxs := make(map[string]bool)
|
||||
for _, tx := range res.Txs {
|
||||
for _, tx := range res.Txs[abci.NumInjectedTxs:] {
|
||||
suite.Require().False(uniqueTxs[string(tx)])
|
||||
uniqueTxs[string(tx)] = true
|
||||
}
|
||||
|
||||
// 6. The number of transactions in the mempool must be correct
|
||||
suite.Require().Equal(tc.expectedNumberTxsInMempool, suite.mempool.CountTx())
|
||||
suite.Require().Equal(tc.expectedNumberTxsInAuctionMempool, suite.mempool.CountAuctionTx())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -301,213 +354,412 @@ func (suite *ABCITestSuite) TestPrepareProposal() {
|
||||
func (suite *ABCITestSuite) TestProcessProposal() {
|
||||
var (
|
||||
// mempool set up
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = true
|
||||
exportRefTxs = true
|
||||
frontRunningTx sdk.Tx
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 3
|
||||
insertRefTxs = false
|
||||
|
||||
// auction set up
|
||||
maxBundleSize uint32 = 10
|
||||
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
|
||||
minBuyInFee = sdk.NewCoin("foo", sdk.NewInt(1000))
|
||||
frontRunningProtection = true
|
||||
maxBundleSize uint32 = 10
|
||||
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
|
||||
minBuyInFee = sdk.NewCoin("foo", sdk.NewInt(1000))
|
||||
)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
malleate func()
|
||||
isTopBidValid bool
|
||||
response abcitypes.ResponseProcessProposal_ProposalStatus
|
||||
name string
|
||||
createTxs func() [][]byte
|
||||
response comettypes.ResponseProcessProposal_ProposalStatus
|
||||
}{
|
||||
{
|
||||
"single normal tx, no auction tx",
|
||||
func() {
|
||||
"single normal tx, no vote extension info",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 1
|
||||
numAuctionTxs = 0
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
txs := suite.exportMempool()
|
||||
|
||||
return txs
|
||||
},
|
||||
false,
|
||||
abcitypes.ResponseProcessProposal_ACCEPT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction tx, no normal txs",
|
||||
func() {
|
||||
"single auction tx, no vote extension info",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
return suite.exportMempool()
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_ACCEPT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction tx, single auction tx",
|
||||
func() {
|
||||
"single auction tx, single auction tx, no vote extension info",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 1
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 0
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
return suite.exportMempool()
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_ACCEPT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction tx, single auction tx with ref txs",
|
||||
func() {
|
||||
"single auction tx with ref txs (no unwrapping)",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 1
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
suite.Require().NotNil(topAuctionTx)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 5)
|
||||
|
||||
proposal := append([][]byte{
|
||||
auctionInfo,
|
||||
txBz,
|
||||
}, suite.exportMempool()...)
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_ACCEPT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction tx, single auction tx with no ref txs",
|
||||
func() {
|
||||
numNormalTxs = 1
|
||||
"single auction tx with ref txs (with unwrapping)",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
insertRefTxs = false
|
||||
exportRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
suite.Require().NotNil(topAuctionTx)
|
||||
|
||||
bidInfo, err := suite.mempool.GetAuctionBidInfo(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 5)
|
||||
|
||||
proposal := append([][]byte{
|
||||
auctionInfo,
|
||||
txBz,
|
||||
}, bidInfo.Transactions...)
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_REJECT,
|
||||
comettypes.ResponseProcessProposal_ACCEPT,
|
||||
},
|
||||
{
|
||||
"multiple auction txs, single normal tx",
|
||||
func() {
|
||||
numNormalTxs = 1
|
||||
"single auction tx but no inclusion of ref txs",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
suite.Require().NotNil(topAuctionTx)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 5)
|
||||
|
||||
return [][]byte{
|
||||
auctionInfo,
|
||||
txBz,
|
||||
}
|
||||
},
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction tx, but auction tx is not valid",
|
||||
func() [][]byte {
|
||||
tx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
1,
|
||||
0, // invalid timeout
|
||||
[]testutils.Account{},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfoBz := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 1)
|
||||
|
||||
return [][]byte{
|
||||
auctionInfoBz,
|
||||
txBz,
|
||||
}
|
||||
},
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction tx with ref txs, but auction tx is not valid",
|
||||
func() [][]byte {
|
||||
tx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(100)),
|
||||
1,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[1], suite.accounts[1], suite.accounts[0]},
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfoBz := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 4)
|
||||
|
||||
bidInfo, err := suite.mempool.GetAuctionBidInfo(tx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return append([][]byte{
|
||||
auctionInfoBz,
|
||||
txBz,
|
||||
}, bidInfo.Transactions...)
|
||||
},
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"multiple auction txs but wrong auction tx is at top of block",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 2
|
||||
numBundledTxs = 4
|
||||
insertRefTxs = true
|
||||
exportRefTxs = true
|
||||
numBundledTxs = 0
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
_, auctionTxBzs := suite.getAllAuctionTxs()
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 1)
|
||||
|
||||
proposal := [][]byte{
|
||||
auctionInfo,
|
||||
auctionTxBzs[1],
|
||||
}
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_REJECT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single auction txs, multiple normal tx",
|
||||
func() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
"multiple auction txs included in block",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 2
|
||||
numBundledTxs = 0
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
_, auctionTxBzs := suite.getAllAuctionTxs()
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 1)
|
||||
|
||||
proposal := [][]byte{
|
||||
auctionInfo,
|
||||
auctionTxBzs[0],
|
||||
auctionTxBzs[1],
|
||||
}
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_ACCEPT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single invalid auction tx, multiple normal tx",
|
||||
func() {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
reserveFee = sdk.NewCoin("foo", sdk.NewInt(100000000000000000))
|
||||
insertRefTxs = true
|
||||
},
|
||||
false,
|
||||
abcitypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single valid auction txs but missing ref txs",
|
||||
func() {
|
||||
"single auction tx, but rest of the mempool is invalid",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 0
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
|
||||
numBundledTxs = 0
|
||||
insertRefTxs = false
|
||||
exportRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
suite.Require().NotNil(topAuctionTx)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 1)
|
||||
|
||||
proposal := [][]byte{
|
||||
auctionInfo,
|
||||
txBz,
|
||||
[]byte("invalid tx"),
|
||||
}
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_REJECT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"single valid auction txs but missing ref txs, with many normal txs",
|
||||
func() {
|
||||
"single auction tx with filled mempool, but rest of the mempool is invalid",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
reserveFee = sdk.NewCoin("foo", sdk.NewInt(1000))
|
||||
numBundledTxs = 0
|
||||
insertRefTxs = false
|
||||
exportRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
topAuctionTx := suite.mempool.GetTopAuctionTx(suite.ctx)
|
||||
suite.Require().NotNil(topAuctionTx)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 1)
|
||||
|
||||
proposal := append([][]byte{
|
||||
auctionInfo,
|
||||
txBz,
|
||||
}, suite.exportMempool()...)
|
||||
|
||||
proposal = append(proposal, []byte("invalid tx"))
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_REJECT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
{
|
||||
"auction tx with frontrunning",
|
||||
func() {
|
||||
randomAccount := testutils.RandomAccounts(suite.random, 1)[0]
|
||||
bidder := suite.accounts[0]
|
||||
bid := sdk.NewCoin("foo", sdk.NewInt(696969696969))
|
||||
nonce := suite.nonces[bidder.Address.String()]
|
||||
frontRunningTx, _ = testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, suite.accounts[0], bid, nonce+1, 1000, []testutils.Account{bidder, randomAccount})
|
||||
suite.Require().NotNil(frontRunningTx)
|
||||
|
||||
"multiple auction txs with filled mempool",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 1
|
||||
numBundledTxs = 4
|
||||
insertRefTxs = true
|
||||
exportRefTxs = true
|
||||
numAuctionTxs = 10
|
||||
numBundledTxs = 0
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
_, auctionTxBzs := suite.getAllAuctionTxs()
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 1)
|
||||
|
||||
proposal := append([][]byte{
|
||||
auctionInfo,
|
||||
auctionTxBzs[0],
|
||||
}, suite.exportMempool()...)
|
||||
|
||||
return proposal
|
||||
},
|
||||
false,
|
||||
abcitypes.ResponseProcessProposal_REJECT,
|
||||
comettypes.ResponseProcessProposal_ACCEPT,
|
||||
},
|
||||
{
|
||||
"auction tx with frontrunning, but frontrunning protection disabled",
|
||||
func() {
|
||||
randomAccount := testutils.RandomAccounts(suite.random, 1)[0]
|
||||
bidder := suite.accounts[0]
|
||||
bid := sdk.NewCoin("foo", sdk.NewInt(696969696969))
|
||||
nonce := suite.nonces[bidder.Address.String()]
|
||||
frontRunningTx, _ = testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, suite.accounts[0], bid, nonce+1, 1000, []testutils.Account{bidder, randomAccount})
|
||||
suite.Require().NotNil(frontRunningTx)
|
||||
"multiple auction txs with ref txs + filled mempool",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 10
|
||||
numBundledTxs = 10
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
auctionTxs, auctionTxBzs := suite.getAllAuctionTxs()
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs(auctionTxBzs, 11)
|
||||
|
||||
bidInfo, err := suite.mempool.GetAuctionBidInfo(auctionTxs[0])
|
||||
suite.Require().NoError(err)
|
||||
|
||||
proposal := append([][]byte{
|
||||
auctionInfo,
|
||||
auctionTxBzs[0],
|
||||
}, bidInfo.Transactions...)
|
||||
|
||||
proposal = append(proposal, suite.exportMempool()...)
|
||||
|
||||
return proposal
|
||||
},
|
||||
comettypes.ResponseProcessProposal_ACCEPT,
|
||||
},
|
||||
{
|
||||
"auction tx with front-running",
|
||||
func() [][]byte {
|
||||
numNormalTxs = 100
|
||||
numAuctionTxs = 0
|
||||
frontRunningProtection = false
|
||||
numBundledTxs = 0
|
||||
insertRefTxs = false
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
topAuctionTx, err := testutils.CreateAuctionTxWithSigners(
|
||||
suite.encodingConfig.TxConfig,
|
||||
suite.accounts[0],
|
||||
sdk.NewCoin("foo", sdk.NewInt(1000000)),
|
||||
0,
|
||||
1,
|
||||
[]testutils.Account{suite.accounts[0], suite.accounts[1]}, // front-running
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bidInfo, err := suite.mempool.GetAuctionBidInfo(topAuctionTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{txBz}, 3)
|
||||
|
||||
proposal := append([][]byte{
|
||||
auctionInfo,
|
||||
txBz,
|
||||
}, bidInfo.Transactions...)
|
||||
|
||||
proposal = append(proposal, suite.exportMempool()...)
|
||||
|
||||
return proposal
|
||||
},
|
||||
true,
|
||||
abcitypes.ResponseProcessProposal_ACCEPT,
|
||||
comettypes.ResponseProcessProposal_REJECT,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.SetupTest() // reset
|
||||
tc.malleate()
|
||||
|
||||
suite.createFilledMempool(numNormalTxs, numAuctionTxs, numBundledTxs, insertRefTxs)
|
||||
|
||||
// reset the proposal handler with the new mempool
|
||||
suite.proposalHandler = abci.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
|
||||
|
||||
if frontRunningTx != nil {
|
||||
suite.Require().NoError(suite.mempool.Insert(suite.ctx, frontRunningTx))
|
||||
}
|
||||
|
||||
// create a new auction
|
||||
params := buildertypes.Params{
|
||||
MaxBundleSize: maxBundleSize,
|
||||
ReserveFee: reserveFee,
|
||||
MinBuyInFee: minBuyInFee,
|
||||
FrontRunningProtection: frontRunningProtection,
|
||||
FrontRunningProtection: true,
|
||||
MinBidIncrement: suite.minBidIncrement,
|
||||
}
|
||||
suite.builderKeeper.SetParams(suite.ctx, params)
|
||||
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), suite.mempool)
|
||||
suite.Require().Equal(tc.isTopBidValid, suite.isTopBidValid())
|
||||
|
||||
txs := suite.exportMempool(exportRefTxs)
|
||||
|
||||
if frontRunningTx != nil {
|
||||
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(frontRunningTx)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().True(bytes.Equal(txs[0], txBz))
|
||||
}
|
||||
// reset the proposal handler with the new mempool
|
||||
suite.proposalHandler = abci.NewProposalHandler(suite.mempool, suite.logger, suite.anteHandler, suite.encodingConfig.TxConfig.TxEncoder(), suite.encodingConfig.TxConfig.TxDecoder())
|
||||
|
||||
handler := suite.proposalHandler.ProcessProposalHandler()
|
||||
res := handler(suite.ctx, abcitypes.RequestProcessProposal{
|
||||
Txs: txs,
|
||||
res := handler(suite.ctx, comettypes.RequestProcessProposal{
|
||||
Txs: tc.createTxs(),
|
||||
})
|
||||
|
||||
// Check if the response is valid
|
||||
@ -515,16 +767,3 @@ func (suite *ABCITestSuite) TestProcessProposal() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// isTopBidValid returns true if the top bid is valid. We purposefully insert invalid
|
||||
// auction transactions into the mempool to test the handlers.
|
||||
func (suite *ABCITestSuite) isTopBidValid() bool {
|
||||
iterator := suite.mempool.AuctionBidSelect(suite.ctx)
|
||||
if iterator == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the top bid is valid
|
||||
_, err := suite.anteHandler(suite.ctx, iterator.Tx(), true)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
1414
api/pob/abci/v1/auction.pulsar.go
Normal file
1414
api/pob/abci/v1/auction.pulsar.go
Normal file
File diff suppressed because it is too large
Load Diff
17
proto/pob/abci/v1/auction.proto
Normal file
17
proto/pob/abci/v1/auction.proto
Normal file
@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
package pob.abci.v1;
|
||||
|
||||
option go_package = "github.com/skip-mev/pob/abci";
|
||||
|
||||
// AuctionInfo contains information about the top of block auction
|
||||
// that was run in PrepareProposal using vote extensions.
|
||||
message AuctionInfo {
|
||||
// extended_commit_info contains the vote extensions that were used to run the auction.
|
||||
bytes extended_commit_info = 1;
|
||||
|
||||
// max_tx_bytes is the maximum number of bytes that were allowed for the proposal.
|
||||
int64 max_tx_bytes = 2;
|
||||
|
||||
// num_txs is the number of transactions that were included in the proposal.
|
||||
uint64 num_txs = 3;
|
||||
}
|
||||
@ -125,6 +125,15 @@ func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, t
|
||||
return txBuilder.GetTx(), nil
|
||||
}
|
||||
|
||||
func CreateRandomTxBz(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout uint64) ([]byte, error) {
|
||||
tx, err := CreateRandomTx(txCfg, account, nonce, numberMsgs, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return txCfg.TxEncoder()(tx)
|
||||
}
|
||||
|
||||
func CreateTxWithSigners(txCfg client.TxConfig, nonce, timeout uint64, signers []Account) (authsigning.Tx, error) {
|
||||
msgs := []sdk.Msg{}
|
||||
for _, signer := range signers {
|
||||
@ -199,6 +208,20 @@ func CreateAuctionTxWithSigners(txCfg client.TxConfig, bidder Account, bid sdk.C
|
||||
return txBuilder.GetTx(), nil
|
||||
}
|
||||
|
||||
func CreateAuctionTxWithSignerBz(txCfg client.TxConfig, bidder Account, bid sdk.Coin, nonce, timeout uint64, signers []Account) ([]byte, error) {
|
||||
bidTx, err := CreateAuctionTxWithSigners(txCfg, bidder, bid, nonce, timeout, signers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bz, err := txCfg.TxEncoder()(bidTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
func CreateRandomMsgs(acc sdk.AccAddress, numberMsgs int) []sdk.Msg {
|
||||
msgs := make([]sdk.Msg, numberMsgs)
|
||||
for i := 0; i < numberMsgs; i++ {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user