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
9763200f
Commit
9763200f
authored
Jan 16, 2022
by
inphi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor; implement more rpcs
parent
eb511f11
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
598 additions
and
251 deletions
+598
-251
backend.go
go/proxyd/backend.go
+37
-6
cache.go
go/proxyd/cache.go
+41
-116
cache_test.go
go/proxyd/cache_test.go
+168
-42
lvc.go
go/proxyd/lvc.go
+85
-0
methods.go
go/proxyd/methods.go
+209
-72
proxyd.go
go/proxyd/proxyd.go
+53
-12
server.go
go/proxyd/server.go
+5
-3
No files found.
go/proxyd/backend.go
View file @
9763200f
...
@@ -16,7 +16,9 @@ import (
...
@@ -16,7 +16,9 @@ import (
"strings"
"strings"
"time"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/websocket"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus"
)
)
...
@@ -84,6 +86,7 @@ type Backend struct {
...
@@ -84,6 +86,7 @@ type Backend struct {
authPassword
string
authPassword
string
rateLimiter
RateLimiter
rateLimiter
RateLimiter
client
*
http
.
Client
client
*
http
.
Client
blockNumberLVC
*
EthLastValueCache
dialer
*
websocket
.
Dialer
dialer
*
websocket
.
Dialer
maxRetries
int
maxRetries
int
maxResponseSize
int64
maxResponseSize
int64
...
@@ -166,7 +169,7 @@ func NewBackend(
...
@@ -166,7 +169,7 @@ func NewBackend(
wsURL
string
,
wsURL
string
,
rateLimiter
RateLimiter
,
rateLimiter
RateLimiter
,
opts
...
BackendOpt
,
opts
...
BackendOpt
,
)
*
Backend
{
)
(
*
Backend
,
error
)
{
backend
:=
&
Backend
{
backend
:=
&
Backend
{
Name
:
name
,
Name
:
name
,
rpcURL
:
rpcURL
,
rpcURL
:
rpcURL
,
...
@@ -183,11 +186,28 @@ func NewBackend(
...
@@ -183,11 +186,28 @@ func NewBackend(
opt
(
backend
)
opt
(
backend
)
}
}
rpcClient
,
err
:=
rpc
.
DialHTTPWithClient
(
rpcURL
,
backend
.
client
)
if
err
!=
nil
{
return
nil
,
err
}
backend
.
blockNumberLVC
=
newLVC
(
ethclient
.
NewClient
(
rpcClient
),
func
(
ctx
context
.
Context
,
client
*
ethclient
.
Client
)
(
interface
{},
error
)
{
blockNumber
,
err
:=
client
.
BlockNumber
(
ctx
)
return
blockNumber
,
err
})
if
!
backend
.
stripTrailingXFF
&&
backend
.
proxydIP
==
""
{
if
!
backend
.
stripTrailingXFF
&&
backend
.
proxydIP
==
""
{
log
.
Warn
(
"proxied requests' XFF header will not contain the proxyd ip address"
)
log
.
Warn
(
"proxied requests' XFF header will not contain the proxyd ip address"
)
}
}
return
backend
return
backend
,
nil
}
func
(
b
*
Backend
)
Start
()
{
b
.
blockNumberLVC
.
Start
()
}
func
(
b
*
Backend
)
Stop
()
{
b
.
blockNumberLVC
.
Stop
()
}
}
func
(
b
*
Backend
)
Forward
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
func
(
b
*
Backend
)
Forward
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
...
@@ -268,6 +288,14 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet
...
@@ -268,6 +288,14 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet
return
NewWSProxier
(
b
,
clientConn
,
backendConn
,
methodWhitelist
),
nil
return
NewWSProxier
(
b
,
clientConn
,
backendConn
,
methodWhitelist
),
nil
}
}
func
(
b
*
Backend
)
BlockNumber
()
uint64
{
var
blockNum
uint64
if
val
:=
b
.
blockNumberLVC
.
Read
();
val
!=
nil
{
blockNum
=
val
.
(
uint64
)
}
return
blockNum
}
func
(
b
*
Backend
)
Online
()
bool
{
func
(
b
*
Backend
)
Online
()
bool
{
online
,
err
:=
b
.
rateLimiter
.
IsBackendOnline
(
b
.
Name
)
online
,
err
:=
b
.
rateLimiter
.
IsBackendOnline
(
b
.
Name
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -395,13 +423,16 @@ type BackendGroup struct {
...
@@ -395,13 +423,16 @@ type BackendGroup struct {
Backends
[]
*
Backend
Backends
[]
*
Backend
}
}
func
(
b
*
BackendGroup
)
Forward
(
ctx
context
.
Context
,
rpcReq
*
RPCReq
)
(
*
RPCRes
,
error
)
{
func
(
b
*
BackendGroup
)
Forward
(
ctx
context
.
Context
,
rpcReq
*
RPCReq
)
(
*
RPCRes
,
uint64
,
error
)
{
rpcRequestsTotal
.
Inc
()
rpcRequestsTotal
.
Inc
()
for
_
,
back
:=
range
b
.
Backends
{
for
_
,
back
:=
range
b
.
Backends
{
// The blockNum must precede the forwarded RPC to establish a synchronization point
blockNum
:=
back
.
BlockNumber
()
res
,
err
:=
back
.
Forward
(
ctx
,
rpcReq
)
res
,
err
:=
back
.
Forward
(
ctx
,
rpcReq
)
if
errors
.
Is
(
err
,
ErrMethodNotWhitelisted
)
{
if
errors
.
Is
(
err
,
ErrMethodNotWhitelisted
)
{
return
nil
,
err
return
nil
,
0
,
err
}
}
if
errors
.
Is
(
err
,
ErrBackendOffline
)
{
if
errors
.
Is
(
err
,
ErrBackendOffline
)
{
log
.
Warn
(
log
.
Warn
(
...
@@ -431,11 +462,11 @@ func (b *BackendGroup) Forward(ctx context.Context, rpcReq *RPCReq) (*RPCRes, er
...
@@ -431,11 +462,11 @@ func (b *BackendGroup) Forward(ctx context.Context, rpcReq *RPCReq) (*RPCRes, er
)
)
continue
continue
}
}
return
res
,
nil
return
res
,
blockNum
,
nil
}
}
RecordUnserviceableRequest
(
ctx
,
RPCRequestSourceHTTP
)
RecordUnserviceableRequest
(
ctx
,
RPCRequestSourceHTTP
)
return
nil
,
ErrNoBackends
return
nil
,
0
,
ErrNoBackends
}
}
func
(
b
*
BackendGroup
)
ProxyWS
(
ctx
context
.
Context
,
clientConn
*
websocket
.
Conn
,
methodWhitelist
*
StringSet
)
(
*
WSProxier
,
error
)
{
func
(
b
*
BackendGroup
)
ProxyWS
(
ctx
context
.
Context
,
clientConn
*
websocket
.
Conn
,
methodWhitelist
*
StringSet
)
(
*
WSProxier
,
error
)
{
...
...
go/proxyd/cache.go
View file @
9763200f
...
@@ -2,23 +2,21 @@ package proxyd
...
@@ -2,23 +2,21 @@ package proxyd
import
(
import
(
"context"
"context"
"encoding/json"
"time"
"time"
"github.com/go-redis/redis/v8"
"github.com/go-redis/redis/v8"
"github.com/golang/snappy"
lru
"github.com/hashicorp/golang-lru"
lru
"github.com/hashicorp/golang-lru"
)
)
type
Cache
interface
{
type
Cache
interface
{
Get
(
ctx
context
.
Context
,
key
string
)
(
string
,
error
)
Get
(
ctx
context
.
Context
,
key
string
)
(
string
,
error
)
Put
(
ctx
context
.
Context
,
key
string
,
value
string
)
error
Put
(
ctx
context
.
Context
,
key
string
,
value
string
,
ttl
time
.
Duration
)
error
Remove
(
ctx
context
.
Context
,
key
string
)
error
}
}
// assuming an average RPCRes size of 3 KB
const
(
const
(
memoryCacheLimit
=
4096
// assuming an average RPCRes size of 3 KB
numBlockConfirmations
=
50
memoryCacheLimit
=
4096
)
)
type
cache
struct
{
type
cache
struct
{
...
@@ -37,11 +35,16 @@ func (c *cache) Get(ctx context.Context, key string) (string, error) {
...
@@ -37,11 +35,16 @@ func (c *cache) Get(ctx context.Context, key string) (string, error) {
return
""
,
nil
return
""
,
nil
}
}
func
(
c
*
cache
)
Put
(
ctx
context
.
Context
,
key
string
,
value
string
)
error
{
func
(
c
*
cache
)
Put
(
ctx
context
.
Context
,
key
string
,
value
string
,
ttl
time
.
Duration
)
error
{
c
.
lru
.
Add
(
key
,
value
)
c
.
lru
.
Add
(
key
,
value
)
return
nil
return
nil
}
}
func
(
c
*
cache
)
Remove
(
ctx
context
.
Context
,
key
string
)
error
{
c
.
lru
.
Remove
(
key
)
return
nil
}
type
redisCache
struct
{
type
redisCache
struct
{
rdb
*
redis
.
Client
rdb
*
redis
.
Client
}
}
...
@@ -69,7 +72,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) {
...
@@ -69,7 +72,7 @@ func (c *redisCache) Get(ctx context.Context, key string) (string, error) {
return
val
,
nil
return
val
,
nil
}
}
func
(
c
*
redisCache
)
Put
(
ctx
context
.
Context
,
key
string
,
value
string
)
error
{
func
(
c
*
redisCache
)
Put
(
ctx
context
.
Context
,
key
string
,
value
string
,
ttl
time
.
Duration
)
error
{
err
:=
c
.
rdb
.
Set
(
ctx
,
key
,
value
,
0
)
.
Err
()
err
:=
c
.
rdb
.
Set
(
ctx
,
key
,
value
,
0
)
.
Err
()
if
err
!=
nil
{
if
err
!=
nil
{
RecordRedisError
(
"CacheSet"
)
RecordRedisError
(
"CacheSet"
)
...
@@ -77,45 +80,44 @@ func (c *redisCache) Put(ctx context.Context, key string, value string) error {
...
@@ -77,45 +80,44 @@ func (c *redisCache) Put(ctx context.Context, key string, value string) error {
return
err
return
err
}
}
func
(
c
*
redisCache
)
Remove
(
ctx
context
.
Context
,
key
string
)
error
{
err
:=
c
.
rdb
.
Del
(
ctx
,
key
)
.
Err
()
return
err
}
type
GetLatestBlockNumFn
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
type
GetLatestBlockNumFn
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
type
GetLatestGasPriceFn
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
type
RPCCache
interface
{
type
RPCCache
interface
{
GetRPC
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
GetRPC
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
PutRPC
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
}
type
rpcCache
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
handlers
map
[
string
]
RPCMethodHandler
}
type
CachedRPC
struct
{
BlockNum
uint64
`json:"blockNum"`
Res
*
RPCRes
`json:"res"`
TTL
int64
`json:"ttl"`
}
func
(
c
*
CachedRPC
)
Encode
()
[]
byte
{
return
mustMarshalJSON
(
c
)
}
func
(
c
*
CachedRPC
)
Decode
(
b
[]
byte
)
error
{
// The blockNumberSync is used to enforce Sequential Consistency. We make the following assumptions to do this:
return
json
.
Unmarshal
(
b
,
c
)
// 1. No Reorgs. Reoorgs are handled by the Cache during retrieval
// 2. The backend yields synchronized block numbers and RPC Responses.
// 2. No backend failover. If there's a failover then we may desync as we use a different backend
// that doesn't have our block.
PutRPC
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
,
blockNumberSync
uint64
)
error
}
}
func
(
c
*
CachedRPC
)
Expiration
()
time
.
Time
{
type
rpcCache
struct
{
return
time
.
Unix
(
0
,
c
.
TTL
*
int64
(
time
.
Millisecond
))
cache
Cache
handlers
map
[
string
]
RPCMethodHandler
}
}
func
newRPCCache
(
cache
Cache
,
getLatestBlockNumFn
GetLatestBlockNumFn
)
RPCCache
{
func
newRPCCache
(
cache
Cache
,
getLatestBlockNumFn
GetLatestBlockNumFn
,
getLatestGasPriceFn
GetLatestGasPriceFn
)
RPCCache
{
handlers
:=
map
[
string
]
RPCMethodHandler
{
handlers
:=
map
[
string
]
RPCMethodHandler
{
"eth_chainId"
:
&
StaticRPCMethodHandler
{
"eth_chainId"
},
"eth_chainId"
:
&
StaticMethodHandler
{},
"net_version"
:
&
StaticRPCMethodHandler
{
"net_version"
},
"net_version"
:
&
StaticMethodHandler
{},
"eth_getBlockByNumber"
:
&
EthGetBlockByNumberMethod
{
getLatestBlockNumFn
},
"eth_getBlockByNumber"
:
&
EthGetBlockByNumberMethodHandler
{
cache
,
getLatestBlockNumFn
},
"eth_getBlockRange"
:
&
EthGetBlockRangeMethod
{
getLatestBlockNumFn
},
"eth_getBlockRange"
:
&
EthGetBlockRangeMethodHandler
{
cache
,
getLatestBlockNumFn
},
"eth_blockNumber"
:
&
EthBlockNumberMethodHandler
{
getLatestBlockNumFn
},
"eth_gasPrice"
:
&
EthGasPriceMethodHandler
{
getLatestGasPriceFn
},
"eth_call"
:
&
EthCallMethodHandler
{
cache
,
getLatestBlockNumFn
},
}
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
)
{
func
(
c
*
rpcCache
)
GetRPC
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
...
@@ -123,90 +125,13 @@ func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) {
...
@@ -123,90 +125,13 @@ func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) {
if
handler
==
nil
{
if
handler
==
nil
{
return
nil
,
nil
return
nil
,
nil
}
}
cacheable
,
err
:=
handler
.
IsCacheable
(
req
)
return
handler
.
GetRPCMethod
(
ctx
,
req
)
if
err
!=
nil
{
return
nil
,
err
}
if
!
cacheable
{
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
}
item
:=
new
(
CachedRPC
)
if
err
:=
json
.
Unmarshal
(
val
,
item
);
err
!=
nil
{
return
nil
,
err
}
expired
:=
item
.
Expiration
()
.
After
(
time
.
Now
())
curBlockNum
,
err
:=
c
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
if
curBlockNum
>
item
.
BlockNum
&&
expired
{
// TODO: what to do with expired items? Ideally they shouldn't count towards recency
return
nil
,
nil
}
else
if
curBlockNum
<
item
.
BlockNum
{
// reorg?
return
nil
,
nil
}
RecordCacheHit
(
req
.
Method
)
res
:=
item
.
Res
res
.
ID
=
req
.
ID
return
res
,
nil
/*
res := new(RPCRes)
err = json.Unmarshal(val, res)
if err != nil {
return nil, err
}
res.ID = req.ID
return res, nil
*/
}
}
func
(
c
*
rpcCache
)
PutRPC
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
)
error
{
func
(
c
*
rpcCache
)
PutRPC
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
,
blockNumberSync
uint64
)
error
{
handler
:=
c
.
handlers
[
req
.
Method
]
handler
:=
c
.
handlers
[
req
.
Method
]
if
handler
==
nil
{
if
handler
==
nil
{
return
nil
return
nil
}
}
cacheable
,
err
:=
handler
.
IsCacheable
(
req
)
return
handler
.
PutRPCMethod
(
ctx
,
req
,
res
,
blockNumberSync
)
if
err
!=
nil
{
return
err
}
if
!
cacheable
{
return
nil
}
requiresConfirmations
,
err
:=
handler
.
RequiresUnconfirmedBlocks
(
ctx
,
req
)
if
err
!=
nil
{
return
err
}
if
requiresConfirmations
{
return
nil
}
blockNum
,
err
:=
c
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
err
}
key
:=
handler
.
CacheKey
(
req
)
item
:=
CachedRPC
{
BlockNum
:
blockNum
,
Res
:
res
,
TTL
:
time
.
Now
()
.
UnixNano
()
/
int64
(
time
.
Millisecond
)}
val
:=
item
.
Encode
()
//val := mustMarshalJSON(res)
encodedVal
:=
snappy
.
Encode
(
nil
,
val
)
return
c
.
cache
.
Put
(
ctx
,
key
,
string
(
encodedVal
))
}
}
go/proxyd/cache_test.go
View file @
9763200f
...
@@ -5,18 +5,19 @@ import (
...
@@ -5,18 +5,19 @@ import (
"math"
"math"
"strconv"
"strconv"
"testing"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
)
)
func
TestRPCCache
Whitelist
(
t
*
testing
.
T
)
{
func
TestRPCCache
ImmutableRPCs
(
t
*
testing
.
T
)
{
const
blockHead
=
math
.
MaxUint64
const
blockHead
=
math
.
MaxUint64
ctx
:=
context
.
Background
()
ctx
:=
context
.
Background
()
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
getBlockNum
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
return
blockHead
,
nil
}
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
getBlockNum
,
nil
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
rpcs
:=
[]
struct
{
...
@@ -110,7 +111,7 @@ func TestRPCCacheWhitelist(t *testing.T) {
...
@@ -110,7 +111,7 @@ func TestRPCCacheWhitelist(t *testing.T) {
for
_
,
rpc
:=
range
rpcs
{
for
_
,
rpc
:=
range
rpcs
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
)
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
...
@@ -120,6 +121,72 @@ func TestRPCCacheWhitelist(t *testing.T) {
...
@@ -120,6 +121,72 @@ 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
)
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
,
blockHead
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
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
)
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
,
blockHead
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
}
func
TestRPCCacheUnsupportedMethod
(
t
*
testing
.
T
)
{
func
TestRPCCacheUnsupportedMethod
(
t
*
testing
.
T
)
{
const
blockHead
=
math
.
MaxUint64
const
blockHead
=
math
.
MaxUint64
ctx
:=
context
.
Background
()
ctx
:=
context
.
Background
()
...
@@ -127,21 +194,21 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) {
...
@@ -127,21 +194,21 @@ func TestRPCCacheUnsupportedMethod(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
return
blockHead
,
nil
}
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
JSONRPC
:
"2.0"
,
Method
:
"eth_
blockNumber
"
,
Method
:
"eth_
syncing
"
,
ID
:
ID
,
ID
:
ID
,
}
}
res
:=
&
RPCRes
{
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
JSONRPC
:
"2.0"
,
Result
:
`0x1000`
,
Result
:
false
,
ID
:
ID
,
ID
:
ID
,
}
}
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
)
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
...
@@ -156,7 +223,7 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
...
@@ -156,7 +223,7 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
return
blockHead
,
nil
}
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
rpcs
:=
[]
struct
{
...
@@ -164,20 +231,22 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
...
@@ -164,20 +231,22 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
res
*
RPCRes
res
*
RPCRes
name
string
name
string
}{
}{
{
/*
req
:
&
RPCReq
{
{
JSONRPC
:
"2.0"
,
req: &RPCReq{
Method
:
"eth_getBlockByNumber"
,
JSONRPC: "2.0",
Params
:
[]
byte
(
`["0x1", false]`
),
Method: "eth_getBlockByNumber",
ID
:
ID
,
Params: []byte(`["0x1", false]`),
},
ID: ID,
res
:
&
RPCRes
{
},
JSONRPC
:
"2.0"
,
res: &RPCRes{
Result
:
`{"difficulty": "0x1", "number": "0x1"}`
,
JSONRPC: "2.0",
ID
:
ID
,
Result: `{"difficulty": "0x1", "number": "0x1"}`,
ID: ID,
},
name: "recent block num",
},
},
name
:
"recent block num"
,
*/
},
{
{
req
:
&
RPCReq
{
req
:
&
RPCReq
{
JSONRPC
:
"2.0"
,
JSONRPC
:
"2.0"
,
...
@@ -210,7 +279,7 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
...
@@ -210,7 +279,7 @@ func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
for
_
,
rpc
:=
range
rpcs
{
for
_
,
rpc
:=
range
rpcs
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
)
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
...
@@ -227,7 +296,7 @@ func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
...
@@ -227,7 +296,7 @@ func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
return
blockHead
,
nil
}
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
req
:=
&
RPCReq
{
...
@@ -242,7 +311,7 @@ func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
...
@@ -242,7 +311,7 @@ func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
ID
:
ID
,
ID
:
ID
,
}
}
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
)
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
,
blockHead
)
require
.
Error
(
t
,
err
)
require
.
Error
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
...
@@ -257,7 +326,7 @@ func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
...
@@ -257,7 +326,7 @@ func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
return
blockHead
,
nil
}
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
rpcs
:=
[]
struct
{
...
@@ -265,20 +334,22 @@ func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
...
@@ -265,20 +334,22 @@ func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
res
*
RPCRes
res
*
RPCRes
name
string
name
string
}{
}{
{
/*
req
:
&
RPCReq
{
{
JSONRPC
:
"2.0"
,
req: &RPCReq{
Method
:
"eth_getBlockRange"
,
JSONRPC: "2.0",
Params
:
[]
byte
(
`["0x1", "0x1000", false]`
),
Method: "eth_getBlockRange",
ID
:
ID
,
Params: []byte(`["0x1", "0x1000", false]`),
ID: ID,
},
res: &RPCRes{
JSONRPC: "2.0",
Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
ID: ID,
},
name: "recent block num",
},
},
res
:
&
RPCRes
{
*/
JSONRPC
:
"2.0"
,
Result
:
`[{"number": "0x1"}, {"number": "0x2"}]`
,
ID
:
ID
,
},
name
:
"recent block num"
,
},
{
{
req
:
&
RPCReq
{
req
:
&
RPCReq
{
JSONRPC
:
"2.0"
,
JSONRPC
:
"2.0"
,
...
@@ -325,7 +396,7 @@ func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
...
@@ -325,7 +396,7 @@ func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
for
_
,
rpc
:=
range
rpcs
{
for
_
,
rpc
:=
range
rpcs
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
)
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
...
@@ -342,7 +413,7 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
...
@@ -342,7 +413,7 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
fn
:=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
return
blockHead
,
nil
return
blockHead
,
nil
}
}
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
)
cache
:=
newRPCCache
(
newMemoryCache
(),
fn
,
nil
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
rpcs
:=
[]
struct
{
rpcs
:=
[]
struct
{
...
@@ -382,7 +453,7 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
...
@@ -382,7 +453,7 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
for
_
,
rpc
:=
range
rpcs
{
for
_
,
rpc
:=
range
rpcs
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
rpc
.
name
,
func
(
t
*
testing
.
T
)
{
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
)
err
:=
cache
.
PutRPC
(
ctx
,
rpc
.
req
,
rpc
.
res
,
blockHead
)
require
.
Error
(
t
,
err
)
require
.
Error
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
rpc
.
req
)
...
@@ -391,3 +462,58 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
...
@@ -391,3 +462,58 @@ func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
})
})
}
}
}
}
func
TestRPCCacheEthCall
(
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
)
ID
:=
[]
byte
(
strconv
.
Itoa
(
1
))
req
:=
&
RPCReq
{
JSONRPC
:
"2.0"
,
Method
:
"eth_call"
,
Params
:
[]
byte
(
`{}`
),
ID
:
ID
,
}
res
:=
&
RPCRes
{
JSONRPC
:
"2.0"
,
Result
:
`0x0`
,
ID
:
ID
,
}
err
:=
cache
.
PutRPC
(
ctx
,
req
,
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
:=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
// scenario: no new block, but we've exceeded cacheTTL
cacheTTL
=
24
*
time
.
Hour
err
=
cache
.
PutRPC
(
ctx
,
req
,
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
cachedRes
,
err
=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
// scenario: new block, but cached TTL is live
cacheTTL
=
24
*
time
.
Hour
err
=
cache
.
PutRPC
(
ctx
,
req
,
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
blockHead
+=
1
// new block
cachedRes
,
err
=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
res
,
cachedRes
)
// scenario: new bloc, cache TTL exceeded; cache invalidation
cacheTTL
=
0
*
time
.
Second
err
=
cache
.
PutRPC
(
ctx
,
req
,
res
,
blockHead
)
require
.
NoError
(
t
,
err
)
blockHead
+=
1
// new block
cachedRes
,
err
=
cache
.
GetRPC
(
ctx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
cachedRes
)
}
go/proxyd/l
atestblock
.go
→
go/proxyd/l
vc
.go
View file @
9763200f
...
@@ -9,85 +9,77 @@ import (
...
@@ -9,85 +9,77 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
)
)
const
blockHeadSyncPeriod
=
1
*
time
.
Second
const
cacheSyncRate
=
1
*
time
.
Second
type
LatestBlockHead
struct
{
type
lvcUpdateFn
func
(
context
.
Context
,
*
ethclient
.
Client
)
(
interface
{},
error
)
url
string
client
*
ethclient
.
Client
quit
chan
struct
{}
done
chan
struct
{}
mutex
sync
.
RWMutex
type
EthLastValueCache
struct
{
blockNum
uint64
client
*
ethclient
.
Client
updater
lvcUpdateFn
quit
chan
struct
{}
mutex
sync
.
RWMutex
value
interface
{}
}
}
func
newLatestBlockHead
(
url
string
)
(
*
LatestBlockHead
,
error
)
{
func
newLVC
(
client
*
ethclient
.
Client
,
updater
lvcUpdateFn
)
*
EthLastValueCache
{
client
,
err
:=
ethclient
.
Dial
(
url
)
return
&
EthLastValueCache
{
if
err
!=
nil
{
client
:
client
,
return
nil
,
err
updater
:
updater
,
quit
:
make
(
chan
struct
{}),
}
}
return
&
LatestBlockHead
{
url
:
url
,
client
:
client
,
quit
:
make
(
chan
struct
{}),
done
:
make
(
chan
struct
{}),
},
nil
}
}
func
(
h
*
LatestBlockHead
)
Start
()
{
func
(
h
*
EthLastValueCache
)
Start
()
{
go
func
()
{
go
func
()
{
ticker
:=
time
.
NewTicker
(
blockHeadSyncPeriod
)
ticker
:=
time
.
NewTicker
(
cacheSyncRate
)
defer
ticker
.
Stop
()
defer
ticker
.
Stop
()
for
{
for
{
select
{
select
{
case
<-
ticker
.
C
:
case
<-
ticker
.
C
:
blockNum
,
err
:=
h
.
getBlockNum
()
value
,
err
:=
h
.
getUpdate
()
if
err
!=
nil
{
if
err
!=
nil
{
log
.
Error
(
"error retrieving latest
block number
"
,
"error"
,
err
)
log
.
Error
(
"error retrieving latest
value
"
,
"error"
,
err
)
continue
continue
}
}
log
.
Trace
(
"polling
block number"
,
"blockNum"
,
blockNum
)
log
.
Trace
(
"polling
latest value"
,
"value"
,
value
)
h
.
mutex
.
Lock
()
h
.
mutex
.
Lock
()
h
.
blockNum
=
blockNum
h
.
value
=
value
h
.
mutex
.
Unlock
()
h
.
mutex
.
Unlock
()
case
<-
h
.
quit
:
case
<-
h
.
quit
:
close
(
h
.
done
)
return
return
}
}
}
}
}()
}()
}
}
func
(
h
*
LatestBlockHead
)
getBlockNum
()
(
uint64
,
error
)
{
func
(
h
*
EthLastValueCache
)
getUpdate
()
(
interface
{}
,
error
)
{
const
maxRetries
=
5
const
maxRetries
=
5
var
err
error
var
err
error
for
i
:=
0
;
i
<=
maxRetries
;
i
++
{
for
i
:=
0
;
i
<=
maxRetries
;
i
++
{
var
blockNum
uint64
var
value
interface
{}
blockNum
,
err
=
h
.
client
.
BlockNumber
(
context
.
Background
()
)
value
,
err
=
h
.
updater
(
context
.
Background
(),
h
.
client
)
if
err
!=
nil
{
if
err
!=
nil
{
backoff
:=
calcBackoff
(
i
)
backoff
:=
calcBackoff
(
i
)
log
.
Warn
(
"http operation failed. retrying..."
,
"error"
,
err
,
"backoff"
,
backoff
)
log
.
Warn
(
"http operation failed. retrying..."
,
"error"
,
err
,
"backoff"
,
backoff
)
time
.
Sleep
(
backoff
)
time
.
Sleep
(
backoff
)
continue
continue
}
}
return
blockNum
,
nil
return
value
,
nil
}
}
return
0
,
wrapErr
(
err
,
"exceeded retries"
)
return
0
,
wrapErr
(
err
,
"exceeded retries"
)
}
}
func
(
h
*
LatestBlockHead
)
Stop
()
{
func
(
h
*
EthLastValueCache
)
Stop
()
{
close
(
h
.
quit
)
close
(
h
.
quit
)
<-
h
.
done
h
.
client
.
Close
()
}
}
func
(
h
*
LatestBlockHead
)
GetBlockNum
()
uint64
{
func
(
h
*
EthLastValueCache
)
Read
()
interface
{}
{
h
.
mutex
.
RLock
()
h
.
mutex
.
RLock
()
defer
h
.
mutex
.
RUnlock
()
defer
h
.
mutex
.
RUnlock
()
return
h
.
blockNum
return
h
.
value
}
}
go/proxyd/methods.go
View file @
9763200f
...
@@ -5,36 +5,56 @@ import (
...
@@ -5,36 +5,56 @@ import (
"encoding/json"
"encoding/json"
"errors"
"errors"
"fmt"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/snappy"
)
)
var
errInvalidRPCParams
=
errors
.
New
(
"invalid RPC params"
)
var
(
cacheTTL
=
5
*
time
.
Second
errInvalidRPCParams
=
errors
.
New
(
"invalid RPC params"
)
)
type
RPCMethodHandler
interface
{
type
RPCMethodHandler
interface
{
CacheKey
(
req
*
RPCReq
)
string
GetRPCMethod
(
context
.
Context
,
*
RPCReq
)
(
*
RPCRes
,
error
)
IsCacheable
(
req
*
RPCReq
)
(
bool
,
error
)
PutRPCMethod
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
,
uint64
)
error
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
}
}
type
StaticRPCMethodHandler
struct
{
type
StaticMethodHandler
struct
{
method
string
cache
*
RPCRes
m
sync
.
RWMutex
}
}
func
(
s
*
StaticRPCMethodHandler
)
CacheKey
(
req
*
RPCReq
)
string
{
func
(
e
*
StaticMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
return
fmt
.
Sprintf
(
"method:%s"
,
s
.
method
)
e
.
m
.
RLock
()
cache
:=
e
.
cache
e
.
m
.
RUnlock
()
if
cache
!=
nil
{
cache
=
copyRes
(
cache
)
cache
.
ID
=
req
.
ID
}
return
cache
,
nil
}
}
func
(
s
*
StaticRPCMethodHandler
)
IsCacheable
(
*
RPCReq
)
(
bool
,
error
)
{
return
true
,
nil
}
func
(
e
*
StaticMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
,
blockNumSync
uint64
)
error
{
func
(
s
*
StaticRPCMethodHandler
)
RequiresUnconfirmedBlocks
(
context
.
Context
,
*
RPCReq
)
(
bool
,
error
)
{
e
.
m
.
Lock
()
return
false
,
nil
if
e
.
cache
==
nil
{
e
.
cache
=
copyRes
(
res
)
}
e
.
m
.
Unlock
()
return
nil
}
}
type
EthGetBlockByNumberMethod
struct
{
type
EthGetBlockByNumberMethodHandler
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
getLatestBlockNumFn
GetLatestBlockNumFn
}
}
func
(
e
*
EthGetBlockByNumberMethod
)
C
acheKey
(
req
*
RPCReq
)
string
{
func
(
e
*
EthGetBlockByNumberMethod
Handler
)
c
acheKey
(
req
*
RPCReq
)
string
{
input
,
includeTx
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
input
,
includeTx
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
if
err
!=
nil
{
if
err
!=
nil
{
return
""
return
""
...
@@ -42,7 +62,7 @@ func (e *EthGetBlockByNumberMethod) CacheKey(req *RPCReq) string {
...
@@ -42,7 +62,7 @@ func (e *EthGetBlockByNumberMethod) CacheKey(req *RPCReq) string {
return
fmt
.
Sprintf
(
"method:eth_getBlockByNumber:%s:%t"
,
input
,
includeTx
)
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
)
blockNum
,
_
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
if
err
!=
nil
{
if
err
!=
nil
{
return
false
,
err
return
false
,
err
...
@@ -50,33 +70,28 @@ func (e *EthGetBlockByNumberMethod) IsCacheable(req *RPCReq) (bool, error) {
...
@@ -50,33 +70,28 @@ func (e *EthGetBlockByNumberMethod) IsCacheable(req *RPCReq) (bool, error) {
return
!
isBlockDependentParam
(
blockNum
),
nil
return
!
isBlockDependentParam
(
blockNum
),
nil
}
}
func
(
e
*
EthGetBlockByNumberMethod
)
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
{
func
(
e
*
EthGetBlockByNumberMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
false
,
err
}
blockInput
,
_
,
err
:=
decodeGetBlockByNumberParams
(
req
.
Params
)
if
err
!=
nil
{
return
false
,
err
}
}
if
isBlockDependentParam
(
blockInput
)
{
key
:=
e
.
cacheKey
(
req
)
return
true
,
nil
return
getBlockDependentCachedRPCResponse
(
ctx
,
e
.
cache
,
e
.
getLatestBlockNumFn
,
key
,
req
)
}
}
if
blockInput
==
"earliest"
{
return
false
,
nil
func
(
e
*
EthGetBlockByNumberMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
,
blockNumberSync
uint64
)
error
{
}
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
blockNum
,
err
:=
decodeBlockInput
(
blockInput
)
return
err
if
err
!=
nil
{
return
false
,
err
}
}
return
curBlock
<=
blockNum
+
numBlockConfirmations
,
nil
key
:=
e
.
cacheKey
(
req
)
return
putBlockDependentCachedRPCResponse
(
ctx
,
e
.
cache
,
key
,
res
,
blockNumberSync
)
}
}
type
EthGetBlockRangeMethod
struct
{
type
EthGetBlockRangeMethodHandler
struct
{
cache
Cache
getLatestBlockNumFn
GetLatestBlockNumFn
getLatestBlockNumFn
GetLatestBlockNumFn
}
}
func
(
e
*
EthGetBlockRangeMethod
)
C
acheKey
(
req
*
RPCReq
)
string
{
func
(
e
*
EthGetBlockRangeMethod
Handler
)
c
acheKey
(
req
*
RPCReq
)
string
{
start
,
end
,
includeTx
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
start
,
end
,
includeTx
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
if
err
!=
nil
{
if
err
!=
nil
{
return
""
return
""
...
@@ -84,7 +99,7 @@ func (e *EthGetBlockRangeMethod) CacheKey(req *RPCReq) string {
...
@@ -84,7 +99,7 @@ func (e *EthGetBlockRangeMethod) CacheKey(req *RPCReq) string {
return
fmt
.
Sprintf
(
"method:eth_getBlockRange:%s:%s:%t"
,
start
,
end
,
includeTx
)
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
)
start
,
end
,
_
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
if
err
!=
nil
{
if
err
!=
nil
{
return
false
,
err
return
false
,
err
...
@@ -92,58 +107,85 @@ func (e *EthGetBlockRangeMethod) IsCacheable(req *RPCReq) (bool, error) {
...
@@ -92,58 +107,85 @@ func (e *EthGetBlockRangeMethod) IsCacheable(req *RPCReq) (bool, error) {
return
!
isBlockDependentParam
(
start
)
&&
!
isBlockDependentParam
(
end
),
nil
return
!
isBlockDependentParam
(
start
)
&&
!
isBlockDependentParam
(
end
),
nil
}
}
func
(
e
*
EthGetBlockRangeMethod
)
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
{
func
(
e
*
EthGetBlockRangeMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
curBlock
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
false
,
err
}
}
key
:=
e
.
cacheKey
(
req
)
return
getBlockDependentCachedRPCResponse
(
ctx
,
e
.
cache
,
e
.
getLatestBlockNumFn
,
key
,
req
)
}
start
,
end
,
_
,
err
:=
decodeGetBlockRangeParams
(
req
.
Params
)
func
(
e
*
EthGetBlockRangeMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
,
blockNumberSync
uint64
)
error
{
if
err
!=
nil
{
if
ok
,
err
:=
e
.
cacheable
(
req
);
!
ok
||
err
!=
nil
{
return
false
,
err
return
err
}
if
isBlockDependentParam
(
start
)
||
isBlockDependentParam
(
end
)
{
return
true
,
nil
}
if
start
==
"earliest"
&&
end
==
"earliest"
{
return
false
,
nil
}
}
key
:=
e
.
cacheKey
(
req
)
return
putBlockDependentCachedRPCResponse
(
ctx
,
e
.
cache
,
key
,
res
,
blockNumberSync
)
}
if
start
!=
"earliest"
{
type
EthCallMethodHandler
struct
{
startNum
,
err
:=
decodeBlockInput
(
start
)
cache
Cache
if
err
!=
nil
{
getLatestBlockNumFn
GetLatestBlockNumFn
return
false
,
err
}
}
if
curBlock
<=
startNum
+
numBlockConfirmations
{
func
(
e
*
EthCallMethodHandler
)
cacheKey
(
req
*
RPCReq
)
string
{
return
true
,
nil
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"`
}
}
if
end
!=
"earliest"
{
var
params
ethCallParams
endNum
,
err
:=
decodeBlockInput
(
end
)
if
err
:=
json
.
Unmarshal
(
req
.
Params
,
&
params
);
err
!=
nil
{
if
err
!=
nil
{
return
""
return
false
,
err
}
if
curBlock
<=
endNum
+
numBlockConfirmations
{
return
true
,
nil
}
}
}
return
false
,
nil
// ensure the order is consistent
keyParams
:=
fmt
.
Sprintf
(
"%s:%s:%s:%s:%s:%s"
,
params
.
From
,
params
.
To
,
params
.
Gas
,
params
.
GasPrice
,
params
.
Value
,
params
.
Data
)
return
fmt
.
Sprintf
(
"method:eth_call:%s"
,
keyParams
)
}
}
type
EthGasPriceMethod
struct
{
func
(
e
*
EthCallMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
key
:=
e
.
cacheKey
(
req
)
return
getBlockDependentCachedRPCResponse
(
ctx
,
e
.
cache
,
e
.
getLatestBlockNumFn
,
key
,
req
)
}
func
(
e
*
EthCallMethodHandler
)
PutRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
,
res
*
RPCRes
,
blockNumberSync
uint64
)
error
{
key
:=
e
.
cacheKey
(
req
)
return
putBlockDependentCachedRPCResponse
(
ctx
,
e
.
cache
,
key
,
res
,
blockNumberSync
)
}
type
EthBlockNumberMethodHandler
struct
{
getLatestBlockNumFn
GetLatestBlockNumFn
getLatestBlockNumFn
GetLatestBlockNumFn
}
}
func
(
e
*
EthGasPriceMethod
)
CacheKey
(
key
*
RPCReq
)
string
{
func
(
e
*
EthBlockNumberMethodHandler
)
GetRPCMethod
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
return
fmt
.
Sprintf
(
"method:%s"
,
"eth_gasPrice"
)
blockNum
,
err
:=
e
.
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
return
makeRPCRes
(
req
,
hexutil
.
EncodeUint64
(
blockNum
)),
nil
}
}
func
(
e
*
Eth
GasPriceMethod
)
IsCacheable
(
req
*
RPCReq
)
(
bool
,
error
)
{
func
(
e
*
Eth
BlockNumberMethodHandler
)
PutRPCMethod
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
,
uint64
)
error
{
return
true
,
nil
return
nil
}
}
func
(
e
*
EthGasPriceMethod
)
RequiresUnconfirmedBlocks
(
ctx
context
.
Context
,
req
*
RPCReq
)
(
bool
,
error
)
{
type
EthGasPriceMethodHandler
struct
{
return
false
,
nil
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
,
uint64
)
error
{
return
nil
}
}
func
isBlockDependentParam
(
s
string
)
bool
{
func
isBlockDependentParam
(
s
string
)
bool
{
...
@@ -209,3 +251,98 @@ func validBlockInput(input string) bool {
...
@@ -209,3 +251,98 @@ func validBlockInput(input string) bool {
_
,
err
:=
decodeBlockInput
(
input
)
_
,
err
:=
decodeBlockInput
(
input
)
return
err
==
nil
return
err
==
nil
}
}
func
makeRPCRes
(
req
*
RPCReq
,
result
interface
{})
*
RPCRes
{
return
&
RPCRes
{
JSONRPC
:
JSONRPCVersion
,
ID
:
req
.
ID
,
Result
:
result
,
}
}
func
copyResError
(
err
*
RPCErr
)
*
RPCErr
{
if
err
==
nil
{
return
nil
}
return
&
RPCErr
{
Code
:
err
.
Code
,
Message
:
err
.
Message
,
HTTPErrorCode
:
err
.
HTTPErrorCode
,
}
}
func
copyRes
(
res
*
RPCRes
)
*
RPCRes
{
return
&
RPCRes
{
JSONRPC
:
res
.
JSONRPC
,
Result
:
res
.
Result
,
Error
:
copyResError
(
res
.
Error
),
ID
:
res
.
ID
,
}
}
type
CachedRPC
struct
{
BlockNum
uint64
`json:"blockNum"`
Res
*
RPCRes
`json:"res"`
Expiration
int64
`json:"expiration"`
// in millis since epoch
}
func
(
c
*
CachedRPC
)
Encode
()
[]
byte
{
return
mustMarshalJSON
(
c
)
}
func
(
c
*
CachedRPC
)
Decode
(
b
[]
byte
)
error
{
return
json
.
Unmarshal
(
b
,
c
)
}
func
(
c
*
CachedRPC
)
ExpirationTime
()
time
.
Time
{
return
time
.
Unix
(
0
,
c
.
Expiration
*
int64
(
time
.
Millisecond
))
}
func
getBlockDependentCachedRPCResponse
(
ctx
context
.
Context
,
cache
Cache
,
getLatestBlockNumFn
GetLatestBlockNumFn
,
key
string
,
req
*
RPCReq
)
(
*
RPCRes
,
error
)
{
encodedVal
,
err
:=
cache
.
Get
(
ctx
,
key
)
if
err
!=
nil
{
return
nil
,
err
}
if
encodedVal
==
""
{
return
nil
,
nil
}
val
,
err
:=
snappy
.
Decode
(
nil
,
[]
byte
(
encodedVal
))
if
err
!=
nil
{
return
nil
,
err
}
item
:=
new
(
CachedRPC
)
if
err
:=
json
.
Unmarshal
(
val
,
item
);
err
!=
nil
{
return
nil
,
err
}
curBlockNum
,
err
:=
getLatestBlockNumFn
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
expired
:=
time
.
Now
()
.
After
(
item
.
ExpirationTime
())
if
(
curBlockNum
>
item
.
BlockNum
&&
expired
)
||
(
curBlockNum
<
item
.
BlockNum
)
/* desync: reorgs, backend failover */
{
// TODO: be careful removing keys once there are multiple proxyd instances
err
:=
cache
.
Remove
(
ctx
,
key
)
return
nil
,
err
}
res
:=
item
.
Res
res
.
ID
=
req
.
ID
return
res
,
nil
}
func
putBlockDependentCachedRPCResponse
(
ctx
context
.
Context
,
cache
Cache
,
key
string
,
res
*
RPCRes
,
blockNumberSync
uint64
)
error
{
if
key
==
""
{
return
nil
}
item
:=
CachedRPC
{
BlockNum
:
blockNumberSync
,
Res
:
res
,
Expiration
:
time
.
Now
()
.
Add
(
cacheTTL
)
.
UnixNano
()
/
int64
(
time
.
Millisecond
),
}
val
:=
item
.
Encode
()
encodedVal
:=
snappy
.
Encode
(
nil
,
val
)
return
cache
.
Put
(
ctx
,
key
,
string
(
encodedVal
),
cacheTTL
)
}
go/proxyd/proxyd.go
View file @
9763200f
...
@@ -9,6 +9,7 @@ import (
...
@@ -9,6 +9,7 @@ import (
"os"
"os"
"time"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
)
...
@@ -109,7 +110,12 @@ func Start(config *Config) (func(), error) {
...
@@ -109,7 +110,12 @@ func Start(config *Config) (func(), error) {
opts
=
append
(
opts
,
WithStrippedTrailingXFF
())
opts
=
append
(
opts
,
WithStrippedTrailingXFF
())
}
}
opts
=
append
(
opts
,
WithProxydIP
(
os
.
Getenv
(
"PROXYD_IP"
)))
opts
=
append
(
opts
,
WithProxydIP
(
os
.
Getenv
(
"PROXYD_IP"
)))
back
:=
NewBackend
(
name
,
rpcURL
,
wsURL
,
lim
,
opts
...
)
back
,
err
:=
NewBackend
(
name
,
rpcURL
,
wsURL
,
lim
,
opts
...
)
if
err
!=
nil
{
return
err
}
back
.
Start
()
defer
back
.
Stop
()
backendNames
=
append
(
backendNames
,
name
)
backendNames
=
append
(
backendNames
,
name
)
backendsByName
[
name
]
=
back
backendsByName
[
name
]
=
back
log
.
Info
(
"configured backend"
,
"name"
,
name
,
"rpc_url"
,
rpcURL
,
"ws_url"
,
wsURL
)
log
.
Info
(
"configured backend"
,
"name"
,
name
,
"rpc_url"
,
rpcURL
,
"ws_url"
,
wsURL
)
...
@@ -163,7 +169,6 @@ func Start(config *Config) (func(), error) {
...
@@ -163,7 +169,6 @@ func Start(config *Config) (func(), error) {
}
}
var
rpcCache
RPCCache
var
rpcCache
RPCCache
var
latestHead
*
LatestBlockHead
if
config
.
Cache
.
Enabled
{
if
config
.
Cache
.
Enabled
{
var
getLatestBlockNumFn
GetLatestBlockNumFn
var
getLatestBlockNumFn
GetLatestBlockNumFn
if
config
.
Cache
.
BlockSyncRPCURL
==
""
{
if
config
.
Cache
.
BlockSyncRPCURL
==
""
{
...
@@ -184,16 +189,18 @@ func Start(config *Config) (func(), error) {
...
@@ -184,16 +189,18 @@ func Start(config *Config) (func(), error) {
cache
=
newMemoryCache
()
cache
=
newMemoryCache
()
}
}
latestHead
,
err
=
newLatestBlockHead
(
blockSyncRPCURL
)
if
config
.
Cache
.
BlockSyncRPCURL
==
""
{
return
fmt
.
Errorf
(
"block sync node config is required for caching"
)
}
ethClient
,
err
:=
ethclient
.
Dial
(
config
.
Cache
.
BlockSyncRPCURL
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
latestHead
.
Start
()
lvcCtx
,
lvcCancel
:=
context
.
WithCancel
(
context
.
Background
())
defer
lvcCancel
()
getLatestBlockNumFn
=
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
blockNumFn
:=
makeGetLatestBlockNumFn
(
ethClient
,
lvcCtx
.
Done
())
return
latestHead
.
GetBlockNum
(),
nil
gasPriceFn
:=
makeGetLatestGasPriceFn
(
ethClient
,
lvcCtx
.
Done
())
}
rpcCache
=
newRPCCache
(
cache
,
blockNumFn
,
gasPriceFn
)
rpcCache
=
newRPCCache
(
cache
,
getLatestBlockNumFn
)
}
}
srv
:=
NewServer
(
srv
:=
NewServer
(
...
@@ -246,9 +253,7 @@ func Start(config *Config) (func(), error) {
...
@@ -246,9 +253,7 @@ func Start(config *Config) (func(), error) {
return
func
()
{
return
func
()
{
log
.
Info
(
"shutting down proxyd"
)
log
.
Info
(
"shutting down proxyd"
)
if
latestHead
!=
nil
{
// TODO(inphi): Stop LVCs here
latestHead
.
Stop
()
}
srv
.
Shutdown
()
srv
.
Shutdown
()
if
err
:=
lim
.
FlushBackendWSConns
(
backendNames
);
err
!=
nil
{
if
err
:=
lim
.
FlushBackendWSConns
(
backendNames
);
err
!=
nil
{
log
.
Error
(
"error flushing backend ws conns"
,
"err"
,
err
)
log
.
Error
(
"error flushing backend ws conns"
,
"err"
,
err
)
...
@@ -281,3 +286,39 @@ func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) {
...
@@ -281,3 +286,39 @@ func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) {
return
tlsConfig
,
nil
return
tlsConfig
,
nil
}
}
func
makeGetLatestBlockNumFn
(
client
*
ethclient
.
Client
,
quit
<-
chan
struct
{})
GetLatestBlockNumFn
{
lvc
:=
newLVC
(
client
,
func
(
ctx
context
.
Context
,
c
*
ethclient
.
Client
)
(
interface
{},
error
)
{
return
c
.
BlockNumber
(
ctx
)
})
lvc
.
Start
()
go
func
()
{
<-
quit
lvc
.
Stop
()
}()
return
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
value
:=
lvc
.
Read
()
if
value
==
nil
{
return
0
,
fmt
.
Errorf
(
"block number is unavailable"
)
}
return
value
.
(
uint64
),
nil
}
}
func
makeGetLatestGasPriceFn
(
client
*
ethclient
.
Client
,
quit
<-
chan
struct
{})
GetLatestGasPriceFn
{
lvc
:=
newLVC
(
client
,
func
(
ctx
context
.
Context
,
c
*
ethclient
.
Client
)
(
interface
{},
error
)
{
return
c
.
SuggestGasPrice
(
ctx
)
})
lvc
.
Start
()
go
func
()
{
<-
lvc
.
quit
lvc
.
Stop
()
}()
return
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
{
value
:=
lvc
.
Read
()
if
value
==
nil
{
return
0
,
fmt
.
Errorf
(
"gas price is unavailable"
)
}
return
value
.
(
uint64
),
nil
}
}
go/proxyd/server.go
View file @
9763200f
...
@@ -218,7 +218,9 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
...
@@ -218,7 +218,9 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
return
backendRes
return
backendRes
}
}
backendRes
,
err
=
s
.
backendGroups
[
group
]
.
Forward
(
ctx
,
req
)
// NOTE: We call into the specific backend here to ensure that the RPCRes is synchronized with the blockNum.
var
blockNum
uint64
backendRes
,
blockNum
,
err
=
s
.
backendGroups
[
group
]
.
Forward
(
ctx
,
req
)
if
err
!=
nil
{
if
err
!=
nil
{
log
.
Error
(
log
.
Error
(
"error forwarding RPC request"
,
"error forwarding RPC request"
,
...
@@ -230,7 +232,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
...
@@ -230,7 +232,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
}
}
if
backendRes
.
Error
==
nil
{
if
backendRes
.
Error
==
nil
{
if
err
=
s
.
cache
.
PutRPC
(
ctx
,
req
,
backendRes
);
err
!=
nil
{
if
err
=
s
.
cache
.
PutRPC
(
ctx
,
req
,
backendRes
,
blockNum
);
err
!=
nil
{
log
.
Warn
(
log
.
Warn
(
"cache put error"
,
"cache put error"
,
"req_id"
,
GetReqID
(
ctx
),
"req_id"
,
GetReqID
(
ctx
),
...
@@ -425,6 +427,6 @@ func (n *NoopRPCCache) GetRPC(context.Context, *RPCReq) (*RPCRes, error) {
...
@@ -425,6 +427,6 @@ func (n *NoopRPCCache) GetRPC(context.Context, *RPCReq) (*RPCRes, error) {
return
nil
,
nil
return
nil
,
nil
}
}
func
(
n
*
NoopRPCCache
)
PutRPC
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
)
error
{
func
(
n
*
NoopRPCCache
)
PutRPC
(
context
.
Context
,
*
RPCReq
,
*
RPCRes
,
uint64
)
error
{
return
nil
return
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