feat: [ENG-792] Implement E2E Testing Framework (#107)

This commit is contained in:
Aleksandr Bezobchuk
2023-05-08 14:45:17 -04:00
committed by GitHub
parent e913bee3cd
commit cc74b9e027
20 changed files with 3051 additions and 22 deletions
+91
View File
@@ -0,0 +1,91 @@
package e2e
import (
"fmt"
"os"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
cometrand "github.com/cometbft/cometbft/libs/rand"
"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) {
tmpDir, err := os.MkdirTemp("", "pob-e2e-testnet-")
if err != nil {
return nil, err
}
return &chain{
id: "chain-" + cometrand.NewRand().Str(6),
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",
}
}
+297
View File
@@ -0,0 +1,297 @@
package e2e
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
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/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/spf13/viper"
"github.com/stretchr/testify/suite"
)
var (
numValidators = 3
minGasPrice = sdk.NewDecCoinFromDec(app.BondDenom, sdk.MustNewDecFromStr("0.02")).String()
initBalanceStr = sdk.NewInt64Coin(app.BondDenom, 510000000000).String()
stakeAmount, _ = sdk.NewIntFromString("100000000000")
stakeAmountCoin = sdk.NewCoin(app.BondDenom, stakeAmount)
)
type 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()
for _, val := range s.chain.validators {
valAddr, err := val.keyInfo.GetAddress()
s.Require().NoError(err)
s.Require().NoError(addGenesisAccount(val0ConfigDir, "", initBalanceStr, valAddr))
}
// 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, sdk.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"
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
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
},
5*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",
}
}
+7
View File
@@ -0,0 +1,7 @@
//go:build e2e
package e2e
func (s *IntegrationTestSuite) TestTmp() {
s.Require().True(true)
}
+110
View File
@@ -0,0 +1,110 @@
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"
)
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 addGenesisAccount(path, moniker, amountStr string, accAddr sdk.AccAddress) 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
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)
}
+42
View File
@@ -0,0 +1,42 @@
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)
}
+19
View File
@@ -0,0 +1,19 @@
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
}
+45
View File
@@ -0,0 +1,45 @@
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
}
+272
View File
@@ -0,0 +1,272 @@
package e2e
import (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
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"
txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/genutil"
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 = genutil.ExportGenesisFile(genDoc, 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: sdk.MustNewDecFromStr("0.1"),
MaxRate: sdk.MustNewDecFromStr("0.2"),
MaxChangeRate: sdk.MustNewDecFromStr("0.01"),
}
// get the initial validator min self delegation
minSelfDelegation, _ := sdk.NewIntFromString("1")
valPubKey, err := cryptocodec.FromTmPubKeyInterface(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)
signerData := authsigning.SignerData{
ChainID: v.chain.id,
AccountNumber: 0,
Sequence: 0,
}
// 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.
pubKey, err := v.keyInfo.GetPubKey()
if err != nil {
return nil, err
}
sig := txsigning.SignatureV2{
PubKey: pubKey,
Data: &txsigning.SingleSignatureData{
SignMode: txsigning.SignMode_SIGN_MODE_DIRECT,
Signature: nil,
},
Sequence: 0,
}
if err := txBuilder.SetSignatures(sig); err != nil {
return nil, err
}
bytesToSign, err := encodingConfig.TxConfig.SignModeHandler().GetSignBytes(
txsigning.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 = txsigning.SignatureV2{
PubKey: pubKey,
Data: &txsigning.SingleSignatureData{
SignMode: txsigning.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)
}