block-sdk/tests/integration/chain_setup.go
David Terpay b9d6761776
feat(ABCI): New Proposal Struct with Associated Metadata (#126)
* new proto types for proposal info

* new proposal type

* nits

* lane input

* lint

* feat(ABCI): Deprecating `CheckOrderHandler` with new Proposal MetaData (#127)

* refactor without checkorder

* nits

* more nits

* lint

* nits

* feat(ABCI): Updating MEV lane to have no `CheckOrder` handler + testing (#128)

* updating mev lane

* nits

* preventing adding multiple bid txs in prepare

* update
2023-09-28 11:10:13 -04:00

449 lines
13 KiB
Go

package integration
import (
"archive/tar"
"bytes"
"context"
"encoding/hex"
"encoding/json"
"io"
"os"
"path"
"strings"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"cosmossdk.io/math"
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"
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"
auctiontypes "github.com/skip-mev/block-sdk/x/auction/types"
)
type KeyringOverride struct {
keyringOptions keyring.Option
cdc codec.Codec
}
// 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
}
// BuildInterchain creates a new Interchain testing env with the configured Block SDK CosmosChain
func BuildInterchain(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 (s *IntegrationTestSuite) CreateTx(ctx context.Context, chain *cosmos.CosmosChain, user cosmos.User, seqIncrement, height uint64, GasPrice int64, msgs ...sdk.Msg) []byte {
// create tx factory + Client Context
txf, err := s.bc.GetFactory(ctx, user)
s.Require().NoError(err)
cc, err := s.bc.GetClientContext(ctx, user)
s.Require().NoError(err)
txf = txf.WithSimulateAndExecute(true)
txf, err = txf.Prepare(cc)
s.Require().NoError(err)
// set timeout height
if height != 0 {
txf = txf.WithTimeoutHeight(height)
}
// get gas for tx
_, gas, err := tx.CalculateGas(cc, txf, msgs...)
s.Require().NoError(err)
txf.WithGas(gas)
// update sequence number
txf = txf.WithSequence(txf.Sequence() + seqIncrement)
txf = txf.WithGasPrices(sdk.NewDecCoins(sdk.NewDecCoin(chain.Config().Denom, math.NewInt(GasPrice))).String())
// sign the tx
txBuilder, err := txf.BuildUnsignedTx(msgs...)
s.Require().NoError(err)
s.Require().NoError(tx.Sign(cc.CmdContext, txf, cc.GetFromName(), txBuilder, true))
// encode and return
bz, err := cc.TxConfig.TxEncoder()(txBuilder.GetTx())
s.Require().NoError(err)
return bz
}
// SimulateTx simulates the provided messages, and checks whether the provided failure condition is met
func (s *IntegrationTestSuite) SimulateTx(ctx context.Context, chain *cosmos.CosmosChain, user cosmos.User, height uint64, expectFail bool, msgs ...sdk.Msg) {
// create tx factory + Client Context
txf, err := s.bc.GetFactory(ctx, user)
s.Require().NoError(err)
cc, err := s.bc.GetClientContext(ctx, user)
s.Require().NoError(err)
txf, err = txf.Prepare(cc)
s.Require().NoError(err)
// set timeout height
if height != 0 {
txf = txf.WithTimeoutHeight(height)
}
// get gas for tx
_, _, err = tx.CalculateGas(cc, txf, msgs...)
s.Require().Equal(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 (s *IntegrationTestSuite) CreateAuctionBidMsg(ctx context.Context, searcher cosmos.User, chain *cosmos.CosmosChain, bid sdk.Coin, txsPerUser []Tx) (*auctiontypes.MsgAuctionBid, [][]byte) {
// for each MessagesForUser get the signed bytes
txs := make([][]byte, len(txsPerUser))
for i, tx := range txsPerUser {
txs[i] = s.CreateTx(ctx, chain, tx.User, tx.SequenceIncrement, tx.Height, tx.GasPrice, tx.Msgs...)
}
bech32SearcherAddress := searcher.FormattedAddress()
accAddr, err := sdk.AccAddressFromBech32(bech32SearcherAddress)
s.Require().NoError(err)
// create a message auction bid
return auctiontypes.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 (s *IntegrationTestSuite) BroadcastTxs(ctx context.Context, chain *cosmos.CosmosChain, msgsPerUser []Tx) [][]byte {
return s.BroadcastTxsWithCallback(ctx, chain, msgsPerUser, nil)
}
// 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. The callback
// function is called for each tx that is included in a block.
func (s *IntegrationTestSuite) BroadcastTxsWithCallback(
ctx context.Context,
chain *cosmos.CosmosChain,
msgsPerUser []Tx,
cb func(tx []byte, resp *rpctypes.ResultTx),
) [][]byte {
txs := make([][]byte, len(msgsPerUser))
for i, msg := range msgsPerUser {
txs[i] = s.CreateTx(ctx, chain, msg.User, msg.SequenceIncrement, msg.Height, msg.GasPrice, msg.Msgs...)
}
// broadcast each tx
s.Require().True(len(chain.Nodes()) > 0)
client := chain.Nodes()[0].Client
statusResp, err := client.Status(context.Background())
s.Require().NoError(err)
s.T().Logf("broadcasting transactions at latest height of %d", statusResp.SyncInfo.LatestBlockHeight)
for i, tx := range txs {
// broadcast tx
resp, err := client.BroadcastTxSync(ctx, tx)
// check execution was successful
if !msgsPerUser[i].ExpectFail {
s.Require().Equal(resp.Code, uint32(0))
} else {
if resp != nil {
s.Require().NotEqual(resp.Code, uint32(0))
} else {
s.Require().Error(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(30*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
}
if cb != nil {
cb(tx, res)
}
return true, nil
})
})
}
s.Require().NoError(eg.Wait())
return txs
}
// QueryAuctionParams queries the x/auction module's params
func QueryAuctionParams(t *testing.T, chain ibc.Chain) auctiontypes.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(), "auction", "params")
require.NoError(t, err)
// unmarshal params
var params auctiontypes.Params
err = json.Unmarshal(resp, &params)
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.Int64()
}
// 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, 100*time.Millisecond, 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+1]))
offset += 1
}
// verify the txs in sequence
for i, tx := range txs {
require.Equal(t, TxHash(tx), TxHash(block.Block.Data.Txs[i+offset+1]))
}
}
func TxHash(tx []byte) string {
return strings.ToUpper(hex.EncodeToString(comettypes.Tx(tx).Hash()))
}
func (s *IntegrationTestSuite) setupBroadcaster() {
bc := cosmos.NewBroadcaster(s.T(), s.chain.(*cosmos.CosmosChain))
if s.broadcasterOverrides == nil {
s.bc = bc
return
}
// get the key-ring-dir from the node locally
keyringDir := s.keyringDirFromNode()
// create a new keyring
kr, err := keyring.New("", keyring.BackendTest, keyringDir, os.Stdin, s.broadcasterOverrides.cdc, s.broadcasterOverrides.keyringOptions)
s.Require().NoError(err)
// override factory + client context keyrings
bc.ConfigureFactoryOptions(
func(factory tx.Factory) tx.Factory {
return factory.WithKeybase(kr)
},
)
bc.ConfigureClientContextOptions(
func(cc client.Context) client.Context {
return cc.WithKeyring(kr)
},
)
s.bc = bc
}
// sniped from here: https://github.com/strangelove-ventures/interchaintest ref: 9341b001214d26be420f1ca1ab0f15bad17faee6
func (s *IntegrationTestSuite) keyringDirFromNode() string {
node := s.chain.(*cosmos.CosmosChain).Nodes()[0]
// create a temp-dir
localDir := s.T().TempDir()
containerKeyringDir := path.Join(node.HomeDir(), "keyring-test")
reader, _, err := node.DockerClient.CopyFromContainer(context.Background(), node.ContainerID(), containerKeyringDir)
s.Require().NoError(err)
s.Require().NoError(os.Mkdir(path.Join(localDir, "keyring-test"), os.ModePerm))
tr := tar.NewReader(reader)
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
s.Require().NoError(err)
var fileBuff bytes.Buffer
_, err = io.Copy(&fileBuff, tr)
s.Require().NoError(err)
name := hdr.Name
extractedFileName := path.Base(name)
isDirectory := extractedFileName == ""
if isDirectory {
continue
}
filePath := path.Join(localDir, "keyring-test", extractedFileName)
s.Require().NoError(os.WriteFile(filePath, fileBuff.Bytes(), os.ModePerm))
}
return localDir
}