ethhelpers

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2023 License: LGPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package ethhelpers provides helper methods for the Go Ethereum client library.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BigIntAsInt64

func BigIntAsInt64(v *big.Int) (int64, bool)

func BigIntAsInt64OrDefaultValue

func BigIntAsInt64OrDefaultValue(v *big.Int, defaultValue int64) int64

func BigIntAsInt64OrZero

func BigIntAsInt64OrZero(v *big.Int) int64

func BigIntAsUint64

func BigIntAsUint64(v *big.Int) (uint64, bool)

func BigIntAsUint64OrDefaultValue

func BigIntAsUint64OrDefaultValue(v *big.Int, defaultValue uint64) uint64

func BigIntAsUint64OrZero

func BigIntAsUint64OrZero(v *big.Int) uint64

func BigIntAsUint64OrZeroIfNil added in v0.1.1

func BigIntAsUint64OrZeroIfNil(v *big.Int) (uint64, bool)

func ContextWithClient

func ContextWithClient(ctx context.Context, client Client) context.Context

ContextWithClient creates a new context which contains an ethhelpers.Client.

The context will return the client when calling ClientFromContext and other compatible methods.

Note that there can only be one client type in addition to the RPC client stored in the context, other client interface variants are stored using the same context key.

func ContextWithClientsFromRPCClient

func ContextWithClientsFromRPCClient(ctx context.Context, rpcClient *rpc.Client) context.Context

ContextWithClients creates a new context which contains both the RPC client and a newly created ethclient client.

The context will return the clients when calling ClientFromContext and RPCClientFromContext.

func ContextWithConfig

func ContextWithConfig(ctx context.Context, config Config) context.Context

ContextWithConfig creates a new context which contains an ethhelpers config.

The context will return the config when calling ConfigFromContext.

func ContextWithRPCClient

func ContextWithRPCClient(ctx context.Context, rpcClient *rpc.Client) context.Context

ContextWithRPCClient creates a new context which contains an RPC client.

The context will return the client when calling RPCClientFromContext.

func DefaultErrorHandlerWithMessages

func DefaultErrorHandlerWithMessages(msgHandler func(txHash common.Hash, msg string)) func(txHash common.Hash, err error) error

DefaultErrorHandlerWithLogger is used by e.g. WaitForTransactionReceipt to decide if the error is a temporary connection / receipt not found issue and it should retry.

This is a work-in-progress.

func RPCClientFromContext

func RPCClientFromContext(ctx context.Context) (*rpc.Client, bool)

RPCClientFromContext retrieves an *rpc.Client from the context, if any.

func RetryIfTemporaryError added in v0.1.2

func RetryIfTemporaryError(unknownError func(context.Context, error) error) func(context.Context, func(context.Context) error) error

func SubscribeFilterLogsWithHTTP added in v0.1.1

func SubscribeFilterLogsWithHTTP(callerCtx context.Context, opts *HTTPSubscriberOptions) (ethereum.Subscription, error)

The context argument cancels the RPC request that sets up the subscription but has no effect on the subscription after Subscribe has returned.

Subscribers should be using the same underlying go-ethereum rpc client connection as the block number ticker to ensure there are no race-conditions.

To conform to the go-ethereum SubscriberFilterLogs api the ticker should only return current, and not historic, block numbers.

The current block number is requested before the subscription is returned.

func WaitForTransactionReceipt

func WaitForTransactionReceipt(ctx context.Context, options WaitForTransactionReceiptOptions) (<-chan ReceiptOrError, func())

WaitForTransactionReceipt

The result channel always sends either the receipt or error, and does not close.

The caller must ensure that either the context is canceled or the returned cancel function is called.

Example
ctx := context.Background()

sim, closeSim := newExampleDefaultSimulatedBackend()
defer closeSim()

signedTx, err := sendTestTransaction(ctx, sim)
if err != nil {
	log.Fatal(err)
}

resultChan, cancel := ethhelpers.WaitForTransactionReceipt(ctx, ethhelpers.WaitForTransactionReceiptOptions{
	Client: sim.Backend,
	TxHash: signedTx.Hash(),
	ErrorHandler: ethhelpers.DefaultErrorHandlerWithMessages(func(txHash common.Hash, msg string) {
	}),
})
defer cancel()

sim.Backend.Commit()

result := <-resultChan
if result.Error != nil {
	log.Fatal(result.Error)
}

_ = result.Receipt
Output:

Types

type BlockNumber added in v0.1.3

