Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
c2f92c2f
Unverified
Commit
c2f92c2f
authored
Jan 21, 2022
by
Matthew Slipper
Committed by
GitHub
Jan 21, 2022
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2029 from ethereum-optimism/inphi/proxyd-blockcache
go/proxyd: Cache block-dependent RPCs
parents
e8ea9654
19f5659f
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
749 additions
and
261 deletions
+749
-261
silent-kids-sing.md
.changeset/silent-kids-sing.md
+5
-0
cache.go
go/proxyd/cache.go
+54
-62
cache_test.go
go/proxyd/cache_test.go
+267
-38
config.go
go/proxyd/config.go
+3
-2
failover_test.go
go/proxyd/integration_tests/failover_test.go
+1
-1
latestblock.go
go/proxyd/latestblock.go
+0
-93
lvc.go
go/proxyd/lvc.go
+87
-0
methods.go
go/proxyd/methods.go
+254
-50
metrics.go
go/proxyd/metrics.go
+18
-2
proxyd.go
go/proxyd/proxyd.go
+60
-13
No files found.
.changeset/silent-kids-sing.md
0 → 100644
View file @
c2f92c2f
---
'
@eth-optimism/proxyd'
:
minor
---
proxyd: Cache block-dependent RPCs
go/proxyd/cache.go
View file @
c2f92c2f
...
...
@@ -2,7 +2,6 @@ package proxyd
import
(
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"github.com/golang/snappy"
...
...
@@ -14,10 +13,9 @@ type Cache interface {
Put
(
ctx
context
.
Context
,
key
string
,
value
string
)
error
}
// assuming an average RPCRes size of 3 KB
const
(
// assuming an average RPCRes size of 3 KB
memoryCacheLimit
=
4096
numBlockConfirmations
=
50
)
type
cache
struct
{
...
...
@@ -76,7 +74,36 @@ func (c *redisCache) Put(ctx context.Context, key string, value string) error {
return
err
}
type
cacheWithCompression
struct
{
cache
Cache
}
func
newCacheWithCompression
(
cache
Cache
)
*
cacheWithCompression
{
return
&
cacheWithCompression
{
cache
}
}
func
(
c
*
cacheWithCompression
)
Get
(
ctx
context
.
Context
,
key
string
)
(
string
,
error
)
{
encodedVal
,
err
:=
c
.
cache
.
Get
(
ctx
,
key
)
if
err
!=
nil
{
return
""
,
err
}
if
encodedVal
==
""
{
return
""
,
nil
}
val
,
err
:=
snappy
.
Decode
(
nil
,
[]
byte
(
encodedVal
))
if
err
!=
nil
{
return
""
,
err
}
return
string
(
val
),
nil
}
func
(
c
*
cacheWithCompression
)
Put
(
ctx
context
.
Context
,
key
string
,
value
string
)
error
{
encodedVal
:=
snappy
.
Encode
(
nil
,
[]
byte
(
value
))
return
c
.
cache
.
Put
(
ctx
,
key
,
string
(
encodedVal
))
}
type
GetLatestBlockNumFn
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
type
GetLatestGasPriceFn
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
type
RPCCache
interface
{
GetRPC
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
...
...
@@ -85,18 +112,23 @@ type RPCCache interface {
type
rpcCache
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
handlers
map
[
string
]
RPCMethodHandler
}
func
newRPCCache
(
cache
Cache
,
getLatestBlockNumFn
GetLatestBlockNumFn
)
RPCCache
{
func
newRPCCache
(
cache
Cache
,
getLatestBlockNumFn
GetLatestBlockNumFn
,
getLatestGasPriceFn
GetLatestGasPriceFn
,
numBlockConfirmations
int
)
RPCCache
{
handlers
:=
map
[
string
]
RPCMethodHandler
{
"eth_chainId"
:
&
StaticRPCMethodHandler
{
"eth_chainId"
},
"net_version"
:
&
StaticRPCMethodHandler
{
"net_version"
},
"eth_getBlockByNumber"
:
&
EthGetBlockByNumberMethod
{
getLatestBlockNumFn
},
"eth_getBlockRange"
:
&
EthGetBlockRangeMethod
{
getLatestBlockNumFn
},
"eth_chainId"
:
&
StaticMethodHandler
{},
"net_version"
:
&
StaticMethodHandler
{},
"eth_getBlockByNumber"
:
&
EthGetBlockByNumberMethodHandler
{
cache
,
getLatestBlockNumFn
,
numBlockConfirmations
},
"eth_getBlockRange"
:
&
EthGetBlockRangeMethodHandler
{
cache
,
getLatestBlockNumFn
,
numBlockConfirmations
},
"eth_blockNumber"
:
&
EthBlockNumberMethodHandler
{
getLatestBlockNumFn
},
"eth_gasPrice"
:
&
EthGasPriceMethodHandler
{
getLatestGasPriceFn
},
"eth_call"
:
&
EthCallMethodHandler
{
cache
,
getLatestBlockNumFn
,
numBlockConfirmations
},
}
return
&
rpcCache
{
cache
:
cache
,
handlers
:
handlers
,
}
return
&
rpcCache
{
cache
:
cache
,
getLatestBlockNumFn
:
getLatestBlockNumFn
,
handlers
:
handlers
}
}
func
(
c
*
rpcCache
)
GetRPC
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
...
...
@@ -104,37 +136,15 @@ func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) {
if
handler
==
nil
{
return
nil
,
nil
}
cacheable
,
err
:=
handler
.
IsCacheable
(
req
)
if
err
!=
nil
{
return
nil
,
err
}
if
!
cacheable
{
res
,
err
:=
handler
.
GetRPCMethod
(
ctx
,
req
)
if
res
!=
nil
{
if
res
==
nil
{
RecordCacheMiss
(
req
.
Method
)
return
nil
,
nil
}
key
:=
handler
.
CacheKey
(
req
)
encodedVal
,
err
:=
c
.
cache
.
Get
(
ctx
,
key
)
if
err
!=
nil
{
return
nil
,
err
}
if
encodedVal
==
""
{
RecordCacheMiss
(
req
.
Method
)
return
nil
,
nil
}
val
,
err
:=
snappy
.
Decode
(
nil
,
[]
byte
(
encodedVal
))
if
err
!=
nil
{
return
nil
,
err
}
}
else
{
RecordCacheHit
(
req
.
Method
)
res
:=
new
(
RPCRes
)
err
=
json
.
Unmarshal
(
val
,
res
)
if
err
!=
nil
{
return
nil
,
err
}
res
.
ID
=
req
.
ID
return
res
,
nil
}
return
res
,
err
}
func
(
c
*
rpcCache
)
PutRPC
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
...
...
@@ -142,23 +152,5 @@ func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error {
if
handler
==
nil
{
return
nil
}
cacheable
,
err
:=
handler
.
IsCacheable
(
req
)
if
err
!=
nil
{
return
err
}
if
!
cacheable
{
return
nil
}
requiresConfirmations
,
err
:=
handler
.
RequiresUnconfirmedBlocks
(
ctx
,
req
)
if
err
!=
nil
{
return
err
}
if
requiresConfirmations
{
return
nil
}
key
:=
handler
.
CacheKey
(
req
)
val
:=
mustMarshalJSON
(
res
)
encodedVal
:=
snappy
.
Encode
(
nil
,
val
)
return
c
.
cache
.
Put
(
ctx
,
key
,
string
(
encodedVal
))
return
handler
.
PutRPCMethod
(
ctx
,
req
,
res
)
}
go/proxyd/cache_test.go
View file @
c2f92c2f
...
...
@@ -9,14 +9,16 @@ import (
"github.com/stretchr/testify/require"
)
func
TestRPCCacheWhitelist
(
t
*
testing
.
T
)
{
const
numBlockConfirmations
=
10
func
TestRPCCacheImmutableRPCs
(
t
*
testing
.
T
)
{
const
blockHead
=
math
.
MaxUint64
ctx
:=
context
.
Background
()
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
getBlockNum
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
getBlockNum
,
nil
,
numBlockConfirmations
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
...
...
@@ -120,6 +122,82 @@ func TestRPCCacheWhitelist(t *testing.T) {
}
}
func
TestRPCCacheBlockNumber
(
t
*
testing
.
T
)
{
var
blockHead
uint64
=
0x1000
var
gasPrice
uint64
=
0x100
ctx
:=
context
.
Background
()
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
getGasPrice
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
gasPrice
,
nil
}
getBlockNum
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
getBlockNum
,
getGasPrice
,
numBlockConfirmations
)
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_blockNumber"
,
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x1000`
,
ID
:
ID
,
}
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
blockHead
=
0x1001
cachedRes
,
err
=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x1001`
,
ID
:
ID
},
cachedRes
)
}
func
TestRPCCacheGasPrice
(
t
*
testing
.
T
)
{
var
blockHead
uint64
=
0x1000
var
gasPrice
uint64
=
0x100
ctx
:=
context
.
Background
()
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
getGasPrice
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
gasPrice
,
nil
}
getBlockNum
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
getBlockNum
,
getGasPrice
,
numBlockConfirmations
)
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_gasPrice"
,
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x100`
,
ID
:
ID
,
}
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
gasPrice
=
0x101
cachedRes
,
err
=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x101`
,
ID
:
ID
},
cachedRes
)
}
func
TestRPCCacheUnsupportedMethod
(
t
*
testing
.
T
)
{
const
blockHead
=
math
.
MaxUint64
ctx
:=
context
.
Background
()
...
...
@@ -127,17 +205,17 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_
blockNumber
"
,
Method
:
"eth_
syncing
"
,
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x1000`
,
Result
:
false
,
ID
:
ID
,
}
...
...
@@ -149,6 +227,62 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) {
require
.
Nil
(
t
,
cachedRes
)
}
func
TestRPCCacheEthGetBlockByNumber
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
var
blockHead
uint64
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
makeCache
:=
func
()
RPCCache
{
return
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
}
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_getBlockByNumber"
,
Params
:
[]
byte
(
`["0xa", false]`
),
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`{"difficulty": "0x1", "number": "0x1"}`
,
ID
:
ID
,
}
req2
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_getBlockByNumber"
,
Params
:
[]
byte
(
`["0xb", false]`
),
ID
:
ID
,
}
res2
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`{"difficulty": "0x2", "number": "0x2"}`
,
ID
:
ID
,
}
t
.
Run
(
"set multiple finalized blocks"
,
func
(
t
*
testing
.
T
)
{
blockHead
=
100
cache
:=
makeCache
()
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req2
,
res2
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
cachedRes
,
err
=
cache
.
GetRPC
(
ctx
,
req2
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res2
,
cachedRes
)
})
t
.
Run
(
"unconfirmed block"
,
func
(
t
*
testing
.
T
)
{
blockHead
=
0xc
cache
:=
makeCache
()
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
cachedRes
)
})
}
func
TestRPCCacheEthGetBlockByNumberForRecentBlocks
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
...
...
@@ -156,7 +290,7 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
...
...
@@ -164,20 +298,6 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
res
*
RPCRes
name
string
}{
{
req
:
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_getBlockByNumber"
,
Params
:
[]
byte
(
`["0x1", false]`
),
ID
:
ID
,
},
res
:
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`{"difficulty": "0x1", "number": "0x1"}`
,
ID
:
ID
,
},
name
:
"recent block num"
,
},
{
req
:
&
RPCReq
{
JSONRPC
:
"2.0"
,
...
...
@@ -227,7 +347,7 @@ func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
...
...
@@ -250,35 +370,71 @@ func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
require
.
Nil
(
t
,
cachedRes
)
}
func
TestRPCCacheEthGetBlockRange
ForRecentBlocks
(
t
*
testing
.
T
)
{
func
TestRPCCacheEthGetBlockRange
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
var
blockHead
uint64
=
0x1000
var
blockHead
uint64
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
makeCache
:=
func
()
RPCCache
{
return
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
}
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
req
*
RPCReq
res
*
RPCRes
name
string
}{
{
req
:
&
RPCReq
{
t
.
Run
(
"finalized block"
,
func
(
t
*
testing
.
T
)
{
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_getBlockRange"
,
Params
:
[]
byte
(
`["0x1", "0x10", false]`
),
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`[{"number": "0x1"}, {"number": "0x10"}]`
,
ID
:
ID
,
}
blockHead
=
0x1000
cache
:=
makeCache
()
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
})
t
.
Run
(
"unconfirmed block"
,
func
(
t
*
testing
.
T
)
{
cache
:=
makeCache
()
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_getBlockRange"
,
Params
:
[]
byte
(
`["0x1", "0x1000", false]`
),
ID
:
ID
,
},
res
:
&
RPCRes
{
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`[{"number": "0x1"}, {"number": "0x2"}]`
,
ID
:
ID
,
},
name
:
"recent block num"
,
},
}
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
cachedRes
)
})
}
func
TestRPCCacheEthGetBlockRangeForRecentBlocks
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
var
blockHead
uint64
=
0x1000
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
req
*
RPCReq
res
*
RPCRes
name
string
}{
{
req
:
&
RPCReq
{
JSONRPC
:
"2.0"
,
...
...
@@ -342,7 +498,7 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
...
...
@@ -391,3 +547,76 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
})
}
}
func
TestRPCCacheEthCall
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
var
blockHead
uint64
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
}
makeCache
:=
func
()
RPCCache
{
return
newRPCCache
(
newMemoryCache
(),
fn
,
nil
,
numBlockConfirmations
)
}
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_call"
,
Params
:
[]
byte
(
`[{"to": "0xDEADBEEF", "data": "0x1"}, "0x10"]`
),
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x0`
,
ID
:
ID
,
}
t
.
Run
(
"finalized block"
,
func
(
t
*
testing
.
T
)
{
blockHead
=
0x100
cache
:=
makeCache
()
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
})
t
.
Run
(
"unconfirmed block"
,
func
(
t
*
testing
.
T
)
{
blockHead
=
0x10
cache
:=
makeCache
()
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
cachedRes
)
})
t
.
Run
(
"latest block"
,
func
(
t
*
testing
.
T
)
{
blockHead
=
0x100
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_call"
,
Params
:
[]
byte
(
`[{"to": "0xDEADBEEF", "data": "0x1"}, "latest"]`
),
ID
:
ID
,
}
cache
:=
makeCache
()
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
cachedRes
)
})
t
.
Run
(
"pending block"
,
func
(
t
*
testing
.
T
)
{
blockHead
=
0x100
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_call"
,
Params
:
[]
byte
(
`[{"to": "0xDEADBEEF", "data": "0x1"}, "pending"]`
),
ID
:
ID
,
}
cache
:=
makeCache
()
require
.
NoError
(
t
,
cache
.
PutRPC
(
ctx
,
req
,
res
))
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
cachedRes
)
})
}
go/proxyd/config.go
View file @
c2f92c2f
...
...
@@ -17,6 +17,7 @@ type ServerConfig struct {
type
CacheConfig
struct
{
Enabled
bool
`toml:"enabled"`
BlockSyncRPCURL
string
`toml:"block_sync_rpc_url"`
NumBlockConfirmations
int
`toml:"num_block_confirmations"`
}
type
RedisConfig
struct
{
...
...
go/proxyd/integration_tests/failover_test.go
View file @
c2f92c2f
go/proxyd/latestblock.go
deleted
100644 → 0
View file @
e8ea9654
package
proxyd
import
(
"context"
"sync"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
const
blockHeadSyncPeriod
=
1
*
time
.
Second
type
LatestBlockHead
struct
{
url
string
client
*
ethclient
.
Client
quit
chan
struct
{}
done
chan
struct
{}
mutex
sync
.
RWMutex
blockNum
uint64
}
func
newLatestBlockHead
(
url
string
)
(
*
LatestBlockHead
,
error
)
{
client
,
err
:=
ethclient
.
Dial
(
url
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
LatestBlockHead
{
url
:
url
,
client
:
client
,
quit
:
make
(
chan
struct
{}),
done
:
make
(
chan
struct
{}),
},
nil
}
func
(
h
*
LatestBlockHead
)
Start
()
{
go
func
()
{
ticker
:=
time
.
NewTicker
(
blockHeadSyncPeriod
)
defer
ticker
.
Stop
()
for
{
select
{
case
<-
ticker
.
C
:
blockNum
,
err
:=
h
.
getBlockNum
()
if
err
!=
nil
{
log
.
Error
(
"error retrieving latest block number"
,
"error"
,
err
)
continue
}
log
.
Trace
(
"polling block number"
,
"blockNum"
,
blockNum
)
h
.
mutex
.
Lock
()
h
.
blockNum
=
blockNum
h
.
mutex
.
Unlock
()
case
<-
h
.
quit
:
close
(
h
.
done
)
return
}
}
}()
}
func
(
h
*
LatestBlockHead
)
getBlockNum
()
(
uint64
,
error
)
{
const
maxRetries
=
5
var
err
error
for
i
:=
0
;
i
<=
maxRetries
;
i
++
{
var
blockNum
uint64
blockNum
,
err
=
h
.
client
.
BlockNumber
(
context
.
Background
())
if
err
!=
nil
{
backoff
:=
calcBackoff
(
i
)
log
.
Warn
(
"http operation failed. retrying..."
,
"error"
,
err
,
"backoff"
,
backoff
)
time
.
Sleep
(
backoff
)
continue
}
return
blockNum
,
nil
}
return
0
,
wrapErr
(
err
,
"exceeded retries"
)
}
func
(
h
*
LatestBlockHead
)
Stop
()
{
close
(
h
.
quit
)
<-
h
.
done
h
.
client
.
Close
()
}
func
(
h
*
LatestBlockHead
)
GetBlockNum
()
uint64
{
h
.
mutex
.
RLock
()
defer
h
.
mutex
.
RUnlock
()
return
h
.
blockNum
}
go/proxyd/lvc.go
0 → 100644
View file @
c2f92c2f
package
proxyd
import
(
"context"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
const
cacheSyncRate
=
1
*
time
.
Second
type
lvcUpdateFn
func
(
context
.
Context
,
*
ethclient
.
Client
)
(
string
,
error
)
type
EthLastValueCache
struct
{
client
*
ethclient
.
Client
cache
Cache
key
string
updater
lvcUpdateFn
quit
chan
struct
{}
}
func
newLVC
(
client
*
ethclient
.
Client
,
cache
Cache
,
cacheKey
string
,
updater
lvcUpdateFn
)
*
EthLastValueCache
{
return
&
EthLastValueCache
{
client
:
client
,
cache
:
cache
,
key
:
cacheKey
,
updater
:
updater
,
quit
:
make
(
chan
struct
{}),
}
}
func
(
h
*
EthLastValueCache
)
Start
()
{
go
func
()
{
ticker
:=
time
.
NewTicker
(
cacheSyncRate
)
defer
ticker
.
Stop
()
for
{
select
{
case
<-
ticker
.
C
:
lvcPollTimeGauge
.
WithLabelValues
(
h
.
key
)
.
SetToCurrentTime
()
value
,
err
:=
h
.
getUpdate
()
if
err
!=
nil
{
log
.
Error
(
"error retrieving latest value"
,
"key"
,
h
.
key
,
"error"
,
err
)
continue
}
log
.
Trace
(
"polling latest value"
,
"value"
,
value
)
if
err
:=
h
.
cache
.
Put
(
context
.
Background
(),
h
.
key
,
value
);
err
!=
nil
{
log
.
Error
(
"error writing last value to cache"
,
"key"
,
h
.
key
,
"error"
,
err
)
}
case
<-
h
.
quit
:
return
}
}
}()
}
func
(
h
*
EthLastValueCache
)
getUpdate
()
(
string
,
error
)
{
const
maxRetries
=
5
var
err
error
for
i
:=
0
;
i
<=
maxRetries
;
i
++
{
var
value
string
value
,
err
=
h
.
updater
(
context
.
Background
(),
h
.
client
)
if
err
!=
nil
{
backoff
:=
calcBackoff
(
i
)
log
.
Warn
(
"http operation failed. retrying..."
,
"error"
,
err
,
"backoff"
,
backoff
)
lvcErrorsTotal
.
WithLabelValues
(
h
.
key
)
.
Inc
()
time
.
Sleep
(
backoff
)
continue
}
return
value
,
nil
}
return
""
,
wrapErr
(
err
,
"exceeded retries"
)
}
func
(
h
*
EthLastValueCache
)
Stop
()
{
close
(
h
.
quit
)
}
func
(
h
*
EthLastValueCache
)
Read
(
ctx
context
.
Context
)
(
string
,
error
)
{
return
h
.
cache
.
Get
(
ctx
,
h
.
key
)
}
go/proxyd/methods.go
View file @
c2f92c2f
...
...
@@ -5,36 +5,56 @@ import (
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var
errInvalidRPCParams
=
errors
.
New
(
"invalid RPC params"
)
var
(
errInvalidRPCParams
=
errors
.
New
(
"invalid RPC params"
)
)
type
RPCMethodHandler
interface
{
CacheKey
(
req
*
RPCReq
)
string
IsCacheable
(
req
*
RPCReq
)
(
bool
,
error
)
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
GetRPCMethod
(
context
.
Context
,
*
RPCReq
)
(
*
RPCRes
,
error
)
PutRPCMethod
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
)
error
}
type
StaticRPCMethodHandler
struct
{
method
string
type
StaticMethodHandler
struct
{
cache
interface
{}
m
sync
.
RWMutex
}
func
(
s
*
StaticRPCMethodHandler
)
CacheKey
(
req
*
RPCReq
)
string
{
return
fmt
.
Sprintf
(
"method:%s"
,
s
.
method
)
func
(
e
*
StaticMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
e
.
m
.
RLock
()
cache
:=
e
.
cache
e
.
m
.
RUnlock
()
if
cache
==
nil
{
return
nil
,
nil
}
return
&
RPCRes
{
JSONRPC
:
req
.
JSONRPC
,
Result
:
cache
,
ID
:
req
.
ID
,
},
nil
}
func
(
s
*
StaticRPCMethodHandler
)
IsCacheable
(
*
RPCReq
)
(
bool
,
error
)
{
return
true
,
nil
}
func
(
s
*
StaticRPCMethodHandler
)
RequiresUnconfirmedBlocks
(
context
.
Context
,
*
RPCReq
)
(
bool
,
error
)
{
return
false
,
nil
func
(
e
*
StaticMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
e
.
m
.
Lock
()
if
e
.
cache
==
nil
{
e
.
cache
=
res
.
Result
}
e
.
m
.
Unlock
()
return
nil
}
type
EthGetBlockByNumberMethod
struct
{
type
EthGetBlockByNumberMethodHandler
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
numBlockConfirmations
int
}
func
(
e
*
EthGetBlockByNumberMethod
)
C
acheKey
(
req
*
RPCReq
)
string
{
func
(
e
*
EthGetBlockByNumberMethod
Handler
)
c
acheKey
(
req
*
RPCReq
)
string
{
input
,
includeTx
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
if
err
!=
nil
{
return
""
...
...
@@ -42,7 +62,7 @@ func (e *EthGetBlockByNumberMethod) CacheKey(req *RPCReq) string {
return
fmt
.
Sprintf
(
"method:eth_getBlockByNumber:%s:%t"
,
input
,
includeTx
)
}
func
(
e
*
EthGetBlockByNumberMethod
)
IsC
acheable
(
req
*
RPCReq
)
(
bool
,
error
)
{
func
(
e
*
EthGetBlockByNumberMethod
Handler
)
c
acheable
(
req
*
RPCReq
)
(
bool
,
error
)
{
blockNum
,
_
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
if
err
!=
nil
{
return
false
,
err
...
...
@@ -50,33 +70,51 @@ func (e *EthGetBlockByNumberMethod) IsCacheable(req *RPCReq) (bool, error) {
return
!
isBlockDependentParam
(
blockNum
),
nil
}
func
(
e
*
EthGetBlockByNumberMethod
)
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
{
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
false
,
err
func
(
e
*
EthGetBlockByNumberMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
return
nil
,
err
}
key
:=
e
.
cacheKey
(
req
)
return
getImmutableRPCResponse
(
ctx
,
e
.
cache
,
key
,
req
)
}
func
(
e
*
EthGetBlockByNumberMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
return
err
}
blockInput
,
_
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
if
err
!=
nil
{
return
false
,
err
return
err
}
if
isBlockDependentParam
(
blockInput
)
{
return
true
,
nil
return
nil
}
if
blockInput
==
"earliest"
{
return
false
,
nil
if
blockInput
!=
"earliest"
{
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
err
}
blockNum
,
err
:=
decodeBlockInput
(
blockInput
)
if
err
!=
nil
{
return
false
,
err
return
err
}
if
curBlock
<=
blockNum
+
uint64
(
e
.
numBlockConfirmations
)
{
return
nil
}
}
return
curBlock
<=
blockNum
+
numBlockConfirmations
,
nil
key
:=
e
.
cacheKey
(
req
)
return
putImmutableRPCResponse
(
ctx
,
e
.
cache
,
key
,
req
,
res
)
}
type
EthGetBlockRangeMethod
struct
{
type
EthGetBlockRangeMethodHandler
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
numBlockConfirmations
int
}
func
(
e
*
EthGetBlockRangeMethod
)
C
acheKey
(
req
*
RPCReq
)
string
{
func
(
e
*
EthGetBlockRangeMethod
Handler
)
c
acheKey
(
req
*
RPCReq
)
string
{
start
,
end
,
includeTx
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
if
err
!=
nil
{
return
""
...
...
@@ -84,7 +122,7 @@ func (e *EthGetBlockRangeMethod) CacheKey(req *RPCReq) string {
return
fmt
.
Sprintf
(
"method:eth_getBlockRange:%s:%s:%t"
,
start
,
end
,
includeTx
)
}
func
(
e
*
EthGetBlockRangeMethod
)
IsC
acheable
(
req
*
RPCReq
)
(
bool
,
error
)
{
func
(
e
*
EthGetBlockRangeMethod
Handler
)
c
acheable
(
req
*
RPCReq
)
(
bool
,
error
)
{
start
,
end
,
_
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
if
err
!=
nil
{
return
false
,
err
...
...
@@ -92,42 +130,144 @@ func (e *EthGetBlockRangeMethod) IsCacheable(req *RPCReq) (bool, error) {
return
!
isBlockDependentParam
(
start
)
&&
!
isBlockDependentParam
(
end
),
nil
}
func
(
e
*
EthGetBlockRangeMethod
)
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
{
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
false
,
err
func
(
e
*
EthGetBlockRangeMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
return
nil
,
err
}
key
:=
e
.
cacheKey
(
req
)
return
getImmutableRPCResponse
(
ctx
,
e
.
cache
,
key
,
req
)
}
func
(
e
*
EthGetBlockRangeMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
return
err
}
start
,
end
,
_
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
if
err
!=
nil
{
return
false
,
err
return
err
}
if
isBlockDependentParam
(
start
)
||
isBlockDependentParam
(
end
)
{
return
true
,
nil
}
if
start
==
"earliest"
&&
end
==
"earliest"
{
return
false
,
nil
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
err
}
if
start
!=
"earliest"
{
startNum
,
err
:=
decodeBlockInput
(
start
)
if
err
!=
nil
{
return
false
,
err
return
err
}
if
curBlock
<=
startNum
+
numBlockConfirmations
{
return
true
,
nil
if
curBlock
<=
startNum
+
uint64
(
e
.
numBlockConfirmations
)
{
return
nil
}
}
if
end
!=
"earliest"
{
endNum
,
err
:=
decodeBlockInput
(
end
)
if
err
!=
nil
{
return
false
,
err
return
err
}
if
curBlock
<=
endNum
+
numBlockConfirmations
{
return
true
,
nil
if
curBlock
<=
endNum
+
uint64
(
e
.
numBlockConfirmations
)
{
return
nil
}
}
return
false
,
nil
key
:=
e
.
cacheKey
(
req
)
return
putImmutableRPCResponse
(
ctx
,
e
.
cache
,
key
,
req
,
res
)
}
type
EthCallMethodHandler
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
numBlockConfirmations
int
}
func
(
e
*
EthCallMethodHandler
)
cacheable
(
params
*
ethCallParams
,
blockTag
string
)
bool
{
if
isBlockDependentParam
(
blockTag
)
{
return
false
}
if
params
.
From
!=
""
||
params
.
Gas
!=
""
{
return
false
}
if
params
.
Value
!=
""
&&
params
.
Value
!=
"0x0"
{
return
false
}
return
true
}
func
(
e
*
EthCallMethodHandler
)
cacheKey
(
params
*
ethCallParams
,
blockTag
string
)
string
{
keyParams
:=
fmt
.
Sprintf
(
"%s:%s:%s"
,
params
.
To
,
params
.
Data
,
blockTag
)
return
fmt
.
Sprintf
(
"method:eth_call:%s"
,
keyParams
)
}
func
(
e
*
EthCallMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
params
,
blockTag
,
err
:=
decodeEthCallParams
(
req
)
if
err
!=
nil
{
return
nil
,
err
}
if
!
e
.
cacheable
(
params
,
blockTag
)
{
return
nil
,
nil
}
key
:=
e
.
cacheKey
(
params
,
blockTag
)
return
getImmutableRPCResponse
(
ctx
,
e
.
cache
,
key
,
req
)
}
func
(
e
*
EthCallMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
params
,
blockTag
,
err
:=
decodeEthCallParams
(
req
)
if
err
!=
nil
{
return
err
}
if
!
e
.
cacheable
(
params
,
blockTag
)
{
return
nil
}
if
blockTag
!=
"earliest"
{
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
err
}
blockNum
,
err
:=
decodeBlockInput
(
blockTag
)
if
err
!=
nil
{
return
err
}
if
curBlock
<=
blockNum
+
uint64
(
e
.
numBlockConfirmations
)
{
return
nil
}
}
key
:=
e
.
cacheKey
(
params
,
blockTag
)
return
putImmutableRPCResponse
(
ctx
,
e
.
cache
,
key
,
req
,
res
)
}
type
EthBlockNumberMethodHandler
struct
{
getLatestBlockNumFn
GetLatestBlockNumFn
}
func
(
e
*
EthBlockNumberMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
blockNum
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
return
makeRPCRes
(
req
,
hexutil
.
EncodeUint64
(
blockNum
)),
nil
}
func
(
e
*
EthBlockNumberMethodHandler
)
PutRPCMethod
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
)
error
{
return
nil
}
type
EthGasPriceMethodHandler
struct
{
getLatestGasPrice
GetLatestGasPriceFn
}
func
(
e
*
EthGasPriceMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
gasPrice
,
err
:=
e
.
getLatestGasPrice
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
return
makeRPCRes
(
req
,
hexutil
.
EncodeUint64
(
gasPrice
)),
nil
}
func
(
e
*
EthGasPriceMethodHandler
)
PutRPCMethod
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
)
error
{
return
nil
}
func
isBlockDependentParam
(
s
string
)
bool
{
...
...
@@ -186,6 +326,34 @@ func decodeBlockInput(input string) (uint64, error) {
return
hexutil
.
DecodeUint64
(
input
)
}
type
ethCallParams
struct
{
From
string
`json:"from"`
To
string
`json:"to"`
Gas
string
`json:"gas"`
GasPrice
string
`json:"gasPrice"`
Value
string
`json:"value"`
Data
string
`json:"data"`
}
func
decodeEthCallParams
(
req
*
RPCReq
)
(
*
ethCallParams
,
string
,
error
)
{
var
input
[]
json
.
RawMessage
if
err
:=
json
.
Unmarshal
(
req
.
Params
,
&
input
);
err
!=
nil
{
return
nil
,
""
,
err
}
if
len
(
input
)
!=
2
{
return
nil
,
""
,
fmt
.
Errorf
(
"invalid eth_call parameters"
)
}
params
:=
new
(
ethCallParams
)
if
err
:=
json
.
Unmarshal
(
input
[
0
],
params
);
err
!=
nil
{
return
nil
,
""
,
err
}
var
blockTag
string
if
err
:=
json
.
Unmarshal
(
input
[
1
],
&
blockTag
);
err
!=
nil
{
return
nil
,
""
,
err
}
return
params
,
blockTag
,
nil
}
func
validBlockInput
(
input
string
)
bool
{
if
input
==
"earliest"
||
input
==
"pending"
||
input
==
"latest"
{
return
true
...
...
@@ -193,3 +361,39 @@ func validBlockInput(input string) bool {
_
,
err
:=
decodeBlockInput
(
input
)
return
err
==
nil
}
func
makeRPCRes
(
req
*
RPCReq
,
result
interface
{})
*
RPCRes
{
return
&
RPCRes
{
JSONRPC
:
JSONRPCVersion
,
ID
:
req
.
ID
,
Result
:
result
,
}
}
func
getImmutableRPCResponse
(
ctx
context
.
Context
,
cache
Cache
,
key
string
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
val
,
err
:=
cache
.
Get
(
ctx
,
key
)
if
err
!=
nil
{
return
nil
,
err
}
if
val
==
""
{
return
nil
,
nil
}
var
result
interface
{}
if
err
:=
json
.
Unmarshal
([]
byte
(
val
),
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
&
RPCRes
{
JSONRPC
:
req
.
JSONRPC
,
Result
:
result
,
ID
:
req
.
ID
,
},
nil
}
func
putImmutableRPCResponse
(
ctx
context
.
Context
,
cache
Cache
,
key
string
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
if
key
==
""
{
return
nil
}
val
:=
mustMarshalJSON
(
res
.
Result
)
return
cache
.
Put
(
ctx
,
key
,
string
(
val
))
}
go/proxyd/metrics.go
View file @
c2f92c2f
...
...
@@ -145,7 +145,7 @@ var (
requestPayloadSizesGauge
=
promauto
.
NewHistogramVec
(
prometheus
.
HistogramOpts
{
Namespace
:
MetricsNamespace
,
Name
:
"request_payload_sizes"
,
Help
:
"
Gauge
of client request payload sizes."
,
Help
:
"
Histogram
of client request payload sizes."
,
Buckets
:
PayloadSizeBuckets
,
},
[]
string
{
"auth"
,
...
...
@@ -154,7 +154,7 @@ var (
responsePayloadSizesGauge
=
promauto
.
NewHistogramVec
(
prometheus
.
HistogramOpts
{
Namespace
:
MetricsNamespace
,
Name
:
"response_payload_sizes"
,
Help
:
"
Gauge
of client response payload sizes."
,
Help
:
"
Histogram
of client response payload sizes."
,
Buckets
:
PayloadSizeBuckets
,
},
[]
string
{
"auth"
,
...
...
@@ -176,6 +176,22 @@ var (
"method"
,
})
lvcErrorsTotal
=
promauto
.
NewCounterVec
(
prometheus
.
CounterOpts
{
Namespace
:
MetricsNamespace
,
Name
:
"lvc_errors_total"
,
Help
:
"Count of lvc errors."
,
},
[]
string
{
"key"
,
})
lvcPollTimeGauge
=
promauto
.
NewGaugeVec
(
prometheus
.
GaugeOpts
{
Namespace
:
MetricsNamespace
,
Name
:
"lvc_poll_time_gauge"
,
Help
:
"Gauge of lvc poll time."
,
},
[]
string
{
"key"
,
})
rpcSpecialErrors
=
[]
string
{
"nonce too low"
,
"gas price too high"
,
...
...
go/proxyd/proxyd.go
View file @
c2f92c2f
...
...
@@ -7,8 +7,10 @@ import (
"fmt"
"net/http"
"os"
"strconv"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
...
...
@@ -162,10 +164,18 @@ func Start(config *Config) (func(), error) {
}
}
var
rpcCache
RPCCache
var
latestHead
*
LatestBlockHead
var
(
rpcCache
RPCCache
blockNumLVC
*
EthLastValueCache
gasPriceLVC
*
EthLastValueCache
)
if
config
.
Cache
.
Enabled
{
var
getLatestBlockNumFn
GetLatestBlockNumFn
var
(
cache
Cache
blockNumFn
GetLatestBlockNumFn
gasPriceFn
GetLatestGasPriceFn
)
if
config
.
Cache
.
BlockSyncRPCURL
==
""
{
return
nil
,
fmt
.
Errorf
(
"block sync node required for caching"
)
}
...
...
@@ -174,7 +184,6 @@ func Start(config *Config) (func(), error) {
return
nil
,
err
}
var
cache
Cache
if
redisURL
!=
""
{
if
cache
,
err
=
newRedisCache
(
redisURL
);
err
!=
nil
{
return
nil
,
err
...
...
@@ -183,17 +192,16 @@ func Start(config *Config) (func(), error) {
log
.
Warn
(
"redis is not configured, using in-memory cache"
)
cache
=
newMemoryCache
()
}
latestHead
,
err
=
newLatestBlockHead
(
blockSyncRPCURL
)
// Ideally, the BlocKSyncRPCURL should be the sequencer or a HA replica that's not far behind
ethClient
,
err
:=
ethclient
.
Dial
(
blockSyncRPCURL
)
if
err
!=
nil
{
return
nil
,
err
}
latestHead
.
Start
()
defer
ethClient
.
Close
()
getLatestBlockNumFn
=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
latestHead
.
GetBlockNum
(),
nil
}
rpcCache
=
newRPCCache
(
cache
,
getLatestBlockNumFn
)
blockNumLVC
,
blockNumFn
=
makeGetLatestBlockNumFn
(
ethClient
,
cache
)
gasPriceLVC
,
gasPriceFn
=
makeGetLatestGasPriceFn
(
ethClient
,
cache
)
rpcCache
=
newRPCCache
(
newCacheWithCompression
(
cache
),
blockNumFn
,
gasPriceFn
,
config
.
Cache
.
NumBlockConfirmations
)
}
srv
:=
NewServer
(
...
...
@@ -246,8 +254,11 @@ func Start(config *Config) (func(), error) {
return
func
()
{
log
.
Info
(
"shutting down proxyd"
)
if
latestHead
!=
nil
{
latestHead
.
Stop
()
if
blockNumLVC
!=
nil
{
blockNumLVC
.
Stop
()
}
if
gasPriceLVC
!=
nil
{
gasPriceLVC
.
Stop
()
}
srv
.
Shutdown
()
if
err
:=
lim
.
FlushBackendWSConns
(
backendNames
);
err
!=
nil
{
...
...
@@ -281,3 +292,39 @@ func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) {
return
tlsConfig
,
nil
}
func
makeUint64LastValueFn
(
client
*
ethclient
.
Client
,
cache
Cache
,
key
string
,
updater
lvcUpdateFn
)
(
*
EthLastValueCache
,
func
(
context
.
Context
)
(
uint64
,
error
))
{
lvc
:=
newLVC
(
client
,
cache
,
key
,
updater
)
lvc
.
Start
()
return
lvc
,
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
value
,
err
:=
lvc
.
Read
(
ctx
)
if
err
!=
nil
{
return
0
,
err
}
if
value
==
""
{
return
0
,
fmt
.
Errorf
(
"%s is unavailable"
,
key
)
}
valueUint
,
err
:=
strconv
.
ParseUint
(
value
,
10
,
64
)
if
err
!=
nil
{
return
0
,
err
}
return
valueUint
,
nil
}
}
func
makeGetLatestBlockNumFn
(
client
*
ethclient
.
Client
,
cache
Cache
)
(
*
EthLastValueCache
,
GetLatestBlockNumFn
)
{
return
makeUint64LastValueFn
(
client
,
cache
,
"lvc:block_number"
,
func
(
ctx
context
.
Context
,
c
*
ethclient
.
Client
)
(
string
,
error
)
{
blockNum
,
err
:=
c
.
BlockNumber
(
ctx
)
return
strconv
.
FormatUint
(
blockNum
,
10
),
err
})
}
func
makeGetLatestGasPriceFn
(
client
*
ethclient
.
Client
,
cache
Cache
)
(
*
EthLastValueCache
,
GetLatestGasPriceFn
)
{
return
makeUint64LastValueFn
(
client
,
cache
,
"lvc:gas_price"
,
func
(
ctx
context
.
Context
,
c
*
ethclient
.
Client
)
(
string
,
error
)
{
gasPrice
,
err
:=
c
.
SuggestGasPrice
(
ctx
)
if
err
!=
nil
{
return
""
,
err
}
return
gasPrice
.
String
(),
nil
})
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment