diff --git a/scripts/chain-initiator/gen-tx.go b/scripts/chain-initiator/gen-tx.go index bb6e351041..72805d7274 100644 --- a/scripts/chain-initiator/gen-tx.go +++ b/scripts/chain-initiator/gen-tx.go @@ -5,9 +5,9 @@ import ( "os/exec" ) -func genTx(cmdPath, name, balance, chainId, homePath, keyringBackend string) { +func genTx(cmdPath, name, amount, chainId, homePath, keyringBackend string) { // Command and arguments - args := []string{"gentx", name, balance + "rowan", "--chain-id", chainId, "--home", homePath, "--keyring-backend", keyringBackend} + 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 { @@ -15,5 +15,5 @@ func genTx(cmdPath, name, balance, chainId, homePath, keyringBackend string) { } // If execution reaches here, the command was successful - log.Printf("gen tx with name %s, balance: %s, chain id %s, home path %s and keyring backend %s successfully", name, balance, chainId, homePath, keyringBackend) + log.Printf("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..76d0af533d --- /dev/null +++ b/scripts/chain-initiator/get-args.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" +) + +func getArgs(args []string) (snapshotUrl, newVersion string) { + snapshotUrl = args[0] // https://snapshots.polkachu.com/snapshots/sifchain/sifchain_15048938.tar.lz4 + if snapshotUrl == "" { + log.Fatalf("snapshot url is required") + } + + newVersion = args[1] // v0.1.0 + if newVersion == "" { + log.Fatalf("new version 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..2dd7904469 --- /dev/null +++ b/scripts/chain-initiator/get-flags.go @@ -0,0 +1,26 @@ +package main + +import ( + "log" + + "github.com/spf13/cobra" +) + +const ( + flagHome = "home" + flagCmd = "cmd" +) + +func getFlags(cmd *cobra.Command) (homePath, cmdPath string) { + homePath, _ = cmd.Flags().GetString(flagHome) + if homePath == "" { + log.Fatalf("home path is required") + } + + cmdPath, _ = cmd.Flags().GetString(flagCmd) + if cmdPath == "" { + log.Fatalf("cmd path is required") + } + + return +} diff --git a/scripts/chain-initiator/initiator.go b/scripts/chain-initiator/initiator.go index a1dc365b5d..0434d8dd2c 100644 --- a/scripts/chain-initiator/initiator.go +++ b/scripts/chain-initiator/initiator.go @@ -2,32 +2,34 @@ package main import ( "log" + "os" app "github.com/Sifchain/sifnode/app" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/spf13/cobra" ) const ( - moniker = "node" - chainId = "sifchain-1" - keyringBackend = "test" - validatorKeyName = "validator" - validatorBalance = "4000000000000000000000000000" - genesisFilePath = "/tmp/genesis.json" + moniker = "node" + chainId = "sifchain-1" + keyringBackend = "test" + validatorKeyName = "validator" + validatorBalance = "4000000000000000000000000000" + validatorSelfDelegation = "1000000000000000000000000000" + genesisFilePath = "/tmp/genesis.json" + node = "tcp://localhost:26657" + broadcastMode = "block" ) func main() { var rootCmd = &cobra.Command{ - Use: "initiator [cmd_path] [home_path] [snapshot_url]]", - Short: "Chain Initiator is a tool for modifying genesis files", - Long: `A tool for performing various operations on genesis files of a blockchain setup.`, - Args: cobra.ExactArgs(3), // Expect exactly two arguments + Use: "initiator [snapshot_url] [new_version] [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(2), // Expect exactly 1 argument Run: func(cmd *cobra.Command, args []string) { - cmdPath := args[0] // sifnoded - homePath := args[1] // /tmp/node - snapshotUrl := args[2] // https://snapshots.polkachu.com/snapshots/sifchain/sifchain_15048938.tar.lz4 + snapshotUrl, newVersion := getArgs(args) + _ = snapshotUrl + homePath, cmdPath := getFlags(cmd) // set address prefix app.SetConfig(false) @@ -57,7 +59,7 @@ func main() { addGenesisAccount(cmdPath, validatorAddress, validatorBalance, homePath) // generate genesis tx - genTx(cmdPath, validatorKeyName, validatorBalance, chainId, homePath, keyringBackend) + genTx(cmdPath, validatorKeyName, validatorSelfDelegation, chainId, homePath, keyringBackend) // collect genesis txs collectGentxs(cmdPath, homePath) @@ -65,69 +67,32 @@ func main() { // validate genesis validateGenesis(cmdPath, homePath) - genesis, err := readGenesisFile(genesisFilePath) - if err != nil { - log.Fatalf("Error reading genesis file: %v", err) - } + // update genesis + updateGenesis(validatorBalance, homePath) - genesisInitFilePath := homePath + "/config/genesis.json" - genesisInit, err := readGenesisFile(genesisInitFilePath) - if err != nil { - log.Fatalf("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("4000000000000000000000000000") - if !ok { - panic("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 + // start chain + startCmd := start(cmdPath, homePath) - // reset distribution data - genesis.AppState.Distribution = genesisInit.AppState.Distribution + // wait for node to start + waitForNodeToStart(node) - // set genutil from genesisInit - genesis.AppState.Genutil = genesisInit.AppState.Genutil + // query and calculate upgrade block height + upgradeBlockHeight := queryAndCalcUpgradeBlockHeight(cmdPath, node) - outputFilePath := homePath + "/config/genesis.json" - if err := writeGenesisFile(outputFilePath, genesis); err != nil { - log.Fatalf("Error writing genesis file: %v", err) - } + // submit upgrade proposal + submitUpgradeProposal(cmdPath, validatorKeyName, newVersion, upgradeBlockHeight, homePath, keyringBackend, chainId, node, broadcastMode) - // start chain - start(cmdPath, homePath) + // listen for signals + listenForSignals(startCmd) }, } + // get HOME environment variable + homeEnv, _ := os.LookupEnv("HOME") + + rootCmd.PersistentFlags().String(flagCmd, homeEnv+"/go/bin/sifnoded", "path to sifnoded") + rootCmd.PersistentFlags().String(flagHome, homeEnv+"/.sifnoded", "home directory") + if err := rootCmd.Execute(); err != nil { log.Fatalf("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..910257be6e --- /dev/null +++ b/scripts/chain-initiator/listen-for-signals.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "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 + if cmd != nil && cmd.Process != nil { + err := cmd.Process.Kill() + if err != nil { + log.Fatalf("Failed to kill process: %v", err) + } + log.Println("Process killed successfully") + } +} 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..f9ee37c41b --- /dev/null +++ b/scripts/chain-initiator/query-and-calc-upgrade-block-height.go @@ -0,0 +1,23 @@ +package main + +import ( + "log" + "strconv" +) + +func queryAndCalcUpgradeBlockHeight(cmdPath, node string) string { + // query block height + blockHeight := queryBlockHeight(cmdPath, node) + + // Convert blockHeight from string to int + blockHeightInt, err := strconv.Atoi(blockHeight) + if err != nil { + log.Fatalf("Failed to convert blockHeight to integer: %v", err) + } + + // set upgrade block height + upgradeBlockHeight := blockHeightInt + 100 + + // 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..8cb8cc8179 --- /dev/null +++ b/scripts/chain-initiator/query-block-height.go @@ -0,0 +1,26 @@ +package main + +import ( + "encoding/json" + "log" + "os/exec" +) + +func queryBlockHeight(cmdPath, node string) string { + // Command and arguments + args := []string{"status", "--node", node} + + // Execute the command + output, err := exec.Command(cmdPath, args...).CombinedOutput() + if err != nil { + log.Fatalf("Command execution failed: %v", err) + } + + // Unmarshal the JSON output + var statusOutput StatusOutput + if err := json.Unmarshal(output, &statusOutput); err != nil { + log.Fatalf("Failed to unmarshal JSON output: %v", err) + } + + return statusOutput.SyncInfo.LatestBlockHeight +} diff --git a/scripts/chain-initiator/start.go b/scripts/chain-initiator/start.go index bd153c3fee..20349c6bf4 100644 --- a/scripts/chain-initiator/start.go +++ b/scripts/chain-initiator/start.go @@ -6,7 +6,7 @@ import ( "os/exec" ) -func start(cmdPath, homePath string) { +func start(cmdPath, homePath string) *exec.Cmd { // Command and arguments args := []string{"start", "--home", homePath} @@ -17,8 +17,12 @@ func start(cmdPath, homePath string) { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - // Execute the command and stream the output - if err := cmd.Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) - } + // Execute the command and stream the output in a goroutine to avoid blocking + go func() { + if err := cmd.Run(); err != nil { + log.Fatalf("Command execution failed: %v", err) + } + }() + + return cmd } diff --git a/scripts/chain-initiator/submit-upgrade-proposal.go b/scripts/chain-initiator/submit-upgrade-proposal.go new file mode 100644 index 0000000000..468db9ee56 --- /dev/null +++ b/scripts/chain-initiator/submit-upgrade-proposal.go @@ -0,0 +1,51 @@ +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 + output, err := exec.Command(cmdPath, args...).CombinedOutput() + if err != nil { + log.Fatalf("Command execution failed: %v", err) + } + + // print the output + log.Printf("%s", output) + + // If execution reaches here, the command was successful + log.Printf("Submitted upgrade proposal: %s, upgrade block height: %s", newVersion, upgradeHeight) +} diff --git a/scripts/chain-initiator/types.go b/scripts/chain-initiator/types.go index 9fcd9f4166..86801a5b78 100644 --- a/scripts/chain-initiator/types.go +++ b/scripts/chain-initiator/types.go @@ -361,3 +361,10 @@ type KeyOutput struct { 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"` +} diff --git a/scripts/chain-initiator/update-genesis.go b/scripts/chain-initiator/update-genesis.go new file mode 100644 index 0000000000..45695fcf4e --- /dev/null +++ b/scripts/chain-initiator/update-genesis.go @@ -0,0 +1,68 @@ +package main + +import ( + "log" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func updateGenesis(validatorBalance, homePath string) { + genesis, err := readGenesisFile(genesisFilePath) + if err != nil { + log.Fatalf("Error reading genesis file: %v", err) + } + + genesisInitFilePath := homePath + "/config/genesis.json" + genesisInit, err := readGenesisFile(genesisInitFilePath) + if err != nil { + log.Fatalf("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("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 + + outputFilePath := homePath + "/config/genesis.json" + if err := writeGenesisFile(outputFilePath, genesis); err != nil { + log.Fatalf("Error writing genesis file: %v", err) + } +} 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..debc811f8b --- /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("Node did not start within the specified timeout") + } + log.Println("Waiting for node to start...") + time.Sleep(5 * time.Second) + } + log.Println("Node is running.") +}