-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support multiple ethereum rpcs #215
Conversation
All functions requiring cmd/bridge.go L171 in address, tx, _, err := wrappers.DeployGravity(auth, ethRPC, gravityIDBytes32, validators, powers) cmd/bridge.go L546 in contract, err := wrappers.NewGravity(ethcmn.HexToAddress(gravityAddr), ethRPC) cmd/bridge.go L560 in contract, err := wrappers.NewERC20(ethcmn.HexToAddress(erc20AddrStr), ethRPC) cmd/orchestrator.go L126 in ethProvider := provider.NewEVMProvider(ethRPC) Those functions require structs of the types If the commands in |
A note: The current behavior of EthRPCManager is as follows: Stores the following: type EthRPCManager struct {
currentEndpoint int // index (in the slice of configured RPC endpoints) of most recent endpoint used
client *rpc.Client
konfig *koanf.Koanf
} Has the following methods which return the current client if non-nil, otherwise attempting to dial each configured eth rpc endpoint in a roundrobin fashion, returning the first successfully dialed and only trying each endpoint a maximum of once per call. // returns the current eth RPC client, dialing one first if nonexistent
func (em *EthRPCManager) GetClient() (*rpc.Client, error)
// returns the current eth RPC client, dialing one first if nonexistent
func (em *EthRPCManager) GetEthClient() (*ethclient.Client, error) Additionally has the following methods, which call the same-name method on the current eth client, dialing one first if nil, and closing the current eth client if the wrapped function returns an error. // wraps ethclient.PendingNonceAt, also closing client if PendingNonceAt returns an error
func (em *EthRPCManager) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error)
// wraps ethclient.ChainID, also closing client if ChainID returns an error
func (em *EthRPCManager) ChainID(ctx context.Context) (*big.Int, error)
// wraps ethclient.SuggestGasPrice, also closing client if SuggestGasPrice returns an error
func (em *EthRPCManager) SuggestGasPrice(ctx context.Context) (*big.Int, error) |
Remaining things we might want to do:
|
I like this :) |
@toteki excellent summary and I like the approach. Regarding to the remaining (optional?) items:
Can you elaborate a bit more on this? Which functions in particular? You mean calls to Thoughts @facundomedica ? I'll be reviewing this PR shortly :) |
@@ -43,11 +43,19 @@ Ref: https://keepachangelog.com/en/1.0.0/ | |||
|
|||
## [Unreleased] | |||
|
|||
### Features | |||
|
|||
- [#215](https://github.com/umee-network/peggo/pull/215) Add the flag `--eth-rpcs` and support multiple ethereum rpc endpoints |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- [#215](https://github.com/umee-network/peggo/pull/215) Add the flag `--eth-rpcs` and support multiple ethereum rpc endpoints | |
- [#215](https://github.com/umee-network/peggo/pull/215) Add the flag `--eth-rpcs` and support multiple ethereum rpc endpoints. |
@@ -123,13 +122,12 @@ func getOrchestratorCmd() *cobra.Command { | |||
return fmt.Errorf("failed to initialize Ethereum account: %w", err) | |||
} | |||
|
|||
ethRPCEndpoint := konfig.String(flagEthRPC) | |||
ethRPC, err := ethrpc.Dial(ethRPCEndpoint) | |||
em := NewEthRPCManager(konfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean @toteki. I think the biggest win for peggo would be to have ETH RPC rotation at the orchestrator level, but it seems in order to do that, we'd have to do way too much wrapping :-/
"github.com/pkg/errors" | ||
) | ||
|
||
type EthRPCManager struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a godoc :)
} | ||
|
||
// creates an instance of EthRPCManager with a given konfig. | ||
func NewEthRPCManager(konfig *koanf.Koanf) *EthRPCManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible for there to be concurrent use of an EthRPCManager
instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use of the embedded clients themselves should be concurrency-safe, but concurrent redialing / closing would need to be worked on - for example if one goroutine closed the client (perhaps as a result of an error in a grpc call) while another was doing something, the latter could geta very unexpected "connection was closed" type of error.
Or if two concurrent redialing loops were active, a whole lot could happen. It should be possible to add a lock on DialNext() in particular before merging if we move forward with this.
em.client = cli | ||
return true | ||
} | ||
fmt.Fprintf(os.Stderr, "Failed to dial to Ethereum RPC: %s\n", rpcs[i]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably pass the core logger to NewEthRPCManager
instead of using fmt.*
.
return false | ||
} | ||
|
||
// first tries all endpoints in the slice after the current index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably combine both these loops into one using the modulo operator.
Just started looking deeper into this one. I'd consider this an "all or nothing" situation given that if just a single endpoint isn't covered, then we'll be back at where we started. For example see this one: https://github.com/umee-network/peggo/blob/main/orchestrator/eth_event_watcher_test.go#L185-L192 If any of those endpoints are not covered by this, then sadly the whole effort was in vain. type EVMProvider interface {
bind.ContractCaller // <- Must remove these and add their functions here too (probably)
bind.ContractFilterer
PendingNonceAt(ctx context.Context, account ethcmn.Address) (uint64, error)
PendingCodeAt(ctx context.Context, account ethcmn.Address) ([]byte, error)
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
SuggestGasPrice(ctx context.Context) (*big.Int, error)
TransactionByHash(ctx context.Context, hash ethcmn.Hash) (tx *types.Transaction, isPending bool, err error)
TransactionReceipt(ctx context.Context, txHash ethcmn.Hash) (*types.Receipt, error)
SendTransaction(ctx context.Context, tx *types.Transaction) error
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
} We could even change EVMProvider/EVMProviderWithRet to be EthRPCManager? Now, is it worth it? I'm not sure...
EDIT: I feel like I gave this text a harsh tone, so want to clarify that I love what you did here @toteki and it was quite eye-opening to see how hard it can be to add something that said in conversation sounds easy to do ("just add a backup lol"). |
I do agree that if we don't have RPC rotation at the orchestrator level, we don't get much of a win. @facundomedica are you suggesting we just scrap this in favor of providing some general nginx configuration to peggo operators? |
If nginx can do it generally without us having to go into the |
@facundomedica can you share what you have in mind re nginx? |
A valoper shared this in Discord: https://github.com/stakingcare/node-tools/blob/main/eth-api-failover/default |
Closing for now |
struct
EthRPCManager
supports multiple ethereum RPC endpoints and attempts to rotate through them when one failscloses #196