synapsecns/sanguine

View on GitHub
ethergo/chain/client/lifecycle.go

Summary

Maintainability
D
3 days
Test Coverage
package client

import (
    "context"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/params"
    "github.com/ethereum/go-ethereum/rpc"
    "github.com/lmittmann/w3/w3types"
    "github.com/synapsecns/sanguine/ethergo/chain/client/near"
)

// Permitter handles permit acquires/releases for a lifecycle client.
// this is useful for implementing generic pre/post request logic on
// different kinds of clients that conform to the evm client.
//
//go:generate go run github.com/vektra/mockery/v2 --name Permitter --output ./mocks --case=underscore
type Permitter interface {
    AcquirePermit(ctx context.Context) (err error)
    // ReleasePermit releases a permit
    ReleasePermit()
}

// LifecycleClient is an evm client that acquires a permit upon request start and releases after the request.
// this logic can be implemented however the caller needs.
type LifecycleClient struct {
    underlyingClient EVMClient
    Permitter
    // chainID is the chain id used for the client
    chainID *big.Int
    // requestTimeout gets the request timeout.
    requestTimeout time.Duration
}

// NewLifecycleClient creates a new lifecyle client from an underlying client and a permitter.
// requestTimeout is the maximum amount of time to wait for any request. This does not include
// the acquirePermit/releasePermit time.
func NewLifecycleClient(client EVMClient, chainID *big.Int, permitter Permitter, requestTimeout time.Duration) LifecycleClient {
    return LifecycleClient{
        underlyingClient: client,
        Permitter:        permitter,
        chainID:          chainID,
        requestTimeout:   requestTimeout,
    }
}

// CallContract calls contract on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) (contractResponse []byte, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return contractResponse, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.CallContract(requestCtx, call, blockNumber)
}

// PendingCallContract calls contract on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) PendingCallContract(ctx context.Context, call ethereum.CallMsg) (contractResponse []byte, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return contractResponse, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.PendingCallContract(requestCtx, call)
}

// PendingCodeAt calls PendingCodeAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) PendingCodeAt(ctx context.Context, account common.Address) (codeResponse []byte, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return codeResponse, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.PendingCodeAt(requestCtx, account)
}

// PendingBalanceAt calls PendingBalanceAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) PendingBalanceAt(ctx context.Context, account common.Address) (pendingBalance *big.Int, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return pendingBalance, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.PendingBalanceAt(requestCtx, account)
}

// PendingStorageAt calls PendingStorageAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) (pendingStorage []byte, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return pendingStorage, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.PendingStorageAt(requestCtx, account, key)
}

// PendingNonceAt calls PendingNonceAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) PendingNonceAt(ctx context.Context, account common.Address) (pendingNonce uint64, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return pendingNonce, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.PendingNonceAt(requestCtx, account)
}

// PendingTransactionCount calls PendingTransactionCount on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) PendingTransactionCount(ctx context.Context) (count uint, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return count, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.PendingTransactionCount(requestCtx)
}

// NetworkID calls NetworkID on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) NetworkID(ctx context.Context) (id *big.Int, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return id, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.NetworkID(requestCtx)
}

// SyncProgress calls SyncProgress on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) SyncProgress(ctx context.Context) (syncProgress *ethereum.SyncProgress, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return nil, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.SyncProgress(requestCtx)
}

// SuggestGasPrice calls SuggestGasPrice on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) SuggestGasPrice(ctx context.Context) (gasPrice *big.Int, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return gasPrice, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.SuggestGasPrice(requestCtx)
}

// EstimateGas calls EstimateGas on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return gas, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.EstimateGas(requestCtx, call)
}

// SendTransaction calls SendTransaction on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) SendTransaction(ctx context.Context, tx *types.Transaction) (err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.SendTransaction(requestCtx, tx)
}

// FilterLogs calls FilterLogs on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) (logs []types.Log, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return logs, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.FilterLogs(requestCtx, query)
}

// SubscribeFilterLogs calls SubscribeFilterLogs on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (sub ethereum.Subscription, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return sub, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.SubscribeFilterLogs(requestCtx, query, ch)
}

// BlockByHash calls BlockByHash on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) BlockByHash(ctx context.Context, hash common.Hash) (block *types.Block, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return block, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    if m.chainID.Cmp(AuroraMainnet.ChainID) == 0 {
        return near.BlockByHash(ctx, m, hash)
    }

    return m.underlyingClient.BlockByHash(requestCtx, hash)
}

