From 0f3b9e90174b6d1e9ab673bb423b3bc35342ba58 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:23:07 -0700 Subject: [PATCH 01/12] initial commit and some questions --- cmd/peggo/bridge.go | 20 ++++++++------------ cmd/peggo/flags.go | 4 ++++ cmd/peggo/grpcclient.go | 31 +++++++++++++++++++++++++++++++ cmd/peggo/orchestrator.go | 2 ++ 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 cmd/peggo/grpcclient.go diff --git a/cmd/peggo/bridge.go b/cmd/peggo/bridge.go index 0315a9bb..d058e04a 100644 --- a/cmd/peggo/bridge.go +++ b/cmd/peggo/bridge.go @@ -113,10 +113,9 @@ func deployGravityCmd() *cobra.Command { } // ETH RPC - ethRPCEndpoint := konfig.String(flagEthRPC) - ethRPC, err := ethclient.Dial(ethRPCEndpoint) + ethRPC, err := getEthClient(konfig) if err != nil { - return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) + return err } auth, err := buildTransactOpts(konfig, ethRPC) @@ -194,10 +193,9 @@ func deployERC20Cmd() *cobra.Command { return err } - ethRPCEndpoint := konfig.String(flagEthRPC) - ethRPC, err := ethclient.Dial(ethRPCEndpoint) + ethRPC, err := getEthClient(konfig) if err != nil { - return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) + return err } auth, err := buildTransactOpts(konfig, ethRPC) @@ -329,10 +327,9 @@ network starting.`, return err } - ethRPCEndpoint := konfig.String(flagEthRPC) - ethRPC, err := ethclient.Dial(ethRPCEndpoint) + ethRPC, err := getEthClient(konfig) if err != nil { - return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) + return err } auth, err := buildTransactOpts(konfig, ethRPC) @@ -391,10 +388,9 @@ func sendToCosmosCmd() *cobra.Command { return err } - ethRPCEndpoint := konfig.String(flagEthRPC) - ethRPC, err := ethclient.Dial(ethRPCEndpoint) + ethRPC, err := getEthClient(konfig) if err != nil { - return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) + return err } gravityAddr := args[0] diff --git a/cmd/peggo/flags.go b/cmd/peggo/flags.go index 3f92b1c1..e64120dc 100644 --- a/cmd/peggo/flags.go +++ b/cmd/peggo/flags.go @@ -2,6 +2,8 @@ package peggo import ( + "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/spf13/pflag" ) @@ -32,6 +34,7 @@ const ( flagEthPK = "eth-pk" flagEthUseLedger = "eth-use-ledger" flagEthRPC = "eth-rpc" + flagEthRPCs = "eth-rpcs" flagEthGasAdjustment = "eth-gas-price-adjustment" flagEthGasLimitAdjustment = "eth-gas-limit-adjustment" flagEthAlchemyWS = "eth-alchemy-ws" @@ -93,6 +96,7 @@ func ethereumOptsFlagSet() *pflag.FlagSet { fs.String(flagEthRPC, "http://localhost:8545", "Specify the RPC address of an Ethereum node") fs.Float64(flagEthGasAdjustment, float64(1.3), "Specify a gas price adjustment for Ethereum transactions") fs.Float64(flagEthGasLimitAdjustment, float64(1.2), "Specify a gas limit adjustment for Ethereum transactions") + _ = fs.MarkDeprecated(flagEthRPC, fmt.Sprintf("Use the '%s' flag instead to provide one or more Ethereum RPC instances", flagEthRPCs)) return fs } diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go new file mode 100644 index 00000000..46a2896c --- /dev/null +++ b/cmd/peggo/grpcclient.go @@ -0,0 +1,31 @@ +package peggo + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/knadh/koanf" + "github.com/pkg/errors" +) + +// dials configured ethereum rpc endpoints, returning a client connected to the first successfully dialed +func getEthClient(konfig *koanf.Koanf) (*ethclient.Client, error) { + rpcs := konfig.Strings(flagEthRPCs) + + for _, endpoint := range rpcs { + if cli, err := ethclient.Dial(endpoint); err == nil { + return cli, nil + } + } + + // todo #196: this doesn't try to remember if an endpoint is failing frequently + // which would be needed to rotate / avoid trying to dial it every single time. + // + // a cheap way to do basically that would be to store the last endpoint *successfully* + // dialed using a var at the top of this file, always trying that first if non-empty, + // clearing it on fail, and setting it to a new string whenever a dial succeeds on + // an endpoint in the for loop + // + // also, if there are other reasons we want to avoid an endpoint (e.g. dialing succeeds + // but the rpcs are behaving badly) then this function won't be able to help + + return nil, errors.New("Could not connect to any of the Ethereum RPC endpoints provided") +} diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index b8f1e8c7..76e8650e 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -116,6 +116,8 @@ func getOrchestratorCmd() *cobra.Command { return fmt.Errorf("failed to initialize Ethereum account: %w", err) } + // todo #196: change this to try multiple endpoints. Also, is this one more complicated + // than the other cmds, i.e. the client is being reused over time? ethRPCEndpoint := konfig.String(flagEthRPC) ethRPC, err := ethrpc.Dial(ethRPCEndpoint) if err != nil { From 0a18552a6891106e811aabb436ef8a9014b14f0d Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:48:26 -0700 Subject: [PATCH 02/12] flags updates --- cmd/peggo/flags.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/peggo/flags.go b/cmd/peggo/flags.go index c5d4b2f8..0040248c 100644 --- a/cmd/peggo/flags.go +++ b/cmd/peggo/flags.go @@ -91,6 +91,7 @@ func ethereumOptsFlagSet() *pflag.FlagSet { fs := pflag.NewFlagSet("", pflag.ContinueOnError) fs.String(flagEthRPC, "http://localhost:8545", "Specify the RPC address of an Ethereum node") + fs.StringSlice(flagEthRPCs, []string{"http://localhost:8545"}, "Specify RPC addresses of one or more Ethereum nodes") fs.Float64(flagEthGasAdjustment, float64(1.3), "Specify a gas price adjustment for Ethereum transactions") fs.Float64(flagEthGasLimitAdjustment, float64(1.2), "Specify a gas limit adjustment for Ethereum transactions") _ = fs.MarkDeprecated(flagEthRPC, fmt.Sprintf("Use the '%s' flag instead to provide one or more Ethereum RPC instances", flagEthRPCs)) @@ -102,8 +103,10 @@ func bridgeFlagSet() *pflag.FlagSet { fs := pflag.NewFlagSet("", pflag.ContinueOnError) fs.String(flagEthRPC, "http://localhost:8545", "Specify the RPC address of an Ethereum node") + fs.StringSlice(flagEthRPCs, []string{"http://localhost:8545"}, "Specify RPC addresses of one or more Ethereum nodes") fs.Int64(flagEthGasPrice, 0, "The Ethereum gas price to include in the transaction; If zero, gas price will be estimated") fs.Int64(flagEthGasLimit, 6000000, "The Ethereum gas limit to include in the transaction") + _ = fs.MarkDeprecated(flagEthRPC, fmt.Sprintf("Use the '%s' flag instead to provide one or more Ethereum RPC instances", flagEthRPCs)) return fs } From c105f8973750fd2d078156c5d9cd9e8b125480a6 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:49:45 -0700 Subject: [PATCH 03/12] lint++ --- cmd/peggo/grpcclient.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index 46a2896c..c4184726 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -27,5 +27,5 @@ func getEthClient(konfig *koanf.Koanf) (*ethclient.Client, error) { // also, if there are other reasons we want to avoid an endpoint (e.g. dialing succeeds // but the rpcs are behaving badly) then this function won't be able to help - return nil, errors.New("Could not connect to any of the Ethereum RPC endpoints provided") + return nil, errors.New("could not connect to any of the Ethereum RPC endpoints provided") } From 79f54544162d9277b0adb158730f1e115be8e278 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Tue, 22 Feb 2022 08:31:18 -0700 Subject: [PATCH 04/12] current progress --- cmd/peggo/bridge.go | 21 ++++------- cmd/peggo/grpcclient.go | 77 ++++++++++++++++++++++++++++++--------- cmd/peggo/orchestrator.go | 1 + cmd/peggo/peggo.go | 2 +- 4 files changed, 69 insertions(+), 32 deletions(-) diff --git a/cmd/peggo/bridge.go b/cmd/peggo/bridge.go index d058e04a..66ab9d16 100644 --- a/cmd/peggo/bridge.go +++ b/cmd/peggo/bridge.go @@ -113,7 +113,8 @@ func deployGravityCmd() *cobra.Command { } // ETH RPC - ethRPC, err := getEthClient(konfig) + InitEthRPCManager(konfig) + ethRPC, err := ethManager.GetClient() if err != nil { return err } @@ -193,10 +194,8 @@ func deployERC20Cmd() *cobra.Command { return err } - ethRPC, err := getEthClient(konfig) - if err != nil { - return err - } + InitEthRPCManager(konfig) + ethRPC, err := ethManager.GetClient() auth, err := buildTransactOpts(konfig, ethRPC) if err != nil { @@ -327,10 +326,8 @@ network starting.`, return err } - ethRPC, err := getEthClient(konfig) - if err != nil { - return err - } + InitEthRPCManager(konfig) + ethRPC, err := ethManager.GetClient() auth, err := buildTransactOpts(konfig, ethRPC) if err != nil { @@ -388,10 +385,8 @@ func sendToCosmosCmd() *cobra.Command { return err } - ethRPC, err := getEthClient(konfig) - if err != nil { - return err - } + InitEthRPCManager(konfig) + ethRPC, err := ethManager.GetClient() gravityAddr := args[0] diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index c4184726..54a369f3 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -6,26 +6,67 @@ import ( "github.com/pkg/errors" ) -// dials configured ethereum rpc endpoints, returning a client connected to the first successfully dialed -func getEthClient(konfig *koanf.Koanf) (*ethclient.Client, error) { - rpcs := konfig.Strings(flagEthRPCs) +var ethManager *EthRPCManager - for _, endpoint := range rpcs { - if cli, err := ethclient.Dial(endpoint); err == nil { - return cli, nil +type EthRPCManager struct { + currentEndpoint int // the slice index of the endpoint currently used + client *ethclient.Client // the current client + konfig *koanf.Koanf + + // TODO: how to detect client failures so we can call DialNext() + // maybe wrap methods +} + +// initializes the single instance of EthRPCManager with a given config (uses flagEthRPCs) +func InitEthRPCManager(konfig *koanf.Koanf) { + if ethManager == nil { + ethManager = &EthRPCManager{ + konfig: konfig, + } + } +} + +// closes the current client and dials configured ethereum rpc endpoints in a roundrobin fashion until one +// is connected. returns an error if no endpoints exist or all dials failed +func (em *EthRPCManager) DialNext() error { + rpcs := em.konfig.Strings(flagEthRPCs) + + if em.client != nil { + em.client.Close() + em.client = nil + } + + dialIndex := func(i int) bool { + if cli, err := ethclient.Dial(rpcs[i]); err == nil { + em.currentEndpoint = i + em.client = cli + return true } + return false } - // todo #196: this doesn't try to remember if an endpoint is failing frequently - // which would be needed to rotate / avoid trying to dial it every single time. - // - // a cheap way to do basically that would be to store the last endpoint *successfully* - // dialed using a var at the top of this file, always trying that first if non-empty, - // clearing it on fail, and setting it to a new string whenever a dial succeeds on - // an endpoint in the for loop - // - // also, if there are other reasons we want to avoid an endpoint (e.g. dialing succeeds - // but the rpcs are behaving badly) then this function won't be able to help - - return nil, errors.New("could not connect to any of the Ethereum RPC endpoints provided") + // first tries all endpoints in the slice after the current index + for i := range rpcs { + if i > em.currentEndpoint && dialIndex(i) { + return nil + } + } + + // then tries remaining endpoints from the beginning of the slice + for i := range rpcs { + if i <= em.currentEndpoint && dialIndex(i) { + return nil + } + } + + return errors.New("could not dial any of the Ethereum RPC endpoints provided") +} + +func (em *EthRPCManager) GetClient() (*ethclient.Client, error) { + if em.client == nil { + if err := em.DialNext(); err != nil { + return nil, err + } + } + return em.client, nil } diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 1bac8d3e..463aac52 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -120,6 +120,7 @@ func getOrchestratorCmd() *cobra.Command { // than the other cmds, i.e. the client is being reused over time? ethRPCEndpoint := konfig.String(flagEthRPC) ethRPC, err := ethrpc.Dial(ethRPCEndpoint) + //ethRPC, err := ethManager.GetClient() // todo: wrong type. find out why. if err != nil { return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) } diff --git a/cmd/peggo/peggo.go b/cmd/peggo/peggo.go index 58612cc8..a72108b9 100644 --- a/cmd/peggo/peggo.go +++ b/cmd/peggo/peggo.go @@ -21,7 +21,7 @@ func NewRootCmd() *cobra.Command { Long: `Peggo is a companion executable for orchestrating a Gravity validator. Inputs in the CLI commands can be provided via flags or environment variables. If -using the later, prefix the environment variable with PEGGO_ and the named of the +using the latter, prefix the environment variable with PEGGO_ and the named of the flag (e.g. PEGGO_COSMOS_PK).`, } From bb1609f5c97fe22a66dd01ef3e21749a3adf74e1 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Tue, 22 Feb 2022 09:20:30 -0700 Subject: [PATCH 05/12] resolved a type issue --- cmd/peggo/bridge.go | 8 ++++---- cmd/peggo/grpcclient.go | 20 +++++++++++++++----- cmd/peggo/orchestrator.go | 8 +++----- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/cmd/peggo/bridge.go b/cmd/peggo/bridge.go index 66ab9d16..eed14279 100644 --- a/cmd/peggo/bridge.go +++ b/cmd/peggo/bridge.go @@ -114,7 +114,7 @@ func deployGravityCmd() *cobra.Command { // ETH RPC InitEthRPCManager(konfig) - ethRPC, err := ethManager.GetClient() + ethRPC, err := ethManager.GetEthClient() if err != nil { return err } @@ -195,7 +195,7 @@ func deployERC20Cmd() *cobra.Command { } InitEthRPCManager(konfig) - ethRPC, err := ethManager.GetClient() + ethRPC, err := ethManager.GetEthClient() auth, err := buildTransactOpts(konfig, ethRPC) if err != nil { @@ -327,7 +327,7 @@ network starting.`, } InitEthRPCManager(konfig) - ethRPC, err := ethManager.GetClient() + ethRPC, err := ethManager.GetEthClient() auth, err := buildTransactOpts(konfig, ethRPC) if err != nil { @@ -386,7 +386,7 @@ func sendToCosmosCmd() *cobra.Command { } InitEthRPCManager(konfig) - ethRPC, err := ethManager.GetClient() + ethRPC, err := ethManager.GetEthClient() gravityAddr := args[0] diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index 54a369f3..48a13b19 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -2,6 +2,7 @@ package peggo import ( "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" "github.com/knadh/koanf" "github.com/pkg/errors" ) @@ -9,9 +10,10 @@ import ( var ethManager *EthRPCManager type EthRPCManager struct { - currentEndpoint int // the slice index of the endpoint currently used - client *ethclient.Client // the current client - konfig *koanf.Koanf + currentEndpoint int // the slice index of the endpoint currently used + //client *ethclient.Client // the current client + client *rpc.Client + konfig *koanf.Koanf // TODO: how to detect client failures so we can call DialNext() // maybe wrap methods @@ -37,7 +39,7 @@ func (em *EthRPCManager) DialNext() error { } dialIndex := func(i int) bool { - if cli, err := ethclient.Dial(rpcs[i]); err == nil { + if cli, err := rpc.Dial(rpcs[i]); err == nil { em.currentEndpoint = i em.client = cli return true @@ -62,7 +64,7 @@ func (em *EthRPCManager) DialNext() error { return errors.New("could not dial any of the Ethereum RPC endpoints provided") } -func (em *EthRPCManager) GetClient() (*ethclient.Client, error) { +func (em *EthRPCManager) GetClient() (*rpc.Client, error) { if em.client == nil { if err := em.DialNext(); err != nil { return nil, err @@ -70,3 +72,11 @@ func (em *EthRPCManager) GetClient() (*ethclient.Client, error) { } return em.client, nil } + +func (em *EthRPCManager) GetEthClient() (*ethclient.Client, error) { + cli, err := em.GetClient() + if err != nil { + return nil, err + } + return ethclient.NewClient(cli), nil +} diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index 463aac52..f82317c8 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -11,7 +11,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcmn "github.com/ethereum/go-ethereum/common" - ethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/rs/zerolog" "github.com/spf13/cobra" rpchttp "github.com/tendermint/tendermint/rpc/client/http" @@ -118,14 +117,13 @@ func getOrchestratorCmd() *cobra.Command { // todo #196: change this to try multiple endpoints. Also, is this one more complicated // than the other cmds, i.e. the client is being reused over time? - ethRPCEndpoint := konfig.String(flagEthRPC) - ethRPC, err := ethrpc.Dial(ethRPCEndpoint) - //ethRPC, err := ethManager.GetClient() // todo: wrong type. find out why. + // ethRPCEndpoint := konfig.String(flagEthRPC) + ethRPC, err := ethManager.GetClient() if err != nil { return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) } - fmt.Fprintf(os.Stderr, "Connected to Ethereum RPC: %s\n", ethRPCEndpoint) + //fmt.Fprintf(os.Stderr, "Connected to Ethereum RPC: %s\n", ethRPCEndpoint) ethProvider := provider.NewEVMProvider(ethRPC) ethGasPriceAdjustment := konfig.Float64(flagEthGasAdjustment) From 6dc4898c8a0576aee4926eba4c24b4397fcbf600 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Tue, 22 Feb 2022 10:32:25 -0700 Subject: [PATCH 06/12] comments --- cmd/peggo/grpcclient.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index 48a13b19..93efa6d9 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -1,6 +1,8 @@ package peggo import ( + "fmt" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/knadh/koanf" @@ -28,15 +30,20 @@ func InitEthRPCManager(konfig *koanf.Koanf) { } } -// closes the current client and dials configured ethereum rpc endpoints in a roundrobin fashion until one -// is connected. returns an error if no endpoints exist or all dials failed -func (em *EthRPCManager) DialNext() error { - rpcs := em.konfig.Strings(flagEthRPCs) - +// closes and sets to nil the stored eth RPC client +func (em *EthRPCManager) CloseClient() { if em.client != nil { em.client.Close() em.client = nil } +} + +// closes the current client and dials configured ethereum rpc endpoints in a roundrobin fashion until one +// is connected. returns an error if no endpoints ar configured or all dials failed +func (em *EthRPCManager) DialNext() error { + rpcs := em.konfig.Strings(flagEthRPCs) + + em.CloseClient() dialIndex := func(i int) bool { if cli, err := rpc.Dial(rpcs[i]); err == nil { @@ -44,6 +51,7 @@ func (em *EthRPCManager) DialNext() error { em.client = cli return true } + // todo: should likely log the error return false } @@ -61,9 +69,10 @@ func (em *EthRPCManager) DialNext() error { } } - return errors.New("could not dial any of the Ethereum RPC endpoints provided") + return errors.New(fmt.Sprintf("could not dial any of the %d Ethereum RPC endpoints configured", len(rpcs))) } +// returns the current eth RPC client, dialing one first if nonexistent func (em *EthRPCManager) GetClient() (*rpc.Client, error) { if em.client == nil { if err := em.DialNext(); err != nil { @@ -73,6 +82,7 @@ func (em *EthRPCManager) GetClient() (*rpc.Client, error) { return em.client, nil } +// returns the current eth RPC client, dialing one first if nonexistent func (em *EthRPCManager) GetEthClient() (*ethclient.Client, error) { cli, err := em.GetClient() if err != nil { From 77894acd1b44389dc0050dfd9e2d299ed8c6f3b0 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 23 Feb 2022 18:17:04 -0700 Subject: [PATCH 07/12] comments --- cmd/peggo/orchestrator.go | 4 ---- cmd/peggo/peggo.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd/peggo/orchestrator.go b/cmd/peggo/orchestrator.go index dd50e293..e631cb3a 100644 --- a/cmd/peggo/orchestrator.go +++ b/cmd/peggo/orchestrator.go @@ -122,15 +122,11 @@ func getOrchestratorCmd() *cobra.Command { return fmt.Errorf("failed to initialize Ethereum account: %w", err) } - // todo #196: change this to try multiple endpoints. Also, is this one more complicated - // than the other cmds, i.e. the client is being reused over time? - // ethRPCEndpoint := konfig.String(flagEthRPC) ethRPC, err := ethManager.GetClient() if err != nil { return fmt.Errorf("failed to dial Ethereum RPC node: %w", err) } - //fmt.Fprintf(os.Stderr, "Connected to Ethereum RPC: %s\n", ethRPCEndpoint) ethProvider := provider.NewEVMProvider(ethRPC) ethGasPriceAdjustment := konfig.Float64(flagEthGasAdjustment) diff --git a/cmd/peggo/peggo.go b/cmd/peggo/peggo.go index a72108b9..a9cf8da9 100644 --- a/cmd/peggo/peggo.go +++ b/cmd/peggo/peggo.go @@ -81,7 +81,7 @@ func parseServerConfig(cmd *cobra.Command) (*koanf.Koanf, error) { konfig := koanf.New(".") // load from file first (if provided) - // TODO: Support config files if/when needed. + // TODO: Support config files if/when needed. Koanf also supports watch the config file for changes // if configPath := ctx.String(config.ConfigPath); len(configPath) != 0 { // if err := konfig.Load(file.Provider(configPath), toml.Parser()); err != nil { // return nil, err From 34c97e0c26e50eede2cb0e430733f4e2f149f6bd Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 23 Feb 2022 19:50:42 -0700 Subject: [PATCH 08/12] simple functions wrapped --- cmd/peggo/bridge.go | 20 ++++++++-------- cmd/peggo/grpcclient.go | 51 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/cmd/peggo/bridge.go b/cmd/peggo/bridge.go index b9fd3d9c..98cacd08 100644 --- a/cmd/peggo/bridge.go +++ b/cmd/peggo/bridge.go @@ -125,7 +125,7 @@ func deployGravityCmd() *cobra.Command { return err } - auth, err := buildTransactOpts(konfig, ethRPC) + auth, err := buildTransactOpts(konfig) if err != nil { return err } @@ -203,7 +203,7 @@ func deployERC20Cmd() *cobra.Command { InitEthRPCManager(konfig) ethRPC, err := ethManager.GetEthClient() - auth, err := buildTransactOpts(konfig, ethRPC) + auth, err := buildTransactOpts(konfig) if err != nil { return err } @@ -341,7 +341,7 @@ network starting.`, InitEthRPCManager(konfig) ethRPC, err := ethManager.GetEthClient() - auth, err := buildTransactOpts(konfig, ethRPC) + auth, err := buildTransactOpts(konfig) if err != nil { return err } @@ -416,7 +416,7 @@ func sendToCosmosCmd() *cobra.Command { } } - auth, err := buildTransactOpts(konfig, ethRPC) + auth, err := buildTransactOpts(konfig) if err != nil { return err } @@ -459,7 +459,9 @@ Transaction: %s return cmd } -func buildTransactOpts(konfig *koanf.Koanf, ethClient *ethclient.Client) (*bind.TransactOpts, error) { +func buildTransactOpts(konfig *koanf.Koanf) (*bind.TransactOpts, error) { + InitEthRPCManager(konfig) + ethPrivKeyHexStr := konfig.String(flagEthPK) privKey, err := ethcrypto.ToECDSA(ethcmn.FromHex(ethPrivKeyHexStr)) @@ -478,7 +480,7 @@ func buildTransactOpts(konfig *koanf.Koanf, ethClient *ethclient.Client) (*bind. fromAddress := ethcrypto.PubkeyToAddress(*publicKeyECDSA) - nonce, err := ethClient.PendingNonceAt(goCtx, fromAddress) + nonce, err := ethManager.PendingNonceAt(goCtx, fromAddress) if err != nil { return nil, err } @@ -486,7 +488,7 @@ func buildTransactOpts(konfig *koanf.Koanf, ethClient *ethclient.Client) (*bind. goCtx, cancel = context.WithTimeout(context.Background(), 15*time.Second) defer cancel() - ethChainID, err := ethClient.ChainID(goCtx) + ethChainID, err := ethManager.ChainID(goCtx) if err != nil { return nil, fmt.Errorf("failed to get Ethereum chain ID: %w", err) } @@ -507,7 +509,7 @@ func buildTransactOpts(konfig *koanf.Koanf, ethClient *ethclient.Client) (*bind. gasPrice = big.NewInt(gasPriceInt) default: - gasPrice, err = ethClient.SuggestGasPrice(context.Background()) + gasPrice, err = ethManager.SuggestGasPrice(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get Ethereum gas estimate: %w", err) } @@ -555,7 +557,7 @@ func approveERC20(konfig *koanf.Koanf, ethRPC *ethclient.Client, erc20AddrStr, g return fmt.Errorf("failed to create ERC20 contract instance: %w", err) } - auth, err := buildTransactOpts(konfig, ethRPC) + auth, err := buildTransactOpts(konfig) if err != nil { return err } diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index 93efa6d9..aa110144 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -1,8 +1,11 @@ package peggo import ( + "context" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/knadh/koanf" @@ -21,7 +24,8 @@ type EthRPCManager struct { // maybe wrap methods } -// initializes the single instance of EthRPCManager with a given config (uses flagEthRPCs) +// initializes the single instance of EthRPCManager with a given config (uses flagEthRPCs). +// no-op if already initialized, even if konfig would be different. func InitEthRPCManager(konfig *koanf.Koanf) { if ethManager == nil { ethManager = &EthRPCManager{ @@ -41,6 +45,9 @@ func (em *EthRPCManager) CloseClient() { // closes the current client and dials configured ethereum rpc endpoints in a roundrobin fashion until one // is connected. returns an error if no endpoints ar configured or all dials failed func (em *EthRPCManager) DialNext() error { + if em.konfig == nil { + return errors.New("ethRPCManager konfig is nil") + } rpcs := em.konfig.Strings(flagEthRPCs) em.CloseClient() @@ -90,3 +97,45 @@ func (em *EthRPCManager) GetEthClient() (*ethclient.Client, error) { } return ethclient.NewClient(cli), nil } + +// wraps ethclient.PendingNonceAt, also closing client if PendingNonceAt returns an error +func (em *EthRPCManager) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error) { + cli, err := em.GetEthClient() + if err != nil { + return 0, err + } + nonce, err := cli.PendingNonceAt(ctx, addr) + if err != nil { + em.CloseClient() + return 0, err + } + return nonce, nil +} + +// wraps ethclient.ChainID, also closing client if ChainID returns an error +func (em *EthRPCManager) ChainID(ctx context.Context) (*big.Int, error) { + cli, err := em.GetEthClient() + if err != nil { + return nil, err + } + id, err := cli.ChainID(ctx) + if err != nil { + em.CloseClient() + return nil, err + } + return id, nil +} + +// wraps ethclient.SuggestGasPrice, also closing client if SuggestGasPrice returns an error +func (em *EthRPCManager) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + cli, err := em.GetEthClient() + if err != nil { + return nil, err + } + price, err := cli.SuggestGasPrice(ctx) + if err != nil { + em.CloseClient() + return nil, err + } + return price, nil +} From 767e958cb519511ceed84985188d1829b0969084 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 23 Feb 2022 19:55:49 -0700 Subject: [PATCH 09/12] lint++ --- cmd/peggo/bridge.go | 9 +++++++++ cmd/peggo/grpcclient.go | 8 ++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/peggo/bridge.go b/cmd/peggo/bridge.go index 98cacd08..930ff418 100644 --- a/cmd/peggo/bridge.go +++ b/cmd/peggo/bridge.go @@ -202,6 +202,9 @@ func deployERC20Cmd() *cobra.Command { InitEthRPCManager(konfig) ethRPC, err := ethManager.GetEthClient() + if err != nil { + return err + } auth, err := buildTransactOpts(konfig) if err != nil { @@ -340,6 +343,9 @@ network starting.`, InitEthRPCManager(konfig) ethRPC, err := ethManager.GetEthClient() + if err != nil { + return err + } auth, err := buildTransactOpts(konfig) if err != nil { @@ -399,6 +405,9 @@ func sendToCosmosCmd() *cobra.Command { InitEthRPCManager(konfig) ethRPC, err := ethManager.GetEthClient() + if err != nil { + return err + } gravityAddr := args[0] diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index aa110144..4038b084 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -16,12 +16,8 @@ var ethManager *EthRPCManager type EthRPCManager struct { currentEndpoint int // the slice index of the endpoint currently used - //client *ethclient.Client // the current client - client *rpc.Client - konfig *koanf.Koanf - - // TODO: how to detect client failures so we can call DialNext() - // maybe wrap methods + client *rpc.Client + konfig *koanf.Koanf } // initializes the single instance of EthRPCManager with a given config (uses flagEthRPCs). From 70915db3bda09e873249583ea18ea12d8ef6242f Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 23 Feb 2022 20:05:29 -0700 Subject: [PATCH 10/12] e2e flag change --- test/e2e/e2e_setup_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/e2e_setup_test.go b/test/e2e/e2e_setup_test.go index e0c1ed1e..1be6031c 100644 --- a/test/e2e/e2e_setup_test.go +++ b/test/e2e/e2e_setup_test.go @@ -545,7 +545,7 @@ func (s *IntegrationTestSuite) runContractDeployment() { "peggo", "bridge", "deploy-gravity", - "--eth-rpc", + "--eth-rpcs", fmt.Sprintf("http://%s:8545", s.ethResource.Container.Name[1:]), "--cosmos-grpc", fmt.Sprintf("tcp://%s:9090", s.valResources[0].Container.Name[1:]), From 354a1df1e1a44dd38a8ac3925f7cd19a7938bab6 Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 23 Feb 2022 21:41:13 -0700 Subject: [PATCH 11/12] troubleshoot flag --- cmd/peggo/grpcclient.go | 3 ++- test/e2e/e2e_setup_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index 4038b084..9be086da 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -44,7 +44,8 @@ func (em *EthRPCManager) DialNext() error { if em.konfig == nil { return errors.New("ethRPCManager konfig is nil") } - rpcs := em.konfig.Strings(flagEthRPCs) + //rpcs := em.konfig.Strings(flagEthRPCs) + rpcs := []string{em.konfig.String(flagEthRPC)} em.CloseClient() diff --git a/test/e2e/e2e_setup_test.go b/test/e2e/e2e_setup_test.go index 1be6031c..e0c1ed1e 100644 --- a/test/e2e/e2e_setup_test.go +++ b/test/e2e/e2e_setup_test.go @@ -545,7 +545,7 @@ func (s *IntegrationTestSuite) runContractDeployment() { "peggo", "bridge", "deploy-gravity", - "--eth-rpcs", + "--eth-rpc", fmt.Sprintf("http://%s:8545", s.ethResource.Container.Name[1:]), "--cosmos-grpc", fmt.Sprintf("tcp://%s:9090", s.valResources[0].Container.Name[1:]), From ec3a6c5c3b4784476e404af4c0eb71396a5ec53a Mon Sep 17 00:00:00 2001 From: toteki <63419657+toteki@users.noreply.github.com> Date: Wed, 23 Feb 2022 22:07:57 -0700 Subject: [PATCH 12/12] switch to comma-separated ethRPCs flag --- cmd/peggo/flags.go | 2 +- cmd/peggo/grpcclient.go | 5 +++-- test/e2e/e2e_setup_test.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/peggo/flags.go b/cmd/peggo/flags.go index 622a9022..97032baa 100644 --- a/cmd/peggo/flags.go +++ b/cmd/peggo/flags.go @@ -107,7 +107,7 @@ func bridgeFlagSet() *pflag.FlagSet { fs := pflag.NewFlagSet("", pflag.ContinueOnError) fs.String(flagEthRPC, "http://localhost:8545", "Specify the RPC address of an Ethereum node") - fs.StringSlice(flagEthRPCs, []string{"http://localhost:8545"}, "Specify RPC addresses of one or more Ethereum nodes") + fs.String(flagEthRPCs, "http://localhost:8545", "Specify comma-separated RPC addresses of one or more Ethereum nodes") fs.Int64(flagEthGasPrice, 0, "The Ethereum gas price to include in the transaction; If zero, gas price will be estimated") fs.Int64(flagEthGasLimit, 6000000, "The Ethereum gas limit to include in the transaction") _ = fs.MarkDeprecated(flagEthRPC, fmt.Sprintf("Use the '%s' flag instead to provide one or more Ethereum RPC instances", flagEthRPCs)) diff --git a/cmd/peggo/grpcclient.go b/cmd/peggo/grpcclient.go index 9be086da..ed9ad336 100644 --- a/cmd/peggo/grpcclient.go +++ b/cmd/peggo/grpcclient.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -44,8 +45,8 @@ func (em *EthRPCManager) DialNext() error { if em.konfig == nil { return errors.New("ethRPCManager konfig is nil") } - //rpcs := em.konfig.Strings(flagEthRPCs) - rpcs := []string{em.konfig.String(flagEthRPC)} + + rpcs := strings.Split(strings.ReplaceAll(em.konfig.String(flagEthRPCs), " ", ""), ",") em.CloseClient() diff --git a/test/e2e/e2e_setup_test.go b/test/e2e/e2e_setup_test.go index e0c1ed1e..1be6031c 100644 --- a/test/e2e/e2e_setup_test.go +++ b/test/e2e/e2e_setup_test.go @@ -545,7 +545,7 @@ func (s *IntegrationTestSuite) runContractDeployment() { "peggo", "bridge", "deploy-gravity", - "--eth-rpc", + "--eth-rpcs", fmt.Sprintf("http://%s:8545", s.ethResource.Container.Name[1:]), "--cosmos-grpc", fmt.Sprintf("tcp://%s:9090", s.valResources[0].Container.Name[1:]),