ethergo/chain/chain_impl.go
package chain
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/params"
"github.com/pkg/errors"
"github.com/synapsecns/sanguine/ethergo/chain/chainwatcher"
"github.com/synapsecns/sanguine/ethergo/chain/client"
"github.com/synapsecns/sanguine/ethergo/chain/gas"
"github.com/synapsecns/sanguine/ethergo/chain/watcher"
"math/big"
"sync"
)
var _ Chain = &baseChain{}
// baseChain is an extensible struct that implements chain. This is used for interacting with an underlying
// evm rpc.
type baseChain struct {
// ctx: this is used on all non-client methods
//nolint: containedctx
ctx context.Context
// EVMClient is the rpc client used to access the chain
client.MeteredEVMClient
// cfg is the configuration for the chain
cfg *client.Config
// structMux is the mutex
structMux sync.RWMutex
// chainConfig is the chain config
chainConfig *params.ChainConfig
// height watcher keeps track of the current chain tip height
heightWatcher chainwatcher.BlockHeightWatcher
// contractWatcher watches for events on the contract
contractWatcher chainwatcher.ContractWatcher
// chainID is the cached chain id
chainID uint
// cancel is used to close the session
cancel context.CancelFunc
// rpcAddress used by the chain
rpcAddress string
// estimator stores the gas estimator
// Deprecated: use setter
estimator gas.PriceEstimator
// setter is used to set gas on a tx
setter gas.Setter
}
func (b *baseChain) GasSetter() gas.Setter {
return b.setter
}
// Name is the name of the chain.
// Deprecated: do not use.
func (b *baseChain) Name() string {
return ""
}
// Config gets the config for the chain.
func (b *baseChain) Config() *client.Config {
return b.cfg
}
// RPCAddress gets the rpc address the chain is currently connected to.
func (b *baseChain) RPCAddress() string {
return b.rpcAddress
}
// ChainConfig gets the chain config if it's available.
func (b *baseChain) ChainConfig() *params.ChainConfig {
b.structMux.RLock()
defer b.structMux.RUnlock()
return b.chainConfig
}
// Estimator gets the gas estimator.
func (b *baseChain) Estimator() gas.PriceEstimator {
return b.estimator
}
// GetHeightWatcher gets a block height watcher.
func (b *baseChain) GetHeightWatcher() chainwatcher.BlockHeightWatcher {
return b.heightWatcher
}
// SetChainConfig sets the chain config manually. This is used mostly for testing.
func (b *baseChain) SetChainConfig(config *params.ChainConfig) {
b.structMux.Lock()
defer b.structMux.Unlock()
b.chainConfig = config
}
// NewFromMeteredClient creaates a new client from a metered evm client.
func NewFromMeteredClient(ctx context.Context, config *client.Config, meteredClient client.MeteredEVMClient) (chain Chain, err error) {
b := baseChain{}
b.ctx, b.cancel = context.WithCancel(ctx)
b.cfg = config
if len(config.RPCUrl) > 0 {
b.rpcAddress = config.RPCUrl[0]
}
logger.Debugf("creating eth client %s", config.RPCUrl)
// TODO, correctly set chain id here, only affects metrics
b.MeteredEVMClient = meteredClient
b.chainID = uint(config.ChainID)
b.chainConfig = client.ConfigFromID(b.GetBigChainID())
// initialize monitor/estimator
b.heightWatcher = watcher.NewBlockHeightWatcher(ctx, uint64(b.chainID), b.MeteredEVMClient)
b.contractWatcher = watcher.NewContractWatcher(ctx, &b, b.heightWatcher, b.cfg.RequiredConfirmations)
b.estimator = gas.NewGasPriceEstimator(ctx, &b)
b.setter = gas.NewGasSetter(ctx, &b)
return &b, nil
}
// NewFromURL creates a new client from a url.
//
//nolint:ireturn
func NewFromURL(ctx context.Context, url string) (Chain, error) {
tmpClient, err := client.NewClient(ctx, url)
if err != nil {
return nil, fmt.Errorf("could not create new client at %s: %w", url, err)
}
chainID, err := tmpClient.ChainID(ctx)
if err != nil {
return nil, fmt.Errorf("could not get chain id: %w", err)
}
meteredClient := client.NewMeteredClient(tmpClient, chainID, url, nil)
return NewFromMeteredClient(ctx, &client.Config{
RPCUrl: []string{url},
ChainID: int(chainID.Int64()),
}, meteredClient)
}
// NewFromClient gets a chain from client.
func NewFromClient(ctx context.Context, config *client.Config, evmClient client.EVMClient) (chain Chain, err error) {
rpcURL := ""
if len(config.RPCUrl) > 0 {
rpcURL = config.RPCUrl[0]
}
meteredClient := client.NewMeteredClient(evmClient, big.NewInt(0), rpcURL, config.LimiterConfig)
return NewFromMeteredClient(ctx, config, meteredClient)
}
// New creates a new rpc client for querying an evm-based rpc server and attempts to connect to the chain.
func New(ctx context.Context, config *client.Config) (evmClient Chain, err error) {
if len(config.RPCUrl) == 0 {
return nil, errors.New("could not get ws url")
}
// RPCUrl gets the rpc url from the client
baseClient, err := client.NewClientFromChainID(ctx, config.RPCUrl[0], big.NewInt(int64(config.ChainID)))
if err != nil {
return nil, fmt.Errorf("could not create pool client: %w", err)
}
return NewFromClient(ctx, config, baseClient)
}
// ChainName gets the chain name from the config.
func (b *baseChain) ChainName() string {
return b.Name()
}
// GetChainID gets the cached chain id.
func (b *baseChain) GetChainID() uint {
return b.chainID
}
// GetBigChainID gets the chain id as a big int.
func (b *baseChain) GetBigChainID() *big.Int {
return big.NewInt(int64(b.chainID))
}
// ListenOnContract registers an event listener on a contract address
// the events emitted by the a contractAddress after confirmations (defined in the config).
func (b *baseChain) ListenOnContract(ctx context.Context, contractAddress string, eventLog chan interface{}) error {
err := b.contractWatcher.ListenOnContract(ctx, contractAddress, eventLog)
if err != nil {
return fmt.Errorf("could not listen on contract: %w", err)
}
return nil
}