-
Notifications
You must be signed in to change notification settings - Fork 0
/
rewriter.go
310 lines (271 loc) · 7.99 KB
/
rewriter.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package proxyd
import (
"encoding/json"
"errors"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)
type RewriteContext struct {
latest hexutil.Uint64
safe hexutil.Uint64
finalized hexutil.Uint64
maxBlockRange uint64
}
type RewriteResult uint8
const (
// RewriteNone means request should be forwarded as-is
RewriteNone RewriteResult = iota
// RewriteOverrideError means there was an error attempting to rewrite
RewriteOverrideError
// RewriteOverrideRequest means the modified request should be forwarded to the backend
RewriteOverrideRequest
// RewriteOverrideResponse means to skip calling the backend and serve the overridden response
RewriteOverrideResponse
)
var (
ErrRewriteBlockOutOfRange = errors.New("block is out of range")
ErrRewriteRangeTooLarge = errors.New("block range is too large")
)
// RewriteTags modifies the request and the response based on block tags
func RewriteTags(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
rw, err := RewriteResponse(rctx, req, res)
if rw == RewriteOverrideResponse {
return rw, err
}
return RewriteRequest(rctx, req, res)
}
// RewriteResponse modifies the response object to comply with the rewrite context
// after the method has been called at the backend
// RewriteResult informs the decision of the rewrite
func RewriteResponse(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
switch req.Method {
case "eth_blockNumber":
res.Result = rctx.latest
return RewriteOverrideResponse, nil
}
return RewriteNone, nil
}
// RewriteRequest modifies the request object to comply with the rewrite context
// before the method has been called at the backend
// it returns false if nothing was changed
func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
switch req.Method {
case "eth_getLogs",
"eth_newFilter":
return rewriteRange(rctx, req, res, 0)
case "debug_getRawReceipts", "consensus_getReceipts":
return rewriteParam(rctx, req, res, 0, true, false)
case "eth_getBalance",
"eth_getCode",
"eth_getTransactionCount",
"eth_call":
return rewriteParam(rctx, req, res, 1, false, true)
case "eth_getStorageAt",
"eth_getProof":
return rewriteParam(rctx, req, res, 2, false, true)
case "eth_getBlockTransactionCountByNumber",
"eth_getUncleCountByBlockNumber",
"eth_getBlockByNumber",
"eth_getTransactionByBlockNumberAndIndex",
"eth_getUncleByBlockNumberAndIndex":
return rewriteParam(rctx, req, res, 0, false, false)
}
return RewriteNone, nil
}
func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool, blockNrOrHash bool) (RewriteResult, error) {
var p []interface{}
err := json.Unmarshal(req.Params, &p)
if err != nil {
return RewriteOverrideError, err
}
// we assume latest if the param is missing,
// and we don't rewrite if there is not enough params
if len(p) == pos && !required {
p = append(p, "latest")
} else if len(p) <= pos {
return RewriteNone, nil
}
// support for https://eips.ethereum.org/EIPS/eip-1898
var val interface{}
var rw bool
if blockNrOrHash {
bnh, err := remarshalBlockNumberOrHash(p[pos])
if err != nil {
// fallback to string
s, ok := p[pos].(string)
if ok {
val, rw, err = rewriteTag(rctx, s)
if err != nil {
return RewriteOverrideError, err
}
} else {
return RewriteOverrideError, errors.New("expected BlockNumberOrHash or string")
}
} else {
val, rw, err = rewriteTagBlockNumberOrHash(rctx, bnh)
if err != nil {
return RewriteOverrideError, err
}
}
} else {
s, ok := p[pos].(string)
if !ok {
return RewriteOverrideError, errors.New("expected string")
}
val, rw, err = rewriteTag(rctx, s)
if err != nil {
return RewriteOverrideError, err
}
}
if rw {
p[pos] = val
paramRaw, err := json.Marshal(p)
if err != nil {
return RewriteOverrideError, err
}
req.Params = paramRaw
return RewriteOverrideRequest, nil
}
return RewriteNone, nil
}
func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) {
var p []map[string]interface{}
err := json.Unmarshal(req.Params, &p)
if err != nil {
return RewriteOverrideError, err
}
// if either fromBlock or toBlock is defined, default the other to "latest" if unset
_, hasFrom := p[pos]["fromBlock"]
_, hasTo := p[pos]["toBlock"]
if hasFrom && !hasTo {
p[pos]["toBlock"] = "latest"
} else if hasTo && !hasFrom {
p[pos]["fromBlock"] = "latest"
}
modifiedFrom, err := rewriteTagMap(rctx, p[pos], "fromBlock")
if err != nil {
return RewriteOverrideError, err
}
modifiedTo, err := rewriteTagMap(rctx, p[pos], "toBlock")
if err != nil {
return RewriteOverrideError, err
}
if rctx.maxBlockRange > 0 && (hasFrom || hasTo) {
from, err := blockNumber(p[pos], "fromBlock", uint64(rctx.latest))
if err != nil {
return RewriteOverrideError, err
}
to, err := blockNumber(p[pos], "toBlock", uint64(rctx.latest))
if err != nil {
return RewriteOverrideError, err
}
if to-from > rctx.maxBlockRange {
return RewriteOverrideError, ErrRewriteRangeTooLarge
}
}
// if any of the fields the request have been changed, re-marshal the params
if modifiedFrom || modifiedTo {
paramsRaw, err := json.Marshal(p)
req.Params = paramsRaw
if err != nil {
return RewriteOverrideError, err
}
return RewriteOverrideRequest, nil
}
return RewriteNone, nil
}
func blockNumber(m map[string]interface{}, key string, latest uint64) (uint64, error) {
current, ok := m[key].(string)
if !ok {
return 0, errors.New("expected string")
}
// the latest/safe/finalized tags are already replaced by rewriteTag
if current == "earliest" {
return 0, nil
}
if current == "pending" {
return latest + 1, nil
}
return hexutil.DecodeUint64(current)
}
func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (bool, error) {
if m[key] == nil || m[key] == "" {
return false, nil
}
current, ok := m[key].(string)
if !ok {
return false, errors.New("expected string")
}
val, rw, err := rewriteTag(rctx, current)
if err != nil {
return false, err
}
if rw {
m[key] = val
return true, nil
}
return false, nil
}
func remarshalBlockNumberOrHash(current interface{}) (*rpc.BlockNumberOrHash, error) {
jv, err := json.Marshal(current)
if err != nil {
return nil, err
}
var bnh rpc.BlockNumberOrHash
err = bnh.UnmarshalJSON(jv)
if err != nil {
return nil, err
}
return &bnh, nil
}
func rewriteTag(rctx RewriteContext, current string) (string, bool, error) {
bnh, err := remarshalBlockNumberOrHash(current)
if err != nil {
return "", false, err
}
// this is a hash, not a block
if bnh.BlockNumber == nil {
return current, false, nil
}
switch *bnh.BlockNumber {
case rpc.PendingBlockNumber,
rpc.EarliestBlockNumber:
return current, false, nil
case rpc.FinalizedBlockNumber:
return rctx.finalized.String(), true, nil
case rpc.SafeBlockNumber:
return rctx.safe.String(), true, nil
case rpc.LatestBlockNumber:
return rctx.latest.String(), true, nil
default:
if bnh.BlockNumber.Int64() > int64(rctx.latest) {
return "", false, ErrRewriteBlockOutOfRange
}
}
return current, false, nil
}
func rewriteTagBlockNumberOrHash(rctx RewriteContext, current *rpc.BlockNumberOrHash) (*rpc.BlockNumberOrHash, bool, error) {
// this is a hash, not a block number
if current.BlockNumber == nil {
return current, false, nil
}
switch *current.BlockNumber {
case rpc.PendingBlockNumber,
rpc.EarliestBlockNumber:
return current, false, nil
case rpc.FinalizedBlockNumber:
bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.finalized))
return &bn, true, nil
case rpc.SafeBlockNumber:
bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.safe))
return &bn, true, nil
case rpc.LatestBlockNumber:
bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.latest))
return &bn, true, nil
default:
if current.BlockNumber.Int64() > int64(rctx.latest) {
return nil, false, ErrRewriteBlockOutOfRange
}
}
return current, false, nil
}