Skip to content

Commit d7f1fa5

Browse files
authored
handle batch EVM requests (#84)
* add decodeRequestMiddleware & batch request decoding * progress towards batch request processing * hackily overriding headers fixes response * refactor, headers still incorrect * handle passing response headers * handle empty batch requests * rename & add docs to batchResponseWriter * make batch requests concurrent * cleanup & add doc comments to all funcs * unit test the cache state header response * fix default CORs header for cache misses * fix cache hit counting * reduce debug log noise * add happy path testing for batch evm requests * lol, don't fail b/c it is 4PM * pass through error statuses! * test more unhappy paths * update missing .env vars, misc cleanup * fix CI docker for e2e tests * replace request logging mdw w/ decode request mdw * update middleware sequence documentation * add ProxyMaximumBatchSize configuration proxy service responds 413 if it receives a request with >ProxyMaximumBatchSize sub-requests * onErrStatus(status) -> onErrorHandler(status, body) * update error handler to include current response headers * refactor metrics tests to use require.Eventually instead of arbitrarily sleeping and praying that the metric was created, wait for the metrics to be created. if the expected metrics are not created, the test will fail with a timeout.
1 parent 1683d4e commit d7f1fa5

20 files changed

+861
-122
lines changed

.env

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ PROXY_CONTAINER_DEBUG_PORT=2345
3333
PROXY_HOST_DEBUG_PORT=2345
3434

3535
##### E2E Testing Config
36+
TEST_UNCONFIGURED_PROXY_PORT=7779
37+
TEST_UNCONFIGURED_PROXY_URL=http://localhost:7779
3638
TEST_PROXY_SERVICE_EVM_RPC_URL=http://localhost:7777
3739
TEST_PROXY_SERVICE_EVM_RPC_HOSTNAME=localhost:7777
3840
TEST_PROXY_SERVICE_EVM_RPC_PRUNING_URL=http://localhost:7778
@@ -56,7 +58,7 @@ TEST_REDIS_ENDPOINT_URL=localhost:6379
5658
##### Kava Proxy Config
5759
# What port the proxy service listens on
5860
PROXY_SERVICE_PORT=7777
59-
LOG_LEVEL=TRACE
61+
LOG_LEVEL=DEBUG
6062
HTTP_READ_TIMEOUT_SECONDS=30
6163
HTTP_WRITE_TIMEOUT_SECONDS=60
6264
# Address of the origin server to proxy all requests to
@@ -66,6 +68,8 @@ PROXY_BACKEND_HOST_URL_MAP=localhost:7777>http://kava-validator:8545,localhost:7
6668
# otherwise, it falls back to the value in PROXY_BACKEND_HOST_URL_MAP
6769
PROXY_HEIGHT_BASED_ROUTING_ENABLED=true
6870
PROXY_PRUNING_BACKEND_HOST_URL_MAP=localhost:7777>http://kava-pruning:8545,localhost:7778>http://kava-pruning:8545
71+
# PROXY_MAXIMUM_REQ_BATCH_SIZE is a proxy-enforced limit on the number of subrequest in a batch
72+
PROXY_MAXIMUM_REQ_BATCH_SIZE=100
6973
# Configuration for the servcie to connect to it's database
7074
DATABASE_NAME=postgres
7175
DATABASE_ENDPOINT_URL=postgres:5432

architecture/MIDDLEWARE.MD

+31-29
Original file line numberDiff line numberDiff line change
@@ -20,49 +20,51 @@ Any modifications that the middleware function makes to the request or response
2020

2121
The earlier the middleware is instantiated, the later it will run. For example the first middleware created by the proxy service is the middleware that will run after the request has been logged and proxied, thereby allowing it to access both the recorded request body and response body, and any context enrichment added by prior middleware.
2222

23-
```golang
24-
service := ProxyService{}
23+
https://github.com/Kava-Labs/kava-proxy-service/blob/847d7889bf5f37770d373d73cd4600a769ebd29c/service/service.go#L54-L110
2524

26-
// create an http router for registering handlers for a given route
27-
mux := http.NewServeMux()
25+
## Middleware
2826

29-
// will run after the proxy middleware handler and is
30-
// the final function called after all other middleware
31-
// allowing it to access values added to the request context
32-
// to do things like metric the response and cache the response
33-
afterProxyFinalizer := createAfterProxyFinalizer(&service)
27+
The middleware sequence of EVM requests to the proxy service:
28+
![Middleware Sequence for Proxy Service](./images/proxy_service_middleware_sequence.jpg)
3429

35-
// create an http handler that will proxy any request to the specified URL
36-
proxyMiddleware := createProxyRequestMiddleware(afterProxyFinalizer, config, serviceLogger)
30+
### Decode Request Middleware
3731

38-
// create an http handler that will log the request to stdout
39-
// this handler will run before the proxyMiddleware handler
40-
requestLoggingMiddleware := createRequestLoggingMiddleware(proxyMiddleware, serviceLogger)
32+
1. Captures start time of request for latency metrics calculations
33+
1. Attempts to decode the request:
34+
* As a single EVM request. If successful, forwards to Single Request Middleware Sequence with the request in the context.
35+
* As a batch EVM request. If successful, forwards to Batch Processing Middleware with batch in context.
36+
* On failure to decode, the request is sent down the Single Request Middleware Sequence, but with nothing in the context.
4137

42-
// register middleware chain as the default handler for any request to the proxy service
43-
mux.HandleFunc("/", requestLoggingMiddleware)
38+
### Single Request Middleware Sequence
39+
If a single request is decoded (as opposed to a batch list), or the request fails to decode, it is forwarded down this middleware sequence. Additionally, each individual sub-request of a batch is routed through this sequence in order to leverage caching and metrics collection.
4440

45-
// create an http server for the caller to start on demand with a call to ProxyService.Run()
46-
server := &http.Server{
47-
Addr: fmt.Sprintf(":%s", config.ProxyServicePort),
48-
Handler: mux,
49-
}
50-
```
41+
This middleware sequence uses the decoded single request from the request context.
5142

52-
## Middleware
43+
#### IsCached Middleware
44+
The front part of the two-part caching middleware. Responsible for determining if an incoming request can be fielded from the cache. If it can, the cached response is put into the context.
5345

54-
### Request Logging Middleware
46+
See [CACHING](./CACHING.md#iscachedmiddleware) for more details.
5547

56-
1. Logs the request body to stdout and stores a parsed version of the request body in the context key `X-KAVA-PROXY-DECODED-REQUEST-BODY` for use by other middleware.
57-
58-
### Proxy Middleware
48+
#### Proxy Middleware
5949

6050
1. Proxies the request to the configured backend origin server.
51+
2. Times the roundtrip latency for the response from the backend origin server and stores the latency in the context key `X-KAVA-PROXY-ORIGIN-ROUNDTRIP-LATENCY-MILLISECONDS` for use by other middleware.
6152

62-
1. Times the roundtrip latency for the response from the backend origin server and stores the latency in the context key `X-KAVA-PROXY-ORIGIN-ROUNDTRIP-LATENCY-MILLISECONDS` for use by other middleware.
53+
The Proxy middleware is responsible for writing the response to the requestor. Subsequent middlewares are non-blocking to the response.
6354

6455
See [Proxy Routing](./PROXY_ROUTING.md) for details on configuration and how requests are routed.
6556

66-
### After Proxy Middleware
57+
#### Caching Middleware
58+
Handles determining if a response can be put in the cache if it isn't already.
59+
60+
See [CACHING](./CACHING.md#cachingmiddleware) for more details.
61+
62+
#### After Proxy Middleware
6763

6864
1. Parses the request body and latency from context key values and creates a request metric for the proxied request.
65+
66+
### Batch Processing Middleware
67+
1. Pulls decoded batch out of the request context
68+
2. Separates into individual sub-requests
69+
3. Routes each sub-request through the Single Request Middleware Sequence in order to leverage caching and metrics creation.
70+
4. Combines all sub-request responses into a single response to the client.
Loading

ci.docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ services:
3131
EVM_QUERY_SERVICE_URL: https://evmrpc.internal.testnet.proxy.kava.io
3232
ports:
3333
- "${PROXY_HOST_PORT}:${PROXY_CONTAINER_PORT}"
34+
- "${TEST_UNCONFIGURED_PROXY_PORT}:${PROXY_CONTAINER_PORT}"
3435
- "${PROXY_CONTAINER_EVM_RPC_PRUNING_PORT}:${PROXY_CONTAINER_PORT}"
3536
- "${PROXY_HOST_DEBUG_PORT}:${PROXY_CONTAINER_DEBUG_PORT}"

config/config.go

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Config struct {
2020
EnableHeightBasedRouting bool
2121
ProxyPruningBackendHostURLMapRaw string
2222
ProxyPruningBackendHostURLMap map[string]url.URL
23+
ProxyMaximumBatchSize int
2324
EvmQueryServiceURL string
2425
DatabaseName string
2526
DatabaseEndpointURL string
@@ -64,6 +65,8 @@ const (
6465
PROXY_BACKEND_HOST_URL_MAP_ENVIRONMENT_KEY = "PROXY_BACKEND_HOST_URL_MAP"
6566
PROXY_HEIGHT_BASED_ROUTING_ENABLED_KEY = "PROXY_HEIGHT_BASED_ROUTING_ENABLED"
6667
PROXY_PRUNING_BACKEND_HOST_URL_MAP_ENVIRONMENT_KEY = "PROXY_PRUNING_BACKEND_HOST_URL_MAP"
68+
PROXY_MAXIMUM_BATCH_SIZE_ENVIRONMENT_KEY = "PROXY_MAXIMUM_REQ_BATCH_SIZE"
69+
DEFAULT_PROXY_MAXIMUM_BATCH_SIZE = 500
6770
PROXY_SERVICE_PORT_ENVIRONMENT_KEY = "PROXY_SERVICE_PORT"
6871
DATABASE_NAME_ENVIRONMENT_KEY = "DATABASE_NAME"
6972
DATABASE_ENDPOINT_URL_ENVIRONMENT_KEY = "DATABASE_ENDPOINT_URL"
@@ -279,6 +282,7 @@ func ReadConfig() Config {
279282
EnableHeightBasedRouting: EnvOrDefaultBool(PROXY_HEIGHT_BASED_ROUTING_ENABLED_KEY, false),
280283
ProxyPruningBackendHostURLMapRaw: rawProxyPruningBackendHostURLMap,
281284
ProxyPruningBackendHostURLMap: parsedProxyPruningBackendHostURLMap,
285+
ProxyMaximumBatchSize: EnvOrDefaultInt(PROXY_MAXIMUM_BATCH_SIZE_ENVIRONMENT_KEY, DEFAULT_PROXY_MAXIMUM_BATCH_SIZE),
282286
DatabaseName: os.Getenv(DATABASE_NAME_ENVIRONMENT_KEY),
283287
DatabaseEndpointURL: os.Getenv(DATABASE_ENDPOINT_URL_ENVIRONMENT_KEY),
284288
DatabaseUserName: os.Getenv(DATABASE_USERNAME_ENVIRONMENT_KEY),

decode/evm_rpc.go

+7
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@ func DecodeEVMRPCRequest(body []byte) (*EVMRPCRequestEnvelope, error) {
254254
return &request, err
255255
}
256256

257+
// DecodeEVMRPCRequest attempts to decode raw bytes to a list of EVMRPCRequestEnvelopes
258+
func DecodeEVMRPCRequestList(body []byte) ([]*EVMRPCRequestEnvelope, error) {
259+
var request []*EVMRPCRequestEnvelope
260+
err := json.Unmarshal(body, &request)
261+
return request, err
262+
}
263+
257264
// ExtractBlockNumberFromEVMRPCRequest attempts to extract the block number
258265
// associated with a request if
259266
// - the request is a valid evm rpc request

docker-compose.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ services:
1111

1212
# run redis for proxy service to cache responses
1313
redis:
14-
image: 'bitnami/redis:latest'
14+
image: "bitnami/redis:latest"
1515
env_file: .env
1616
ports:
1717
- "${REDIS_HOST_PORT}:${REDIS_CONTAINER_PORT}"
@@ -59,6 +59,7 @@ services:
5959
ports:
6060
- "${PROXY_HOST_PORT}:${PROXY_CONTAINER_PORT}"
6161
- "${PROXY_CONTAINER_EVM_RPC_PRUNING_PORT}:${PROXY_CONTAINER_PORT}"
62+
- "${TEST_UNCONFIGURED_PROXY_PORT}:${PROXY_CONTAINER_PORT}"
6263
- "${PROXY_HOST_DEBUG_PORT}:${PROXY_CONTAINER_DEBUG_PORT}"
6364
cap_add:
6465
- SYS_PTRACE # Allows for attaching debugger to process in this container

0 commit comments

Comments
 (0)