type BlockNumber struct {
	BlockNumber uint64
	Timestamp   time.Time
	Truncated   bool
}

type BlockNumberReader added in v0.1.1

type BlockNumberReader interface {
	BlockNumber(ctx context.Context) (uint64, error)
}

type BlockNumberTicker added in v0.1.1

type BlockNumberTicker interface {
	// Wait returns a channel that emits BlockNumber, and must be called before
	// each read.
	//
	// Reading from previously returned channels is not supported, and may in
	// some edge cases cause the new channel to skip a tick.
	//
	// To make sure the block number returned is up-to-date, call Wait rather
	// than reuse an unread channel.
	//
	// Truncated in the result is true if it was truncated by the window size.
	Wait() <-chan BlockNumber

	// Err returns a channel that emits errors that occur while waiting for block numbers.
	Err() <-chan error

	// CloneFromBlock creates a new ticker that starts from the given block number.
	CloneFromBlock(fromBlock uint64) BlockNumberTicker

	// Stop stops the ticker.
	Stop()
}

BlockNumberTicker is a ticker that emits block numbers.

func NewPeriodicBlockNumberTicker added in v0.1.1

func NewPeriodicBlockNumberTicker(ctx context.Context, client BlockNumberReader, interval time.Duration) BlockNumberTicker

NewPeriodicBlockNumberTicker creates a new block number ticker that ticks at a fixed time interval, starting from the current block number.

func NewPeriodicBlockNumberTickerFromBlock added in v0.1.2

func NewPeriodicBlockNumberTickerFromBlock(ctx context.Context, client BlockNumberReader, interval time.Duration, fromBlock uint64) BlockNumberTicker

NewPeriodicBlockNumberTickerFromBlock creates a new block number ticker that ticks at a fixed time interval, starting from the given block number.

The ticker continues to make BlockNumber request calls after calling Wait if fromBlock was not reached. Therefor the ticker should be manually stopped and/or not used with fromBlock values that are not imminient.

Example
package main

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

	"github.com/ethereum/go-ethereum/core"
	"github.com/rakshasa/go-ethereum-helpers/ethhelpers"
	"github.com/rakshasa/go-ethereum-helpers/ethtesting"

	ethlog "github.com/ethereum/go-ethereum/log"
)

func newExampleDefaultSimulatedBackend() (*ethtesting.SimulatedBackendWithAccounts, func()) {
	oldHandler := ethlog.Root().GetHandler()
	ethlog.Root().SetHandler(ethlog.DiscardHandler())

	sim := ethtesting.NewSimulatedBackendWithAccounts(
		ethtesting.GenesisAccountWithPrivateKey{
			PrivateKey: ethtesting.MockPrivateKey1,
			GenesisAccount: core.GenesisAccount{
				Balance: big.NewInt(10_000_000_000_000_000),
			},
		},
		ethtesting.GenesisAccountWithPrivateKey{
			PrivateKey: ethtesting.MockPrivateKey2,
		},
	)

	return sim, func() {
		sim.Backend.Close()
		ethlog.Root().SetHandler(oldHandler)
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	sim, closeSim := newExampleDefaultSimulatedBackend()
	defer closeSim()

	fromBlock := uint64(3)

	ticker := ethhelpers.NewPeriodicBlockNumberTickerFromBlock(ctx, ethtesting.NewSimulatedClient(sim.Backend), 200*time.Millisecond, fromBlock)
	defer ticker.Stop()

	go func() {
		time.Sleep(50 * time.Millisecond)

		for {
			select {
			case <-time.After(100 * time.Millisecond):
				sim.Backend.Commit()
			case <-ctx.Done():
			}
		}
	}()

	var currentBlock uint64

	for {
		select {
		case bn := <-ticker.Wait():
			currentBlock = bn.BlockNumber

			fmt.Printf("currentBlock: %d\n", currentBlock)

			if currentBlock == 5 {
				time.Sleep(500 * time.Millisecond)
			}
			if currentBlock < 12 {
				continue
			}

		case err := <-ticker.Err():
			fmt.Printf("err: %v\n", err)

			// if check_if_temporary_error {
			// 	ticker.Reset(currentBlock)
			// 	continue
			// }
		}

		return
	}

}
Output:

currentBlock: 3
currentBlock: 5
currentBlock: 10
currentBlock: 11
currentBlock: 13

func NewPeriodicBlockNumberTickerFromBlockWithWindowSize added in v0.1.3

func NewPeriodicBlockNumberTickerFromBlockWithWindowSize(ctx context.Context, client BlockNumberReader, interval time.Duration, fromBlock uint64, windowSize uint64) BlockNumberTicker

func NewPeriodicBlockNumberTickerWithWindowSize added in v0.1.3

func NewPeriodicBlockNumberTickerWithWindowSize(ctx context.Context, client BlockNumberReader, interval time.Duration, windowSize uint64) BlockNumberTicker

type ChainReaderAndTransactionSender added in v0.1.2

type ChainReaderAndTransactionSender interface {
	ethereum.TransactionSender
	ethereum.ChainReader
}

type Client

type Client interface {

	// ethereum.ChainReader
	BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
	BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
	HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
	HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
	TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error)
	TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error)
	SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error)

	// ethereum.ChainStateReader
	BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
	StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
	CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
	NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)

	// ethereum.ChainSyncReader
	SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error)

	// ethereum.ContractCaller
	CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)

	// ethereum.GasEstimator
	EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error)

	// ethereum.GasPricer
	SuggestGasPrice(ctx context.Context) (*big.Int, error)

	// ethereum.LogFilterer
	FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
	SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)

	// ethereum.PendingContractCaller
	PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)

	// ethereum.PendingStateReader
	PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error)
	PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error)
	PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
	PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
	PendingTransactionCount(ctx context.Context) (uint, error)

	// ethereum.TransactionReader
	TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
	TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)

	// ethereum.TransactionSender
	SendTransaction(ctx context.Context, tx *types.Transaction) error

	// BlockNumberReader
	BlockNumber(ctx context.Context) (uint64, error)

	CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
	ChainID(ctx context.Context) (*big.Int, error)
	// TODO: Remove Close?
	Close()
	FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error)
	NetworkID(ctx context.Context) (*big.Int, error)
	PeerCount(ctx context.Context) (uint64, error)
	SuggestGasTipCap(ctx context.Context) (*big.Int, error)
}

