feat(ITS): [ENG-1627]: Moving ITS from release branch to main (#236)
Co-authored-by: Nikhil <nikhil@skip.money>
This commit is contained in:
parent
14f83b5e25
commit
1ed1adc856
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -64,4 +64,4 @@ jobs:
|
||||
- name: Test E2E
|
||||
if: env.GIT_DIFF
|
||||
run: |
|
||||
make test-e2e
|
||||
make test-integration
|
||||
|
||||
37
Makefile
37
Makefile
@ -85,29 +85,46 @@ build-and-start-app: build-test-app
|
||||
|
||||
.PHONY: build-test-app build-and-start-app
|
||||
|
||||
###############################################################################
|
||||
## Workspaces ##
|
||||
###############################################################################
|
||||
|
||||
use-main:
|
||||
go work edit -use .
|
||||
go work edit -dropuse ./tests/integration
|
||||
|
||||
use-integration:
|
||||
go work edit -dropuse .
|
||||
go work edit -use ./tests/integration
|
||||
|
||||
.PHONY: docker-build docker-build-integration
|
||||
###############################################################################
|
||||
## Docker ##
|
||||
###############################################################################
|
||||
|
||||
docker-build:
|
||||
docker-build: use-main
|
||||
@echo "Building E2E Docker image..."
|
||||
@DOCKER_BUILDKIT=1 docker build -t skip-mev/pob-e2e -f contrib/images/pob.e2e.Dockerfile .
|
||||
|
||||
docker-build-integration: use-main
|
||||
@echo "Building integration-test Docker image..."
|
||||
@DOCKER_BUILDKIT=1 docker build -t pob-integration -f contrib/images/pob.integration.Dockerfile .
|
||||
|
||||
###############################################################################
|
||||
### Tests ###
|
||||
###############################################################################
|
||||
|
||||
TEST_E2E_TAGS = e2e
|
||||
TEST_E2E_DEPS = docker-build
|
||||
TEST_INTEGRATION_DEPS = docker-build-integration use-integration
|
||||
TEST_INTEGRATION_TAGS = integration
|
||||
|
||||
test-e2e: $(TEST_E2E_DEPS)
|
||||
@echo "Running E2E tests..."
|
||||
@go test ./tests/e2e/... -mod=readonly -timeout 30m -race -v -tags='$(TEST_E2E_TAGS)'
|
||||
test-integration: $(TEST_INTEGRATION_DEPS)
|
||||
@ echo "Running integration tests..."
|
||||
@go test ./tests/integration/pob_integration_test.go -timeout 30m -race -v -tags='$(TEST_INTEGRATION_TAGS)'
|
||||
|
||||
test:
|
||||
test: use-main
|
||||
@go test -v -race $(shell go list ./... | grep -v tests/)
|
||||
|
||||
.PHONY: test test-e2e
|
||||
.PHONY: test test-integration
|
||||
|
||||
###############################################################################
|
||||
### Protobuf ###
|
||||
@ -149,12 +166,12 @@ proto-update-deps:
|
||||
golangci_lint_cmd=golangci-lint
|
||||
golangci_version=v1.51.2
|
||||
|
||||
lint:
|
||||
lint: use-main
|
||||
@echo "--> Running linter"
|
||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version)
|
||||
@golangci-lint run
|
||||
|
||||
lint-fix:
|
||||
lint-fix: use-main
|
||||
@echo "--> Running linter"
|
||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version)
|
||||
@golangci-lint run --fix
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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")
|
||||
)
|
||||
@ -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])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -1,17 +1,14 @@
|
||||
FROM golang:1.20-bullseye AS builder
|
||||
|
||||
WORKDIR /src/pob
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
WORKDIR /src/pob
|
||||
COPY . .
|
||||
|
||||
RUN go mod tidy
|
||||
RUN make build-test-app
|
||||
|
||||
## Prepare the final clear binary
|
||||
FROM ubuntu:rolling
|
||||
EXPOSE 26656 26657 1317 9090 7171
|
||||
ENTRYPOINT ["testappd", "start"]
|
||||
|
||||
COPY --from=builder /src/pob/build/* /usr/local/bin/
|
||||
RUN apt-get update && apt-get install ca-certificates -y
|
||||
14
go.work.sum
Normal file
14
go.work.sum
Normal file
@ -0,0 +1,14 @@
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8=
|
||||
github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
|
||||
github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32 h1:zlCp9n3uwQieELltZWHRmwPmPaZ8+XoL2Sj+A2YJlr8=
|
||||
github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -18,7 +18,6 @@ import (
|
||||
feegrantkeeper "cosmossdk.io/x/feegrant/keeper"
|
||||
feegrantmodule "cosmossdk.io/x/feegrant/module"
|
||||
cometabci "github.com/cometbft/cometbft/abci/types"
|
||||
tmtypes "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
@ -62,7 +61,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
|
||||
veabci "github.com/skip-mev/pob/abci"
|
||||
"github.com/skip-mev/pob/blockbuster"
|
||||
"github.com/skip-mev/pob/blockbuster/abci"
|
||||
"github.com/skip-mev/pob/blockbuster/lanes/auction"
|
||||
@ -340,27 +338,14 @@ func New(
|
||||
app.App.SetAnteHandler(anteHandler)
|
||||
|
||||
// Set the proposal handlers on base app
|
||||
proposalHandler := veabci.NewProposalHandler(
|
||||
lanes,
|
||||
tobLane,
|
||||
proposalHandler := abci.NewProposalHandler(
|
||||
app.Logger(),
|
||||
app.txConfig.TxEncoder(),
|
||||
app.txConfig.TxDecoder(),
|
||||
veabci.NoOpValidateVoteExtensionsFn(),
|
||||
app.TxConfig().TxDecoder(),
|
||||
mempool,
|
||||
)
|
||||
app.App.SetPrepareProposal(proposalHandler.PrepareProposalHandler())
|
||||
app.App.SetProcessProposal(proposalHandler.ProcessProposalHandler())
|
||||
|
||||
// Set the vote extension handler on the app.
|
||||
voteExtensionHandler := veabci.NewVoteExtensionHandler(
|
||||
app.Logger(),
|
||||
tobLane,
|
||||
app.txConfig.TxDecoder(),
|
||||
app.txConfig.TxEncoder(),
|
||||
)
|
||||
app.App.SetExtendVoteHandler(voteExtensionHandler.ExtendVoteHandler())
|
||||
app.App.SetVerifyVoteExtensionHandler(voteExtensionHandler.VerifyVoteExtensionHandler())
|
||||
|
||||
// Set the custom CheckTx handler on BaseApp.
|
||||
checkTxHandler := abci.NewCheckTxHandler(
|
||||
app.App,
|
||||
@ -415,31 +400,6 @@ func (app *TestApp) SetCheckTx(handler abci.CheckTx) {
|
||||
app.checkTxHandler = handler
|
||||
}
|
||||
|
||||
// TODO: remove this once we have a proper config file
|
||||
func (app *TestApp) InitChain(req *cometabci.RequestInitChain) (*cometabci.ResponseInitChain, error) {
|
||||
req.ConsensusParams.Abci.VoteExtensionsEnableHeight = 2
|
||||
resp, err := app.App.InitChain(req)
|
||||
if resp == nil {
|
||||
resp = &cometabci.ResponseInitChain{}
|
||||
}
|
||||
resp.ConsensusParams = &tmtypes.ConsensusParams{
|
||||
Abci: &tmtypes.ABCIParams{
|
||||
VoteExtensionsEnableHeight: 2,
|
||||
},
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// TODO: remove this once we have a proper config file
|
||||
func (app *TestApp) FinalizeBlock(req *cometabci.RequestFinalizeBlock) (*cometabci.ResponseFinalizeBlock, error) {
|
||||
resp, err := app.App.FinalizeBlock(req)
|
||||
if resp != nil {
|
||||
resp.ConsensusParamUpdates = nil
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Name returns the name of the App
|
||||
func (app *TestApp) Name() string { return app.BaseApp.Name() }
|
||||
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
dbm "github.com/cosmos/cosmos-db"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
"github.com/skip-mev/pob/tests/app"
|
||||
"github.com/skip-mev/pob/tests/app/params"
|
||||
)
|
||||
|
||||
const (
|
||||
keyringPassphrase = "testpassphrase"
|
||||
keyringAppName = "testnet"
|
||||
)
|
||||
|
||||
var (
|
||||
encodingConfig params.EncodingConfig
|
||||
cdc codec.Codec
|
||||
)
|
||||
|
||||
func init() {
|
||||
testApp := app.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(app.DefaultNodeHome))
|
||||
encodingConfig = params.EncodingConfig{
|
||||
InterfaceRegistry: testApp.InterfaceRegistry(),
|
||||
Codec: testApp.AppCodec(),
|
||||
TxConfig: testApp.TxConfig(),
|
||||
Amino: testApp.LegacyAmino(),
|
||||
}
|
||||
cdc = encodingConfig.Codec
|
||||
}
|
||||
|
||||
type chain struct {
|
||||
dataDir string
|
||||
id string
|
||||
validators []*validator
|
||||
}
|
||||
|
||||
func newChain() (*chain, error) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpDir, err := os.MkdirTemp(pwd, ".pob-e2e-testnet-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chain{
|
||||
id: app.ChainID,
|
||||
dataDir: tmpDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *chain) configDir() string {
|
||||
return fmt.Sprintf("%s/%s", c.dataDir, c.id)
|
||||
}
|
||||
|
||||
func (c *chain) createAndInitValidators(count int) error {
|
||||
for i := 0; i < count; i++ {
|
||||
node := c.createValidator(i)
|
||||
|
||||
// generate genesis files
|
||||
if err := node.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.validators = append(c.validators, node)
|
||||
|
||||
// create keys
|
||||
if err := node.createKey("val"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := node.createNodeKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := node.createConsensusKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chain) createValidator(index int) *validator {
|
||||
return &validator{
|
||||
chain: c,
|
||||
index: index,
|
||||
moniker: "testapp",
|
||||
}
|
||||
}
|
||||
@ -1,323 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
cometcfg "github.com/cometbft/cometbft/config"
|
||||
cometjson "github.com/cometbft/cometbft/libs/json"
|
||||
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/skip-mev/pob/tests/app"
|
||||
"github.com/skip-mev/pob/x/builder/types"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var (
|
||||
numValidators = 4
|
||||
minGasPrice = sdk.NewDecCoinFromDec(app.BondDenom, math.LegacyMustNewDecFromStr("0.02")).String()
|
||||
initBalanceStr = sdk.NewInt64Coin(app.BondDenom, 1000000000000000000).String()
|
||||
stakeAmount = math.NewInt(100000000000)
|
||||
stakeAmountCoin = sdk.NewCoin(app.BondDenom, stakeAmount)
|
||||
)
|
||||
|
||||
type (
|
||||
TestAccount struct {
|
||||
PrivateKey *secp256k1.PrivKey
|
||||
Address sdk.AccAddress
|
||||
}
|
||||
|
||||
IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
tmpDirs []string
|
||||
chain *chain
|
||||
dkrPool *dockertest.Pool
|
||||
dkrNet *dockertest.Network
|
||||
valResources []*dockertest.Resource
|
||||
}
|
||||
)
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(IntegrationTestSuite))
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
s.T().Log("setting up e2e integration test suite...")
|
||||
|
||||
var err error
|
||||
s.chain, err = newChain()
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.T().Logf("starting e2e infrastructure; chain-id: %s; datadir: %s", s.chain.id, s.chain.dataDir)
|
||||
|
||||
s.dkrPool, err = dockertest.NewPool("")
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.dkrNet, err = s.dkrPool.CreateNetwork(fmt.Sprintf("%s-testnet", s.chain.id))
|
||||
s.Require().NoError(err)
|
||||
|
||||
// The bootstrapping phase is as follows:
|
||||
//
|
||||
// 1. Initialize TestApp validator nodes.
|
||||
// 2. Create and initialize TestApp validator genesis files, i.e. setting
|
||||
// delegate keys for validators.
|
||||
// 3. Start TestApp network.
|
||||
s.initNodes()
|
||||
s.initGenesis()
|
||||
s.initValidatorConfigs()
|
||||
s.runValidators()
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
if str := os.Getenv("POB_E2E_SKIP_CLEANUP"); len(str) > 0 {
|
||||
skipCleanup, err := strconv.ParseBool(str)
|
||||
s.Require().NoError(err)
|
||||
|
||||
if skipCleanup {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.T().Log("tearing down e2e integration test suite...")
|
||||
|
||||
for _, vc := range s.valResources {
|
||||
s.Require().NoError(s.dkrPool.Purge(vc))
|
||||
}
|
||||
|
||||
s.Require().NoError(s.dkrPool.RemoveNetwork(s.dkrNet))
|
||||
|
||||
os.RemoveAll(s.chain.dataDir)
|
||||
for _, td := range s.tmpDirs {
|
||||
os.RemoveAll(td)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) initNodes() {
|
||||
s.Require().NoError(s.chain.createAndInitValidators(numValidators))
|
||||
|
||||
// initialize a genesis file for the first validator
|
||||
val0ConfigDir := s.chain.validators[0].configDir()
|
||||
|
||||
// Define the builder module parameters
|
||||
params := types.Params{
|
||||
MaxBundleSize: 5,
|
||||
EscrowAccountAddress: sdk.MustAccAddressFromBech32("cosmos14j5j2lsx7629590jvpk3vj0xe9w8203jf4yknk").Bytes(),
|
||||
ReserveFee: sdk.NewCoin(app.BondDenom, math.NewInt(1000000)),
|
||||
MinBidIncrement: sdk.NewCoin(app.BondDenom, math.NewInt(1000000)),
|
||||
ProposerFee: math.LegacyMustNewDecFromStr("0.1"),
|
||||
FrontRunningProtection: true,
|
||||
}
|
||||
|
||||
for _, val := range s.chain.validators {
|
||||
valAddr, err := val.keyInfo.GetAddress()
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(initGenesisFile(val0ConfigDir, "", initBalanceStr, valAddr, params))
|
||||
}
|
||||
|
||||
// copy the genesis file to the remaining validators
|
||||
for _, val := range s.chain.validators[1:] {
|
||||
_, err := copyFile(
|
||||
filepath.Join(val0ConfigDir, "config", "genesis.json"),
|
||||
filepath.Join(val.configDir(), "config", "genesis.json"),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) initGenesis() {
|
||||
serverCtx := server.NewDefaultContext()
|
||||
config := serverCtx.Config
|
||||
|
||||
config.SetRoot(s.chain.validators[0].configDir())
|
||||
config.Moniker = s.chain.validators[0].moniker
|
||||
|
||||
genFilePath := config.GenesisFile()
|
||||
appGenState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFilePath)
|
||||
s.T().Log("starting e2e infrastructure; validator_0 config:", genFilePath)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// x/gov
|
||||
var govGenState govtypesv1.GenesisState
|
||||
s.Require().NoError(cdc.UnmarshalJSON(appGenState[govtypes.ModuleName], &govGenState))
|
||||
|
||||
votingPeriod := 5 * time.Second
|
||||
govGenState.Params.VotingPeriod = &votingPeriod
|
||||
govGenState.Params.MinDeposit = sdk.NewCoins(sdk.NewCoin(app.BondDenom, math.NewInt(100)))
|
||||
|
||||
bz, err := cdc.MarshalJSON(&govGenState)
|
||||
s.Require().NoError(err)
|
||||
appGenState[govtypes.ModuleName] = bz
|
||||
|
||||
var genUtilGenState genutiltypes.GenesisState
|
||||
s.Require().NoError(cdc.UnmarshalJSON(appGenState[genutiltypes.ModuleName], &genUtilGenState))
|
||||
|
||||
// x/genutil genesis txs
|
||||
genTxs := make([]json.RawMessage, len(s.chain.validators))
|
||||
for i, val := range s.chain.validators {
|
||||
createValMsg, err := val.buildCreateValidatorMsg(stakeAmountCoin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
signedTx, err := val.signMsg(createValMsg)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txRaw, err := cdc.MarshalJSON(signedTx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
genTxs[i] = txRaw
|
||||
}
|
||||
|
||||
genUtilGenState.GenTxs = genTxs
|
||||
|
||||
bz, err = cdc.MarshalJSON(&genUtilGenState)
|
||||
s.Require().NoError(err)
|
||||
appGenState[genutiltypes.ModuleName] = bz
|
||||
|
||||
bz, err = json.MarshalIndent(appGenState, "", " ")
|
||||
s.Require().NoError(err)
|
||||
|
||||
genDoc.AppState = bz
|
||||
|
||||
bz, err = cometjson.MarshalIndent(genDoc, "", " ")
|
||||
s.Require().NoError(err)
|
||||
|
||||
// write the updated genesis file to each validator
|
||||
for _, val := range s.chain.validators {
|
||||
writeFile(filepath.Join(val.configDir(), "config", "genesis.json"), bz)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) initValidatorConfigs() {
|
||||
for i, val := range s.chain.validators {
|
||||
tmCfgPath := filepath.Join(val.configDir(), "config", "config.toml")
|
||||
|
||||
vpr := viper.New()
|
||||
vpr.SetConfigFile(tmCfgPath)
|
||||
s.Require().NoError(vpr.ReadInConfig())
|
||||
|
||||
valConfig := cometcfg.DefaultConfig()
|
||||
s.Require().NoError(vpr.Unmarshal(valConfig))
|
||||
|
||||
valConfig.P2P.ListenAddress = "tcp://0.0.0.0:26656"
|
||||
valConfig.P2P.AddrBookStrict = false
|
||||
valConfig.P2P.ExternalAddress = fmt.Sprintf("%s:%d", val.instanceName(), 26656)
|
||||
valConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657"
|
||||
valConfig.StateSync.Enable = false
|
||||
valConfig.LogLevel = "info"
|
||||
valConfig.BaseConfig.Genesis = filepath.Join("config", "genesis.json")
|
||||
valConfig.RootDir = filepath.Join("root", ".simapp")
|
||||
valConfig.Consensus.TimeoutCommit = 2 * time.Second
|
||||
|
||||
var peers []string
|
||||
|
||||
for j := 0; j < len(s.chain.validators); j++ {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
|
||||
peer := s.chain.validators[j]
|
||||
peerID := fmt.Sprintf("%s@%s%d:26656", peer.nodeKey.ID(), peer.moniker, j)
|
||||
peers = append(peers, peerID)
|
||||
}
|
||||
|
||||
valConfig.P2P.PersistentPeers = strings.Join(peers, ",")
|
||||
cometcfg.WriteConfigFile(tmCfgPath, valConfig)
|
||||
|
||||
// set application configuration
|
||||
appCfgPath := filepath.Join(val.configDir(), "config", "app.toml")
|
||||
appConfig := srvconfig.DefaultConfig()
|
||||
appConfig.API.Enable = true
|
||||
appConfig.MinGasPrices = minGasPrice
|
||||
appConfig.API.Address = "tcp://0.0.0.0:1317"
|
||||
appConfig.GRPC.Address = "0.0.0.0:9090"
|
||||
|
||||
srvconfig.WriteConfigFile(appCfgPath, appConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) runValidators() {
|
||||
s.T().Log("starting POB TestApp validator containers...")
|
||||
|
||||
s.valResources = make([]*dockertest.Resource, len(s.chain.validators))
|
||||
for i, val := range s.chain.validators {
|
||||
runOpts := &dockertest.RunOptions{
|
||||
Name: val.instanceName(),
|
||||
NetworkID: s.dkrNet.Network.ID,
|
||||
Mounts: []string{
|
||||
fmt.Sprintf("%s/:/root/.testapp", val.configDir()),
|
||||
},
|
||||
Repository: "docker.io/skip-mev/pob-e2e",
|
||||
}
|
||||
|
||||
// expose the first validator for debugging and communication
|
||||
if val.index == 0 {
|
||||
runOpts.PortBindings = map[docker.Port][]docker.PortBinding{
|
||||
"1317/tcp": {{HostIP: "", HostPort: "1317"}},
|
||||
"6060/tcp": {{HostIP: "", HostPort: "6060"}},
|
||||
"6061/tcp": {{HostIP: "", HostPort: "6061"}},
|
||||
"6062/tcp": {{HostIP: "", HostPort: "6062"}},
|
||||
"6063/tcp": {{HostIP: "", HostPort: "6063"}},
|
||||
"6064/tcp": {{HostIP: "", HostPort: "6064"}},
|
||||
"6065/tcp": {{HostIP: "", HostPort: "6065"}},
|
||||
"9090/tcp": {{HostIP: "", HostPort: "9090"}},
|
||||
"26656/tcp": {{HostIP: "", HostPort: "26656"}},
|
||||
"26657/tcp": {{HostIP: "", HostPort: "26657"}},
|
||||
}
|
||||
}
|
||||
|
||||
resource, err := s.dkrPool.RunWithOptions(runOpts, noRestart)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.valResources[i] = resource
|
||||
s.T().Logf("started POB TestApp validator container: %s", resource.Container.ID)
|
||||
}
|
||||
|
||||
rpcClient, err := rpchttp.New("tcp://localhost:26657", "/websocket")
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.Require().Eventually(
|
||||
func() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
status, err := rpcClient.Status(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// let the node produce a few blocks
|
||||
if status.SyncInfo.CatchingUp || status.SyncInfo.LatestBlockHeight < 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
2*time.Minute,
|
||||
time.Second,
|
||||
"POB TestApp node failed to produce blocks",
|
||||
)
|
||||
}
|
||||
|
||||
func noRestart(config *docker.HostConfig) {
|
||||
// in this case we don't want the nodes to restart on failure
|
||||
config.RestartPolicy = docker.RestartPolicy{
|
||||
Name: "no",
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,180 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/skip-mev/pob/tests/app"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
)
|
||||
|
||||
// execMsgSendTx executes a send transaction on the given validator given the provided
|
||||
// recipient and amount. This function returns the transaction hash. It does not wait for the
|
||||
// transaction to be committed.
|
||||
func (s *IntegrationTestSuite) execMsgSendTx(valIdx int, to sdk.AccAddress, amount sdk.Coin) string {
|
||||
address, err := s.chain.validators[valIdx].keyInfo.GetAddress()
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.T().Logf(
|
||||
"sending %s from %s to %s",
|
||||
amount, address, to,
|
||||
)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
exec, err := s.dkrPool.Client.CreateExec(docker.CreateExecOptions{
|
||||
Context: ctx,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Container: s.valResources[valIdx].Container.ID,
|
||||
User: "root",
|
||||
Cmd: []string{
|
||||
"testappd",
|
||||
"tx",
|
||||
"bank",
|
||||
"send",
|
||||
address.String(), // sender
|
||||
to.String(), // receiver
|
||||
amount.String(), // amount
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFrom, s.chain.validators[valIdx].keyInfo.Name),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagChainID, s.chain.id),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoin(app.BondDenom, math.NewInt(1000000000)).String()),
|
||||
"--keyring-backend=test",
|
||||
"--broadcast-mode=sync",
|
||||
"-y",
|
||||
},
|
||||
})
|
||||
s.Require().NoError(err)
|
||||
|
||||
var (
|
||||
outBuf bytes.Buffer
|
||||
errBuf bytes.Buffer
|
||||
)
|
||||
|
||||
err = s.dkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{
|
||||
Context: ctx,
|
||||
Detach: false,
|
||||
OutputStream: &outBuf,
|
||||
ErrorStream: &errBuf,
|
||||
})
|
||||
s.Require().NoErrorf(err, "stdout: %s, stderr: %s", outBuf.String(), errBuf.String())
|
||||
|
||||
output := outBuf.String()
|
||||
resp := strings.Split(output, ":")
|
||||
txHash := strings.TrimSpace(resp[len(resp)-1])
|
||||
|
||||
return txHash
|
||||
}
|
||||
|
||||
// createAuctionBidTx creates a transaction that bids on an auction given the provided bidder, bid, and transactions.
|
||||
func (s *IntegrationTestSuite) createAuctionBidTx(account TestAccount, bid sdk.Coin, transactions [][]byte, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte {
|
||||
msgs := []sdk.Msg{
|
||||
&buildertypes.MsgAuctionBid{
|
||||
Bidder: account.Address.String(),
|
||||
Bid: bid,
|
||||
Transactions: transactions,
|
||||
},
|
||||
}
|
||||
|
||||
return s.createTx(account, msgs, sequenceOffset, height, gasLimit, fees)
|
||||
}
|
||||
|
||||
// createMsgSendTx creates a send transaction given the provided signer, recipient, amount, sequence number offset, and block height timeout.
|
||||
// This function is primarily used to create bundles of transactions.
|
||||
func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress string, amount sdk.Coins, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte {
|
||||
msgs := []sdk.Msg{
|
||||
&banktypes.MsgSend{
|
||||
FromAddress: account.Address.String(),
|
||||
ToAddress: toAddress,
|
||||
Amount: amount,
|
||||
},
|
||||
}
|
||||
|
||||
return s.createTx(account, msgs, sequenceOffset, height, gasLimit, fees)
|
||||
}
|
||||
|
||||
// createMsgDelegateTx creates a delegate transaction given the provided signer, validator, amount, sequence number offset
|
||||
// and block height timeout.
|
||||
func (s *IntegrationTestSuite) createMsgDelegateTx(account TestAccount, validator string, amount sdk.Coin, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte {
|
||||
msgs := []sdk.Msg{
|
||||
&stakingtypes.MsgDelegate{
|
||||
DelegatorAddress: account.Address.String(),
|
||||
ValidatorAddress: validator,
|
||||
Amount: amount,
|
||||
},
|
||||
}
|
||||
|
||||
return s.createTx(account, msgs, sequenceOffset, height, gasLimit, fees)
|
||||
}
|
||||
|
||||
// createTx creates a transaction given the provided messages, sequence number offset, and block height timeout.
|
||||
func (s *IntegrationTestSuite) createTx(account TestAccount, msgs []sdk.Msg, sequenceOffset, height, gasLimit uint64, fees sdk.Coins) []byte {
|
||||
txConfig := encodingConfig.TxConfig
|
||||
txBuilder := txConfig.NewTxBuilder()
|
||||
|
||||
// Get account info of the sender to set the account number and sequence number
|
||||
baseAccount := s.queryAccount(account.Address)
|
||||
sequenceNumber := baseAccount.Sequence + sequenceOffset
|
||||
|
||||
s.Require().NoError(txBuilder.SetMsgs(msgs...))
|
||||
txBuilder.SetFeeAmount(fees)
|
||||
txBuilder.SetGasLimit(gasLimit)
|
||||
txBuilder.SetTimeoutHeight(height)
|
||||
|
||||
signerData := authsigning.SignerData{
|
||||
ChainID: app.ChainID,
|
||||
AccountNumber: baseAccount.AccountNumber,
|
||||
Sequence: sequenceNumber,
|
||||
PubKey: account.PrivateKey.PubKey(),
|
||||
}
|
||||
|
||||
sig := signing.SignatureV2{
|
||||
PubKey: account.PrivateKey.PubKey(),
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: nil,
|
||||
},
|
||||
Sequence: sequenceNumber,
|
||||
}
|
||||
|
||||
s.Require().NoError(txBuilder.SetSignatures(sig))
|
||||
|
||||
bytesToSign, err := authsigning.GetSignBytesAdapter(
|
||||
context.Background(),
|
||||
encodingConfig.TxConfig.SignModeHandler(),
|
||||
signing.SignMode_SIGN_MODE_DIRECT,
|
||||
signerData,
|
||||
txBuilder.GetTx(),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
sigBytes, err := account.PrivateKey.Sign(bytesToSign)
|
||||
s.Require().NoError(err)
|
||||
|
||||
sig = signing.SignatureV2{
|
||||
PubKey: account.PrivateKey.PubKey(),
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: sigBytes,
|
||||
},
|
||||
Sequence: sequenceNumber,
|
||||
}
|
||||
s.Require().NoError(txBuilder.SetSignatures(sig))
|
||||
|
||||
signedTx := txBuilder.GetTx()
|
||||
bz, err := encodingConfig.TxConfig.TxEncoder()(signedTx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return bz
|
||||
}
|
||||
@ -1,401 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
cmtclient "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
// createClientContext creates a client.Context for use in integration tests.
|
||||
// Note, it assumes all queries and broadcasts go to the first node.
|
||||
func (s *IntegrationTestSuite) createClientContext() client.Context {
|
||||
node := s.valResources[0]
|
||||
|
||||
rpcURI := node.GetHostPort("26657/tcp")
|
||||
gRPCURI := node.GetHostPort("9090/tcp")
|
||||
|
||||
rpcClient, err := client.NewClientFromNode(rpcURI)
|
||||
s.Require().NoError(err)
|
||||
|
||||
grpcClient, err := grpc.Dial(gRPCURI, []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}...)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return client.Context{}.
|
||||
WithNodeURI(rpcURI).
|
||||
WithClient(rpcClient).
|
||||
WithGRPCClient(grpcClient).
|
||||
WithInterfaceRegistry(encodingConfig.InterfaceRegistry).
|
||||
WithCodec(encodingConfig.Codec).
|
||||
WithChainID(s.chain.id).
|
||||
WithBroadcastMode(flags.BroadcastSync)
|
||||
}
|
||||
|
||||
// createTestAccounts creates and funds test accounts with a balance.
|
||||
func (s *IntegrationTestSuite) createTestAccounts(numAccounts int, balance sdk.Coin) []TestAccount {
|
||||
accounts := make([]TestAccount, numAccounts)
|
||||
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
// Generate a new account with private key that will be used to sign transactions.
|
||||
privKey := secp256k1.GenPrivKey()
|
||||
pubKey := privKey.PubKey()
|
||||
addr := sdk.AccAddress(pubKey.Address())
|
||||
|
||||
account := TestAccount{
|
||||
PrivateKey: privKey,
|
||||
Address: addr,
|
||||
}
|
||||
|
||||
// Fund the account.
|
||||
s.execMsgSendTx(0, account.Address, balance)
|
||||
|
||||
// Wait for the balance to be updated.
|
||||
s.Require().Eventually(func() bool {
|
||||
return !s.queryBalancesOf(addr.String()).IsZero()
|
||||
},
|
||||
10*time.Second,
|
||||
1*time.Second,
|
||||
)
|
||||
|
||||
accounts[i] = account
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
// calculateProposerEscrowSplit calculates the amount of a bid that should go to the escrow account
|
||||
// and the amount that should go to the proposer. The simulation e2e environment does not support
|
||||
// checking the proposer's balance, it only validates that the escrow address has the correct balance.
|
||||
func (s *IntegrationTestSuite) calculateProposerEscrowSplit(bid sdk.Coin) sdk.Coin {
|
||||
// Get the params to determine the proposer fee.
|
||||
params := s.queryBuilderParams()
|
||||
proposerFee := params.ProposerFee
|
||||
|
||||
var proposerReward sdk.Coins
|
||||
if proposerFee.IsZero() {
|
||||
// send the entire bid to the escrow account when no proposer fee is set
|
||||
return bid
|
||||
}
|
||||
|
||||
// determine the amount of the bid that goes to the (previous) proposer
|
||||
bidDec := sdk.NewDecCoinsFromCoins(bid)
|
||||
proposerReward, _ = bidDec.MulDecTruncate(proposerFee).TruncateDecimal()
|
||||
|
||||
// Determine the amount of the remaining bid that goes to the escrow account.
|
||||
// If a decimal remainder exists, it'll stay with the bidding account.
|
||||
escrowTotal := bidDec.Sub(sdk.NewDecCoinsFromCoins(proposerReward...))
|
||||
escrowReward, _ := escrowTotal.TruncateDecimal()
|
||||
|
||||
return sdk.NewCoin(bid.Denom, escrowReward.AmountOf(bid.Denom))
|
||||
}
|
||||
|
||||
// waitForABlock will wait until the current block height has increased by a single block.
|
||||
func (s *IntegrationTestSuite) waitForABlock() {
|
||||
height := s.queryCurrentHeight()
|
||||
s.Require().Eventually(
|
||||
func() bool {
|
||||
return s.queryCurrentHeight() >= height+1
|
||||
},
|
||||
10*time.Second,
|
||||
50*time.Millisecond,
|
||||
)
|
||||
}
|
||||
|
||||
// waitForNBlocks will wait until the current block height has increased by n blocks.
|
||||
func (s *IntegrationTestSuite) waitForNBlocks(n int) {
|
||||
height := s.queryCurrentHeight()
|
||||
s.Require().Eventually(
|
||||
func() bool {
|
||||
return s.queryCurrentHeight() >= height+uint64(n)
|
||||
},
|
||||
10*time.Second,
|
||||
50*time.Millisecond,
|
||||
)
|
||||
}
|
||||
|
||||
// bundleToTxHashes converts a bundle to a slice of transaction hashes.
|
||||
func (s *IntegrationTestSuite) bundleToTxHashes(bidTx []byte, bundle [][]byte) []string {
|
||||
hashes := make([]string, len(bundle)+1)
|
||||
|
||||
// encode the bid transaction into a hash
|
||||
hashBz := sha256.Sum256(bidTx)
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
hashes[0] = hash
|
||||
|
||||
for i, hash := range s.normalTxsToTxHashes(bundle) {
|
||||
hashes[i+1] = hash
|
||||
}
|
||||
|
||||
return hashes
|
||||
}
|
||||
|
||||
// normalTxsToTxHashes converts a slice of normal transactions to a slice of transaction hashes.
|
||||
func (s *IntegrationTestSuite) normalTxsToTxHashes(txs [][]byte) []string {
|
||||
hashes := make([]string, len(txs))
|
||||
|
||||
for i, tx := range txs {
|
||||
hashBz := sha256.Sum256(tx)
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
hashes[i] = hash
|
||||
}
|
||||
|
||||
return hashes
|
||||
}
|
||||
|
||||
// verifyTopOfBlockAuction verifies that blocks that include a bid transaction execute as expected.
|
||||
func (s *IntegrationTestSuite) verifyTopOfBlockAuction(height uint64, bundle []string, expectedExecution map[string]bool) {
|
||||
s.waitForABlock()
|
||||
s.T().Logf("Verifying block %d", height)
|
||||
|
||||
// Get the block's transactions and display the expected and actual block for debugging.
|
||||
txs := s.queryBlockTxs(height)
|
||||
s.displayBlock(txs, bundle)
|
||||
|
||||
// Ensure that all transactions executed as expected (i.e. landed or failed to land).
|
||||
for tx, landed := range expectedExecution {
|
||||
s.T().Logf("Verifying tx %s executed as %t", tx, landed)
|
||||
s.Require().Equal(landed, s.queryTxPassed(tx) == nil)
|
||||
}
|
||||
s.T().Logf("All txs executed as expected")
|
||||
|
||||
// Check that the block contains the expected transactions in the expected order
|
||||
// iff the bid transaction was expected to execute.
|
||||
if len(bundle) > 0 && len(expectedExecution) > 0 && expectedExecution[bundle[0]] && len(txs) > 0 {
|
||||
if expectedExecution[bundle[0]] {
|
||||
hashBz := sha256.Sum256(txs[0])
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
s.Require().Equal(strings.ToUpper(bundle[0]), strings.ToUpper(hash))
|
||||
|
||||
for index, bundleTx := range bundle[1:] {
|
||||
hashBz := sha256.Sum256(txs[index+1])
|
||||
txHash := hex.EncodeToString(hashBz[:])
|
||||
|
||||
s.Require().Equal(strings.ToUpper(bundleTx), strings.ToUpper(txHash))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyBlock verifies that the transactions in the block at the given height were seen
|
||||
// and executed in the order they were submitted.
|
||||
func (s *IntegrationTestSuite) verifyBlock(height uint64, txs []string, expectedExecution map[string]bool) {
|
||||
s.waitForABlock()
|
||||
s.T().Logf("Verifying block %d", height)
|
||||
|
||||
// Get the block's transactions and display the expected and actual block for debugging.
|
||||
blockTxs := s.queryBlockTxs(height)
|
||||
s.displayBlock(blockTxs, txs)
|
||||
|
||||
// Ensure that all transactions executed as expected (i.e. landed or failed to land).
|
||||
for tx, landed := range expectedExecution {
|
||||
s.T().Logf("Verifying tx %s executed as %t", tx, landed)
|
||||
s.Require().Equal(landed, s.queryTxPassed(tx) == nil)
|
||||
}
|
||||
s.T().Logf("All txs executed as expected")
|
||||
|
||||
// Check that the block contains the expected transactions in the expected order.
|
||||
s.Require().Equal(len(txs), len(blockTxs))
|
||||
|
||||
hashBlockTxs := s.normalTxsToTxHashes(blockTxs)
|
||||
for index, tx := range txs {
|
||||
s.Require().Equal(strings.ToUpper(tx), strings.ToUpper(hashBlockTxs[index]))
|
||||
}
|
||||
|
||||
s.T().Logf("Block %d contains the expected transactions in the expected order", height)
|
||||
}
|
||||
|
||||
// displayExpectedBlock displays the expected and actual blocks.
|
||||
func (s *IntegrationTestSuite) displayBlock(txs [][]byte, expectedTxs []string) {
|
||||
if len(expectedTxs) != 0 {
|
||||
expectedBlock := fmt.Sprintf("Expected block:\n\t(%d, %s)\n", 0, expectedTxs[0])
|
||||
for index, expectedTx := range expectedTxs[1:] {
|
||||
expectedBlock += fmt.Sprintf("\t(%d, %s)\n", index+1, expectedTx)
|
||||
}
|
||||
|
||||
s.T().Logf(expectedBlock)
|
||||
}
|
||||
|
||||
// Display the actual block.
|
||||
if len(txs) == 0 {
|
||||
s.T().Logf("Actual block is empty")
|
||||
return
|
||||
}
|
||||
|
||||
hashBz := sha256.Sum256(txs[0])
|
||||
hash := hex.EncodeToString(hashBz[:])
|
||||
actualBlock := fmt.Sprintf("Actual block:\n\t(%d, %s)\n", 0, hash)
|
||||
for index, tx := range txs[1:] {
|
||||
hashBz := sha256.Sum256(tx)
|
||||
txHash := hex.EncodeToString(hashBz[:])
|
||||
|
||||
actualBlock += fmt.Sprintf("\t(%d, %s)\n", index+1, txHash)
|
||||
}
|
||||
|
||||
s.T().Logf(actualBlock)
|
||||
}
|
||||
|
||||
// displayExpectedBundle displays the expected order of the bid and bundled transactions.
|
||||
func (s *IntegrationTestSuite) displayExpectedBundle(prefix string, bidTx []byte, bundle [][]byte) {
|
||||
// encode the bid transaction into a hash
|
||||
hashes := s.bundleToTxHashes(bidTx, bundle)
|
||||
|
||||
expectedBundle := fmt.Sprintf("%s expected bundle:\n\t(%d, %s)\n", prefix, 0, hashes[0])
|
||||
for index, bundleTx := range hashes[1:] {
|
||||
expectedBundle += fmt.Sprintf("\t(%d, %s)\n", index+1, bundleTx)
|
||||
}
|
||||
|
||||
s.T().Logf(expectedBundle)
|
||||
}
|
||||
|
||||
// broadcastTx broadcasts a transaction to the network using the given validator.
|
||||
func (s *IntegrationTestSuite) broadcastTx(tx []byte, valIdx int) {
|
||||
node := s.valResources[valIdx]
|
||||
gRPCURI := node.GetHostPort("9090/tcp")
|
||||
|
||||
grpcConn, err := grpc.Dial(
|
||||
gRPCURI,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
client := txtypes.NewServiceClient(grpcConn)
|
||||
|
||||
req := &txtypes.BroadcastTxRequest{TxBytes: tx, Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC}
|
||||
client.BroadcastTx(context.Background(), req)
|
||||
}
|
||||
|
||||
// queryTx queries a transaction by its hash and returns whether there was an
|
||||
// error in including the transaction in a block.
|
||||
func (s *IntegrationTestSuite) queryTxPassed(txHash string) error {
|
||||
queryClient := txtypes.NewServiceClient(s.createClientContext())
|
||||
|
||||
req := &txtypes.GetTxRequest{Hash: txHash}
|
||||
resp, err := queryClient.GetTx(context.Background(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.TxResponse.Code != 0 {
|
||||
return fmt.Errorf("tx failed: %s", resp.TxResponse.RawLog)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// queryBuilderParams returns the params of the builder module.
|
||||
func (s *IntegrationTestSuite) queryBuilderParams() buildertypes.Params {
|
||||
queryClient := buildertypes.NewQueryClient(s.createClientContext())
|
||||
|
||||
req := &buildertypes.QueryParamsRequest{}
|
||||
resp, err := queryClient.Params(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return resp.Params
|
||||
}
|
||||
|
||||
// queryBalancesOf returns the balances of an account.
|
||||
func (s *IntegrationTestSuite) queryBalancesOf(address string) sdk.Coins {
|
||||
queryClient := banktypes.NewQueryClient(s.createClientContext())
|
||||
|
||||
req := &banktypes.QueryAllBalancesRequest{Address: address}
|
||||
resp, err := queryClient.AllBalances(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return resp.Balances
|
||||
}
|
||||
|
||||
// queryBalanceOf returns the balance of an account for a specific denom.
|
||||
func (s *IntegrationTestSuite) queryBalanceOf(address string, denom string) sdk.Coin {
|
||||
queryClient := banktypes.NewQueryClient(s.createClientContext())
|
||||
|
||||
req := &banktypes.QueryBalanceRequest{Address: address, Denom: denom}
|
||||
resp, err := queryClient.Balance(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return *resp.Balance
|
||||
}
|
||||
|
||||
// queryAccount returns the account of an address.
|
||||
func (s *IntegrationTestSuite) queryAccount(address sdk.AccAddress) *authtypes.BaseAccount {
|
||||
queryClient := authtypes.NewQueryClient(s.createClientContext())
|
||||
|
||||
req := &authtypes.QueryAccountRequest{Address: address.String()}
|
||||
resp, err := queryClient.Account(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
account := &authtypes.BaseAccount{}
|
||||
err = account.Unmarshal(resp.Account.Value)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
// queryCurrentHeight returns the current block height.
|
||||
func (s *IntegrationTestSuite) queryCurrentHeight() uint64 {
|
||||
queryClient := cmtclient.NewServiceClient(s.createClientContext())
|
||||
|
||||
req := &cmtclient.GetLatestBlockRequest{}
|
||||
resp, err := queryClient.GetLatestBlock(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return uint64(resp.SdkBlock.Header.Height)
|
||||
}
|
||||
|
||||
// queryBlockTxs returns the txs of the block at the given height.
|
||||
func (s *IntegrationTestSuite) queryBlockTxs(height uint64) [][]byte {
|
||||
queryClient := cmtclient.NewServiceClient(s.createClientContext())
|
||||
|
||||
req := &cmtclient.GetBlockByHeightRequest{Height: int64(height)}
|
||||
resp, err := queryClient.GetBlockByHeight(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txs := resp.GetSdkBlock().Data.Txs
|
||||
|
||||
// The first transaction is the vote extension.
|
||||
s.Require().Greater(len(txs), 0)
|
||||
|
||||
return txs[1:]
|
||||
}
|
||||
|
||||
// queryTx returns information about a transaction.
|
||||
func (s *IntegrationTestSuite) queryTx(txHash string) *txtypes.GetTxResponse {
|
||||
queryClient := txtypes.NewServiceClient(s.createClientContext())
|
||||
|
||||
req := &txtypes.GetTxRequest{Hash: txHash}
|
||||
resp, err := queryClient.GetTx(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// queryTxExecutionHeight returns the block height at which a transaction was executed.
|
||||
func (s *IntegrationTestSuite) queryTxExecutionHeight(txHash string) uint64 {
|
||||
txResp := s.queryTx(txHash)
|
||||
return uint64(txResp.TxResponse.Height)
|
||||
}
|
||||
|
||||
// queryValidators returns the validators of the network.
|
||||
func (s *IntegrationTestSuite) queryValidators() []stakingtypes.Validator {
|
||||
queryClient := stakingtypes.NewQueryClient(s.createClientContext())
|
||||
|
||||
req := &stakingtypes.QueryValidatorsRequest{}
|
||||
resp, err := queryClient.Validators(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return resp.Validators
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
comettypes "github.com/cometbft/cometbft/types"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
"github.com/skip-mev/pob/x/builder/types"
|
||||
)
|
||||
|
||||
func getGenDoc(path string) (*comettypes.GenesisDoc, error) {
|
||||
serverCtx := server.NewDefaultContext()
|
||||
config := serverCtx.Config
|
||||
config.SetRoot(path)
|
||||
|
||||
genFile := config.GenesisFile()
|
||||
doc := &comettypes.GenesisDoc{}
|
||||
|
||||
if _, err := os.Stat(genFile); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
|
||||
doc, err = comettypes.GenesisDocFromFile(genFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read genesis doc from file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func initGenesisFile(path, moniker, amountStr string, accAddr sdk.AccAddress, params types.Params) error {
|
||||
serverCtx := server.NewDefaultContext()
|
||||
config := serverCtx.Config
|
||||
|
||||
config.SetRoot(path)
|
||||
config.Moniker = moniker
|
||||
|
||||
coins, err := sdk.ParseCoinsNormalized(amountStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse coins: %w", err)
|
||||
}
|
||||
|
||||
balances := banktypes.Balance{Address: accAddr.String(), Coins: coins.Sort()}
|
||||
genAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0)
|
||||
|
||||
genFile := config.GenesisFile()
|
||||
appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal genesis state: %w", err)
|
||||
}
|
||||
|
||||
authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState)
|
||||
|
||||
accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get accounts from any: %w", err)
|
||||
}
|
||||
|
||||
if accs.Contains(accAddr) {
|
||||
return fmt.Errorf("failed to add account to genesis state; account already exists: %s", accAddr)
|
||||
}
|
||||
|
||||
// Add the new account to the set of genesis accounts and sanitize the
|
||||
// accounts afterwards.
|
||||
accs = append(accs, genAccount)
|
||||
accs = authtypes.SanitizeGenesisAccounts(accs)
|
||||
|
||||
genAccs, err := authtypes.PackAccounts(accs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert accounts into any's: %w", err)
|
||||
}
|
||||
|
||||
authGenState.Accounts = genAccs
|
||||
|
||||
authGenStateBz, err := cdc.MarshalJSON(&authGenState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal auth genesis state: %w", err)
|
||||
}
|
||||
|
||||
appState[authtypes.ModuleName] = authGenStateBz
|
||||
|
||||
bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState)
|
||||
bankGenState.Balances = append(bankGenState.Balances, balances)
|
||||
bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances)
|
||||
|
||||
bankGenStateBz, err := cdc.MarshalJSON(bankGenState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal bank genesis state: %w", err)
|
||||
}
|
||||
|
||||
appState[banktypes.ModuleName] = bankGenStateBz
|
||||
|
||||
builderGenState := types.GetGenesisStateFromAppState(cdc, appState)
|
||||
builderGenState.Params = params
|
||||
|
||||
builderGenStateBz, err := cdc.MarshalJSON(&builderGenState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal builder genesis state: %w", err)
|
||||
}
|
||||
|
||||
appState[types.ModuleName] = builderGenStateBz
|
||||
|
||||
appStateJSON, err := json.Marshal(appState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal application genesis state: %w", err)
|
||||
}
|
||||
|
||||
genDoc.AppState = appStateJSON
|
||||
return genutil.ExportGenesisFile(genDoc, genFile)
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func copyFile(src, dst string) (int64, error) {
|
||||
sourceFileStat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !sourceFileStat.Mode().IsRegular() {
|
||||
return 0, fmt.Errorf("%s is not a regular file", src)
|
||||
}
|
||||
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
destination, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer destination.Close()
|
||||
|
||||
nBytes, err := io.Copy(destination, source)
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
func writeFile(path string, body []byte) error {
|
||||
_, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, body, 0o600)
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"github.com/cosmos/go-bip39"
|
||||
)
|
||||
|
||||
func createMnemonic() (string, error) {
|
||||
entropySeed, err := bip39.NewEntropy(256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mnemonic, err := bip39.NewMnemonic(entropySeed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return mnemonic, nil
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/unknownproto"
|
||||
sdktx "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
)
|
||||
|
||||
func decodeTx(txBytes []byte) (*sdktx.Tx, error) {
|
||||
var raw sdktx.TxRaw
|
||||
|
||||
// reject all unknown proto fields in the root TxRaw
|
||||
err := unknownproto.RejectUnknownFieldsStrict(txBytes, &raw, encodingConfig.InterfaceRegistry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to reject unknown fields: %w", err)
|
||||
}
|
||||
|
||||
if err := cdc.Unmarshal(txBytes, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body sdktx.TxBody
|
||||
if err := cdc.Unmarshal(raw.BodyBytes, &body); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode tx: %w", err)
|
||||
}
|
||||
|
||||
var authInfo sdktx.AuthInfo
|
||||
|
||||
// reject all unknown proto fields in AuthInfo
|
||||
err = unknownproto.RejectUnknownFieldsStrict(raw.AuthInfoBytes, &authInfo, encodingConfig.InterfaceRegistry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to reject unknown fields: %w", err)
|
||||
}
|
||||
|
||||
if err := cdc.Unmarshal(raw.AuthInfoBytes, &authInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode auth info: %w", err)
|
||||
}
|
||||
|
||||
return &sdktx.Tx{
|
||||
Body: &body,
|
||||
AuthInfo: &authInfo,
|
||||
Signatures: raw.Signatures,
|
||||
}, nil
|
||||
}
|
||||
@ -1,292 +0,0 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
cometcfg "github.com/cometbft/cometbft/config"
|
||||
"github.com/cometbft/cometbft/p2p"
|
||||
"github.com/cometbft/cometbft/privval"
|
||||
sdkcrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdktx "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
genutilstypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/skip-mev/pob/tests/app"
|
||||
)
|
||||
|
||||
type validator struct {
|
||||
chain *chain
|
||||
index int
|
||||
moniker string
|
||||
mnemonic string
|
||||
keyInfo keyring.Record
|
||||
privateKey cryptotypes.PrivKey
|
||||
consensusKey privval.FilePVKey
|
||||
nodeKey p2p.NodeKey
|
||||
}
|
||||
|
||||
func (v *validator) instanceName() string {
|
||||
return fmt.Sprintf("%s%d", v.moniker, v.index)
|
||||
}
|
||||
|
||||
func (v *validator) configDir() string {
|
||||
return fmt.Sprintf("%s/%s", v.chain.configDir(), v.instanceName())
|
||||
}
|
||||
|
||||
func (v *validator) createConfig() error {
|
||||
p := path.Join(v.configDir(), "config")
|
||||
return os.MkdirAll(p, 0o755)
|
||||
}
|
||||
|
||||
func (v *validator) init() error {
|
||||
if err := v.createConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serverCtx := server.NewDefaultContext()
|
||||
config := serverCtx.Config
|
||||
|
||||
config.SetRoot(v.configDir())
|
||||
config.Moniker = v.moniker
|
||||
|
||||
genDoc, err := getGenDoc(v.configDir())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appState, err := json.MarshalIndent(app.ModuleBasics.DefaultGenesis(cdc), "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to JSON encode app genesis state: %w", err)
|
||||
}
|
||||
|
||||
genDoc.ChainID = v.chain.id
|
||||
genDoc.Validators = nil
|
||||
genDoc.AppState = appState
|
||||
|
||||
if err := genDoc.SaveAs(config.GenesisFile()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genAppState, err := genutilstypes.AppGenesisFromFile(config.GenesisFile())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal genesis state: %w", err)
|
||||
}
|
||||
|
||||
if err = genutil.ExportGenesisFile(genAppState, config.GenesisFile()); err != nil {
|
||||
return fmt.Errorf("failed to export app genesis state: %w", err)
|
||||
}
|
||||
|
||||
cometcfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *validator) createNodeKey() error {
|
||||
serverCtx := server.NewDefaultContext()
|
||||
config := serverCtx.Config
|
||||
|
||||
config.SetRoot(v.configDir())
|
||||
config.Moniker = v.moniker
|
||||
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.nodeKey = *nodeKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *validator) createConsensusKey() error {
|
||||
serverCtx := server.NewDefaultContext()
|
||||
config := serverCtx.Config
|
||||
|
||||
config.SetRoot(v.configDir())
|
||||
config.Moniker = v.moniker
|
||||
|
||||
pvKeyFile := config.PrivValidatorKeyFile()
|
||||
if err := os.MkdirAll(filepath.Dir(pvKeyFile), 0o777); err != nil {
|
||||
return fmt.Errorf("could not create directory %q: %w", filepath.Dir(pvKeyFile), err)
|
||||
}
|
||||
|
||||
pvStateFile := config.PrivValidatorStateFile()
|
||||
if err := os.MkdirAll(filepath.Dir(pvStateFile), 0o777); err != nil {
|
||||
return fmt.Errorf("could not create directory %q: %w", filepath.Dir(pvStateFile), err)
|
||||
}
|
||||
|
||||
filePV := privval.LoadOrGenFilePV(pvKeyFile, pvStateFile)
|
||||
v.consensusKey = filePV.Key
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *validator) createKeyFromMnemonic(name, mnemonic string) error {
|
||||
kb, err := keyring.New(keyringAppName, keyring.BackendTest, v.configDir(), nil, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyringAlgos, _ := kb.SupportedAlgorithms()
|
||||
algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := kb.NewAccount(name, mnemonic, "", sdk.FullFundraiserPath, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privKeyArmor, err := kb.ExportPrivKeyArmor(name, keyringPassphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privKey, _, err := sdkcrypto.UnarmorDecryptPrivKey(privKeyArmor, keyringPassphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.keyInfo = *info
|
||||
v.mnemonic = mnemonic
|
||||
v.privateKey = privKey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *validator) createKey(name string) error {
|
||||
mnemonic, err := createMnemonic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.createKeyFromMnemonic(name, mnemonic)
|
||||
}
|
||||
|
||||
func (v *validator) buildCreateValidatorMsg(amount sdk.Coin) (sdk.Msg, error) {
|
||||
description := stakingtypes.NewDescription(v.moniker, "", "", "", "")
|
||||
commissionRates := stakingtypes.CommissionRates{
|
||||
Rate: math.LegacyMustNewDecFromStr("0.1"),
|
||||
MaxRate: math.LegacyMustNewDecFromStr("0.2"),
|
||||
MaxChangeRate: math.LegacyMustNewDecFromStr("0.01"),
|
||||
}
|
||||
|
||||
// get the initial validator min self delegation
|
||||
minSelfDelegation := math.NewInt(1)
|
||||
|
||||
valPubKey, err := cryptocodec.FromCmtPubKeyInterface(v.consensusKey.PubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valAddr, err := v.keyInfo.GetAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stakingtypes.NewMsgCreateValidator(
|
||||
sdk.ValAddress(valAddr),
|
||||
valPubKey,
|
||||
amount,
|
||||
description,
|
||||
commissionRates,
|
||||
minSelfDelegation,
|
||||
)
|
||||
}
|
||||
|
||||
func (v *validator) signMsg(msgs ...sdk.Msg) (*sdktx.Tx, error) {
|
||||
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
|
||||
|
||||
if err := txBuilder.SetMsgs(msgs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txBuilder.SetMemo(fmt.Sprintf("%s@%s:26656", v.nodeKey.ID(), v.instanceName()))
|
||||
txBuilder.SetFeeAmount(sdk.NewCoins())
|
||||
txBuilder.SetGasLimit(200_000)
|
||||
|
||||
pubKey, err := v.keyInfo.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signerData := authsigning.SignerData{
|
||||
ChainID: v.chain.id,
|
||||
AccountNumber: 0,
|
||||
Sequence: 0,
|
||||
PubKey: pubKey,
|
||||
}
|
||||
|
||||
// For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on
|
||||
// TxBuilder under the hood, and SignerInfos is needed to generate the sign
|
||||
// bytes. This is the reason for setting SetSignatures here, with a nil
|
||||
// signature.
|
||||
//
|
||||
// Note: This line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it
|
||||
// also doesn't affect its generated sign bytes, so for code's simplicity
|
||||
// sake, we put it here.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := signing.SignatureV2{
|
||||
PubKey: pubKey,
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: nil,
|
||||
},
|
||||
Sequence: 0,
|
||||
}
|
||||
|
||||
if err := txBuilder.SetSignatures(sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytesToSign, err := authsigning.GetSignBytesAdapter(
|
||||
context.Background(),
|
||||
encodingConfig.TxConfig.SignModeHandler(),
|
||||
signing.SignMode_SIGN_MODE_DIRECT,
|
||||
signerData,
|
||||
txBuilder.GetTx(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigBytes, err := v.privateKey.Sign(bytesToSign)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig = signing.SignatureV2{
|
||||
PubKey: pubKey,
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: sigBytes,
|
||||
},
|
||||
Sequence: 0,
|
||||
}
|
||||
if err := txBuilder.SetSignatures(sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signedTx := txBuilder.GetTx()
|
||||
bz, err := encodingConfig.TxConfig.TxEncoder()(signedTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeTx(bz)
|
||||
}
|
||||
341
tests/integration/chain_setup.go
Normal file
341
tests/integration/chain_setup.go
Normal file
@ -0,0 +1,341 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
rpctypes "github.com/cometbft/cometbft/rpc/core/types"
|
||||
comettypes "github.com/cometbft/cometbft/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
interchaintest "github.com/strangelove-ventures/interchaintest/v7"
|
||||
"github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
|
||||
"github.com/strangelove-ventures/interchaintest/v7/ibc"
|
||||
"github.com/strangelove-ventures/interchaintest/v7/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
// ChainBuilderFromChainSpec creates an interchaintest chain builder factory given a ChainSpec
|
||||
// and returns the associated chain
|
||||
func ChainBuilderFromChainSpec(t *testing.T, spec *interchaintest.ChainSpec) ibc.Chain {
|
||||
// require that NumFullNodes == NumValidators == 4
|
||||
require.Equal(t, *spec.NumValidators, 4)
|
||||
|
||||
cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{spec})
|
||||
|
||||
chains, err := cf.Chains(t.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, chains, 1)
|
||||
chain := chains[0]
|
||||
|
||||
_, ok := chain.(*cosmos.CosmosChain)
|
||||
require.True(t, ok)
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// BuildPOBInterchain creates a new Interchain testing env with the configured POB CosmosChain
|
||||
func BuildPOBInterchain(t *testing.T, ctx context.Context, chain ibc.Chain) *interchaintest.Interchain {
|
||||
ic := interchaintest.NewInterchain()
|
||||
ic.AddChain(chain)
|
||||
|
||||
// create docker network
|
||||
client, networkID := interchaintest.DockerSetup(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// build the interchain
|
||||
err := ic.Build(ctx, nil, interchaintest.InterchainBuildOptions{
|
||||
SkipPathCreation: true,
|
||||
Client: client,
|
||||
NetworkID: networkID,
|
||||
TestName: t.Name(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return ic
|
||||
}
|
||||
|
||||
// CreateTx creates a new transaction to be signed by the given user, including a provided set of messages
|
||||
func CreateTx(t *testing.T, ctx context.Context, chain *cosmos.CosmosChain, user cosmos.User, seqIncrement, height uint64, GasPrice int64, msgs ...sdk.Msg) []byte {
|
||||
// create a broadcaster
|
||||
broadcaster := cosmos.NewBroadcaster(t, chain)
|
||||
|
||||
// create tx factory + Client Context
|
||||
txf, err := broadcaster.GetFactory(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
cc, err := broadcaster.GetClientContext(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
txf, err = txf.Prepare(cc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// set timeout height
|
||||
if height != 0 {
|
||||
txf = txf.WithTimeoutHeight(height)
|
||||
}
|
||||
|
||||
// get gas for tx
|
||||
_, gas, err := tx.CalculateGas(cc, txf, msgs...)
|
||||
require.NoError(t, err)
|
||||
txf.WithGas(gas)
|
||||
|
||||
// update sequence number
|
||||
txf = txf.WithSequence(txf.Sequence() + seqIncrement)
|
||||
txf = txf.WithGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(chain.Config().Denom, sdk.NewInt(GasPrice))).String())
|
||||
|
||||
// sign the tx
|
||||
txBuilder, err := txf.BuildUnsignedTx(msgs...)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, tx.Sign(txf, cc.GetFromName(), txBuilder, true))
|
||||
|
||||
// encode and return
|
||||
bz, err := cc.TxConfig.TxEncoder()(txBuilder.GetTx())
|
||||
require.NoError(t, err)
|
||||
return bz
|
||||
}
|
||||
|
||||
// SimulateTx simulates the provided messages, and checks whether the provided failure condition is met
|
||||
func SimulateTx(t *testing.T, ctx context.Context, chain *cosmos.CosmosChain, user cosmos.User, height uint64, expectFail bool, msgs ...sdk.Msg) {
|
||||
// create a broadcaster
|
||||
broadcaster := cosmos.NewBroadcaster(t, chain)
|
||||
|
||||
// create tx factory + Client Context
|
||||
txf, err := broadcaster.GetFactory(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
cc, err := broadcaster.GetClientContext(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
txf, err = txf.Prepare(cc)
|
||||
require.NoError(t, err)
|
||||
|
||||
// set timeout height
|
||||
if height != 0 {
|
||||
txf = txf.WithTimeoutHeight(height)
|
||||
}
|
||||
|
||||
// get gas for tx
|
||||
_, _, err = tx.CalculateGas(cc, txf, msgs...)
|
||||
require.Equal(t, err != nil, expectFail)
|
||||
}
|
||||
|
||||
type Tx struct {
|
||||
User cosmos.User
|
||||
Msgs []sdk.Msg
|
||||
GasPrice int64
|
||||
SequenceIncrement uint64
|
||||
Height uint64
|
||||
SkipInclusionCheck bool
|
||||
ExpectFail bool
|
||||
}
|
||||
|
||||
// CreateAuctionBidMsg creates a new AuctionBid tx signed by the given user, the order of txs in the MsgAuctionBid will be determined by the contents + order of the MessageForUsers
|
||||
func CreateAuctionBidMsg(t *testing.T, ctx context.Context, searcher cosmos.User, chain *cosmos.CosmosChain, bid sdk.Coin, txsPerUser []Tx) (*buildertypes.MsgAuctionBid, [][]byte) {
|
||||
// for each MessagesForUser get the signed bytes
|
||||
txs := make([][]byte, len(txsPerUser))
|
||||
for i, tx := range txsPerUser {
|
||||
txs[i] = CreateTx(t, ctx, chain, tx.User, tx.SequenceIncrement, tx.Height, tx.GasPrice, tx.Msgs...)
|
||||
}
|
||||
|
||||
bech32SearcherAddress := searcher.FormattedAddress()
|
||||
accAddr, err := sdk.AccAddressFromBech32(bech32SearcherAddress)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a message auction bid
|
||||
return buildertypes.NewMsgAuctionBid(
|
||||
accAddr,
|
||||
bid,
|
||||
txs,
|
||||
), txs
|
||||
}
|
||||
|
||||
// BroadcastTxs broadcasts the given messages for each user. This function returns the broadcasted txs. If a message
|
||||
// is not expected to be included in a block, set SkipInclusionCheck to true and the method
|
||||
// will not block on the tx's inclusion in a block, otherwise this method will block on the tx's inclusion
|
||||
func BroadcastTxs(t *testing.T, ctx context.Context, chain *cosmos.CosmosChain, msgsPerUser []Tx) [][]byte {
|
||||
txs := make([][]byte, len(msgsPerUser))
|
||||
|
||||
for i, msg := range msgsPerUser {
|
||||
txs[i] = CreateTx(t, ctx, chain, msg.User, msg.SequenceIncrement, msg.Height, msg.GasPrice, msg.Msgs...)
|
||||
}
|
||||
|
||||
// broadcast each tx
|
||||
require.True(t, len(chain.Nodes()) > 0)
|
||||
client := chain.Nodes()[0].Client
|
||||
|
||||
for i, tx := range txs {
|
||||
// broadcast tx
|
||||
_, err := client.BroadcastTxSync(ctx, tx)
|
||||
|
||||
// check execution was successful
|
||||
if !msgsPerUser[i].ExpectFail {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// block on all txs being included in block
|
||||
eg := errgroup.Group{}
|
||||
for i, tx := range txs {
|
||||
// if we don't expect this tx to be included.. skip it
|
||||
if msgsPerUser[i].SkipInclusionCheck || msgsPerUser[i].ExpectFail {
|
||||
continue
|
||||
}
|
||||
|
||||
tx := tx // pin
|
||||
eg.Go(func() error {
|
||||
return testutil.WaitForCondition(4*time.Second, 500*time.Millisecond, func() (bool, error) {
|
||||
res, err := client.Tx(context.Background(), comettypes.Tx(tx).Hash(), false)
|
||||
|
||||
if err != nil || res.TxResult.Code != uint32(0) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
require.NoError(t, eg.Wait())
|
||||
|
||||
return txs
|
||||
}
|
||||
|
||||
// QueryBuilderParams queries the x/builder module's params
|
||||
func QueryBuilderParams(t *testing.T, chain ibc.Chain) buildertypes.Params {
|
||||
// cast chain to cosmos-chain
|
||||
cosmosChain, ok := chain.(*cosmos.CosmosChain)
|
||||
require.True(t, ok)
|
||||
// get nodes
|
||||
nodes := cosmosChain.Nodes()
|
||||
require.True(t, len(nodes) > 0)
|
||||
// make params query to first node
|
||||
resp, _, err := nodes[0].ExecQuery(context.Background(), "builder", "params")
|
||||
require.NoError(t, err)
|
||||
|
||||
// unmarshal params
|
||||
var params buildertypes.Params
|
||||
err = json.Unmarshal(resp, ¶ms)
|
||||
require.NoError(t, err)
|
||||
return params
|
||||
}
|
||||
|
||||
// QueryValidators queries for all of the network's validators
|
||||
func QueryValidators(t *testing.T, chain *cosmos.CosmosChain) []sdk.ValAddress {
|
||||
// get grpc client of the node
|
||||
grpcAddr := chain.GetHostGRPCAddress()
|
||||
cc, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
client := stakingtypes.NewQueryClient(cc)
|
||||
|
||||
// query validators
|
||||
resp, err := client.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
addrs := make([]sdk.ValAddress, len(resp.Validators))
|
||||
|
||||
// unmarshal validators
|
||||
for i, val := range resp.Validators {
|
||||
addrBz, err := sdk.GetFromBech32(val.OperatorAddress, chain.Config().Bech32Prefix+sdk.PrefixValidator+sdk.PrefixOperator)
|
||||
require.NoError(t, err)
|
||||
|
||||
addrs[i] = sdk.ValAddress(addrBz)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
// QueryAccountBalance queries a given account's balance on the chain
|
||||
func QueryAccountBalance(t *testing.T, chain ibc.Chain, address, denom string) int64 {
|
||||
// cast the chain to a cosmos-chain
|
||||
cosmosChain, ok := chain.(*cosmos.CosmosChain)
|
||||
require.True(t, ok)
|
||||
// get nodes
|
||||
balance, err := cosmosChain.GetBalance(context.Background(), address, denom)
|
||||
require.NoError(t, err)
|
||||
return balance
|
||||
}
|
||||
|
||||
// QueryAccountSequence
|
||||
func QueryAccountSequence(t *testing.T, chain *cosmos.CosmosChain, address string) uint64 {
|
||||
// get nodes
|
||||
nodes := chain.Nodes()
|
||||
require.True(t, len(nodes) > 0)
|
||||
|
||||
resp, _, err := nodes[0].ExecQuery(context.Background(), "auth", "account", address)
|
||||
require.NoError(t, err)
|
||||
// unmarshal json response
|
||||
var accResp codectypes.Any
|
||||
require.NoError(t, json.Unmarshal(resp, &accResp))
|
||||
|
||||
// unmarshal into baseAccount
|
||||
var acc authtypes.BaseAccount
|
||||
require.NoError(t, acc.Unmarshal(accResp.Value))
|
||||
|
||||
return acc.GetSequence()
|
||||
}
|
||||
|
||||
// Block returns the block at the given height
|
||||
func Block(t *testing.T, chain *cosmos.CosmosChain, height int64) *rpctypes.ResultBlock {
|
||||
// get nodes
|
||||
nodes := chain.Nodes()
|
||||
require.True(t, len(nodes) > 0)
|
||||
|
||||
client := nodes[0].Client
|
||||
|
||||
resp, err := client.Block(context.Background(), &height)
|
||||
require.NoError(t, err)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// WaitForHeight waits for the chain to reach the given height
|
||||
func WaitForHeight(t *testing.T, chain *cosmos.CosmosChain, height uint64) {
|
||||
// wait for next height
|
||||
err := testutil.WaitForCondition(30*time.Second, time.Second, func() (bool, error) {
|
||||
pollHeight, err := chain.Height(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return pollHeight == height, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// VerifyBlock takes a Block and verifies that it contains the given bid at the 0-th index, and the bundled txs immediately after
|
||||
func VerifyBlock(t *testing.T, block *rpctypes.ResultBlock, offset int, bidTxHash string, txs [][]byte) {
|
||||
// verify the block
|
||||
if bidTxHash != "" {
|
||||
require.Equal(t, bidTxHash, TxHash(block.Block.Data.Txs[offset]))
|
||||
offset += 1
|
||||
}
|
||||
|
||||
// verify the txs in sequence
|
||||
for i, tx := range txs {
|
||||
require.Equal(t, TxHash(tx), TxHash(block.Block.Data.Txs[i+offset]))
|
||||
}
|
||||
}
|
||||
|
||||
func TxHash(tx []byte) string {
|
||||
return strings.ToUpper(hex.EncodeToString(comettypes.Tx(tx).Hash()))
|
||||
}
|
||||
246
tests/integration/go.mod
Normal file
246
tests/integration/go.mod
Normal file
@ -0,0 +1,246 @@
|
||||
module github.com/skip-mev/pob/tests/integration
|
||||
|
||||
go 1.20
|
||||
|
||||
replace (
|
||||
// interchaintest supports ICS features so we need this for now
|
||||
// github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.13-ics
|
||||
github.com/ChainSafe/go-schnorrkel => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d
|
||||
github.com/ChainSafe/go-schnorrkel/1 => github.com/ChainSafe/go-schnorrkel v1.0.0
|
||||
github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.2 //indirect
|
||||
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
|
||||
|
||||
github.com/vedhavyas/go-subkey => github.com/strangelove-ventures/go-subkey v1.0.7
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
cosmossdk.io/api v0.3.1 // indirect
|
||||
cosmossdk.io/core v0.5.1 // indirect
|
||||
cosmossdk.io/depinject v1.0.0-alpha.3 // indirect
|
||||
cosmossdk.io/errors v1.0.0 // indirect
|
||||
cosmossdk.io/log v1.1.1-0.20230704160919-88f2c830b0ca // indirect
|
||||
cosmossdk.io/math v1.0.1 // indirect
|
||||
cosmossdk.io/tools/rosetta v0.2.1 // indirect
|
||||
github.com/cosmos/cosmos-sdk v0.47.4
|
||||
github.com/skip-mev/pob v1.0.3 // reference local
|
||||
github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230721183422-fb937bb0e165
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/sync v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cometbft/cometbft v0.37.2
|
||||
google.golang.org/grpc v1.56.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.4 // indirect
|
||||
cloud.google.com/go/compute v1.20.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.0 // indirect
|
||||
cloud.google.com/go/storage v1.30.1 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/99designs/keyring v1.2.2 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
|
||||
github.com/ChainSafe/go-schnorrkel/1 v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 // indirect
|
||||
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
|
||||
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/StirlingMarketingGroup/go-namecase v1.0.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/avast/retry-go/v4 v4.3.4 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.203 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/coinbase/rosetta-sdk-go/types v1.0.0 // indirect
|
||||
github.com/cometbft/cometbft-db v0.8.0 // indirect
|
||||
github.com/confio/ics23/go v0.9.0 // indirect
|
||||
github.com/cosmos/btcutil v1.0.5 // indirect
|
||||
github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect
|
||||
github.com/cosmos/go-bip39 v1.0.0 // indirect
|
||||
github.com/cosmos/gogogateway v1.2.0 // indirect
|
||||
github.com/cosmos/gogoproto v1.4.10 // indirect
|
||||
github.com/cosmos/iavl v0.20.0 // indirect
|
||||
github.com/cosmos/ibc-go/v7 v7.2.0 // indirect
|
||||
github.com/cosmos/ics23/go v0.10.0 // indirect
|
||||
github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect
|
||||
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
|
||||
github.com/creachadair/taskgroup v0.4.2 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set v1.8.0 // indirect
|
||||
github.com/decred/base58 v1.0.4 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v24.0.4+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.20 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-kit/kit v0.12.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/gogo/googleapis v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.3 // indirect
|
||||
github.com/golang/glog v1.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/orderedcode v0.0.1 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/gtank/ristretto255 v0.1.2 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-getter v1.7.1 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
|
||||
github.com/huandu/skiplist v1.2.0 // indirect
|
||||
github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845 // indirect
|
||||
github.com/improbable-eng/grpc-web v0.15.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/ipfs/go-cid v0.2.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jmhodges/levigo v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.16.3 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
github.com/libp2p/go-libp2p v0.22.0 // indirect
|
||||
github.com/libp2p/go-openssl v0.1.0 // indirect
|
||||
github.com/linxGnu/grocksdb v1.7.16 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-pointer v0.0.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||
github.com/minio/highwayhash v1.0.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/misko9/go-substrate-rpc-client/v4 v4.0.0-20230413215336-5bd2aea337ae // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/multiformats/go-base32 v0.0.4 // indirect
|
||||
github.com/multiformats/go-base36 v0.1.0 // indirect
|
||||
github.com/multiformats/go-multiaddr v0.6.0 // indirect
|
||||
github.com/multiformats/go-multibase v0.1.1 // indirect
|
||||
github.com/multiformats/go-multicodec v0.5.0 // indirect
|
||||
github.com/multiformats/go-multihash v0.2.1 // indirect
|
||||
github.com/multiformats/go-varint v0.0.6 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect
|
||||
github.com/pierrec/xxHash v0.1.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.15.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/rakyll/statik v0.1.7 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rs/cors v1.8.3 // indirect
|
||||
github.com/rs/zerolog v1.29.1 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.3.1 // indirect
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/tendermint/go-amino v0.16.0 // indirect
|
||||
github.com/tidwall/btree v1.6.0 // indirect
|
||||
github.com/tyler-smith/go-bip32 v1.0.0 // indirect
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/zondax/hid v0.9.1 // indirect
|
||||
github.com/zondax/ledger-go v0.14.1 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/oauth2 v0.8.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.126.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/sqlite v1.24.0 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
nhooyr.io/websocket v1.8.6 // indirect
|
||||
pgregory.net/rapid v0.5.5 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
1764
tests/integration/go.sum
Normal file
1764
tests/integration/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
80
tests/integration/pob_integration_test.go
Normal file
80
tests/integration/pob_integration_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
testutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
"github.com/strangelove-ventures/interchaintest/v7"
|
||||
"github.com/skip-mev/pob/tests/integration"
|
||||
"github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
|
||||
"github.com/strangelove-ventures/interchaintest/v7/ibc"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var (
|
||||
// config params
|
||||
numValidators = 4
|
||||
numFullNodes = 0
|
||||
denom = "stake"
|
||||
|
||||
image = ibc.DockerImage{
|
||||
Repository: "pob-integration",
|
||||
Version: "latest",
|
||||
UidGid: "1000:1000",
|
||||
}
|
||||
encodingConfig = MakeEncodingConfig()
|
||||
noHostMount = false
|
||||
gasAdjustment = float64(2.0)
|
||||
|
||||
genesisKV = []cosmos.GenesisKV{
|
||||
{
|
||||
Key: "app_state.builder.params.max_bundle_size",
|
||||
Value: 3,
|
||||
},
|
||||
}
|
||||
|
||||
// interchain specification
|
||||
spec = &interchaintest.ChainSpec{
|
||||
ChainName: "pob",
|
||||
Name: "pob",
|
||||
NumValidators: &numValidators,
|
||||
NumFullNodes: &numFullNodes,
|
||||
Version: "latest",
|
||||
NoHostMount: &noHostMount,
|
||||
GasAdjustment: &gasAdjustment,
|
||||
ChainConfig: ibc.ChainConfig{
|
||||
EncodingConfig: encodingConfig,
|
||||
Images: []ibc.DockerImage{
|
||||
image,
|
||||
},
|
||||
Type: "cosmos",
|
||||
Name: "pob",
|
||||
Denom: denom,
|
||||
ChainID: "chain-id-0",
|
||||
Bin: "testappd",
|
||||
Bech32Prefix: "cosmos",
|
||||
CoinType: "118",
|
||||
GasAdjustment: gasAdjustment,
|
||||
GasPrices: fmt.Sprintf("0%s", denom),
|
||||
TrustingPeriod: "48h",
|
||||
NoHostMount: noHostMount,
|
||||
UsingNewGenesisCommand: true,
|
||||
ModifyGenesis: cosmos.ModifyGenesis(genesisKV),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func MakeEncodingConfig() *testutil.TestEncodingConfig {
|
||||
cfg := cosmos.DefaultEncoding()
|
||||
|
||||
// register builder types
|
||||
buildertypes.RegisterInterfaces(cfg.InterfaceRegistry)
|
||||
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, integration.NewPOBIntegrationTestSuiteFromSpec(spec))
|
||||
}
|
||||
1290
tests/integration/pob_suite.go
Normal file
1290
tests/integration/pob_suite.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user