-
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
Changes from all commits
dd43a41
749bf6d
d123a1b
c196f12
059e8d9
94c2ba6
bc97243
f8d7e7a
f1499cb
4c63561
a0d7bea
e130f39
9bb177e
0a51103
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package peggo | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/big" | ||
"os" | ||
"strings" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/ethclient" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
"github.com/knadh/koanf" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type EthRPCManager struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add a godoc :) |
||
currentEndpoint int // index (in the slice of configured RPC endpoints) of most recent endpoint used | ||
client *rpc.Client | ||
konfig *koanf.Koanf | ||
} | ||
|
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible for there to be concurrent use of an There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
ethManager := &EthRPCManager{ | ||
konfig: konfig, | ||
} | ||
return ethManager | ||
} | ||
|
||
// 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 { | ||
if em.konfig == nil { | ||
return errors.New("ethRPCManager konfig is nil") | ||
} | ||
|
||
rpcs := strings.Split(strings.ReplaceAll(em.konfig.String(flagEthRPCs), " ", ""), ",") | ||
|
||
em.CloseClient() | ||
|
||
dialIndex := func(i int) bool { | ||
if cli, err := rpc.Dial(rpcs[i]); err == nil { | ||
em.currentEndpoint = i | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. We should probably pass the core logger to |
||
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 commentThe 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. |
||
for i := range rpcs { | ||
if i > em.currentEndpoint && dialIndex(i) { | ||
fmt.Fprintf(os.Stderr, "Connected to Ethereum RPC: %s\n", rpcs[i]) | ||
return nil | ||
} | ||
} | ||
|
||
// then tries remaining endpoints from the beginning of the slice | ||
for i := range rpcs { | ||
if i <= em.currentEndpoint && dialIndex(i) { | ||
fmt.Fprintf(os.Stderr, "Connected to Ethereum RPC: %s\n", rpcs[i]) | ||
return nil | ||
} | ||
} | ||
|
||
return errors.New(fmt.Sprintf("failed to 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 { | ||
return nil, err | ||
} | ||
} | ||
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 { | ||
return nil, err | ||
} | ||
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 | ||
} |
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.