From 06984221ae235f95b3840090be4ef4fa4f6f6902 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:41:53 +0100 Subject: [PATCH] feat: add chain-initiator tool to use mainnet snapshot in localnet (#3435) * feat: add chain-initiator tool to use mainnet snapshot in localnet * test: linter * feat: chain-initiator uses snapshot url and export genesis file from there * test: lint * feat: chain-initiator include submit software upgrade proposal logic * fix: improved stability * feat: improved chain initiator * test: fix lint * feat: binary url can be remote url or local path --- .../chain-initiator/account-unmarshal-json.go | 38 ++ .../chain-initiator/add-genesis-account.go | 19 + scripts/chain-initiator/add-key.go | 29 ++ scripts/chain-initiator/collect-gentxs.go | 19 + .../download-and-run-version.go | 114 ++++++ scripts/chain-initiator/export.go | 26 ++ scripts/chain-initiator/filter-accounts.go | 17 + scripts/chain-initiator/filter-balances.go | 24 ++ scripts/chain-initiator/gen-tx.go | 19 + scripts/chain-initiator/get-args.go | 24 ++ scripts/chain-initiator/get-flags.go | 105 +++++ scripts/chain-initiator/init-chain.go | 19 + scripts/chain-initiator/initiator.go | 160 ++++++++ scripts/chain-initiator/is-node-running.go | 30 ++ scripts/chain-initiator/listen-for-signals.go | 20 + .../query-and-calc-upgrade-block-height.go | 26 ++ scripts/chain-initiator/query-block-height.go | 25 ++ .../chain-initiator/query-next-proposal-id.go | 42 ++ scripts/chain-initiator/read-genesis-file.go | 23 ++ scripts/chain-initiator/remove-home.go | 19 + scripts/chain-initiator/retrieve-snapshot.go | 20 + .../chain-initiator/should-filter-account.go | 15 + scripts/chain-initiator/start.go | 37 ++ scripts/chain-initiator/stop.go | 17 + .../submit-upgrade-proposal.go | 47 +++ scripts/chain-initiator/types.go | 384 ++++++++++++++++++ scripts/chain-initiator/update-genesis.go | 72 ++++ scripts/chain-initiator/validate-genesis.go | 19 + .../vote-on-upgrade-proposal.go | 30 ++ .../chain-initiator/wait-for-block-height.go | 30 ++ .../chain-initiator/wait-for-next-block.go | 44 ++ .../chain-initiator/wait-for-node-to-start.go | 21 + scripts/chain-initiator/write-genesis-file.go | 28 ++ 33 files changed, 1562 insertions(+) create mode 100644 scripts/chain-initiator/account-unmarshal-json.go create mode 100644 scripts/chain-initiator/add-genesis-account.go create mode 100644 scripts/chain-initiator/add-key.go create mode 100644 scripts/chain-initiator/collect-gentxs.go create mode 100644 scripts/chain-initiator/download-and-run-version.go create mode 100644 scripts/chain-initiator/export.go create mode 100644 scripts/chain-initiator/filter-accounts.go create mode 100644 scripts/chain-initiator/filter-balances.go create mode 100644 scripts/chain-initiator/gen-tx.go create mode 100644 scripts/chain-initiator/get-args.go create mode 100644 scripts/chain-initiator/get-flags.go create mode 100644 scripts/chain-initiator/init-chain.go create mode 100644 scripts/chain-initiator/initiator.go create mode 100644 scripts/chain-initiator/is-node-running.go create mode 100644 scripts/chain-initiator/listen-for-signals.go create mode 100644 scripts/chain-initiator/query-and-calc-upgrade-block-height.go create mode 100644 scripts/chain-initiator/query-block-height.go create mode 100644 scripts/chain-initiator/query-next-proposal-id.go create mode 100644 scripts/chain-initiator/read-genesis-file.go create mode 100644 scripts/chain-initiator/remove-home.go create mode 100644 scripts/chain-initiator/retrieve-snapshot.go create mode 100644 scripts/chain-initiator/should-filter-account.go create mode 100644 scripts/chain-initiator/start.go create mode 100644 scripts/chain-initiator/stop.go create mode 100644 scripts/chain-initiator/submit-upgrade-proposal.go create mode 100644 scripts/chain-initiator/types.go create mode 100644 scripts/chain-initiator/update-genesis.go create mode 100644 scripts/chain-initiator/validate-genesis.go create mode 100644 scripts/chain-initiator/vote-on-upgrade-proposal.go create mode 100644 scripts/chain-initiator/wait-for-block-height.go create mode 100644 scripts/chain-initiator/wait-for-next-block.go create mode 100644 scripts/chain-initiator/wait-for-node-to-start.go create mode 100644 scripts/chain-initiator/write-genesis-file.go diff --git a/scripts/chain-initiator/account-unmarshal-json.go b/scripts/chain-initiator/account-unmarshal-json.go new file mode 100644 index 0000000000..f80a07bd34 --- /dev/null +++ b/scripts/chain-initiator/account-unmarshal-json.go @@ -0,0 +1,38 @@ +package main + +import ( + "encoding/json" + "fmt" +) + +func (a *Account) UnmarshalJSON(data []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + // Set the Type field from the raw data + typeStr, ok := raw["@type"].(string) + if !ok { + return fmt.Errorf("type field is missing or invalid") + } + a.Type = typeStr + + switch a.Type { + case "/cosmos.auth.v1beta1.BaseAccount": + var ba BaseAccount + if err := json.Unmarshal(data, &ba); err != nil { + return err + } + a.BaseAccount = &ba + case "/cosmos.auth.v1beta1.ModuleAccount": + var ma ModuleAccount + if err := json.Unmarshal(data, &ma); err != nil { + return err + } + a.ModuleAccount = &ma + default: + return fmt.Errorf("unknown account type: %s", a.Type) + } + return nil +} diff --git a/scripts/chain-initiator/add-genesis-account.go b/scripts/chain-initiator/add-genesis-account.go new file mode 100644 index 0000000000..ef1d9682ff --- /dev/null +++ b/scripts/chain-initiator/add-genesis-account.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func addGenesisAccount(cmdPath, address, balance, homePath string) { + // Command and arguments + args := []string{"add-genesis-account", address, balance + "rowan", "--home", homePath} + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) // nolint: goconst + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"add genesis account with address %s, balance: %s and home path %s successfully", address, balance, homePath) +} diff --git a/scripts/chain-initiator/add-key.go b/scripts/chain-initiator/add-key.go new file mode 100644 index 0000000000..0ca308b829 --- /dev/null +++ b/scripts/chain-initiator/add-key.go @@ -0,0 +1,29 @@ +package main + +import ( + "encoding/json" + "log" + "os/exec" +) + +func addKey(cmdPath, name, homePath, keyringBackend string) string { + // Command and arguments + args := []string{"keys", "add", name, "--home", homePath, "--keyring-backend", keyringBackend, "--output", "json"} + + // Execute the command + output, err := exec.Command(cmdPath, args...).CombinedOutput() + if err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // Unmarshal the JSON output + var keyOutput KeyOutput + if err := json.Unmarshal(output, &keyOutput); err != nil { + log.Fatalf(Red+"Failed to unmarshal JSON output: %v", err) + } + + // Log the address + log.Printf(Yellow+"add key with name %s, home path: %s, keyring backend %s and address %s successfully", name, homePath, keyringBackend, keyOutput.Address) + + return keyOutput.Address +} diff --git a/scripts/chain-initiator/collect-gentxs.go b/scripts/chain-initiator/collect-gentxs.go new file mode 100644 index 0000000000..7393f132a1 --- /dev/null +++ b/scripts/chain-initiator/collect-gentxs.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func collectGentxs(cmdPath, homePath string) { + // Command and arguments + args := []string{"collect-gentxs", "--home", homePath} + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"collect gen txs with home path %s successfully", homePath) +} diff --git a/scripts/chain-initiator/download-and-run-version.go b/scripts/chain-initiator/download-and-run-version.go new file mode 100644 index 0000000000..fcedd5461a --- /dev/null +++ b/scripts/chain-initiator/download-and-run-version.go @@ -0,0 +1,114 @@ +// nolint: nakedret +package main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "regexp" + "strings" +) + +func isURL(str string) bool { + return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://") +} + +func downloadAndRunVersion(binaryPathOrURL string, skipDownload bool) (path string, version string, err error) { + if !isURL(binaryPathOrURL) { + // If the input is a local path + path = binaryPathOrURL + + // Check if the path exists + if _, err = os.Stat(path); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("binary file does not exist at the specified path: %v", path)) + return + } + + // Run the command 'binary version' + cmd := exec.Command(path, "version") + var versionOutput []byte + versionOutput, err = cmd.CombinedOutput() + if err != nil { + return + } + version = strings.TrimSpace(string(versionOutput)) + + return + } + + if skipDownload { + // Extract version from the URL + re := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?`) + versionMatches := re.FindStringSubmatch(binaryPathOrURL) + if len(versionMatches) == 0 { + err = errors.New("no version found in URL") + return + } + version = versionMatches[0] + + // Remove the "v" prefix if present + if strings.HasPrefix(version, "v") { + version = strings.TrimPrefix(version, "v") + } + + // Set the binary path based on the version + path = "/tmp/sifnoded-" + version + + // Check if the path exists + if _, err = os.Stat(path); os.IsNotExist(err) { + err = errors.New(fmt.Sprintf("binary file does not exist at the specified path: %v", path)) + } + + return + } + + // Download the binary + resp, err := http.Get(binaryPathOrURL) // nolint: gosec + if err != nil { + return + } + defer resp.Body.Close() + + // Create a temporary file + tmpFile, err := ioutil.TempFile("", "binary-*") + if err != nil { + return + } + tmpFilePath := tmpFile.Name() + defer os.Remove(tmpFilePath) // Clean up + + // Write the downloaded content to the file + _, err = io.Copy(tmpFile, resp.Body) + tmpFile.Close() + if err != nil { + return + } + + // Make the file executable + err = os.Chmod(tmpFilePath, 0755) + if err != nil { + return + } + + // Run the command 'binary version' + cmd := exec.Command(tmpFilePath, "version") + versionOutput, err := cmd.CombinedOutput() + if err != nil { + return + } + version = strings.TrimSpace(string(versionOutput)) + + // Rename the temporary file + newFilePath := "/tmp/sifnoded-" + version + err = os.Rename(tmpFilePath, newFilePath) + if err != nil { + return + } + path = newFilePath + + return +} diff --git a/scripts/chain-initiator/export.go b/scripts/chain-initiator/export.go new file mode 100644 index 0000000000..efb502207c --- /dev/null +++ b/scripts/chain-initiator/export.go @@ -0,0 +1,26 @@ +package main + +import ( + "io/ioutil" + "log" + "os/exec" +) + +func export(cmdPath, homePath, genesisFilePath string) { + // Command and arguments + args := []string{"export", "--home", homePath} + + // Execute the command and capture the output + output, err := exec.Command(cmdPath, args...).CombinedOutput() + if err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // Write the output to the specified file + err = ioutil.WriteFile(genesisFilePath, output, 0644) // nolint: gosec + if err != nil { + log.Fatalf(Red+"Failed to write output to file: %v", err) + } + + log.Printf(Yellow+"Output successfully written to %s", genesisFilePath) +} diff --git a/scripts/chain-initiator/filter-accounts.go b/scripts/chain-initiator/filter-accounts.go new file mode 100644 index 0000000000..5929930e93 --- /dev/null +++ b/scripts/chain-initiator/filter-accounts.go @@ -0,0 +1,17 @@ +package main + +func filterAccounts(accounts []Account, filterAddresses []string) []Account { + filterMap := make(map[string]struct{}) + for _, addr := range filterAddresses { + filterMap[addr] = struct{}{} + } + + newAccounts := []Account{} + for _, account := range accounts { + if shouldFilterAccount(account, filterMap) { + continue + } + newAccounts = append(newAccounts, account) + } + return newAccounts +} diff --git a/scripts/chain-initiator/filter-balances.go b/scripts/chain-initiator/filter-balances.go new file mode 100644 index 0000000000..8d58246a12 --- /dev/null +++ b/scripts/chain-initiator/filter-balances.go @@ -0,0 +1,24 @@ +package main + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func filterBalances(balances []banktypes.Balance, filterAddresses []string) ([]banktypes.Balance, sdk.Coins) { + filterMap := make(map[string]struct{}) + for _, addr := range filterAddresses { + filterMap[addr] = struct{}{} + } + + newBalances := []banktypes.Balance{} + var coinsToRemove sdk.Coins + for _, balance := range balances { + if _, exists := filterMap[balance.Address]; exists { + coinsToRemove = coinsToRemove.Add(balance.Coins...) + continue + } + newBalances = append(newBalances, balance) + } + return newBalances, coinsToRemove +} diff --git a/scripts/chain-initiator/gen-tx.go b/scripts/chain-initiator/gen-tx.go new file mode 100644 index 0000000000..55b79e73e1 --- /dev/null +++ b/scripts/chain-initiator/gen-tx.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func genTx(cmdPath, name, amount, chainId, homePath, keyringBackend string) { + // Command and arguments + args := []string{"gentx", name, amount + "rowan", "--chain-id", chainId, "--home", homePath, "--keyring-backend", keyringBackend} + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"gen tx with name %s, amount: %s, chain id %s, home path %s and keyring backend %s successfully", name, amount, chainId, homePath, keyringBackend) +} diff --git a/scripts/chain-initiator/get-args.go b/scripts/chain-initiator/get-args.go new file mode 100644 index 0000000000..f3a457b077 --- /dev/null +++ b/scripts/chain-initiator/get-args.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" +) + +func getArgs(args []string) (snapshotUrl, oldBinaryUrl, newBinaryUrl string) { + snapshotUrl = args[0] // https://snapshots.polkachu.com/snapshots/sifchain/sifchain_15048938.tar.lz4 + if snapshotUrl == "" { + log.Fatalf(Red + "snapshot url is required") + } + + oldBinaryUrl = args[1] // https://github.com/Sifchain/sifnode/releases/download/v1.2.0-beta/sifnoded-v1.2.0-beta-darwin-arm64 + if oldBinaryUrl == "" { + log.Fatalf(Red + "old binary url is required") + } + + newBinaryUrl = args[2] // https://github.com/Sifchain/sifnode/releases/download/v1.3.0-beta/sifnoded-v1.3.0-beta-darwin-arm64 + if newBinaryUrl == "" { + log.Fatalf(Red + "new binary url is required") + } + + return +} diff --git a/scripts/chain-initiator/get-flags.go b/scripts/chain-initiator/get-flags.go new file mode 100644 index 0000000000..6846cd999a --- /dev/null +++ b/scripts/chain-initiator/get-flags.go @@ -0,0 +1,105 @@ +// nolint: nakedret +package main + +import ( + "log" + + "github.com/spf13/cobra" +) + +const ( + flagHome = "home" + flagSkipSnapshot = "skip-snapshot" + flagSkipChainInit = "skip-chain-init" + flagSkipNodeStart = "skip-node-start" + flagSkipProposal = "skip-proposal" + flagSkipBinary = "skip-binary" + flagMoniker = "moniker" + flagChainId = "chain-id" + flagKeyringBackend = "keyring-backend" + flagValidatorKeyName = "validator-key-name" + flagValidatorBalance = "validator-balance" + flagValidatorSelfDelegation = "validator-self-delegation" + flagGenesisFilePath = "genesis-file-path" + flagNode = "node" + flagBroadcastMode = "broadcast-mode" +) + +func getFlags(cmd *cobra.Command) (homePath string, skipSnapshot, skipChainInit, skipNodeStart, skipProposal, skipBinary bool, moniker, chainId, keyringBackend, validatorKeyName, validatorBalance, validatorSelfDelegation, genesisFilePath, node, broadcastMode string) { + homePath, _ = cmd.Flags().GetString(flagHome) + if homePath == "" { + log.Fatalf(Red + "home path is required") + } + + skipSnapshot, _ = cmd.Flags().GetBool(flagSkipSnapshot) + if skipSnapshot { + log.Printf(Yellow + "skipping snapshot retrieval") + } + + skipChainInit, _ = cmd.Flags().GetBool(flagSkipChainInit) + if skipChainInit { + log.Printf(Yellow + "skipping chain init") + } + + skipNodeStart, _ = cmd.Flags().GetBool(flagSkipNodeStart) + if skipNodeStart { + log.Printf(Yellow + "skipping node start") + } + + skipProposal, _ = cmd.Flags().GetBool(flagSkipProposal) + if skipProposal { + log.Printf(Yellow + "skipping proposal") + } + + skipBinary, _ = cmd.Flags().GetBool(flagSkipBinary) + if skipBinary { + log.Printf(Yellow + "skipping binary download") + } + + moniker, _ = cmd.Flags().GetString(flagMoniker) + if moniker == "" { + log.Fatalf(Red + "moniker is required") + } + + chainId, _ = cmd.Flags().GetString(flagChainId) + if chainId == "" { + log.Fatalf(Red + "chain id is required") + } + + keyringBackend, _ = cmd.Flags().GetString(flagKeyringBackend) + if keyringBackend == "" { + log.Fatalf(Red + "keyring backend is required") + } + + validatorKeyName, _ = cmd.Flags().GetString(flagValidatorKeyName) + if validatorKeyName == "" { + log.Fatalf(Red + "validator key name is required") + } + + validatorBalance, _ = cmd.Flags().GetString(flagValidatorBalance) + if validatorBalance == "" { + log.Fatalf(Red + "validator balance is required") + } + + validatorSelfDelegation, _ = cmd.Flags().GetString(flagValidatorSelfDelegation) + if validatorSelfDelegation == "" { + log.Fatalf(Red + "validator self delegation is required") + } + + genesisFilePath, _ = cmd.Flags().GetString(flagGenesisFilePath) + if genesisFilePath == "" { + log.Fatalf(Red + "genesis file path is required") + } + + node, _ = cmd.Flags().GetString(flagNode) + if node == "" { + log.Fatalf(Red + "node is required") + } + + broadcastMode, _ = cmd.Flags().GetString(flagBroadcastMode) + if broadcastMode == "" { + log.Fatalf(Red + "broadcast mode is required") + } + + return +} diff --git a/scripts/chain-initiator/init-chain.go b/scripts/chain-initiator/init-chain.go new file mode 100644 index 0000000000..fb1f09b691 --- /dev/null +++ b/scripts/chain-initiator/init-chain.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func initChain(cmdPath, moniker, chainId, homePath string) { + // Command and arguments + args := []string{"init", moniker, "--chain-id", chainId, "--home", homePath} + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"init chain with moniker %s, chain id %s and home path: %s successfully", moniker, chainId, homePath) +} diff --git a/scripts/chain-initiator/initiator.go b/scripts/chain-initiator/initiator.go new file mode 100644 index 0000000000..f93477d6a6 --- /dev/null +++ b/scripts/chain-initiator/initiator.go @@ -0,0 +1,160 @@ +package main + +import ( + "log" + "os" + "time" + + app "github.com/Sifchain/sifnode/app" + "github.com/spf13/cobra" +) + +func main() { + var rootCmd = &cobra.Command{ + Use: "initiator [snapshot_url] [old_binary_url] [new_binary_url] [flags]", + Short: "Chain Initiator is a tool for running a chain from a snapshot.", + Long: `A tool for running a chain from a snapshot.`, + Args: cobra.ExactArgs(3), // Expect exactly 1 argument + Run: func(cmd *cobra.Command, args []string) { + snapshotUrl, oldBinaryUrl, newBinaryUrl := getArgs(args) + homePath, skipSnapshot, skipChainInit, skipNodeStart, skipProposal, skipBinary, moniker, chainId, keyringBackend, validatorKeyName, validatorBalance, validatorSelfDelegation, genesisFilePath, node, broadcastMode := getFlags(cmd) + + // set address prefix + app.SetConfig(false) + + // download and run old binary + oldBinaryPath, oldVersion, err := downloadAndRunVersion(oldBinaryUrl, skipBinary) + if err != nil { + log.Fatalf(Red+"Error downloading and running old binary: %v", err) + } + + // print old binary path and version + log.Printf(Green+"Old binary path: %v and version: %v", oldBinaryPath, oldVersion) + + // download and run new binary + newBinaryPath, newVersion, err := downloadAndRunVersion(newBinaryUrl, skipBinary) + if err != nil { + log.Fatalf(Red+"Error downloading and running new binary: %v", err) + } + + // print new binary path and version + log.Printf(Green+"New binary path: %v and version: %v", newBinaryPath, newVersion) + + if !skipSnapshot { + // remove home path + removeHome(homePath) + + // init chain + initChain(oldBinaryPath, moniker, chainId, homePath) + + // retrieve the snapshot + retrieveSnapshot(snapshotUrl, homePath) + + // export genesis file + export(oldBinaryPath, homePath, genesisFilePath) + } + + if !skipChainInit { + // remove home path + removeHome(homePath) + + // init chain + initChain(oldBinaryPath, moniker, chainId, homePath) + + // add validator key + validatorAddress := addKey(oldBinaryPath, validatorKeyName, homePath, keyringBackend) + + // add genesis account + addGenesisAccount(oldBinaryPath, validatorAddress, validatorBalance, homePath) + + // generate genesis tx + genTx(oldBinaryPath, validatorKeyName, validatorSelfDelegation, chainId, homePath, keyringBackend) + + // collect genesis txs + collectGentxs(oldBinaryPath, homePath) + + // validate genesis + validateGenesis(oldBinaryPath, homePath) + + // update genesis + updateGenesis(validatorBalance, homePath, genesisFilePath) + } + + if !skipNodeStart { + // start chain + oldBinaryCmd := start(oldBinaryPath, homePath) + + // wait for node to start + waitForNodeToStart(node) + + // wait for next block + waitForNextBlock(oldBinaryPath, node) + + if skipProposal { + // listen for signals + listenForSignals(oldBinaryCmd) + return + } + + // query and calculate upgrade block height + upgradeBlockHeight := queryAndCalcUpgradeBlockHeight(oldBinaryPath, node) + + // query next proposal id + proposalId, err := queryNextProposalId(oldBinaryPath, node) + if err != nil { + log.Fatalf(Red+"Error querying next proposal id: %v", err) + } + + // submit upgrade proposal + submitUpgradeProposal(oldBinaryPath, validatorKeyName, newVersion, upgradeBlockHeight, homePath, keyringBackend, chainId, node, broadcastMode) + + // vote on upgrade proposal + voteOnUpgradeProposal(oldBinaryPath, validatorKeyName, proposalId, homePath, keyringBackend, chainId, node, broadcastMode) + + // wait for upgrade block height + waitForBlockHeight(oldBinaryPath, node, upgradeBlockHeight) + + // stop old binary + stop(oldBinaryCmd) + + // wait 5 seconds + time.Sleep(5 * time.Second) + + // start new binary + newBinaryCmd := start(newBinaryPath, homePath) + + // wait for node to start + waitForNodeToStart(node) + + // wait for next block + waitForNextBlock(newBinaryPath, node) + + // listen for signals + listenForSignals(newBinaryCmd) + } + }, + } + + // get HOME environment variable + homeEnv, _ := os.LookupEnv("HOME") + + rootCmd.PersistentFlags().String(flagHome, homeEnv+"/.sifnoded", "home directory") + rootCmd.PersistentFlags().Bool(flagSkipSnapshot, false, "skip snapshot retrieval") + rootCmd.PersistentFlags().Bool(flagSkipChainInit, false, "skip chain init") + rootCmd.PersistentFlags().Bool(flagSkipNodeStart, false, "skip node start") + rootCmd.PersistentFlags().Bool(flagSkipProposal, false, "skip proposal") + rootCmd.PersistentFlags().Bool(flagSkipBinary, false, "skip binary download") + rootCmd.PersistentFlags().String(flagMoniker, "node", "moniker") + rootCmd.PersistentFlags().String(flagChainId, "sifchain-1", "chain id") + rootCmd.PersistentFlags().String(flagKeyringBackend, "test", "keyring backend") + rootCmd.PersistentFlags().String(flagValidatorKeyName, "validator", "validator key name") + rootCmd.PersistentFlags().String(flagValidatorBalance, "4000000000000000000000000000", "validator balance") + rootCmd.PersistentFlags().String(flagValidatorSelfDelegation, "1000000000000000000000000000", "validator self delegation") + rootCmd.PersistentFlags().String(flagGenesisFilePath, "/tmp/genesis.json", "genesis file path") + rootCmd.PersistentFlags().String(flagNode, "tcp://localhost:26657", "node") + rootCmd.PersistentFlags().String(flagBroadcastMode, "block", "broadcast mode") + + if err := rootCmd.Execute(); err != nil { + log.Fatalf(Red+"Error executing command: %v", err) + } +} diff --git a/scripts/chain-initiator/is-node-running.go b/scripts/chain-initiator/is-node-running.go new file mode 100644 index 0000000000..9e32aec04d --- /dev/null +++ b/scripts/chain-initiator/is-node-running.go @@ -0,0 +1,30 @@ +package main + +import ( + "net" + "net/http" + "strings" +) + +func isNodeRunning(node string) bool { + // Remove the "tcp://" prefix if present + if strings.HasPrefix(node, "tcp://") { + node = strings.TrimPrefix(node, "tcp://") + } + + // Attempt to make a TCP connection + conn, err := net.Dial("tcp", node) + if err == nil { + conn.Close() + return true + } + + // If TCP connection fails, attempt an HTTP GET request + resp, err := http.Get("http://" + node) + if err == nil { + resp.Body.Close() + return resp.StatusCode == http.StatusOK + } + + return false +} diff --git a/scripts/chain-initiator/listen-for-signals.go b/scripts/chain-initiator/listen-for-signals.go new file mode 100644 index 0000000000..9eb951f15d --- /dev/null +++ b/scripts/chain-initiator/listen-for-signals.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + "os/exec" + "os/signal" + "syscall" +) + +func listenForSignals(cmd *exec.Cmd) { + // Set up channel to listen for signals + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + + // Block until a signal is received + <-sigChan + + // Stop the process when a signal is received + stop(cmd) +} diff --git a/scripts/chain-initiator/query-and-calc-upgrade-block-height.go b/scripts/chain-initiator/query-and-calc-upgrade-block-height.go new file mode 100644 index 0000000000..c9cf02092a --- /dev/null +++ b/scripts/chain-initiator/query-and-calc-upgrade-block-height.go @@ -0,0 +1,26 @@ +package main + +import ( + "log" + "strconv" +) + +func queryAndCalcUpgradeBlockHeight(cmdPath, node string) string { + // query block height + blockHeight, err := queryBlockHeight(cmdPath, node) + if err != nil { + log.Fatalf(Red+"Failed to query block height: %v", err) + } + + // Convert blockHeight from string to int + blockHeightInt, err := strconv.Atoi(blockHeight) + if err != nil { + log.Fatalf(Red+"Failed to convert blockHeight to integer: %v", err) + } + + // set upgrade block height + upgradeBlockHeight := blockHeightInt + 5 + + // return upgrade block height as a string + return strconv.Itoa(upgradeBlockHeight) +} diff --git a/scripts/chain-initiator/query-block-height.go b/scripts/chain-initiator/query-block-height.go new file mode 100644 index 0000000000..757afad241 --- /dev/null +++ b/scripts/chain-initiator/query-block-height.go @@ -0,0 +1,25 @@ +package main + +import ( + "encoding/json" + "os/exec" +) + +func queryBlockHeight(cmdPath, node string) (string, error) { + // Command and arguments + args := []string{"status", "--node", node} + + // Execute the command + output, err := exec.Command(cmdPath, args...).CombinedOutput() + if err != nil { + return "-1", err + } + + // Unmarshal the JSON output + var statusOutput StatusOutput + if err := json.Unmarshal(output, &statusOutput); err != nil { + return "-1", err + } + + return statusOutput.SyncInfo.LatestBlockHeight, nil +} diff --git a/scripts/chain-initiator/query-next-proposal-id.go b/scripts/chain-initiator/query-next-proposal-id.go new file mode 100644 index 0000000000..c0e0853d8a --- /dev/null +++ b/scripts/chain-initiator/query-next-proposal-id.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "errors" + "os/exec" + "strconv" +) + +func queryNextProposalId(cmdPath, node string) (string, error) { + // Command and arguments + args := []string{"q", "gov", "proposals", "--node", node, "--limit", "1", "--reverse", "--output", "json"} + + // Execute the command + output, err := exec.Command(cmdPath, args...).CombinedOutput() + if err != nil { + return "-1", err + } + + // Unmarshal the JSON output + var proposalsOutput ProposalsOutput + if err := json.Unmarshal(output, &proposalsOutput); err != nil { + return "-1", err + } + + // check if there are any proposals + if len(proposalsOutput.Proposals) == 0 { + return "1", errors.New("no proposals found") + } + + // increment proposal id + proposalId := proposalsOutput.Proposals[0].ProposalId + proposalIdInt, err := strconv.Atoi(proposalId) + if err != nil { + return "-1", err + } + proposalIdInt++ + // convert back to string + proposalId = strconv.Itoa(proposalIdInt) + + return proposalId, nil +} diff --git a/scripts/chain-initiator/read-genesis-file.go b/scripts/chain-initiator/read-genesis-file.go new file mode 100644 index 0000000000..afeb432d0a --- /dev/null +++ b/scripts/chain-initiator/read-genesis-file.go @@ -0,0 +1,23 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" +) + +func readGenesisFile(filePath string) (Genesis, error) { + var genesis Genesis + file, err := os.Open(filePath) + if err != nil { + return genesis, fmt.Errorf("error opening file: %w", err) + } + defer file.Close() + + if err := json.NewDecoder(bufio.NewReader(file)).Decode(&genesis); err != nil { + return genesis, fmt.Errorf("error decoding JSON: %w", err) + } + + return genesis, nil +} diff --git a/scripts/chain-initiator/remove-home.go b/scripts/chain-initiator/remove-home.go new file mode 100644 index 0000000000..6a67c29d05 --- /dev/null +++ b/scripts/chain-initiator/remove-home.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func removeHome(homePath string) { + // Command and arguments + args := []string{"-rf", homePath} + + // Execute the command + if err := exec.Command("rm", args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"removed home path %s successfully", homePath) +} diff --git a/scripts/chain-initiator/retrieve-snapshot.go b/scripts/chain-initiator/retrieve-snapshot.go new file mode 100644 index 0000000000..f72833ff08 --- /dev/null +++ b/scripts/chain-initiator/retrieve-snapshot.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + "os/exec" +) + +func retrieveSnapshot(snapshotUrl, homePath string) { + // Construct the command string + cmdString := "curl -o - -L " + snapshotUrl + " | lz4 -c -d - | tar -x -C " + homePath + + // Execute the command using /bin/sh + cmd := exec.Command("/bin/sh", "-c", cmdString) + if err := cmd.Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"Snapshot retrieved and extracted to path: %s", homePath) +} diff --git a/scripts/chain-initiator/should-filter-account.go b/scripts/chain-initiator/should-filter-account.go new file mode 100644 index 0000000000..f361c37761 --- /dev/null +++ b/scripts/chain-initiator/should-filter-account.go @@ -0,0 +1,15 @@ +package main + +func shouldFilterAccount(account Account, filterAddresses map[string]struct{}) bool { + if account.BaseAccount != nil { + if _, exists := filterAddresses[account.BaseAccount.Address]; exists { + return true + } + } + if account.ModuleAccount != nil { + if _, exists := filterAddresses[account.ModuleAccount.BaseAccount.Address]; exists { + return true + } + } + return false +} diff --git a/scripts/chain-initiator/start.go b/scripts/chain-initiator/start.go new file mode 100644 index 0000000000..2215cc1189 --- /dev/null +++ b/scripts/chain-initiator/start.go @@ -0,0 +1,37 @@ +package main + +import ( + "log" + "os" + "os/exec" + "syscall" +) + +func start(cmdPath, homePath string) *exec.Cmd { + // Command and arguments + args := []string{"start", "--home", homePath} + + // Set up the command + cmd := exec.Command(cmdPath, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // Execute the command and stream the output in a goroutine to avoid blocking + go func() { + err := cmd.Run() + if err != nil { + // Check if the error is because of the process being killed + if exitErr, ok := err.(*exec.ExitError); ok { + // If the process was killed, log it as a non-fatal error + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok && status.Signaled() { + log.Printf("Process was killed: %v", err) + return + } + } + // For other errors, log them as fatal + log.Fatalf("Command execution failed: %v", err) + } + }() + + return cmd +} diff --git a/scripts/chain-initiator/stop.go b/scripts/chain-initiator/stop.go new file mode 100644 index 0000000000..efa04f682e --- /dev/null +++ b/scripts/chain-initiator/stop.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + "os/exec" +) + +func stop(cmd *exec.Cmd) { + // Stop the process + if cmd != nil && cmd.Process != nil { + err := cmd.Process.Kill() + if err != nil { + log.Fatalf(Red+"Failed to kill process: %v", err) + } + log.Println(Yellow + "Process killed successfully") + } +} diff --git a/scripts/chain-initiator/submit-upgrade-proposal.go b/scripts/chain-initiator/submit-upgrade-proposal.go new file mode 100644 index 0000000000..182f3ee545 --- /dev/null +++ b/scripts/chain-initiator/submit-upgrade-proposal.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "os/exec" + "strings" +) + +func submitUpgradeProposal(cmdPath, name, newVersion, upgradeHeight, homePath, keyringBackend, chainId, node, broadcastMode string) { + planName := newVersion + // Remove the "v" prefix if present + if strings.HasPrefix(planName, "v") { + planName = strings.TrimPrefix(planName, "v") + } + + // Command and arguments + args := []string{ + "tx", + "gov", + // "submit-legacy-proposal", // not available in v0.45.x + "submit-proposal", + "software-upgrade", + planName, + "--title", newVersion, + "--description", newVersion, + "--upgrade-height", upgradeHeight, + // "--no-validate", // not available in v0.45.x + "--from", name, + "--keyring-backend", keyringBackend, + "--chain-id", chainId, + "--node", node, + "--broadcast-mode", broadcastMode, + "--fees", "5000000000000000000000rowan", + "--gas", "1000000", + "--deposit", "50000000000000000000000rowan", + "--home", homePath, + "--yes", + } + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"Submitted upgrade proposal: %s, upgrade block height: %s", newVersion, upgradeHeight) +} diff --git a/scripts/chain-initiator/types.go b/scripts/chain-initiator/types.go new file mode 100644 index 0000000000..7b9d732224 --- /dev/null +++ b/scripts/chain-initiator/types.go @@ -0,0 +1,384 @@ +package main + +import ( + "encoding/json" + "time" + + admintypes "github.com/Sifchain/sifnode/x/admin/types" + clptypes "github.com/Sifchain/sifnode/x/clp/types" + dispensationtypes "github.com/Sifchain/sifnode/x/dispensation/types" + + // epochstypes "github.com/Sifchain/sifnode/x/epochs/types" + margintypes "github.com/Sifchain/sifnode/x/margin/types" + oracletypes "github.com/Sifchain/sifnode/x/oracle/types" + tokenregistrytypes "github.com/Sifchain/sifnode/x/tokenregistry/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authz "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + + // genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + ibcclienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + ibcconnectiontypes "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types" + ibcchanneltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" + ibctypes "github.com/cosmos/ibc-go/v4/modules/core/types" +) + +type Genesis struct { + GenesisTime time.Time `json:"genesis_time"` + ChainID string `json:"chain_id"` + InitialHeight string `json:"initial_height"` + ConsensusParams ConsensusParams `json:"consensus_params"` + AppHash string `json:"app_hash"` + AppState AppState `json:"app_state"` + // Include other top-level fields as needed +} + +type ConsensusParams struct { + Version Version `json:"version"` + Block Block `json:"block"` + Evidence Evidence `json:"evidence"` + Validator Validator `json:"validator"` +} + +type Version struct{} + +type Validator struct { + PubKeyTypes []string `json:"pub_key_types"` +} + +type Evidence struct { + MaxAgeNumBlocks string `json:"max_age_num_blocks"` + MaxAgeDuration string `json:"max_age_duration"` + MaxBytes string `json:"max_bytes,omitempty"` +} + +type Block struct { + MaxBytes string `json:"max_bytes"` + MaxGas string `json:"max_gas"` + TimeIotaMs string `json:"time_iota_ms"` +} + +type AppState struct { + Admin Admin `json:"admin"` + Auth Auth `json:"auth"` + AuthZ authz.GenesisState `json:"authz"` + Bank banktypes.GenesisState `json:"bank"` + Capability Capability `json:"capability"` + CLP CLP `json:"clp"` + Crisis crisistypes.GenesisState `json:"crisis"` + Dispensation Dispensation `json:"dispensation"` + Distribution Distribution `json:"distribution"` + // Epochs epochstypes.GenesisState `json:"epochs"` + Ethbridge struct{} `json:"ethbridge"` + Evidence EvidenceState `json:"evidence"` + Genutil Genutil `json:"genutil"` + Gov Gov `json:"gov"` + Ibc Ibc `json:"ibc"` + Margin Margin `json:"margin"` + Mint Mint `json:"mint"` + Oracle Oracle `json:"oracle"` + Params interface{} `json:"params"` + Slashing Slashing `json:"slashing"` + Staking Staking `json:"staking"` + TokenRegistry TokenRegistry `json:"tokenregistry"` + Transfer transfertypes.GenesisState `json:"transfer"` + Upgrade struct{} `json:"upgrade"` + // Include other fields as needed +} + +type Genutil struct { + // genutiltypes.GenesisState + + GenTxs []interface{} `json:"gen_txs"` +} + +type Admin struct { + admintypes.GenesisState + + AdminAccounts []AdminAccount `json:"admin_accounts"` +} + +type AdminAccount struct { + admintypes.AdminAccount + + AdminType string `json:"admin_type"` +} + +type TokenRegistry struct { + tokenregistrytypes.GenesisState + + Registry Registry `json:"registry"` +} + +type Registry struct { + tokenregistrytypes.Registry + + Entries []*RegistryEntry `json:"entries"` +} + +type RegistryEntry struct { + tokenregistrytypes.RegistryEntry + + Decimals json.Number `json:"decimals"` + Permissions []interface{} `json:"permissions"` +} + +type EvidenceState struct { + evidencetypes.GenesisState + + Evidence []interface{} `json:"evidence"` +} + +type Oracle struct { + oracletypes.GenesisState + + AddressWhitelist []interface{} `json:"address_whitelist"` + Prophecies []interface{} `json:"prophecies"` +} + +type Dispensation struct { + dispensationtypes.GenesisState + + DistributionRecords interface{} `json:"distribution_records"` + Distributions interface{} `json:"distributions"` + Claims interface{} `json:"claims"` +} + +type Capability struct { + capabilitytypes.GenesisState + + Index json.Number `json:"index"` + Owners []interface{} `json:"owners"` +} + +type Slashing struct { + slashingtypes.GenesisState + + Params SlashingParams `json:"params"` + SigningInfos []interface{} `json:"signing_infos"` + MissedBlocks []interface{} `json:"missed_blocks"` +} + +type SlashingParams struct { + slashingtypes.Params + + SignedBlocksWindow json.Number `json:"signed_blocks_window"` + DowntimeJailDuration string `json:"downtime_jail_duration"` +} + +type Mint struct { + minttypes.GenesisState + + Params MintParams `json:"params"` +} + +type MintParams struct { + minttypes.Params + + BlocksPerYear json.Number `json:"blocks_per_year"` +} + +type Gov struct { + govtypes.GenesisState + + StartingProposalId json.Number `json:"starting_proposal_id"` + Deposits []interface{} `json:"deposits"` + Votes []interface{} `json:"votes"` + Proposals []interface{} `json:"proposals"` + DepositParams GovDepositParams `json:"deposit_params"` + VotingParams GovVotingParams `json:"voting_params"` +} + +type GovDepositParams struct { + govtypes.DepositParams + + MaxDepositPeriod string `json:"max_deposit_period"` +} + +type GovVotingParams struct { + govtypes.VotingParams + + VotingPeriod string `json:"voting_period"` +} + +type Staking struct { + stakingtypes.GenesisState + + Params StakingParams `json:"params"` + LastValidatorPowers []interface{} `json:"last_validator_powers"` + Validators []interface{} `json:"validators"` + Delegations []interface{} `json:"delegations"` + UnbondingDelegations []interface{} `json:"unbonding_delegations"` + Redelegations []interface{} `json:"redelegations"` +} + +type StakingParams struct { + stakingtypes.Params + + UnbondingTime string `json:"unbonding_time"` + MaxValidators json.Number `json:"max_validators"` + MaxEntries json.Number `json:"max_entries"` + HistoricalEntries json.Number `json:"historical_entries"` +} + +type Distribution struct { + distributiontypes.GenesisState + + DelegatorWithdrawInfos []interface{} `json:"delegator_withdraw_infos"` + OutstandingRewards []interface{} `json:"outstanding_rewards"` + ValidatorAccumulatedCommissions []interface{} `json:"validator_accumulated_commissions"` + ValidatorHistoricalRewards []interface{} `json:"validator_historical_rewards"` + ValidatorCurrentRewards []interface{} `json:"validator_current_rewards"` + DelegatorStartingInfos []interface{} `json:"delegator_starting_infos"` + ValidatorSlashEvents []interface{} `json:"validator_slash_events"` +} + +type Ibc struct { + ibctypes.GenesisState + + ClientGenesis ClientGenesis `json:"client_genesis"` + ConnectionGenesis ConnectionGenesis `json:"connection_genesis"` + ChannelGenesis ChannelGenesis `json:"channel_genesis"` +} + +type ClientGenesis struct { + ibcclienttypes.GenesisState + + Clients []interface{} `json:"clients"` + ClientsConsensus []interface{} `json:"clients_consensus"` + ClientsMetadata []interface{} `json:"clients_metadata"` + Params ibcclienttypes.Params `json:"params"` + NextClientSequence json.Number `json:"next_client_sequence"` +} + +type ConnectionGenesis struct { + ibcconnectiontypes.GenesisState + + Connections []interface{} `json:"connections"` + ClientConnectionPaths []interface{} `json:"client_connection_paths"` + NextConnectionSequence json.Number `json:"next_connection_sequence"` + Params ConnectionGenesisParams `json:"params"` +} + +type ConnectionGenesisParams struct { + ibcconnectiontypes.Params + + MaxExpectedTimePerBlock json.Number `json:"max_expected_time_per_block"` +} + +type ChannelGenesis struct { + ibcchanneltypes.GenesisState + + Channels []interface{} `json:"channels"` + Acknowledgements []interface{} `json:"acknowledgements"` + Commitments []interface{} `json:"commitments"` + Receipts []interface{} `json:"receipts"` + SendSequences []interface{} `json:"send_sequences"` + RecvSequences []interface{} `json:"recv_sequences"` + AckSequences []interface{} `json:"ack_sequences"` + NextChannelSequence json.Number `json:"next_channel_sequence"` +} + +type CLPParams struct { + clptypes.Params + + MinCreatePoolThreshold json.Number `json:"min_create_pool_threshold"` +} + +type CLP struct { + clptypes.GenesisState + + Params CLPParams `json:"params"` + PoolList []interface{} `json:"pool_list"` + LiquidityProviders []interface{} `json:"liquidity_providers"` + RewardsBucketList []interface{} `json:"rewards_bucket_list,omitempty"` // omitted for now +} + +type Margin struct { + margintypes.GenesisState + + Params MarginParams `json:"params"` +} + +type MarginParams struct { + margintypes.Params + + EpochLength json.Number `json:"epoch_length"` + MaxOpenPositions json.Number `json:"max_open_positions"` +} + +type AuthParams struct { + authtypes.Params + + MaxMemoCharacters json.Number `json:"max_memo_characters"` + TxSigLimit json.Number `json:"tx_sig_limit"` + TxSizeCostPerByte json.Number `json:"tx_size_cost_per_byte"` + SigVerifyCostEd25519 json.Number `json:"sig_verify_cost_ed25519"` + SigVerifyCostSecp256K1 json.Number `json:"sig_verify_cost_secp256k1"` +} + +type BaseAccount struct { + Address string `json:"address"` + PubKey interface{} `json:"pub_key"` + AccountNumber json.Number `json:"account_number"` + Sequence json.Number `json:"sequence"` +} + +type ModuleAccount struct { + BaseAccount BaseAccount `json:"base_account"` + Name string `json:"name"` + Permissions []string `json:"permissions"` +} + +type Account struct { + *BaseAccount + *ModuleAccount + + Type string `json:"@type"` +} + +type Auth struct { + authtypes.GenesisState + + Params AuthParams `json:"params"` + Accounts []Account `json:"accounts"` +} + +// KeyOutput represents the JSON structure of the output from the add key command +type KeyOutput struct { + Name string `json:"name"` + Type string `json:"type"` + Address string `json:"address"` + PubKey string `json:"pubkey"` + Mnemonic string `json:"mnemonic"` +} + +// StatusOutput represents the JSON structure of the output from the status command +type StatusOutput struct { + SyncInfo struct { + LatestBlockHeight string `json:"latest_block_height"` + } `json:"SyncInfo"` +} + +// ProposalsOutput represents the JSON structure of the output from the query proposals command +type ProposalsOutput struct { + Proposals []struct { + ProposalId string `json:"proposal_id"` + } `json:"proposals"` +} + +// Colors +const ( + Red = "\033[31m" + Green = "\033[32m" + Yellow = "\033[33m" +) diff --git a/scripts/chain-initiator/update-genesis.go b/scripts/chain-initiator/update-genesis.go new file mode 100644 index 0000000000..b276de06e3 --- /dev/null +++ b/scripts/chain-initiator/update-genesis.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func updateGenesis(validatorBalance, homePath, genesisFilePath string) { + genesis, err := readGenesisFile(genesisFilePath) + if err != nil { + log.Fatalf(Red+"Error reading genesis file: %v", err) + } + + genesisInitFilePath := homePath + "/config/genesis.json" + genesisInit, err := readGenesisFile(genesisInitFilePath) + if err != nil { + log.Fatalf(Red+"Error reading initial genesis file: %v", err) + } + + filterAccountAddresses := []string{ + "sif1harggtyrlukcfrtmpgjzptsnaedcdh38qqknp2", // multisig account with missing pubkeys + } + filterBalanceAddresses := []string{ + "sif1harggtyrlukcfrtmpgjzptsnaedcdh38qqknp2", + authtypes.NewModuleAddress("distribution").String(), + authtypes.NewModuleAddress("bonded_tokens_pool").String(), + authtypes.NewModuleAddress("not_bonded_tokens_pool").String(), + } + + var coinsToRemove sdk.Coins + + genesis.AppState.Auth.Accounts = filterAccounts(genesis.AppState.Auth.Accounts, filterAccountAddresses) + genesis.AppState.Bank.Balances, coinsToRemove = filterBalances(genesis.AppState.Bank.Balances, filterBalanceAddresses) + + newValidatorBalance, ok := sdk.NewIntFromString(validatorBalance) + if !ok { + panic(Red + "invalid number") + } + newValidatorBalanceCoin := sdk.NewCoin("rowan", newValidatorBalance) + + // update supply + genesis.AppState.Bank.Supply = genesis.AppState.Bank.Supply.Sub(coinsToRemove).Add(newValidatorBalanceCoin) + + // Add new validator account and balance + genesis.AppState.Auth.Accounts = append(genesis.AppState.Auth.Accounts, genesisInit.AppState.Auth.Accounts[0]) + genesis.AppState.Bank.Balances = append(genesis.AppState.Bank.Balances, genesisInit.AppState.Bank.Balances[0]) + + // reset staking data + stakingParams := genesis.AppState.Staking.Params + genesis.AppState.Staking = genesisInit.AppState.Staking + genesis.AppState.Staking.Params = stakingParams + + // reset slashing data + genesis.AppState.Slashing = genesisInit.AppState.Slashing + + // reset distribution data + genesis.AppState.Distribution = genesisInit.AppState.Distribution + + // set genutil from genesisInit + genesis.AppState.Genutil = genesisInit.AppState.Genutil + + // update voting period + genesis.AppState.Gov.VotingParams.VotingPeriod = "10s" + genesis.AppState.Gov.DepositParams.MaxDepositPeriod = "10s" + + outputFilePath := homePath + "/config/genesis.json" + if err := writeGenesisFile(outputFilePath, genesis); err != nil { + log.Fatalf(Red+"Error writing genesis file: %v", err) + } +} diff --git a/scripts/chain-initiator/validate-genesis.go b/scripts/chain-initiator/validate-genesis.go new file mode 100644 index 0000000000..b32eeee608 --- /dev/null +++ b/scripts/chain-initiator/validate-genesis.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func validateGenesis(cmdPath, homePath string) { + // Command and arguments + args := []string{"validate-genesis", "--home", homePath} + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"validate genesis with home path %s successfully", homePath) +} diff --git a/scripts/chain-initiator/vote-on-upgrade-proposal.go b/scripts/chain-initiator/vote-on-upgrade-proposal.go new file mode 100644 index 0000000000..fbd148d5be --- /dev/null +++ b/scripts/chain-initiator/vote-on-upgrade-proposal.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + "os/exec" +) + +func voteOnUpgradeProposal(cmdPath, name, proposalId, homePath, keyringBackend, chainId, node, broadcastMode string) { + // Command and arguments + args := []string{ + "tx", "gov", "vote", proposalId, "yes", + "--from", name, + "--keyring-backend", keyringBackend, + "--chain-id", chainId, + "--node", node, + "--broadcast-mode", broadcastMode, + "--fees", "100000000000000000rowan", + "--gas", "1000000", + "--home", homePath, + "--yes", + } + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf(Yellow+"Voted on upgrade proposal: %s", proposalId) +} diff --git a/scripts/chain-initiator/wait-for-block-height.go b/scripts/chain-initiator/wait-for-block-height.go new file mode 100644 index 0000000000..abc7699e72 --- /dev/null +++ b/scripts/chain-initiator/wait-for-block-height.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + "strconv" + "time" +) + +func waitForBlockHeight(cmdPath, node, height string) { + targetBlockHeight, err := strconv.Atoi(height) + if err != nil { + log.Fatalf(Red+"Error converting target block height to integer: %v", err) + } + + // Now, wait for the block height + for { + var blockHeightStr string + blockHeightStr, err = queryBlockHeight(cmdPath, node) + if err == nil { + newBlockHeight, err := strconv.Atoi(blockHeightStr) + if err == nil && newBlockHeight >= targetBlockHeight { + break + } + } + log.Println(Yellow+"Waiting for block height", height, "...") + time.Sleep(5 * time.Second) // Wait 5 seconds before retrying + } + + log.Printf(Yellow+"Block height %d reached", targetBlockHeight) +} diff --git a/scripts/chain-initiator/wait-for-next-block.go b/scripts/chain-initiator/wait-for-next-block.go new file mode 100644 index 0000000000..a6c568a6d1 --- /dev/null +++ b/scripts/chain-initiator/wait-for-next-block.go @@ -0,0 +1,44 @@ +package main + +import ( + "log" + "strconv" + "time" +) + +func waitForNextBlock(cmdPath, node string) { + var currentBlockHeight, newBlockHeight int + var err error + + // First, get the current block height + for { + var blockHeightStr string + blockHeightStr, err = queryBlockHeight(cmdPath, node) + if err == nil { + currentBlockHeight, err = strconv.Atoi(blockHeightStr) + if err == nil && currentBlockHeight > 0 { + break + } + } + log.Println(Yellow + "Waiting for current block height...") + time.Sleep(5 * time.Second) // Wait 5 seconds before retrying + } + + log.Printf(Yellow+"Current Block Height: %d", currentBlockHeight) + + // Now, wait for the block height to increase + for { + var blockHeightStr string + blockHeightStr, err = queryBlockHeight(cmdPath, node) + if err == nil { + newBlockHeight, err = strconv.Atoi(blockHeightStr) + if err == nil && newBlockHeight > currentBlockHeight { + break + } + } + log.Println(Yellow + "Waiting for next block height...") + time.Sleep(5 * time.Second) // Wait 5 seconds before retrying + } + + log.Printf(Yellow+"New Block Height: %d", newBlockHeight) +} diff --git a/scripts/chain-initiator/wait-for-node-to-start.go b/scripts/chain-initiator/wait-for-node-to-start.go new file mode 100644 index 0000000000..9cc25c658a --- /dev/null +++ b/scripts/chain-initiator/wait-for-node-to-start.go @@ -0,0 +1,21 @@ +package main + +import ( + "log" + "time" +) + +func waitForNodeToStart(node string) { + timeout := 60 * time.Second + start := time.Now() + + // Wait for the node to be running with timout + for !isNodeRunning(node) { + if time.Since(start) > timeout { + log.Fatalf(Red + "Node did not start within the specified timeout") + } + log.Println(Yellow + "Waiting for node to start...") + time.Sleep(5 * time.Second) + } + log.Println(Yellow + "Node is running.") +} diff --git a/scripts/chain-initiator/write-genesis-file.go b/scripts/chain-initiator/write-genesis-file.go new file mode 100644 index 0000000000..d9334ee021 --- /dev/null +++ b/scripts/chain-initiator/write-genesis-file.go @@ -0,0 +1,28 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" +) + +func writeGenesisFile(filePath string, genesis Genesis) error { + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer file.Close() + + writer := bufio.NewWriter(file) + defer writer.Flush() + + encoder := json.NewEncoder(writer) + // encoder.SetIndent("", " ") // disable for now + + if err := encoder.Encode(genesis); err != nil { + return fmt.Errorf("error encoding JSON: %w", err) + } + + return nil +}