Client is an interface that holds the same methods as ethclient.Client.

func ClientFromContext

func ClientFromContext(ctx context.Context) (Client, bool)

ClientFromContext retrieves an interface implementing ethhelpers.Client from the context, if any.

func NewClientWithDefaultHandler added in v0.1.2

func NewClientWithDefaultHandler(defaultHandler func(context.Context, ClientCaller) error) Client

NewClientWithHandlers creates a new client with custom handlers.

The handlers cannot modify the content of the arguments or results, except for overriding the error returned.

Handlers should return nil if it has not changed the error.

func NewClientWithHTTPSubscriptions

func NewClientWithHTTPSubscriptions(client Client, createTicker func(context.Context, uint64) (BlockNumberTicker, error)) Client

func NewClientWithRetry added in v0.1.2

func NewClientWithRetry(client Client, errorHandler func(context.Context, func(context.Context) error) error) Client

type ClientCaller added in v0.1.2

type ClientCaller struct {
	// contains filtered or unexported fields
}

func (ClientCaller) Args added in v0.1.2

func (c ClientCaller) Args() []interface{}

func (ClientCaller) Call added in v0.1.2

func (c ClientCaller) Call(ctx context.Context, client Client) error

func (ClientCaller) Name added in v0.1.2

func (c ClientCaller) Name() string

type Config

type Config struct {
	Endpoint  string
	ChainId   *big.Int // TODO: ChainID
	Contracts ContractContainer
}

func ConfigFromContext

func ConfigFromContext(ctx context.Context) (Config, bool)

ConfigFromContext retrieves an ethhelpers.Config from the context, if any.

type Contract

type Contract interface {
	ChainID() *big.Int
	Address() common.Address
}

func ContractFromConfigInContext

func ContractFromConfigInContext(ctx context.Context, key interface{}) (Contract, bool)

ContractFromConfigInContext retrieves a Contract from the ContractContainer in the Config stored in the context, if any.

func ContractOrNilFromConfigInContext

func ContractOrNilFromConfigInContext(ctx context.Context, key interface{}) Contract

Same as ContractFromConfigInContext, except it returns a nil object if not present.

contractHelper, ok := ethhelpers.ContractOrNilFromConfigInContext(ctx, MyContractKey{}).(*MyContract)
if !ok {
  return fmt.Errorf("missing my contract in context")
}

contract, err := contractHelper.ContractFromContext(ctx)
if err != nil {
  return fmt.Errorf("failed to create my contract: %v", err)
}

type ContractContainer

type ContractContainer struct {
	// contains filtered or unexported fields
}

ContractContainer uses a sync.Map to hold Contract instances.

