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..8e5b822513 --- /dev/null +++ b/scripts/chain-initiator/download-and-run-version.go @@ -0,0 +1,87 @@ +package main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/exec" + "regexp" + "strings" +) + +func downloadAndRunVersion(binaryURL string, skipDownload bool) (path string, version string, err error) { + if skipDownload { + // Extract version from the URL + re := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?`) + versionMatches := re.FindStringSubmatch(binaryURL) + 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(binaryURL) + 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/get-args.go b/scripts/chain-initiator/get-args.go index bf3a95fb58..f3a457b077 100644 --- a/scripts/chain-initiator/get-args.go +++ b/scripts/chain-initiator/get-args.go @@ -4,15 +4,20 @@ import ( "log" ) -func getArgs(args []string) (snapshotUrl, newVersion string) { +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") } - newVersion = args[1] // v0.1.0 - if newVersion == "" { - log.Fatalf(Red + "new version 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 index 790b1b33dd..2b7b0c5273 100644 --- a/scripts/chain-initiator/get-flags.go +++ b/scripts/chain-initiator/get-flags.go @@ -7,24 +7,29 @@ import ( ) const ( - flagHome = "home" - flagCmd = "cmd" - flagSkipSnapshot = "skip-snapshot" - flagSkipChainInit = "skip-chain-init" - flagSkipNodeStart = "skip-node-start" + 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, cmdPath string, skipSnapshot, skipChainInit, skipNodeStart bool) { +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") } - cmdPath, _ = cmd.Flags().GetString(flagCmd) - if cmdPath == "" { - log.Fatalf(Red + "cmd path is required") - } - skipSnapshot, _ = cmd.Flags().GetBool(flagSkipSnapshot) if skipSnapshot { log.Printf(Yellow + "skipping snapshot retrieval") @@ -40,5 +45,60 @@ func getFlags(cmd *cobra.Command) (homePath, cmdPath string, skipSnapshot, skipC 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/initiator.go b/scripts/chain-initiator/initiator.go index 5f0c7e1744..f93477d6a6 100644 --- a/scripts/chain-initiator/initiator.go +++ b/scripts/chain-initiator/initiator.go @@ -3,48 +3,55 @@ package main import ( "log" "os" + "time" app "github.com/Sifchain/sifnode/app" "github.com/spf13/cobra" ) -const ( - 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 [snapshot_url] [new_version] [flags]", + 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(2), // Expect exactly 1 argument + Args: cobra.ExactArgs(3), // Expect exactly 1 argument Run: func(cmd *cobra.Command, args []string) { - snapshotUrl, newVersion := getArgs(args) - homePath, cmdPath, skipSnapshot, skipChainInit, skipNodeStart := getFlags(cmd) + 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(cmdPath, moniker, chainId, homePath) + initChain(oldBinaryPath, moniker, chainId, homePath) // retrieve the snapshot retrieveSnapshot(snapshotUrl, homePath) // export genesis file - export(cmdPath, homePath, genesisFilePath) + export(oldBinaryPath, homePath, genesisFilePath) } if !skipChainInit { @@ -52,45 +59,78 @@ func main() { removeHome(homePath) // init chain - initChain(cmdPath, moniker, chainId, homePath) + initChain(oldBinaryPath, moniker, chainId, homePath) // add validator key - validatorAddress := addKey(cmdPath, validatorKeyName, homePath, keyringBackend) + validatorAddress := addKey(oldBinaryPath, validatorKeyName, homePath, keyringBackend) // add genesis account - addGenesisAccount(cmdPath, validatorAddress, validatorBalance, homePath) + addGenesisAccount(oldBinaryPath, validatorAddress, validatorBalance, homePath) // generate genesis tx - genTx(cmdPath, validatorKeyName, validatorSelfDelegation, chainId, homePath, keyringBackend) + genTx(oldBinaryPath, validatorKeyName, validatorSelfDelegation, chainId, homePath, keyringBackend) // collect genesis txs - collectGentxs(cmdPath, homePath) + collectGentxs(oldBinaryPath, homePath) // validate genesis - validateGenesis(cmdPath, homePath) + validateGenesis(oldBinaryPath, homePath) // update genesis - updateGenesis(validatorBalance, homePath) + updateGenesis(validatorBalance, homePath, genesisFilePath) } if !skipNodeStart { // start chain - startCmd := start(cmdPath, homePath) + oldBinaryCmd := start(oldBinaryPath, homePath) // wait for node to start waitForNodeToStart(node) // wait for next block - waitForNextBlock(cmdPath, node) + waitForNextBlock(oldBinaryPath, node) + + if skipProposal { + // listen for signals + listenForSignals(oldBinaryCmd) + return + } // query and calculate upgrade block height - upgradeBlockHeight := queryAndCalcUpgradeBlockHeight(cmdPath, node) + 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(cmdPath, validatorKeyName, newVersion, upgradeBlockHeight, homePath, keyringBackend, chainId, node, broadcastMode) + 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(startCmd) + listenForSignals(newBinaryCmd) } }, } @@ -98,11 +138,21 @@ func main() { // 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") 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/listen-for-signals.go b/scripts/chain-initiator/listen-for-signals.go index 7ec26f26fc..9eb951f15d 100644 --- a/scripts/chain-initiator/listen-for-signals.go +++ b/scripts/chain-initiator/listen-for-signals.go @@ -1,7 +1,6 @@ package main import ( - "log" "os" "os/exec" "os/signal" @@ -17,11 +16,5 @@ func listenForSignals(cmd *exec.Cmd) { <-sigChan // Stop the process when a signal is received - 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") - } + 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 index 4ead6e29fa..c9cf02092a 100644 --- a/scripts/chain-initiator/query-and-calc-upgrade-block-height.go +++ b/scripts/chain-initiator/query-and-calc-upgrade-block-height.go @@ -19,7 +19,7 @@ func queryAndCalcUpgradeBlockHeight(cmdPath, node string) string { } // set upgrade block height - upgradeBlockHeight := blockHeightInt + 100 + upgradeBlockHeight := blockHeightInt + 5 // return upgrade block height as a string return strconv.Itoa(upgradeBlockHeight) 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/start.go b/scripts/chain-initiator/start.go index c56477be8d..2215cc1189 100644 --- a/scripts/chain-initiator/start.go +++ b/scripts/chain-initiator/start.go @@ -4,6 +4,7 @@ import ( "log" "os" "os/exec" + "syscall" ) func start(cmdPath, homePath string) *exec.Cmd { @@ -12,15 +13,23 @@ func start(cmdPath, homePath string) *exec.Cmd { // Set up the command cmd := exec.Command(cmdPath, args...) - - // Attach command's stdout and stderr to os.Stdout and os.Stderr cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // Execute the command and stream the output in a goroutine to avoid blocking go func() { - if err := cmd.Run(); err != nil { - log.Fatalf(Red+"Command execution failed: %v", err) + 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) } }() 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/types.go b/scripts/chain-initiator/types.go index 6b332b33a9..7b9d732224 100644 --- a/scripts/chain-initiator/types.go +++ b/scripts/chain-initiator/types.go @@ -369,6 +369,13 @@ type StatusOutput struct { } `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" diff --git a/scripts/chain-initiator/update-genesis.go b/scripts/chain-initiator/update-genesis.go index 63336bd2d7..b276de06e3 100644 --- a/scripts/chain-initiator/update-genesis.go +++ b/scripts/chain-initiator/update-genesis.go @@ -7,7 +7,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) -func updateGenesis(validatorBalance, homePath string) { +func updateGenesis(validatorBalance, homePath, genesisFilePath string) { genesis, err := readGenesisFile(genesisFilePath) if err != nil { log.Fatalf(Red+"Error reading genesis file: %v", err) @@ -62,8 +62,8 @@ func updateGenesis(validatorBalance, homePath string) { genesis.AppState.Genutil = genesisInit.AppState.Genutil // update voting period - genesis.AppState.Gov.VotingParams.VotingPeriod = "60s" - genesis.AppState.Gov.DepositParams.MaxDepositPeriod = "60s" + genesis.AppState.Gov.VotingParams.VotingPeriod = "10s" + genesis.AppState.Gov.DepositParams.MaxDepositPeriod = "10s" outputFilePath := homePath + "/config/genesis.json" if err := writeGenesisFile(outputFilePath, genesis); err != nil { 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 index d163cc9e81..a6c568a6d1 100644 --- a/scripts/chain-initiator/wait-for-next-block.go +++ b/scripts/chain-initiator/wait-for-next-block.go @@ -20,7 +20,8 @@ func waitForNextBlock(cmdPath, node string) { break } } - time.Sleep(5 * time.Second) // Wait 5 second before retrying + 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) @@ -35,7 +36,8 @@ func waitForNextBlock(cmdPath, node string) { break } } - time.Sleep(5 * time.Second) // Wait a second before retrying + 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)