From 3d09eda0264c1d8300f46d95181d99c08c55d8ed Mon Sep 17 00:00:00 2001 From: emidev98 Date: Wed, 20 Mar 2024 19:05:24 +0200 Subject: [PATCH 1/3] feat: introduce docker localnet again --- .dockerignore | 2 +- .gitignore | 1 + Makefile | 26 +++++++++- cmd/terrad/root.go | 5 +- docker-compose.yml | 76 +++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + scripts/containers/Dockerfile | 22 +++++++++ scripts/containers/build-wrapper.sh | 20 ++++++++ 9 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yml create mode 100644 scripts/containers/Dockerfile create mode 100755 scripts/containers/build-wrapper.sh diff --git a/.dockerignore b/.dockerignore index d1638636..20c1d078 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -build/ \ No newline at end of file +integration-tests \ No newline at end of file diff --git a/.gitignore b/.gitignore index 840c3aa8..9349a544 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # OS +.testnets .DS_Store *.swp *.swo diff --git a/Makefile b/Makefile index 8fb34900..b81497fc 100644 --- a/Makefile +++ b/Makefile @@ -299,4 +299,28 @@ format: format-tools find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs misspell -w find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs goimports -w -local github.com/cosmos/cosmos-sdk -.PHONY: lint lint-fix lint-docker format-tools format \ No newline at end of file +.PHONY: lint lint-fix lint-docker format-tools format + + +############################################################################### +### Local Testnet (docker) ### +############################################################################### + +localnet-rmi: + $(DOCKER) rmi terra-money/localnet-core 2>/dev/null; true + +localnet-build-env: localnet-rmi + $(DOCKER) build --tag terra-money/localnet-core -f scripts/containers/Dockerfile \ + $(shell git rev-parse --show-toplevel) + +localnet-build-nodes: + $(DOCKER) run --rm -v $(CURDIR)/.testnets:/terra terra-money/localnet-core \ + testnet init-files --v 3 -o /terra --starting-ip-address 192.168.15.20 --keyring-backend=test --chain-id=core-testnet-1 + $(DOCKER) compose up -d + +localnet-stop: + $(DOCKER) compose down + +localnet-start: localnet-stop localnet-build-env localnet-build-nodes + +.PHONY: localnet-stop localnet-start localnet-build-env localnet-build-nodes localnet-rmi \ No newline at end of file diff --git a/cmd/terrad/root.go b/cmd/terrad/root.go index 2a4e6f2e..9cf73bdc 100644 --- a/cmd/terrad/root.go +++ b/cmd/terrad/root.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" + sdkcmd "cosmossdk.io/simapp/simd/cmd" "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/debug" "github.com/cosmos/cosmos-sdk/client/flags" @@ -34,6 +35,7 @@ import ( genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" wasm "github.com/CosmWasm/wasmd/x/wasm" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" terraapp "github.com/terra-money/core/v2/app" "github.com/terra-money/core/v2/app/params" ) @@ -112,12 +114,13 @@ func initRootCmd(rootCmd *cobra.Command, moduleBasics module.BasicManager, encod a := appCreator{encodingConfig} rootCmd.AddCommand( - InitCmd(terraapp.ModuleBasics, terraapp.DefaultNodeHome), + InitCmd(moduleBasics, terraapp.DefaultNodeHome), config.Cmd(), tmcli.NewCompletionCmd(rootCmd, true), debug.Cmd(), pruning.Cmd(a.newApp, terraapp.DefaultNodeHome), snapshot.Cmd(a.newApp), + sdkcmd.NewTestnetCmd(moduleBasics, banktypes.GenesisBalancesIterator{}), ) server.AddCommands(rootCmd, terraapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..a0835518 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,76 @@ +version: "3" + +services: + node: + container_name: node + image: "terra-money/localnet-core" + environment: + - DEBUG=1 + - ID=0 + - LOG=${LOG:-core.log} + cap_add: + - SYS_PTRACE + security_opt: + - seccomp:unconfined + ports: + - "26656-26657:26656-26657" + - "1317:1317" + - "9090:9090" + - "2345:2345" + volumes: + - ./.testnets:/terrad/data:Z + networks: + localnet: + ipv4_address: 192.168.15.20 + + node_1: + container_name: node_1 + image: "terra-money/localnet-core" + environment: + - DEBUG=0 + - ID=1 + - LOG=${LOG:-core.log} + cap_add: + - SYS_PTRACE + security_opt: + - seccomp:unconfined + ports: + - "26666-26667:26656-26657" + - "1318:1317" + - "9091:9090" + - "2346:2345" + volumes: + - ./.testnets:/terrad/data:Z + networks: + localnet: + ipv4_address: 192.168.15.21 + + node_2: + container_name: node_2 + image: "terra-money/localnet-core" + environment: + - DEBUG=0 + - ID=2 + - LOG=${LOG:-core.log} + cap_add: + - SYS_PTRACE + security_opt: + - seccomp:unconfined + ports: + - "26676-26677:26656-26657" + - "1319:1317" + - "9092:9090" + - "2347:2345" + volumes: + - ./.testnets:/terrad/data:Z + networks: + localnet: + ipv4_address: 192.168.15.22 + +networks: + localnet: + driver: bridge + ipam: + driver: default + config: + - subnet: 192.168.15.0/25 \ No newline at end of file diff --git a/go.mod b/go.mod index f7d59387..a85915c1 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( cosmossdk.io/errors v1.0.0 cosmossdk.io/math v1.2.0 + cosmossdk.io/simapp v0.0.0-20230608160436-666c345ad23d cosmossdk.io/tools/rosetta v0.2.1 github.com/CosmWasm/wasmd v0.45.0 github.com/CosmWasm/wasmvm v1.5.2 diff --git a/go.sum b/go.sum index b4736ad2..6e4bf56c 100644 --- a/go.sum +++ b/go.sum @@ -203,6 +203,8 @@ cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk= cosmossdk.io/log v1.2.1/go.mod h1:GNSCc/6+DhFIj1aLn/j7Id7PaO8DzNylUZoOYBL9+I4= cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= +cosmossdk.io/simapp v0.0.0-20230608160436-666c345ad23d h1:E/8y0oG3u9hBR8l4F9MtC0LdZIamPCUwUoLlrHrX86I= +cosmossdk.io/simapp v0.0.0-20230608160436-666c345ad23d/go.mod h1:xbjky3L3DJEylaho6gXplkrMvJ5sFgv+qNX+Nn47bzY= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= diff --git a/scripts/containers/Dockerfile b/scripts/containers/Dockerfile new file mode 100644 index 00000000..a8b1e485 --- /dev/null +++ b/scripts/containers/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.20-bullseye AS build + +RUN apt update && apt install build-essential -y + +WORKDIR /terrad +COPY . /terrad + +RUN make build + +FROM golang:1.20-bullseye AS run +COPY ./scripts/containers/build-wrapper.sh /terrad/build-wrapper.sh +RUN chmod +x /terrad/build-wrapper.sh +RUN wget https://github.com/CosmWasm/wasmvm/releases/download/v1.5.2/libwasmvm.x86_64.so -P /usr/lib/ + +VOLUME /terrad +COPY --from=build /terrad/ /terrad/ +WORKDIR /terrad + +EXPOSE 26656 26657 +ENTRYPOINT ["/terrad/build-wrapper.sh"] +CMD ["start", "--log_format", "plain"] +STOPSIGNAL SIGTERM diff --git a/scripts/containers/build-wrapper.sh b/scripts/containers/build-wrapper.sh new file mode 100755 index 00000000..7f8fa2b7 --- /dev/null +++ b/scripts/containers/build-wrapper.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env sh +set -x + +export PATH=$PATH:/terrad/build/terrad +BINARY=/terrad/build/terrad +ID=${ID:-0} +LOG=${LOG:-terrad.log} + +if ! [ -f "${BINARY}" ]; then + echo "The binary $(basename "${BINARY}") cannot be found." + exit 1 +fi + +export TERRAD_HOME="/terrad/data/node${ID}/simd" + +if [ -d "$(dirname "${TERRAD_HOME}"/"${LOG}")" ]; then + "${BINARY}" --home "${TERRAD_HOME}" "$@" | tee "${TERRAD_HOME}/${LOG}" +else + "${BINARY}" --home "${TERRAD_HOME}" "$@" +fi From 0d4b91f5e211ed2921cd57780a7c85d7586ed38a Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 21 Mar 2024 16:39:25 +0200 Subject: [PATCH 2/3] wip: docker localnet --- cmd/terrad/root.go | 4 +- cmd/terrad/testnet.go | 529 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 530 insertions(+), 3 deletions(-) create mode 100644 cmd/terrad/testnet.go diff --git a/cmd/terrad/root.go b/cmd/terrad/root.go index 9cf73bdc..dd00d605 100644 --- a/cmd/terrad/root.go +++ b/cmd/terrad/root.go @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/client" - sdkcmd "cosmossdk.io/simapp/simd/cmd" "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/debug" "github.com/cosmos/cosmos-sdk/client/flags" @@ -35,7 +34,6 @@ import ( genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" wasm "github.com/CosmWasm/wasmd/x/wasm" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" terraapp "github.com/terra-money/core/v2/app" "github.com/terra-money/core/v2/app/params" ) @@ -120,7 +118,7 @@ func initRootCmd(rootCmd *cobra.Command, moduleBasics module.BasicManager, encod debug.Cmd(), pruning.Cmd(a.newApp, terraapp.DefaultNodeHome), snapshot.Cmd(a.newApp), - sdkcmd.NewTestnetCmd(moduleBasics, banktypes.GenesisBalancesIterator{}), + NewTestnetCmd(moduleBasics), ) server.AddCommands(rootCmd, terraapp.DefaultNodeHome, a.newApp, a.appExport, addModuleInitFlags) diff --git a/cmd/terrad/testnet.go b/cmd/terrad/testnet.go new file mode 100644 index 00000000..3f50b9a3 --- /dev/null +++ b/cmd/terrad/testnet.go @@ -0,0 +1,529 @@ +package main + +// DONTCOVER + +import ( + "bufio" + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + + tmconfig "github.com/cometbft/cometbft/config" + tmrand "github.com/cometbft/cometbft/libs/rand" + "github.com/cometbft/cometbft/types" + tmtime "github.com/cometbft/cometbft/types/time" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + terraapp "github.com/terra-money/core/v2/app" + "github.com/terra-money/core/v2/app/config" + + "cosmossdk.io/math" + + "cosmossdk.io/simapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "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" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + 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" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +var ( + flagNodeDirPrefix = "node-dir-prefix" + flagNumValidators = "v" + flagOutputDir = "output-dir" + flagNodeDaemonHome = "node-daemon-home" + flagStartingIPAddress = "starting-ip-address" + flagEnableLogging = "enable-logging" + flagGRPCAddress = "grpc.address" + flagRPCAddress = "rpc.address" + flagAPIAddress = "api.address" + flagPrintMnemonic = "print-mnemonic" +) + +type initArgs struct { + algo string + chainID string + keyringBackend string + minGasPrices string + nodeDaemonHome string + nodeDirPrefix string + numValidators int + outputDir string + startingIPAddress string +} + +type startArgs struct { + algo string + apiAddress string + chainID string + enableLogging bool + grpcAddress string + minGasPrices string + numValidators int + outputDir string + printMnemonic bool + rpcAddress string +} + +func addTestnetFlagsToCmd(cmd *cobra.Command) { + cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with") + cmd.Flags().StringP(flagOutputDir, "o", "./.testnets", "Directory to store initialization data for the testnet") + cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)") + cmd.Flags().String(flags.FlagKeyType, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for") + + // support old flags name for backwards compatibility + cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { + if name == "algo" { + name = flags.FlagKeyType + } + + return pflag.NormalizedName(name) + }) +} + +// NewTestnetCmd creates a root testnet command with subcommands to run an in-process testnet or initialize +// validator configuration files for running a multi-validator testnet in a separate process +func NewTestnetCmd(mbm module.BasicManager) *cobra.Command { + testnetCmd := &cobra.Command{ + Use: "testnet", + Short: "subcommands for starting or configuring local testnets", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + testnetCmd.AddCommand(testnetStartCmd()) + testnetCmd.AddCommand(testnetInitFilesCmd(mbm)) + + return testnetCmd +} + +// testnetInitFilesCmd returns a cmd to initialize all files for tendermint testnet and application +func testnetInitFilesCmd(mbm module.BasicManager) *cobra.Command { + cmd := &cobra.Command{ + Use: "init-files", + Short: "Initialize config directories & files for a multi-validator testnet running locally via separate processes (e.g. Docker Compose or similar)", + Long: `init-files will setup "v" number of directories and populate each with +necessary files (private validator, genesis, config, etc.) for running "v" validator nodes. + +Booting up a network with these validator folders is intended to be used with Docker Compose, +or a similar setup where each node has a manually configurable IP address. + +Note, strict routability for addresses is turned off in the config file. + +Example: + simd testnet init-files --v 4 --output-dir ./.testnets --starting-ip-address 192.168.10.2 + `, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + serverCtx := server.GetServerContextFromCmd(cmd) + config := serverCtx.Config + + args := initArgs{} + args.outputDir, _ = cmd.Flags().GetString(flagOutputDir) + args.keyringBackend, _ = cmd.Flags().GetString(flags.FlagKeyringBackend) + args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID) + args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices) + args.nodeDirPrefix, _ = cmd.Flags().GetString(flagNodeDirPrefix) + args.nodeDaemonHome, _ = cmd.Flags().GetString(flagNodeDaemonHome) + args.startingIPAddress, _ = cmd.Flags().GetString(flagStartingIPAddress) + args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators) + args.algo, _ = cmd.Flags().GetString(flags.FlagKeyType) + + return initTestnetFiles(clientCtx, cmd, config, mbm, args) + }, + } + + addTestnetFlagsToCmd(cmd) + cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)") + cmd.Flags().String(flagNodeDaemonHome, "simd", "Home directory of the node's daemon configuration") + cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)") + + return cmd +} + +// testnetStartCmd returns a cmd to start multi validator in-process testnet +func testnetStartCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "start", + Short: "Launch an in-process multi-validator testnet", + Long: `testnet will launch an in-process multi-validator testnet, +and generate "v" directories, populated with necessary validator configuration files +(private validator, genesis, config, etc.). + +Example: + simd testnet --v 4 --output-dir ./.testnets + `, + RunE: func(cmd *cobra.Command, _ []string) error { + args := startArgs{} + args.outputDir, _ = cmd.Flags().GetString(flagOutputDir) + args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID) + args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices) + args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators) + args.algo, _ = cmd.Flags().GetString(flags.FlagKeyType) + args.enableLogging, _ = cmd.Flags().GetBool(flagEnableLogging) + args.rpcAddress, _ = cmd.Flags().GetString(flagRPCAddress) + args.apiAddress, _ = cmd.Flags().GetString(flagAPIAddress) + args.grpcAddress, _ = cmd.Flags().GetString(flagGRPCAddress) + args.printMnemonic, _ = cmd.Flags().GetBool(flagPrintMnemonic) + + return startTestnet(cmd, args) + }, + } + + addTestnetFlagsToCmd(cmd) + cmd.Flags().Bool(flagEnableLogging, false, "Enable INFO logging of tendermint validator nodes") + cmd.Flags().String(flagRPCAddress, "tcp://0.0.0.0:26657", "the RPC address to listen on") + cmd.Flags().String(flagAPIAddress, "tcp://0.0.0.0:1317", "the address to listen on for REST API") + cmd.Flags().String(flagGRPCAddress, "0.0.0.0:9090", "the gRPC server address to listen on") + cmd.Flags().Bool(flagPrintMnemonic, true, "print mnemonic of first validator to stdout for manual testing") + return cmd +} + +const nodeDirPerm = 0o755 + +// initTestnetFiles initializes testnet files for a testnet to be run in a separate process +func initTestnetFiles( + clientCtx client.Context, + cmd *cobra.Command, + nodeConfig *tmconfig.Config, + mbm module.BasicManager, + args initArgs, +) error { + if args.chainID == "" { + args.chainID = "chain-" + tmrand.Str(6) + } + nodeIDs := make([]string, args.numValidators) + valPubKeys := make([]cryptotypes.PubKey, args.numValidators) + + simappConfig := srvconfig.DefaultConfig() + simappConfig.MinGasPrices = args.minGasPrices + simappConfig.API.Enable = true + simappConfig.API.Swagger = true + simappConfig.Telemetry.Enabled = true + simappConfig.Telemetry.PrometheusRetentionTime = 60 + simappConfig.Telemetry.EnableHostnameLabel = false + simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", args.chainID}} + + var ( + genAccounts []authtypes.GenesisAccount + genBalances []banktypes.Balance + genFiles []string + ) + + inBuf := bufio.NewReader(cmd.InOrStdin()) + // generate private keys, node IDs, and initial transactions + for i := 0; i < args.numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", args.nodeDirPrefix, i) + nodeDir := filepath.Join(args.outputDir, nodeDirName, args.nodeDaemonHome) + gentxsDir := filepath.Join(args.outputDir, "gentxs") + + nodeConfig.SetRoot(nodeDir) + nodeConfig.Moniker = nodeDirName + nodeConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657" + + if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil { + _ = os.RemoveAll(args.outputDir) + return err + } + + ip, err := getIP(i, args.startingIPAddress) + if err != nil { + _ = os.RemoveAll(args.outputDir) + return err + } + + nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig) + if err != nil { + _ = os.RemoveAll(args.outputDir) + return err + } + + memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) + genFiles = append(genFiles, nodeConfig.GenesisFile()) + + kb, err := keyring.New(sdk.KeyringServiceName(), args.keyringBackend, nodeDir, inBuf, clientCtx.Codec) + if err != nil { + return err + } + + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(args.algo, keyringAlgos) + if err != nil { + return err + } + + addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo) + if err != nil { + _ = os.RemoveAll(args.outputDir) + return err + } + + info := map[string]string{"secret": secret} + + cliPrint, err := json.Marshal(info) + if err != nil { + return err + } + + // save private key seed words + if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), nodeDir, cliPrint); err != nil { + return err + } + + coin := sdk.NewCoin(config.BondDenom, math.NewInt(1_000_000_000_000)) + genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: sdk.NewCoins(coin)}) + genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) + createValMsg, err := stakingtypes.NewMsgCreateValidator( + sdk.ValAddress(addr), + valPubKeys[i], + coin, + stakingtypes.NewDescription(nodeDirName, "", "", "", ""), + stakingtypes.NewCommissionRates(math.LegacyOneDec(), math.LegacyOneDec(), math.LegacyOneDec()), + math.OneInt(), + ) + if err != nil { + return err + } + + txBuilder := clientCtx.TxConfig.NewTxBuilder() + if err := txBuilder.SetMsgs(createValMsg); err != nil { + return err + } + + txBuilder.SetMemo(memo) + + txFactory := tx.Factory{} + txFactory = txFactory. + WithChainID(args.chainID). + WithMemo(memo). + WithKeybase(kb). + WithTxConfig(clientCtx.TxConfig) + + if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil { + return err + } + + txBz, err := clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) + if err != nil { + return err + } + + if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil { + return err + } + + srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config", "app.toml"), simappConfig) + } + + if err := initGenFiles(clientCtx, mbm, args.chainID, genAccounts, genBalances, genFiles, args.numValidators); err != nil { + return err + } + + err := collectGenFiles( + clientCtx, nodeConfig, args.chainID, nodeIDs, valPubKeys, args.numValidators, + args.outputDir, args.nodeDirPrefix, args.nodeDaemonHome, + ) + if err != nil { + return err + } + + cmd.PrintErrf("Successfully initialized %d node directories\n", args.numValidators) + return nil +} + +func initGenFiles( + clientCtx client.Context, mbm module.BasicManager, chainID string, + genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, + genFiles []string, numValidators int, +) error { + appGenState := terraapp.GenesisState(mbm.DefaultGenesis(clientCtx.Codec)). + SetDefaultTerraConfig(clientCtx.Codec) + + // set the accounts in the genesis state + var authGenState authtypes.GenesisState + clientCtx.Codec.MustUnmarshalJSON(appGenState[authtypes.ModuleName], &authGenState) + + accounts, err := authtypes.PackAccounts(genAccounts) + if err != nil { + return err + } + + authGenState.Accounts = accounts + appGenState[authtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&authGenState) + + // set the balances in the genesis state + var bankGenState banktypes.GenesisState + clientCtx.Codec.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState) + + bankGenState.Balances = banktypes.SanitizeGenesisBalances(genBalances) + for _, bal := range bankGenState.Balances { + bankGenState.Supply = bankGenState.Supply.Add(bal.Coins...) + } + appGenState[banktypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&bankGenState) + + appGenStateJSON, err := json.MarshalIndent(appGenState, "", " ") + if err != nil { + return err + } + + genDoc := types.GenesisDoc{ + ChainID: chainID, + AppState: appGenStateJSON, + Validators: nil, + } + + // generate empty genesis files for each validator and save + for i := 0; i < numValidators; i++ { + if err := genDoc.SaveAs(genFiles[i]); err != nil { + return err + } + } + return nil +} + +func collectGenFiles( + clientCtx client.Context, nodeConfig *tmconfig.Config, chainID string, + nodeIDs []string, valPubKeys []cryptotypes.PubKey, numValidators int, + outputDir, nodeDirPrefix, nodeDaemonHome string, +) error { + var appState json.RawMessage + genTime := tmtime.Now() + + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) + nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) + gentxsDir := filepath.Join(outputDir, "gentxs") + nodeConfig.Moniker = nodeDirName + + nodeConfig.SetRoot(nodeDir) + + nodeID, valPubKey := nodeIDs[i], valPubKeys[i] + initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey) + + genDoc, err := types.GenesisDocFromFile(nodeConfig.GenesisFile()) + if err != nil { + return err + } + + nodeAppState, err := genutil.GenAppStateFromConfig(clientCtx.Codec, clientCtx.TxConfig, nodeConfig, initCfg, *genDoc, banktypes.GenesisBalancesIterator{}, genutiltypes.DefaultMessageValidator) + if err != nil { + return err + } + + if appState == nil { + // set the canonical application state (they should not differ) + appState = nodeAppState + } + + genFile := nodeConfig.GenesisFile() + + // overwrite each validator's genesis file to have a canonical genesis time + if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil { + return err + } + } + + return nil +} + +func getIP(i int, startingIPAddr string) (ip string, err error) { + if len(startingIPAddr) == 0 { + ip, err = server.ExternalIP() + if err != nil { + return "", err + } + return ip, nil + } + return calculateIP(startingIPAddr, i) +} + +func calculateIP(ip string, i int) (string, error) { + ipv4 := net.ParseIP(ip).To4() + if ipv4 == nil { + return "", fmt.Errorf("%v: non ipv4 address", ip) + } + + for j := 0; j < i; j++ { + ipv4[3]++ + } + + return ipv4.String(), nil +} + +func writeFile(name string, dir string, contents []byte) error { + file := filepath.Join(dir, name) + + if err := os.MkdirAll(dir, 0o755); err != nil { + return fmt.Errorf("could not create directory %q: %w", dir, err) + } + + if err := os.WriteFile(file, contents, 0o644); err != nil { //nolint: gosec + return err + } + + return nil +} + +// startTestnet starts an in-process testnet +func startTestnet(cmd *cobra.Command, args startArgs) error { + networkConfig := network.DefaultConfig(simapp.NewTestNetworkFixture) + + // Default networkConfig.ChainID is random, and we should only override it if chainID provided + // is non-empty + if args.chainID != "" { + networkConfig.ChainID = args.chainID + } + networkConfig.SigningAlgo = args.algo + networkConfig.MinGasPrices = args.minGasPrices + networkConfig.NumValidators = args.numValidators + networkConfig.EnableTMLogging = args.enableLogging + networkConfig.RPCAddress = args.rpcAddress + networkConfig.APIAddress = args.apiAddress + networkConfig.GRPCAddress = args.grpcAddress + networkConfig.PrintMnemonic = args.printMnemonic + networkLogger := network.NewCLILogger(cmd) + + baseDir := fmt.Sprintf("%s/%s", args.outputDir, networkConfig.ChainID) + if _, err := os.Stat(baseDir); !os.IsNotExist(err) { + return fmt.Errorf( + "testnests directory already exists for chain-id '%s': %s, please remove or select a new --chain-id", + networkConfig.ChainID, baseDir) + } + + testnet, err := network.New(networkLogger, baseDir, networkConfig) + if err != nil { + return err + } + + if _, err := testnet.WaitForHeight(1); err != nil { + return err + } + cmd.Println("press the Enter Key to terminate") + if _, err := fmt.Scanln(); err != nil { // wait for Enter Key + return err + } + testnet.Cleanup() + + return nil +} From bbcdcbdbe8784f41c158eab9211cd1bffc10f3af Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 21 Mar 2024 20:24:59 +0200 Subject: [PATCH 3/3] feat: docker localnet --- cmd/terrad/testnet.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/terrad/testnet.go b/cmd/terrad/testnet.go index 3f50b9a3..19655afa 100644 --- a/cmd/terrad/testnet.go +++ b/cmd/terrad/testnet.go @@ -291,13 +291,19 @@ func initTestnetFiles( return err } - coin := sdk.NewCoin(config.BondDenom, math.NewInt(1_000_000_000_000)) - genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: sdk.NewCoins(coin)}) + accStakingTokens := sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction) + coins := sdk.Coins{ + sdk.NewCoin(config.BondDenom, accStakingTokens), + } + + genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins.Sort()}) genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) + + valTokens := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction) createValMsg, err := stakingtypes.NewMsgCreateValidator( sdk.ValAddress(addr), valPubKeys[i], - coin, + sdk.NewCoin(config.BondDenom, valTokens), stakingtypes.NewDescription(nodeDirName, "", "", "", ""), stakingtypes.NewCommissionRates(math.LegacyOneDec(), math.LegacyOneDec(), math.LegacyOneDec()), math.OneInt(),