// BlockByNumber calls BlockByNumber on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) BlockByNumber(ctx context.Context, number *big.Int) (block *types.Block, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return block, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    if m.chainID.Cmp(AuroraMainnet.ChainID) == 0 {
        return near.BlockByNumber(ctx, m, number)
    }

    return m.underlyingClient.BlockByNumber(requestCtx, number)
}

// HeaderByHash calls HeaderByHash on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) HeaderByHash(ctx context.Context, hash common.Hash) (header *types.Header, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return header, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.HeaderByHash(requestCtx, hash)
}

// HeaderByNumber calls HeaderByNumber on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) HeaderByNumber(ctx context.Context, number *big.Int) (header *types.Header, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return header, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.HeaderByNumber(requestCtx, number)
}

// TransactionCount calls TransactionCount on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) TransactionCount(ctx context.Context, blockHash common.Hash) (txCount uint, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return txCount, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.TransactionCount(requestCtx, blockHash)
}

// TransactionInBlock calls TransactionInBlock on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (tx *types.Transaction, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return tx, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.TransactionInBlock(requestCtx, blockHash, index)
}

// SubscribeNewHead calls SubscribeNewHead on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (sub ethereum.Subscription, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return sub, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.SubscribeNewHead(requestCtx, ch)
}

// TransactionByHash calls TransactionByHash on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return tx, isPending, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.TransactionByHash(requestCtx, txHash)
}

// TransactionReceipt calls TransactionReceipt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return receipt, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.TransactionReceipt(requestCtx, txHash)
}

// BalanceAt calls BalanceAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (balance *big.Int, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return balance, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.BalanceAt(requestCtx, account, blockNumber)
}

// StorageAt calls StorageAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) (storage []byte, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return storage, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.StorageAt(requestCtx, account, key, blockNumber)
}

// BlockNumber gets the latest block number
//
//nolint:wrapcheck
func (m LifecycleClient) BlockNumber(ctx context.Context) (_ uint64, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return 0, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.BlockNumber(requestCtx)
}

// CodeAt calls CodeAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) (codeAt []byte, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return codeAt, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.CodeAt(requestCtx, account, blockNumber)
}

// SuggestGasTipCap gets the suggested gas tip for a chain.
//
//nolint:wrapcheck
func (m LifecycleClient) SuggestGasTipCap(ctx context.Context) (tip *big.Int, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return tip, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.SuggestGasTipCap(requestCtx)
}

// Web3Version calls Web3Version on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) Web3Version(ctx context.Context) (version string, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return version, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.Web3Version(requestCtx)
}

// CallContext calls CallContext on the underlying client. Note: this will bypass the rate-limiter.
//
//nolint:wrapcheck
func (m LifecycleClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) (err error) {
    return m.underlyingClient.CallContext(ctx, result, method, args...)
}

// BatchCallContext calls BatchCallContext on the underlying client. Note: this will bypass the rate-limiter.
//
//nolint:wrapcheck
func (m LifecycleClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
    return m.underlyingClient.BatchCallContext(ctx, b)
}

// NonceAt calls NonceAt on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (nonce uint64, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return nonce, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.NonceAt(requestCtx, account, blockNumber)
}

// ChainConfig calls ChainConfig on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) ChainConfig() *params.ChainConfig {
    return ConfigFromID(m.chainID)
}

// ChainID calls ChainID on the underlying client.
//
//nolint:wrapcheck
func (m LifecycleClient) ChainID(ctx context.Context) (chainID *big.Int, err error) {
    err = m.AcquirePermit(ctx)
    if err != nil {
        return nil, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.ChainID(requestCtx)
}

// BatchContext calls BatchContext on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) BatchContext(ctx context.Context, calls ...w3types.Caller) error {
    err := m.AcquirePermit(ctx)
    if err != nil {
        return err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.BatchContext(requestCtx, calls...)
}

// FeeHistory calls FeeHistory on the underlying client
//
//nolint:wrapcheck
func (m LifecycleClient) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
    err := m.AcquirePermit(ctx)
    if err != nil {
        return nil, err
    }
    defer m.ReleasePermit()

    requestCtx, cancel := context.WithTimeout(ctx, m.requestTimeout)
    defer cancel()

    return m.underlyingClient.FeeHistory(requestCtx, blockCount, lastBlock, rewardPercentiles)
}