diff --git a/cmd/chains.go b/cmd/chains.go index 6bad572..83d2ac5 100644 --- a/cmd/chains.go +++ b/cmd/chains.go @@ -110,7 +110,7 @@ func chainsShowCmd(app *relayer.App) *cobra.Command { cmd := &cobra.Command{ Use: "show [chain_name]", Aliases: []string{"s"}, - Short: "Return a chain's configuration data", + Short: "Return chain's configuration data", Args: withUsage(cobra.ExactArgs(1)), Example: strings.TrimSpace(fmt.Sprintf(` $ %s ch s eth diff --git a/cmd/config.go b/cmd/config.go index 7df5763..787ccb2 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -57,7 +57,7 @@ func configInitCmd(app *relayer.App) *cobra.Command { cmd := &cobra.Command{ Use: "init", Aliases: []string{"i"}, - Short: "Create a default configuration at home directory path defined by --home", + Short: "Create a default configuration at home directory path", Args: withUsage(cobra.NoArgs), Example: strings.TrimSpace(fmt.Sprintf(` $ %s config init --home %s diff --git a/cmd/query.go b/cmd/query.go index 72e78f1..3c1d517 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -16,7 +16,7 @@ func queryCmd(app *relayer.App) *cobra.Command { cmd := &cobra.Command{ Use: "query", Aliases: []string{"q"}, - Short: "Query commands on source and destination chains.", + Short: "Query commands on source and destination chains", } cmd.AddCommand( diff --git a/cmd/root.go b/cmd/root.go index 0233a8e..62e72ad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,24 +28,32 @@ var defaultHome = filepath.Join(os.Getenv("HOME"), ".falcon") // NewRootCmd returns the root command for falcon. func NewRootCmd(log *zap.Logger) *cobra.Command { - app := falcon.NewApp(log, viper.New(), defaultHome, false, nil) + app := falcon.NewApp(log, defaultHome, false, nil) // RootCmd represents the base command when called without any subcommands rootCmd := &cobra.Command{ Use: appName, - Short: "This application relays tunnel messages to the target chains/contracts.", - Long: strings.TrimSpace(`falcon has: - 1. Configuration management for destination chains - 2. Key management for managing multiple keys for multiple chains - 3. transaction Execution functionality on destination chains. - 4. Query functionality on source and destination chains. + Short: "Falcon relays tss tunnel messages from BandChain to destination chains/smart contracts", + Long: strings.TrimSpace(`This application has: + 1. Configuration Management: Handles the configuration of the program. + 2. Key Management: Supports managing multiple keys across multiple chains. + 3. Transaction Execution: Enables executing transactions on destination chains. + 4. Query Functionality: Facilitates querying data from both source and destination chains. NOTE: Most of the commands have aliases that make typing them much quicker (i.e. 'falcon tx', 'falcon q', etc...)`), } rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { - return app.Init(rootCmd.Context()) + // retrieve log level from viper + logLevelViper := viper.GetString("log-level") + if viper.GetBool("debug") { + logLevelViper = "debug" + } + + logFormat := viper.GetString("log-format") + + return app.Init(rootCmd.Context(), logLevelViper, logFormat) } rootCmd.PersistentPostRun = func(cmd *cobra.Command, _ []string) { @@ -58,25 +66,25 @@ func NewRootCmd(log *zap.Logger) *cobra.Command { // Register --home flag rootCmd.PersistentFlags().StringVar(&app.HomePath, flagHome, defaultHome, "set home directory") - if err := app.Viper.BindPFlag(flagHome, rootCmd.PersistentFlags().Lookup(flagHome)); err != nil { + if err := viper.BindPFlag(flagHome, rootCmd.PersistentFlags().Lookup(flagHome)); err != nil { panic(err) } // Register --debug flag rootCmd.PersistentFlags().BoolVarP(&app.Debug, "debug", "d", false, "debug output") - if err := app.Viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug")); err != nil { + if err := viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug")); err != nil { panic(err) } // Register --log-format flag rootCmd.PersistentFlags().String("log-format", "auto", "log output format (auto, logfmt, json, or console)") - if err := app.Viper.BindPFlag("log-format", rootCmd.PersistentFlags().Lookup("log-format")); err != nil { + if err := viper.BindPFlag("log-format", rootCmd.PersistentFlags().Lookup("log-format")); err != nil { panic(err) } // Register --log-level flag rootCmd.PersistentFlags().String("log-level", "", "log level format (info, debug, warn, error, panic or fatal)") - if err := app.Viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level")); err != nil { + if err := viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level")); err != nil { panic(err) } diff --git a/cmd/start.go b/cmd/start.go index 97557d2..519bc69 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -15,7 +15,7 @@ func startCmd(app *relayer.App) *cobra.Command { cmd := &cobra.Command{ Use: "start [tunnel_id...]", Aliases: []string{"st"}, - Short: "Start the falcon tunnel relayer system.", + Short: "Start the falcon tunnel relayer program", Args: withUsage(cobra.MinimumNArgs(0)), Example: strings.TrimSpace(fmt.Sprintf(` $ %s start # start relaying data from every tunnel being registered on source chain. diff --git a/internal/relayertest/mocks/chain_provider.go b/internal/relayertest/mocks/chain_provider.go index 4ff9364..621b8f2 100644 --- a/internal/relayertest/mocks/chain_provider.go +++ b/internal/relayertest/mocks/chain_provider.go @@ -115,18 +115,18 @@ func (mr *MockChainProviderMockRecorder) IsKeyNameExist(keyName any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsKeyNameExist", reflect.TypeOf((*MockChainProvider)(nil).IsKeyNameExist), keyName) } -// Listkeys mocks base method. -func (m *MockChainProvider) Listkeys() []*types0.Key { +// ListKeys mocks base method. +func (m *MockChainProvider) ListKeys() []*types0.Key { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Listkeys") + ret := m.ctrl.Call(m, "ListKeys") ret0, _ := ret[0].([]*types0.Key) return ret0 } -// Listkeys indicates an expected call of Listkeys. -func (mr *MockChainProviderMockRecorder) Listkeys() *gomock.Call { +// ListKeys indicates an expected call of ListKeys. +func (mr *MockChainProviderMockRecorder) ListKeys() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Listkeys", reflect.TypeOf((*MockChainProvider)(nil).Listkeys)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListKeys", reflect.TypeOf((*MockChainProvider)(nil).ListKeys)) } // LoadFreeSenders mocks base method. @@ -283,18 +283,18 @@ func (mr *MockKeyProviderMockRecorder) IsKeyNameExist(keyName any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsKeyNameExist", reflect.TypeOf((*MockKeyProvider)(nil).IsKeyNameExist), keyName) } -// Listkeys mocks base method. -func (m *MockKeyProvider) Listkeys() []*types0.Key { +// ListKeys mocks base method. +func (m *MockKeyProvider) ListKeys() []*types0.Key { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Listkeys") + ret := m.ctrl.Call(m, "ListKeys") ret0, _ := ret[0].([]*types0.Key) return ret0 } -// Listkeys indicates an expected call of Listkeys. -func (mr *MockKeyProviderMockRecorder) Listkeys() *gomock.Call { +// ListKeys indicates an expected call of ListKeys. +func (mr *MockKeyProviderMockRecorder) ListKeys() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Listkeys", reflect.TypeOf((*MockKeyProvider)(nil).Listkeys)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListKeys", reflect.TypeOf((*MockKeyProvider)(nil).ListKeys)) } // LoadFreeSenders mocks base method. diff --git a/internal/relayertest/testdata/chain_config.toml b/internal/relayertest/testdata/chain_config.toml index 19ae6f3..00ae1ef 100644 --- a/internal/relayertest/testdata/chain_config.toml +++ b/internal/relayertest/testdata/chain_config.toml @@ -4,7 +4,6 @@ max_retry = 3 query_timeout = 3000000000 chain_id = 31337 tunnel_router_address = '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9' -private_key = '' block_confirmation = 5 waiting_tx_duration = 3000000000 checking_tx_interval = 1000000000 diff --git a/internal/relayertest/testdata/custom_config.toml b/internal/relayertest/testdata/custom_config.toml index 87e4f0e..c146c42 100644 --- a/internal/relayertest/testdata/custom_config.toml +++ b/internal/relayertest/testdata/custom_config.toml @@ -18,7 +18,6 @@ query_timeout = 3000000000 execute_timeout = 3000000000 chain_id = 31337 tunnel_router_address = '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9' -private_key = '' block_confirmation = 5 waiting_tx_duration = 3000000000 liveliness_checking_interval = 900000000000 diff --git a/internal/relayertest/testdata/custom_config_with_time_str.toml b/internal/relayertest/testdata/custom_config_with_time_str.toml index efb3cff..87bb723 100644 --- a/internal/relayertest/testdata/custom_config_with_time_str.toml +++ b/internal/relayertest/testdata/custom_config_with_time_str.toml @@ -18,7 +18,6 @@ query_timeout = '3s' execute_timeout = '3s' chain_id = 31337 tunnel_router_address = '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9' -private_key = '' block_confirmation = 5 waiting_tx_duration = 3000000000 liveliness_checking_interval = '15m' diff --git a/internal/relayertest/testdata/default_with_chain_config.toml b/internal/relayertest/testdata/default_with_chain_config.toml index 7de744a..cd78c22 100644 --- a/internal/relayertest/testdata/default_with_chain_config.toml +++ b/internal/relayertest/testdata/default_with_chain_config.toml @@ -18,7 +18,6 @@ query_timeout = 3000000000 execute_timeout = 3000000000 chain_id = 31337 tunnel_router_address = '0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9' -private_key = '' block_confirmation = 5 waiting_tx_duration = 3000000000 liveliness_checking_interval = 900000000000 diff --git a/internal/utils.go b/internal/utils.go new file mode 100644 index 0000000..f6ff823 --- /dev/null +++ b/internal/utils.go @@ -0,0 +1,19 @@ +package internal + +import "os" + +// CheckAndCreateFolder checks if the folder exists and creates it if it doesn't. +func CheckAndCreateFolder(path string) error { + // If the folder exists and no error, return nil + _, err := os.Stat(path) + if err == nil { + return nil + } + + // If the folder does not exist, create it. + if os.IsNotExist(err) { + return os.Mkdir(path, os.ModePerm) + } + + return err +} diff --git a/relayer/app.go b/relayer/app.go index 5f78b5e..cad78a0 100644 --- a/relayer/app.go +++ b/relayer/app.go @@ -12,9 +12,9 @@ import ( cosmosclient "github.com/cosmos/cosmos-sdk/client" "github.com/joho/godotenv" "github.com/pelletier/go-toml/v2" - "github.com/spf13/viper" "go.uber.org/zap" + "github.com/bandprotocol/falcon/internal" "github.com/bandprotocol/falcon/relayer/band" bandtypes "github.com/bandprotocol/falcon/relayer/band/types" "github.com/bandprotocol/falcon/relayer/chains" @@ -32,7 +32,6 @@ const ( // App is the main application struct. type App struct { Log *zap.Logger - Viper *viper.Viper HomePath string Debug bool Config *Config @@ -45,14 +44,12 @@ type App struct { // NewApp creates a new App instance. func NewApp( log *zap.Logger, - viper *viper.Viper, homePath string, debug bool, config *Config, ) *App { app := App{ Log: log, - Viper: viper, HomePath: homePath, Debug: debug, Config: config, @@ -60,8 +57,8 @@ func NewApp( return &app } -// Initialize the application. -func (a *App) Init(ctx context.Context) error { +// Init initialize the application. +func (a *App) Init(ctx context.Context, logLevel, logFormat string) error { if a.Config == nil { if err := a.LoadConfigFile(); err != nil { return err @@ -70,50 +67,54 @@ func (a *App) Init(ctx context.Context) error { // initialize logger, if not already initialized if a.Log == nil { - if err := a.initLogger(""); err != nil { + if err := a.initLogger(logLevel, logFormat); err != nil { return err } } - // initialize target chains + // load passphrase from .env file or system environment variables + a.EnvPassphrase = a.loadEnvPassphrase() + + // if config is not initialized, return + if a.Config == nil { + return nil + } + + // initialize target chain clients if err := a.initTargetChains(); err != nil { return err } - // initialize band client - if a.Config != nil { - if err := a.initBandClient(); err != nil { - return err - } + // initialize BandChain client + if err := a.initBandClient(); err != nil { + return err } - a.EnvPassphrase = a.loadEnvPassphrase() - return nil } // initBandClient establishes connection to rpc endpoints. func (a *App) initBandClient() error { - c := band.NewClient(cosmosclient.Context{}, nil, a.Log, a.Config.BandChain.RpcEndpoints) - if err := c.Connect(uint(a.Config.BandChain.Timeout)); err != nil { + a.BandClient = band.NewClient(cosmosclient.Context{}, nil, a.Log, a.Config.BandChain.RpcEndpoints) + + // connect to BandChain, if error occurs, log the error as debug and continue + if err := a.BandClient.Connect(uint(a.Config.BandChain.Timeout)); err != nil { + a.Log.Error("Cannot connect to BandChain", zap.Error(err)) return err } - a.BandClient = c + return nil } -// InitLogger initializes the logger with the given log level. -func (a *App) initLogger(configLogLevel string) error { - logLevel := a.Viper.GetString("log-level") - if a.Viper.GetBool("debug") { - logLevel = "debug" - } else if logLevel == "" { - logLevel = configLogLevel +// initLogger initializes the logger with the given log level. +func (a *App) initLogger(logLevel, logFormat string) error { + if logLevel == "" && a.Config != nil { + logLevel = a.Config.Global.LogLevel } // initialize logger only if user run command "start" or log level is "debug" if os.Args[1] == "start" || logLevel == "debug" { - log, err := newRootLogger(a.Viper.GetString("log-format"), logLevel) + log, err := newRootLogger(logFormat, logLevel) if err != nil { return err } @@ -125,13 +126,9 @@ func (a *App) initLogger(configLogLevel string) error { return nil } -// InitTargetChains initializes the target chains. +// initTargetChains initializes the target chains. func (a *App) initTargetChains() error { a.targetChains = make(chains.ChainProviders) - if a.Config == nil || a.Config.TargetChains == nil { - a.Log.Error("Target chains not found in config") - return nil - } for chainName, chainConfig := range a.Config.TargetChains { cp, err := chainConfig.NewChainProvider(chainName, a.Log, a.HomePath, a.Debug) @@ -145,15 +142,19 @@ func (a *App) initTargetChains() error { a.targetChains[chainName] = cp } + return nil } // LoadConfigFile reads config file into a.Config if file is present. func (a *App) LoadConfigFile() error { cfgPath := path.Join(a.HomePath, configFolderName, configFileName) - if _, err := os.Stat(cfgPath); err != nil { - // don't return error if file doesn't exist + + // check if file doesn't exist, exit the function as the config may not be initialized. + if _, err := os.Stat(cfgPath); os.IsNotExist(err) { return nil + } else if err != nil { + return err } // read the config from config path @@ -162,12 +163,6 @@ func (a *App) LoadConfigFile() error { return err } - if a.Log == nil { - if err := a.initLogger(cfg.Global.LogLevel); err != nil { - return err - } - } - // save configuration a.Config = cfg @@ -207,17 +202,13 @@ func (a *App) InitConfigFile(homePath string, customFilePath string) error { } // Create the home folder if doesn't exist - if _, err := os.Stat(homePath); os.IsNotExist(err) { - if err = os.Mkdir(homePath, os.ModePerm); err != nil { - return err - } + if err := internal.CheckAndCreateFolder(homePath); err != nil { + return err } // Create the config folder if doesn't exist - if _, err := os.Stat(cfgDir); os.IsNotExist(err) { - if err = os.Mkdir(cfgDir, os.ModePerm); err != nil { - return err - } + if err := internal.CheckAndCreateFolder(cfgDir); err != nil { + return err } // Create the file and write the default config to the given location. @@ -264,8 +255,7 @@ func (a *App) QueryTunnelInfo(ctx context.Context, tunnelID uint64) (*types.Tunn return nil, fmt.Errorf("config is not initialized") } - c := a.BandClient - tunnel, err := c.GetTunnel(ctx, tunnelID) + tunnel, err := a.BandClient.GetTunnel(ctx, tunnelID) if err != nil { return nil, err } @@ -278,17 +268,15 @@ func (a *App) QueryTunnelInfo(ctx context.Context, tunnelID uint64) (*types.Tunn tunnel.IsActive, ) - targetChain := tunnel.TargetChainID - targetAddr := tunnel.TargetAddress + cp, ok := a.targetChains[bandChainInfo.TargetChainID] + if !ok { + a.Log.Debug("Target chain provider not found", zap.String("chain_id", bandChainInfo.TargetChainID)) + return types.NewTunnel(bandChainInfo, nil), nil + } - var tunnelChainInfo *chainstypes.Tunnel - cp, ok := a.targetChains[targetChain] - if ok { - var err error - tunnelChainInfo, err = cp.QueryTunnelInfo(ctx, tunnelID, targetAddr) - if err != nil { - return nil, err - } + tunnelChainInfo, err := cp.QueryTunnelInfo(ctx, tunnelID, bandChainInfo.TargetAddress) + if err != nil { + return nil, err } return types.NewTunnel( @@ -303,16 +291,16 @@ func (a *App) QueryTunnelPacketInfo(ctx context.Context, tunnelID uint64, sequen return nil, fmt.Errorf("config is not initialized") } - c := a.BandClient - return c.GetTunnelPacket(ctx, tunnelID, sequence) + return a.BandClient.GetTunnelPacket(ctx, tunnelID, sequence) } +// AddChainConfig adds a new chain configuration to the config file. func (a *App) AddChainConfig(chainName string, filePath string) error { if a.Config == nil { return fmt.Errorf("config does not exist: %s", a.HomePath) } - if _, exist := a.Config.TargetChains[chainName]; exist { + if _, ok := a.Config.TargetChains[chainName]; ok { return fmt.Errorf("existing chain name : %s", chainName) } @@ -335,12 +323,13 @@ func (a *App) AddChainConfig(chainName string, filePath string) error { return os.WriteFile(cfgPath, b, 0o600) } +// DeleteChainConfig deletes the chain configuration from the config file. func (a *App) DeleteChainConfig(chainName string) error { if a.Config == nil { return fmt.Errorf("config does not exist: %s", a.HomePath) } - if _, exist := a.Config.TargetChains[chainName]; !exist { + if _, ok := a.Config.TargetChains[chainName]; !ok { return fmt.Errorf("not existing chain name : %s", chainName) } @@ -358,6 +347,7 @@ func (a *App) DeleteChainConfig(chainName string) error { return os.WriteFile(cfgPath, b, 0o600) } +// GetChainConfig retrieves the chain configuration by given chain name. func (a *App) GetChainConfig(chainName string) (chains.ChainProviderConfig, error) { if a.Config == nil { return nil, fmt.Errorf("config does not exist: %s", a.HomePath) @@ -365,13 +355,14 @@ func (a *App) GetChainConfig(chainName string) (chains.ChainProviderConfig, erro chainProviders := a.Config.TargetChains - if _, exist := chainProviders[chainName]; !exist { + if _, ok := chainProviders[chainName]; !ok { return nil, fmt.Errorf("not existing chain name : %s", chainName) } return chainProviders[chainName], nil } +// AddKey adds a new key to the chain provider. func (a *App) AddKey( chainName string, keyName string, @@ -407,6 +398,7 @@ func (a *App) AddKey( return keyOutput, nil } +// DeleteKey deletes the key from the chain provider. func (a *App) DeleteKey(chainName string, keyName string) error { if a.Config == nil { return fmt.Errorf("config does not exist: %s", a.HomePath) @@ -429,6 +421,7 @@ func (a *App) DeleteKey(chainName string, keyName string) error { return cp.DeleteKey(a.HomePath, keyName, a.EnvPassphrase) } +// ExportKey exports the private key from the chain provider. func (a *App) ExportKey(chainName string, keyName string) (string, error) { if a.Config == nil { return "", fmt.Errorf("config does not exist: %s", a.HomePath) @@ -456,6 +449,7 @@ func (a *App) ExportKey(chainName string, keyName string) (string, error) { return privateKey, nil } +// ListKeys retrieves the list of keys from the chain provider. func (a *App) ListKeys(chainName string) ([]*chainstypes.Key, error) { if a.Config == nil { return make([]*chainstypes.Key, 0), fmt.Errorf("config does not exist: %s", a.HomePath) @@ -467,9 +461,10 @@ func (a *App) ListKeys(chainName string) ([]*chainstypes.Key, error) { return make([]*chainstypes.Key, 0), fmt.Errorf("chain name does not exist: %s", chainName) } - return cp.Listkeys(), nil + return cp.ListKeys(), nil } +// ShowKey retrieves the key information from the chain provider. func (a *App) ShowKey(chainName string, keyName string) (string, error) { if a.Config == nil { return "", fmt.Errorf("config does not exist: %s", a.HomePath) @@ -487,6 +482,7 @@ func (a *App) ShowKey(chainName string, keyName string) (string, error) { return cp.ShowKey(keyName), nil } +// QueryBalance retrieves the balance of the key from the chain provider. func (a *App) QueryBalance(ctx context.Context, chainName string, keyName string) (*big.Int, error) { if a.Config == nil { return nil, fmt.Errorf("config does not exist: %s", a.HomePath) @@ -549,40 +545,18 @@ func (a *App) validatePassphrase(envPassphrase string) error { func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error { a.Log.Info("Starting tunnel relayer") - isSyncTunnelsAllowed := false - // query tunnels - var tunnels []bandtypes.Tunnel - if len(tunnelIDs) == 0 { - var err error - tunnels, err = a.BandClient.GetTunnels(ctx) - isSyncTunnelsAllowed = true - if err != nil { - return err - } - } else { - tunnels = make([]bandtypes.Tunnel, 0, len(tunnelIDs)) - for _, tunnelID := range tunnelIDs { - tunnel, err := a.BandClient.GetTunnel(ctx, tunnelID) - if err != nil { - return err - } - tunnels = append(tunnels, *tunnel) - } - } - - if len(tunnels) == 0 { - a.Log.Error("No tunnel ID provided") - return fmt.Errorf("no tunnel ID provided") + tunnels, err := a.getTunnels(ctx, tunnelIDs) + if err != nil { + a.Log.Error("Cannot get tunnels", zap.Error(err)) } - // initialize the tunnel relayer - tunnelRelayers := []*TunnelRelayer{} - + // validate passphrase if err := a.validatePassphrase(a.EnvPassphrase); err != nil { return err } + // initialize target chain providers for chainName, chainProvider := range a.targetChains { if err := chainProvider.LoadFreeSenders(a.HomePath, a.EnvPassphrase); err != nil { a.Log.Error("Cannot load keys in target chain", @@ -601,6 +575,8 @@ func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error { } } + // initialize the tunnel relayer + tunnelRelayers := []*TunnelRelayer{} for _, tunnel := range tunnels { chainProvider, ok := a.targetChains[tunnel.TargetChainID] if !ok { @@ -619,6 +595,7 @@ func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error { } // start the tunnel relayers + isSyncTunnelsAllowed := (len(tunnelIDs) == 0) scheduler := NewScheduler( a.Log, tunnelRelayers, @@ -636,7 +613,7 @@ func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error { // Relay relays the packet from the source chain to the destination chain. func (a *App) Relay(ctx context.Context, tunnelID uint64) error { - a.Log.Debug("Query tunnel info on band chain", zap.Uint64("tunnel_id", tunnelID)) + a.Log.Debug("Query tunnel info on BandChain", zap.Uint64("tunnel_id", tunnelID)) tunnel, err := a.BandClient.GetTunnel(ctx, tunnelID) if err != nil { return err @@ -670,3 +647,23 @@ func (a *App) Relay(ctx context.Context, tunnelID uint64) error { return tr.CheckAndRelay(ctx) } + +// GetTunnels retrieves the list of tunnels by given tunnel IDs. If no tunnel ID is provided, +// get all tunnels +func (a *App) getTunnels(ctx context.Context, tunnelIDs []uint64) ([]bandtypes.Tunnel, error) { + if len(tunnelIDs) == 0 { + return a.BandClient.GetTunnels(ctx) + } + + tunnels := make([]bandtypes.Tunnel, 0, len(tunnelIDs)) + for _, tunnelID := range tunnelIDs { + tunnel, err := a.BandClient.GetTunnel(ctx, tunnelID) + if err != nil { + return nil, err + } + + tunnels = append(tunnels, *tunnel) + } + + return tunnels, nil +} diff --git a/relayer/app_test.go b/relayer/app_test.go index ec21785..0b42a6c 100644 --- a/relayer/app_test.go +++ b/relayer/app_test.go @@ -54,7 +54,9 @@ func (s *AppTestSuite) SetupTest() { s.chainProvider.EXPECT().Init(gomock.Any()).Return(nil).AnyTimes() cfg := relayer.Config{ - BandChain: band.Config{}, + BandChain: band.Config{ + RpcEndpoints: []string{"http://localhost:26659"}, + }, TargetChains: map[string]chains.ChainProviderConfig{ "testnet_evm": s.chainProviderConfig, }, @@ -62,9 +64,9 @@ func (s *AppTestSuite) SetupTest() { } s.ctx = context.Background() - s.app = relayer.NewApp(log, nil, tmpDir, false, &cfg) + s.app = relayer.NewApp(log, tmpDir, false, &cfg) - err = s.app.Init(s.ctx) + err = s.app.Init(s.ctx, "", "") s.app.BandClient = s.client s.Require().NoError(err) } @@ -179,7 +181,7 @@ func (s *AppTestSuite) TestQueryTunnelInfo() { func (s *AppTestSuite) TestQueryTunnelInfoNotSupportedChain() { s.app.Config.TargetChains = nil - err := s.app.Init(s.ctx) + err := s.app.Init(s.ctx, "", "") s.Require().NoError(err) diff --git a/relayer/band/client.go b/relayer/band/client.go index bfe5400..5f53030 100644 --- a/relayer/band/client.go +++ b/relayer/band/client.go @@ -29,7 +29,7 @@ type Client interface { // Connect will establish connection to rpc endpoints Connect(timeout uint) error - // GetTunnels returns all tunnel in band chain. + // GetTunnels returns all tunnel in BandChain. GetTunnels(ctx context.Context) ([]types.Tunnel, error) } @@ -68,7 +68,7 @@ func NewClient(ctx cosmosclient.Context, queryClient *QueryClient, log *zap.Logg } } -// Connect connects to the Band chain using the provided RPC endpoints. +// Connect connects to the BandChain using the provided RPC endpoints. func (c *client) Connect(timeout uint) error { for _, rpcEndpoint := range c.RpcEndpoints { // Create a new HTTP client for the specified node URI @@ -78,12 +78,6 @@ func (c *client) Connect(timeout uint) error { continue // Try the next endpoint if there's an error } - // Start the client to establish a connection - if err = client.Start(); err != nil { - c.Log.Error("Failed to start client", zap.String("rpcEndpoint", rpcEndpoint), zap.Error(err)) - continue // Try the next endpoint if starting the client fails - } - // Create a new client context and configure it with necessary parameters encodingConfig := MakeEncodingConfig() ctx := cosmosclient.Context{}. @@ -94,12 +88,12 @@ func (c *client) Connect(timeout uint) error { c.Context = ctx c.QueryClient = NewQueryClient(tunneltypes.NewQueryClient(ctx), bandtsstypes.NewQueryClient(ctx)) - c.Log.Info("Connected to Band chain", zap.String("endpoint", rpcEndpoint)) + c.Log.Info("Connected to BandChain", zap.String("endpoint", rpcEndpoint)) return nil } - return nil + return fmt.Errorf("failed to connect to BandChain") } // GetTunnel gets tunnel info from band client @@ -195,11 +189,11 @@ func (c *client) GetTunnelPacket(ctx context.Context, tunnelID uint64, sequence ), nil } -// GetTunnels returns every tss-route tunnels in band chain. +// GetTunnels returns every tss-route tunnels in BandChain. func (c *client) GetTunnels(ctx context.Context) ([]types.Tunnel, error) { // check connection to bandchain if c.QueryClient == nil { - return nil, fmt.Errorf("cannot connect to bandchain") + return nil, fmt.Errorf("cannot connect to BandChain") } tunnels := make([]types.Tunnel, 0) @@ -246,7 +240,7 @@ func (c *client) GetTunnels(ctx context.Context) ([]types.Tunnel, error) { return tunnels, nil } -// unpackAny unpacks the provided *codectypes.Any into the specified interface. +// UnpackAny unpacks the provided *codectypes.Any into the specified interface. func (c *client) UnpackAny(any *codectypes.Any, target interface{}) error { err := c.Context.InterfaceRegistry.UnpackAny(any, target) if err != nil { diff --git a/relayer/chains/evm/config.go b/relayer/chains/evm/config.go index a47f8a5..9bf5c0f 100644 --- a/relayer/chains/evm/config.go +++ b/relayer/chains/evm/config.go @@ -14,7 +14,6 @@ var _ chains.ChainProviderConfig = &EVMChainProviderConfig{} type EVMChainProviderConfig struct { chains.BaseChainProviderConfig `mapstructure:",squash"` - PrivateKey string `mapstructure:"private_key" toml:"private_key"` BlockConfirmation uint64 `mapstructure:"block_confirmation" toml:"block_confirmation"` WaitingTxDuration time.Duration `mapstructure:"waiting_tx_duration" toml:"waiting_tx_duration"` LivelinessCheckingInterval time.Duration `mapstructure:"liveliness_checking_interval" toml:"liveliness_checking_interval"` diff --git a/relayer/chains/evm/keys.go b/relayer/chains/evm/keys.go index 7beae48..06d82a8 100644 --- a/relayer/chains/evm/keys.go +++ b/relayer/chains/evm/keys.go @@ -13,6 +13,7 @@ import ( hdwallet "github.com/miguelmota/go-ethereum-hdwallet" "github.com/pelletier/go-toml/v2" + "github.com/bandprotocol/falcon/internal" chainstypes "github.com/bandprotocol/falcon/relayer/chains/types" ) @@ -154,8 +155,8 @@ func (cp *EVMChainProvider) ExportPrivateKey(keyName, passphrase string) (string return hex.EncodeToString(crypto.FromECDSA(key.PrivateKey)), nil } -// Listkeys lists all keys. -func (cp *EVMChainProvider) Listkeys() []*chainstypes.Key { +// ListKeys lists all keys. +func (cp *EVMChainProvider) ListKeys() []*chainstypes.Key { res := make([]*chainstypes.Key, 0, len(cp.KeyInfo)) for keyName, address := range cp.KeyInfo { key := chainstypes.NewKey("", address, keyName) @@ -190,12 +191,12 @@ func (cp *EVMChainProvider) storeKeyInfo(homePath string) error { keyInfoDir := path.Join(homePath, keyDir, cp.ChainName, infoDir) keyInfoPath := path.Join(keyInfoDir, infoFileName) + // Create the info folder if doesn't exist - if _, err := os.Stat(keyInfoDir); os.IsNotExist(err) { - if err = os.Mkdir(keyInfoDir, os.ModePerm); err != nil { - return err - } + if err := internal.CheckAndCreateFolder(keyInfoDir); err != nil { + return err } + // Create the file and write the default config to the given location. f, err := os.Create(keyInfoPath) if err != nil { diff --git a/relayer/chains/provider.go b/relayer/chains/provider.go index dfb3c85..ad49740 100644 --- a/relayer/chains/provider.go +++ b/relayer/chains/provider.go @@ -54,7 +54,7 @@ type KeyProvider interface { DeleteKey(homePath, keyName, passphrase string) error // ListKeys lists all keys - Listkeys() []*chainstypes.Key + ListKeys() []*chainstypes.Key // ShowKey shows the address of the given key ShowKey(keyName string) string diff --git a/relayer/config_test.go b/relayer/config_test.go index 04f1acb..eb9dafe 100644 --- a/relayer/config_test.go +++ b/relayer/config_test.go @@ -20,7 +20,7 @@ func TestLoadConfig(t *testing.T) { customConfigPath := "" cfgPath := path.Join(tmpDir, "config", "config.toml") - app := relayer.NewApp(nil, nil, tmpDir, false, nil) + app := relayer.NewApp(nil, tmpDir, false, nil) // Prepare config before test err := app.InitConfigFile(tmpDir, customConfigPath) diff --git a/relayer/scheduler.go b/relayer/scheduler.go index 92f93cb..422b074 100644 --- a/relayer/scheduler.go +++ b/relayer/scheduler.go @@ -59,12 +59,13 @@ func NewScheduler( // Start starts all tunnel relayers func (s *Scheduler) Start(ctx context.Context) error { ticker := time.NewTicker(s.CheckingPacketInterval) + defer ticker.Stop() + syncTunnelTicker := time.NewTicker(s.SyncTunnelsInterval) + defer syncTunnelTicker.Stop() // execute once we start the scheduler. s.Execute(ctx) - defer ticker.Stop() - defer syncTunnelTicker.Stop() for { select { @@ -156,10 +157,10 @@ func (s *Scheduler) SyncTunnels(ctx context.Context) { return } - s.Log.Info("Starting sync tunnels") + s.Log.Info("Start syncing new tunnels") tunnels, err := s.BandClient.GetTunnels(ctx) if err != nil { - s.Log.Error("Failed to fetch tunnels from Band Chain", zap.Error(err)) + s.Log.Error("Failed to fetch tunnels from BandChain", zap.Error(err)) return } oldTunnelCount := len(s.TunnelRelayers) @@ -179,6 +180,7 @@ func (s *Scheduler) SyncTunnels(ctx context.Context) { ) continue } + tr := NewTunnelRelayer( s.Log, tunnels[i].ID, diff --git a/relayer/tunnel_relayer.go b/relayer/tunnel_relayer.go index 4d6fc88..fc30fc2 100644 --- a/relayer/tunnel_relayer.go +++ b/relayer/tunnel_relayer.go @@ -100,18 +100,19 @@ func (t *TunnelRelayer) CheckAndRelay(ctx context.Context) (err error) { signing = packet.IncomingGroupSigning } - switch tsstypes.SigningStatus(tsstypes.SigningStatus_value[signing.Status]) { - case tsstypes.SIGNING_STATUS_FALLEN: - err := fmt.Errorf("signing status is fallen") - t.Log.Error("Failed to relay packet", zap.Error(err), zap.Uint64("sequence", seq)) - return err - - case tsstypes.SIGNING_STATUS_WAITING: + // Check signing status; if it is waiting, wait for the completion of the EVM signature. + // If it is not success (Failed or Undefined), return error. + signingStatus := tsstypes.SigningStatus(tsstypes.SigningStatus_value[signing.Status]) + if signingStatus == tsstypes.SIGNING_STATUS_WAITING { t.Log.Info( "The current packet must wait for the completion of the EVM signature", zap.Uint64("sequence", seq), ) return nil + } else if signingStatus != tsstypes.SIGNING_STATUS_SUCCESS { + err := fmt.Errorf("signing status is not success") + t.Log.Error("Failed to relay packet", zap.Error(err), zap.Uint64("sequence", seq)) + return err } if err := t.TargetChainProvider.RelayPacket(ctx, packet); err != nil {