Skip to content

Commit dde8581

Browse files
authored
[QoS][EVM] Add new error type for JSONRPC backend service unmarshaling failures (#451)
## Summary Add new error type for JSONRPC backend service unmarshaling failures and improve error handling ### Primary Changes: - Add REQUEST_ERROR_INTERNAL_JSONRPC_BACKEND_SERVICE_UNMARSHAL_ERROR enum value to track when backend service payloads fail to parse as valid JSONRPC responses - Implement detection logic in requestContext.GetObservations() to set this error when no valid JSONRPC responses are received from endpoints - Add GetRequestErrorForJSONRPCBackendServiceUnmarshalError() helper function to create the new error type ### Secondary Changes: - Enhanced logging in unmarshalResponse() with request context and clearer error messages for debugging - Add helper function getSingleJSONRPCRequestPayload() for single JSONRPC request handling - Update protobuf generated code to include the new error enum value ## Issue Distinguish backend service JSONRPC error responses from error parsing endpoint payload as JSONRPC response. - Issue or PR: #{ISSUE_OR_PR_NUMBER} ## Type of change Select one or more from the following: - [x] New feature, functionality or library - [ ] Bug fix - [ ] Code health or cleanup - [ ] Documentation - [ ] Other (specify) ## QoS Checklist ### E2E Validation & Tests - [ ] `make path_up` - [ ] `make test_e2e_evm_shannon` ### Observability - [ ] 1. `make path_up` - [ ] 2. Run the following E2E test: `make test_request__shannon_relay_util_100` - [ ] 3. View results in LocalNet's [PATH Relay Grafana Dashboard](http://localhost:3003/d/relays/path-service-requests) ## Sanity Checklist - [x] I have updated the GitHub Issue `assignees`, `reviewers`, `labels`, `project`, `iteration` and `milestone` - [ ] For docs, I have run `make docusaurus_start` - [ ] For code, I have run `make test_all` - [ ] For configurations, I have updated the documentation - [x] I added `TODO`s where applicable
1 parent d5fac13 commit dde8581

File tree

5 files changed

+96
-23
lines changed

5 files changed

+96
-23
lines changed

observation/qos/request_error.pb.go

Lines changed: 24 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/path/qos/request_error.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ enum RequestErrorKind {
1414
REQUEST_ERROR_INTERNAL_JSONRPC_PAYLOAD_BUILD_ERROR = 6; // Internal error: Failed to build service payload from JSONRPC request.
1515
REQUEST_ERROR_USER_ERROR_REST_SERVICE_DETECTION_ERROR = 7; // User error: Failed to detect service type from REST request.
1616
REQUEST_ERROR_USER_ERROR_REST_UNSUPPORTED_RPC_TYPE = 8; // User error: unsupported service type in REST request.
17+
REQUEST_ERROR_INTERNAL_JSONRPC_BACKEND_SERVICE_UNMARSHAL_ERROR = 9; // Internal error: JSONRPC backend service payload failed to unmarshal as valid JSONRPC response.
1718
}
1819

1920
// RequestError tracks the details of a request error.

qos/evm/context.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,24 @@ func (rc requestContext) GetObservations() qosobservations.Observations {
225225
requestError = qos.GetRequestErrorForProtocolError()
226226
}
227227

228+
// Check for any successfully parsedresponses.
229+
var foundParsedJSONRPCResponse bool
230+
for _, endpointResponse := range rc.endpointResponses {
231+
// Endpoint payload was successfully parsed as JSONRPC response.
232+
// Mark the request as having no errors.
233+
if endpointResponse.unmarshalErr == nil {
234+
foundParsedJSONRPCResponse = true
235+
break
236+
}
237+
}
238+
239+
// No valid JSONRPC responses received from any endpoints.
240+
// Set request error as backend service malformed payload error.
241+
// i.e. the payload from the the endpoint/backend service failed to parse as a valid JSONRPC response.
242+
if !foundParsedJSONRPCResponse {
243+
requestError = qos.GetRequestErrorForJSONRPCBackendServiceUnmarshalError()
244+
}
245+
228246
return qosobservations.Observations{
229247
ServiceObservations: &qosobservations.Observations_Evm{
230248
Evm: &qosobservations.EVMRequestObservations{

qos/evm/response.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,14 @@ func unmarshalResponse(
6161
var jsonrpcResponse jsonrpc.Response
6262
err := json.Unmarshal(data, &jsonrpcResponse)
6363
if err != nil {
64+
// Get the request payload, for single JSONRPC requests.
65+
requestPayload := getSingleJSONRPCRequestPayload(servicePayloads)
66+
6467
// The response raw payload (e.g. as received from an endpoint) could not be unmarshalled as a JSONRC response.
6568
// Return a generic response to the user.
6669
payloadStr := string(data)
6770
logger.With(
71+
"jsonrpc_request", requestPayload,
6872
"unmarshal_err", err,
6973
"raw_payload", log.Preview(payloadStr),
7074
"endpoint_addr", endpointAddr,
@@ -85,22 +89,35 @@ func unmarshalResponse(
8589
// Get the JSON-RPC request from the service payload.
8690
jsonrpcReq, err := jsonrpc.GetJsonRpcReqFromServicePayload(servicePayload)
8791
if err != nil {
88-
logger.Error().Err(err).Msg("Failed to get JSONRPC request from service payload")
92+
logger.Error().Err(err).Msg("SHOULD NEVER HAPPEN: Failed to get JSONRPC request from service payload")
8993
return getGenericJSONRPCErrResponse(logger, getJsonRpcIDForErrorResponse(servicePayloads), data, err), err
9094
}
9195

9296
// Validate the JSONRPC response.
9397
if err := jsonrpcResponse.Validate(jsonrpcReq.ID); err != nil {
9498
payloadStr := string(data)
9599
logger.With(
96-
"request", jsonrpcReq,
100+
"jsonrpc_request", jsonrpcReq,
97101
"validation_err", err,
98102
"raw_payload", log.Preview(payloadStr),
99103
"endpoint_addr", endpointAddr,
100-
).Debug().Msg("JSON-RPC response validation failed")
104+
).Error().Msg("❌ Failed to unmarshal response payload as valid JSON-RPC")
105+
101106
return getGenericJSONRPCErrResponse(logger, jsonrpcReq.ID, data, err), err
102107
}
103108

109+
// The parsed JSONRPC response contains an error:
110+
// - Log the request and the response
111+
// - Used to track potential endpoint quality issues.
112+
if jsonrpcResponseErr := jsonrpcResponse.Error; jsonrpcResponseErr != nil {
113+
logger.With(
114+
"jsonrpc_request", jsonrpcReq,
115+
"jsonrpc_response_error_code", jsonrpcResponseErr.Code,
116+
"jsonrpc_response_error_message", jsonrpcResponseErr.Message,
117+
"endpoint_addr", endpointAddr,
118+
).Error().Msg("❌ Endpoint returned JSONRPC response with error")
119+
}
120+
104121
// Unmarshal the JSONRPC response into a method-specific response.
105122
unmarshaller, found := methodResponseMappings[jsonrpcReq.Method]
106123
if found {
@@ -147,3 +164,22 @@ func findServicePayloadByID(servicePayloads map[jsonrpc.ID]protocol.Payload, tar
147164
}
148165
return protocol.Payload{}, false
149166
}
167+
168+
// TODO_HACK(@adshmh): Drop this once single and batch JSONRPC request handling logic is fully separated.
169+
// - There should be no need to rely on endpoint's payload to match the request from a batch.
170+
// - Single JSONRPC requests do not need the complexity of batch request logic.
171+
//
172+
// This only targets single JSONRPC requests.
173+
func getSingleJSONRPCRequestPayload(servicePayloads map[jsonrpc.ID]protocol.Payload) string {
174+
if len(servicePayloads) != 1 {
175+
return ""
176+
}
177+
178+
for _, payload := range servicePayloads {
179+
if len(payload.Data) > 0 {
180+
return payload.Data
181+
}
182+
}
183+
184+
return ""
185+
}

qos/request_error.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,17 @@ func GetRequestErrorForProtocolError() *qosobservations.RequestError {
2424
HttpStatusCode: int32(jsonrpcErrorResponse.GetRecommendedHTTPStatusCode()),
2525
}
2626
}
27+
28+
// GetRequestErrorForJSONRPCBackendServiceUnmarshalError returns a request error for a JSONRPC backend service unmarshaling error.
29+
// i.e. the payload returned by the endpoint/backend service failed to parse as a valid JSONRPC response.
30+
func GetRequestErrorForJSONRPCBackendServiceUnmarshalError() *qosobservations.RequestError {
31+
err := errors.New("internal error: JSONRPC backend service error: payload failed to parse as valid JSONRPC response")
32+
// initialize a JSONRPC error response to derive the HTTP status code.
33+
jsonrpcErrorResponse := jsonrpc.NewErrResponseInternalErr(jsonrpc.ID{}, err)
34+
35+
return &qosobservations.RequestError{
36+
ErrorKind: qosobservations.RequestErrorKind_REQUEST_ERROR_INTERNAL_JSONRPC_BACKEND_SERVICE_UNMARSHAL_ERROR,
37+
ErrorDetails: err.Error(),
38+
HttpStatusCode: int32(jsonrpcErrorResponse.GetRecommendedHTTPStatusCode()),
39+
}
40+
}

0 commit comments

Comments
 (0)