diff --git a/cmd/rollapp/da/switch.go b/cmd/rollapp/da/switch.go new file mode 100644 index 000000000..efe41a352 --- /dev/null +++ b/cmd/rollapp/da/switch.go @@ -0,0 +1,153 @@ +package da + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/cosmos/cosmos-sdk/x/params/client/utils" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + + initconfig "github.com/dymensionxyz/roller/cmd/config/init" + "github.com/dymensionxyz/roller/cmd/consts" + datalayer "github.com/dymensionxyz/roller/data_layer" + "github.com/dymensionxyz/roller/data_layer/avail" + "github.com/dymensionxyz/roller/utils/config/tomlconfig" + "github.com/dymensionxyz/roller/utils/filesystem" + "github.com/dymensionxyz/roller/utils/genesis" + "github.com/dymensionxyz/roller/utils/gov" + "github.com/dymensionxyz/roller/utils/rollapp" + "github.com/dymensionxyz/roller/utils/roller" + sequtils "github.com/dymensionxyz/roller/utils/sequencer" +) + +func Cmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "switch-da", + Short: "Switch to another DA.", + Long: ``, + Args: cobra.MaximumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + envs := []string{"avail"} // TODO: support more DAs + env, _ := pterm.DefaultInteractiveSelect. + WithDefaultText("select the DA you want to switch"). + WithOptions(envs). + Show() + + err := initconfig.AddFlags(cmd) + if err != nil { + pterm.Error.Println("failed to switch DA: ", err) + return + } + + home, err := filesystem.ExpandHomePath( + cmd.Flag(initconfig.GlobalFlagNames.Home).Value.String(), + ) + if err != nil { + pterm.Error.Println("failed to switch DA: ", err) + return + } + + rollappConfig, err := roller.LoadConfig(home) + if err != nil { + pterm.Error.Println("failed to load roller config: ", err) + return + } + + if strings.ToLower(string(rollappConfig.DA.Backend)) == env { + pterm.Info.Println("You are switching to the same DA as previous!") + return + } + + var dalayer datalayer.DataLayer + switch env { + case "avail": + raResp, err := rollapp.GetMetadataFromChain(rollappConfig.RollappID, rollappConfig.HubData) + if err != nil { + pterm.Error.Println("failed to get metadata from chain: ", err) + return + } + + drsVersion, err := genesis.GetDrsVersionFromGenesis(home, raResp) + if err != nil { + pterm.Error.Println("failed to get drs version from genesis: ", err) + return + } + + drsVersionInt, err := strconv.ParseInt(drsVersion, 10, 64) + if err != nil { + pterm.Error.Println("failed to get drs version from genesis: ", err) + return + } + + if !rollapp.IsDaConfigNewFormat(drsVersionInt, strings.ToLower(raResp.Rollapp.VmType)) { + pterm.Error.Println("required Rollapp DRS version of at least", drsVersionInt) + return + } + + dalayer = avail.NewAvail(home) + + default: + pterm.Error.Println("switch does not support da: ", env) + return + } + + submited, _ := pterm.DefaultInteractiveConfirm.WithDefaultText("Have you submitted to gov yet?").Show() + if !submited { + // Create Gov + keyName, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Enter your key name").Show() + keyring, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Enter your keyring-backend").Show() + title, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Enter your Proposal Title").Show() + description, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Enter your Proposal Description").Show() + deposit, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("Enter your Proposal Deposit").Show() + newDAParam := json.RawMessage(fmt.Sprintf(`"%s"`, env)) + txHash, err := gov.ParamChangeProposal(home, keyName, keyring, + &utils.ParamChangeProposalJSON{ + Title: title, + Description: description, + Changes: utils.ParamChangesJSON{ + utils.NewParamChangeJSON("rollappparams", "da", newDAParam), + }, + Deposit: deposit + rollappConfig.Denom, + }) + + if err != nil { + pterm.Error.Println("failed to submit proposal", err) + return + } + pterm.Info.Println("Proposal Tx hash: ", txHash) + } + + daConfig := dalayer.GetSequencerDAConfig(consts.NodeType.Sequencer) + rollappConfig.DA.Backend = consts.DAType(env) + + dymintConfigPath := sequtils.GetDymintFilePath(home) + + pterm.Info.Println("updating dymint configuration") + + _ = tomlconfig.UpdateFieldInFile( + dymintConfigPath, + "da_layer", + []string{string(env)}, + ) + + _ = tomlconfig.UpdateFieldInFile( + dymintConfigPath, + "da_config", + daConfig, + ) + + if err := roller.WriteConfig(rollappConfig); err != nil { + pterm.Error.Println("failed to write roller config", err) + return + } + + pterm.Info.Println("the config update process is complete! Now you need to restart the nodes before the proposal passes.") + + }, + } + + return cmd +} diff --git a/cmd/rollapp/drs/upgrade.go b/cmd/rollapp/drs/upgrade.go index d3911459e..a7a6985d2 100644 --- a/cmd/rollapp/drs/upgrade.go +++ b/cmd/rollapp/drs/upgrade.go @@ -3,6 +3,7 @@ package drs import ( "os" "path/filepath" + "strconv" "strings" "github.com/pterm/pterm" @@ -158,9 +159,21 @@ func UpgradeCmd() *cobra.Command { rollappConfig.KeyringBackend, ) + drsVersionInt, err := strconv.ParseInt(drsVersion, 10, 64) + if err != nil { + pterm.Error.Println("failed to get drs version from binary: ", err) + return + } + + targetDrsInt, err := strconv.ParseInt(drsVersion, 10, 64) + if err != nil { + pterm.Error.Println("failed to convert drs version from arg: ", err) + return + } + if rollapp.IsDAConfigMigrationRequired( - drsVersion, - targetDrs, + drsVersionInt, + targetDrsInt, strings.ToLower(raResp.Rollapp.VmType), ) { upgradeDaConfig(sequencer.GetDymintFilePath(home), *damanager) diff --git a/cmd/rollapp/rollapp.go b/cmd/rollapp/rollapp.go index f41baee21..78047684f 100644 --- a/cmd/rollapp/rollapp.go +++ b/cmd/rollapp/rollapp.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/dymensionxyz/roller/cmd/rollapp/config" + "github.com/dymensionxyz/roller/cmd/rollapp/da" "github.com/dymensionxyz/roller/cmd/rollapp/drs" initrollapp "github.com/dymensionxyz/roller/cmd/rollapp/init" "github.com/dymensionxyz/roller/cmd/rollapp/keys" @@ -36,6 +37,7 @@ func Cmd() *cobra.Command { cmd.AddCommand(migrate.Cmd()) cmd.AddCommand(drs.Cmd()) cmd.AddCommand(snapshot.Cmd()) + cmd.AddCommand(da.Cmd()) sl := []string{"rollapp"} cmd.AddCommand( diff --git a/cmd/rollapp/setup/setup.go b/cmd/rollapp/setup/setup.go index 8f77c55ba..6b0af0c48 100644 --- a/cmd/rollapp/setup/setup.go +++ b/cmd/rollapp/setup/setup.go @@ -1070,7 +1070,13 @@ func getDaLayer(home string, raResponse *rollapp.ShowRollappResponse, daType con return nil } - if rollapp.IsDaConfigNewFormat(drsVersion, strings.ToLower(raResponse.Rollapp.VmType)) { + drsVersionInt, err := strconv.ParseInt(drsVersion, 10, 64) + if err != nil { + pterm.Error.Println("failed to get drs version from genesis: ", err) + return nil + } + + if rollapp.IsDaConfigNewFormat(drsVersionInt, strings.ToLower(raResponse.Rollapp.VmType)) { return []string{string(daType)} } else { return daType @@ -1087,7 +1093,13 @@ func getDaConfig(dataLayer datalayer.DataLayer, nodeType string, home string, ra return nil } - if rollapp.IsDaConfigNewFormat(drsVersion, strings.ToLower(raResponse.Rollapp.VmType)) { + drsVersionInt, err := strconv.ParseInt(drsVersion, 10, 64) + if err != nil { + pterm.Error.Println("failed to get drs version from genesis: ", err) + return nil + } + + if rollapp.IsDaConfigNewFormat(drsVersionInt, strings.ToLower(raResponse.Rollapp.VmType)) { return []string{daConfig} } else { return daConfig diff --git a/data_layer/avail/appid.go b/data_layer/avail/appid.go index 83e0d4d27..3d6878617 100644 --- a/data_layer/avail/appid.go +++ b/data_layer/avail/appid.go @@ -11,12 +11,19 @@ import ( func CreateAppID(rpcURL, seedPhrase, rollappID string) (appID uint32, err error) { api, err := sdk.NewSDK(rpcURL) if err != nil { - fmt.Printf("cannot create api:%v", err) + return 0, fmt.Errorf("cannot create appID: %v", err) } // If the appID corresponding to the rollappID already exists, it will be reused. blockStorage, err := api.Client.StorageAt(prim.None[prim.H256]()) + if err != nil { + return 0, err + } + acc, err := sdk.Account.NewKeyPair(seedPhrase) + if err != nil { + return 0, err + } // Fetch Map Storage storage := daPallet.StorageAppKeys{} diff --git a/data_layer/avail/avail.go b/data_layer/avail/avail.go index 76d1f741a..1f1aef1c8 100644 --- a/data_layer/avail/avail.go +++ b/data_layer/avail/avail.go @@ -92,6 +92,14 @@ func NewAvail(root string) *Avail { panic(err) } + daData, exists := consts.DaNetworks[daNetwork] + if !exists { + panic(fmt.Errorf("DA network configuration not found for: %s", daNetwork)) + } + availConfig.RpcEndpoint = daData.ApiUrl + availConfig.AccAddress = keyringPair.Address + availConfig.Root = root + pterm.DefaultSection.WithIndentCharacter("🔔").Println("Please fund your Avail addresses below") pterm.DefaultBasicText.Println(pterm.LightGreen(keyringPair.Address)) @@ -106,23 +114,15 @@ func NewAvail(root string) *Avail { insufficientBalances, err := availConfig.CheckDABalance() if err != nil { - pterm.Error.Println("failed to check balance", err) + panic(pterm.Error.Println("failed to check balance", err)) } err = keys.PrintInsufficientBalancesIfAny(insufficientBalances) if err != nil { - pterm.Error.Println("failed to check insufficient balances: ", err) + panic(pterm.Error.Println("failed to check insufficient balances: ", err)) } - daData, exists := consts.DaNetworks[daNetwork] - if !exists { - panic(fmt.Errorf("DA network configuration not found for: %s", daNetwork)) - } - availConfig.RpcEndpoint = daData.ApiUrl - availConfig.AccAddress = keyringPair.Address - availConfig.Root = root - - availConfig.AppID, err = CreateAppID(rollerData.DA.ApiUrl, availConfig.Mnemonic, rollerData.RollappID) + availConfig.AppID, err = CreateAppID(daData.ApiUrl, availConfig.Mnemonic, rollerData.RollappID) if err != nil { panic(err) } diff --git a/utils/gov/param.go b/utils/gov/param.go new file mode 100644 index 000000000..443062632 --- /dev/null +++ b/utils/gov/param.go @@ -0,0 +1,67 @@ +package gov + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + + paramsutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" + "github.com/dymensionxyz/roller/cmd/consts" + bashutils "github.com/dymensionxyz/roller/utils/bash" + "github.com/pterm/pterm" +) + +type CosmosTx struct { + TxHash string `json:"txhash"` + Code int `json:"code"` + RawLog string `json:"raw_log"` +} + +// ParamChangeProposal submits a param change proposal to the chain, signed by keyName. +func ParamChangeProposal(home, keyName, keyring string, prop *paramsutils.ParamChangeProposalJSON) (string, error) { + content, err := json.Marshal(prop) + if err != nil { + return "", err + } + + hash := sha256.Sum256(content) + proposalFilename := fmt.Sprintf("%x.json", hash) + proposalPath := filepath.Join(home, proposalFilename) + + err = os.WriteFile(proposalPath, content, 0o644) + if err != nil { + pterm.Error.Printf("Error writing prop file: %v\n", err) + return "", err + } + + cmd := exec.Command( + consts.Executables.RollappEVM, + "tx", "gov", "submit-legacy-proposal", + "param-change", + proposalPath, + "--gas", "auto", + "--from", keyName, + "--keyring-backend", keyring, + "--output", "json", + "-y", + ) + + out, err := bashutils.ExecCommandWithStdout(cmd) + if err != nil { + return "", err + } + + output := CosmosTx{} + err = json.Unmarshal(out.Bytes(), &output) + if err != nil { + return "", err + } + if output.Code != 0 { + return output.TxHash, fmt.Errorf("transaction failed with code %d: %s", output.Code, output.RawLog) + } + + return output.TxHash, nil +} diff --git a/utils/rollapp/rollapp.go b/utils/rollapp/rollapp.go index 6f826ba1a..465c06ceb 100644 --- a/utils/rollapp/rollapp.go +++ b/utils/rollapp/rollapp.go @@ -24,8 +24,8 @@ import ( ) const ( - minEvmDRSNewDAConfig = "6" - minWasmDRSNewDAConfig = "9" + minEvmDRSNewDAConfig = 6 + minWasmDRSNewDAConfig = 9 ) func GetHomeDir(home string) string { @@ -310,25 +310,25 @@ func Show(raID string, hd consts.HubData) (*ShowRollappResponse, error) { return &raResponse, nil } -func IsDaConfigNewFormat(drsVersion string, evmType string) bool { - if drsVersion >= minEvmDRSNewDAConfig && "evm" == evmType || - drsVersion >= minWasmDRSNewDAConfig && "wasm" == evmType { +func IsDaConfigNewFormat(drsVersion int64, vmType string) bool { + if drsVersion >= minEvmDRSNewDAConfig && "evm" == vmType || + drsVersion >= minWasmDRSNewDAConfig && "wasm" == vmType { return true } return false } -func IsDAConfigMigrationRequired(oldDRS string, newDRS string, evmType string) bool { - if oldDRS >= minEvmDRSNewDAConfig && "evm" == evmType { +func IsDAConfigMigrationRequired(oldDRS int64, newDRS int64, vmType string) bool { + if oldDRS >= minEvmDRSNewDAConfig && "evm" == vmType { return false } - if oldDRS >= minWasmDRSNewDAConfig && "wasm" == evmType { + if oldDRS >= minWasmDRSNewDAConfig && "wasm" == vmType { return false } - if newDRS < minEvmDRSNewDAConfig && "evm" == evmType { + if newDRS < minEvmDRSNewDAConfig && "evm" == vmType { return false } - if newDRS < minWasmDRSNewDAConfig && "wasm" == evmType { + if newDRS < minWasmDRSNewDAConfig && "wasm" == vmType { return false } return true