From ba28cf7e96eb250460298a70cb5305c00da2d5b6 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:37:50 +0100 Subject: [PATCH 1/9] feat: add chain-initiator tool to use mainnet snapshot in localnet --- .../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 + scripts/chain-initiator/filter-accounts.go | 17 + scripts/chain-initiator/filter-balances.go | 24 ++ scripts/chain-initiator/gen-tx.go | 19 + scripts/chain-initiator/init-chain.go | 19 + scripts/chain-initiator/initiator.go | 121 ++++++ scripts/chain-initiator/read-genesis-file.go | 23 ++ scripts/chain-initiator/remove-home.go | 19 + .../chain-initiator/should-filter-account.go | 15 + scripts/chain-initiator/start.go | 24 ++ scripts/chain-initiator/types.go | 362 ++++++++++++++++++ scripts/chain-initiator/validate-genesis.go | 19 + scripts/chain-initiator/write-genesis-file.go | 28 ++ 16 files changed, 795 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/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/init-chain.go create mode 100644 scripts/chain-initiator/initiator.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/should-filter-account.go create mode 100644 scripts/chain-initiator/start.go create mode 100644 scripts/chain-initiator/types.go create mode 100644 scripts/chain-initiator/validate-genesis.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..6deed9611c --- /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("Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf("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..288053f2a7 --- /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("Command execution failed: %v", err) + } + + // Unmarshal the JSON output + var keyOutput KeyOutput + if err := json.Unmarshal(output, &keyOutput); err != nil { + log.Fatalf("Failed to unmarshal JSON output: %v", err) + } + + // Log the address + log.Printf("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..07fe9834ad --- /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("Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf("collect gen txs with home path %s successfully", homePath) +} diff --git a/scripts/chain-initiator/filter-accounts.go b/scripts/chain-initiator/filter-accounts.go new file mode 100644 index 0000000000..7c6bd83615 --- /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{}{} + } + + var 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..9f6f217e2d --- /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{}{} + } + + var 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..bb6e351041 --- /dev/null +++ b/scripts/chain-initiator/gen-tx.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + "os/exec" +) + +func genTx(cmdPath, name, balance, chainId, homePath, keyringBackend string) { + // Command and arguments + args := []string{"gentx", name, balance + "rowan", "--chain-id", chainId, "--home", homePath, "--keyring-backend", keyringBackend} + + // Execute the command + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf("Command execution failed: %v", err) + } + + // 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) +} diff --git a/scripts/chain-initiator/init-chain.go b/scripts/chain-initiator/init-chain.go new file mode 100644 index 0000000000..3beaa6b1cf --- /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("Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf("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..eca05f7725 --- /dev/null +++ b/scripts/chain-initiator/initiator.go @@ -0,0 +1,121 @@ +package main + +import ( + "log" + + 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" +) + +func main() { + var rootCmd = &cobra.Command{ + Use: "initiator [cmd_path] [home_path] [genesis_file_path]", + 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 + Run: func(cmd *cobra.Command, args []string) { + cmdPath := args[0] // sifnoded + homePath := args[1] // /tmp/node + genesisFilePath := args[2] // /tmp/genesis.json + + // set address prefix + app.SetConfig(false) + + // remove home path + removeHome(homePath) + + // init chain + initChain(cmdPath, moniker, chainId, homePath) + + // add validator key + validatorAddress := addKey(cmdPath, validatorKeyName, homePath, keyringBackend) + + // add genesis account + addGenesisAccount(cmdPath, validatorAddress, validatorBalance, homePath) + + // generate genesis tx + genTx(cmdPath, validatorKeyName, validatorBalance, chainId, homePath, keyringBackend) + + // collect genesis txs + collectGentxs(cmdPath, homePath) + + // validate genesis + validateGenesis(cmdPath, homePath) + + 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("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 + + // 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) + } + + // start chain + start(cmdPath, homePath) + }, + } + + if err := rootCmd.Execute(); err != nil { + log.Fatalf("Error executing command: %v", err) + } +} 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..c370e28995 --- /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("Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf("removed home path %s successfully", 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..bd153c3fee --- /dev/null +++ b/scripts/chain-initiator/start.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "os" + "os/exec" +) + +func start(cmdPath, homePath string) { + // Command and arguments + args := []string{"start", "--home", homePath} + + // 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 + if err := cmd.Run(); err != nil { + log.Fatalf("Command execution failed: %v", err) + } +} diff --git a/scripts/chain-initiator/types.go b/scripts/chain-initiator/types.go new file mode 100644 index 0000000000..02d2afe7a1 --- /dev/null +++ b/scripts/chain-initiator/types.go @@ -0,0 +1,362 @@ +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"` +} + +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"` +} diff --git a/scripts/chain-initiator/validate-genesis.go b/scripts/chain-initiator/validate-genesis.go new file mode 100644 index 0000000000..a8e24d02fe --- /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("Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf("validate genesis with home path %s successfully", homePath) +} 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 +} From 50cc47d738967416f2f35901b5f990ea9196c521 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Fri, 8 Dec 2023 05:56:46 +0100 Subject: [PATCH 2/9] test: linter --- scripts/chain-initiator/filter-accounts.go | 2 +- scripts/chain-initiator/filter-balances.go | 2 +- scripts/chain-initiator/types.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/chain-initiator/filter-accounts.go b/scripts/chain-initiator/filter-accounts.go index 7c6bd83615..5929930e93 100644 --- a/scripts/chain-initiator/filter-accounts.go +++ b/scripts/chain-initiator/filter-accounts.go @@ -6,7 +6,7 @@ func filterAccounts(accounts []Account, filterAddresses []string) []Account { filterMap[addr] = struct{}{} } - var newAccounts []Account + newAccounts := []Account{} for _, account := range accounts { if shouldFilterAccount(account, filterMap) { continue diff --git a/scripts/chain-initiator/filter-balances.go b/scripts/chain-initiator/filter-balances.go index 9f6f217e2d..8d58246a12 100644 --- a/scripts/chain-initiator/filter-balances.go +++ b/scripts/chain-initiator/filter-balances.go @@ -11,7 +11,7 @@ func filterBalances(balances []banktypes.Balance, filterAddresses []string) ([]b filterMap[addr] = struct{}{} } - var newBalances []banktypes.Balance + newBalances := []banktypes.Balance{} var coinsToRemove sdk.Coins for _, balance := range balances { if _, exists := filterMap[balance.Address]; exists { diff --git a/scripts/chain-initiator/types.go b/scripts/chain-initiator/types.go index 02d2afe7a1..9fcd9f4166 100644 --- a/scripts/chain-initiator/types.go +++ b/scripts/chain-initiator/types.go @@ -300,6 +300,7 @@ type CLP struct { 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 { From de70a6435756b6194251050562f9633d91038d25 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:08:42 +0100 Subject: [PATCH 3/9] feat: chain-initiator uses snapshot url and export genesis file from there --- scripts/chain-initiator/export.go | 26 ++++++++++++++++++++ scripts/chain-initiator/initiator.go | 21 +++++++++++++--- scripts/chain-initiator/retrieve-snapshot.go | 20 +++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 scripts/chain-initiator/export.go create mode 100644 scripts/chain-initiator/retrieve-snapshot.go diff --git a/scripts/chain-initiator/export.go b/scripts/chain-initiator/export.go new file mode 100644 index 0000000000..f030bf3258 --- /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("Command execution failed: %v", err) + } + + // Write the output to the specified file + err = ioutil.WriteFile(genesisFilePath, output, 0644) + if err != nil { + log.Fatalf("Failed to write output to file: %v", err) + } + + log.Printf("Output successfully written to %s", genesisFilePath) +} diff --git a/scripts/chain-initiator/initiator.go b/scripts/chain-initiator/initiator.go index eca05f7725..a1dc365b5d 100644 --- a/scripts/chain-initiator/initiator.go +++ b/scripts/chain-initiator/initiator.go @@ -15,18 +15,19 @@ const ( keyringBackend = "test" validatorKeyName = "validator" validatorBalance = "4000000000000000000000000000" + genesisFilePath = "/tmp/genesis.json" ) func main() { var rootCmd = &cobra.Command{ - Use: "initiator [cmd_path] [home_path] [genesis_file_path]", + 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 Run: func(cmd *cobra.Command, args []string) { - cmdPath := args[0] // sifnoded - homePath := args[1] // /tmp/node - genesisFilePath := args[2] // /tmp/genesis.json + cmdPath := args[0] // sifnoded + homePath := args[1] // /tmp/node + snapshotUrl := args[2] // https://snapshots.polkachu.com/snapshots/sifchain/sifchain_15048938.tar.lz4 // set address prefix app.SetConfig(false) @@ -37,6 +38,18 @@ func main() { // init chain initChain(cmdPath, moniker, chainId, homePath) + // retrieve the snapshot + retrieveSnapshot(snapshotUrl, homePath) + + // export genesis file + export(cmdPath, homePath, genesisFilePath) + + // remove home path + removeHome(homePath) + + // init chain + initChain(cmdPath, moniker, chainId, homePath) + // add validator key validatorAddress := addKey(cmdPath, validatorKeyName, homePath, keyringBackend) diff --git a/scripts/chain-initiator/retrieve-snapshot.go b/scripts/chain-initiator/retrieve-snapshot.go new file mode 100644 index 0000000000..c4f8fbd4ec --- /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("Command execution failed: %v", err) + } + + // If execution reaches here, the command was successful + log.Printf("Snapshot retrieved and extracted to path: %s", homePath) +} From 18bf261e3bbb241686643fe01c679f42b27f2a6a Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:18:50 +0100 Subject: [PATCH 4/9] test: lint --- scripts/chain-initiator/export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/chain-initiator/export.go b/scripts/chain-initiator/export.go index f030bf3258..81fd086568 100644 --- a/scripts/chain-initiator/export.go +++ b/scripts/chain-initiator/export.go @@ -17,7 +17,7 @@ func export(cmdPath, homePath, genesisFilePath string) { } // Write the output to the specified file - err = ioutil.WriteFile(genesisFilePath, output, 0644) + err = ioutil.WriteFile(genesisFilePath, output, 0644) // nolint: gosec if err != nil { log.Fatalf("Failed to write output to file: %v", err) } From 4f0d1c8469677d8cc1bfcf54c011f29e8f09b1ef Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:50:52 +0100 Subject: [PATCH 5/9] feat: chain-initiator include submit software upgrade proposal logic --- scripts/chain-initiator/gen-tx.go | 6 +- scripts/chain-initiator/get-args.go | 19 ++++ scripts/chain-initiator/get-flags.go | 26 +++++ scripts/chain-initiator/initiator.go | 107 ++++++------------ scripts/chain-initiator/is-node-running.go | 30 +++++ scripts/chain-initiator/listen-for-signals.go | 27 +++++ .../query-and-calc-upgrade-block-height.go | 23 ++++ scripts/chain-initiator/query-block-height.go | 26 +++++ scripts/chain-initiator/start.go | 14 ++- .../submit-upgrade-proposal.go | 51 +++++++++ scripts/chain-initiator/types.go | 7 ++ scripts/chain-initiator/update-genesis.go | 68 +++++++++++ .../chain-initiator/wait-for-node-to-start.go | 21 ++++ 13 files changed, 346 insertions(+), 79 deletions(-) create mode 100644 scripts/chain-initiator/get-args.go create mode 100644 scripts/chain-initiator/get-flags.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/submit-upgrade-proposal.go create mode 100644 scripts/chain-initiator/update-genesis.go create mode 100644 scripts/chain-initiator/wait-for-node-to-start.go 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.") +} From 7707aed906ced31a8b2133aa17f4b9ffb5a273c9 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Fri, 8 Dec 2023 15:29:50 +0100 Subject: [PATCH 6/9] fix: improved stability --- .../chain-initiator/add-genesis-account.go | 4 +- scripts/chain-initiator/add-key.go | 6 +- scripts/chain-initiator/collect-gentxs.go | 4 +- scripts/chain-initiator/export.go | 6 +- scripts/chain-initiator/gen-tx.go | 4 +- scripts/chain-initiator/get-args.go | 4 +- scripts/chain-initiator/get-flags.go | 28 ++++-- scripts/chain-initiator/init-chain.go | 4 +- scripts/chain-initiator/initiator.go | 85 +++++++++++-------- scripts/chain-initiator/listen-for-signals.go | 4 +- .../query-and-calc-upgrade-block-height.go | 7 +- scripts/chain-initiator/query-block-height.go | 9 +- scripts/chain-initiator/remove-home.go | 4 +- scripts/chain-initiator/retrieve-snapshot.go | 4 +- scripts/chain-initiator/start.go | 2 +- .../submit-upgrade-proposal.go | 10 +-- scripts/chain-initiator/types.go | 7 ++ scripts/chain-initiator/update-genesis.go | 12 ++- scripts/chain-initiator/validate-genesis.go | 4 +- .../chain-initiator/wait-for-next-block.go | 42 +++++++++ .../chain-initiator/wait-for-node-to-start.go | 6 +- 21 files changed, 168 insertions(+), 88 deletions(-) create mode 100644 scripts/chain-initiator/wait-for-next-block.go diff --git a/scripts/chain-initiator/add-genesis-account.go b/scripts/chain-initiator/add-genesis-account.go index 6deed9611c..2dbda9f743 100644 --- a/scripts/chain-initiator/add-genesis-account.go +++ b/scripts/chain-initiator/add-genesis-account.go @@ -11,9 +11,9 @@ func addGenesisAccount(cmdPath, address, balance, homePath string) { // Execute the command if err := exec.Command(cmdPath, args...).Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - log.Printf("add genesis account with address %s, balance: %s and home path %s successfully", address, balance, homePath) + 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 index 288053f2a7..0ca308b829 100644 --- a/scripts/chain-initiator/add-key.go +++ b/scripts/chain-initiator/add-key.go @@ -13,17 +13,17 @@ func addKey(cmdPath, name, homePath, keyringBackend string) string { // Execute the command output, err := exec.Command(cmdPath, args...).CombinedOutput() if err != nil { - log.Fatalf("Command execution failed: %v", err) + 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("Failed to unmarshal JSON output: %v", err) + log.Fatalf(Red+"Failed to unmarshal JSON output: %v", err) } // Log the address - log.Printf("add key with name %s, home path: %s, keyring backend %s and address %s successfully", name, homePath, keyringBackend, keyOutput.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 index 07fe9834ad..7393f132a1 100644 --- a/scripts/chain-initiator/collect-gentxs.go +++ b/scripts/chain-initiator/collect-gentxs.go @@ -11,9 +11,9 @@ func collectGentxs(cmdPath, homePath string) { // Execute the command if err := exec.Command(cmdPath, args...).Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - log.Printf("collect gen txs with home path %s successfully", homePath) + log.Printf(Yellow+"collect gen txs with home path %s successfully", homePath) } diff --git a/scripts/chain-initiator/export.go b/scripts/chain-initiator/export.go index 81fd086568..efb502207c 100644 --- a/scripts/chain-initiator/export.go +++ b/scripts/chain-initiator/export.go @@ -13,14 +13,14 @@ func export(cmdPath, homePath, genesisFilePath string) { // Execute the command and capture the output output, err := exec.Command(cmdPath, args...).CombinedOutput() if err != nil { - log.Fatalf("Command execution failed: %v", err) + 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("Failed to write output to file: %v", err) + log.Fatalf(Red+"Failed to write output to file: %v", err) } - log.Printf("Output successfully written to %s", genesisFilePath) + log.Printf(Yellow+"Output successfully written to %s", genesisFilePath) } diff --git a/scripts/chain-initiator/gen-tx.go b/scripts/chain-initiator/gen-tx.go index 72805d7274..55b79e73e1 100644 --- a/scripts/chain-initiator/gen-tx.go +++ b/scripts/chain-initiator/gen-tx.go @@ -11,9 +11,9 @@ func genTx(cmdPath, name, amount, chainId, homePath, keyringBackend string) { // Execute the command if err := exec.Command(cmdPath, args...).Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - 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) + 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 index 76d0af533d..bf3a95fb58 100644 --- a/scripts/chain-initiator/get-args.go +++ b/scripts/chain-initiator/get-args.go @@ -7,12 +7,12 @@ import ( 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") + log.Fatalf(Red + "snapshot url is required") } newVersion = args[1] // v0.1.0 if newVersion == "" { - log.Fatalf("new version is required") + log.Fatalf(Red + "new version is required") } return diff --git a/scripts/chain-initiator/get-flags.go b/scripts/chain-initiator/get-flags.go index 2dd7904469..790b1b33dd 100644 --- a/scripts/chain-initiator/get-flags.go +++ b/scripts/chain-initiator/get-flags.go @@ -7,19 +7,37 @@ import ( ) const ( - flagHome = "home" - flagCmd = "cmd" + flagHome = "home" + flagCmd = "cmd" + flagSkipSnapshot = "skip-snapshot" + flagSkipChainInit = "skip-chain-init" + flagSkipNodeStart = "skip-node-start" ) -func getFlags(cmd *cobra.Command) (homePath, cmdPath string) { +func getFlags(cmd *cobra.Command) (homePath, cmdPath string, skipSnapshot, skipChainInit, skipNodeStart bool) { homePath, _ = cmd.Flags().GetString(flagHome) if homePath == "" { - log.Fatalf("home path is required") + log.Fatalf(Red + "home path is required") } cmdPath, _ = cmd.Flags().GetString(flagCmd) if cmdPath == "" { - log.Fatalf("cmd path is required") + log.Fatalf(Red + "cmd 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") } return diff --git a/scripts/chain-initiator/init-chain.go b/scripts/chain-initiator/init-chain.go index 3beaa6b1cf..fb1f09b691 100644 --- a/scripts/chain-initiator/init-chain.go +++ b/scripts/chain-initiator/init-chain.go @@ -11,9 +11,9 @@ func initChain(cmdPath, moniker, chainId, homePath string) { // Execute the command if err := exec.Command(cmdPath, args...).Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - log.Printf("init chain with moniker %s, chain id %s and home path: %s successfully", moniker, chainId, homePath) + 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 index 0434d8dd2c..5f0c7e1744 100644 --- a/scripts/chain-initiator/initiator.go +++ b/scripts/chain-initiator/initiator.go @@ -28,62 +28,70 @@ func main() { Args: cobra.ExactArgs(2), // Expect exactly 1 argument Run: func(cmd *cobra.Command, args []string) { snapshotUrl, newVersion := getArgs(args) - _ = snapshotUrl - homePath, cmdPath := getFlags(cmd) + homePath, cmdPath, skipSnapshot, skipChainInit, skipNodeStart := getFlags(cmd) // set address prefix app.SetConfig(false) - // remove home path - removeHome(homePath) + if !skipSnapshot { + // remove home path + removeHome(homePath) - // init chain - initChain(cmdPath, moniker, chainId, homePath) + // init chain + initChain(cmdPath, moniker, chainId, homePath) - // retrieve the snapshot - retrieveSnapshot(snapshotUrl, homePath) + // retrieve the snapshot + retrieveSnapshot(snapshotUrl, homePath) - // export genesis file - export(cmdPath, homePath, genesisFilePath) + // export genesis file + export(cmdPath, homePath, genesisFilePath) + } - // remove home path - removeHome(homePath) + if !skipChainInit { + // remove home path + removeHome(homePath) - // init chain - initChain(cmdPath, moniker, chainId, homePath) + // init chain + initChain(cmdPath, moniker, chainId, homePath) - // add validator key - validatorAddress := addKey(cmdPath, validatorKeyName, homePath, keyringBackend) + // add validator key + validatorAddress := addKey(cmdPath, validatorKeyName, homePath, keyringBackend) - // add genesis account - addGenesisAccount(cmdPath, validatorAddress, validatorBalance, homePath) + // add genesis account + addGenesisAccount(cmdPath, validatorAddress, validatorBalance, homePath) - // generate genesis tx - genTx(cmdPath, validatorKeyName, validatorSelfDelegation, chainId, homePath, keyringBackend) + // generate genesis tx + genTx(cmdPath, validatorKeyName, validatorSelfDelegation, chainId, homePath, keyringBackend) - // collect genesis txs - collectGentxs(cmdPath, homePath) + // collect genesis txs + collectGentxs(cmdPath, homePath) - // validate genesis - validateGenesis(cmdPath, homePath) + // validate genesis + validateGenesis(cmdPath, homePath) - // update genesis - updateGenesis(validatorBalance, homePath) + // update genesis + updateGenesis(validatorBalance, homePath) + } - // start chain - startCmd := start(cmdPath, homePath) + if !skipNodeStart { + // start chain + startCmd := start(cmdPath, homePath) - // wait for node to start - waitForNodeToStart(node) + // wait for node to start + waitForNodeToStart(node) - // query and calculate upgrade block height - upgradeBlockHeight := queryAndCalcUpgradeBlockHeight(cmdPath, node) + // wait for next block + waitForNextBlock(cmdPath, node) - // submit upgrade proposal - submitUpgradeProposal(cmdPath, validatorKeyName, newVersion, upgradeBlockHeight, homePath, keyringBackend, chainId, node, broadcastMode) + // query and calculate upgrade block height + upgradeBlockHeight := queryAndCalcUpgradeBlockHeight(cmdPath, node) - // listen for signals - listenForSignals(startCmd) + // submit upgrade proposal + submitUpgradeProposal(cmdPath, validatorKeyName, newVersion, upgradeBlockHeight, homePath, keyringBackend, chainId, node, broadcastMode) + + // listen for signals + listenForSignals(startCmd) + } }, } @@ -92,8 +100,11 @@ func main() { 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") if err := rootCmd.Execute(); err != nil { - log.Fatalf("Error executing command: %v", err) + 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 910257be6e..7ec26f26fc 100644 --- a/scripts/chain-initiator/listen-for-signals.go +++ b/scripts/chain-initiator/listen-for-signals.go @@ -20,8 +20,8 @@ func listenForSignals(cmd *exec.Cmd) { if cmd != nil && cmd.Process != nil { err := cmd.Process.Kill() if err != nil { - log.Fatalf("Failed to kill process: %v", err) + log.Fatalf(Red+"Failed to kill process: %v", err) } - log.Println("Process killed successfully") + log.Println(Yellow + "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 index f9ee37c41b..4ead6e29fa 100644 --- a/scripts/chain-initiator/query-and-calc-upgrade-block-height.go +++ b/scripts/chain-initiator/query-and-calc-upgrade-block-height.go @@ -7,12 +7,15 @@ import ( func queryAndCalcUpgradeBlockHeight(cmdPath, node string) string { // query block height - blockHeight := queryBlockHeight(cmdPath, node) + 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("Failed to convert blockHeight to integer: %v", err) + log.Fatalf(Red+"Failed to convert blockHeight to integer: %v", err) } // set upgrade block height diff --git a/scripts/chain-initiator/query-block-height.go b/scripts/chain-initiator/query-block-height.go index 8cb8cc8179..757afad241 100644 --- a/scripts/chain-initiator/query-block-height.go +++ b/scripts/chain-initiator/query-block-height.go @@ -2,25 +2,24 @@ package main import ( "encoding/json" - "log" "os/exec" ) -func queryBlockHeight(cmdPath, node string) string { +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 { - log.Fatalf("Command execution failed: %v", err) + return "-1", 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 "-1", err } - return statusOutput.SyncInfo.LatestBlockHeight + return statusOutput.SyncInfo.LatestBlockHeight, nil } diff --git a/scripts/chain-initiator/remove-home.go b/scripts/chain-initiator/remove-home.go index c370e28995..6a67c29d05 100644 --- a/scripts/chain-initiator/remove-home.go +++ b/scripts/chain-initiator/remove-home.go @@ -11,9 +11,9 @@ func removeHome(homePath string) { // Execute the command if err := exec.Command("rm", args...).Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - log.Printf("removed home path %s successfully", homePath) + 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 index c4f8fbd4ec..f72833ff08 100644 --- a/scripts/chain-initiator/retrieve-snapshot.go +++ b/scripts/chain-initiator/retrieve-snapshot.go @@ -12,9 +12,9 @@ func retrieveSnapshot(snapshotUrl, homePath string) { // Execute the command using /bin/sh cmd := exec.Command("/bin/sh", "-c", cmdString) if err := cmd.Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - log.Printf("Snapshot retrieved and extracted to path: %s", homePath) + log.Printf(Yellow+"Snapshot retrieved and extracted to path: %s", homePath) } diff --git a/scripts/chain-initiator/start.go b/scripts/chain-initiator/start.go index 20349c6bf4..c56477be8d 100644 --- a/scripts/chain-initiator/start.go +++ b/scripts/chain-initiator/start.go @@ -20,7 +20,7 @@ func start(cmdPath, homePath string) *exec.Cmd { // 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) + log.Fatalf(Red+"Command execution failed: %v", err) } }() diff --git a/scripts/chain-initiator/submit-upgrade-proposal.go b/scripts/chain-initiator/submit-upgrade-proposal.go index 468db9ee56..182f3ee545 100644 --- a/scripts/chain-initiator/submit-upgrade-proposal.go +++ b/scripts/chain-initiator/submit-upgrade-proposal.go @@ -38,14 +38,10 @@ func submitUpgradeProposal(cmdPath, name, newVersion, upgradeHeight, homePath, k } // Execute the command - output, err := exec.Command(cmdPath, args...).CombinedOutput() - if err != nil { - log.Fatalf("Command execution failed: %v", err) + if err := exec.Command(cmdPath, args...).Run(); err != nil { + log.Fatalf(Red+"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) + 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 index 86801a5b78..6b332b33a9 100644 --- a/scripts/chain-initiator/types.go +++ b/scripts/chain-initiator/types.go @@ -368,3 +368,10 @@ type StatusOutput struct { LatestBlockHeight string `json:"latest_block_height"` } `json:"SyncInfo"` } + +// 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 index 45695fcf4e..63336bd2d7 100644 --- a/scripts/chain-initiator/update-genesis.go +++ b/scripts/chain-initiator/update-genesis.go @@ -10,13 +10,13 @@ import ( func updateGenesis(validatorBalance, homePath string) { genesis, err := readGenesisFile(genesisFilePath) if err != nil { - log.Fatalf("Error reading genesis file: %v", err) + log.Fatalf(Red+"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) + log.Fatalf(Red+"Error reading initial genesis file: %v", err) } filterAccountAddresses := []string{ @@ -36,7 +36,7 @@ func updateGenesis(validatorBalance, homePath string) { newValidatorBalance, ok := sdk.NewIntFromString(validatorBalance) if !ok { - panic("invalid number") + panic(Red + "invalid number") } newValidatorBalanceCoin := sdk.NewCoin("rowan", newValidatorBalance) @@ -61,8 +61,12 @@ func updateGenesis(validatorBalance, homePath string) { // set genutil from genesisInit genesis.AppState.Genutil = genesisInit.AppState.Genutil + // update voting period + genesis.AppState.Gov.VotingParams.VotingPeriod = "60s" + genesis.AppState.Gov.DepositParams.MaxDepositPeriod = "60s" + outputFilePath := homePath + "/config/genesis.json" if err := writeGenesisFile(outputFilePath, genesis); err != nil { - log.Fatalf("Error writing genesis file: %v", err) + 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 index a8e24d02fe..b32eeee608 100644 --- a/scripts/chain-initiator/validate-genesis.go +++ b/scripts/chain-initiator/validate-genesis.go @@ -11,9 +11,9 @@ func validateGenesis(cmdPath, homePath string) { // Execute the command if err := exec.Command(cmdPath, args...).Run(); err != nil { - log.Fatalf("Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) } // If execution reaches here, the command was successful - log.Printf("validate genesis with home path %s successfully", homePath) + log.Printf(Yellow+"validate genesis with home path %s successfully", homePath) } 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..d163cc9e81 --- /dev/null +++ b/scripts/chain-initiator/wait-for-next-block.go @@ -0,0 +1,42 @@ +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 + } + } + time.Sleep(5 * time.Second) // Wait 5 second 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 + } + } + time.Sleep(5 * time.Second) // Wait a second 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 index debc811f8b..9cc25c658a 100644 --- a/scripts/chain-initiator/wait-for-node-to-start.go +++ b/scripts/chain-initiator/wait-for-node-to-start.go @@ -12,10 +12,10 @@ func waitForNodeToStart(node string) { // 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.Fatalf(Red + "Node did not start within the specified timeout") } - log.Println("Waiting for node to start...") + log.Println(Yellow + "Waiting for node to start...") time.Sleep(5 * time.Second) } - log.Println("Node is running.") + log.Println(Yellow + "Node is running.") } From 7d4bcbe0235c472cab951d428159de89b30a5be2 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:45:19 +0100 Subject: [PATCH 7/9] feat: improved chain initiator --- .../download-and-run-version.go | 87 ++++++++++++++ scripts/chain-initiator/get-args.go | 13 +- scripts/chain-initiator/get-flags.go | 82 +++++++++++-- scripts/chain-initiator/initiator.go | 112 +++++++++++++----- scripts/chain-initiator/listen-for-signals.go | 9 +- .../query-and-calc-upgrade-block-height.go | 2 +- .../chain-initiator/query-next-proposal-id.go | 42 +++++++ scripts/chain-initiator/start.go | 17 ++- scripts/chain-initiator/stop.go | 17 +++ scripts/chain-initiator/types.go | 7 ++ scripts/chain-initiator/update-genesis.go | 6 +- .../vote-on-upgrade-proposal.go | 30 +++++ .../chain-initiator/wait-for-block-height.go | 30 +++++ .../chain-initiator/wait-for-next-block.go | 6 +- 14 files changed, 396 insertions(+), 64 deletions(-) create mode 100644 scripts/chain-initiator/download-and-run-version.go create mode 100644 scripts/chain-initiator/query-next-proposal-id.go create mode 100644 scripts/chain-initiator/stop.go create mode 100644 scripts/chain-initiator/vote-on-upgrade-proposal.go create mode 100644 scripts/chain-initiator/wait-for-block-height.go 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) From 78d08fef79106a0b41277aed6f611f56cf2b397b Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Mon, 11 Dec 2023 10:57:00 +0100 Subject: [PATCH 8/9] test: fix lint --- scripts/chain-initiator/add-genesis-account.go | 2 +- scripts/chain-initiator/download-and-run-version.go | 3 ++- scripts/chain-initiator/get-flags.go | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/chain-initiator/add-genesis-account.go b/scripts/chain-initiator/add-genesis-account.go index 2dbda9f743..ef1d9682ff 100644 --- a/scripts/chain-initiator/add-genesis-account.go +++ b/scripts/chain-initiator/add-genesis-account.go @@ -11,7 +11,7 @@ func addGenesisAccount(cmdPath, address, balance, homePath string) { // Execute the command if err := exec.Command(cmdPath, args...).Run(); err != nil { - log.Fatalf(Red+"Command execution failed: %v", err) + log.Fatalf(Red+"Command execution failed: %v", err) // nolint: goconst } // If execution reaches here, the command was successful diff --git a/scripts/chain-initiator/download-and-run-version.go b/scripts/chain-initiator/download-and-run-version.go index 8e5b822513..0c09389589 100644 --- a/scripts/chain-initiator/download-and-run-version.go +++ b/scripts/chain-initiator/download-and-run-version.go @@ -1,3 +1,4 @@ +// nolint: nakedret package main import ( @@ -40,7 +41,7 @@ func downloadAndRunVersion(binaryURL string, skipDownload bool) (path string, ve } // Download the binary - resp, err := http.Get(binaryURL) + resp, err := http.Get(binaryURL) // nolint: gosec if err != nil { return } diff --git a/scripts/chain-initiator/get-flags.go b/scripts/chain-initiator/get-flags.go index 2b7b0c5273..6846cd999a 100644 --- a/scripts/chain-initiator/get-flags.go +++ b/scripts/chain-initiator/get-flags.go @@ -1,3 +1,4 @@ +// nolint: nakedret package main import ( From 26c73f49377848a7251c90c8fa5f6a674cab4282 Mon Sep 17 00:00:00 2001 From: Snobbish Bee <125891987+snobbee@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:29:51 +0100 Subject: [PATCH 9/9] feat: binary url can be remote url or local path --- .../download-and-run-version.go | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/scripts/chain-initiator/download-and-run-version.go b/scripts/chain-initiator/download-and-run-version.go index 0c09389589..fcedd5461a 100644 --- a/scripts/chain-initiator/download-and-run-version.go +++ b/scripts/chain-initiator/download-and-run-version.go @@ -13,11 +13,37 @@ import ( "strings" ) -func downloadAndRunVersion(binaryURL string, skipDownload bool) (path string, version string, err error) { +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(binaryURL) + versionMatches := re.FindStringSubmatch(binaryPathOrURL) if len(versionMatches) == 0 { err = errors.New("no version found in URL") return @@ -41,7 +67,7 @@ func downloadAndRunVersion(binaryURL string, skipDownload bool) (path string, ve } // Download the binary - resp, err := http.Get(binaryURL) // nolint: gosec + resp, err := http.Get(binaryPathOrURL) // nolint: gosec if err != nil { return }