diff --git a/app/testnet.go b/app/testnet.go new file mode 100644 index 0000000000..a7633b4f76 --- /dev/null +++ b/app/testnet.go @@ -0,0 +1,257 @@ +package app + +import ( + "fmt" + "strings" + "time" + + "github.com/cosmos/cosmos-sdk/codec/types" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/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" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + tmos "github.com/tendermint/tendermint/libs/os" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + utypes "github.com/akash-network/node/upgrades/types" +) + +type TestnetValidator struct { + OperatorAddress sdk.ValAddress + ConsensusAddress sdk.ConsAddress + ConsensusPubKey *types.Any + Moniker string + Commission stakingtypes.Commission + MinSelfDelegation sdk.Int +} + +type TestnetUpgrade struct { + Name string +} + +type TestnetVotingPeriod struct { + time.Duration +} + +type TestnetGovConfig struct { + VotingParams *struct { + VotingPeriod TestnetVotingPeriod `json:"voting_period,omitempty"` + } `json:"voting_params,omitempty"` +} + +type TestnetConfig struct { + Accounts []sdk.AccAddress + Validators []TestnetValidator + Gov TestnetGovConfig + Upgrade TestnetUpgrade +} + +func TrimQuotes(data string) string { + data = strings.TrimPrefix(data, "\"") + return strings.TrimSuffix(data, "\"") +} + +func (t *TestnetVotingPeriod) UnmarshalJSON(data []byte) error { + val := TrimQuotes(string(data)) + + if !strings.HasSuffix(val, "s") { + return fmt.Errorf("invalid format of voting period. must contain time unit. Valid time units are ns|us(µs)|ms|s|m|h") // nolint: goerr113 + } + + var err error + t.Duration, err = time.ParseDuration(val) + if err != nil { + return err + } + + return nil +} + +// InitAkashAppForTestnet is broken down into two sections: +// Required Changes: Changes that, if not made, will cause the testnet to halt or panic +// Optional Changes: Changes to customize the testnet to one's liking (lower vote times, fund accounts, etc) +func InitAkashAppForTestnet( + app *AkashApp, + tcfg *TestnetConfig, +) *AkashApp { + // + // Required Changes: + // + + var err error + + defer func() { + if err != nil { + tmos.Exit(err.Error()) + } + }() + + ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{}) + + // Remove all validators from power store + stakingKey := app.GetKey(stakingtypes.ModuleName) + stakingStore := ctx.KVStore(stakingKey) + iterator := app.Keepers.Cosmos.Staking.ValidatorsPowerStoreIterator(ctx) + + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + if err := iterator.Close(); err != nil { + panic(err) + } + + // Remove all validators from last validators store + iterator = app.Keepers.Cosmos.Staking.LastValidatorsIterator(ctx) + + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + if err := iterator.Close(); err != nil { + panic(err) + } + + // Remove all validators from validator store + iterator = storetypes.KVStorePrefixIterator(stakingStore, stakingtypes.ValidatorsKey) + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + if err := iterator.Close(); err != nil { + panic(err) + } + + // Remove all validators from unbonding queue + iterator = storetypes.KVStorePrefixIterator(stakingStore, stakingtypes.ValidatorQueueKey) + for ; iterator.Valid(); iterator.Next() { + stakingStore.Delete(iterator.Key()) + } + if err := iterator.Close(); err != nil { + panic(err) + } + + for _, val := range tcfg.Validators { + // Create Validator struct for our new validator. + newVal := stakingtypes.Validator{ + OperatorAddress: val.OperatorAddress.String(), + ConsensusPubkey: val.ConsensusPubKey, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: sdk.NewInt(900000000000000), + DelegatorShares: sdk.MustNewDecFromStr("10000000"), + Description: stakingtypes.Description{ + Moniker: "Testnet Validator", + }, + Commission: stakingtypes.Commission{ + CommissionRates: stakingtypes.CommissionRates{ + Rate: sdk.MustNewDecFromStr("0.05"), + MaxRate: sdk.MustNewDecFromStr("0.1"), + MaxChangeRate: sdk.MustNewDecFromStr("0.05"), + }, + }, + MinSelfDelegation: sdk.OneInt(), + } + + // Add our validator to power and last validators store + app.Keepers.Cosmos.Staking.SetValidator(ctx, newVal) + err = app.Keepers.Cosmos.Staking.SetValidatorByConsAddr(ctx, newVal) + if err != nil { + return nil + } + + app.Keepers.Cosmos.Staking.SetValidatorByPowerIndex(ctx, newVal) + + valAddr := newVal.GetOperator() + app.Keepers.Cosmos.Staking.SetLastValidatorPower(ctx, valAddr, 0) + + app.Keepers.Cosmos.Distr.Hooks().AfterValidatorCreated(ctx, valAddr) + app.Keepers.Cosmos.Slashing.Hooks().AfterValidatorCreated(ctx, valAddr) + + // DISTRIBUTION + // + + // Initialize records for this validator across all distribution stores + app.Keepers.Cosmos.Distr.SetValidatorHistoricalRewards(ctx, valAddr, 0, distrtypes.NewValidatorHistoricalRewards(sdk.DecCoins{}, 1)) + app.Keepers.Cosmos.Distr.SetValidatorCurrentRewards(ctx, valAddr, distrtypes.NewValidatorCurrentRewards(sdk.DecCoins{}, 1)) + app.Keepers.Cosmos.Distr.SetValidatorAccumulatedCommission(ctx, valAddr, distrtypes.InitialValidatorAccumulatedCommission()) + app.Keepers.Cosmos.Distr.SetValidatorOutstandingRewards(ctx, valAddr, distrtypes.ValidatorOutstandingRewards{Rewards: sdk.DecCoins{}}) + + // SLASHING + // + + newConsAddr := val.ConsensusAddress + + // Set validator signing info for our new validator. + newValidatorSigningInfo := slashingtypes.ValidatorSigningInfo{ + Address: newConsAddr.String(), + StartHeight: app.LastBlockHeight() - 1, + Tombstoned: false, + } + + _, _ = app.Keepers.Cosmos.Staking.ApplyAndReturnValidatorSetUpdates(ctx) + + app.Keepers.Cosmos.Slashing.SetValidatorSigningInfo(ctx, newConsAddr, newValidatorSigningInfo) + } + + // + // Optional Changes: + // + + // GOV + // + + voteParams := app.Keepers.Cosmos.Gov.GetVotingParams(ctx) + voteParams.VotingPeriod = tcfg.Gov.VotingParams.VotingPeriod.Duration + app.Keepers.Cosmos.Gov.SetVotingParams(ctx, voteParams) + + // BANK + // + + defaultCoins := sdk.NewCoins( + sdk.NewInt64Coin("uakt", 1000000000000), + sdk.NewInt64Coin("ibc/12C6A0C374171B595A0A9E18B83FA09D295FB1F2D8C6DAA3AC28683471752D84", 1000000000000), // axlUSDC + ) + + for _, account := range tcfg.Accounts { + err := app.Keepers.Cosmos.Bank.MintCoins(ctx, minttypes.ModuleName, defaultCoins) + if err != nil { + return nil + } + err = app.Keepers.Cosmos.Bank.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, account, defaultCoins) + if err != nil { + return nil + } + } + + // UPGRADE + // + if tcfg.Upgrade.Name != "" { + upgradePlan := upgradetypes.Plan{ + Name: tcfg.Upgrade.Name, + Height: app.LastBlockHeight() + 10, + } + + err = app.Keepers.Cosmos.Upgrade.ScheduleUpgrade(ctx, upgradePlan) + if err != nil { + panic(err) + } + + for name, fn := range utypes.GetUpgradesList() { + upgrade, err := fn(app.Logger(), &app.App) + if err != nil { + panic(err) + } + + if tcfg.Upgrade.Name == name { + app.Logger().Info(fmt.Sprintf("configuring upgrade `%s`", name)) + if storeUpgrades := upgrade.StoreLoader(); storeUpgrades != nil && tcfg.Upgrade.Name == name { + app.Logger().Info(fmt.Sprintf("setting up store upgrades for `%s`", name)) + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(app.LastBlockHeight(), storeUpgrades)) + } + } + } + } + + return app +} diff --git a/cmd/akash/cmd/app_creator.go b/cmd/akash/cmd/app_creator.go new file mode 100644 index 0000000000..5d86c8e8bd --- /dev/null +++ b/cmd/akash/cmd/app_creator.go @@ -0,0 +1,31 @@ +package cmd + +import ( + "io" + + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + + akash "github.com/akash-network/node/app" + "github.com/akash-network/node/cmd/akash/cmd/testnetify" +) + +// for a testnet to be created from the provided app. +func newTestnetApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { + // Create an app and type cast to an AkashApp + app := newApp(logger, db, traceStore, appOpts) + akashApp, ok := app.(*akash.AkashApp) + if !ok { + panic("app created from newApp is not of type AkashApp") + } + + tcfg, valid := appOpts.Get(testnetify.KeyTestnetConfig).(*akash.TestnetConfig) + if !valid { + panic("cflags.KeyTestnetConfig is not of type akash.TestnetConfig") + } + + // Make modifications to the normal AkashApp required to run the network locally + return akash.InitAkashAppForTestnet(akashApp, tcfg) +} diff --git a/cmd/akash/cmd/root.go b/cmd/akash/cmd/root.go index 25d5ddd160..73a9b799bd 100644 --- a/cmd/akash/cmd/root.go +++ b/cmd/akash/cmd/root.go @@ -138,7 +138,6 @@ func ExecuteWithCtx(ctx context.Context, rootCmd *cobra.Command, envPrefix strin func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { debugCmd := debug.Cmd() debugCmd.AddCommand(ConvertBech32Cmd()) - debugCmd.AddCommand(testnetify.Cmd()) rootCmd.AddCommand( rpc.StatusCommand(), @@ -158,6 +157,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { sdkserver.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler), ) + rootCmd.AddCommand(testnetify.GetCmd(newTestnetApp)) rootCmd.AddCommand(server.Commands(app.DefaultHome, newApp, createAppAndExport, addModuleInitFlags)...) rootCmd.SetOut(rootCmd.OutOrStdout()) diff --git a/cmd/akash/cmd/testnetify/accounts.go b/cmd/akash/cmd/testnetify/accounts.go deleted file mode 100644 index cf3c522f9e..0000000000 --- a/cmd/akash/cmd/testnetify/accounts.go +++ /dev/null @@ -1,31 +0,0 @@ -package testnetify - -import ( - "github.com/theckman/yacspin" - - "github.com/cosmos/cosmos-sdk/codec" -) - -func (ga *GenesisState) modifyAccounts(sp *yacspin.Spinner, cdc codec.Codec, cfg *AccountsConfig) error { - for _, acc := range cfg.Add { - if err := ga.AddNewAccount(cdc, acc.Address.AccAddress, acc.PubKey.PubKey); err != nil { - return err - } - - if err := ga.IncreaseSupply(cdc, acc.Coins.ToSDK()...); err != nil { - return err - } - - if err := ga.IncreaseBalances(cdc, acc.Address.AccAddress, acc.Coins.ToSDK()); err != nil { - return err - } - } - - if err := ga.validateBalances(); err != nil { - return err - } - - sp.Message("added new accounts") - - return nil -} diff --git a/cmd/akash/cmd/testnetify/cmd.go b/cmd/akash/cmd/testnetify/cmd.go deleted file mode 100644 index 4bf8820e3a..0000000000 --- a/cmd/akash/cmd/testnetify/cmd.go +++ /dev/null @@ -1,334 +0,0 @@ -package testnetify - -import ( - "encoding/json" - "fmt" - "io" - "os" - "time" - - "github.com/spf13/cobra" - "github.com/theckman/yacspin" - - cmtjson "github.com/tendermint/tendermint/libs/json" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - ibccltypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - ibcchtypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" -) - -const ( - flagConfig = "config" - flagSpinner = "spinner" - denomDecimalPlaces = 1e6 -) - -// yeah, I know -var cdc codec.Codec - -func Cmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "testnetify INFILE OUTFILE", - Short: "Utility to alter exported genesis state for testing purposes", - Args: cobra.ExactArgs(2), - PreRunE: func(cmd *cobra.Command, _ []string) error { - cctx := client.GetClientContextFromCmd(cmd) - cdc = cctx.Codec - - sID, err := cmd.Flags().GetInt(flagSpinner) - if err != nil { - return err - } - - if sID < 0 || sID > 90 { - return fmt.Errorf("invalid value %d for --spinner. expecting 0..90", sID) // nolint: goerr113 - } - - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { - cctx := client.GetClientContextFromCmd(cmd) - cdc := cctx.Codec - - oFile, err := os.Create(args[1]) - if err != nil { - return err - } - - modifyCompleted := false - - var spinner *yacspin.Spinner - - defer func() { - _ = oFile.Sync() - _ = oFile.Close() - - if !modifyCompleted { - _ = os.Remove(args[1]) - } - - if spinner != nil && spinner.Status() == yacspin.SpinnerRunning { - if err != nil { - _ = spinner.StopFail() - } else { - _ = spinner.Stop() - } - } - }() - - cfg := config{} - - if cfgPath, _ := cmd.Flags().GetString(flagConfig); cfgPath != "" { - cfgFile, err := os.Open(cfgPath) - if err != nil { - return err - } - - defer func() { - _ = cfgFile.Close() - }() - - cfgData, err := io.ReadAll(cfgFile) - if err != nil { - return err - } - if err = json.Unmarshal(cfgData, &cfg); err != nil { - return err - } - } - - spinnerID, _ := cmd.Flags().GetInt(flagSpinner) - - ycfg := yacspin.Config{ - Frequency: 100 * time.Millisecond, - CharSet: yacspin.CharSets[spinnerID], - ColorAll: true, - Prefix: "", - Suffix: " ", - SuffixAutoColon: true, - StopCharacter: "✓", - StopFailCharacter: "✗", - StopColors: []string{"fgGreen"}, - StopFailColors: []string{"fgRed"}, - } - - spinner, err = yacspin.New(ycfg) - if err != nil { - return err - } - - cmd.SilenceErrors = true - - inSource := "stdin" - if args[0] != "-" { - inSource = args[0] - } - - var gState *GenesisState - - { - spinner.Message(fmt.Sprintf("loading genesis from \"%s\"", inSource)) - spinner.StopMessage(fmt.Sprintf("loaded genesis from \"%s\"", inSource)) - _ = spinner.Start() - appState, genDoc, err := loadGenesis(cmd, args[0]) - if err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed to load genesis state: %s", err.Error())) - return err - } - _ = spinner.Stop() - - spinner.Message("preparing genesis state") - spinner.StopMessage("prepared genesis state") - _ = spinner.Start() - gState, err = NewGenesisState(spinner, appState, genDoc) - if err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed to prepare genesis state: %s", err.Error())) - return err - } - } - - if c := cfg.ChainID; c != nil { - spinner.Message("modifying chain_id") - spinner.StopMessage("modified chain_id") - - gState.doc.ChainID = *c - - _ = spinner.Stop() - } - - if c := cfg.Escrow; c != nil { - spinner.Message("modifying escrow module") - spinner.StopMessage("modified escrow module") - _ = spinner.Start() - if err = gState.modifyEscrowState(cdc, c); err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed modifying escrow module. %s", err.Error())) - return err - } - _ = spinner.Stop() - } - - if c := cfg.IBC; c != nil { - spinner.Message("modifying IBC module") - spinner.StopMessage("modified IBC module") - _ = spinner.Start() - if err = gState.modifyIBC(cdc, c); err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed modifying IBC. %s", err.Error())) - return err - } - _ = spinner.Stop() - } - - if c := cfg.Gov; c != nil { - spinner.Message("modifying gov module") - spinner.StopMessage("modified gov module") - _ = spinner.Start() - if err = gState.modifyGov(cdc, c); err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed modifying gov module. %s", err.Error())) - return err - } - _ = spinner.Stop() - } - - if c := cfg.Accounts; c != nil { - spinner.Message("modifying accounts") - spinner.StopMessage("modified accounts") - _ = spinner.Start() - if err = gState.modifyAccounts(spinner, cdc, c); err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed modifying accounts. %s", err.Error())) - return err - } - _ = spinner.Stop() - } - - if c := cfg.Validators; c != nil { - spinner.Message("modifying validators") - spinner.StopMessage("modified validators") - _ = spinner.Start() - if err = gState.modifyValidators(cdc, c); err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed modifying validators. %s", err.Error())) - return err - } - _ = spinner.Stop() - } - - spinner.Message("marshaling genesis state") - spinner.StopMessage("marshaled genesis state") - _ = spinner.Start() - if err = gState.pack(cdc); err != nil { - spinner.StopFailMessage(fmt.Sprintf("failed to pack genesis state. %s", err.Error())) - return err - } - - spinner.Message("validating genesis state") - spinner.StopMessage("validated genesis state") - _ = spinner.Start() - if err := gState.doc.ValidateAndComplete(); err != nil { - spinner.StopFailMessage(fmt.Sprintf("error validating genesis state. %s", err.Error())) - return err - } - _ = spinner.Stop() - - spinner.Message(fmt.Sprintf("exporting genesis doc to \"%s\"", args[1])) - spinner.StopMessage(fmt.Sprintf("exported genesis doc to \"%s\"", args[1])) - _ = spinner.Start() - genBytes, err := cmtjson.MarshalIndent(gState.doc, "", " ") - if err != nil { - spinner.StopFailMessage(fmt.Sprintf("error marshaling genesis doc. %s", err.Error())) - return err - } - - _, err = oFile.Write(genBytes) - if err != nil { - spinner.StopFailMessage(fmt.Sprintf("error exporting genesis doc. %s", err.Error())) - return err - } - - modifyCompleted = true - _ = spinner.Stop() - return nil - }, - } - - cmd.Flags().StringP(flagConfig, "c", "", "config file") - cmd.Flags().Int(flagSpinner, 52, "spinner type. allowed values 0..90") - - return cmd -} - -func loadGenesis(cmd *cobra.Command, file string) (genesisState map[string]json.RawMessage, genDoc *tmtypes.GenesisDoc, err error) { - var stream []byte - - reader := cmd.InOrStdin() - - defer func() { - if rd, closer := reader.(io.Closer); reader != nil && closer { - _ = rd.Close() - } - }() - - if file != "-" { - reader, err = os.Open(file) - if err != nil { - reader = nil - return genesisState, genDoc, err - } - } - - if stream, err = io.ReadAll(reader); err != nil { - return genesisState, genDoc, err - } - - if genDoc, err = tmtypes.GenesisDocFromJSON(stream); err != nil { - return genesisState, genDoc, err - } - - genesisState, err = genutiltypes.GenesisStateFromGenDoc(*genDoc) - return genesisState, genDoc, err -} - -func (ga *GenesisState) modifyGov(cdc codec.Codec, cfg *GovConfig) error { - if cfg == nil { - return nil - } - - if err := ga.app.GovState.unpack(cdc); err != nil { - return err - } - - if params := cfg.VotingParams; params != nil { - if params.VotingPeriod.Duration > 0 { - ga.app.GovState.state.VotingParams.VotingPeriod = params.VotingPeriod.Duration - } - } - - return nil -} - -func (ga *GenesisState) modifyIBC(cdc codec.Codec, cfg *IBCConfig) error { - if cfg == nil { - return nil - } - - if err := ga.app.IBCState.unpack(cdc); err != nil { - return err - } - - if cfg.Prune { - ga.app.IBCState.state.ChannelGenesis.Channels = []ibcchtypes.IdentifiedChannel{} - ga.app.IBCState.state.ChannelGenesis.Acknowledgements = []ibcchtypes.PacketState{} - ga.app.IBCState.state.ChannelGenesis.Commitments = []ibcchtypes.PacketState{} - ga.app.IBCState.state.ChannelGenesis.Receipts = []ibcchtypes.PacketState{} - ga.app.IBCState.state.ChannelGenesis.SendSequences = []ibcchtypes.PacketSequence{} - ga.app.IBCState.state.ChannelGenesis.RecvSequences = []ibcchtypes.PacketSequence{} - ga.app.IBCState.state.ChannelGenesis.AckSequences = []ibcchtypes.PacketSequence{} - - ga.app.IBCState.state.ClientGenesis.Clients = ibccltypes.IdentifiedClientStates{} - ga.app.IBCState.state.ClientGenesis.ClientsConsensus = ibccltypes.ClientsConsensusStates{} - ga.app.IBCState.state.ClientGenesis.ClientsMetadata = []ibccltypes.IdentifiedGenesisMetadata{} - } - - return nil -} diff --git a/cmd/akash/cmd/testnetify/config.go b/cmd/akash/cmd/testnetify/config.go index d82bcb55e2..842bf63547 100644 --- a/cmd/akash/cmd/testnetify/config.go +++ b/cmd/akash/cmd/testnetify/config.go @@ -1,15 +1,33 @@ package testnetify import ( - "fmt" "strings" - "time" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/tendermint/tendermint/crypto" + cmtjson "github.com/tendermint/tendermint/libs/json" + pvm "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + akash "github.com/akash-network/node/app" ) +type PrivValidatorKey struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` +} + +type NodeKey struct { + PrivKey crypto.PrivKey `json:"priv_key"` +} + +type Keys struct { + Priv PrivValidatorKey `json:"priv"` + Node NodeKey `json:"node"` +} type AccAddress struct { sdk.AccAddress } @@ -22,82 +40,37 @@ type ConsAddress struct { sdk.ConsAddress } -type JSONCoin struct { - sdk.Coin -} - -type JSONCoins []JSONCoin - -type PubKey struct { - cryptotypes.PubKey -} - -type VotingPeriod struct { - time.Duration -} - -type AccountConfig struct { - Address AccAddress `json:"address"` - PubKey PubKey `json:"pubkey"` - Coins JSONCoins `json:"coins,omitempty"` -} - -type Delegator struct { - Address AccAddress `json:"address"` - Coins JSONCoins `json:"coins"` -} - -type ValidatorConfig struct { - Operator AccAddress `json:"operator"` - PubKey PubKey `json:"pubkey"` - Name string `json:"name"` - Bonded bool `json:"bonded"` - Delegators []Delegator `json:"delegators,omitempty"` - Rates stakingtypes.CommissionRates `json:"rates"` -} - -type AccountsConfig struct { - Add []AccountConfig `json:"add,omitempty"` - Del []AccAddress `json:"del,omitempty"` -} +type TestnetValidator struct { + Moniker string `json:"moniker"` + Operator AccAddress `json:"operator"` + Bonded bool `json:"bonded"` + Commission stakingtypes.Commission `json:"commission"` + MinSelfDelegation sdk.Int `json:"min_self_delegation"` + Home string `json:"home"` -type GovConfig struct { - VotingParams *struct { - VotingPeriod VotingPeriod `json:"voting_period,omitempty"` - } `json:"voting_params,omitempty"` + privValidator *pvm.FilePV + pubKey crypto.PubKey + validatorAddress crypto.Address + consAddress sdk.ConsAddress } -type ValidatorsConfig struct { - Add []ValidatorConfig `json:"add,omitempty"` - Del []AccAddress `json:"del,omitempty"` -} - -type IBCConfig struct { - Prune bool `json:"prune"` -} +type TestnetValidators []TestnetValidator -type EscrowConfig struct { - PatchDanglingPayments bool `json:"patch_dangling_payments"` +type TestnetConfig struct { + ChainID string `json:"chain_id"` + Validators TestnetValidators `json:"validators"` + Accounts []sdk.AccAddress `json:"accounts"` + Gov akash.TestnetGovConfig `json:"gov"` + upgrade akash.TestnetUpgrade } -type config struct { - ChainID *string `json:"chain_id"` - Accounts *AccountsConfig `json:"accounts,omitempty"` - Validators *ValidatorsConfig `json:"validators"` - IBC *IBCConfig `json:"ibc"` - Escrow *EscrowConfig `json:"escrow"` - Gov *GovConfig `json:"gov,omitempty"` +func TrimQuotes(data string) string { + data = strings.TrimPrefix(data, "\"") + return strings.TrimSuffix(data, "\"") } -func (t *VotingPeriod) UnmarshalJSON(data []byte) error { - val := TrimQuotes(string(data)) - - if !strings.HasSuffix(val, "s") { - return fmt.Errorf("invalid format of voting period. must contain time unit. Valid time units are ns|us(µs)|ms|s|m|h") // nolint: goerr113 - } - - var err error - t.Duration, err = time.ParseDuration(val) +func (k *PrivValidatorKey) UnmarshalJSON(data []byte) error { + err := cmtjson.Unmarshal(data, k) if err != nil { return err } @@ -105,14 +78,6 @@ func (t *VotingPeriod) UnmarshalJSON(data []byte) error { return nil } -func (k *PubKey) UnmarshalJSON(data []byte) error { - if err := cdc.UnmarshalInterfaceJSON(data, &k.PubKey); err != nil { - return err - } - - return nil -} - func (s *ValAddress) UnmarshalJSON(data []byte) error { var err error if s.ValAddress, err = sdk.ValAddressFromBech32(TrimQuotes(string(data))); err != nil { @@ -139,29 +104,3 @@ func (s *ConsAddress) UnmarshalJSON(data []byte) error { return nil } - -func (k *JSONCoin) UnmarshalJSON(data []byte) error { - coin, err := sdk.ParseCoinNormalized(TrimQuotes(string(data))) - if err != nil { - return err - } - - k.Coin = coin - - return nil -} - -func (k JSONCoins) ToSDK() sdk.Coins { - coins := make(sdk.Coins, 0, len(k)) - - for _, coin := range k { - coins = append(coins, coin.Coin) - } - - return coins -} - -func TrimQuotes(data string) string { - data = strings.TrimPrefix(data, "\"") - return strings.TrimSuffix(data, "\"") -} diff --git a/cmd/akash/cmd/testnetify/escrow.go b/cmd/akash/cmd/testnetify/escrow.go deleted file mode 100644 index c24ce32bab..0000000000 --- a/cmd/akash/cmd/testnetify/escrow.go +++ /dev/null @@ -1,49 +0,0 @@ -package testnetify - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - - etypes "github.com/akash-network/akash-api/go/node/escrow/v1beta3" -) - -func (ga *GenesisState) modifyEscrowState(cdc codec.Codec, cfg *EscrowConfig) error { - if cfg == nil { - return nil - } - - if err := ga.app.EscrowState.unpack(cdc); err != nil { - return err - } - - amap := make(map[etypes.AccountID]etypes.Account, len(ga.app.EscrowState.state.Accounts)) - - for _, acc := range ga.app.EscrowState.state.Accounts { - amap[acc.ID] = acc - } - - if cfg.PatchDanglingPayments { - for idx, payment := range ga.app.EscrowState.state.Payments { - // make sure there's an account - acc, found := amap[payment.AccountID] - if !found { - return fmt.Errorf("%w: no account for payment %s %s (idx %v)", etypes.ErrAccountNotFound, payment.AccountID, payment.PaymentID, idx) - } - - if ((payment.State == etypes.PaymentOpen) && (acc.State != etypes.AccountOpen)) || - ((payment.State == etypes.PaymentOverdrawn) && (acc.State != etypes.AccountOverdrawn)) { - switch acc.State { - case etypes.AccountOpen: - ga.app.EscrowState.state.Payments[idx].State = etypes.PaymentOpen - case etypes.AccountOverdrawn: - ga.app.EscrowState.state.Payments[idx].State = etypes.PaymentOverdrawn - case etypes.AccountClosed: - ga.app.EscrowState.state.Payments[idx].State = etypes.PaymentClosed - } - } - } - } - - return nil -} diff --git a/cmd/akash/cmd/testnetify/state.go b/cmd/akash/cmd/testnetify/state.go deleted file mode 100644 index 741e32a086..0000000000 --- a/cmd/akash/cmd/testnetify/state.go +++ /dev/null @@ -1,1197 +0,0 @@ -package testnetify - -import ( - "encoding/json" - "fmt" - "sort" - "sync" - "time" - - "github.com/theckman/yacspin" - - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - ibchost "github.com/cosmos/ibc-go/v4/modules/core/24-host" - ibccoretypes "github.com/cosmos/ibc-go/v4/modules/core/types" - - atypes "github.com/akash-network/akash-api/go/node/audit/v1beta3" - ctypes "github.com/akash-network/akash-api/go/node/cert/v1beta3" - dtypes "github.com/akash-network/akash-api/go/node/deployment/v1beta3" - etypes "github.com/akash-network/akash-api/go/node/escrow/v1beta3" - mtypes "github.com/akash-network/akash-api/go/node/market/v1beta4" - ptypes "github.com/akash-network/akash-api/go/node/provider/v1beta3" - - "github.com/akash-network/node/x/audit" - "github.com/akash-network/node/x/cert" - "github.com/akash-network/node/x/deployment" - "github.com/akash-network/node/x/escrow" - "github.com/akash-network/node/x/market" - "github.com/akash-network/node/x/provider" -) - -type ( - GenesisValidators []tmtypes.GenesisValidator - StakingValidators []stakingtypes.Validator -) - -type iState interface { - pack(cdc codec.Codec) error - unpack(cdc codec.Codec) error -} - -func (u GenesisValidators) Len() int { - return len(u) -} - -func (u GenesisValidators) Swap(i, j int) { - u[i], u[j] = u[j], u[i] -} - -func (u GenesisValidators) Less(i, j int) bool { - return u[i].Power < u[j].Power -} - -func (u StakingValidators) Len() int { - return len(u) -} - -func (u StakingValidators) Swap(i, j int) { - u[i], u[j] = u[j], u[i] -} - -func (u StakingValidators) Less(i, j int) bool { - return u[i].DelegatorShares.LT(u[j].DelegatorShares) -} - -type AuthState struct { - gstate map[string]json.RawMessage - state authtypes.GenesisState - once sync.Once - accs authtypes.GenesisAccounts - unusedAccountNumbers []uint64 -} - -type BankState struct { - gstate map[string]json.RawMessage - state *banktypes.GenesisState - once sync.Once -} - -type GovState struct { - gstate map[string]json.RawMessage - state *govtypes.GenesisState - once sync.Once -} - -type IBCState struct { - gstate map[string]json.RawMessage - state *ibccoretypes.GenesisState - once sync.Once -} - -type StakingState struct { - gstate map[string]json.RawMessage - state *stakingtypes.GenesisState - once sync.Once -} - -type SlashingState struct { - gstate map[string]json.RawMessage - state *slashingtypes.GenesisState - once sync.Once -} - -type DistributionState struct { - gstate map[string]json.RawMessage - state *distributiontypes.GenesisState - once sync.Once -} - -type AuditState struct { - gstate map[string]json.RawMessage - state *atypes.GenesisState - once sync.Once -} - -type CertState struct { - gstate map[string]json.RawMessage - state *ctypes.GenesisState - once sync.Once -} - -type DeploymentState struct { - gstate map[string]json.RawMessage - state *dtypes.GenesisState - once sync.Once -} - -type EscrowState struct { - gstate map[string]json.RawMessage - state *etypes.GenesisState - once sync.Once -} - -type MarketState struct { - gstate map[string]json.RawMessage - state *mtypes.GenesisState - once sync.Once -} - -type ProviderState struct { - gstate map[string]json.RawMessage - state *ptypes.GenesisState - once sync.Once -} - -var ( - _ iState = (*AuthState)(nil) - _ iState = (*BankState)(nil) - _ iState = (*DistributionState)(nil) - _ iState = (*IBCState)(nil) - _ iState = (*GovState)(nil) - _ iState = (*StakingState)(nil) - _ iState = (*SlashingState)(nil) - _ iState = (*AuditState)(nil) - _ iState = (*CertState)(nil) - _ iState = (*DeploymentState)(nil) - _ iState = (*EscrowState)(nil) - _ iState = (*MarketState)(nil) - _ iState = (*ProviderState)(nil) -) - -type GenesisState struct { - doc *tmtypes.GenesisDoc - state map[string]json.RawMessage - - app struct { - AuthState - BankState - GovState - IBCState - StakingState - SlashingState - DistributionState - AuditState - CertState - DeploymentState - EscrowState - MarketState - ProviderState - } - - moduleAddresses struct { - bondedPool sdk.AccAddress - notBondedPool sdk.AccAddress - distribution sdk.AccAddress - } -} - -func NewGenesisState(sp *yacspin.Spinner, state map[string]json.RawMessage, doc *tmtypes.GenesisDoc) (*GenesisState, error) { - st := &GenesisState{ - doc: doc, - state: state, - } - - st.app.AuthState.gstate = state - st.app.BankState.gstate = state - st.app.GovState.gstate = state - st.app.IBCState.gstate = state - st.app.StakingState.gstate = state - st.app.SlashingState.gstate = state - st.app.DistributionState.gstate = state - st.app.AuditState.gstate = state - st.app.CertState.gstate = state - st.app.DeploymentState.gstate = state - st.app.EscrowState.gstate = state - st.app.MarketState.gstate = state - st.app.ProviderState.gstate = state - - sp.Message("lookup pool addresses") - sp.StopMessage("identified modules addresses") - _ = sp.Start() - - var err error - st.moduleAddresses.bondedPool, err = st.findModuleAccount(cdc, stakingtypes.BondedPoolName) - if err != nil { - return nil, fmt.Errorf("couldn't find bonded_tokens_pool account") // nolint: goerr113 - } - - st.moduleAddresses.notBondedPool, err = st.findModuleAccount(cdc, stakingtypes.NotBondedPoolName) - if err != nil { - return nil, fmt.Errorf("couldn't find not_bonded_tokens_pool account") // nolint: goerr113 - } - - st.moduleAddresses.distribution, err = st.findModuleAccount(cdc, "distribution") - if err != nil { - return nil, fmt.Errorf("couldn't find distribution account") // nolint: goerr113 - } - - if err = st.app.BankState.unpack(cdc); err != nil { - return nil, err - } - - if err = st.app.StakingState.unpack(cdc); err != nil { - return nil, err - } - - if err = st.validateBalances(); err != nil { - return nil, err - } - - _ = sp.Stop() - - return st, nil -} - -func (ga *GenesisState) validateBalances() error { - bondedTokens := sdk.ZeroInt() - notBondedTokens := sdk.ZeroInt() - - for _, val := range ga.app.StakingState.state.Validators { - switch val.GetStatus() { - case stakingtypes.Bonded: - bondedTokens = bondedTokens.Add(val.GetTokens()) - case stakingtypes.Unbonding, stakingtypes.Unbonded: - notBondedTokens = notBondedTokens.Add(val.GetTokens()) - default: - return fmt.Errorf("invalid validator status") // nolint: goerr113 - } - } - - for _, ubd := range ga.app.StakingState.state.UnbondingDelegations { - for _, entry := range ubd.Entries { - notBondedTokens = notBondedTokens.Add(entry.Balance) - } - } - - bondedCoins := sdk.NewCoins(sdk.NewCoin(ga.app.StakingState.state.Params.BondDenom, bondedTokens)) - notBondedCoins := sdk.NewCoins(sdk.NewCoin(ga.app.StakingState.state.Params.BondDenom, notBondedTokens)) - - var bondedBalance sdk.Coins - var notBondedBalance sdk.Coins - - for _, balance := range ga.app.BankState.state.Balances { - if balance.Address == ga.moduleAddresses.bondedPool.String() { - bondedBalance = bondedBalance.Add(balance.Coins...) - } - } - - for _, balance := range ga.app.BankState.state.Balances { - if balance.Address == ga.moduleAddresses.notBondedPool.String() { - notBondedBalance = notBondedBalance.Add(balance.Coins...) - } - } - - bondedBalance.Sort() - notBondedBalance.Sort() - - if !bondedBalance.IsEqual(bondedCoins) { - return fmt.Errorf("bonded pool balance is different from bonded coins: %s <-> %s", notBondedBalance, notBondedCoins) // nolint: goerr113 - } - - return nil -} - -func (ga *GenesisState) pack(cdc codec.Codec) error { - if err := ga.validateBalances(); err != nil { - return err - } - - if err := ga.ensureActiveSet(cdc); err != nil { - return err - } - - if err := ga.app.AuthState.pack(cdc); err != nil { - return err - } - - if err := ga.app.BankState.pack(cdc); err != nil { - return err - } - - if err := ga.app.GovState.pack(cdc); err != nil { - return err - } - - if err := ga.app.IBCState.pack(cdc); err != nil { - return err - } - - if err := ga.app.StakingState.pack(cdc); err != nil { - return err - } - - if err := ga.app.SlashingState.pack(cdc); err != nil { - return err - } - - if err := ga.app.DistributionState.pack(cdc); err != nil { - return err - } - - if err := ga.app.AuditState.pack(cdc); err != nil { - return err - } - - if err := ga.app.CertState.pack(cdc); err != nil { - return err - } - - if err := ga.app.DeploymentState.pack(cdc); err != nil { - return err - } - - if err := ga.app.EscrowState.pack(cdc); err != nil { - return err - } - - if err := ga.app.MarketState.pack(cdc); err != nil { - return err - } - - if err := ga.app.ProviderState.pack(cdc); err != nil { - return err - } - - state, err := json.Marshal(ga.state) - if err != nil { - return err - } - - ga.doc.AppState = state - - return nil -} - -func (ga *AuthState) unpack(cdc codec.Codec) error { - var err error - - ga.once.Do(func() { - ga.state = authtypes.GetGenesisStateFromAppState(cdc, ga.gstate) - ga.accs, err = authtypes.UnpackAccounts(ga.state.Accounts) - - if err != nil { - err = fmt.Errorf("failed to get accounts from any: %s", err.Error()) // nolint: goerr113 - } - - ga.accs = authtypes.SanitizeGenesisAccounts(ga.accs) - - prevAccountNumber := uint64(0) - for _, acc := range ga.accs { - diff := acc.GetAccountNumber() - prevAccountNumber - if diff > 1 { - accNumber := prevAccountNumber - for i := uint64(0); i < (diff - 1); i++ { - accNumber++ - ga.unusedAccountNumbers = append(ga.unusedAccountNumbers, accNumber) - } - } - - prevAccountNumber = acc.GetAccountNumber() - } - }) - - if err != nil { - return err - } - - return nil -} - -func (ga *AuthState) nextAccountNumber() uint64 { - if len(ga.unusedAccountNumbers) > 0 { - accNumber := ga.unusedAccountNumbers[0] - if len(ga.unusedAccountNumbers) > 1 { - ga.unusedAccountNumbers = ga.unusedAccountNumbers[1:] - } else { - ga.unusedAccountNumbers = []uint64{} - } - - return accNumber - } - - return ga.accs[len(ga.accs)-1].GetAccountNumber() + 1 -} - -func (ga *AuthState) pack(cdc codec.Codec) error { - if len(ga.accs) > 0 { - ga.accs = authtypes.SanitizeGenesisAccounts(ga.accs) - var err error - ga.state.Accounts, err = authtypes.PackAccounts(ga.accs) - if err != nil { - return fmt.Errorf("failed to convert accounts into any's: %s", err.Error()) // nolint: goerr113 - } - - stateBz, err := cdc.MarshalJSON(&ga.state) - if err != nil { - return fmt.Errorf("failed to marshal auth genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[authtypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *BankState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = banktypes.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *BankState) pack(cdc codec.Codec) error { - if ga.state != nil { - ga.state.Balances = banktypes.SanitizeGenesisBalances(ga.state.Balances) - - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal bank genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[banktypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *GovState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = GetGovGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *GovState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal gov genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[govtypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *IBCState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = GetIBCGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *IBCState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal ibc genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[ibchost.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *StakingState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = stakingtypes.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *StakingState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal staking genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[stakingtypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *SlashingState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = GetSlashingGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *SlashingState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal staking genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[slashingtypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *DistributionState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = GetDistributionGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *DistributionState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal distribution genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[distributiontypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -// nolint: unused -func (ga *AuditState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = audit.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *AuditState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal audit genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[atypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -// nolint: unused -func (ga *CertState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = cert.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *CertState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal cert genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[ctypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -// nolint: unused -func (ga *DeploymentState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = deployment.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *DeploymentState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal deployment genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[dtypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -// nolint: unused -func (ga *EscrowState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = escrow.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *EscrowState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal escrow genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[etypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -// nolint: unused -func (ga *MarketState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = market.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *MarketState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal market genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[mtypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -// nolint: unused -func (ga *ProviderState) unpack(cdc codec.Codec) error { - ga.once.Do(func() { - ga.state = provider.GetGenesisStateFromAppState(cdc, ga.gstate) - }) - - return nil -} - -func (ga *ProviderState) pack(cdc codec.Codec) error { - if ga.state != nil { - stateBz, err := cdc.MarshalJSON(ga.state) - if err != nil { - return fmt.Errorf("failed to marshal provider genesis state: %s", err.Error()) // nolint: goerr113 - } - - ga.gstate[ptypes.ModuleName] = stateBz - - ga.once = sync.Once{} - } - - return nil -} - -func (ga *GenesisState) createCoin(cdc codec.Codec, coin sdk.Coin) error { - if err := ga.app.BankState.unpack(cdc); err != nil { - return nil - } - - supply := ga.app.BankState.state.Supply - - supply = append(supply, coin) - - ga.app.BankState.state.Supply = supply.Sort() - - return nil -} - -func (ga *GenesisState) IncreaseSupply(cdc codec.Codec, coins ...sdk.Coin) error { - if err := ga.app.BankState.unpack(cdc); err != nil { - return nil - } - - for _, coin := range coins { - found := false - for idx, sCoin := range ga.app.BankState.state.Supply { - if sCoin.Denom == coin.Denom { - found = true - ga.app.BankState.state.Supply[idx] = ga.app.BankState.state.Supply[idx].Add(coin) - break - } - } - - if !found { - if err := ga.createCoin(cdc, coin); err != nil { - return err - } - } - } - - return nil -} - -func (ga *GenesisState) DecreaseSupply(cdc codec.Codec, coins ...sdk.Coin) error { - if err := ga.app.BankState.unpack(cdc); err != nil { - return nil - } - - for _, coin := range coins { - found := false - for idx, sCoin := range ga.app.BankState.state.Supply { - if sCoin.Denom == coin.Denom { - found = true - ga.app.BankState.state.Supply[idx] = ga.app.BankState.state.Supply[idx].Sub(coin) - break - } - } - - if !found { - return fmt.Errorf("cannot decrease supply for not existing token") // nolint: goerr113 - } - } - - return nil -} - -func (ga *GenesisState) SendFromModuleToModule(cdc codec.Codec, from, to sdk.AccAddress, amt sdk.Coins) error { - if err := ga.DecreaseBalances(cdc, from, amt); err != nil { - return err - } - - if err := ga.IncreaseBalances(cdc, to, amt); err != nil { - return err - } - - return nil -} - -func (ga *GenesisState) DelegateToPool(cdc codec.Codec, from, to sdk.AccAddress, amt sdk.Coins) error { - if err := ga.DecreaseBalances(cdc, from, amt); err != nil { - return err - } - - if err := ga.IncreaseBalances(cdc, to, amt); err != nil { - return err - } - - return nil -} - -// IncreaseBalances increases the balance of an account -// and the overall supply of corresponding token by the same amount -func (ga *GenesisState) IncreaseBalances(cdc codec.Codec, addr sdk.AccAddress, coins sdk.Coins) error { - if err := ga.app.BankState.unpack(cdc); err != nil { - return nil - } - - var balance *banktypes.Balance - - for idx := range ga.app.BankState.state.Balances { - if ga.app.BankState.state.Balances[idx].GetAddress().Equals(addr) { - balance = &ga.app.BankState.state.Balances[idx] - break - } - } - - if balance == nil { - return fmt.Errorf("no balances found for account (%s)", addr.String()) // nolint: goerr113 - } - - for _, coin := range coins { - found := false - for idx, bCoin := range balance.Coins { - if bCoin.Denom == coin.Denom { - found = true - balance.Coins[idx] = balance.Coins[idx].Add(coin) - break - } - } - - if !found { - balance.Coins = append(balance.Coins, coin) - } - } - - balance.Coins = balance.Coins.Sort() - - return nil -} - -func (ga *GenesisState) DecreaseBalances(cdc codec.Codec, addr sdk.AccAddress, coins sdk.Coins) error { - if err := ga.app.BankState.unpack(cdc); err != nil { - return nil - } - - var balance *banktypes.Balance - - for idx := range ga.app.BankState.state.Balances { - if ga.app.BankState.state.Balances[idx].GetAddress().Equals(addr) { - balance = &ga.app.BankState.state.Balances[idx] - break - } - } - - if balance == nil { - return fmt.Errorf("no balances found for account (%s)", addr.String()) // nolint: goerr113 - } - - for _, coin := range coins { - for idx, bCoin := range balance.Coins { - if bCoin.Denom == coin.Denom { - balance.Coins[idx] = balance.Coins[idx].Sub(coin) - break - } - } - } - - return nil -} - -func (ga *GenesisState) IncreaseDelegatorStake( - cdc codec.Codec, - addr sdk.AccAddress, - val sdk.ValAddress, - coins sdk.Coins, -) error { - if err := ga.app.StakingState.unpack(cdc); err != nil { - return err - } - - if err := ga.app.DistributionState.unpack(cdc); err != nil { - return err - } - - var info *distributiontypes.DelegatorStartingInfoRecord - var delegation *stakingtypes.Delegation - var sVal *stakingtypes.Validator - - for idx, vl := range ga.app.StakingState.state.Validators { - if vl.OperatorAddress == val.String() { - sVal = &ga.app.StakingState.state.Validators[idx] - } - } - - if sVal == nil { - return fmt.Errorf("staking validator (%s) does not exists", val.String()) // nolint: goerr113 - } - - for idx, d := range ga.app.StakingState.state.Delegations { - if d.DelegatorAddress == addr.String() && d.ValidatorAddress == val.String() { - delegation = &ga.app.StakingState.state.Delegations[idx] - break - } - } - - if delegation == nil { - ga.app.StakingState.state.Delegations = append(ga.app.StakingState.state.Delegations, stakingtypes.Delegation{ - DelegatorAddress: addr.String(), - ValidatorAddress: val.String(), - }) - - delegation = &ga.app.StakingState.state.Delegations[len(ga.app.StakingState.state.Delegations)-1] - } - - for idx, inf := range ga.app.DistributionState.state.DelegatorStartingInfos { - if inf.DelegatorAddress == addr.String() && inf.ValidatorAddress == val.String() { - info = &ga.app.DistributionState.state.DelegatorStartingInfos[idx] - break - } - } - - if info == nil { - ga.app.DistributionState.state.DelegatorStartingInfos = append(ga.app.DistributionState.state.DelegatorStartingInfos, - distributiontypes.DelegatorStartingInfoRecord{ - DelegatorAddress: addr.String(), - ValidatorAddress: val.String(), - StartingInfo: distributiontypes.DelegatorStartingInfo{ - PreviousPeriod: uint64(ga.doc.InitialHeight - 2), - Height: uint64(ga.doc.InitialHeight), - }, - }) - - info = &ga.app.DistributionState.state.DelegatorStartingInfos[len(ga.app.DistributionState.state.DelegatorStartingInfos)-1] - } - - stake := sdk.NewDec(0) - - for _, coin := range coins { - stake = stake.Add(coin.Amount.ToDec()) - *sVal, _ = sVal.AddTokensFromDel(coin.Amount) - } - - info.StartingInfo.Stake = stake - delegation.Shares = stake - - var err error - if sVal.IsBonded() { - err = ga.DelegateToPool(cdc, addr, ga.moduleAddresses.bondedPool, coins) - } else { - err = ga.DelegateToPool(cdc, addr, ga.moduleAddresses.notBondedPool, coins) - } - - if err != nil { - return err - } - - if err = ga.sortValidatorsByShares(); err != nil { - return err - } - - return nil -} - -func (ga *GenesisState) sortValidatorsByShares() error { - sVal := StakingValidators(ga.app.StakingState.state.Validators) - - sort.Sort(sort.Reverse(sVal)) - - ga.app.StakingState.state.Validators = sVal - - return nil -} - -func (ga *GenesisState) ensureActiveSet(cdc codec.Codec) error { - if err := ga.app.StakingState.unpack(cdc); err != nil { - return err - } - - sVals := ga.app.StakingState.state.Validators - - vCount := ga.app.StakingState.state.Params.MaxValidators - if vCount > uint32(len(sVals)) { - vCount = uint32(len(sVals)) - } - - vals := make([]tmtypes.GenesisValidator, 0, vCount) - sPowers := make([]stakingtypes.LastValidatorPower, 0, vCount) - - totalPower := int64(0) - - for i, val := range sVals { - coins := sdk.NewCoins(sdk.NewCoin(ga.app.StakingState.state.Params.BondDenom, val.Tokens)) - - if uint32(len(vals)) < vCount { - if val.IsJailed() { - continue - } - - if !val.IsBonded() { - sVals[i].Status = stakingtypes.Bonded - - err := ga.SendFromModuleToModule(cdc, ga.moduleAddresses.notBondedPool, ga.moduleAddresses.bondedPool, coins) - if err != nil { - return err - } - } - - pubkey, err := val.ConsPubKey() - if err != nil { - return err - } - - tmPk, err := cryptocodec.ToTmPubKeyInterface(pubkey) - if err != nil { - return err - } - - power := val.GetDelegatorShares().QuoInt64(denomDecimalPlaces).RoundInt64() - totalPower += power - - vals = append(vals, tmtypes.GenesisValidator{ - Address: sdk.ConsAddress(tmPk.Address()).Bytes(), - PubKey: tmPk, - Power: power, - Name: val.Description.Moniker, - }) - - sPowers = append(sPowers, stakingtypes.LastValidatorPower{ - Address: val.OperatorAddress, - Power: power, - }) - } else if val.IsBonded() { - sVals[i].Status = stakingtypes.Unbonding - sVals[i].UnbondingHeight = ga.doc.InitialHeight - err := ga.SendFromModuleToModule(cdc, ga.moduleAddresses.bondedPool, ga.moduleAddresses.notBondedPool, coins) - if err != nil { - return err - } - } - } - - ga.app.StakingState.state.LastTotalPower = sdk.NewInt(totalPower) - ga.app.StakingState.state.LastValidatorPowers = sPowers - sort.Sort(sort.Reverse(GenesisValidators(vals))) - - ga.doc.Validators = vals - - return nil -} - -func (ga *GenesisState) findModuleAccount(cdc codec.Codec, name string) (sdk.AccAddress, error) { - if err := ga.app.AuthState.unpack(cdc); err != nil { - return nil, err - } - - var addr sdk.AccAddress - for _, acc := range ga.app.AuthState.accs { - macc, valid := acc.(authtypes.ModuleAccountI) - if !valid { - continue - } - - if macc.GetName() == name { - addr = macc.GetAddress() - break - } - } - - return addr, nil -} - -func (ga *GenesisState) AddNewAccount(cdc codec.Codec, addr sdk.AccAddress, pubkey cryptotypes.PubKey) error { - if err := ga.app.AuthState.unpack(cdc); err != nil { - return err - } - - if err := ga.app.BankState.unpack(cdc); err != nil { - return err - } - - if ga.app.AuthState.accs.Contains(addr) { - return fmt.Errorf("account (%s) already exists", addr.String()) // nolint: goerr113 - } - - genAccount := authtypes.NewBaseAccount(addr, pubkey, ga.app.AuthState.nextAccountNumber(), 0) - - if err := genAccount.Validate(); err != nil { - return fmt.Errorf("failed to validate new genesis account: %s", err.Error()) // nolint: goerr113 - } - - ga.app.AuthState.accs = append(ga.app.AuthState.accs, genAccount) - ga.app.AuthState.accs = authtypes.SanitizeGenesisAccounts(ga.app.AuthState.accs) - - ga.app.BankState.state.Balances = append(ga.app.BankState.state.Balances, - banktypes.Balance{ - Address: addr.String(), - Coins: sdk.Coins{}, - }, - ) - - return nil -} - -func (ga *GenesisState) AddNewValidator( - cdc codec.Codec, - addr sdk.ValAddress, - pk cryptotypes.PubKey, - name string, - rates stakingtypes.CommissionRates, -) error { - if err := ga.app.StakingState.unpack(cdc); err != nil { - return err - } - - if err := ga.app.SlashingState.unpack(cdc); err != nil { - return err - } - - if err := ga.app.DistributionState.unpack(cdc); err != nil { - return err - } - - consAddr := sdk.ConsAddress(pk.Address()) - - pkAny, err := codectypes.NewAnyWithValue(pk) - if err != nil { - return err - } - - ga.app.StakingState.state.Validators = append(ga.app.StakingState.state.Validators, stakingtypes.Validator{ - OperatorAddress: addr.String(), - ConsensusPubkey: pkAny, - Jailed: false, - Status: stakingtypes.Unbonded, - Tokens: sdk.NewInt(0), - DelegatorShares: sdk.NewDec(0), - Description: stakingtypes.Description{ - Moniker: name, - }, - Commission: stakingtypes.Commission{ - CommissionRates: rates, - UpdateTime: time.Now().UTC(), - }, - MinSelfDelegation: sdk.NewInt(1), - }) - - ga.app.DistributionState.state.ValidatorHistoricalRewards = append(ga.app.DistributionState.state.ValidatorHistoricalRewards, - distributiontypes.ValidatorHistoricalRewardsRecord{ - ValidatorAddress: addr.String(), - Period: uint64(ga.doc.InitialHeight - 2), - Rewards: distributiontypes.ValidatorHistoricalRewards{ - CumulativeRewardRatio: sdk.DecCoins{}, - ReferenceCount: 2, - }, - }) - - ga.app.DistributionState.state.ValidatorCurrentRewards = append(ga.app.DistributionState.state.ValidatorCurrentRewards, - distributiontypes.ValidatorCurrentRewardsRecord{ - ValidatorAddress: addr.String(), - Rewards: distributiontypes.ValidatorCurrentRewards{ - Rewards: sdk.DecCoins{}, - Period: uint64(ga.doc.InitialHeight - 1), - }, - }) - - ga.app.SlashingState.state.SigningInfos = append(ga.app.SlashingState.state.SigningInfos, - slashingtypes.SigningInfo{ - Address: consAddr.String(), - ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{ - Address: consAddr.String(), - StartHeight: ga.doc.InitialHeight - 3, - IndexOffset: 0, - JailedUntil: time.Time{}, - Tombstoned: false, - MissedBlocksCounter: 0, - }, - }) - return nil -} diff --git a/cmd/akash/cmd/testnetify/testnetify.go b/cmd/akash/cmd/testnetify/testnetify.go new file mode 100644 index 0000000000..2305aa3e89 --- /dev/null +++ b/cmd/akash/cmd/testnetify/testnetify.go @@ -0,0 +1,523 @@ +package testnetify + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" + "github.com/tendermint/tendermint/crypto/tmhash" + + "github.com/tendermint/tendermint/node" + pvm "github.com/tendermint/tendermint/privval" + cmtstate "github.com/tendermint/tendermint/proto/tendermint/state" + cmtproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/store" + cmttypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdksrv "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/types" + + akash "github.com/akash-network/node/app" +) + +const ( + // testnet keys + + FlagTraceStore = "trace-store" + FlagTestnetRootDir = "testnet-rootdir" + KeyTestnetRootDir = FlagTestnetRootDir + + KeyIsTestnet = "is-testnet" + KeyTestnetConfig = "testnet-config" + KeyTestnetTriggerUpgrade = "testnet-trigger-upgrade" +) + +// GetCmd uses the provided chainID and operatorAddress as well as the local private validator key to +// control the network represented in the data folder. This is useful to create testnets nearly identical to your +// mainnet environment. +func GetCmd(testnetAppCreator types.AppCreator) *cobra.Command { + cmd := &cobra.Command{ + Use: "testnetify", + Short: "Create a testnet from current local state", + Long: `Create a testnet from current local state. +After utilizing this command the network will start. If the network is stopped, +the normal "start" command should be used. Re-using this command on state that +has already been modified by this command could result in unexpected behavior. + +Additionally, the first block may take a few minutes to be committed, depending +on how old the block is. For instance, if a snapshot was taken weeks ago and we want +to turn this into a testnet, it is possible lots of pending state needs to be committed +(expiring locks, etc.). It is recommended that you should wait for this block to be committed +before stopping the daemon. + +If the --trigger-testnet-upgrade flag is set, the upgrade handler specified by the flag will be run +on the first block of the testnet. + +Regardless of whether the flag is set or not, if any new stores are introduced in the daemon being run, +those stores will be registered in order to prevent panics. Therefore, you only need to set the flag if +you want to test the upgrade handler itself. +`, + Example: "testnetify", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + sctx := sdksrv.GetServerContextFromCmd(cmd) + cctx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + _, err = sdksrv.GetPruningOptionsFromFlags(sctx.Viper) + if err != nil { + return err + } + + rootDir, err := cmd.Flags().GetString(KeyTestnetRootDir) + if err != nil { + return err + } + + sctx.Logger.Info("testnetifying blockchain state") + cfg := TestnetConfig{} + + cfgFilePath, err := cmd.Flags().GetString(KeyTestnetConfig) + if err != nil { + return err + } + cfgFile, err := os.Open(cfgFilePath) + if err != nil { + return err + } + + defer func() { + _ = cfgFile.Close() + }() + + cfgData, err := io.ReadAll(cfgFile) + if err != nil { + return err + } + + if err = json.Unmarshal(cfgData, &cfg); err != nil { + return err + } + + sctx.Logger.Info(fmt.Sprintf("loaded config from %s", cfgFilePath)) + + if name, _ := cmd.Flags().GetString(KeyTestnetTriggerUpgrade); name != "" { + cfg.upgrade.Name = name + } + + if skip, _ := cmd.Flags().GetBool(flags.FlagSkipConfirmation); !skip { + // Confirmation prompt to prevent accidental modification of state. + reader := bufio.NewReader(os.Stdin) + fmt.Println("This operation will modify state in your data folder and cannot be undone. Do you want to continue? (y/n)") + text, _ := reader.ReadString('\n') + response := strings.TrimSpace(strings.ToLower(text)) + if response != "y" && response != "yes" { + fmt.Println("Operation canceled.") + return nil + } + } + + for i := range cfg.Validators { + cfg.Validators[i].Home = filepath.Join(rootDir, cfg.Validators[i].Home) + } + + home := sctx.Config.RootDir + db, err := openDB(home) + if err != nil { + return err + } + + traceWriter, traceCleanupFn, err := setupTraceWriter(sctx) + if err != nil { + return err + } + + app, err := testnetify(sctx, cfg, testnetAppCreator, db, traceWriter) + if err != nil { + return err + } + + defer func() { + traceCleanupFn() + + if localErr := db.Close(); localErr != nil { + sctx.Logger.Error(localErr.Error()) + } + }() + + go func() { + defer func() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt) + sigChan <- os.Interrupt + }() + ctx := cmd.Context() + + cctx, err := client.GetClientQueryContext(cmd) + if err != nil { + return + } + + ticker := time.NewTicker(time.Second) + timeout := time.After(1 * time.Minute) + + var h int64 + + loop: + for { + select { + case <-timeout: + ticker.Stop() + return + case <-ticker.C: + status, err := cctx.Client.Status(ctx) + if err == nil { + h = status.SyncInfo.LatestBlockHeight + break loop + } + } + } + + ticker = time.NewTicker(time.Second) + timeout = time.After(1 * time.Minute) + + for { + select { + case <-timeout: + ticker.Stop() + return + case <-ticker.C: + status, err := cctx.Client.Status(ctx) + if err == nil && status != nil { + if status.SyncInfo.LatestBlockHeight > h+1 { + return + } + } + } + } + }() + + err = sdksrv.StartInProcess(sctx, cctx, app) + if err != nil && !strings.Contains(err.Error(), "130") { + sctx.Logger.Error("testnetify finished with error", "err", err.Error()) + return err + } + + sctx.Logger.Info("testnetify completed") + + return nil + }, + } + + cmd.Flags().Bool(flags.FlagSkipConfirmation, false, "Skip the confirmation prompt") + cmd.Flags().String(KeyTestnetTriggerUpgrade, "", "If set (example: \"v1.0.0\"), triggers the v1.0.0 upgrade handler to run on the first block of the testnet") + cmd.Flags().StringP(KeyTestnetConfig, "c", "", "testnet config file config file") + cmd.Flags().String(KeyTestnetRootDir, "", "path to where testnet validators are located") + cmd.Flags().String(flags.FlagNode, "tcp://localhost:26657", "") + _ = cmd.MarkFlagRequired(KeyTestnetConfig) + _ = cmd.MarkFlagRequired(KeyTestnetRootDir) + + cmd.MarkFlagsRequiredTogether(KeyTestnetConfig, KeyTestnetRootDir) + + return cmd +} + +// testnetify modifies both state and blockStore, allowing the provided operator address and local validator key to control the network +// that the state in the data folder represents. The chainID of the local genesis file is modified to match the provided chainID. +func testnetify(sctx *sdksrv.Context, tcfg TestnetConfig, testnetAppCreator types.AppCreator, db dbm.DB, traceWriter io.WriteCloser) (types.Application, error) { + cfg := sctx.Config + + thisVal := cfg.PrivValidatorKeyFile() + sort.Slice(tcfg.Validators, func(i, j int) bool { + return thisVal == tcfg.Validators[i].Home + }) + + newChainID := tcfg.ChainID + + // Modify app genesis chain ID and save to the genesis file. + genDocProvider := node.DefaultGenesisDocProviderFunc(cfg) + cGen, err := genDocProvider() + if err != nil { + return nil, err + } + + cGen.GenesisDoc.ChainID = newChainID + err = cGen.GenesisDoc.ValidateAndComplete() + if err != nil { + return nil, err + } + + err = cGen.GenesisDoc.SaveAs(cfg.GenesisFile()) + if err != nil { + return nil, err + } + + blockStoreDB, err := node.DefaultDBProvider(&node.DBContext{ID: "blockstore", Config: cfg}) + if err != nil { + return nil, err + } + blockStore := store.NewBlockStore(blockStoreDB) + defer func() { + _ = blockStore.Close() + }() + + stateDB, err := node.DefaultDBProvider(&node.DBContext{ID: "state", Config: cfg}) + if err != nil { + return nil, err + } + defer func() { + _ = stateDB.Close() + }() + + jsonBlob, err := os.ReadFile(cfg.GenesisFile()) + if err != nil { + return nil, fmt.Errorf("couldn't read GenesisDoc file: %w", err) + } + + // Since we modified the chainID, we set the new genesisDocHash in the stateDB. + updatedChecksum := tmhash.Sum(jsonBlob) + + if err = stateDB.SetSync(node.GenesisDocHashKey, updatedChecksum); err != nil { + return nil, node.ErrSaveGenesisDocHash{Err: err} + } + + state, stateStore, _, err := node.LoadStateFromDBOrGenesisDocProvider(stateDB, genDocProvider, "") + if err != nil { + return nil, err + } + + appConfig := &akash.TestnetConfig{ + Accounts: tcfg.Accounts, + Gov: tcfg.Gov, + Validators: make([]akash.TestnetValidator, 0, len(tcfg.Validators)), + Upgrade: tcfg.upgrade, + } + + for i, val := range tcfg.Validators { + configDir := filepath.Join(val.Home, "config") + dataDir := filepath.Join(val.Home, "data") + + // Regenerate addrbook.json to prevent peers on old network from causing error logs. + addrBookPath := filepath.Join(configDir, "addrbook.json") + if err := os.Remove(addrBookPath); err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("failed to remove existing addrbook.json: %w", err) + } + + emptyAddrBook := []byte("{}") + if err := os.WriteFile(addrBookPath, emptyAddrBook, 0o600); err != nil { + return nil, fmt.Errorf("failed to create empty addrbook.json: %w", err) + } + + keyFile := filepath.Join(configDir, "priv_validator_key.json") + stateFile := filepath.Join(dataDir, "priv_validator_state.json") + + privValidator := pvm.LoadOrGenFilePV(keyFile, stateFile) + pubKey, err := privValidator.GetPubKey() + if err != nil { + return nil, err + } + validatorAddress := pubKey.Address() + + pubkey := &ed25519.PubKey{Key: pubKey.Bytes()} + consensusPubkey, err := codectypes.NewAnyWithValue(pubkey) + if err != nil { + return nil, err + } + + appConfig.Validators = append(appConfig.Validators, akash.TestnetValidator{ + OperatorAddress: val.Operator.Bytes(), + ConsensusAddress: pubKey.Address().Bytes(), + ConsensusPubKey: consensusPubkey, + Moniker: val.Moniker, + Commission: val.Commission, + MinSelfDelegation: val.MinSelfDelegation, + }) + + tcfg.Validators[i].privValidator = privValidator + tcfg.Validators[i].pubKey = pubKey + tcfg.Validators[i].validatorAddress = validatorAddress + tcfg.Validators[i].consAddress = pubKey.Address().Bytes() + } + + sctx.Viper.Set(KeyTestnetConfig, appConfig) + + testnetApp := testnetAppCreator(sctx.Logger, db, traceWriter, sctx.Viper) + + // We need to create a temporary proxyApp to get the initial state of the application. + // Depending on how the node was stopped, the application height can differ from the blockStore height. + // This height difference changes how we go about modifying the state. + cmtApp := testnetApp + + clientCreator := proxy.NewLocalClientCreator(cmtApp) + + proxyApp := proxy.NewAppConns(clientCreator) + if err := proxyApp.Start(); err != nil { + return nil, fmt.Errorf("error starting proxy app connections: %w", err) + } + res, err := proxyApp.Query().InfoSync(proxy.RequestInfo) + if err != nil { + return nil, fmt.Errorf("error calling Info: %w", err) + } + err = proxyApp.Stop() + if err != nil { + return nil, err + } + appHash := res.LastBlockAppHash + appHeight := res.LastBlockHeight + + var block *cmttypes.Block + switch { + case appHeight == blockStore.Height(): + block = blockStore.LoadBlock(blockStore.Height()) + // If the state's last blockstore height does not match the app and blockstore height, we likely stopped with the halt height flag. + if state.LastBlockHeight != appHeight { + state.LastBlockHeight = appHeight + block.AppHash = appHash + state.AppHash = appHash + } else { + // Node was likely stopped via SIGTERM, delete the next block's seen commit + err := blockStoreDB.Delete(fmt.Appendf(nil, "SC:%v", blockStore.Height()+1)) + if err != nil { + return nil, err + } + } + case blockStore.Height() > state.LastBlockHeight: + // This state usually occurs when we gracefully stop the node. + err = blockStore.DeleteLatestBlock() + if err != nil { + return nil, err + } + block = blockStore.LoadBlock(blockStore.Height()) + default: + // If there is any other state, we just load the block + block = blockStore.LoadBlock(blockStore.Height()) + } + + block.ChainID = newChainID + state.ChainID = newChainID + + block.LastBlockID = state.LastBlockID + block.LastCommit.BlockID = state.LastBlockID + + newValidators := make([]*cmttypes.Validator, 0, len(tcfg.Validators)) + + signatures := make([]cmttypes.CommitSig, 0, len(tcfg.Validators)) + + for _, val := range tcfg.Validators { + // Create a vote from our validator + vote := cmttypes.Vote{ + Type: cmtproto.PrecommitType, + Height: state.LastBlockHeight, + Round: 0, + BlockID: state.LastBlockID, + Timestamp: time.Now(), + ValidatorAddress: val.validatorAddress, + ValidatorIndex: 0, + Signature: []byte{}, + } + + voteProto := vote.ToProto() + + err = val.privValidator.SignVote(newChainID, voteProto) + if err != nil { + return nil, err + } + vote.Signature = voteProto.Signature + vote.Timestamp = voteProto.Timestamp + + signatures = append(signatures, cmttypes.CommitSig{ + BlockIDFlag: block.LastCommit.Signatures[0].BlockIDFlag, + ValidatorAddress: val.validatorAddress, + Timestamp: voteProto.Timestamp, + Signature: voteProto.Signature, + }) + + newValidators = append(newValidators, &cmttypes.Validator{ + Address: val.validatorAddress, + PubKey: val.pubKey, + VotingPower: 900000000000000, + }) + } + + // Replace all valSets in state to be the valSet with just our validator. + // and set the very first validator as proposer + newValSet := &cmttypes.ValidatorSet{ + Validators: newValidators, + Proposer: newValidators[0], + } + + // Modify the block's lastCommit to be signed only by our validator set + block.LastCommit.Signatures = signatures + + // Load the seenCommit of the lastBlockHeight and modify it to be signed from our validator + seenCommit := blockStore.LoadSeenCommit(state.LastBlockHeight) + + seenCommit.BlockID = state.LastBlockID + seenCommit.Round = 0 + seenCommit.Signatures = signatures + + err = blockStore.SaveSeenCommit(state.LastBlockHeight, seenCommit) + if err != nil { + return nil, err + } + + state.Validators = newValSet + state.LastValidators = newValSet + state.NextValidators = newValSet + state.LastHeightValidatorsChanged = blockStore.Height() + + err = stateStore.Save(state) + if err != nil { + return nil, err + } + + // Create a ValidatorsInfo struct to store in stateDB. + valSet, err := state.Validators.ToProto() + if err != nil { + return nil, err + } + valInfo := &cmtstate.ValidatorsInfo{ + ValidatorSet: valSet, + LastHeightChanged: state.LastBlockHeight, + } + buf, err := valInfo.Marshal() + if err != nil { + return nil, err + } + + // Modify Validators stateDB entry. + err = stateDB.Set(fmt.Appendf(nil, "validatorsKey:%v", blockStore.Height()), buf) + if err != nil { + return nil, err + } + + // Modify LastValidators stateDB entry. + err = stateDB.Set(fmt.Appendf(nil, "validatorsKey:%v", blockStore.Height()-1), buf) + if err != nil { + return nil, err + } + + // Modify NextValidators stateDB entry. + err = stateDB.Set(fmt.Appendf(nil, "validatorsKey:%v", blockStore.Height()+1), buf) + if err != nil { + return nil, err + } + + return testnetApp, err +} diff --git a/cmd/akash/cmd/testnetify/util.go b/cmd/akash/cmd/testnetify/util.go deleted file mode 100644 index 12c2a6a246..0000000000 --- a/cmd/akash/cmd/testnetify/util.go +++ /dev/null @@ -1,52 +0,0 @@ -package testnetify - -import ( - "encoding/json" - - "github.com/cosmos/cosmos-sdk/codec" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - ibchost "github.com/cosmos/ibc-go/v4/modules/core/24-host" - ibccoretypes "github.com/cosmos/ibc-go/v4/modules/core/types" -) - -func GetIBCGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *ibccoretypes.GenesisState { - var genesisState ibccoretypes.GenesisState - - if appState[ibchost.ModuleName] != nil { - cdc.MustUnmarshalJSON(appState[ibchost.ModuleName], &genesisState) - } - - return &genesisState -} - -func GetGovGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *govtypes.GenesisState { - var genesisState govtypes.GenesisState - - if appState[govtypes.ModuleName] != nil { - cdc.MustUnmarshalJSON(appState[govtypes.ModuleName], &genesisState) - } - - return &genesisState -} - -func GetSlashingGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *slashingtypes.GenesisState { - var genesisState slashingtypes.GenesisState - - if appState[slashingtypes.ModuleName] != nil { - cdc.MustUnmarshalJSON(appState[slashingtypes.ModuleName], &genesisState) - } - - return &genesisState -} - -func GetDistributionGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *distributiontypes.GenesisState { - var genesisState distributiontypes.GenesisState - - if appState[distributiontypes.ModuleName] != nil { - cdc.MustUnmarshalJSON(appState[distributiontypes.ModuleName], &genesisState) - } - - return &genesisState -} diff --git a/cmd/akash/cmd/testnetify/utils.go b/cmd/akash/cmd/testnetify/utils.go new file mode 100644 index 0000000000..c2275a33fa --- /dev/null +++ b/cmd/akash/cmd/testnetify/utils.go @@ -0,0 +1,49 @@ +package testnetify + +import ( + "io" + "os" + "path/filepath" + + sdksrv "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + dbm "github.com/tendermint/tm-db" +) + +func openDB(rootDir string) (dbm.DB, error) { + dataDir := filepath.Join(rootDir, "data") + return sdk.NewLevelDB("application", dataDir) +} + +func setupTraceWriter(svrCtx *sdksrv.Context) (traceWriter io.WriteCloser, cleanup func(), err error) { + // clean up the traceWriter when the server is shutting down + cleanup = func() {} + + traceWriterFile := svrCtx.Viper.GetString(FlagTraceStore) + traceWriter, err = openTraceWriter(traceWriterFile) + if err != nil { + return traceWriter, cleanup, err + } + + // if flagTraceStore is not used, then traceWriter is nil + if traceWriter != nil { + cleanup = func() { + if err = traceWriter.Close(); err != nil { + svrCtx.Logger.Error("failed to close trace writer", "err", err) + } + } + } + + return traceWriter, cleanup, nil +} + +func openTraceWriter(traceWriterFile string) (w io.WriteCloser, err error) { + if traceWriterFile == "" { + return + } + return os.OpenFile( + traceWriterFile, + os.O_WRONLY|os.O_APPEND|os.O_CREATE, + 0o666, + ) +} diff --git a/cmd/akash/cmd/testnetify/validators.go b/cmd/akash/cmd/testnetify/validators.go deleted file mode 100644 index 90bf46d632..0000000000 --- a/cmd/akash/cmd/testnetify/validators.go +++ /dev/null @@ -1,30 +0,0 @@ -package testnetify - -import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (ga *GenesisState) modifyValidators(cdc codec.Codec, cfg *ValidatorsConfig) error { - for _, val := range cfg.Add { - operatorAddress := sdk.ValAddress(val.Operator.AccAddress) - - if err := ga.AddNewValidator(cdc, operatorAddress, val.PubKey.PubKey, val.Name, val.Rates); err != nil { - return err - } - - for _, delegator := range val.Delegators { - err := ga.IncreaseDelegatorStake( - cdc, - delegator.Address.AccAddress, - operatorAddress, - delegator.Coins.ToSDK(), - ) - if err != nil { - return err - } - } - } - - return nil -} diff --git a/go.mod b/go.mod index 8bbb37a30f..df4821bae1 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-go/v4 v4.6.0 github.com/gogo/protobuf v1.3.3 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/go-github/v56 v56.0.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 @@ -31,7 +32,6 @@ require ( github.com/stretchr/testify v1.10.0 github.com/tendermint/tendermint v0.34.27 github.com/tendermint/tm-db v0.6.7 - github.com/theckman/yacspin v0.13.12 go.step.sm/crypto v0.44.6 golang.org/x/mod v0.17.0 golang.org/x/oauth2 v0.23.0 @@ -52,7 +52,7 @@ retract ( replace ( // use cosmos fork of keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 - github.com/cosmos/cosmos-sdk => github.com/akash-network/cosmos-sdk v0.45.16-akash.3 + github.com/cosmos/cosmos-sdk => github.com/akash-network/cosmos-sdk v0.45.16-akash.5 // use akash version of cosmos ledger api github.com/cosmos/ledger-cosmos-go => github.com/akash-network/ledger-go/cosmos v0.14.4 @@ -68,7 +68,7 @@ replace ( github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // use cometBFT system fork of tendermint with akash patches - github.com/tendermint/tendermint => github.com/akash-network/cometbft v0.34.27-akash.3 + github.com/tendermint/tendermint => github.com/akash-network/cometbft v0.34.27-akash.5 github.com/zondax/hid => github.com/troian/hid v0.13.2 github.com/zondax/ledger-go => github.com/akash-network/ledger-go v0.14.3 @@ -77,8 +77,6 @@ replace ( google.golang.org/grpc => google.golang.org/grpc v1.33.2 ) -require github.com/golang-jwt/jwt/v5 v5.2.2 - require ( cosmossdk.io/api v0.2.6 // indirect cosmossdk.io/core v0.5.1 // indirect @@ -121,7 +119,6 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/edwingeng/deque/v2 v2.1.1 // indirect - github.com/fatih/color v1.14.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect @@ -160,7 +157,6 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect @@ -175,7 +171,6 @@ require ( github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/cors v1.8.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 0bad0c717e..d38cd7872c 100644 --- a/go.sum +++ b/go.sum @@ -78,10 +78,10 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/akash-network/akash-api v0.0.82 h1:/Qkwhp1hm6UAzWgTBQdghrbSVArjq9hGzeOcycgCVqI= github.com/akash-network/akash-api v0.0.82/go.mod h1:Y9Bq4S3c/MMjvWj/3KuEYmDfv3leo9OeGGxo7xL0pVc= -github.com/akash-network/cometbft v0.34.27-akash.3 h1:ObmkKrMybIuRLPcwPwUMJ8Pllsr+Gsve443mkJsonMA= -github.com/akash-network/cometbft v0.34.27-akash.3/go.mod h1:BcCbhKv7ieM0KEddnYXvQZR+pZykTKReJJYf7YC7qhw= -github.com/akash-network/cosmos-sdk v0.45.16-akash.3 h1:QiHOQ1ACzCvAEXRlzGNQhp9quWLOowE104D0uESGrEk= -github.com/akash-network/cosmos-sdk v0.45.16-akash.3/go.mod h1:NTnk/GuQdFyfk/iGFxDAgQH9fwcbRW/hREap6qaPg48= +github.com/akash-network/cometbft v0.34.27-akash.5 h1:Xhj+lCkN5XhN04sMuhfA0wUN9iWFSehynIwNjFYNVxE= +github.com/akash-network/cometbft v0.34.27-akash.5/go.mod h1:BcCbhKv7ieM0KEddnYXvQZR+pZykTKReJJYf7YC7qhw= +github.com/akash-network/cosmos-sdk v0.45.16-akash.5 h1:1Pcoo37GFr8DETF61ej47jHzVQlD/xP/s+6Saajj/Is= +github.com/akash-network/cosmos-sdk v0.45.16-akash.5/go.mod h1:W3yB30N27EE+hFVxm3Y0k/S0LjZekPrjAa2TyzZ3Ypw= github.com/akash-network/ledger-go v0.14.3 h1:LCEFkTfgGA2xFMN2CtiKvXKE7dh0QSM77PJHCpSkaAo= github.com/akash-network/ledger-go v0.14.3/go.mod h1:NfsjfFvno9Kaq6mfpsKz4sqjnAVVEsVsnBJfKB4ueAs= github.com/akash-network/ledger-go/cosmos v0.14.4 h1:h3WiXmoKKs9wkj1LHcJ12cLjXXg6nG1fp+UQ5+wu/+o= @@ -314,8 +314,6 @@ github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+ne github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -707,8 +705,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= @@ -886,8 +882,6 @@ github.com/regen-network/gocuke v0.6.2/go.mod h1:zYaqIHZobHyd0xOrHGPQjbhGJsuZ1oE github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -994,8 +988,6 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= -github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4= -github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg= github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= diff --git a/tests/upgrade/testnet.json b/tests/upgrade/testnet.json new file mode 100644 index 0000000000..83252dd2be --- /dev/null +++ b/tests/upgrade/testnet.json @@ -0,0 +1,25 @@ +{ + "chain_id": "localakash", + "gov": { + "voting_params": { + "voting_period": "60s" + } + }, + "accounts": [ + "akash1dge0lcc5rqxkw52rgav4q0fyhx7arcufmrkyww" + ], + "validators": [ + { + "moniker": "upgrade-tester-1", + "operator": "akash1dge0lcc5rqxkw52rgav4q0fyhx7arcufmrkyww", + "bonded": true, + "home": ".akash0", + "commission": { + "rate": "0.05", + "maxRate": "0.8", + "maxChangeRate": "0.1" + }, + "min_self_delegation": "1" + } + ] +} diff --git a/util/server/server.go b/util/server/server.go index fb8c51919b..c8d9af514d 100644 --- a/util/server/server.go +++ b/util/server/server.go @@ -190,3 +190,10 @@ func openTraceWriter(traceWriterFile string) (w io.Writer, err error) { 0o666, ) } + +// AddTestnetCreatorCommand allows chains to create a testnet from the state existing in their node's data directory. +func AddTestnetCreatorCommand(rootCmd *cobra.Command, appCreator types.AppCreator, addStartFlags types.ModuleInitFlags) { + testnetCreateCmd := sdkserver.InPlaceTestnetCreator(appCreator) + addStartFlags(testnetCreateCmd) + rootCmd.AddCommand(testnetCreateCmd) +}