synapsecns/sanguine

View on GitHub
ethergo/chain/chain_impl.go

Summary

Maintainability
A
0 mins
Test Coverage
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
}