feat(ITS): [ENG-1627]: Moving ITS from release branch to main (#236)

Co-authored-by: Nikhil <nikhil@skip.money>
This commit is contained in:
David Terpay
2023-08-11 18:22:39 +00:00
committed by GitHub
co-authored by Nikhil
parent 14f83b5e25
commit 1ed1adc856
31 changed files with 3771 additions and 6086 deletions
-317
View File
@@ -1,317 +0,0 @@
package abci_test
import (
"math/rand"
"testing"
"time"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
comettypes "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/skip-mev/pob/abci"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/lanes/base"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/ante"
"github.com/skip-mev/pob/x/builder/keeper"
buildertypes "github.com/skip-mev/pob/x/builder/types"
"github.com/stretchr/testify/suite"
)
type ABCITestSuite struct {
suite.Suite
ctx sdk.Context
// mempool and lane set up
mempool blockbuster.Mempool
tobLane *auction.TOBLane
baseLane *base.DefaultLane
logger log.Logger
encodingConfig testutils.EncodingConfig
proposalHandler *abci.ProposalHandler
voteExtensionHandler *abci.VoteExtensionHandler
// builder setup
builderKeeper keeper.Keeper
bankKeeper *testutils.MockBankKeeper
accountKeeper *testutils.MockAccountKeeper
distrKeeper *testutils.MockDistributionKeeper
stakingKeeper *testutils.MockStakingKeeper
builderDecorator ante.BuilderDecorator
key *storetypes.KVStoreKey
authorityAccount sdk.AccAddress
// account set up
accounts []testutils.Account
balance sdk.Coin
random *rand.Rand
nonces map[string]uint64
}
func TestABCISuite(t *testing.T) {
suite.Run(t, new(ABCITestSuite))
}
func (suite *ABCITestSuite) SetupTest() {
// General config
suite.encodingConfig = testutils.CreateTestEncodingConfig()
suite.random = rand.New(rand.NewSource(time.Now().Unix()))
suite.key = storetypes.NewKVStoreKey(buildertypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(suite.T(), suite.key, storetypes.NewTransientStoreKey("transient_test"))
suite.ctx = testCtx.Ctx.WithBlockHeight(10)
suite.logger = log.NewTestLogger(suite.T())
suite.ctx = suite.ctx.WithConsensusParams(cmtproto.ConsensusParams{
Abci: &cmtproto.ABCIParams{
VoteExtensionsEnableHeight: 1,
},
})
// Lanes configuration
//
// TOB lane set up
tobConfig := blockbuster.BaseLaneConfig{
Logger: suite.logger,
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(),
}
suite.tobLane = auction.NewTOBLane(
tobConfig,
0, // No bound on the number of transactions in the lane
auction.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder()),
)
// Base lane set up
baseConfig := blockbuster.BaseLaneConfig{
Logger: suite.logger,
TxEncoder: suite.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: suite.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: suite.anteHandler,
MaxBlockSpace: math.LegacyZeroDec(),
IgnoreList: []blockbuster.Lane{suite.tobLane},
}
suite.baseLane = base.NewDefaultLane(
baseConfig,
)
// Mempool set up
suite.mempool = blockbuster.NewMempool(
suite.logger,
suite.tobLane,
suite.baseLane,
)
// Mock keepers set up
ctrl := gomock.NewController(suite.T())
suite.accountKeeper = testutils.NewMockAccountKeeper(ctrl)
suite.accountKeeper.EXPECT().GetModuleAddress(buildertypes.ModuleName).Return(sdk.AccAddress{}).AnyTimes()
suite.bankKeeper = testutils.NewMockBankKeeper(ctrl)
suite.distrKeeper = testutils.NewMockDistributionKeeper(ctrl)
suite.stakingKeeper = testutils.NewMockStakingKeeper(ctrl)
suite.authorityAccount = sdk.AccAddress([]byte("authority"))
// Builder keeper / decorator set up
suite.builderKeeper = keeper.NewKeeper(
suite.encodingConfig.Codec,
suite.key,
suite.accountKeeper,
suite.bankKeeper,
suite.distrKeeper,
suite.stakingKeeper,
suite.authorityAccount.String(),
)
err := suite.builderKeeper.SetParams(suite.ctx, buildertypes.DefaultParams())
suite.Require().NoError(err)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.tobLane, suite.mempool)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 10)
suite.balance = sdk.NewCoin("stake", math.NewInt(1000000000000000000))
suite.nonces = make(map[string]uint64)
for _, acc := range suite.accounts {
suite.nonces[acc.Address.String()] = 0
}
// Proposal handler set up
suite.proposalHandler = abci.NewProposalHandler(
[]blockbuster.Lane{
suite.tobLane,
suite.baseLane,
},
suite.tobLane,
suite.logger,
suite.encodingConfig.TxConfig.TxEncoder(),
suite.encodingConfig.TxConfig.TxDecoder(),
abci.NoOpValidateVoteExtensionsFn(),
)
suite.voteExtensionHandler = abci.NewVoteExtensionHandler(
log.NewTestLogger(suite.T()),
suite.tobLane,
suite.encodingConfig.TxConfig.TxDecoder(),
suite.encodingConfig.TxConfig.TxEncoder(),
)
}
func (suite *ABCITestSuite) anteHandler(ctx sdk.Context, tx sdk.Tx, _ bool) (sdk.Context, error) {
suite.bankKeeper.EXPECT().GetBalance(ctx, gomock.Any(), suite.balance.Denom).AnyTimes().Return(suite.balance)
next := func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}
return suite.builderDecorator.AnteHandle(ctx, tx, false, next)
}
// fillBaseLane fills the base lane with numTxs transactions that are randomly created.
func (suite *ABCITestSuite) fillBaseLane(numTxs int) {
for i := 0; i < numTxs; i++ {
// randomly select an account to create the tx
randomIndex := suite.random.Intn(len(suite.accounts))
acc := suite.accounts[randomIndex]
// create a few random msgs and construct the tx
nonce := suite.nonces[acc.Address.String()]
randomMsgs := testutils.CreateRandomMsgs(acc.Address, 3)
tx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, acc, nonce, 1000, randomMsgs)
suite.Require().NoError(err)
// insert the tx into the lane and update the account
suite.nonces[acc.Address.String()]++
priority := suite.random.Int63n(100) + 1
suite.Require().NoError(suite.mempool.Insert(suite.ctx.WithPriority(priority), tx))
}
}
// fillTOBLane fills the TOB lane with numTxs transactions that are randomly created.
func (suite *ABCITestSuite) fillTOBLane(numTxs int, numBundledTxs int) {
// Insert a bunch of auction transactions into the global mempool and auction mempool
for i := 0; i < numTxs; i++ {
// randomly select a bidder to create the tx
randomIndex := suite.random.Intn(len(suite.accounts))
acc := suite.accounts[randomIndex]
// create a randomized auction transaction
nonce := suite.nonces[acc.Address.String()]
bidAmount := math.NewInt(int64(suite.random.Intn(1000) + 1))
bid := sdk.NewCoin("stake", bidAmount)
signers := []testutils.Account{}
for j := 0; j < numBundledTxs; j++ {
signers = append(signers, suite.accounts[0])
}
tx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, acc, bid, nonce, 1000, signers)
suite.Require().NoError(err)
// insert the auction tx into the global mempool
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
suite.nonces[acc.Address.String()]++
}
}
func (suite *ABCITestSuite) createPrepareProposalRequest(maxBytes int64) comettypes.RequestPrepareProposal {
voteExtensions := make([]comettypes.ExtendedVoteInfo, 0)
auctionIterator := suite.tobLane.Select(suite.ctx, nil)
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) 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
}
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, maxTxBytes int64) []byte {
auctionInfo := abci.AuctionInfo{
ExtendedCommitInfo: suite.createExtendedCommitInfoFromTxBzs(txs),
NumTxs: numTxs,
MaxTxBytes: maxTxBytes,
}
auctionInfoBz, err := auctionInfo.Marshal()
suite.Require().NoError(err)
return auctionInfoBz
}
func (suite *ABCITestSuite) getAuctionBidInfoFromTxBz(txBz []byte) *buildertypes.BidInfo {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(txBz)
suite.Require().NoError(err)
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
return bidInfo
}
-395
View File
@@ -1,395 +0,0 @@
// 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
View File
@@ -1,512 +0,0 @@
package abci_test
import (
"cosmossdk.io/math"
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("stake", math.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("stake", math.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("stake", math.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("stake", math.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("stake", math.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("stake", math.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("stake", math.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("stake", math.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("stake", math.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,
ReserveFee: sdk.NewCoin("stake", math.NewInt(100)),
MinBidIncrement: sdk.NewCoin("stake", math.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("stake", math.NewInt(101)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(1)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(1000)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(101)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(101)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(101)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]testutils.Account{suite.accounts[0]},
)
suite.Require().NoError(err)
bidTx2, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[1],
sdk.NewCoin("stake", math.NewInt(102)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(1000)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]testutils.Account{suite.accounts[0], suite.accounts[1]},
)
suite.Require().NoError(err)
bidTx2, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[1],
sdk.NewCoin("stake", math.NewInt(200)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(101)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(102)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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("stake", math.NewInt(101)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]testutils.Account{suite.accounts[0]},
)
suite.Require().NoError(err)
bidTx2, err := testutils.CreateAuctionTxWithSigners(
suite.encodingConfig.TxConfig,
suite.accounts[1],
sdk.NewCoin("stake", math.NewInt(102)),
0,
uint64(suite.ctx.BlockHeight())+2,
[]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.GetTotalTxBytes(), tc.maxBytes)
if winningBid == nil {
suite.Require().Len(proposal.GetTxs(), 0)
suite.Require().Equal(proposal.GetTotalTxBytes(), int64(0))
} else {
// Get info about the winning bid
winningBidBz, err := suite.encodingConfig.TxConfig.TxEncoder()(winningBid)
suite.Require().NoError(err)
auctionBidInfo, err := suite.tobLane.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.GetTxs()), len(auctionBidInfo.Transactions)+1)
// Verify that the winning bid is the first transaction in the proposal
suite.Require().Equal(proposal.GetTxs()[0], winningBidBz)
// Verify the ordering of transactions in the proposal
for index, tx := range proposal.GetTxs()[1:] {
suite.Equal(tx, auctionBidInfo.Transactions[index])
}
}
})
}
}
-212
View File
@@ -1,212 +0,0 @@
package abci
import (
"fmt"
"reflect"
"sort"
abci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/utils"
)
// 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) *blockbuster.Proposal {
// 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.
topOfBlock := blockbuster.NewProposal(maxBytes)
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, maxBytes)
if err != nil {
h.logger.Info(
"vote extension auction failed to verify auction tx",
"err", err,
)
txsToRemove[bidTx] = struct{}{}
continue
}
// 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
}
// Remove all of the transactions that were not valid.
if err := utils.RemoveTxsFromLane(txsToRemove, h.tobLane); err != nil {
h.logger.Error(
"failed to remove transactions from lane",
"err", err,
)
}
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)
}
// unmarshal 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)
}
// verify that the included vote extensions are valid in accordance with the
// the preferences of the application
cacheCtx, _ := ctx.CacheContext()
if err := h.validateVoteExtensionsFn(cacheCtx, cacheCtx.BlockHeight(), lastCommitInfo); err != nil {
return nil, fmt.Errorf("failed to validate vote extensions: %w", err)
}
// Build the top of block proposal from the auction info.
expectedTOB := h.BuildTOB(cacheCtx, 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.GetTxs()) {
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.tobLane.GetAuctionBidInfo(bidTxs[i])
if err != nil {
return false
}
bidInfoJ, err := h.tobLane.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, maxBytes int64) (*blockbuster.Proposal, error) {
proposal := blockbuster.NewProposal(maxBytes)
// cache the bytes of the bid transaction
txBz, _, err := utils.GetTxHashStr(h.txEncoder, bidTx)
if err != nil {
return proposal, err
}
// Ensure that the bid transaction is valid
if err := h.tobLane.VerifyTx(ctx, bidTx); err != nil {
return proposal, err
}
bidInfo, err := h.tobLane.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
txs := [][]byte{txBz}
// Ensure that the bundled transactions are valid
for _, rawRefTx := range bidInfo.Transactions {
// convert the bundled raw transaction to a sdk.Tx
refTx, err := h.tobLane.WrapBundleTransaction(rawRefTx)
if err != nil {
return proposal, err
}
// convert the sdk.Tx to a hash and bytes
txBz, _, err := utils.GetTxHashStr(h.txEncoder, refTx)
if err != nil {
return proposal, err
}
txs = append(txs, txBz)
}
// Add the bundled transactions to the proposal.
if err := proposal.UpdateProposal(h.tobLane, txs); err != nil {
return proposal, err
}
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.tobLane.GetAuctionBidInfo(bidTx); err != nil || bidInfo == nil {
return nil, fmt.Errorf("vote extension does not contain an auction transaction")
}
return bidTx, nil
}
-231
View File
@@ -1,231 +0,0 @@
package abci
import (
"cosmossdk.io/log"
"cosmossdk.io/math"
cometabci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/abci"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/utils"
)
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 (
// TOBLaneProposal is the interface that defines all of the dependencies that
// are required to interact with the top of block lane.
TOBLaneProposal interface {
sdkmempool.Mempool
// Factory defines the API/functionality which is responsible for determining
// if a transaction is a bid transaction and how to extract relevant
// information from the transaction (bid, timeout, bidder, etc.).
auction.Factory
// VerifyTx is utilized to verify a bid transaction according to the preferences
// of the top of block lane.
VerifyTx(ctx sdk.Context, tx sdk.Tx) error
// GetMaxBlockSpace returns the maximum block space that can be used by the top of
// block lane as a percentage of the total block space.
GetMaxBlockSpace() math.LegacyDec
// Logger returns the logger for the top of block lane.
Logger() log.Logger
// Name returns the name of the top of block lane.
Name() string
}
// ProposalHandler contains the functionality and handlers required to\
// process, validate and build blocks.
ProposalHandler struct {
logger log.Logger
txEncoder sdk.TxEncoder
txDecoder sdk.TxDecoder
// prepareLanesHandler is responsible for preparing the proposal by selecting
// transactions from each lane according to each lane's selection logic.
prepareLanesHandler blockbuster.PrepareLanesHandler
// processLanesHandler is responsible for verifying that the proposal is valid
// according to each lane's verification logic.
processLanesHandler blockbuster.ProcessLanesHandler
// tobLane is the top of block lane which is utilized to verify transactions that
// should be included in the top of block.
tobLane TOBLaneProposal
// validateVoteExtensionsFn is the function responsible for validating vote extensions.
validateVoteExtensionsFn ValidateVoteExtensionsFn
}
)
// NewProposalHandler returns a ProposalHandler that contains the functionality and handlers
// required to process, validate and build blocks.
func NewProposalHandler(
lanes []blockbuster.Lane,
tobLane TOBLaneProposal,
logger log.Logger,
txEncoder sdk.TxEncoder,
txDecoder sdk.TxDecoder,
validateVeFN ValidateVoteExtensionsFn,
) *ProposalHandler {
return &ProposalHandler{
// We prepare lanes skipping the first lane because the first lane is the top of block lane.
prepareLanesHandler: abci.ChainPrepareLanes(lanes[1:]...),
processLanesHandler: abci.ChainProcessLanes(lanes...),
tobLane: tobLane,
logger: logger,
txEncoder: txEncoder,
txDecoder: txDecoder,
validateVoteExtensionsFn: validateVeFN,
}
}
// PrepareProposalHandler returns the PrepareProposal ABCI handler that performs
// top-of-block auctioning and general block proposal construction. This handler
// will first attempt to construct the top of the block by utilizing the vote
// extensions from the previous height. If the vote extensions are not available,
// then no top of block auction is performed. After this, the rest of the proposal
// will be constructed by selecting transactions from each lane according to each
// lane's selection logic.
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req *cometabci.RequestPrepareProposal) (*cometabci.ResponsePrepareProposal, error) {
partialProposal := blockbuster.NewProposal(req.MaxTxBytes)
voteExtensionsEnabled := h.VoteExtensionsEnabled(ctx)
h.logger.Info(
"preparing proposal",
"height", req.Height,
"vote_extensions_enabled", voteExtensionsEnabled,
)
if voteExtensionsEnabled {
// Build the top of block portion of the proposal given the vote extensions
// from the previous height.
partialProposal = h.BuildTOB(ctx, req.LocalLastCommit, req.MaxTxBytes)
h.logger.Info(
"built top of block",
"num_txs", partialProposal.GetNumTxs(),
"size", partialProposal.GetTotalTxBytes(),
)
// 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 {
h.logger.Error("failed to marshal last commit info", "err", err)
return &cometabci.ResponsePrepareProposal{Txs: nil}, err
}
auctionInfo := &AuctionInfo{
ExtendedCommitInfo: lastCommitInfo,
MaxTxBytes: req.MaxTxBytes,
NumTxs: uint64(partialProposal.GetNumTxs()),
}
// Add the auction info and top of block transactions into the proposal.
auctionInfoBz, err := auctionInfo.Marshal()
if err != nil {
h.logger.Error("failed to marshal auction info", "err", err)
return &cometabci.ResponsePrepareProposal{Txs: nil}, err
}
partialProposal.AddVoteExtension(auctionInfoBz)
}
// Prepare the proposal by selecting transactions from each lane according to
// each lane's selection logic.
finalProposal, err := h.prepareLanesHandler(ctx, partialProposal)
if err != nil {
h.logger.Error("failed to prepare proposal", "err", err)
return &cometabci.ResponsePrepareProposal{Txs: nil}, err
}
h.logger.Info(
"prepared proposal",
"num_txs", finalProposal.GetNumTxs(),
"size", finalProposal.GetTotalTxBytes(),
)
return &cometabci.ResponsePrepareProposal{Txs: finalProposal.GetProposal()}, err
}
}
// ProcessProposalHandler returns the ProcessProposal ABCI handler that performs
// block proposal verification. This handler will first attempt to verify the top
// of block transactions by utilizing the vote extensions from the previous height.
// If the vote extensions are not available, then no top of block verification is done.
// After this, the rest of the proposal will be verified according to each lane's
// verification logic.
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req *cometabci.RequestProcessProposal) (*cometabci.ResponseProcessProposal, error) {
txs := req.Txs
voteExtensionsEnabled := h.VoteExtensionsEnabled(ctx)
h.logger.Info(
"processing proposal",
"height", req.Height,
"vote_extensions_enabled", voteExtensionsEnabled,
"num_txs", len(req.Txs),
)
// If vote extensions have been enabled, verify that the same top of block transactions can be
// built from the vote extensions included in the proposal. Otherwise verify that the proposal
// is valid according to each lane's verification logic.
if voteExtensionsEnabled {
auctionInfo, err := h.VerifyTOB(ctx, txs)
if err != nil {
h.logger.Error("failed to verify top of block transactions", "err", err)
return &cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, err
}
h.logger.Info(
"verified top of block",
"num_txs", auctionInfo.NumTxs,
)
txs = req.Txs[NumInjectedTxs:]
}
decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, txs)
if err != nil {
h.logger.Error("failed to decode transactions", "err", err)
return &cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, err
}
// Verify that the rest of the proposal is valid according to each lane's verification logic.
if _, err = h.processLanesHandler(ctx, decodedTxs); err != nil {
h.logger.Error("failed to process proposal", "err", err)
return &cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_REJECT}, err
}
return &cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}, nil
}
}
// VoteExtensionsEnabled determines if vote extensions are enabled for the current block.
func (h *ProposalHandler) VoteExtensionsEnabled(ctx sdk.Context) bool {
cp := ctx.ConsensusParams()
if cp.Abci == nil || cp.Abci.VoteExtensionsEnableHeight == 0 {
return false
}
// We do a > here because the vote extensions are enabled at block height H
// but will only be used at block height H+1.
return ctx.BlockHeight() > cp.Abci.VoteExtensionsEnableHeight
}
-816
View File
@@ -1,816 +0,0 @@
package abci_test
import (
"cosmossdk.io/log"
"cosmossdk.io/math"
comettypes "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/pob/abci"
"github.com/skip-mev/pob/blockbuster"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/lanes/base"
testutils "github.com/skip-mev/pob/testutils"
"github.com/skip-mev/pob/x/builder/ante"
buildertypes "github.com/skip-mev/pob/x/builder/types"
)
func (suite *ABCITestSuite) TestPrepareProposal() {
var (
// the modified transactions cannot exceed this size
maxTxBytes int64 = 1000000000000000000
// mempool configuration
normalTxs []sdk.Tx
auctionTxs []sdk.Tx
winningBidTx sdk.Tx
insertBundledTxs = false
// auction configuration
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("stake", math.NewInt(1000))
frontRunningProtection = true
)
cases := []struct {
name string
malleate func()
expectedNumberProposalTxs int
expectedMempoolDistribution map[string]int
}{
{
"single valid tob transaction in the mempool",
func() {
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = false
},
2,
map[string]int{
base.LaneName: 0,
auction.LaneName: 1,
},
},
{
"single invalid tob transaction in the mempool",
func() {
bidder := suite.accounts[0]
bid := reserveFee.Sub(sdk.NewCoin("stake", math.NewInt(1))) // bid is less than the reserve fee
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = nil
insertBundledTxs = false
},
0,
map[string]int{
base.LaneName: 0,
auction.LaneName: 0,
},
},
{
"normal transactions in the mempool",
func() {
account := suite.accounts[0]
nonce := suite.nonces[account.Address.String()]
timeout := uint64(100)
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{normalTx}
auctionTxs = []sdk.Tx{}
winningBidTx = nil
insertBundledTxs = false
},
1,
map[string]int{
base.LaneName: 1,
auction.LaneName: 0,
},
},
{
"normal transactions and tob transactions in the mempool",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid default transaction
account := suite.accounts[1]
nonce = suite.nonces[account.Address.String()] + 1
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{normalTx}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = false
},
3,
map[string]int{
base.LaneName: 1,
auction.LaneName: 1,
},
},
{
"multiple tob transactions where the first is invalid",
func() {
// Create an invalid tob transaction (frontrunning)
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder, suite.accounts[1]}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("stake", math.NewInt(1000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTx2, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx, bidTx2}
winningBidTx = bidTx2
insertBundledTxs = false
},
2,
map[string]int{
base.LaneName: 0,
auction.LaneName: 1,
},
},
{
"multiple tob transactions where the first is valid",
func() {
// Create an valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("stake", math.NewInt(1000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTx2, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx, bidTx2}
winningBidTx = bidTx
insertBundledTxs = false
frontRunningProtection = false
},
3,
map[string]int{
base.LaneName: 0,
auction.LaneName: 2,
},
},
{
"single tob transactions where the first is valid and bundle is inserted into mempool",
func() {
frontRunningProtection = false
// Create an valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], suite.accounts[1], bidder, suite.accounts[3], suite.accounts[4]}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = true
},
6,
map[string]int{
base.LaneName: 5,
auction.LaneName: 1,
},
},
{
"single tob transaction with other normal transactions in the mempool",
func() {
// Create an valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], suite.accounts[1], bidder, suite.accounts[3], suite.accounts[4]}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
account := suite.accounts[5]
nonce = suite.nonces[account.Address.String()]
timeout = uint64(100)
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
normalTxs = []sdk.Tx{normalTx}
auctionTxs = []sdk.Tx{bidTx}
winningBidTx = bidTx
insertBundledTxs = true
},
7,
map[string]int{
base.LaneName: 6,
auction.LaneName: 1,
},
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
tc.malleate()
// Insert all of the normal transactions into the default lane
for _, tx := range normalTxs {
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
// Insert all of the auction transactions into the TOB lane
for _, tx := range auctionTxs {
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
// Insert all of the bundled transactions into the mempool if desired
if insertBundledTxs {
for _, tx := range auctionTxs {
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
for _, txBz := range bidInfo.Transactions {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(txBz)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
}
}
// create a new auction
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
}
suite.builderKeeper.SetParams(suite.ctx, params)
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.tobLane, suite.mempool)
suite.proposalHandler = abci.NewProposalHandler(
[]blockbuster.Lane{
suite.tobLane,
suite.baseLane,
},
suite.tobLane,
suite.logger,
suite.encodingConfig.TxConfig.TxEncoder(),
suite.encodingConfig.TxConfig.TxDecoder(),
abci.NoOpValidateVoteExtensionsFn(),
)
handler := suite.proposalHandler.PrepareProposalHandler()
req := suite.createPrepareProposalRequest(maxTxBytes)
res, _ := handler(suite.ctx, &req)
// -------------------- Check Invariants -------------------- //
// The first slot in the proposal must be the auction info (if vote extensions are enabled)
auctionInfo := abci.AuctionInfo{}
err := auctionInfo.Unmarshal(res.Txs[abci.AuctionInfoIndex])
suite.Require().NoError(err)
// Total bytes must be less than or equal to maxTxBytes
totalBytes := int64(0)
for _, tx := range res.Txs[abci.NumInjectedTxs:] {
totalBytes += int64(len(tx))
}
suite.Require().LessOrEqual(totalBytes, maxTxBytes)
// 2. the number of transactions in the response must be equal to the number of expected transactions
// NOTE: We add 1 to the expected number of transactions because the first transaction in the response
// is the auction transaction
suite.Require().Equal(tc.expectedNumberProposalTxs+1, len(res.Txs))
// 3. 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 winningBidTx != nil {
auctionTx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[1])
suite.Require().NoError(err)
bidInfo, err := suite.tobLane.GetAuctionBidInfo(auctionTx)
suite.Require().NoError(err)
for index, tx := range bidInfo.Transactions {
suite.Require().Equal(tx, res.Txs[index+1+abci.NumInjectedTxs])
}
} else if len(res.Txs) > 1 {
tx, err := suite.encodingConfig.TxConfig.TxDecoder()(res.Txs[1])
suite.Require().NoError(err)
bidInfo, err := suite.tobLane.GetAuctionBidInfo(tx)
suite.Require().NoError(err)
suite.Require().Nil(bidInfo)
}
// 4. All of the transactions must be unique
uniqueTxs := make(map[string]bool)
for _, tx := range res.Txs {
suite.Require().False(uniqueTxs[string(tx)])
uniqueTxs[string(tx)] = true
}
// 5. The number of transactions in the mempool must be correct
suite.Require().Equal(tc.expectedMempoolDistribution, suite.mempool.GetTxDistribution())
})
}
}
func (suite *ABCITestSuite) TestPrepareProposalPreVoteExtensions() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Insert the bid transaction into the mempool
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx))
account := suite.accounts[5]
nonce = suite.nonces[account.Address.String()]
timeout = uint64(100)
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTx(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
// Insert the normal transaction into the mempool
suite.Require().NoError(suite.mempool.Insert(suite.ctx, normalTx))
handler := suite.proposalHandler.PrepareProposalHandler()
req := suite.createPrepareProposalRequest(1000000000000)
suite.ctx = suite.ctx.WithBlockHeight(0)
res, _ := handler(suite.ctx, &req)
suite.Require().Equal(1, len(res.Txs))
}
func (suite *ABCITestSuite) TestProcessProposal() {
var (
// auction configuration
maxBundleSize uint32 = 10
reserveFee = sdk.NewCoin("stake", math.NewInt(1000))
frontRunningProtection = true
maxTxBytes int64 = 1000000000000000000
// mempool configuration
proposal [][]byte
)
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: frontRunningProtection,
}
suite.builderKeeper.SetParams(suite.ctx, params)
cases := []struct {
name string
createTxs func()
response comettypes.ResponseProcessProposal_ProposalStatus
}{
{
"no transactions in mempool with no vote extension info",
func() {
proposal = nil
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"no transactions in mempool with empty vote extension info",
func() {
proposal = [][]byte{}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single normal tx, no vote extension info",
func() {
account := suite.accounts[0]
nonce := suite.nonces[account.Address.String()]
timeout := uint64(100)
numberMsgs := uint64(3)
normalTxBz, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
proposal = [][]byte{normalTxBz}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, single auction tx, no vote extension info",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid default transaction
account := suite.accounts[1]
nonce = suite.nonces[account.Address.String()] + 1
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
proposal = [][]byte{bidTx, normalTx}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs (no unwrapping)",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTx, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create a valid default transaction
account := suite.accounts[1]
nonce = suite.nonces[account.Address.String()] + 1
numberMsgs := uint64(3)
normalTx, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, account, nonce, numberMsgs, timeout)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTx}, 2, maxTxBytes)
proposal = [][]byte{
auctionInfo,
bidTx,
normalTx,
}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx with ref txs (with unwrapping)",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 2, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"single auction tx with ref txs but misplaced in proposal",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[1], bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 3, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = [][]byte{
auctionInfo,
bidTxBz,
bidInfo.Transactions[1],
bidInfo.Transactions[0],
}
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, but auction tx is not valid",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, suite.accounts[1]} // front-running
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 3, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs but wrong auction tx is at top of block",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create another valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("stake", math.NewInt(1000000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz, bidTxBz2}, 3, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs and correct auction tx is selected",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create another valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("stake", math.NewInt(1000000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz, bidTxBz2}, 2, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz2)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz2,
},
bidInfo.Transactions...,
)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"multiple auction txs included in block",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder, bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
// Create another valid tob transaction
bidder = suite.accounts[1]
bid = sdk.NewCoin("stake", math.NewInt(1000000))
nonce = suite.nonces[bidder.Address.String()]
timeout = uint64(100)
signers = []testutils.Account{bidder}
bidTxBz2, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz, bidTxBz2}, 2, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz2)
bidInfo2 := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz2,
},
bidInfo.Transactions...,
)
proposal = append(proposal, bidTxBz)
proposal = append(proposal, bidInfo2.Transactions...)
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"single auction tx, but rest of the mempool is invalid",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 2, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
proposal = append(proposal, []byte("invalid tx"))
},
comettypes.ResponseProcessProposal_REJECT,
},
{
"multiple auction txs with ref txs + normal transactions",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(1000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{bidder}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, 2, maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
normalTxBz, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[1], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
normalTxBz, err = testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[2], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
{
"front-running protection disabled",
func() {
// Create a valid tob transaction
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(10000000))
nonce := suite.nonces[bidder.Address.String()]
timeout := uint64(100)
signers := []testutils.Account{suite.accounts[2], suite.accounts[1], bidder, suite.accounts[3], suite.accounts[4]}
bidTxBz, err := testutils.CreateAuctionTxWithSignerBz(suite.encodingConfig.TxConfig, bidder, bid, nonce, timeout, signers)
suite.Require().NoError(err)
auctionInfo := suite.createAuctionInfoFromTxBzs([][]byte{bidTxBz}, uint64(len(signers)+1), maxTxBytes)
bidInfo := suite.getAuctionBidInfoFromTxBz(bidTxBz)
proposal = append(
[][]byte{
auctionInfo,
bidTxBz,
},
bidInfo.Transactions...,
)
normalTxBz, err := testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[5], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
normalTxBz, err = testutils.CreateRandomTxBz(suite.encodingConfig.TxConfig, suite.accounts[6], nonce, 3, timeout)
suite.Require().NoError(err)
proposal = append(proposal, normalTxBz)
// disable frontrunning protection
params := buildertypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
FrontRunningProtection: false,
}
suite.builderKeeper.SetParams(suite.ctx, params)
},
comettypes.ResponseProcessProposal_ACCEPT,
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
// suite.SetupTest() // reset
suite.builderDecorator = ante.NewBuilderDecorator(suite.builderKeeper, suite.encodingConfig.TxConfig.TxEncoder(), suite.tobLane, suite.mempool)
// reset the proposal handler with the new mempool
suite.proposalHandler = abci.NewProposalHandler(
[]blockbuster.Lane{
suite.tobLane,
suite.baseLane,
},
suite.tobLane, log.NewTestLogger(suite.T()),
suite.encodingConfig.TxConfig.TxEncoder(),
suite.encodingConfig.TxConfig.TxDecoder(),
abci.NoOpValidateVoteExtensionsFn(),
)
tc.createTxs()
handler := suite.proposalHandler.ProcessProposalHandler()
res, _ := handler(suite.ctx, &comettypes.RequestProcessProposal{
Txs: proposal,
})
// Check if the response is valid
suite.Require().Equal(tc.response, res.Status)
})
}
}
-21
View File
@@ -1,21 +0,0 @@
package abci
import (
cometabci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// ValidateVoteExtensionsFn defines the function for validating vote extensions. This
// function is not explicitly used to validate the oracle data but rather that
// the signed vote extensions included in the proposal are valid and provide
// a supermajority of vote extensions for the current block. This method is
// expected to be used in ProcessProposal, the expected ctx is the ProcessProposalState's ctx.
type ValidateVoteExtensionsFn func(ctx sdk.Context, currentHeight int64, extendedCommitInfo cometabci.ExtendedCommitInfo) error
// NoOpValidateVoteExtensionsFn returns a ValidateVoteExtensionsFn that does nothing. This should NOT
// be used in production.
func NoOpValidateVoteExtensionsFn() ValidateVoteExtensionsFn {
return func(_ sdk.Context, _ int64, _ cometabci.ExtendedCommitInfo) error {
return nil
}
}
-225
View File
@@ -1,225 +0,0 @@
package abci
import (
"crypto/sha256"
"encoding/hex"
"cosmossdk.io/log"
cometabci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/skip-mev/pob/blockbuster/lanes/auction"
"github.com/skip-mev/pob/blockbuster/utils"
)
type (
// TOBLaneVE contains the methods required by the VoteExtensionHandler
// to interact with the local mempool i.e. the top of block lane.
TOBLaneVE interface {
sdkmempool.Mempool
// Factory defines the API/functionality which is responsible for determining
// if a transaction is a bid transaction and how to extract relevant
// information from the transaction (bid, timeout, bidder, etc.).
auction.Factory
// VerifyTx is utilized to verify a bid transaction according to the preferences
// of the top of block lane.
VerifyTx(ctx sdk.Context, tx sdk.Tx) error
}
// VoteExtensionHandler contains the functionality and handlers required to
// process, validate and build vote extensions.
VoteExtensionHandler struct {
logger log.Logger
// tobLane is the top of block lane which is used to extract the top bidding
// auction transaction from the local mempool.
tobLane TOBLaneVE
// txDecoder is used to decode the top bidding auction transaction
txDecoder sdk.TxDecoder
// txEncoder is used to encode the top bidding auction transaction
txEncoder sdk.TxEncoder
// cache is used to store the results of the vote extension verification
// for a given block height.
cache map[string]error
// currentHeight is the block height the cache is valid for.
currentHeight int64
}
)
// NewVoteExtensionHandler returns an VoteExtensionHandler that contains the functionality and handlers
// required to inject, process, and validate vote extensions.
func NewVoteExtensionHandler(logger log.Logger, lane TOBLaneVE, txDecoder sdk.TxDecoder, txEncoder sdk.TxEncoder) *VoteExtensionHandler {
return &VoteExtensionHandler{
logger: logger,
tobLane: lane,
txDecoder: txDecoder,
txEncoder: txEncoder,
cache: make(map[string]error),
currentHeight: 0,
}
}
// ExtendVoteHandler returns the ExtendVoteHandler ABCI handler that extracts
// the top bidding valid auction transaction from a validator's local mempool and
// returns it in its vote extension.
func (h *VoteExtensionHandler) ExtendVoteHandler() sdk.ExtendVoteHandler {
return func(ctx sdk.Context, req *cometabci.RequestExtendVote) (*cometabci.ResponseExtendVote, error) {
// Iterate through auction bids until we find a valid one
auctionIterator := h.tobLane.Select(ctx, nil)
txsToRemove := make(map[sdk.Tx]struct{}, 0)
defer func() {
if err := utils.RemoveTxsFromLane(txsToRemove, h.tobLane); err != nil {
h.logger.Info(
"failed to remove transactions from lane",
"err", err,
)
}
}()
for ; auctionIterator != nil; auctionIterator = auctionIterator.Next() {
bidTx := auctionIterator.Tx()
// Verify the bid tx can be encoded and included in vote extension
bidTxBz, hash, err := utils.GetTxHashStr(h.txEncoder, bidTx)
if err != nil {
h.logger.Info(
"failed to get hash of auction bid tx",
"err", err,
)
txsToRemove[bidTx] = struct{}{}
continue
}
// Validate the auction transaction against a cache state
cacheCtx, _ := ctx.CacheContext()
if err := h.tobLane.VerifyTx(cacheCtx, bidTx); err != nil {
h.logger.Info(
"failed to verify auction bid tx",
"tx_hash", hash,
"err", err,
)
txsToRemove[bidTx] = struct{}{}
continue
}
h.logger.Info("extending vote with auction transaction", "tx_hash", hash)
return &cometabci.ResponseExtendVote{VoteExtension: bidTxBz}, nil
}
h.logger.Info(
"extending vote with no auction transaction",
"height", ctx.BlockHeight(),
)
return &cometabci.ResponseExtendVote{VoteExtension: []byte{}}, nil
}
}
// VerifyVoteExtensionHandler returns the VerifyVoteExtensionHandler ABCI handler
// that verifies the vote extension included in RequestVerifyVoteExtension.
// In particular, it verifies that the vote extension is a valid auction transaction.
func (h *VoteExtensionHandler) VerifyVoteExtensionHandler() sdk.VerifyVoteExtensionHandler {
return func(ctx sdk.Context, req *cometabci.RequestVerifyVoteExtension) (*cometabci.ResponseVerifyVoteExtension, error) {
txBz := req.VoteExtension
if len(txBz) == 0 {
h.logger.Info(
"verified vote extension with no auction transaction",
"height", ctx.BlockHeight(),
)
return &cometabci.ResponseVerifyVoteExtension{Status: cometabci.ResponseVerifyVoteExtension_ACCEPT}, nil
}
// Reset the cache if necessary
h.resetCache(ctx.BlockHeight())
hashBz := sha256.Sum256(txBz)
hash := hex.EncodeToString(hashBz[:])
// Short circuit if we have already verified this vote extension
if err, ok := h.cache[hash]; ok {
if err != nil {
h.logger.Info(
"rejected vote extension",
"tx_hash", hash,
"height", ctx.BlockHeight(),
)
return &cometabci.ResponseVerifyVoteExtension{Status: cometabci.ResponseVerifyVoteExtension_REJECT}, err
}
h.logger.Info(
"verified vote extension",
"tx_hash", hash,
"height", ctx.BlockHeight(),
)
return &cometabci.ResponseVerifyVoteExtension{Status: cometabci.ResponseVerifyVoteExtension_ACCEPT}, nil
}
// Decode the vote extension which should be a valid auction transaction
bidTx, err := h.txDecoder(txBz)
if err != nil {
h.logger.Info(
"rejected vote extension",
"tx_hash", hash,
"height", ctx.BlockHeight(),
"err", err,
)
h.cache[hash] = err
return &cometabci.ResponseVerifyVoteExtension{Status: cometabci.ResponseVerifyVoteExtension_REJECT}, err
}
// Verify the auction transaction and cache the result
if err = h.tobLane.VerifyTx(ctx, bidTx); err != nil {
h.logger.Info(
"rejected vote extension",
"tx_hash", hash,
"height", ctx.BlockHeight(),
"err", err,
)
if err := h.tobLane.Remove(bidTx); err != nil {
h.logger.Info(
"failed to remove auction transaction from lane",
"tx_hash", hash,
"height", ctx.BlockHeight(),
"err", err,
)
}
h.cache[hash] = err
return &cometabci.ResponseVerifyVoteExtension{Status: cometabci.ResponseVerifyVoteExtension_REJECT}, err
}
h.cache[hash] = nil
h.logger.Info(
"verified vote extension",
"tx_hash", hash,
"height", ctx.BlockHeight(),
)
return &cometabci.ResponseVerifyVoteExtension{Status: cometabci.ResponseVerifyVoteExtension_ACCEPT}, nil
}
}
// checkStaleCache checks if the current height differs than the previous height at which
// the vote extensions were verified in. If so, it resets the cache to allow transactions to be
// reverified.
func (h *VoteExtensionHandler) resetCache(blockHeight int64) {
if h.currentHeight != blockHeight {
h.cache = make(map[string]error)
h.currentHeight = blockHeight
}
}
-346
View File
@@ -1,346 +0,0 @@
package abci_test
import (
"cosmossdk.io/log"
"cosmossdk.io/math"
cometabci "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"
"github.com/skip-mev/pob/x/builder/types"
)
func (suite *ABCITestSuite) TestExtendVoteExtensionHandler() {
params := types.Params{
MaxBundleSize: 5,
ReserveFee: sdk.NewCoin("stake", math.NewInt(10)),
FrontRunningProtection: true,
}
testCases := []struct {
name string
getExpectedVE func() []byte
}{
{
"empty mempool",
func() []byte {
return []byte{}
},
},
{
"filled mempool with no auction transactions",
func() []byte {
suite.fillBaseLane(10)
return []byte{}
},
},
{
"mempool with invalid auction transaction (too many bundled transactions)",
func() []byte {
suite.fillTOBLane(3, int(params.MaxBundleSize)+1)
return []byte{}
},
},
{
"mempool with invalid auction transaction (invalid bid)",
func() []byte {
bidder := suite.accounts[0]
bid := params.ReserveFee.Sub(sdk.NewCoin("stake", math.NewInt(1)))
signers := []testutils.Account{bidder}
timeout := 1
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx))
// this should return nothing since the top bid is not valid
return []byte{}
},
},
{
"mempool contains only invalid auction bids (bid is too low)",
func() []byte {
params.ReserveFee = sdk.NewCoin("stake", math.NewInt(10000000000000000))
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
// this way all of the bids will be too small
suite.fillTOBLane(4, 1)
return []byte{}
},
},
{
"mempool contains bid that has an invalid timeout",
func() []byte {
bidder := suite.accounts[0]
bid := params.ReserveFee
signers := []testutils.Account{bidder}
timeout := 0
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.Require().NoError(suite.tobLane.Insert(suite.ctx, bidTx))
// this should return nothing since the top bid is not valid
return []byte{}
},
},
{
"top bid is invalid but next best is valid",
func() []byte {
params.ReserveFee = sdk.NewCoin("stake", math.NewInt(10))
bidder := suite.accounts[0]
bid := params.ReserveFee.Add(params.ReserveFee)
signers := []testutils.Account{bidder}
timeout := 0
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx))
bidder = suite.accounts[1]
bid = params.ReserveFee
signers = []testutils.Account{bidder}
timeout = 100
bidTx2, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
suite.Require().NoError(suite.mempool.Insert(suite.ctx, bidTx2))
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(bidTx2)
suite.Require().NoError(err)
return bz
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
expectedVE := tc.getExpectedVE()
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
// Reset the handler with the new mempool
suite.voteExtensionHandler = abci.NewVoteExtensionHandler(
log.NewTestLogger(suite.T()),
suite.tobLane,
suite.encodingConfig.TxConfig.TxDecoder(),
suite.encodingConfig.TxConfig.TxEncoder(),
)
handler := suite.voteExtensionHandler.ExtendVoteHandler()
resp, err := handler(suite.ctx, nil)
suite.Require().NoError(err)
suite.Require().Equal(expectedVE, resp.VoteExtension)
})
}
}
func (suite *ABCITestSuite) TestVerifyVoteExtensionHandler() {
params := types.Params{
MaxBundleSize: 5,
ReserveFee: sdk.NewCoin("stake", math.NewInt(100)),
FrontRunningProtection: true,
}
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
testCases := []struct {
name string
req func() *cometabci.RequestVerifyVoteExtension
expectedErr bool
}{
{
"invalid vote extension bytes",
func() *cometabci.RequestVerifyVoteExtension {
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: []byte("invalid vote extension"),
}
},
true,
},
{
"empty vote extension bytes",
func() *cometabci.RequestVerifyVoteExtension {
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: []byte{},
}
},
false,
},
{
"nil vote extension bytes",
func() *cometabci.RequestVerifyVoteExtension {
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: nil,
}
},
false,
},
{
"invalid extension with bid tx with bad timeout",
func() *cometabci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(10))
signers := []testutils.Account{bidder}
timeout := 0
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
true,
},
{
"invalid vote extension with bid tx with bad bid",
func() *cometabci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := sdk.NewCoin("stake", math.NewInt(0))
signers := []testutils.Account{bidder}
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
true,
},
{
"valid vote extension",
func() *cometabci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
signers := []testutils.Account{bidder}
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
false,
},
{
"invalid vote extension with front running bid tx",
func() *cometabci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
timeout := 10
bundlee := testutils.RandomAccounts(suite.random, 1)[0]
signers := []testutils.Account{bidder, bundlee}
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
true,
},
{
"invalid vote extension with too many bundle txs",
func() *cometabci.RequestVerifyVoteExtension {
// disable front running protection
params.FrontRunningProtection = false
err := suite.builderKeeper.SetParams(suite.ctx, params)
suite.Require().NoError(err)
bidder := suite.accounts[0]
bid := params.ReserveFee
signers := testutils.RandomAccounts(suite.random, int(params.MaxBundleSize)+1)
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
true,
},
{
"invalid vote extension with a failing bundle tx",
func() *cometabci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
msgAuctionBid, err := testutils.CreateMsgAuctionBid(suite.encodingConfig.TxConfig, bidder, bid, 0, 0)
suite.Require().NoError(err)
// Create a failing tx
msgAuctionBid.Transactions = [][]byte{{0x01}}
bidTx, err := testutils.CreateTx(suite.encodingConfig.TxConfig, suite.accounts[0], 0, 1, []sdk.Msg{msgAuctionBid})
suite.Require().NoError(err)
bz, err := suite.encodingConfig.TxConfig.TxEncoder()(bidTx)
suite.Require().NoError(err)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
true,
},
{
"valid vote extension + no comparison to local mempool",
func() *cometabci.RequestVerifyVoteExtension {
bidder := suite.accounts[0]
bid := params.ReserveFee
signers := []testutils.Account{bidder}
timeout := 10
bz := suite.createAuctionTxBz(bidder, bid, signers, timeout)
// Add a bid to the mempool that is greater than the one in the vote extension
bid = bid.Add(params.ReserveFee)
bidTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 10, 1, signers)
suite.Require().NoError(err)
err = suite.mempool.Insert(suite.ctx, bidTx)
suite.Require().NoError(err)
tx := suite.tobLane.GetTopAuctionTx(suite.ctx)
suite.Require().NotNil(tx)
return &cometabci.RequestVerifyVoteExtension{
VoteExtension: bz,
}
},
false,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
req := tc.req()
handler := suite.voteExtensionHandler.VerifyVoteExtensionHandler()
_, err := handler(suite.ctx, req)
if tc.expectedErr {
suite.Require().Error(err)
} else {
suite.Require().NoError(err)
}
})
}
}
func (suite *ABCITestSuite) createAuctionTxBz(bidder testutils.Account, bid sdk.Coin, signers []testutils.Account, timeout int) []byte {
auctionTx, err := testutils.CreateAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, uint64(timeout), signers)
suite.Require().NoError(err)
txBz, err := suite.encodingConfig.TxConfig.TxEncoder()(auctionTx)
suite.Require().NoError(err)
return txBz
}