feat(ITS): [ENG-1627]: Moving ITS from release branch to main (#236)
Co-authored-by: Nikhil <nikhil@skip.money>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user