lazy_dial.go 2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
package client

import (
	"context"
	"errors"
	"fmt"
	"sync"

	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/rpc"
)

13
// lazyRPC defers connection attempts to the usage of the RPC.
14 15
// This allows a websocket connection to be established lazily.
// The underlying RPC should handle reconnects.
16
type lazyRPC struct {
17 18 19 20 21 22
	// mutex to prevent more than one active dial attempt at a time.
	mu sync.Mutex
	// inner is the actual RPC client.
	// It is initialized once. The underlying RPC handles reconnections.
	inner RPC
	// options to initialize `inner` with.
23
	cfg      rpcConfig
24 25 26 27 28 29
	endpoint string
	// If we have not initialized `inner` yet,
	// do not try to do so after closing the client.
	closed bool
}

30
var _ RPC = (*lazyRPC)(nil)
31

32 33 34
func newLazyRPC(endpoint string, cfg rpcConfig) *lazyRPC {
	return &lazyRPC{
		cfg:      cfg,
35 36 37 38
		endpoint: endpoint,
	}
}

39
func (l *lazyRPC) dial(ctx context.Context) error {
40 41 42 43 44 45 46 47
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.inner != nil {
		return nil
	}
	if l.closed {
		return errors.New("cannot dial RPC, client was already closed")
	}
48
	underlying, err := rpc.DialOptions(ctx, l.endpoint, l.cfg.gethRPCOptions...)
49 50 51
	if err != nil {
		return fmt.Errorf("failed to dial: %w", err)
	}
52
	l.inner = wrapClient(underlying, l.cfg)
53 54 55
	return nil
}

56
func (l *lazyRPC) Close() {
57 58 59 60 61 62 63 64
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.inner != nil {
		l.inner.Close()
	}
	l.closed = true
}

65
func (l *lazyRPC) CallContext(ctx context.Context, result any, method string, args ...any) error {
66 67 68 69 70 71
	if err := l.dial(ctx); err != nil {
		return err
	}
	return l.inner.CallContext(ctx, result, method, args...)
}

72
func (l *lazyRPC) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error {
73 74 75 76 77 78
	if err := l.dial(ctx); err != nil {
		return err
	}
	return l.inner.BatchCallContext(ctx, b)
}

79
func (l *lazyRPC) Subscribe(ctx context.Context, namespace string, channel any, args ...any) (ethereum.Subscription, error) {
80 81 82
	if err := l.dial(ctx); err != nil {
		return nil, err
	}
83
	return l.inner.Subscribe(ctx, namespace, channel, args...)
84
}