From 2a99f2c35272905c83f9d66ea6e0eef95164a6ce Mon Sep 17 00:00:00 2001 From: artpav <19916123+artemijspavlovs@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:08:55 +0200 Subject: [PATCH] feat(eibc): store json object as the group and policy metadata (#1153) --- cmd/consts/dymension.go | 2 - cmd/consts/eibc.go | 6 + cmd/eibc/fulfill/feeshare/feeshare.go | 69 +++ cmd/eibc/fulfill/fulfill.go | 2 + cmd/eibc/fulfill/rollapps/remove/remove.go | 16 + cmd/eibc/fulfill/rollapps/set/set.go | 19 +- cmd/eibc/init/init.go | 636 ++++++++++----------- sequencer/status.go | 2 + utils/eibc/config.go | 34 ++ utils/eibc/eibc.go | 2 +- utils/eibc/operatormetadata.go | 325 +++++++++++ utils/eibc/types.go | 53 ++ 12 files changed, 829 insertions(+), 337 deletions(-) create mode 100644 cmd/consts/eibc.go create mode 100644 cmd/eibc/fulfill/feeshare/feeshare.go create mode 100644 utils/eibc/config.go create mode 100644 utils/eibc/operatormetadata.go diff --git a/cmd/consts/dymension.go b/cmd/consts/dymension.go index 8c241afa..8e214877 100644 --- a/cmd/consts/dymension.go +++ b/cmd/consts/dymension.go @@ -1,7 +1,5 @@ package consts -const DefaultIndexer = "http://44.206.211.230:3000/" - var MainnetHubData = HubData{ Environment: "mainnet", ApiUrl: "https://dymension-mainnet-rest.public.blastapi.io", diff --git a/cmd/consts/eibc.go b/cmd/consts/eibc.go new file mode 100644 index 00000000..595b5df5 --- /dev/null +++ b/cmd/consts/eibc.go @@ -0,0 +1,6 @@ +package consts + +const ( + DefaultIndexer = "http://44.206.211.230:3000/" + DefaultEibcOperatorFeeShare = 0.1 +) diff --git a/cmd/eibc/fulfill/feeshare/feeshare.go b/cmd/eibc/fulfill/feeshare/feeshare.go new file mode 100644 index 00000000..5235b8c8 --- /dev/null +++ b/cmd/eibc/fulfill/feeshare/feeshare.go @@ -0,0 +1,69 @@ +package feeshare + +import ( + "os" + "path/filepath" + "strconv" + + "github.com/pterm/pterm" + "github.com/spf13/cobra" + + "github.com/dymensionxyz/roller/cmd/consts" + "github.com/dymensionxyz/roller/utils/config/yamlconfig" + eibcutils "github.com/dymensionxyz/roller/utils/eibc" +) + +func Cmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "fee-share [fee-share]", + Short: "Set", + Example: "roller eibc fulfill fee-share 0.1", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + home, err := os.UserHomeDir() + if err != nil { + pterm.Error.Println("failed to get user home dir", err) + return + } + + if err != nil { + pterm.Error.Println("failed to expand home directory") + return + } + + eibcHome := filepath.Join(home, consts.ConfigDirName.Eibc) + eibcConfigPath := filepath.Join(eibcHome, "config.yaml") + + fee := args[0] + ff, err := strconv.ParseFloat(fee, 64) + if err != nil { + pterm.Error.Printf("fee must be a valid number, got %s\n", fee) + return + } + + updates := map[string]interface{}{ + "operator.min_fee_share": ff, + } + err = yamlconfig.UpdateNestedYAML(eibcConfigPath, updates) + if err != nil { + pterm.Error.Println("failed to update config", err) + return + } + + var cfg eibcutils.Config + err = cfg.LoadConfig(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to load eibc config: ", err) + return + } + + err = eibcutils.UpdateGroupOperatorMinFee(eibcConfigPath, ff, cfg, home) + if err != nil { + pterm.Error.Println("failed to update eibc operator metadata: ", err) + return + } + }, + } + + return cmd +} diff --git a/cmd/eibc/fulfill/fulfill.go b/cmd/eibc/fulfill/fulfill.go index 6c4c3376..2f626c05 100644 --- a/cmd/eibc/fulfill/fulfill.go +++ b/cmd/eibc/fulfill/fulfill.go @@ -3,6 +3,7 @@ package fulfill import ( "github.com/spf13/cobra" + "github.com/dymensionxyz/roller/cmd/eibc/fulfill/feeshare" "github.com/dymensionxyz/roller/cmd/eibc/fulfill/order" "github.com/dymensionxyz/roller/cmd/eibc/fulfill/rollapps" ) @@ -14,6 +15,7 @@ func Cmd() *cobra.Command { } cmd.AddCommand(order.Cmd()) + cmd.AddCommand(feeshare.Cmd()) cmd.AddCommand(rollapps.Cmd()) return cmd diff --git a/cmd/eibc/fulfill/rollapps/remove/remove.go b/cmd/eibc/fulfill/rollapps/remove/remove.go index f75fb4de..1ad6f1d1 100644 --- a/cmd/eibc/fulfill/rollapps/remove/remove.go +++ b/cmd/eibc/fulfill/rollapps/remove/remove.go @@ -10,6 +10,7 @@ import ( "github.com/dymensionxyz/roller/cmd/consts" "github.com/dymensionxyz/roller/utils/eibc" + eibcutils "github.com/dymensionxyz/roller/utils/eibc" "github.com/dymensionxyz/roller/utils/filesystem" ) @@ -54,6 +55,7 @@ func Cmd() *cobra.Command { return } + lspn, _ := pterm.DefaultSpinner.Start("removing rollapp to eibc config") config.RemoveChain(rollAppID) updatedData, err := yaml.Marshal(&config) if err != nil { @@ -66,6 +68,20 @@ func Cmd() *cobra.Command { pterm.Error.Printf("Error reading file: %v\n", err) return } + lspn.Success("rollapp removed from eibc config") + + var cfg eibcutils.Config + err = cfg.LoadConfig(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to load eibc config: ", err) + return + } + + err = eibcutils.UpdateGroupSupportedRollapps(eibcConfigPath, cfg, home) + if err != nil { + pterm.Error.Println("failed to update eibc operator metadata: ", err) + return + } }, } diff --git a/cmd/eibc/fulfill/rollapps/set/set.go b/cmd/eibc/fulfill/rollapps/set/set.go index e6409d6f..0e8038d2 100644 --- a/cmd/eibc/fulfill/rollapps/set/set.go +++ b/cmd/eibc/fulfill/rollapps/set/set.go @@ -10,6 +10,7 @@ import ( "github.com/dymensionxyz/roller/cmd/consts" "github.com/dymensionxyz/roller/utils/eibc" + eibcutils "github.com/dymensionxyz/roller/utils/eibc" "github.com/dymensionxyz/roller/utils/filesystem" ) @@ -34,6 +35,7 @@ instance. } eibcHome := filepath.Join(home, consts.ConfigDirName.Eibc) + eibcConfigPath := filepath.Join(eibcHome, "config.yaml") isEibcClientInitialized, err := filesystem.DirNotEmpty(eibcHome) if err != nil { pterm.Error.Println("failed to check eibc client initialized", err) @@ -54,10 +56,25 @@ instance. fNodes := strings.Split(fullNodes, ",") - err = eibc.AddRollappToEibc(rollAppID, eibcHome, fNodes) + lspn, _ := pterm.DefaultSpinner.Start("adding rollapp to eibc config") + err = eibc.AddRollappToEibcConfig(rollAppID, eibcHome, fNodes) if err != nil { return } + lspn.Success("rollapp added to eibc config") + + var cfg eibcutils.Config + err = cfg.LoadConfig(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to load eibc config: ", err) + return + } + + err = eibcutils.UpdateGroupSupportedRollapps(eibcConfigPath, cfg, home) + if err != nil { + pterm.Error.Println("failed to update eibc operator metadata: ", err) + return + } }, } diff --git a/cmd/eibc/init/init.go b/cmd/eibc/init/init.go index 6f2717fe..3813f5b5 100644 --- a/cmd/eibc/init/init.go +++ b/cmd/eibc/init/init.go @@ -3,12 +3,14 @@ package init import ( "embed" _ "embed" + "encoding/base64" "errors" "fmt" "io/fs" "os" "path/filepath" "strings" + "time" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -19,9 +21,7 @@ import ( "github.com/dymensionxyz/roller/utils/bash" "github.com/dymensionxyz/roller/utils/config" "github.com/dymensionxyz/roller/utils/config/yamlconfig" - "github.com/dymensionxyz/roller/utils/dependencies" eibcutils "github.com/dymensionxyz/roller/utils/eibc" - "github.com/dymensionxyz/roller/utils/errorhandling" "github.com/dymensionxyz/roller/utils/filesystem" "github.com/dymensionxyz/roller/utils/keys" "github.com/dymensionxyz/roller/utils/rollapp" @@ -64,46 +64,15 @@ func Cmd() *cobra.Command { eibcConfigPath := filepath.Join(eibcHome, "config.yaml") var ki *keys.KeyInfo + // get hub data rollerConfig, err := roller.LoadConfig(rollerHome) if err != nil || rollerConfig.HubData.ID == consts.MockHubID || rollerConfig.HubData.ID == "" { - pterm.Warning.Println("no roller config found") - pterm.Info.Println("initializing for environment") - - envs := []string{"playground", "custom"} - env, _ := pterm.DefaultInteractiveSelect. - WithDefaultText( - "select the environment you want to initialize eibc client for", - ). - WithOptions(envs). - Show() - - if env == "custom" { - var rollerConfig roller.RollappConfig - hdid, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("provide hub chain id"). - Show() - hdrpc, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("provide hub rpc endpoint"). - Show() - - rollerConfig.HubData.ID = hdid - rollerConfig.HubData.RpcUrl = hdrpc - - hd = rollerConfig.HubData - - rollerCfgDir := roller.GetRootDir() - err = os.MkdirAll(rollerCfgDir, 0o755) - if err != nil { - pterm.Error.Println("failed to create roller config dir", err) - return - } - err := roller.WriteConfig(rollerConfig) - if err != nil { - pterm.Error.Println("failed to write roller config", err) - return - } - } else { - hd = consts.Hubs[env] + hd, err = initializeEibcForEnvironment() + if err != nil { + pterm.Error.Println("failed to initialize hub metadata for eibc client: ", err) + return } } else { hd = rollerConfig.HubData @@ -115,333 +84,122 @@ func Cmd() *cobra.Command { return } - if isEibcClientInitialized { - pterm.Warning.Println("eibc client already initialized") - - kc := eibcutils.GetKeyConfig() - pterm.Info.Printfln("checking for existing %s address", kc.ID) - ok, err := kc.IsInKeyring(home) + if !isEibcClientInitialized { + pterm.Info.Println("initializing eibc client") + c := eibcutils.GetInitCmd() + err = bash.ExecCmd(c) if err != nil { - pterm.Error.Println("failed to get eibc key info: ", err) + pterm.Error.Println("failed to initialize eibc client: ", err) return } + } - if ok { - ki, err = kc.Info(home) - if err != nil { - pterm.Error.Println("failed to get eibc key info: ", err) - return - } - - pterm.Info.Println("eibc key already present in the keyring") - pterm.Info.Println("checking for existing delegation groups") - grp, err := eibcutils.GetGroups(eibcHome, ki.Address, hd) - if err != nil { - pterm.Error.Println("failed to get groups: ", err) - return - } - - if len(grp.Groups) > 0 { - groupID := grp.Groups[0].ID - pterm.Info.Printfln("delegation group found with ID: %s", groupID) + ki, err = eibcutils.EnsureWhaleAccount() + if err != nil { + pterm.Error.Printf("failed to create whale account: %v\n", err) + return + } - pterm.Info.Println("checking for existing policies") - pol, err := eibcutils.GetPolicies(eibcHome, groupID, hd) - if err != nil { - pterm.Error.Println("failed to get policies: ", err) - return - } + pterm.Info.Printfln( + "eibc operator address: %s", + pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). + Sprint(ki.Address), + ) - if pol != nil && len(pol.GroupPolicies) > 0 { - pterm.Info.Printfln("policies already present for %s", kc.ID) + g, err := eibcutils.GetGroups(eibcHome, ki.Address, hd) + if err != nil { + pterm.Error.Println("failed to get groups: ", err) + return + } - printPolicyAddress(pol.GroupPolicies[0].Address) + var gID string + var policyAddr string - updates := map[string]interface{}{ - "fulfillers.policy_address": pol.GroupPolicies[0].Address, - "operator.group_id": groupID, - } - err = yamlconfig.UpdateNestedYAML(eibcConfigPath, updates) - if err != nil { - pterm.Error.Println("failed to update config", err) - return - } + if len(g.Groups) == 0 { + err = setupEibcClient(hd, eibcHome, ki) + if err != nil { + pterm.Error.Println("failed to setup eibc client: ", err) + return + } - return - } else { - policyAddr, err := createPolicyIfNotPresent(eibcHome, groupID, hd) - if err != nil { - pterm.Error.Println("failed to create policy: ", err) - return - } - - printPolicyAddress(policyAddr) - updates := map[string]interface{}{ - "fulfillers.policy_address": policyAddr, - "operator.group_id": groupID, - } - err = yamlconfig.UpdateNestedYAML(eibcConfigPath, updates) - if err != nil { - pterm.Error.Println("failed to update config", err) - return - } - return - } + for { + cqc := keys.ChainQueryConfig{ + Binary: consts.Executables.Dymension, + Denom: consts.Denoms.Hub, + RPC: hd.RpcUrl, } - } else { - pterm.Info.Printfln("safe to override %s", eibcHome) - - msg := fmt.Sprintf( - "Directory %s is not empty. Do you want to overwrite it?", - eibcHome, - ) - shouldOverwrite, err := pterm.DefaultInteractiveConfirm.WithDefaultText(msg). - WithDefaultValue(false). - Show() + balance, err := keys.QueryBalance(cqc, ki.Address) if err != nil { - errorhandling.PrettifyErrorIfExists(err) + pterm.Error.Println("failed to get balance: ", err) return } - if shouldOverwrite { - err = os.RemoveAll(eibcHome) - if err != nil { - errorhandling.PrettifyErrorIfExists(err) - return - } - // nolint:gofumpt - err = os.MkdirAll(eibcHome, 0o755) - if err != nil { - errorhandling.PrettifyErrorIfExists(err) + if !balance.Amount.IsPositive() { + pterm.Info.Println( + "please fund the addresses below to run the eibc client. this address will be the operator address of the client.", + ) + ki.Print(keys.WithName(), keys.WithMnemonic()) + proceed, _ := pterm.DefaultInteractiveConfirm.WithDefaultValue(false). + WithDefaultText( + "press 'y' when the wallets are funded", + ).Show() + if !proceed { + pterm.Error.Println("cancelled by user") return } } else { - os.Exit(0) - } - - c := eibcutils.GetInitCmd() - err = bash.ExecCmd(c) - if err != nil { - pterm.Error.Println("failed to initialize eibc client", err) - return - } - - ki, err = eibcutils.EnsureWhaleAccount() - if err != nil { - pterm.Error.Printf("failed to create whale account: %v\n", err) - return - } - } - } else { - deps := dependencies.DefaultEibcClientPrebuiltDependencies() - for _, v := range deps { - err := dependencies.InstallBinaryFromRelease(v) - if err != nil { - pterm.Error.Printfln("failed to install binary: %s", err) - return + break } } - pterm.Info.Printfln("not initialized, initializing %s", eibcHome) - c := eibcutils.GetInitCmd() - err = bash.ExecCmd(c) + pterm.Info.Println( + "you are about to run the eibc client for the following Dymension network:", + ) + fmt.Println("network ID:", + pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). + Sprint(hd.ID), + ) + + raIDs, err := eibcutils.LoadSupportedRollapps(eibcConfigPath) if err != nil { - pterm.Error.Println("failed to initialize eibc client", err) + pterm.Error.Println("failed to load supported rollapps: ", err) return } - ki, err = eibcutils.EnsureWhaleAccount() + metadata := eibcutils.NewEibcOperatorMetadata(raIDs) + mb, err := metadata.ToBytes() if err != nil { - pterm.Error.Printf("failed to create whale account: %v\n", err) + pterm.Error.Println("failed to generate eibc operator metadata: ", err) return } - } - - pterm.Info.Println( - "you are about to run the eibc client for the following Dymension network:", - ) - fmt.Println("network ID:", - pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). - Sprint(hd.ID), - ) - for { - cqc := keys.ChainQueryConfig{ - Binary: consts.Executables.Dymension, - Denom: consts.Denoms.Hub, - RPC: hd.RpcUrl, - } - balance, err := keys.QueryBalance(cqc, ki.Address) + gID, err = createGroupIfNotPresent(ki, hd, eibcHome, mb) if err != nil { - pterm.Error.Println("failed to get balance: ", err) + pterm.Error.Println("failed to create group: ", err) return } - if !balance.Amount.IsPositive() { - pterm.Info.Println( - "please fund the addresses below to run the eibc client. this address will be the operator address of the client.", - ) - ki.Print(keys.WithName(), keys.WithMnemonic()) - proceed, _ := pterm.DefaultInteractiveConfirm.WithDefaultValue(false). - WithDefaultText( - "press 'y' when the wallets are funded", - ).Show() - if !proceed { - pterm.Error.Println("cancelled by user") - return - } - } else { - break - } - } - - var runForExisting bool - var raID string - rollerConfigFilePath := filepath.Join(roller.GetRootDir(), consts.RollerConfigFileName) - var rollerData roller.RollappConfig - - _, err = os.Stat(rollerConfigFilePath) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - pterm.Info.Println("existing roller configuration not found") - runForExisting = false - } else { - pterm.Error.Println("failed to check existing roller config") + policyAddr, err = createPolicyIfNotPresent(eibcHome, gID, hd, mb) + if err != nil { + pterm.Error.Println("failed to create policy: ", err) return } } else { - pterm.Info.Println("existing roller configuration found, retrieving RollApp ID from it") + mb := []byte{} - rollerData, err = roller.LoadConfig(roller.GetRootDir()) + gID, err = createGroupIfNotPresent(ki, hd, eibcHome, mb) if err != nil { - pterm.Error.Printf("failed to load rollapp config: %v\n", err) + pterm.Error.Println("failed to create group: ", err) return } - rollerRaID := rollerData.RollappID - rollerHubData := rollerData.HubData - - var rlyFromRoller bool - if rollerRaID != "" { - msg := fmt.Sprintf( - "the retrieved RollApp ID is: %s, would you like to initialize the eibc client for this RollApp?", - pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). - Sprint(rollerRaID), - ) - rlyFromRoller, _ = pterm.DefaultInteractiveConfirm.WithDefaultText(msg).Show() - if rlyFromRoller { - raID = rollerRaID - hd = rollerHubData - runForExisting = true - } - } - - if !rlyFromRoller { - runForExisting = false - } - } - - if !runForExisting { - for { - raID, _ = pterm.DefaultInteractiveTextInput.WithDefaultText("Please enter the RollApp ID to fulfill eibc orders for"). - Show() - - _, err := rollapp.ValidateChainID(raID) - if err != nil { - pterm.Error.Printf("'%s' is not a valid RollApp ID: %v\n", raID, err) - continue - } else { - break - } - } - } - - var fNodes []string - var rpc string - for { - // Prompt the user for the RPC URL - rpc, _ = pterm.DefaultInteractiveTextInput.WithDefaultText( - "dymint rpc endpoint that you trust, leave empty to fetch from chain (example: rpc.rollapp.dym.xyz)", - ).Show() - if !strings.HasPrefix(rpc, "http://") && !strings.HasPrefix(rpc, "https://") { - rpc = "https://" + rpc - } - - if strings.TrimSpace(rpc) == "" { - rpc, err = sequencerutils.GetRpcEndpointFromChain(raID, hd) - if err != nil { - pterm.Error.Println("failed to retrieve rollapp rpc endpoint: ", err) - rpc, _ = pterm.DefaultInteractiveTextInput.WithDefaultText( - "can't fetch rpc endpoint from chain, provide manually (example: rpc.rollapp.dym.xyz)", - ).Show() - if !strings.HasPrefix(rpc, "http://") && - !strings.HasPrefix(rpc, "https://") { - rpc = "https://" + rpc - } - } - } - - rpc = strings.TrimSuffix(rpc, "/") - isValid := config.IsValidURL(rpc) - - if !isValid { - pterm.Error.Println("Invalid URL. Please try again.") - } else { - fNodes = append(fNodes, rpc) - break + policyAddr, err = createPolicyIfNotPresent(eibcHome, gID, hd, mb) + if err != nil { + pterm.Error.Println("failed to create policy: ", err) + return } } - err = eibcutils.AddRollappToEibc(raID, eibcHome, fNodes) - if err != nil { - pterm.Error.Println("failed to add the rollapp to eibc config: ", err) - return - } - - err = updateEibcConfig(eibcConfigPath, hd) - if err != nil { - pterm.Error.Println("failed to update config", err) - return - } - - cfgBytes, err := os.ReadFile(eibcConfigPath) - if err != nil { - pterm.Error.Println("failed to read eibc config file: ", err) - return - } - - var cfg eibcutils.Config - err = yaml.Unmarshal(cfgBytes, &cfg) - if err != nil { - pterm.Error.Println("failed to unmarshal eibc config file: ", err) - return - } - - cfg.RemoveChain("example_1234-1") - updatedData, err := yaml.Marshal(&cfg) - if err != nil { - pterm.Error.Println("failed to marshal eibc config file: ", err) - return - } - - err = os.WriteFile(eibcConfigPath, updatedData, 0o644) - if err != nil { - pterm.Error.Println("failed to write eibc config file: ", err) - return - } - - gID, err := createGroupIfNotPresent(ki, hd, eibcHome) - if err != nil { - pterm.Error.Println("failed to create group: ", err) - return - } - - policyAddr, err := createPolicyIfNotPresent(eibcHome, gID, hd) - if err != nil { - pterm.Error.Println("failed to create policy: ", err) - return - } - printPolicyAddress(policyAddr) updates := map[string]interface{}{ "fulfillers.policy_address": policyAddr, @@ -468,7 +226,209 @@ func Cmd() *cobra.Command { return cmd } -func createGroupIfNotPresent(ki *keys.KeyInfo, hd consts.HubData, eibcHome string) (string, error) { +func setupEibcClient(hd consts.HubData, eibcHome string, ki *keys.KeyInfo) error { + var runForExisting bool + eibcConfigPath := filepath.Join(eibcHome, "config.yaml") + var raID string + rollerConfigFilePath := filepath.Join(roller.GetRootDir(), consts.RollerConfigFileName) + var rollerData roller.RollappConfig + + _, err := os.Stat(rollerConfigFilePath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + pterm.Info.Println("existing roller configuration not found") + runForExisting = false + } else { + pterm.Error.Println("failed to check existing roller config") + return err + } + } else { + pterm.Info.Println("existing roller configuration found, retrieving RollApp ID from it") + + rollerData, err = roller.LoadConfig(roller.GetRootDir()) + if err != nil { + pterm.Error.Printf("failed to load rollapp config: %v\n", err) + return err + } + rollerRaID := rollerData.RollappID + + var eibcFromRoller bool + if rollerRaID != "" { + msg := fmt.Sprintf( + "the retrieved RollApp ID is: %s, would you like to initialize the eibc client for this RollApp?", + pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). + Sprint(rollerRaID), + ) + eibcFromRoller, _ = pterm.DefaultInteractiveConfirm.WithDefaultText(msg).Show() + if eibcFromRoller { + raID = rollerRaID + runForExisting = true + } + } + + if !eibcFromRoller { + runForExisting = false + } + } + + if !runForExisting { + for { + raID, _ = pterm.DefaultInteractiveTextInput.WithDefaultText("Please enter the RollApp ID to fulfill eibc orders for"). + Show() + + _, err := rollapp.ValidateChainID(raID) + if err != nil { + pterm.Error.Printf("'%s' is not a valid RollApp ID: %v\n", raID, err) + continue + } else { + break + } + } + } + + var fNodes []string + var rpc string + for { + rpc, _ = pterm.DefaultInteractiveTextInput.WithDefaultText( + "dymint rpc endpoint that you trust, leave empty to fetch from chain (example: rpc.rollapp.dym.xyz)", + ).Show() + + if strings.TrimSpace(rpc) == "" { + rpcSpinner, _ := pterm.DefaultSpinner.WithRemoveWhenDone(true). + Start("fetching rpc endpoint from chain") + rpc, err = sequencerutils.GetRpcEndpointFromChain(raID, hd) + if err != nil { + pterm.Error.Println("failed to retrieve rollapp rpc endpoint: ", err) + rpc, _ = pterm.DefaultInteractiveTextInput.WithDefaultText( + "can't fetch rpc endpoint from chain, provide manually (example: rpc.rollapp.dym.xyz)", + ).Show() + } + rpcSpinner.Success("rpc endpoint fetched from chain") + } + + if !strings.HasPrefix(rpc, "http://") && + !strings.HasPrefix(rpc, "https://") { + rpc = "https://" + rpc + } + + rpc = strings.TrimSuffix(rpc, "/") + + isValid := config.IsValidURL(rpc) + + if !isValid { + pterm.Error.Println("Invalid URL. Please try again.") + } else { + fNodes = append(fNodes, rpc) + break + } + } + + err = eibcutils.AddRollappToEibcConfig(raID, eibcHome, fNodes) + if err != nil { + pterm.Error.Println("failed to add the rollapp to eibc config: ", err) + return err + } + + err = updateEibcConfig(eibcConfigPath, hd) + if err != nil { + pterm.Error.Println("failed to update config", err) + return err + } + + cfgBytes, err := os.ReadFile(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to read eibc config file: ", err) + return err + } + + var cfg eibcutils.Config + err = yaml.Unmarshal(cfgBytes, &cfg) + if err != nil { + pterm.Error.Println("failed to unmarshal eibc config file: ", err) + return err + } + + err = removeDefaultEibcChain(cfg, eibcConfigPath, ki, hd, eibcHome) + if err != nil { + pterm.Error.Println("failed to remove default eibc chain: ", err) + return err + } + return nil +} + +func initializeEibcForEnvironment() (consts.HubData, error) { + pterm.Warning.Println("no roller config found") + pterm.Info.Println("initializing for environment") + var hd consts.HubData + + envs := []string{"playground", "custom"} + env, _ := pterm.DefaultInteractiveSelect. + WithDefaultText( + "select the environment you want to initialize eibc client for", + ). + WithOptions(envs). + Show() + + if env == "custom" { + var rollerConfig roller.RollappConfig + hdid, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("provide hub chain id"). + Show() + hdrpc, _ := pterm.DefaultInteractiveTextInput.WithDefaultText("provide hub rpc endpoint"). + Show() + + rollerConfig.HubData.ID = hdid + rollerConfig.HubData.RpcUrl = hdrpc + + hd = rollerConfig.HubData + + rollerCfgDir := roller.GetRootDir() + err := os.MkdirAll(rollerCfgDir, 0o755) + if err != nil { + pterm.Error.Println("failed to create roller config dir", err) + return consts.HubData{}, err + } + + err = roller.WriteConfig(rollerConfig) + if err != nil { + pterm.Error.Println("failed to write roller config", err) + return consts.HubData{}, err + } + } else { + hd = consts.Hubs[env] + } + + return hd, nil +} + +func removeDefaultEibcChain( + cfg eibcutils.Config, + eibcConfigPath string, + ki *keys.KeyInfo, + hd consts.HubData, + eibcHome string, +) error { + cfg.RemoveChain("example_1234-1") + updatedData, err := yaml.Marshal(&cfg) + if err != nil { + pterm.Error.Println("failed to marshal eibc config file: ", err) + return err + } + + err = os.WriteFile(eibcConfigPath, updatedData, 0o644) + if err != nil { + pterm.Error.Println("failed to write eibc config file: ", err) + return err + } + + return nil +} + +func createGroupIfNotPresent( + ki *keys.KeyInfo, + hd consts.HubData, + eibcHome string, + metadata []byte, +) (string, error) { grp, err := eibcutils.GetGroups(eibcHome, ki.Address, hd) if err != nil { pterm.Error.Println("failed to get groups: ", err) @@ -476,7 +436,12 @@ func createGroupIfNotPresent(ki *keys.KeyInfo, hd consts.HubData, eibcHome strin } if len(grp.Groups) > 0 { - pterm.Info.Printfln("delegation group found with ID: %s", grp.Groups[0].ID) + pterm.Info.Printfln( + "delegation group found with ID: %s", + pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). + Sprint(grp.Groups[0].ID), + ) + return grp.Groups[0].ID, nil } @@ -488,10 +453,11 @@ func createGroupIfNotPresent(ki *keys.KeyInfo, hd consts.HubData, eibcHome strin pterm.Error.Println("failed to create members file: ", err) return "", err } + membersDefinitionFilePath := filepath.Join(eibcHome, "init", "members.json") cGrpCmd := eibcutils.GetCreateGroupDelegationCmd( eibcHome, - "eibc-operator", + base64.StdEncoding.EncodeToString(metadata), membersDefinitionFilePath, hd, ) @@ -525,18 +491,17 @@ func createGroupIfNotPresent(ki *keys.KeyInfo, hd consts.HubData, eibcHome strin return groupID, err } -func createPolicyIfNotPresent(eibcHome, groupID string, hd consts.HubData) (string, error) { +func createPolicyIfNotPresent( + eibcHome, groupID string, + hd consts.HubData, + metadata []byte, +) (string, error) { pol, err := eibcutils.GetPolicies(eibcHome, groupID, hd) if err != nil { return "", err } if len(pol.GroupPolicies) > 0 { - pterm.Info.Printfln( - "found existing policy for %s: %s", - groupID, - pol.GroupPolicies[0].Address, - ) return pol.GroupPolicies[0].Address, nil } @@ -547,10 +512,11 @@ func createPolicyIfNotPresent(eibcHome, groupID string, hd consts.HubData) (stri pterm.Error.Println("failed to create members file: ", err) return "", err } + policyDefinitionFilePath := filepath.Join(eibcHome, "init", "policy.json") cPolicyCmd := eibcutils.GetCreateGroupPolicyCmd( eibcHome, - "eibc-operator", + base64.StdEncoding.EncodeToString(metadata), policyDefinitionFilePath, groupID, hd, @@ -578,6 +544,10 @@ func createPolicyIfNotPresent(eibcHome, groupID string, hd consts.HubData) (stri return "", err } + s, _ := pterm.DefaultSpinner.WithRemoveWhenDone(true).Start("finalizing") + time.Sleep(time.Second * 2) + s.Success("done") + return pol.GroupPolicies[0].Address, nil } @@ -650,5 +620,5 @@ func printPolicyAddress(policyAddr string) { pterm.DefaultBasicText.WithStyle(pterm.FgYellow.ToStyle()). Sprint(policyAddr), ) - pterm.Info.Println("share this with the LP provider") + pterm.Info.Println("share the policy address with the LP provider") } diff --git a/sequencer/status.go b/sequencer/status.go index 29a2c2c0..c06eec27 100644 --- a/sequencer/status.go +++ b/sequencer/status.go @@ -54,12 +54,14 @@ func (seq *Sequencer) GetRollappHeight() (string, error) { if err != nil { return "-1", err } + //nolint:errcheck defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "-1", err } + var response Response if err := json.Unmarshal(body, &response); err != nil { return "-2", err diff --git a/utils/eibc/config.go b/utils/eibc/config.go new file mode 100644 index 00000000..fd8ae598 --- /dev/null +++ b/utils/eibc/config.go @@ -0,0 +1,34 @@ +package eibc + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +func LoadSupportedRollapps(eibcConfigPath string) ([]string, error) { + data, err := os.ReadFile(eibcConfigPath) + if err != nil { + fmt.Println("failed to read: ", err) + return nil, err + } + + var config Config + err = yaml.Unmarshal(data, &config) + if err != nil { + fmt.Println("failed to unmarshal eibc config file: ", err) + return nil, err + } + + if config.Rollapps == nil { + return []string{}, nil + } + + keys := make([]string, 0, len(config.Rollapps)) + for k := range config.Rollapps { + keys = append(keys, k) + } + + return keys, nil +} diff --git a/utils/eibc/eibc.go b/utils/eibc/eibc.go index e04e42ad..e253c00d 100644 --- a/utils/eibc/eibc.go +++ b/utils/eibc/eibc.go @@ -123,7 +123,7 @@ func CreateMongoDbContainer() error { return err } -func AddRollappToEibc(raID, eibcHome string, fullnodes []string) error { +func AddRollappToEibcConfig(raID, eibcHome string, fullnodes []string) error { eibcConfigPath := filepath.Join(eibcHome, "config.yaml") updates := map[string]interface{}{ diff --git a/utils/eibc/operatormetadata.go b/utils/eibc/operatormetadata.go new file mode 100644 index 00000000..0b3b49c3 --- /dev/null +++ b/utils/eibc/operatormetadata.go @@ -0,0 +1,325 @@ +package eibc + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os/exec" + "path/filepath" + + "github.com/pterm/pterm" + "gopkg.in/yaml.v3" + + "github.com/dymensionxyz/roller/cmd/consts" + "github.com/dymensionxyz/roller/utils/bash" + "github.com/dymensionxyz/roller/utils/tx" +) + +// TODO: refactor everything here, it'll be a lot easier and cleaner to use io.Reader/Writer + []byte + +// EibcOperatorMetadata struct represents the metadata for the eibc operator which is associated with the +// eibc group and delegation policy +type EibcOperatorMetadata struct { + Moniker string `json:"moniker" yaml:"moniker"` + Description string `json:"description" yaml:"description"` + ContactDetails EibcOperatorContactDetails `json:"contact_details" yaml:"contact_details"` + FeeShare float64 `json:"fee_share" yaml:"fee_share"` + SupportedRollapps []string `json:"supported_rollapps" yaml:"supported_rollapps"` +} + +// EibcOperatorContactDetails struct represents the contact details for the eibc operator +type EibcOperatorContactDetails struct { + X string `json:"x" yaml:"x"` + Website string `json:"website" yaml:"website"` + Telegram string `json:"telegram" yaml:"telegram"` +} + +// ToBytes converts EibcOperatorMetadata to []byte +func (m *EibcOperatorMetadata) ToBytes() ([]byte, error) { + return json.Marshal(m) +} + +// WithDescription sets the description and returns the modified EibcOperatorMetadata +func (m *EibcOperatorMetadata) WithDescription(description string) *EibcOperatorMetadata { + m.Description = description + return m +} + +// WithX sets the X (Twitter) contact detail and returns the modified EibcOperatorMetadata +func (m *EibcOperatorMetadata) WithX(x string) *EibcOperatorMetadata { + m.ContactDetails.X = x + return m +} + +// WithWebsite sets the website contact detail and returns the modified EibcOperatorMetadata +func (m *EibcOperatorMetadata) WithWebsite(website string) *EibcOperatorMetadata { + m.ContactDetails.Website = website + return m +} + +// WithTelegram sets the telegram contact detail and returns the modified EibcOperatorMetadata +func (m *EibcOperatorMetadata) WithTelegram(telegram string) *EibcOperatorMetadata { + m.ContactDetails.Telegram = telegram + return m +} + +// WithFeeShare sets the fee share and returns the modified EibcOperatorMetadata +func (m *EibcOperatorMetadata) WithFeeShare(feeShare float64) *EibcOperatorMetadata { + m.FeeShare = feeShare + return m +} + +// WithSupportedRollapps sets the supported rollapps and returns the modified EibcOperatorMetadata +func (m *EibcOperatorMetadata) WithSupportedRollapps(rollapps []string) *EibcOperatorMetadata { + m.SupportedRollapps = rollapps + return m +} + +// NewEibcOperatorMetadata function creates a new EibcOperatorMetadata instance with the provided rollapp ID +// and prompts the user for the required information +func NewEibcOperatorMetadata(raIDs []string) *EibcOperatorMetadata { + pterm.Info.Println( + "the information provided below will be associated with the eibc group and delegation policy", + ) + moniker, _ := pterm.DefaultInteractiveTextInput.WithDefaultText( + "provide a moniker for the eibc operator", + ).Show() + + metadata := &EibcOperatorMetadata{ + Moniker: moniker, + ContactDetails: EibcOperatorContactDetails{}, + FeeShare: consts.DefaultEibcOperatorFeeShare, + SupportedRollapps: raIDs, + } + + shouldFillOptionalFields, _ := pterm.DefaultInteractiveConfirm.WithDefaultText( + "Would you also like to fill optional metadata for your sequencer?", + ).Show() + + if shouldFillOptionalFields { + description, _ := pterm.DefaultInteractiveTextInput.WithDefaultText( + "provide a description for the eibc operator (leave empty to skip)", + ).Show() + + x, _ := pterm.DefaultInteractiveTextInput.WithDefaultText( + "provide a link to your X (leave empty to skip)", + ).Show() + website, _ := pterm.DefaultInteractiveTextInput.WithDefaultText( + "provide a link to your website (leave empty to skip)", + ).Show() + telegram, _ := pterm.DefaultInteractiveTextInput.WithDefaultText( + "provide a link to your telegram (leave empty to skip)", + ).Show() + + if description != "" { + metadata.WithDescription(description) + } + + if x != "" { + metadata.WithX(x) + } + + if website != "" { + metadata.WithWebsite(website) + } + + if telegram != "" { + metadata.WithTelegram(telegram) + } + } + + return metadata +} + +func getUpdateEibcOperatorMetadataCmd( + eibcHome string, + adminAddr, groupID, metadata string, + hd consts.HubData, +) *exec.Cmd { + cmd := exec.Command( + consts.Executables.Dymension, + "tx", + "group", + "update-group-metadata", + adminAddr, + groupID, + metadata, + "--home", + eibcHome, + "--node", + hd.RpcUrl, + "--chain-id", + hd.ID, + "--keyring-backend", + "test", + "--fees", + fmt.Sprintf("%d%s", consts.DefaultTxFee, consts.Denoms.Hub), + "-y", + ) + + return cmd +} + +func UpdateEibcOperatorMetadata(home, metadata string, hd consts.HubData) error { + eibcHome := filepath.Join(home, consts.ConfigDirName.Eibc) + kc := GetKeyConfig() + ki, err := kc.Info(home) + if err != nil { + return err + } + + gid, err := GetGroups(eibcHome, ki.Address, hd) + if err != nil { + return err + } + + c := getUpdateEibcOperatorMetadataCmd(eibcHome, ki.Address, gid.Groups[0].ID, metadata, hd) + + out, err := bash.ExecCommandWithStdout(c) + if err != nil { + pterm.Error.Println("failed to create group: ", err) + return err + } + + txHash, err := bash.ExtractTxHash(out.String()) + if err != nil { + pterm.Error.Println("failed to extract tx hash: ", err) + return err + } + + err = tx.MonitorTransaction(hd.RpcUrl, txHash) + if err != nil { + return err + } + + return nil +} + +func EibcOperatorMetadataFromChain( + home string, + hd consts.HubData, +) (*EibcOperatorMetadata, error) { + eibcHome := filepath.Join(home, consts.ConfigDirName.Eibc) + kc := GetKeyConfig() + + ki, err := kc.Info(home) + if err != nil { + return nil, err + } + + pol, err := GetGroups(eibcHome, ki.Address, hd) + if err != nil { + return nil, err + } + + metadataB64 := pol.Groups[0].Metadata + metadata, err := base64.StdEncoding.DecodeString(metadataB64) + if err != nil { + return nil, err + } + + var m EibcOperatorMetadata + err = json.Unmarshal(metadata, &m) + if err != nil { + return nil, err + } + + return &m, nil +} + +// UpdateGroupSupportedRollapps function updates the supported rollapps list in the onchain metadata +// of group and group-policy and returns an error if any +func UpdateGroupSupportedRollapps(eibcConfigPath string, cfg Config, home string) error { + rspn, _ := pterm.DefaultSpinner.Start("updating eibc operator metadata") + rspn.UpdateText("retrieving updated supported rollapp list") + raIDs, err := LoadSupportedRollapps(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to load supported rollapps: ", err) + return err + } + hd, err := cfg.HubDataFromHubRpc(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to retrieve hub data: ", err) + return err + } + + rspn.UpdateText("retrieving existing eibc operator metadata") + metadata, err := EibcOperatorMetadataFromChain(home, *hd) + if err != nil { + pterm.Error.Println("failed to retrieve eibc operator metadata: ", err) + return err + } + rspn.UpdateText("updating supported rollapp list") + metadata.SupportedRollapps = raIDs + + mb, err := metadata.ToBytes() + if err != nil { + pterm.Error.Println("failed to generate eibc operator metadata: ", err) + return err + } + mbs := base64.StdEncoding.EncodeToString(mb) + + rspn.UpdateText("pushing changes to chain") + err = UpdateEibcOperatorMetadata(home, mbs, *hd) + if err != nil { + pterm.Error.Println("failed to update eibc operator metadata: ", err) + return err + } + rspn.Success("operator metadata updated, new metadata:") + ym, err := yaml.Marshal(metadata) + if err != nil { + pterm.Error.Println("failed to marshal eibc operator metadata: ", err) + return err + } + fmt.Println(string(ym)) + return nil +} + +// UpdateGroupSupportedRollapps function updates the supported rollapps list in the onchain metadata +// of group and group-policy and returns an error if any +func UpdateGroupOperatorMinFee( + eibcConfigPath string, + feeShare float64, + cfg Config, + home string, +) error { + rspn, _ := pterm.DefaultSpinner.Start("updating eibc operator metadata") + rspn.UpdateText("retrieving updated supported rollapp list") + hd, err := cfg.HubDataFromHubRpc(eibcConfigPath) + if err != nil { + pterm.Error.Println("failed to retrieve hub data: ", err) + return err + } + + rspn.UpdateText("retrieving existing eibc operator metadata") + metadata, err := EibcOperatorMetadataFromChain(home, *hd) + if err != nil { + pterm.Error.Println("failed to retrieve eibc operator metadata: ", err) + return err + } + + rspn.UpdateText("updating supported rollapp list") + metadata.FeeShare = feeShare + + mb, err := metadata.ToBytes() + if err != nil { + pterm.Error.Println("failed to generate eibc operator metadata: ", err) + return err + } + mbs := base64.StdEncoding.EncodeToString(mb) + + rspn.UpdateText("pushing changes to chain") + err = UpdateEibcOperatorMetadata(home, mbs, *hd) + if err != nil { + pterm.Error.Println("failed to update eibc operator metadata: ", err) + return err + } + rspn.Success("operator metadata updated, new metadata:") + ym, err := yaml.Marshal(metadata) + if err != nil { + pterm.Error.Println("failed to marshal eibc operator metadata: ", err) + return err + } + fmt.Println(string(ym)) + return nil +} diff --git a/utils/eibc/types.go b/utils/eibc/types.go index 87722805..e18d74dc 100644 --- a/utils/eibc/types.go +++ b/utils/eibc/types.go @@ -1,9 +1,18 @@ package eibc import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" "time" "github.com/ignite/cli/ignite/pkg/cosmosaccount" + "gopkg.in/yaml.v3" + + "github.com/dymensionxyz/roller/cmd/consts" + "github.com/dymensionxyz/roller/sequencer" ) type Config struct { @@ -73,3 +82,47 @@ type slackConfig struct { func (e *Config) RemoveChain(chainId string) { delete(e.Rollapps, chainId) } + +func (e *Config) LoadConfig(eibcConfigPath string) error { + data, err := os.ReadFile(eibcConfigPath) + if err != nil { + return err + } + + err = yaml.Unmarshal(data, &e) + if err != nil { + return err + } + + return nil +} + +func (e *Config) HubDataFromHubRpc(eibcConfigPath string) (*consts.HubData, error) { + err := e.LoadConfig(eibcConfigPath) + if err != nil { + return nil, err + } + + resp, err := http.Get(fmt.Sprintf("%s/status", e.NodeAddress)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var response sequencer.Response + if err := json.Unmarshal(body, &response); err != nil { + return nil, err + } + + hd := consts.HubData{ + ID: response.Result.NodeInfo.Network, + RpcUrl: e.NodeAddress, + } + + return &hd, nil +}