/
eth_rpc_client.go
executable file
·162 lines (144 loc) · 5.56 KB
/
eth_rpc_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package ethrpcclient
import (
"context"
"math/big"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/map-bgp/browserbook/browserbook-mesh/common/types"
"github.com/map-bgp/browserbook/browserbook-mesh/ethereum/ratelimit"
)
// Client defines the methods needed to satisfy the subsdet of ETH JSON-RPC client
// methods used by Mesh
type Client interface {
HeaderByHash(ctx context.Context, hash common.Hash) (*ethtypes.Header, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.MiniHeader, error)
FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]ethtypes.Log, error)
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
GetRateLimitDroppedRequests() int64
}
// client is a Client through which _all_ Ethereum JSON-RPC requests should be routed through. It
// enforces a max requestTimeout and also rate-limits requests
type client struct {
// rpcClient is the underlying RPC client or provider
rpcClient ethclient.RPCClient
// client is the higher level Ethereum RPC client with lots of helper methods
// for converting Go types to JSON and vice versa.
client *ethclient.Client
requestTimeout time.Duration
rateLimiter ratelimit.RateLimiter
// rateLimitDroppedRequests counts the number of requests that had their context cancelled or expire
// and were therefore never granted
rateLimitDroppedRequests int64
}
// New returns a new instance of client
func New(rpcClient ethclient.RPCClient, requestTimeout time.Duration, rateLimiter ratelimit.RateLimiter) (Client, error) {
ethClient := ethclient.NewClient(rpcClient)
return &client{
client: ethClient,
rpcClient: rpcClient,
requestTimeout: requestTimeout,
rateLimiter: rateLimiter,
}, nil
}
// CallContext performs a JSON-RPC call with the given arguments. If the context is
// canceled before the call has successfully returned, CallContext returns immediately.
//
// The result must be a pointer so that package json can unmarshal into it. You
// can also pass nil, in which case the result is ignored.
func (ec *client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
err := ec.rateLimiter.Wait(ctx)
if err != nil {
atomic.AddInt64(&ec.rateLimitDroppedRequests, 1)
// Context cancelled or deadline exceeded
return err
}
ctx, cancel := context.WithTimeout(ctx, ec.requestTimeout)
defer cancel()
return ec.rpcClient.CallContext(ctx, &result, method, args...)
}
// HeaderByHash fetches a block header by its block hash. If no block exists with this number it will return
// a `ethereum.NotFound` error.
func (ec *client) HeaderByHash(ctx context.Context, hash common.Hash) (*ethtypes.Header, error) {
err := ec.rateLimiter.Wait(ctx)
if err != nil {
atomic.AddInt64(&ec.rateLimitDroppedRequests, 1)
// Context cancelled or deadline exceeded
return nil, err
}
ctx, cancel := context.WithTimeout(ctx, ec.requestTimeout)
defer cancel()
header, err := ec.client.HeaderByHash(ctx, hash)
if err != nil {
return nil, err
}
return header, nil
}
func (ec *client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.MiniHeader, error) {
err := ec.rateLimiter.Wait(ctx)
if err != nil {
atomic.AddInt64(&ec.rateLimitDroppedRequests, 1)
// Context cancelled or deadline exceeded
return nil, err
}
header, err := ec.client.HeaderByNumber(ctx, number)
if err != nil {
return nil, err
}
miniHeader := &types.MiniHeader{
Hash: header.Hash(),
Parent: header.ParentHash,
Number: header.Number,
Timestamp: time.Unix(int64(header.Time), 0),
}
return miniHeader, nil
}
// CodeAt returns the code of the given account. This is needed to differentiate
// between contract internal errors and the local chain being out of sync.
func (ec *client) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
err := ec.rateLimiter.Wait(ctx)
if err != nil {
atomic.AddInt64(&ec.rateLimitDroppedRequests, 1)
// Context cancelled or deadline exceeded
return []byte{}, err
}
ctx, cancel := context.WithTimeout(ctx, ec.requestTimeout)
defer cancel()
return ec.client.CodeAt(ctx, contract, blockNumber)
}
// CallContract executes an Ethereum contract call with the specified data as the input.
func (ec *client) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
err := ec.rateLimiter.Wait(ctx)
if err != nil {
atomic.AddInt64(&ec.rateLimitDroppedRequests, 1)
// Context cancelled or deadline exceeded
return []byte{}, err
}
ctx, cancel := context.WithTimeout(ctx, ec.requestTimeout)
defer cancel()
return ec.client.CallContract(ctx, call, blockNumber)
}
// FilterLogs returns the logs that satisfy the supplied filter query.
func (ec *client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]ethtypes.Log, error) {
err := ec.rateLimiter.Wait(ctx)
if err != nil {
atomic.AddInt64(&ec.rateLimitDroppedRequests, 1)
// Context cancelled or deadline exceeded
return nil, err
}
ctx, cancel := context.WithTimeout(ctx, ec.requestTimeout)
defer cancel()
logs, err := ec.client.FilterLogs(ctx, q)
if err != nil {
return nil, err
}
return logs, nil
}
func (ec *client) GetRateLimitDroppedRequests() int64 {
return ec.rateLimitDroppedRequests
}