189 lines
6.0 KiB
Go
189 lines
6.0 KiB
Go
package integration_tests
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/ethereum-optimism/optimism/proxyd"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBatching(t *testing.T) {
|
|
config := ReadConfig("batching")
|
|
|
|
chainIDResponse1 := `{"jsonrpc": "2.0", "result": "hello1", "id": 1}`
|
|
chainIDResponse2 := `{"jsonrpc": "2.0", "result": "hello2", "id": 2}`
|
|
chainIDResponse3 := `{"jsonrpc": "2.0", "result": "hello3", "id": 3}`
|
|
netVersionResponse1 := `{"jsonrpc": "2.0", "result": "1.0", "id": 1}`
|
|
callResponse1 := `{"jsonrpc": "2.0", "result": "ekans1", "id": 1}`
|
|
|
|
ethAccountsResponse2 := `{"jsonrpc": "2.0", "result": [], "id": 2}`
|
|
|
|
backendResTooLargeResponse1 := `{"error":{"code":-32020,"message":"backend response too large"},"id":1,"jsonrpc":"2.0"}`
|
|
backendResTooLargeResponse2 := `{"error":{"code":-32020,"message":"backend response too large"},"id":2,"jsonrpc":"2.0"}`
|
|
|
|
type mockResult struct {
|
|
method string
|
|
id string
|
|
result interface{}
|
|
}
|
|
|
|
chainIDMock1 := mockResult{"eth_chainId", "1", "hello1"}
|
|
chainIDMock2 := mockResult{"eth_chainId", "2", "hello2"}
|
|
chainIDMock3 := mockResult{"eth_chainId", "3", "hello3"}
|
|
netVersionMock1 := mockResult{"net_version", "1", "1.0"}
|
|
callMock1 := mockResult{"eth_call", "1", "ekans1"}
|
|
|
|
tests := []struct {
|
|
name string
|
|
handler http.Handler
|
|
mocks []mockResult
|
|
reqs []*proxyd.RPCReq
|
|
expectedRes string
|
|
maxUpstreamBatchSize int
|
|
numExpectedForwards int
|
|
maxResponseSizeBytes int64
|
|
}{
|
|
{
|
|
name: "backend returns batches out of order",
|
|
mocks: []mockResult{chainIDMock1, chainIDMock2, chainIDMock3},
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "eth_chainId", nil),
|
|
NewRPCReq("2", "eth_chainId", nil),
|
|
NewRPCReq("3", "eth_chainId", nil),
|
|
},
|
|
expectedRes: asArray(chainIDResponse1, chainIDResponse2, chainIDResponse3),
|
|
maxUpstreamBatchSize: 2,
|
|
numExpectedForwards: 2,
|
|
},
|
|
{
|
|
// infura behavior
|
|
name: "backend returns single RPC response object as error",
|
|
handler: SingleResponseHandler(500, `{"jsonrpc":"2.0","error":{"code":-32001,"message":"internal server error"},"id":1}`),
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "eth_chainId", nil),
|
|
NewRPCReq("2", "eth_chainId", nil),
|
|
},
|
|
expectedRes: asArray(
|
|
`{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`,
|
|
`{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`,
|
|
),
|
|
maxUpstreamBatchSize: 10,
|
|
numExpectedForwards: 1,
|
|
},
|
|
{
|
|
name: "backend returns single RPC response object for minibatches",
|
|
handler: SingleResponseHandler(500, `{"jsonrpc":"2.0","error":{"code":-32001,"message":"internal server error"},"id":1}`),
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "eth_chainId", nil),
|
|
NewRPCReq("2", "eth_chainId", nil),
|
|
},
|
|
expectedRes: asArray(
|
|
`{"error":{"code":-32011,"message":"no backends available for method"},"id":1,"jsonrpc":"2.0"}`,
|
|
`{"error":{"code":-32011,"message":"no backends available for method"},"id":2,"jsonrpc":"2.0"}`,
|
|
),
|
|
maxUpstreamBatchSize: 1,
|
|
numExpectedForwards: 2,
|
|
},
|
|
{
|
|
name: "duplicate request ids are on distinct batches",
|
|
mocks: []mockResult{
|
|
netVersionMock1,
|
|
chainIDMock2,
|
|
chainIDMock1,
|
|
callMock1,
|
|
},
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "net_version", nil),
|
|
NewRPCReq("2", "eth_chainId", nil),
|
|
NewRPCReq("1", "eth_chainId", nil),
|
|
NewRPCReq("1", "eth_call", nil),
|
|
},
|
|
expectedRes: asArray(netVersionResponse1, chainIDResponse2, chainIDResponse1, callResponse1),
|
|
maxUpstreamBatchSize: 2,
|
|
numExpectedForwards: 3,
|
|
},
|
|
{
|
|
name: "over max size",
|
|
mocks: []mockResult{},
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "net_version", nil),
|
|
NewRPCReq("2", "eth_chainId", nil),
|
|
NewRPCReq("3", "eth_chainId", nil),
|
|
NewRPCReq("4", "eth_call", nil),
|
|
NewRPCReq("5", "eth_call", nil),
|
|
NewRPCReq("6", "eth_call", nil),
|
|
},
|
|
expectedRes: "{\"error\":{\"code\":-32014,\"message\":\"over batch size custom message\"},\"id\":null,\"jsonrpc\":\"2.0\"}",
|
|
maxUpstreamBatchSize: 2,
|
|
numExpectedForwards: 0,
|
|
},
|
|
{
|
|
name: "eth_accounts does not get forwarded",
|
|
mocks: []mockResult{
|
|
callMock1,
|
|
},
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "eth_call", nil),
|
|
NewRPCReq("2", "eth_accounts", nil),
|
|
},
|
|
expectedRes: asArray(callResponse1, ethAccountsResponse2),
|
|
maxUpstreamBatchSize: 2,
|
|
numExpectedForwards: 1,
|
|
},
|
|
{
|
|
name: "large upstream response gets dropped",
|
|
mocks: []mockResult{chainIDMock1, chainIDMock2},
|
|
reqs: []*proxyd.RPCReq{
|
|
NewRPCReq("1", "eth_chainId", nil),
|
|
NewRPCReq("2", "eth_chainId", nil),
|
|
},
|
|
expectedRes: asArray(backendResTooLargeResponse1, backendResTooLargeResponse2),
|
|
maxUpstreamBatchSize: 2,
|
|
numExpectedForwards: 1,
|
|
maxResponseSizeBytes: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
config.Server.MaxUpstreamBatchSize = tt.maxUpstreamBatchSize
|
|
config.BackendOptions.MaxResponseSizeBytes = tt.maxResponseSizeBytes
|
|
|
|
handler := tt.handler
|
|
if handler == nil {
|
|
router := NewBatchRPCResponseRouter()
|
|
for _, mock := range tt.mocks {
|
|
router.SetRoute(mock.method, mock.id, mock.result)
|
|
}
|
|
handler = router
|
|
}
|
|
|
|
goodBackend := NewMockBackend(handler)
|
|
defer goodBackend.Close()
|
|
require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", goodBackend.URL()))
|
|
|
|
client := NewProxydClient("http://127.0.0.1:8545")
|
|
_, shutdown, err := proxyd.Start(config)
|
|
require.NoError(t, err)
|
|
defer shutdown()
|
|
|
|
res, statusCode, err := client.SendBatchRPC(tt.reqs...)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, statusCode)
|
|
RequireEqualJSON(t, []byte(tt.expectedRes), res)
|
|
|
|
if tt.numExpectedForwards != 0 {
|
|
require.Equal(t, tt.numExpectedForwards, len(goodBackend.Requests()))
|
|
}
|
|
|
|
if handler, ok := handler.(*BatchRPCResponseRouter); ok {
|
|
for i, mock := range tt.mocks {
|
|
require.Equal(t, 1, handler.GetNumCalls(mock.method, mock.id), i)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|