It is recommended that the ContractContainer is created and populated at the same time as the config and client are created and added to the context.

When suitable, for the key it is recommended to use an empty instance of the bound contract.

For variants of generic contracts it is possible to use a struct type with a member variable to differentiate contract instances.

func NewContractContainer

func NewContractContainer() ContractContainer

func (*ContractContainer) Delete

func (c *ContractContainer) Delete(key interface{})

func (*ContractContainer) Get

func (c *ContractContainer) Get(key interface{}) (Contract, bool)

func (*ContractContainer) GetOrNil

func (c *ContractContainer) GetOrNil(key interface{}) Contract

func (*ContractContainer) MustPut

func (c *ContractContainer) MustPut(key interface{}, value Contract)

func (*ContractContainer) Put

func (c *ContractContainer) Put(key interface{}, value Contract) bool

type FilterLogsReader added in v0.1.1

type FilterLogsReader interface {
	FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
}

type HTTPSubscriberClient added in v0.1.3

type HTTPSubscriberClient interface {
	BlockNumberReader
	FilterLogsReader
}

type HTTPSubscriberOptions added in v0.1.3

type HTTPSubscriberOptions struct {
	Client HTTPSubscriberClient

	// CreateContext returns a context that is used for the subscription, or
	// context.Background() if nil.
	CreateContext func() (context.Context, context.CancelFunc)

	// CreateTicker is a function that creates a block number ticker.
	//
	// The context is canceled when a subscription is unsubscribed or encounters
	// an error, or if the context passed to the function creating the
	// HTTPSubscriber function is canceled before returning a valid
	// subscription.
	//
	// The method is called only once per subscription.
	CreateTicker func(ctx context.Context, fromBlock uint64) (BlockNumberTicker, error)

	FilterQuery ethereum.FilterQuery
	Logs        chan<- types.Log
}

type ReceiptOrError

type ReceiptOrError struct {
	Receipt *types.Receipt
	Error   error
}

TODO: Move to types.

type WaitForTransactionReceiptOptions

type WaitForTransactionReceiptOptions struct {
	// TODO: Add TransactionReaderFromContext, use it if Client is nil.
	Client ethereum.TransactionReader
	TxHash common.Hash

	// ErrorHandler is called when Client.TransactionReceipt returns a non-nil
	// error. The handler is not called if the main context was canceled.
	//
	// If the handler returns a non-nil error then the error is sent to the
	// result channel and no further attempts are made.
	ErrorHandler func(txHash common.Hash, err error) error
}

type WaitTransactionReceipts

type WaitTransactionReceipts struct {
	// contains filtered or unexported fields
}
Example
ctx := context.Background()

sim, closeSim := newExampleDefaultSimulatedBackend()
defer closeSim()

waiter := ethhelpers.NewWaitTransactionReceipts(ctx, func(ctx context.Context, txHash common.Hash) (<-chan ethhelpers.ReceiptOrError, func()) {
	return ethhelpers.WaitForTransactionReceipt(ctx, ethhelpers.WaitForTransactionReceiptOptions{
		Client: sim.Backend,
		TxHash: txHash,
		ErrorHandler: ethhelpers.DefaultErrorHandlerWithMessages(func(txHash common.Hash, msg string) {
		}),
	})
})
defer waiter.Stop()

signedTx1, err := sendTestTransaction(ctx, sim)
if err != nil {
	log.Fatal(err)
}
waiter.Add(signedTx1.Hash())

signedTx2, err := sendTestTransaction(ctx, sim)
if err != nil {
	log.Fatal(err)
}
waiter.Add(signedTx2.Hash())

sim.Backend.Commit()

result := <-waiter.Result()
if result.Error != nil {
	log.Fatal(result.Error)
}

_ = result.Receipt

// waiter.Result() will not return any more results.
Output:

func NewWaitTransactionReceipts

func NewWaitTransactionReceipts(ctx context.Context, addFn func(context.Context, common.Hash) (<-chan ReceiptOrError, func())) *WaitTransactionReceipts

func (*WaitTransactionReceipts) Add

func (w *WaitTransactionReceipts) Add(txHash common.Hash)

TODO: Add different different WatchFor* functions that allow us to use either pooling TransactionReceipt or a websocket subscription.

func (*WaitTransactionReceipts) Result

func (w *WaitTransactionReceipts) Result() <-chan ReceiptOrError

Only read channel once.

func (*WaitTransactionReceipts) Stop

func (w *WaitTransactionReceipts) Stop()

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL