2022-08-01 14:43:47 +00:00
|
|
|
package execution
|
2022-04-09 01:28:40 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2022-08-29 23:34:29 +00:00
|
|
|
"strings"
|
2022-04-09 01:28:40 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
gethRPC "github.com/ethereum/go-ethereum/rpc"
|
|
|
|
"github.com/pkg/errors"
|
2022-08-16 12:20:13 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/v3/config/params"
|
|
|
|
contracts "github.com/prysmaticlabs/prysm/v3/contracts/deposit"
|
|
|
|
"github.com/prysmaticlabs/prysm/v3/io/logs"
|
|
|
|
"github.com/prysmaticlabs/prysm/v3/network"
|
|
|
|
"github.com/prysmaticlabs/prysm/v3/network/authorization"
|
2022-04-09 01:28:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (s *Service) setupExecutionClientConnections(ctx context.Context, currEndpoint network.Endpoint) error {
|
|
|
|
client, err := s.newRPCClientWithAuth(ctx, currEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not dial execution node")
|
|
|
|
}
|
|
|
|
// Attach the clients to the service struct.
|
|
|
|
fetcher := ethclient.NewClient(client)
|
|
|
|
s.rpcClient = client
|
|
|
|
s.httpLogger = fetcher
|
|
|
|
|
|
|
|
depositContractCaller, err := contracts.NewDepositContractCaller(s.cfg.depositContractAddr, fetcher)
|
|
|
|
if err != nil {
|
|
|
|
client.Close()
|
|
|
|
return errors.Wrap(err, "could not initialize deposit contract caller")
|
|
|
|
}
|
|
|
|
s.depositContractCaller = depositContractCaller
|
|
|
|
|
|
|
|
// Ensure we have the correct chain and deposit IDs.
|
|
|
|
if err := ensureCorrectExecutionChain(ctx, fetcher); err != nil {
|
|
|
|
client.Close()
|
2022-08-30 19:09:42 +00:00
|
|
|
errStr := err.Error()
|
|
|
|
if strings.Contains(errStr, "401 Unauthorized") {
|
|
|
|
errStr = "could not verify execution chain ID as your connection is not authenticated. " +
|
|
|
|
"If connecting to your execution client via HTTP, you will need to set up JWT authentication. " +
|
|
|
|
"See our documentation here https://docs.prylabs.network/docs/execution-node/authentication"
|
|
|
|
}
|
|
|
|
return errors.Wrap(err, errStr)
|
2022-04-09 01:28:40 +00:00
|
|
|
}
|
|
|
|
s.updateConnectedETH1(true)
|
|
|
|
s.runError = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Every N seconds, defined as a backoffPeriod, attempts to re-establish an execution client
|
|
|
|
// connection and if this does not work, we fallback to the next endpoint if defined.
|
|
|
|
func (s *Service) pollConnectionStatus(ctx context.Context) {
|
|
|
|
// Use a custom logger to only log errors
|
|
|
|
logCounter := 0
|
|
|
|
errorLogger := func(err error, msg string) {
|
|
|
|
if logCounter > logThreshold {
|
2022-08-05 10:52:02 +00:00
|
|
|
log.WithError(err).Error(msg)
|
2022-04-09 01:28:40 +00:00
|
|
|
logCounter = 0
|
|
|
|
}
|
|
|
|
logCounter++
|
|
|
|
}
|
|
|
|
ticker := time.NewTicker(backOffPeriod)
|
|
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
log.Debugf("Trying to dial endpoint: %s", logs.MaskCredentialsLogging(s.cfg.currHttpEndpoint.Url))
|
2022-04-28 13:09:03 +00:00
|
|
|
currClient := s.rpcClient
|
2022-04-09 01:28:40 +00:00
|
|
|
if err := s.setupExecutionClientConnections(ctx, s.cfg.currHttpEndpoint); err != nil {
|
|
|
|
errorLogger(err, "Could not connect to execution client endpoint")
|
2022-04-28 13:09:03 +00:00
|
|
|
continue
|
2022-04-09 01:28:40 +00:00
|
|
|
}
|
2022-04-28 13:09:03 +00:00
|
|
|
// Close previous client, if connection was successful.
|
2022-05-19 02:53:24 +00:00
|
|
|
if currClient != nil {
|
|
|
|
currClient.Close()
|
|
|
|
}
|
2022-04-28 13:09:03 +00:00
|
|
|
log.Infof("Connected to new endpoint: %s", logs.MaskCredentialsLogging(s.cfg.currHttpEndpoint.Url))
|
|
|
|
return
|
2022-04-09 01:28:40 +00:00
|
|
|
case <-s.ctx.Done():
|
|
|
|
log.Debug("Received cancelled context,closing existing powchain service")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Forces to retry an execution client connection.
|
|
|
|
func (s *Service) retryExecutionClientConnection(ctx context.Context, err error) {
|
2022-12-13 23:13:49 +00:00
|
|
|
s.runError = errors.Wrap(err, "retryExecutionClientConnection")
|
2022-04-09 01:28:40 +00:00
|
|
|
s.updateConnectedETH1(false)
|
|
|
|
// Back off for a while before redialing.
|
|
|
|
time.Sleep(backOffPeriod)
|
2022-04-28 13:09:03 +00:00
|
|
|
currClient := s.rpcClient
|
2022-04-09 01:28:40 +00:00
|
|
|
if err := s.setupExecutionClientConnections(ctx, s.cfg.currHttpEndpoint); err != nil {
|
2022-12-13 23:13:49 +00:00
|
|
|
s.runError = errors.Wrap(err, "setupExecutionClientConnections")
|
2022-04-09 01:28:40 +00:00
|
|
|
return
|
|
|
|
}
|
2022-04-28 13:09:03 +00:00
|
|
|
// Close previous client, if connection was successful.
|
2022-05-19 02:53:24 +00:00
|
|
|
if currClient != nil {
|
|
|
|
currClient.Close()
|
|
|
|
}
|
2022-04-09 01:28:40 +00:00
|
|
|
// Reset run error in the event of a successful connection.
|
|
|
|
s.runError = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initializes an RPC connection with authentication headers.
|
|
|
|
func (s *Service) newRPCClientWithAuth(ctx context.Context, endpoint network.Endpoint) (*gethRPC.Client, error) {
|
|
|
|
// Need to handle ipc and http
|
|
|
|
var client *gethRPC.Client
|
|
|
|
u, err := url.Parse(endpoint.Url)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
switch u.Scheme {
|
|
|
|
case "http", "https":
|
2023-02-10 06:19:15 +00:00
|
|
|
client, err = gethRPC.DialOptions(ctx, endpoint.Url, gethRPC.WithHTTPClient(endpoint.HttpClient()))
|
2022-04-09 01:28:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-08-26 23:05:28 +00:00
|
|
|
case "", "ipc":
|
2022-04-09 01:28:40 +00:00
|
|
|
client, err = gethRPC.DialIPC(ctx, endpoint.Url)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("no known transport for URL scheme %q", u.Scheme)
|
|
|
|
}
|
|
|
|
if endpoint.Auth.Method != authorization.None {
|
|
|
|
header, err := endpoint.Auth.ToHeaderValue()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
client.SetHeader("Authorization", header)
|
|
|
|
}
|
2022-08-29 23:34:29 +00:00
|
|
|
for _, h := range s.cfg.headers {
|
|
|
|
if h != "" {
|
|
|
|
keyValue := strings.Split(h, "=")
|
|
|
|
if len(keyValue) < 2 {
|
|
|
|
log.Warnf("Incorrect HTTP header flag format. Skipping %v", keyValue[0])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
client.SetHeader(keyValue[0], strings.Join(keyValue[1:], "="))
|
|
|
|
}
|
|
|
|
}
|
2022-04-09 01:28:40 +00:00
|
|
|
return client, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks the chain ID of the execution client to ensure
|
|
|
|
// it matches local parameters of what Prysm expects.
|
|
|
|
func ensureCorrectExecutionChain(ctx context.Context, client *ethclient.Client) error {
|
|
|
|
cID, err := client.ChainID(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
wantChainID := params.BeaconConfig().DepositChainID
|
|
|
|
if cID.Uint64() != wantChainID {
|
|
|
|
return fmt.Errorf("wanted chain ID %d, got %d", wantChainID, cID.Uint64())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|