From 5e1c33344b23b69858682744782921a6eca1640d Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 2 Apr 2025 21:07:03 -0400 Subject: [PATCH 01/26] TEMP commit on framework/jsonrpc package --- proto/path/qos/framework/endpoint.proto | 15 ++ .../qos/framework/endpoint_attribute.proto | 20 ++ .../framework/endpoint_attribute_error.proto | 30 +++ .../qos/framework/endpoint_query_result.proto | 13 + .../qos/framework/endpoint_sanction.proto | 20 ++ proto/path/qos/framework/jsonrpc.proto | 45 ++++ proto/path/qos/framework/observations.proto | 20 ++ proto/path/qos/framework/request.proto | 39 +++ qos/framework/jsonrpc/client_http_response.go | 131 ++++++++++ .../jsonrpc/context_endpoint_checks.go | 10 + .../jsonrpc/context_endpoint_result.go | 145 +++++++++++ .../jsonrpc/context_endpoint_selection.go | 118 +++++++++ qos/framework/jsonrpc/context_request.go | 191 +++++++++++++++ .../jsonrpc/context_request_builder.go | 150 ++++++++++++ qos/framework/jsonrpc/context_state_update.go | 50 ++++ qos/framework/jsonrpc/endpoint.go | 50 ++++ qos/framework/jsonrpc/endpoint_attribute.go | 45 ++++ .../jsonrpc/endpoint_attribute_error.go | 16 ++ qos/framework/jsonrpc/endpoint_query.go | 17 ++ .../jsonrpc/endpoint_query_result.go | 19 ++ .../jsonrpc/endpoint_query_result_defaults.go | 130 ++++++++++ qos/framework/jsonrpc/endpoint_sanction.go | 21 ++ .../jsonrpc/endpoint_sanction_defaults.go | 51 ++++ qos/framework/jsonrpc/endpoint_store.go | 39 +++ qos/framework/jsonrpc/example_evm/check.go | 3 + qos/framework/jsonrpc/example_evm/config.go | 16 ++ .../endpoint_result_blocknumber.go | 49 ++++ .../example_evm/endpoint_result_chainid.go | 56 +++++ .../jsonrpc/example_evm/endpoint_selection.go | 67 ++++++ qos/framework/jsonrpc/example_evm/qos.go | 53 ++++ .../jsonrpc/example_evm/state_update.go | 55 +++++ qos/framework/jsonrpc/framework.go | 57 +++++ qos/framework/jsonrpc/jsonrpc_errors.go | 226 ++++++++++++++++++ qos/framework/jsonrpc/observations.go | 26 ++ .../jsonrpc/observations_endpoint_result.go | 117 +++++++++ qos/framework/jsonrpc/qos.go | 72 ++++++ qos/framework/jsonrpc/service_info.go | 12 + qos/framework/jsonrpc/service_state.go | 191 +++++++++++++++ 38 files changed, 2385 insertions(+) create mode 100644 proto/path/qos/framework/endpoint.proto create mode 100644 proto/path/qos/framework/endpoint_attribute.proto create mode 100644 proto/path/qos/framework/endpoint_attribute_error.proto create mode 100644 proto/path/qos/framework/endpoint_query_result.proto create mode 100644 proto/path/qos/framework/endpoint_sanction.proto create mode 100644 proto/path/qos/framework/jsonrpc.proto create mode 100644 proto/path/qos/framework/observations.proto create mode 100644 proto/path/qos/framework/request.proto create mode 100644 qos/framework/jsonrpc/client_http_response.go create mode 100644 qos/framework/jsonrpc/context_endpoint_checks.go create mode 100644 qos/framework/jsonrpc/context_endpoint_result.go create mode 100644 qos/framework/jsonrpc/context_endpoint_selection.go create mode 100644 qos/framework/jsonrpc/context_request.go create mode 100644 qos/framework/jsonrpc/context_request_builder.go create mode 100644 qos/framework/jsonrpc/context_state_update.go create mode 100644 qos/framework/jsonrpc/endpoint.go create mode 100644 qos/framework/jsonrpc/endpoint_attribute.go create mode 100644 qos/framework/jsonrpc/endpoint_attribute_error.go create mode 100644 qos/framework/jsonrpc/endpoint_query.go create mode 100644 qos/framework/jsonrpc/endpoint_query_result.go create mode 100644 qos/framework/jsonrpc/endpoint_query_result_defaults.go create mode 100644 qos/framework/jsonrpc/endpoint_sanction.go create mode 100644 qos/framework/jsonrpc/endpoint_sanction_defaults.go create mode 100644 qos/framework/jsonrpc/endpoint_store.go create mode 100644 qos/framework/jsonrpc/example_evm/check.go create mode 100644 qos/framework/jsonrpc/example_evm/config.go create mode 100644 qos/framework/jsonrpc/example_evm/endpoint_result_blocknumber.go create mode 100644 qos/framework/jsonrpc/example_evm/endpoint_result_chainid.go create mode 100644 qos/framework/jsonrpc/example_evm/endpoint_selection.go create mode 100644 qos/framework/jsonrpc/example_evm/qos.go create mode 100644 qos/framework/jsonrpc/example_evm/state_update.go create mode 100644 qos/framework/jsonrpc/framework.go create mode 100644 qos/framework/jsonrpc/jsonrpc_errors.go create mode 100644 qos/framework/jsonrpc/observations.go create mode 100644 qos/framework/jsonrpc/observations_endpoint_result.go create mode 100644 qos/framework/jsonrpc/qos.go create mode 100644 qos/framework/jsonrpc/service_info.go create mode 100644 qos/framework/jsonrpc/service_state.go diff --git a/proto/path/qos/framework/endpoint.proto b/proto/path/qos/framework/endpoint.proto new file mode 100644 index 000000000..6200db72b --- /dev/null +++ b/proto/path/qos/framework/endpoint.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/endpoint_query_result.proto"; // Import RequestObservation + +// EndpointObservation captures details about an endpoint query. +message EndpointObservation { + // Address of the endpoint that handled the request + string endpoint_addr = 1; + + // Single result item extracted from this endpoint query. + EndpointQueryResult result = 2; +} diff --git a/proto/path/qos/framework/endpoint_attribute.proto b/proto/path/qos/framework/endpoint_attribute.proto new file mode 100644 index 000000000..5b05d0e65 --- /dev/null +++ b/proto/path/qos/framework/endpoint_attribute.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/endpoint_attribute_error.proto"; // import EndpointAttributeError + +// EndpointAttribute represents a single data item extracted from an endpoint response. +// Maps to EndpointAttribute in endpoint_attribute.go +message EndpointAttribute { + // We use oneof to represent the mutually exclusive types (string or int) + oneof value { + string string_value = 1; + int32 int_value = 2; + } + + // Error information if this attribute is associated with a failure + // Optional field + optional EndpointAttributeError error = 3; +} diff --git a/proto/path/qos/framework/endpoint_attribute_error.proto b/proto/path/qos/framework/endpoint_attribute_error.proto new file mode 100644 index 000000000..41753ee2f --- /dev/null +++ b/proto/path/qos/framework/endpoint_attribute_error.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/endpoint_sanction.proto"; // import Sanction definitions + +// EndpointErrorKind identifies different kinds of endpoint data errors. +enum EndpointErrorKind { + ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED = 0; + ENDPOINT_DATA_ERROR_KIND_NO_INTERACTION = 1; // No endpoint interaction occurred or no payload received + ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD = 2; // Empty payload from endpoint + ENDPOINT_DATA_ERROR_KIND_UNMARSHALING = 3; // Could not parse endpoint payload + ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT = 4; // Payload result doesn't match expected value +} + +// EndpointAttributeError contains error details for endpoint queries. +// Maps to EndpointAttributeError in endpoint_attribute_error.go +message EndpointAttributeError { + // Specifies the kind of endpoint eror observed. + // Example: Returned payload cannot be parsed into a JSONRPC response. + EndpointErrorKind error_kind = 1; + + // Description set by the custom service implementation + string description = 2; + + // RecommendedSanction set by the custom service implementation + // Only set if the endpoint error has fetched it a sanction. + optional Sanction recommended_sanction = 3; +} diff --git a/proto/path/qos/framework/endpoint_query_result.proto b/proto/path/qos/framework/endpoint_query_result.proto new file mode 100644 index 000000000..38bb91752 --- /dev/null +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/endpoint_attribute.proto"; // import EndpointAttribute + +// EndpointQueryResult captures information extracted from a service endpoint query. +// Maps to EndpointQueryResult in endpoint_query_result.go +message EndpointQueryResult { + // The set of endpoint attributes + map endpoint_attributes = 1; +} diff --git a/proto/path/qos/framework/endpoint_sanction.proto b/proto/path/qos/framework/endpoint_sanction.proto new file mode 100644 index 000000000..f9e8011a4 --- /dev/null +++ b/proto/path/qos/framework/endpoint_sanction.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "google/protobuf/timestamp.proto"; + +// SanctionType identifies different types of endpoint sanctions. +enum SanctionType { + SANCTION_TYPE_UNSPECIFIED = 0; + SANCTION_TYPE_TEMPORARY = 1; // Time-limited exclusion + SANCTION_TYPE_PERMANENT = 2; // Permanent exclusion +} + +// Sanction represents a recommendation to limit endpoint usage. +message Sanction { + SanctionType type = 1; // Type of sanction + string reason = 2; // Reason for the sanction + google.protobuf.Timestamp expiry_timestamp = 3; +} diff --git a/proto/path/qos/framework/jsonrpc.proto b/proto/path/qos/framework/jsonrpc.proto new file mode 100644 index 000000000..99041b77e --- /dev/null +++ b/proto/path/qos/framework/jsonrpc.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +// JsonRpcRequest represents essential fields of a JSON-RPC request for observation purposes. +// Reference: https://www.jsonrpc.org/specification#request_object +message JsonRpcRequest { + // Client-established identifier. Must be a String, Number, or NULL if present. + string id = 1; + + // Name of the JSON-RPC method being called (e.g., eth_chainId for EVM chains) + string method = 2; + + // Note: This message captures only essential JSON-RPC fields. + // Add fields as needed. +} + +// JsonRpcResponse represents essential fields of a JSON-RPC response for observation purposes. +// Reference: https://www.jsonrpc.org/specification#response_object +message JsonRpcResponse { + // Must match the id value from the corresponding request + string id = 1; + + // JSON-serializable response data + string result = 2; + + // Error details, if the request failed + optional JsonRpcResponseError err = 3; + + // Note: This message captures only essential JSON-RPC fields. + // Add fields as needed. +} + +// JsonRpcResponseError represents core error fields from a JSON-RPC response. +// Reference: https://www.jsonrpc.org/specification#error_object +// +// Only includes fields required for QoS observations. +message JsonRpcResponseError { + // Error code indicating the type of failure + int64 code = 1; + + // Human-readable error description + string message = 2; +} diff --git a/proto/path/qos/framework/observations.proto b/proto/path/qos/framework/observations.proto new file mode 100644 index 000000000..fd51d7640 --- /dev/null +++ b/proto/path/qos/framework/observations.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/request.proto"; // Import RequestObservation +import "path/qos/framework/endpoint.proto"; // Import EndpointObservation + +// Observations is the top-level container for all QoS observations for a request. +message Observations { + // Service identification + string service_id = 1; + string service_description = 2; // e.g. EVM, Solana, CometBFT + + // Observation of the client request + RequestObservation request_observation = 3; + + // Observations from endpoint(s) + repeated EndpointObservation endpoint_observations = 4; +} diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto new file mode 100644 index 000000000..a73df250e --- /dev/null +++ b/proto/path/qos/framework/request.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/jsonrpc.proto"; // Import basic JSONRPC messages. + +// RequestValidationError identifies the type of validation error encountered. +enum RequestValidationError { + REQUEST_VALIDATION_ERROR_UNSPECIFIED = 0; + REQUEST_VALIDATION_ERROR_BODY_READ_FAILURE = 1; // Error reading HTTP request body + REQUEST_VALIDATION_ERROR_UNMARSHALING_FAILURE = 2; // Error parsing JSON-RPC request + REQUEST_VALIDATION_ERROR_INVALID_REQUEST = 3; // Request format is incorrect + REQUEST_VALIDATION_ERROR_INVALID_METHOD = 4; // Method is not supported + REQUEST_VALIDATION_ERROR_INVALID_PARAMS = 5; // Parameters are incorrect +} + +// TODO_IN_THIS_PR: Consider removing the http_status_code. + +// ValidationFailure contains details about a request validation failure +message ValidationFailure { + RequestValidationError error_type = 1; + int32 http_status_code = 2; + string validation_error_details = 3; + + // Only set for validation failures on valid JSONRPC structures + // e.g. missing params field when required. + optional JsonRpcRequest jsonrpc_request = 4; +} + +// TODO_IN_THIS_PR: use `oneof` below: either the request is set, or the validationerror +// RequestObservation captures details about the original client request. +message RequestObservation { + // Only set if validation was successful + optional JsonRpcRequest jsonrpc_request = 1; + + // Only set if validation failed + optional ValidationFailure validation_failure = 2; +} diff --git a/qos/framework/jsonrpc/client_http_response.go b/qos/framework/jsonrpc/client_http_response.go new file mode 100644 index 000000000..f370967c7 --- /dev/null +++ b/qos/framework/jsonrpc/client_http_response.go @@ -0,0 +1,131 @@ +package framework + +import ( + "github.com/buildwithgrove/path/gateway" +) + +// ClientHTTPResponse implements the gateway.HTTPResponse interface +// and provides a standardized way to return HTTP responses to clients. +type ClientHTTPResponse struct { + StatusCode int + Headers map[string]string + Payload []byte +} + +// GetPayload returns the response body payload. +func (r *ClientHTTPResponse) GetPayload() []byte { + return r.Payload +} + +// GetHTTPStatusCode returns the HTTP status code. +func (r *ClientHTTPResponse) GetHTTPStatusCode() int { + return r.StatusCode +} + +// GetHTTPHeaders returns the HTTP response headers. +func (r *ClientHTTPResponse) GetHTTPHeaders() map[string]string { + return r.Headers +} + +// newHTTPResponse creates a new HTTP response with the given status code and payload. +func newHTTPResponse(statusCode int, payload []byte) *ClientHTTPResponse { + return &ClientHTTPResponse{ + StatusCode: statusCode, + Headers: map[string]string{"Content-Type": "application/json"}, + Payload: payload, + } +} + +// buildHTTPResponse creates an HTTP response from a JSONRPC response. +// It performs logging only if errors occur during the process. +func buildHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.Response) gateway.HTTPResponse { + if jsonrpcResp == nil { + logger.Error().Msg("Received nil JSONRPC response") + return buildErrorResponse(logger, jsonrpc.ID{}) + } + + payload, err := jsonrpcResp.MarshalJSON() + if err != nil { + logger.Error().Err(err).Msg("Failed to marshal JSONRPC response") + return buildErrorResponse(logger, jsonrpcResp.ID) + } + + return &ClientHTTPResponse{ + StatusCode: jsonrpcResp.GetRecommendedHTTPStatusCode(), + Headers: map[string]string{"Content-Type": "application/json"}, + Payload: payload, + } +} + +// buildErrorResponse creates an internal error HTTP response with the given ID. +func buildErrorResponse(logger polylog.Logger, id jsonrpc.ID) gateway.HTTPResponse { + errResp := newErrResponseInternalError(id) + errPayload, _ := errResp.MarshalJSON() + return &ClientHTTPResponse{ + StatusCode: errResp.GetRecommendedHTTPStatusCode(), + Headers: map[string]string{"Content-Type": "application/json"}, + Payload: errPayload, + } +} + +// ====================> DROP/REFACTOR these methods + +// BuildInternalErrorResponse creates a generic internal error HTTP response. +func BuildInternalErrorResponse() gateway.HTTPResponse { + errResp := newErrResponseInternalError(jsonrpc.ID{}) + errPayload, _ := MarshalErrorResponse(nil, errResp) + return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) +} + +// BuildInternalErrorHTTPResponse creates an internal error HTTP response with logging. +func BuildInternalErrorHTTPResponse(logger polylog.Logger) gateway.HTTPResponse { + errResp := newErrResponseInternalError(jsonrpc.ID{}) + errPayload, err := MarshalErrorResponse(logger, errResp) + if err != nil { + logger.Error().Err(err).Msg("Failed to marshal internal error response") + } + return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) +} + +// BuildErrorHTTPResponse creates an HTTP response for an error response. +func BuildErrorHTTPResponse(logger polylog.Logger, errResp *jsonrpc.ErrorResponse) gateway.HTTPResponse { + payload, err := MarshalErrorResponse(logger, errResp) + if err != nil { + logger.Error().Err(err).Msg("Failed to marshal error response") + } + return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), payload) +} + +// BuildSuccessHTTPResponse creates an HTTP response for a successful JSONRPC response. +func BuildSuccessHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.JsonRpcResponse) gateway.HTTPResponse { + response := jsonrpc.Response{ + ID: jsonrpcResp.Id, + JSONRPC: jsonrpc.Version2, + Result: jsonrpcResp.Result, + } + payload, err := response.MarshalJSON() + if err != nil { + logger.Error().Err(err).Msg("Failed to marshal JSONRPC response") + errResp := newErrResponseMarshalError(response.ID, err) + errPayload, _ := MarshalErrorResponse(logger, errResp) + return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) + } + return NewHTTPResponse(response.GetRecommendedHTTPStatusCode(), payload) +} + +// MarshalErrorResponse marshals an error response to JSON. +func MarshalErrorResponse(logger polylog.Logger, errResp *jsonrpc.ErrorResponse) ([]byte, error) { + payload, err := errResp.MarshalJSON() + if err != nil && logger != nil { + logger.Error().Err(err).Msg("Failed to marshal error response") + } + return payload, err +} + +// NewHTTPResponse creates a new HTTP response with the given status code and payload. +func NewHTTPResponse(statusCode int, payload []byte) gateway.HTTPResponse { + return gateway.HTTPResponse{ + StatusCode: statusCode, + Body: payload, + } +} diff --git a/qos/framework/jsonrpc/context_endpoint_checks.go b/qos/framework/jsonrpc/context_endpoint_checks.go new file mode 100644 index 000000000..2fc873244 --- /dev/null +++ b/qos/framework/jsonrpc/context_endpoint_checks.go @@ -0,0 +1,10 @@ +package evm + +// TODO_IN_THIS_PR: define, in the framework package, a context for adding QoS endpoint quality checks: +// - Struct name: QualityCheckContext +// - Struct Methods: +// - GetEndpoint(): to skip unnecessary checks. +// - GetState(): e.g. for Archival checks +// - AddCheck(jsonrpc.Request) +// + diff --git a/qos/framework/jsonrpc/context_endpoint_result.go b/qos/framework/jsonrpc/context_endpoint_result.go new file mode 100644 index 000000000..082d32cd6 --- /dev/null +++ b/qos/framework/jsonrpc/context_endpoint_result.go @@ -0,0 +1,145 @@ +package jsonrpc + +// ResultBuilder is implemented by custom service implementations to build an EndpointResult from an endpointCall. +// It processes a valid JSONRPC response for a specific method and extracts the relevant data or error information. +// It can potentially makr a JSONRPC response as invalid: +// For example if the result field cannot be parsed into a number in a response to an `eth_blockNumber` request. +type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult + +// TODO_FUTURE(@adshmh): Support overriding the JSONRPC response through the EndpointQueryResultContext, IFF there is a use case for it. +// +// EndpointQueryResultContext provides context for processing a result with the service state. +// Provides a fluent API for custom service implementations to create endpoint query results without directly constructing types. +type EndpointQueryResultContext struct { + // Allows direct Get calls on the current service state. + // It is read only: this is not the context for updating service state. + *ReadonlyServiceState + + // The endpoint query for which results are being built. + endpointQuery *endpointQuery + + // Custom result builders, supplied by the QoS Definition. + jsonrpcMethodResultBuilders map[string]EndpointQueryResultBuilder + + // Response is the parsed JSON-RPC response received from the endpoint. + response *jsonrpc.JsonRpcResponse + + // The result data that will be returned to the caller (requestContext) + result *EndpointQueryResult +} + +// buildResult uses the supplied method builder to build and return the EndpointResult. +// A default builder is used if no matches were found for the request method. +func (ctx *EndpointResultContext) buildResult() *EndpointQueryResult { + if parsingFailureResult, shouldContinue := ctx.parseEndpointPayload(); !shouldContinue { + return parsingFailureResult + } + + builder, found := ctx.jsonrpcMethodResultBuilders[ctx.endpointQuery.request.Method] + + if !found { + // Use default processor for unrecognized methods + builder = defaultResultBuilder + } + + // Process the result using service-specific processor with context + ctx.result.ResultData = builder(ctx) + return ctx.result +} + +// TODO_IN_THIS_PR: define/allow customization of sanctions for endpoint errors: e.g. malformed response. +// +// parseEndpointPayload parses the payload from an endpoint and handles empty responses and parse errors. +// It returns the result and a boolean indicating whether processing should continue (true) or stop (false). +func (ctx *EndpointResultContext) parseEndpointPayload() (*EndpointQueryResult, bool) { + // Check for empty response + if len(call.ReceivedData) == 0 { + result := buildResultForEmptyResponse(eq) + return result, false + } + + // Parse JSONRPC response + var jsonrpcResp jsonrpc.JsonRpcResponse + if err := json.Unmarshal(eq.receivedData, &jsonrpcResp); err != nil { + result := buildResultForErrorUnmarshalingEndpointReturnedData(call, err) + return result, false + } + + // Validate the JSONRPC response + if err := jsonrpcResp.Validate(eq.request.ID); err != nil { + // TODO_IN_THIS_PR: define a separate method for JSONRPC response validation errors. + result := buildResultForErrorUnmarshalingEndpointReturnedData(eq, err) + return result, false + } + + // Store the parsed result + ctx.parsedResponse = jsonrpcResp + + // Return true to signal that parsing was successful. + // Processing will continue to the next step. + return nil, true +} + +// Success creates a success result with the given value. +func (ctx *ResultBuilderContext) Success(value string) *ResultData { + valuePtr := &value + return &ResultData{ + Type: ctx.Method, + Value: valuePtr, + } +} + +// ErrorResult creates an error result with the given message and no sanction. +func (ctx *ResultBuilderContext) ErrorResult(description string) *ResultData { + return &ResultData{ + Type: ctx.Method, + Error: &ResultError{ + Description: description, + kind: EndpointDataErrorKindInvalidResult, + }, + CreatedTime: time.Now(), + } +} + +// SanctionEndpoint creates an error result with a temporary sanction. +func (ctx *ResultBuilderContext) SanctionEndpoint(description, reason string, duration time.Duration) *ResultData { + return &ResultData{ + Type: ctx.Method, + Error: &ResultError{ + Description: description, + RecommendedSanction: &SanctionRecommendation{ + Sanction: Sanction{ + Type: SanctionTypeTemporary, + Reason: reason, + ExpiryTime: time.Now().Add(duration), + CreatedTime: time.Now(), + }, + SourceDataType: ctx.Method, + TriggerDetails: description, + }, + kind: EndpointDataErrorKindInvalidResult, + }, + CreatedTime: time.Now(), + } +} + +// PermanentSanction creates an error result with a permanent sanction. +func (ctx *ResultBuilderContext) PermanentSanction(description, reason string) *ResultData { + return &ResultData{ + Type: ctx.Method, + Error: &ResultError{ + Description: description, + RecommendedSanction: &SanctionRecommendation{ + Sanction: Sanction{ + Type: SanctionTypePermanent, + Reason: reason, + CreatedTime: time.Now(), + }, + SourceDataType: ctx.Method, + TriggerDetails: description, + }, + kind: EndpointDataErrorKindInvalidResult, + }, + CreatedTime: time.Now(), + } +} diff --git a/qos/framework/jsonrpc/context_endpoint_selection.go b/qos/framework/jsonrpc/context_endpoint_selection.go new file mode 100644 index 000000000..5b00a5565 --- /dev/null +++ b/qos/framework/jsonrpc/context_endpoint_selection.go @@ -0,0 +1,118 @@ +package jsonrpc + +import ( + "github.com/buildwithgrove/path/protocol" +) + +// TODO_FUTURE: consider ranking filtered endpoints, e.g. based on latency, rather than randomization. + +// TODO_MVP(@adshmh): Remove expired Sanctions from endpoints' results. +// + +// EndpointSelectionContext provides context for selecting endpoints. +type EndpointSelectionContext struct { + // Allows direct Get calls on the current service state. + // It is read only: this is not the context for updating service state. + *ReadonlyServiceState + + // Supplied from the Custom QoS service definition. + // Used to select an endpoint among those without an active sanction. + // e.g. EVM QoS implements a selector that disqualifies endpoints that are out of sync. + customSelector EndpointSelector + + // Used to retrieve stored endpoints to examine their attributes during selection. + endpointStore *endpointStore + + // TODO_TECHDEBT(@adshmh): make this readonly and allow access through a getter method to prevent accidental modification by user. + // The JSONRPC request for which an endpoint is to be selected. + Request *jsonrpc.Request + + // Endpoints disqualified from current selection context. + disqualifiedEndpoints map[protocol.EndpointAddr]struct{} +} + +// Entry method into the endpoint selection context. +// Called by gateway.requestContext. +// Implements protocol.EndpointSelector interface. +func (ctx *EndpointSelectionContext) Select(availableEndpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { + // Retrieve all the available endpoints from the endpoint store. + candidates := make(map[protocol.EndpointAddr]Endpoint) + for _, availableEndpoint := range availableEndpoints { + endpointAddr := availableEndpoint.Addr() + + // Use an empty endpoint struct for initialization if no entry was found in the store. + candidates[endpointAddr] = ctx.endpointStore.GetEndpoint(endpointAddr) + } + + // Drop endpoints with active sanctions from the list. + filteredEndpoints := ctx.filterSanctionedEndpoints(candidates) + + // If all endpoints were sanctioned, return early + if len(unsanctionedEndpoints) == 0 { + // TODO_IN_THIS_PR: should we error out here instead? + s.logger.Info().Msg("All endpoints are currently sanctioned: returning a random endpoint.") + return ctx.selectRandomEndpoint(availableEndpoints) + } + + // TODO_IN_THIS_PR: implement this in the EndpointSelectionContext struct. + // If no custom selector is provided, use a random selector. + if s.customSelector == nil { + return ctx.SelectRandom() + } + + // Call the custom selector with the context + return s.customSelector(ctx) +} + +// Select marks an endpoint as selected. +func (ctx *EndpointSelectionContext) Select(endpoint Endpoint) *EndpointSelectionContext { + ctx.selected = append(ctx.selected, endpoint) + return ctx +} + +// SelectAll marks all available endpoints as selected. +func (ctx *EndpointSelectionContext) SelectAll() *EndpointSelectionContext { + ctx.selected = append(ctx.selected[:0], ctx.Endpoints...) + return ctx +} + +// SelectIf selects endpoints that match a predicate function. +func (ctx *EndpointSelectionContext) SelectIf(predicate func(Endpoint) bool) *EndpointSelectionContext { + for _, endpoint := range ctx.Endpoints { + if predicate(endpoint) { + ctx.selected = append(ctx.selected, endpoint) + } + } + return ctx +} + +// Selected returns the selected endpoints. +func (ctx *EndpointSelectionContext) Selected() []Endpoint { + return ctx.selected +} + +// filterSanctionedEndpoints removes endpoints with active sanctions +// This is an internal function used by the framework before passing +// endpoints to the custom service +func (ctx *EndpointSelectionContext) filterSanctionedEndpoints(endpoints map[protocol.EndpointAddr]Endpoint) []Endpoint { + filteredEndpoints := make([]Endpoint, 0, len(endpoints)) + + for endpointAddr, endpoint := range endpoints { + // Check if the endpoint is sanctioned. + activeSanction, isSanctioned := endpoint.GetActiveSanction() + + // Skip sanctioned endpoints. + if isSanctioned { + ctx.logger.With( + "endpoint_addr", string(endpointAddr), + "sanction", activeSanction.String(), + ).Debug().Msg("Dropping sanctioned endpoint") + + continue + } + + filteredEndpoints = append(filteredEndpoints, endpoint) + } + + return filteredEndpoints +} diff --git a/qos/framework/jsonrpc/context_request.go b/qos/framework/jsonrpc/context_request.go new file mode 100644 index 000000000..12abb9ef6 --- /dev/null +++ b/qos/framework/jsonrpc/context_request.go @@ -0,0 +1,191 @@ +package jsonrpc + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" + "github.com/pokt-network/poktroll/pkg/polylog" +) + +// TODO_REFACTOR: Improve naming clarity by distinguishing between interfaces and adapters +// in the metrics/qos/evm and qos/evm packages, and elsewhere names like `response` are used. +// Consider renaming: +// - metrics/qos/evm: response → EVMMetricsResponse +// - qos/evm: response → EVMQoSResponse +// - observation/evm: observation -> EVMObservation +// +// TODO_TECHDEBT: Need to add a Validate() method here to allow the caller (e.g. gateway) +// determine whether the endpoint's response was valid, and whether a retry makes sense. +// + +const ( + // TODO_MVP(@adshmh): Support individual configuration of timeout for every service that uses EVM QoS. + // The default timeout when sending a request to an EVM blockchain endpoint. + defaultServiceRequestTimeoutMillisec = 10000 +) + +// requestContext provides the support required by the gateway +// package for handling service requests. +var _ gateway.RequestQoSContext = &requestContext{} + +// TODO_IN_THIS_PR: change the errorKind to private + find the correct file for it. + +// TODO_FUTURE(@adshmh): implement custom, typed result extractors that are commonly used by custom QoS implementations. +// Example: +// ResultContext. +// endpointErrorKind identifies different kinds of endpoint data errors. +type endpointErrorKind int + +const ( + EndpointDataErrorKindUnspecified endpointErrorKind = iota + EndpointDataErrorKindNoInteraction // No endpoint interaction occurred or no payload received + EndpointDataErrorKindEmptyPayload // Empty payload from endpoint + EndpointDataErrorKindUnmarshaling // Could not parse endpoint payload + EndpointDataErrorKindInvalidResult // Payload result doesn't match expected format +) + +// TODO_IN_THIS_PR: sort out the scope of fields and methods: private/public on private structs. +// +// requestQoSContext holds the context for a request through its lifecycl. +// It contains all the state needed to process the request, build responses, and generate observations. +type requestQoSContext struct { + Logger polylog.Logger + + // Service Identification fields + ServiceID ServiceID + + // Request is the JSONRPC request that was sent + Request *jsonrpc.Request + + // Error response to return if validation failed + JSONRPCErrorResponse *jsonrpc.Response + + // Read-only form of the service state. + // Used to instantiate EndpointQueryResultContext and EndpointSelectionContext. + serviceState *ServiceStateReadOnly + + // Used to instantiate the EndpointSelectionContext, to select an endpoint for serving the client's request. + endpointStore *endpointStore + + // Used to instantiate the EndpointSelectionContext + customEndpointSelector EndpointSelector + + // Used to instantiate the EndpointResultContext, to build an endpoint result from an endpoint query. + jsonrpcMethodResultBuilders map[string]EndpointResultBuilder + + // Tracks results processed in the current request context. + processedResults []*EndpointQueryResult +} + +// TODO_MVP(@adshmh): Ensure the JSONRPC request struct can handle all valid service requests. +func (rc requestQoSContext) GetServicePayload() protocol.Payload { + reqBz, err := json.Marshal(*rc.Request) + if err != nil { + // TODO_MVP(@adshmh): find a way to guarantee this never happens, + // e.g. by storing the serialized form of the JSONRPC request + // at the time of creating the request context. + return protocol.Payload{} + } + + return protocol.Payload{ + Data: string(reqBz), + // Method is alway POST for EVM-based blockchains. + Method: http.MethodPost, + + // Path field is not used for JSONRPC services. + + // TODO_IMPROVE: adjust the timeout based on the request method: + // An endpoint may need more time to process certain requests, + // as indicated by the request's method and/or parameters. + TimeoutMillisec: defaultServiceRequestTimeoutMillisec, + } +} + +// UpdateWithResponse is NOT safe for concurrent use +func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAddr, receivedData []byte) { + // TODO_IMPROVE(@adshmh): check whether the request was valid, and return an error if it was not. + // This would be an extra safety measure, as the caller should have checked the returned value + // indicating the validity of the request when calling on QoS instance's ParseHTTPRequest + // + // Instantiate an endpointQuery to capture the interaction with the service endpoint. + endpointQuery := &endpointQuery{ + serviceID: rc.ServiceID, + request: rc.Request, + endpointAddr: endpointAddr, + receivedData: receivedData, + } + + // instantiate a result context to process the endpointQuery. + resultCtx := &EndpointQueryResultContext{ + ReadonlyServiceState: rc.readonlyServiceState, + jsonrpcMethodResultBuilders: rc.jsonrpcMethodResultBuilders, + endpointQuery: endpointQuery, + } + + // Process the endpointQuery using the correct context. + endpointQueryResult := resultCtx.buildEndpointResult() + + // Track the processed result + p.processedResults = append(p.processedResults, endpointQueryResult) +} + +// TODO_TECHDEBT: support batch JSONRPC requests by breaking them into single JSONRPC requests and tracking endpoints' response(s) to each. +// This would also require combining the responses into a single, valid response to the batch JSONRPC request. +// See the following link for more details: +// https://www.jsonrpc.org/specification#batch +// +// GetHTTPResponse builds the HTTP response that should be returned for a JSONRPC service request. +// Implements the gateway.RequestQoSContext interface. +func (rc requestQoSContext) GetHTTPResponse() gateway.HTTPResponse { + if rc.JSONRPCErrorResponse != nil { + return buildHTTPResponse(rc.Logger, rc.JSONRPCErrorResponse) + } + + return buildHTTPResponse(rc.Logger, rc.getJSONRPCResponse()) +} + +// GetObservations returns QoS observations from all processed results. +// GetObservations returns all endpoint observations from the request context. +// Implements gateway.RequestQoSContext interface. +func (rc requestContext) GetObservations() qosobservations.Observations { + /* + return qosobservations.Observations { + RequestObservations: rc. resut???? .GetObservation(), + EndpointObservations: rc.EndpointCallsProcessor.GetObservations(), + // TODO_IN_THIS_PR: Implement this method in observations.go. + // Return basic observations for now + ServiceId: p.ServiceID, + ServiceDescription: p.ServiceDescription, + RequestObservation: p.RequestObservation, + } + */ +} + +// Build and returns an instance EndpointSelectionContext to perform endpoint selection for the client request. +// Implements the gateway.RequestQoSContext +func (rc *requestQoSContext) GetEndpointSelector() protocol.EndpointSelector { + return &EndpointSelectionContext{ + *ReadonlyServiceState: rc.serviceState, + Request: rc.Request, + endpointStore: rc.endpointStore, + customSelector: rc.customEndpointSelector, + } +} + +// TODO_FUTURE(@adshmh): A retry mechanism would require support from this struct to determine if the most recent endpoint call has been successful. +// +// getJSONRPCResponse simply returns the result associated with the most recently reported EndpointCall. +func (rc requestContext) getJSONRPCResponse() *jsonrpc.Response { + // Check if we received any endpoint results + if len(rc.processedResults) == 0 { + // If no results were processed, handle it as a protocol error + return buildResultForNoResponse(rc.Request) + } + + // Return the latest result. + return rc.processedResults[len(rc.processedResults)-1] +} diff --git a/qos/framework/jsonrpc/context_request_builder.go b/qos/framework/jsonrpc/context_request_builder.go new file mode 100644 index 000000000..1260afcb0 --- /dev/null +++ b/qos/framework/jsonrpc/context_request_builder.go @@ -0,0 +1,150 @@ +package framework + +import ( + "context" + "encoding/json" + "io" + "net/http" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// TODO_IN_THIS_PR: ensure the observations will contain: +// - HTTP Status code: e.g. httpStatusRequestValidationFailureUnmarshalFailure, +// - Validation error: e.g. qosobservations.EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE, +// - Error details. + + +// TODO_TECHDEBT(@adshmh): Simplify the qos package by refactoring gateway.QoSContextBuilder. +// Proposed change: Create a new ServiceRequest type containing raw payload data ([]byte) +// Benefits: Decouples the qos package from HTTP-specific error handling. + +// maximum length of the error message stored in request validation failure observations and logs. +// This is used to prevent overly verbose error messages from being stored in logs and metrics leading to excessive memory usage and cost. +const maxErrMessageLen = 1000 + +// TODO_IN_THIS_PR: add hydratedLoggers. + +// requestBuilder handles the construction of requestQoSContext objects +type requestBuilder struct { + Logger polylog.Logger + ServiceID ServiceID + EndpointCallProcessor endpointCallProcessor + EndpointSelector endpointSelector + + context *requestQoSContext +} + +// parseHTTPRequest reads and processes the HTTP request +// validates an HTTP request, extracting and validating its EVM JSONRPC payload. +func (rb *requestBuilder) ParseHTTPRequest(httpReq *http.Request) *requestBuilder { + requestCtx := requestQoSContext { + Logger: rb.Logger, + ServiceID: rb.ServiceID, + } + + // Read the HTTP request body + body, err := io.ReadAll(httpReq.Body) + defer httpReq.Body.Close() + + // TODO_IMPROVE(@adshmh): Propagate a request ID parameter on internal errors + // that occur after successful request parsing. + // There are no such cases as of PR #186. + if err != nil { + // Handle read error (internal server error) + rb.Logger.Error().Err(err).Msg("Failed to read request body") + + // Create error response for read failure + errResp := newErrResponseInternalReadError(err) + + requestCtx.JSONRPCErrorResponse = &errResp + rb.context = requestCtx + return rb + } + + // Parse the JSON-RPC request + var jsonrpcReq jsonrpc.JsonRpcRequest + if err := json.Unmarshal(body, &jsonrpcReq); err != nil { + // TODO_IN_THIS_PR: log the first 1K bytes of the body. + // Handle parse error (client error) + rb.Logger.Error().Err(err).Msg("Failed to parse JSON-RPC request") + + // Create error response for parse failure + errResp := newErrResponseParseError(err) + + requestCtx.JSONRPCErrorResponse = &errResp + rb.context = requestCtx + return rb + } + + // Store the parsed request + requestCtx.Request = &jsonrpcReq + rb.context = requestCtx + return rb +} + +// validateRequest validates the JSONRPC request using the default validator +func (rb *requestBuilder) ValidateRequest() *requestBuilder { + // Skip validation if we already have an error + if rb.context.errorResponse != nil || rb.context.request == nil { + return rb + } + + // Validate the request + errResp, isValid := validateRequest(rb.context.request) + if !isValid { + // Request is invalid according to the validator + rb.Logger.Warn(). + Str("method", rb.context.Request.Method). + Msg("Request validation failed") + + rb.context.JSONRPCErrorResponse = errResp + return rb + } + + rb.context.EndpointCallsProcessor = rb.EndpointCallProcessor + rb.context.EndpointSelector = rb.EndpointSelector + + // Request is valid + rb.Logger.Info(). + Str("method", rb.context.request.Method). + Msg("Request validation successful") + + return rb +} + +// build finalizes and returns the request context +func (rb *requestBuilder) Build() (*requestQoSContext, bool) { + return rb.context, rb.context.errorResponse == nil +} + +// TODO_MVP(@adshmh): Allow custom QoS services to supply custom request validation logic. +// Example use case: specifying a list of allowed JSONRPC request methods. +// This would require: +// 1. Declaring a public RequestValidator interface. +// 2. Helper functions, e.g. BuildRequestValidatorForAllowedMethods. +// +// validateRequest provides a basic validation of JSONRPC requests. +// It checks: +// - JSONRPC version (must be "2.0") +// - Method presence +// +// It returns a JSONRPC response if validation fails and a boolean indicating whether request processing should continue. +func validateRequest(request *jsonrpc.JsonRpcRequest, allowedMethods map[string]bool) (*jsonrpc.Response, bool) { + // Check JSONRPC version + if request.Jsonrpc != jsonrpc.Version2 { + resp := newErrResponseInvalidVersionError(request.Id) + return &resp, false + } + + // Check method presence + if request.Method == "" { + resp := newErrResponseMissingMethodError(request.Id) + return &resp, false + } + + // Request is valid + return nil, true +} diff --git a/qos/framework/jsonrpc/context_state_update.go b/qos/framework/jsonrpc/context_state_update.go new file mode 100644 index 000000000..395781943 --- /dev/null +++ b/qos/framework/jsonrpc/context_state_update.go @@ -0,0 +1,50 @@ +package jsonrpc + +import ( + +) + +// StateUpdateContext provides context and helper methods for updating service state. +type StateUpdateContext struct { + // The result data observations + Results []*ResultData + + // Current service state (read-only copy) + currentState map[string]string + + // New state being built + newState map[string]string +} + +// CopyCurrentState creates a copy of the current state as the starting point. +func (ctx *StateUpdateContext) CopyCurrentState() { + ctx.newState = make(map[string]string, len(ctx.currentState)) + for k, v := range ctx.currentState { + ctx.newState[k] = v + } +} + +// SetValue sets a value in the new state. +func (ctx *StateUpdateContext) SetValue(key, value string) *StateUpdateContext { + ctx.newState[key] = value + return ctx +} + +// DeleteValue removes a value from the new state. +func (ctx *StateUpdateContext) DeleteValue(key string) *StateUpdateContext { + delete(ctx.newState, key) + return ctx +} + +// GetValue gets a value from the current state. +func (ctx *StateUpdateContext) GetValue(key string) (string, bool) { + value, exists := ctx.currentState[key] + return value, exists +} + +// GetState returns the new state map. +func (ctx *StateUpdateContext) GetState() map[string]string { + return ctx.newState +} + + diff --git a/qos/framework/jsonrpc/endpoint.go b/qos/framework/jsonrpc/endpoint.go new file mode 100644 index 000000000..9eb53a84c --- /dev/null +++ b/qos/framework/jsonrpc/endpoint.go @@ -0,0 +1,50 @@ +package jsonrpc + +// TODO_TECHDEBT(@adshmh): Persist this state (which may include sanctions) across restarts to maintain endpoint exclusions. +// TODO_MVP(@adshmh): add an ExpiryTime field and the required support for removing expired items. +// +// Endpoint represents a service endpoint with its associated attributes. +// - Read-only for client code +// - All attributes are set internally by the framework +type Endpoint struct { + attributes map[string]EndpointAttribute +} + +// GetStringAttribute retrieves an attribute's string value, using its key. +func (e *Endpoint) GetStringAttribute(attrName string) (string, bool) { + attr, exists := e.attributes[attrName] + if !exists { + return "", false + } + + return attr.GetStringValue() +} + +// GetIntAttribute retrieves an attribute's integer value, using its key. +func (e *Endpoint) GetIntAttribute(attrName string) (int, bool) { + attr, exists := e.attributes[attrName] + if !exists { + return 0, false + } + + return attr.GetIntValue() +} + +// ApplyQueryResult updates the endpoint's attributes with attributes from the query result. +// It merges the EndpointAttributes from the query result into the endpoint's attributes map. +func (e *Endpoint) ApplyQueryResult(queryResult *EndpointQueryResult) { + // Initialize attributes map if it doesn't exist + if e.attributes == nil { + e.attributes = make(map[string]EndpointAttribute) + } + + // Add or update attributes from the query result + for key, attr := range queryResult.EndpointAttributes { + e.attributes[key] = attr + } +} + +// TODO_IN_THIS_PR: implement. +func (e *Endpoint) HasActiveSanction() (Sanction, bool) { + +} diff --git a/qos/framework/jsonrpc/endpoint_attribute.go b/qos/framework/jsonrpc/endpoint_attribute.go new file mode 100644 index 000000000..740dcf87a --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_attribute.go @@ -0,0 +1,45 @@ +package jsonrpc + +import ( + "errors" + "time" +) + +// TODO_IMPROVE(@adshmh): Enhance EndpointAttribute to support data types commonly used for endpoint attributes. +// TODO_MVP(@adshmh): Add and support an ExpiryTime field. +// +// EndpointAttribute represents a single data item extracted from an endpoint response. +// - Stores either a string or integer value (not both) +// - Contains error/sanction information if associated with a failure +type EndpointAttribute struct { + // Separate typed fields for different attribute types + stringValue *string + intValue *int + + // Error information if this attribute is associated with a failure. + // To be set and handled by framework only. + // QoS services should use helper functions to set it, e.g. from the EndpointAttributeContext struct. + err *EndpointAttributeError +} + +// GetStringValue returns the attribute value as a string +// Returns false if the attribute does not have a string value set: +// It is either an integer attribute, or has an error value set. +func (a EndpointAttribute) GetStringValue() (string, bool) { + if a.stringValue == nil { + return "", false + } + + return *a.stringValue, true +} + +// GetIntValue returns the attribute value as an int +// Returns false if the attribute does not have an integer value set: +// It is either an string attribute, or has an error value set. +func (a EndpointAttribute) GetIntValue() (int, bool) { + if a.intValue == nil { + return 0, false + } + + return *a.intValue, true +} diff --git a/qos/framework/jsonrpc/endpoint_attribute_error.go b/qos/framework/jsonrpc/endpoint_attribute_error.go new file mode 100644 index 000000000..956f06f23 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_attribute_error.go @@ -0,0 +1,16 @@ +package jsonrpc + +// EndpointAttributeError contains error details for endpoint queries. +// An EndpointError is always associated with an Endpoint Attribute struct. +type EndpointAttributeError struct { + // Description is set by the custom service implementation + Description string + + // RecommendedSanction is set by the custom service implementation + // It is under ResultError to clarify the reason a sanction was recommended. + RecommendedSanction *Sanction + + // Internal fields used by the framework + kind endpointErrorKind + rawPayload []byte +} diff --git a/qos/framework/jsonrpc/endpoint_query.go b/qos/framework/jsonrpc/endpoint_query.go new file mode 100644 index 000000000..2fd57db29 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_query.go @@ -0,0 +1,17 @@ +package jsonrpc + +// endpointQuery represents a raw communication attempt with an endpoint. +// Instantiated by: RequestQoSContext. +// Used in EndpointQueryResultContext. +type endpointQuery struct { + serviceName ServiceName + + // request is the JSONRPC request that was sent + request *jsonrpc.Request + + // endpointAddr identifies the endpoint + endpointAddr protocol.EndpointAddr + + // receivedData is the raw response data received from the endpoint (may be nil) + receivedData []byte +} diff --git a/qos/framework/jsonrpc/endpoint_query_result.go b/qos/framework/jsonrpc/endpoint_query_result.go new file mode 100644 index 000000000..b7fb00d26 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_query_result.go @@ -0,0 +1,19 @@ +package jsonrpc + +// EndpointQueryResult captures information extracted from a service endpoint query. +type EndpointQueryResult struct { + // The endpointQuery from which this result was built. + // It can be used, e.g. to retrieve the JSONRPC request and its method. + *endpointQuery + + // The set of endpoint attributes, extracted from the endpoint query and the endpoint's parsed JSONRPC response. + // e.g. for a Solana `getEpochInfo` request, the custom service could derive two endpoint attributes as follows: + // - "BlockHeight": "0x1234" + // - "Epoch": "5" + // It could also include sanctions: + // e.g. for a returned value of "invalid" to an EVM `eth_blockNumber` request, the custom service could set: + // - "eth_blockNumber": EndpointAtribute{error: EndpointError:{RecommendedSanction: {Duration: 5 * time.Minute}}} + EndpointAttributes map[string]EndpointAttribute + + // TODO_FUTURE(@adshmh): add a JSONRPCErrorResponse to allow a result builder to supply its custom JSONRPC response. +} diff --git a/qos/framework/jsonrpc/endpoint_query_result_defaults.go b/qos/framework/jsonrpc/endpoint_query_result_defaults.go new file mode 100644 index 000000000..e7b7c14fd --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_query_result_defaults.go @@ -0,0 +1,130 @@ +package framework + +import ( + "fmt" + "time" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// TODO_IN_THIS_PR: reword/rename the method and the comment. +// defaultResultBuilder is applied by the endpointCallProcessor on EndpointCalls not matching any of the JSONRPC methods specified by the custom service QoS. +// It builds an EndpointResult to track JSONRPC requests/responses which are not utilized by the custom QoS service for updating the service state or endpoint selection. +func defaultResultBuilder(ctx *EndpointQueryResultContext) *EndpointQueryResult { + //TODO_IN_THIS_PR: implement this function: + /* + JsonrpcResponse: &qosobservations.JsonRpcResponse{ + Id: r.jsonRPCResponse.ID.String(), + }, + ResponseValidationError: r.validationError, + HttpStatusCode: int32(r.getHTTPStatusCode()), + */ +} + + +// TODO_IN_THIS_PR: clarify that the following happens for a NoResponse: +// - NoResponse's underlying getHTTPStatusCode always returns a 500 Internal error. +// - NoResponse is always an invalid response. +// +// buildResultForNoResponse handles the case when no endpoint response was received. +// This can occur due to protocol-level failures or when no endpoint was selected. +// This is not the same as empty responses (where an endpoint responded with empty data). +func buildResultForNoResponse(request *jsonrpc.JsonRpcRequest) *EndpointQueryResult { + // Create a synthetic result for protocol error + result := &EndpointResult{ + Call: &EndpointCall{ + Request: request, + ReceivedAt: time.Now(), + }, + parseResult: &parseResult{ + parseError: fmt.Errorf("no endpoint responses received"), + }, + } + + // Create result context for creating the result + ctx := &ResultContext{ + Request: request, + } + + // Apply default sanction for no response + result.ResultData = applySanctionForNoResponse(ctx) + + // Set error kind + result.ResultData.Error.kind = EndpointDataErrorKindNoInteraction + + // Set error response + result.ErrorResponse = newErrResponseNoEndpointResponses(request.Id) + + return result +} + +// TODO_MVP(@adshmh): Implement request retry support: +// 1. Add ShouldRetry() method to gateway.RequestQoSContext +// 2. Integrate ShouldRetry() into gateway request handler +// 3. Extend evm.response interface with ShouldRetry() +// 4. Add ShouldRetry() to evm.requestContext to evaluate retry eligibility based on responses +// +// TODO_IN_THIS_PR: update comments to show the following for Empty response: +// EmptyResponse always returns a 500 Internal error HTTP status code. +// An empty response is always invalid: e.g. EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_EMPTY +// +// buildResultForEmptyResponse handles the case when an endpoint returned an empty response. +func buildResultForEmptyResponse(call *EndpointCall) *EndpointQueryResult { + // Create a new result with the call + result := &EndpointResult{ + Call: call, + parseResult: &parseResult{ + isEmpty: true, + parseError: fmt.Errorf("empty response from endpoint"), + }, + } + + // Create result context for creating the result + ctx := &ResultContext{ + EndpointAddr: call.EndpointAddr, + Request: call.Request, + } + + // Apply default sanction for empty response + result.ResultData = applySanctionForEmptyResponse(ctx) + + // Set error kind + result.ResultData.Error.kind = EndpointDataErrorKindEmptyPayload + + // Set error response + result.ErrorResponse = newErrResponseEmptyResponse(call.Request.Id) + + return result +} + +// buildResultForErrorUnmarshalingEndpointReturnedData handles the case when parsing the endpoint's returned data failed. +func buildResultForErrorUnmarshalingEndpointReturnedData( + call *EndpointCall, + parseError error, +) *EndpointQueryResult { + // Create a new result with the call + result := &EndpointResult{ + Call: call, + parseResult: &parseResult{ + parseError: parseError, + }, + } + + // Create result context for creating the result + ctx := &ResultContext{ + EndpointAddr: call.EndpointAddr, + Request: call.Request, + } + + // Apply default sanction for parse error + result.ResultData = applySanctionForUnmarshalingError(ctx, parseError) + + // Set error kind and raw payload + result.ResultData.Error.kind = EndpointDataErrorKindUnmarshaling + result.ResultData.Error.rawPayload = call.ReceivedData + + // Set error response + result.ErrorResponse = newErrResponseParseError(call.Request.Id, parseError) + + return result +} diff --git a/qos/framework/jsonrpc/endpoint_sanction.go b/qos/framework/jsonrpc/endpoint_sanction.go new file mode 100644 index 000000000..b5df1db97 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_sanction.go @@ -0,0 +1,21 @@ +package jsonrpc + +// ====================== +// Sanction Types +// ====================== + +// SanctionType identifies different types of endpoint sanctions. +type SanctionType int + +const ( + _ SanctionType = iota + SanctionTypeTemporary // Time-limited exclusion + SanctionTypePermanent // Permanent exclusion +) + +// Sanction represents a recommendation to limit endpoint usage. +type Sanction struct { + Type SanctionType + Reason string + ExpiryTime time.Time // Zero time means permanent +} diff --git a/qos/framework/jsonrpc/endpoint_sanction_defaults.go b/qos/framework/jsonrpc/endpoint_sanction_defaults.go new file mode 100644 index 000000000..6d24195f6 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_sanction_defaults.go @@ -0,0 +1,51 @@ +package framework + +import ( + "fmt" + "time" +) + +// TODO_FUTURE(@adshmh): Support one or both of the following: +// - ake these sanction durations/types configurable through service config, +const ( + // Default sanction duration for empty responses + DefaultEmptyResponseSanctionDuration = 5 * time.Minute + + // Default sanction duration for parse errors + DefaultParseErrorSanctionDuration = 5 * time.Minute + + // Default sanction duration for no responses + DefaultNoResponseSanctionDuration = 5 * time.Minute +) + +// applySanctionForNoResponse applies the default sanction for when no endpoint responded. +// This can occur due to protocol-level failures: e.g. the selected endpoint was temporarily unavailable. +// This is not the same as empty responses (where an endpoint responded with empty data). +func applySanctionForNoResponse(ctx *ResultContext) *ResultData { + return ctx.SanctionEndpoint( + "No endpoint responses received", + "Protocol error", + DefaultNoResponseSanctionDuration, + ) +} + +// applySanctionForEmptyResponse applies the default sanction for empty responses. +func applySanctionForEmptyResponse(ctx *ResultContext) *ResultData { + return ctx.SanctionEndpoint( + "Empty response from endpoint", + "Empty response", + DefaultEmptyResponseSanctionDuration, + ) +} + +// applySanctionForUnmarshalingError applies the default sanction for parse errors. +func applySanctionForUnmarshalingError(ctx *ResultContext, err error) *ResultData { + return ctx.SanctionEndpoint( + fmt.Sprintf("Failed to parse endpoint response: %v", err), + "Parse error", + DefaultParseErrorSanctionDuration, + ) +} + +// TODO_FUTURE: Add capability to override default sanctions and/or make them configurable +// through service configuration or dynamic policy updates. diff --git a/qos/framework/jsonrpc/endpoint_store.go b/qos/framework/jsonrpc/endpoint_store.go new file mode 100644 index 000000000..b1df20657 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_store.go @@ -0,0 +1,39 @@ +package jsonrpc + +import ( + "sync" + + "github.com/buildwithgrove/path/protocol" +) + +// endpointStore maintains data on the set of available endpoints. +// It is package-private and not meant to be used directly by any entity outside the jsonrpc package. +type endpointStore struct { + endpointsMu sync.RWMutex + endpoints map[protocol.EndpointAddr]Endpoint +} + +// storeEndpoint stores or updates an endpoint in the store. +func (es *endpointStore) storeEndpoint(addr protocol.EndpointAddr, endpoint Endpoint) { + es.endpointsMu.Lock() + defer es.endpointsMu.Unlock() + + if es.endpoints == nil { + es.endpoints = make(map[protocol.EndpointAddr]Endpoint) + } + + es.endpoints[addr] = endpoint +} + +// getEndpoint retrieves an endpoint by its address. +func (es *endpointStore) getEndpoint(addr protocol.EndpointAddr) (Endpoint, bool) { + es.endpointsMu.RLock() + defer es.endpointsMu.RUnlock() + + if es.endpoints == nil { + return Endpoint{}, false + } + + endpoint, found := es.endpoints[addr] + return endpoint, found +} diff --git a/qos/framework/jsonrpc/example_evm/check.go b/qos/framework/jsonrpc/example_evm/check.go new file mode 100644 index 000000000..436a51071 --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/check.go @@ -0,0 +1,3 @@ +package evm + +// TODO_IN_THIS_PR: use the framework's EndpointQualityCheckContext for adding QoS checks. diff --git a/qos/framework/jsonrpc/example_evm/config.go b/qos/framework/jsonrpc/example_evm/config.go new file mode 100644 index 000000000..7ab1513f5 --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/config.go @@ -0,0 +1,16 @@ +package evm + +// Config captures the modifiable settings of the EVM QoS service. +// This will enable the QoS service to be used as part of EVM-based blockchains which may have different desired QoS properties. +// e.g. different blockchains QoS instances could have different tolerance levels for deviation from the current block height. +type Config struct { + // TODO_TECHDEBT(@adshmh): apply the sync allowance when validating an endpoint's block height. + // SyncAllowance specifies the maximum number of blocks an endpoint + // can be behind, compared to the blockchain's perceived block height, + // before being filtered out. + SyncAllowance uint64 + + // ChainID is the ID used by the corresponding blockchain. + // It is used to verify responses to service requests with `eth_chainId` method. + ChainID string +} diff --git a/qos/framework/jsonrpc/example_evm/endpoint_result_blocknumber.go b/qos/framework/jsonrpc/example_evm/endpoint_result_blocknumber.go new file mode 100644 index 000000000..08c491bfc --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/endpoint_result_blocknumber.go @@ -0,0 +1,49 @@ +package evm + +import ( + framework "github.com/buildwithgrove/path/qos/framework/jsonrpc" +) + +const ( + // methodBlockNumber is the JSON-RPC method for getting the latest block number. + // Reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber + methodBlockNumber = jsonrpc.Method("eth_blockNumber") + + // Endpoint attirbute to track its response to `eth_blockNumber` + attrETHBlockNumber = methodBlockNumber +) + +// responseToBlockNumber provides the functionality required from a response by a requestContext instance. +var _ framework.EndpointResultBuilder = responseBuilderBlockNumber + +// TODO_TECHDEBT(@adshmh): validate the `eth_blockNumber` request that was sent to the endpoint. +// +// responseBuilderBlockNumber handles attribute building and sanctioning of endpoints based on the data returned to `eth_blockNumber` requests. +func responseBuilderBlockNumber(ctx *framework.EndpointResultContext) *framework.ResultData { + // TODO_MVP(@adshmh): implement the framework's RequestValidator interface to filter out invalid `eth_blockNumber` requests. + // + // The endpoint returned an error response: no further processing needed. + if ctx.IsJSONRPCError() { + return ctx.Error("endpoint returned a valid JSONRPC error response.") + } + + // TODO_MVP(@adshmh): use the contents of the result field to determine the validity of the response. + // e.g. a response that fails parsing as a number is not valid. + // + // The endpoint returned an error: no need to do further processing of the response. + blockNumber, err := ctx.GetResultAsInt() + if err != nil { + return ctx.SanctionEndpoint(5 * time.Minute, "endpoint returned malformed response to eth_blockNumber") + } + + // Sanction the endpoint if it returned an invalid block number. + if blockNumber <= 0 { + return ctx.SanctionEndpoint(5 * time.Minute, "endpoint returned invalid value as block number") + } + + // Store the endpoint's reported block number as its attribute. + // This attribute will be used in: + // - state update: to determine the perceived block number on the blockchain. + // - endpoint selection: to drop out-of-sync endpoints. + return ctx.Success(ctx.BuildIntAttribute(ethBlockNumber, blockNumber) +} diff --git a/qos/framework/jsonrpc/example_evm/endpoint_result_chainid.go b/qos/framework/jsonrpc/example_evm/endpoint_result_chainid.go new file mode 100644 index 000000000..d39090bc0 --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/endpoint_result_chainid.go @@ -0,0 +1,56 @@ +package evm + +import ( + framework "github.com/buildwithgrove/path/qos/framework/jsonrpc" +) + +const ( + // methodChainID is the JSON-RPC method for getting the chain ID. + // Reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_chainid + methodChainID = jsonrpc.Method("eth_chainId") + + // Endpoint attirbute to track its response to `eth_chainId` + attrETHChainID = methodChainID +) + +// endpointResultBuilderChainID provides the functionality required from an endpoint result builder. +var _ framework.EndpointResultBuilder = endpointAttributeBuilderChainID + +// TODO_MVP(@adshmh): handle the following scenarios: +// 1. An endpoint returned a malformed, i.e. Not in JSONRPC format, response. +// The user-facing response should include the request's ID. +// 2. An endpoint returns a JSONRPC response indicating a user error: +// This should be returned to the user as-is. +// 3. An endpoint returns a valid JSONRPC response to a valid user request: +// This should be returned to the user as-is. +// +// TODO_TECHDEBT(@adshmh): validate the `eth_chainId` request that was sent to the endpoint. +// +// endpointResultBuilderChainID handles attribute building and sanctioning of endpoints based on the data returned to `eth_chainId` requests. +func endpointResultBuilderChainID(ctx *framework.EndpointResultContext) *framework.ResultData { + // TODO_MVP(@adshmh): implement the framework's RequestValidator interface to filter out invalid `eth_chainId` requests. + // + // The endpoint returned an error response: no further processing needed. + if ctx.IsJSONRPCError() { + return ctx.Error("endpoint returned a JSONRPC error response.") + } + + // TODO_MVP(@adshmh): use the contents of the result field to determine the validity of the response. + // e.g. a response that fails parsing as a number is not valid. + // + // The endpoint returned an error: no need to do further processing of the response. + chainID, err := ctx.GetResultAsString() + if err != nil { + return ctx.SanctionEndpoint(5 * time.Minute, "endpoint returned malformed response to eth_chainID") + } + + // Sanction the endpoint if it returned an invalid chain ID + if chainID != config.GetChainID() { + return ctx.SanctionEndpoint(5 * time.Minute, "endpoint returned invalid value as chain ID") + } + + // Store the endpoint's reported chainID as its attribute. + // This attribute will be used in: + // - endpoint selection: to drop misconfigured endpoints. + return ctx.Success(ctx.BuildStringAttribute(ethChainID, chainID) +} diff --git a/qos/framework/jsonrpc/example_evm/endpoint_selection.go b/qos/framework/jsonrpc/example_evm/endpoint_selection.go new file mode 100644 index 000000000..f344bc8e7 --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/endpoint_selection.go @@ -0,0 +1,67 @@ +package evm + +import ( + "github.com/buildwithgrove/path/protocol" + framework "github.com/buildwithgrove/path/qos/framework/jsonrpc" +) + +// The errors below list all the possible validation errors on an endpoint. +var ( + errNoChainIDObs = fmt.Errorf("endpoint has not had an observation of its response to a %q request", methodChainID) + errInvalidChainIDObs = fmt.Errorf("endpoint returned an invalid response to a %q request", methodChainID) + errNoBlockNumberObs = fmt.Errorf("endpoint has not had an observation of its response to a %q request", methodBlockNumber) + errInvalidBlockNumberObs = fmt.Errorf("endpoint returned an invalid response to a %q request", methodBlockNumber) + errHasReturnedEmptyResponse = errors.New("endpoint is invalid: history of empty responses") +) + +var _ framework.EndpointSelector = evmEndpointSelector + +// evmEndpointSelector selects an endpoint from the set of available ones. +// It uses the configuration and service state to filter out misconfigured/out-of-sync/invalid endpoints. +func evmEndpointSelector(ctx *EndpointSelectionContext) (protocol.EndpointAddr, error) { + // TODO_IN_THIS_PR: the framework should log an entry if the state attribute is not set. + // Fetch latest block number from the service state. + perceivedBlockNumber := ctx.GetState().GetIntAttribute(attrETHBlockNumber) + + // TODO_FUTURE(@adshmh): use service-specific metrics to add an endpoint ranking method. + // e.g. use latency to break the tie between valid endpoints. + for _, endpoint := range ctx.GetAvailableEndpoints() { + endpointChainID, err := endpoint.GetStringAttribute(attrETHChainID) + // ChainID attribute not set: Disqualify the endpoint. + if err != nil { + ctx.DisqualifyEndpoint(endpoint, err) + continue + } + + // TODO_MVP(@adshmh): pass the EVM config to endpoint selector. + // Invalid ChainID returned by the endpoint: Disqualify. + if endpointChainID != config.GetChainID() { + ctx.DisqualifyEndpoint(endpoint, fmt.Errorf("invalid chain ID %s, expected: %s", endpointChainID, config.GetChainID())) + continue + } + + endpointBlockNumber, err := endpoint.GetIntAttribute(attrETHBlockNumber) + // BlockNumber attribute not set: Disqualify the endpoint. + if err != nil { + ctx.DisqualifyEndpoint(endpoint, err) + continue + } + + // endpoint will only be disqualified if the State has reported a block number. + if perceivedBlockNumber <= 0 { + continue + } + + // endpoint is out-of-sync: Disqualify. + if endpointBlockNumber < perceivedBlockNumber { + ctx.DisqualifyEndpoint(endpoint, fmt.Errorf("endpoint out of sync got %d block number, expected: %d", endpointBlockNumber, perceivedBlockNumber)) + continue + } + + // TODO_IN_THIS_PR: validate archival state. + } + + // All invalid endpoints have been marked as disqualified. + // Return a randomly selected Qualified endpoint. + return ctx.SelectRandomQualifiedEndpoint() +} diff --git a/qos/framework/jsonrpc/example_evm/qos.go b/qos/framework/jsonrpc/example_evm/qos.go new file mode 100644 index 000000000..c3a3b38c2 --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/qos.go @@ -0,0 +1,53 @@ +package evm + +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + framework "github.com/buildwithgrove/path/qos/framework/jsonrpc" +) + +// NewQoSInstance builds and returns an instance of the EVM QoS service. +func NewQoSInstance(logger polylog.Logger, evmChainID string) *QoS { + logger = logger.With( + "qos_instance", "evm", + "evm_chain_id", evmChainID, + ) + + // Setup the QoS definitions for EVM blockchains QoS service + qosDefinition := framework.QoSDefinition { + Logger: logger, + + // ServiceInfo for the EVM blockchain QoS service + ServiceInfo: framework.ServiceInfo{ + Name: "EVM-QoS", + Description: "QoS service for EVM blockchains, built using PATH's QoS framework", + }, + + // ResultBuilders for JSONRPC request methods used for endpoint attributes. + ResultBuilders: getJSONRPCMethodEndpointResultBuilders(), + + // StateUpdater uses the endpoint attributes to update the service state. + StateUpdater: + + // custom endpoint selection logic + EndpointSelector: + + // TODO_FUTURE(@adshmh): implement and supply a custom request validator to control the set of allowed JSONRPC methods. + // + // Use the framework's default request validator: i.e. accept any valid JSONRPC request. + RequestValidator: nil, + } + + return framework.NewQoSService(qosDefinition) +} + +// Return the set of endpoint result builders to be called by the framework. +// A result builder will be called if it matches the method of the JSONRPC request from the client. +func getJSONRPCMethodEndpointResultBuilders() map[jsonrpc.Method]EndpointResultBuilder { + // EVM QoS service collects endpoint attributes based on responses to the following methods. + return map[jsonrpc.Method]EndpointResultBuilder { + jsonrpc.Method(methodETHChainID): endpointResultBuilderChainID, + jsonrpc.Method(methodETHBlockNumber): endpointResultBuilderBlockNumber, + // TODO_IN_THIS_PR: add eth_getBalance + } +} diff --git a/qos/framework/jsonrpc/example_evm/state_update.go b/qos/framework/jsonrpc/example_evm/state_update.go new file mode 100644 index 000000000..bf84caeaf --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/state_update.go @@ -0,0 +1,55 @@ +package evm + +import ( + framework "github.com/buildwithgrove/path/qos/framework/jsonrpc" +) + +type evmStateUpdater struct { + logger polylog.Logger + config Config +} + +// implements the framework.StateUpdater interface +func (esu evmStateUpdater) UpdateServiceState(ctx *StateUpdateContext) *framework.StateUpdate { + + var maxObservedBlockNumber int + + // Loop over all updated endpoints. + for _, endpoint := range ctx.GetUpdatedEndpoints() { + + // validate endpoint's Chain ID attribute. + endpointChainID, err := endpoint.GetStringAttribute(attrETHChainID) + // Do not use endpoints with invalid/missing chain ID for service state update. + if err != nil { + logger.Debug().Err(err).Msg("Skipping endpoint with missing/invalid chain id") + continue + } + + // TODO_TECHDEBT(@adshmh): use a more resilient method for updating block height. + // E.g. one endpoint returning a very large number as block height should + // not result in all other endpoints being marked as invalid. + // + // validate endpoint's Block Number attribute. + endpointBlockNumber, err := endpoint.GetIntAttribute(attrETHBlockNumber) + if err != nil { + logger.Debug().Err(err).Msg("Skipping endpoint with missing/invalid block number") + continue + } + + if endpointBlockNumber > maxObservedBlockNumber { + maxObservedBlockNumber = endpointBlockNumber + } + } + + // Fetch the latest block number from the service state. + perceivedBlockNumber := ctx.GetStateIntAttribute(attrETHBlockNumber) + + // Skip state update if block number has not increased. + if perceivedBlockNumber >= maxObservedBlockNumber { + return nil + } + + // Update the service state to maximum observed block number. + s.MarkStateIntAttributeForUpdate(attrETHBlockNumber, maxObservedBlockNumber) + return s.BuildStateUpdateData() +} diff --git a/qos/framework/jsonrpc/framework.go b/qos/framework/jsonrpc/framework.go new file mode 100644 index 000000000..7e7da8914 --- /dev/null +++ b/qos/framework/jsonrpc/framework.go @@ -0,0 +1,57 @@ +// Package jsonrpc provides a framework for implementing Quality of Service (QoS) for JSONRPC-based services. +// +// Key components: +// - Context-based processing for standardizing service interactions +// - Custom endpoint selection based on service state +// - Custom result processing and extraction +// - Service state management with observability +// +// Users implement the QoSDefinition interface to create custom QoS services that +// leverage the framework's request handling, endpoint management, and state tracking. +package jsonrpc + +// TODO_FUTURE(@adshmh): Provide reasonable defaults for components to enable a no-config JSONRPC service QoS. +// +// QoSDefinition contains all custom behavior for a JSONRPC QoS service. +// Implementers must provide all of the customization functions below. +type QoSDefinition struct { + // Logger for service logs. If nil, a default logger is used + Logger polylog.Logger + + // ServiceInfo identifies and describes the service + ServiceInfo ServiceInfo + + // ResultBuilders maps JSONRPC methods to custom result processing logic + ResultBuilders map[string]EndpointResultBuilder + + // StateUpdater defines how endpoint results affect service state + StateUpdater StateUpdater + + // EndpointSelector defines custom endpoint selection logic + EndpointSelector EndpointSelector + + // Optional: do request validation on a parsed JSONRPC request. + RequestValidator RequestValidator + + // TODO_FUTURE(@adshmh): Add additional configuration options: + // - InitialState: Starting values for service state + // - AllowedMethods: Restrict which JSONRPC methods can be processed + // - RequestTimeout: Custom timeout for requests + // - RetryPolicy: Configuration for request retries + // - StateExpiryPolicy: Rules for expiring state entries + // - MetricsCollection: Settings for performance metrics +} + +// EndpointResultBuilder processes a response and extracts relevant data +type EndpointResultBuilder func(ctx *EndpointResultContext) *ResultData + +// StateUpdater updates service state based on endpoint results +type StateUpdater func(ctx *StateUpdateContext) map[string]string + +// EndpointSelector chooses an endpoint for a request based on service state +type EndpointSelector func(ctx *EndpointSelectionContext) (protocol.EndpointAddr, error) + +// NewQoSService creates a new QoS service with the given definition +func NewQoSService(def QoSDefinition) *QoS { + // TODO_IN_THIS_PR: instantiate the framrwork using the QoSDeinition struct. +} diff --git a/qos/framework/jsonrpc/jsonrpc_errors.go b/qos/framework/jsonrpc/jsonrpc_errors.go new file mode 100644 index 000000000..030fe051d --- /dev/null +++ b/qos/framework/jsonrpc/jsonrpc_errors.go @@ -0,0 +1,226 @@ +package framework + +import ( + "fmt" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +const ( + // Standard JSONRPC 2.0 error codes + ErrorCodeParseError int64 = -32700 + ErrorCodeInvalidRequest int64 = -32600 + ErrorCodeMethodNotFound int64 = -32601 + ErrorCodeInvalidParams int64 = -32602 + ErrorCodeInternalError int64 = -32603 + + // Server error codes (reserved from -32000 to -32099) + ErrorCodeServerError int64 = -32000 +) + +// newErrResponseEmptyEndpointResponse creates a JSON-RPC error response for empty endpoint responses: +// - Preserves original request ID +// - Marks error as retryable for safe client retry +func newErrResponseEmptyEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, // Use request's original ID if present + -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html + "Endpoint (data/service node error): Received an empty response. The endpoint will be dropped from the selection pool. Please try again.", // Error Response Message + map[string]string{ + // Custom extension - not part of the official JSON-RPC spec + // Marks the error as retryable to allow clients to safely retry their request. + "retryable": "true", + }, + ) +} + +// newErrResponseParseError creates a JSON-RPC error response for parse errors. +// This response: +// - Preserves the original request ID +// - Marks error as retryable +// - Indicates the endpoint response couldn't be parsed +func newErrResponseParseError(requestID jsonrpc.ID, parseErr error) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + ErrorCodeParseError, + "Failed to parse endpoint response", + map[string]string{ + "error": parseErr.Error(), + "retryable": "true", + }, + ) +} + +// newErrResponseNoEndpointResponse creates a JSON-RPC error response for the case +// where no endpoint response was received at all. +// This response: +// - Preserves the original request ID +// - Marks error as retryable for safe client retry +// - Provides actionable message for clients +func newErrResponseNoEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, // Use request's original ID if present + -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html + "Failed to receive any response from endpoints. This could be due to network issues or high load. Please try again.", // Error Response Message + map[string]string{ + // Custom extension - not part of the official JSON-RPC spec + // Marks the error as retryable to allow clients to safely retry their request. + "retryable": "true", + }, + ) +} + +// newErrResponseInternalError creates a JSON-RPC error response for internal server errors. +// e.g. error reading HTTP request's body. +// This response: +// - Preserves the original request ID if available +// - Marks error as retryable for safe client retry +// - Provides a generic internal error message +func newErrResponseInternalError(requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html + fmt.Sprintf("internal error: %s", err.Error()), // Error Message + map[string]string{ + "error": err.Error(), + // Custom extension - not part of the official JSON-RPC spec + // Marks the error as retryable to allow clients to safely retry their request + "retryable": "true", + }, + ) +} + +// newErrResponseMarshalError creates a JSON-RPC error response for marshaling errors. +// This response: +// - Preserves the original request ID if available +// - Marks error as retryable +// - Indicates the response couldn't be serialized +func newErrResponseMarshalError(requestID jsonrpc.ID, marshalErr error) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + ErrorCodeInternalError, + fmt.Sprintf("Failed to marshal response: %s", marshalErr.Error()), + map[string]string{ + "retryable": "true", + }, + ) +} + +// The error indicates the request cannot be processed due to issues like: +// - Failed JSON-RPC deserialization +// - Missing required JSON-RPC fields (e.g. `method`) +// - Unsupported JSON-RPC method +// +// If the request contains a valid JSON-RPC ID, it is included in the error response. +// The error is marked as permanent since retrying without correcting the request will fail. +func newErrResponseInvalidRequest(err error, requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, // Use request's original ID if present + -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html + fmt.Sprintf("invalid request: %s", err.Error()), // Error Message + map[string]string{ + "error": err.Error(), + // Custom extension - not part of the official JSON-RPC spec + // Indicates this error is permanent - the request must be corrected as retrying will not succeed + "retryable": "false", + }, + ) +} + +// newErrResponseMarshalError creates a JSON-RPC error response for marshaling errors. +// This response: +// - Preserves the original request ID if available +// - Marks error as retryable +// - Indicates the response couldn't be serialized +func newErrResponseMarshalError(requestID jsonrpc.ID, marshalErr error) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + ErrorCodeInternalError, + fmt.Sprintf("Failed to marshal response: %s", marshalErr.Error()), + map[string]string{ + "retryable": "true", + }, + ) +} + +// newErrResponseInvalidVersionError creates a JSON-RPC error response for invalid version errors. +// This response: +// - Preserves the original request ID +// - Marks error as non-retryable since it's a client issue +// - Indicates the JSONRPC version is invalid +func newErrResponseInvalidVersionError(requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + ErrorCodeInvalidRequest, + "Invalid JSON-RPC version, expected '2.0'", + map[string]string{ + "retryable": "false", + }, + ) +} + +// newErrResponseMissingMethodError creates a JSON-RPC error response for missing method errors. +// This response: +// - Preserves the original request ID +// - Marks error as non-retryable since it's a client issue +// - Indicates that the method field is required +func newErrResponseMissingMethodError(requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + ErrorCodeInvalidRequest, + "Method is required", + map[string]string{ + "retryable": "false", + }, + ) +} + +// newErrResponseInternalReadError creates a JSON-RPC error response for HTTP request read errors. +// This response: +// - Uses an empty ID since the request couldn't be read +// - Marks error as retryable since it's likely a server issue +// - Provides the specific read error message +func newErrResponseInternalReadError(readErr error) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + jsonrpc.ID{}, // No ID for read errors + ErrorCodeInternalError, + "Internal server error: failed to read request", + map[string]string{ + "error": readErr.Error(), + "retryable": "true", + }, + ) +} + +// newErrResponseParseRequestError creates a JSON-RPC error response for parse errors. +// This response: +// - Uses an empty ID since we couldn't parse the request to get an ID +// - Marks error as non-retryable since it's likely a client issue with the JSONRPC format +// - Indicates the request couldn't be parsed +func newErrResponseParseRequestError(parseErr error) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + jsonrpc.ID{}, // No ID for parse errors + ErrorCodeParseError, + "Failed to parse JSON-RPC request", + map[string]string{ + "error": parseErr.Error(), + "retryable": "false", + }, + ) +} + +// MarshalErrorResponse marshals a JSONRPC error response to JSON. +// This handles the serialization of the error response to bytes. +func MarshalErrorResponse( + logger polylog.Logger, + response jsonrpc.Response, +) ([]byte, error) { + payload, err := response.MarshalJSON() + if err != nil { + // Create a simple fallback error response as raw JSON + fallback := fmt.Sprintf(`{"jsonrpc":"2.0","id":"%v","error":{"code":%d,"message":"%s"}}`, + response.ID, response.Error.Code, response.Error.Message) + return []byte(fallback), nil + } + return payload, nil +} diff --git a/qos/framework/jsonrpc/observations.go b/qos/framework/jsonrpc/observations.go new file mode 100644 index 000000000..7b5e438d6 --- /dev/null +++ b/qos/framework/jsonrpc/observations.go @@ -0,0 +1,26 @@ +package framework + +import ( + "github.com/buildwithgrove/path/observation/qos/jsonrpc" + observations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +func (eq *endpointQuery) buildObservations() *observations.Observations { + return observations.Observations{ + // Service info + ServiceName: + ServiceDescription: + + // Observation of the client request + RequestObservation *RequestObservation + // Observations from endpoint(s) + EndpointObservations []*EndpointObservation + + } +} + +func (eqr *EndpointQueryResult) buildObservations() *observations.EndpointQueryResult + +func extractEndpointQueryResults(observations *observations.Observations) []*EndpointQueryResult + diff --git a/qos/framework/jsonrpc/observations_endpoint_result.go b/qos/framework/jsonrpc/observations_endpoint_result.go new file mode 100644 index 000000000..e198f2250 --- /dev/null +++ b/qos/framework/jsonrpc/observations_endpoint_result.go @@ -0,0 +1,117 @@ +package framework + +import ( + "github.com/buildwithgrove/path/observation/qos/jsonrpc" + jsonrpcobservations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// buildObservations converts an EndpointQueryResult to jsonrpc.Observations +// for reporting metrics and analysis. +func (eqr *EndpointQueryResult) buildObservations() *observations.EndpointQueryResult { + // Create the endpoint attributes map for the protobuf message + protoAttributes := make(map[string]*observations.EndpointAttribute) + + // Convert each EndpointAttribute to its protobuf representation + for key, attr := range eqr.EndpointAttributes { + protoAttr := &observations.EndpointAttribute{} + + // Convert the value based on its type (string or int) + if strVal, ok := attr.GetStringValue(); ok { + protoAttr.Value = &observations.EndpointAttribute_StringValue{ + StringValue: strVal, + } + } else if intVal, ok := attr.GetIntValue(); ok { + protoAttr.Value = &observations.EndpointAttribute_IntValue{ + IntValue: int32(intVal), + } + } + + // Add error information if present + if attr.err != nil { + protoAttr.Error = &observations.EndpointAttributeError{ + ErrorKind: observations.EndpointErrorKind(attr.err.kind), + Description: attr.err.Description, + } + + // Add sanction information if present + if attr.err.RecommendedSanction != nil { + protoAttr.Error.RecommendedSanction = &observations.Sanction{ + Type: observations.SanctionType(attr.err.RecommendedSanction.Type), + Reason: attr.err.RecommendedSanction.Reason, + } + + // Convert expiry time if it's not zero + if !attr.err.RecommendedSanction.ExpiryTime.IsZero() { + ts, _ := ptypes.TimestampProto(attr.err.RecommendedSanction.ExpiryTime) + protoAttr.Error.RecommendedSanction.ExpiryTimestamp = ts + } + } + } + + protoAttributes[key] = protoAttr + } + + // Create and return the EndpointQueryResult proto + return &observations.EndpointQueryResult{ + EndpointAttributes: protoAttributes, + } +} + +// extractEndpointAttributes converts protobuf Observations to []*EndpointQueryResult +// This allows the framework to recreate EndpointQueryResults from serialized observations +func extractEndpointQueryResults(observations *observations.Observations) []*EndpointQueryResult { + results := make([]*EndpointQueryResult, 0, len(observations.EndpointObservations)) + + // Extract data from each endpoint observation + for _, observation := range observations.EndpointObservations { + // Create a new EndpointQueryResult + result := &EndpointQueryResult{ + EndpointAttributes: make(map[string]jsonrpc.EndpointAttribute), + } + + // Convert protobuf attributes to EndpointAttribute objects + for key, protoAttr := range observation.Result.EndpointAttributes { + attr := jsonrpc.EndpointAttribute{} + + // Convert value based on type + switch v := protoAttr.Value.(type) { + case *observations.EndpointAttribute_StringValue: + strVal := v.StringValue + attr.stringValue = &strVal + case *observations.EndpointAttribute_IntValue: + intVal := int(v.IntValue) + attr.intValue = &intVal + } + + // Convert error information if present + if protoAttr.Error != nil { + attr.err = &jsonrpc.EndpointAttributeError{ + Description: protoAttr.Error.Description, + kind: jsonrpc.endpointErrorKind(protoAttr.Error.ErrorKind), + } + + // Convert sanction information if present + if protoAttr.Error.RecommendedSanction != nil { + attr.err.RecommendedSanction = &jsonrpc.Sanction{ + Type: jsonrpc.SanctionType(protoAttr.Error.RecommendedSanction.Type), + Reason: protoAttr.Error.RecommendedSanction.Reason, + } + + // Convert expiry timestamp if present + if protoAttr.Error.RecommendedSanction.ExpiryTimestamp != nil { + t, _ := ptypes.Timestamp(protoAttr.Error.RecommendedSanction.ExpiryTimestamp) + attr.err.RecommendedSanction.ExpiryTime = t + } + } + } + + // Add attribute to the result + result.EndpointAttributes[key] = attr + } + + results = append(results, result) + } + + return results +} diff --git a/qos/framework/jsonrpc/qos.go b/qos/framework/jsonrpc/qos.go new file mode 100644 index 000000000..7c8efae88 --- /dev/null +++ b/qos/framework/jsonrpc/qos.go @@ -0,0 +1,72 @@ +package jsonrpc + +import ( + "context" + "encoding/json" + "io" + "net/http" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/observation/qos" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// QoS represents a service that processes JSONRPC requests and applies QoS policies based on data returned by endpoints. +type QoS struct { + // Logger for diagnostics + logger polylog.Logger + + // Service identification + serviceID ServiceID + + endpointCallProcessor endpointCallProcessor + endpointSelector endpointSelector + serviceState serviceState + + // TODO_MVP(@adshmh): Enable custom service QoS implementations to provide + // a list of allowed methods which the requestValidator needs to enforce. +} + +// Stored all the fields required for identification of the service. +// Used when interpreting observations. +type ServiceID struct { + ID string + Description string +} + +// ParseHTTPRequest handles parsing an HTTP request and validating its content +// It returns a RequestQoSContext and a boolean indicating if processing should continue +func (s *QoSService) ParseHTTPRequest( + ctx context.Context, + httpReq *http.Request, +) (*requestQoSContext, bool) { + builder := requestContextBuilder{ + Logger: s.Logger, + ServiceID: s.ServiceID, + } + + // Parse the HTTP request + builder.ParseHTTPRequest(httpReq) + + // Validate the JSONRPC request + builder.ValidateRequest() + + // Build and return the final context + reqContext, shouldContinue := builder.Build() + return reqContext, shouldContinue +} + +// TODO_IN_THIS_PR: implement this method +// func (qos *QoS) ParseWebsocketRequest(_ context.Context) (gateway.RequestQoSContext, bool) + + +func (s *QoSService) ApplyObservations(observations *qosobservations.Observations) error +) { +// -> Framework updates the endpoints + state as part of ApplyObservations +// -> custom ResultBuilders return the set of attributes for the endpoint. +// --> + expiryTime to drop endpoint attributes after expiry. + jsonrpcSvcObservations := observations.GetJsonrpc() + endpointResults := extractEndpointResultsFromObservations(jsonrpcSvcObservations) + return s.serviceState.UpdateFromEndpointResults(endpointResults) +} diff --git a/qos/framework/jsonrpc/service_info.go b/qos/framework/jsonrpc/service_info.go new file mode 100644 index 000000000..c9b2b5227 --- /dev/null +++ b/qos/framework/jsonrpc/service_info.go @@ -0,0 +1,12 @@ +package jsonrpc + +// ServiceInfo identifies and describes a service +type ServiceInfo struct { + // Name of the QoS service: e.g. "EVM-QoS" + Name string + + // Description provides detailed information about the QoS service. + Description string +} + + diff --git a/qos/framework/jsonrpc/service_state.go b/qos/framework/jsonrpc/service_state.go new file mode 100644 index 000000000..322e3f954 --- /dev/null +++ b/qos/framework/jsonrpc/service_state.go @@ -0,0 +1,191 @@ +package jsonrpc + +import ( + "sync" + "time" + + "github.com/buildwithgrove/path/protocol" + "github.com/pokt-network/poktroll/pkg/polylog" +) + + +// TODO_IN_THIS_PR: Add a Consensus type as a StateParamValue type (and find better names): +// e.g. ReadonlyServiceState.GetConsensusValue(key string) Consensus (map[string]int) ==> for Archival checks (and maybe blockNumber) + +// TODO_IN_THIS_PR: find the best fitting file for this. +// ====> framework.go ???? +// EndpointSelector is called to select eligible endpoints based on the service state. +type EndpointSelector func(ctx *EndpointSelectionContext, []protocol.Endpoint) (protocol.EndpointAddr, error) + +// EndpointResultBuilder is called to process a result based on the current service state. +type EndpointResultBuilder func(ctx *ResultContext) *ResultData + +// StateUpdater is called to update the service state based on observed results. +type StateUpdater func(ctx *StateUpdateContext) *FullState + +// TODO_IN_THIS_PR: choose a name for this struct. +type FullState struct { + +} + + + +// serviceState provides the functionality required for selecting an endpoint from the set of available ones. +var _ protocol.EndpointSelector = &serviceState{} + + +// serviceState maintains the state for a QoS service. +// It provides methods for updating endpoint result data, applying observations, and filtering endpoints based on the service's state. +// It implements the protocol.EndpointSelector interface. +type serviceState struct { + // mu protects the state map from concurrent access + mu sync.RWMutex + + // TODO_IN_THIS_PR: add the data type required for archival checks ONLY (e.g. a map[string]json.RawMessage) + // state is a simple key-value store for the service state + // This is intentionally kept as string-to-string for simplicity + state map[string]string + + // logger for diagnostics + logger polylog.Logger + + // Custom service callbacks + resultProcessor ResultProcessor + stateUpdater StateUpdater + endpointSelector EndpointSelector + + + endpointStore *endpointStore + + // Map of method-specific result builders, specified by the custom QoS service. + // The framework will utilize a default result builder for JSONRC methods not specified by the custom QoS service. + MethodResultBuilders map[jsonrpc.Method]EndpointResultBuilder + +} + + +func (s *serviceState) buildResult(parsedResult *EndpointResult) *EndpointResult { + // Create result context for the processor + ctx := &EndpointResultContext{ + EndpointAddr: call.EndpointAddr, /// <<< remove: the Result should already have an endpointCall pointer in it. + Request: call.Request, + Response: result.parseResult.Response, + + // Process valid JSONRPC response based on method. + // The context will use a default builder if a builder matching the method is not found. + resultBuilder: s.methodResultBuilders[call.Request.Method] + } + + return ctx.buildResult() +} + + +// ProcessResult calls the custom processor with the result data +// and the current service state, returning the updated result data. +func (s *ServiceState) ProcessResult(resultData *ResultData, endpointCtx *EndpointContext) *ResultData { + stateCopy := s.GetStateCopy() + + // Create the result context + ctx := &ResultContext{ + ResultData: resultData, + EndpointCtx: endpointCtx, + state: stateCopy, + } + + // Call the custom processor with the context + return s.resultProcessor(ctx) +} + +// UpdateStateFromResults calls the custom updater with the results +// and the current service state, updating the service state. +func (s *ServiceState) UpdateStateFromResults(results []*ResultData) { + // No need to process if no custom state updater is provided. + if stateUpdater == nil { + return + } + + // No need to process if no results + if len(results) == 0 { + return + } + + stateCopy := s.GetStateCopy() + + // Create the state update context + ctx := &StateUpdateContext{ + Results: results, + currentState: stateCopy, + newState: make(map[string]string), + } + + // Initialize with a copy of the current state + ctx.CopyCurrentState() + + // Call the custom updater with the context + updatedState := s.stateUpdater(ctx) + + // Acquire write lock to update the state + s.mu.Lock() + defer s.mu.Unlock() + + // Replace the state with the updated state + s.state = updatedState +} + +// Select initializes an instance of EndpointSelectionContext to which it delegates the endpoint selection. +func (s *ServiceState) Select(request *jsonrpc.Request, endpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { + stateCopy := s.GetStateCopy() + + // Create the endpoint selection context + ctx := &EndpointSelectionContext{ + logger: s.logger, + Request: request, + Endpoints: unsanctionedEndpoints, + State: stateCopy, + + endpointStore: s.endpointStore, + customSelector: s.endpointSelector, + selected: make([]Endpoint, 0), + } + + return ctx.selectEndpoint() +} + +// GetStateValue retrieves a value from the service state by key. +// Returns the value and a boolean indicating if the key was found. +func (s *ServiceState) GetStateValue(key string) (string, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + + value, exists := s.state[key] + return value, exists +} + +// SetStateValue sets a value in the service state by key. +func (s *ServiceState) SetStateValue(key, value string) { + s.mu.Lock() + defer s.mu.Unlock() + + s.state[key] = value +} + +// DeleteStateValue removes a value from the service state by key. +func (s *ServiceState) DeleteStateValue(key string) { + s.mu.Lock() + defer s.mu.Unlock() + + delete(s.state, key) +} + +// GetStateCopy returns a copy of the entire service state. +func (s *ServiceState) GetStateCopy() map[string]string { + s.mu.RLock() + defer s.mu.RUnlock() + + stateCopy := make(map[string]string, len(s.state)) + for k, v := range s.state { + stateCopy[k] = v + } + + return stateCopy +} From 0aded170d08444ae747bf20f5c1d7b7d0e547f75 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 7 Apr 2025 17:45:56 -0400 Subject: [PATCH 02/26] introduce a requestJournal to track all data related to a request --- .../jsonrpc/context_endpoint_result.go | 54 ++++---- qos/framework/jsonrpc/context_request.go | 120 +++++------------- .../jsonrpc/context_request_builder.go | 6 +- .../jsonrpc/context_request_errors.go | 115 +++++++++++++++++ qos/framework/jsonrpc/endpoint_query.go | 8 +- qos/framework/jsonrpc/request_journal.go | 107 ++++++++++++++++ 6 files changed, 288 insertions(+), 122 deletions(-) create mode 100644 qos/framework/jsonrpc/context_request_errors.go create mode 100644 qos/framework/jsonrpc/request_journal.go diff --git a/qos/framework/jsonrpc/context_endpoint_result.go b/qos/framework/jsonrpc/context_endpoint_result.go index 082d32cd6..2a6cd5191 100644 --- a/qos/framework/jsonrpc/context_endpoint_result.go +++ b/qos/framework/jsonrpc/context_endpoint_result.go @@ -15,69 +15,69 @@ type EndpointQueryResultContext struct { // It is read only: this is not the context for updating service state. *ReadonlyServiceState - // The endpoint query for which results are being built. - endpointQuery *endpointQuery - // Custom result builders, supplied by the QoS Definition. jsonrpcMethodResultBuilders map[string]EndpointQueryResultBuilder // Response is the parsed JSON-RPC response received from the endpoint. response *jsonrpc.JsonRpcResponse - - // The result data that will be returned to the caller (requestContext) - result *EndpointQueryResult } -// buildResult uses the supplied method builder to build and return the EndpointResult. +// buildResult uses the supplied method builder to build the EndpointResult for the supplied endpointQuery. // A default builder is used if no matches were found for the request method. -func (ctx *EndpointResultContext) buildResult() *EndpointQueryResult { - if parsingFailureResult, shouldContinue := ctx.parseEndpointPayload(); !shouldContinue { - return parsingFailureResult +// Returns the endpointQuery augmented with the endpoint result. +func (ctx *EndpointResultContext) buildEndpointQueryResult(endpointQuery *endpointQuery) *endpointQuery { + parsedEndpointQuery, shouldContinue := ctx.parseEndpointPayload(endpointQuery) + if !shouldContinue { + // parsing the request failed: stop the request processing flow. + // Return a failure result for building the client's response and observations. + return parsedEndpointQuery } - builder, found := ctx.jsonrpcMethodResultBuilders[ctx.endpointQuery.request.Method] - + // Use the custom endpoint query result builder, if one is found matching the JSONRPC request's method. + builder, found := ctx.jsonrpcMethodResultBuilders[parsedEndpointQuery.request.Method] if !found { - // Use default processor for unrecognized methods + // Use default processor for methods not specified by the custom QoS service definition. builder = defaultResultBuilder } - // Process the result using service-specific processor with context - ctx.result.ResultData = builder(ctx) - return ctx.result + // Process the result using service-specific processor with the context + parsedEndpointQuery.result = builder(ctx) + + // Return the updated endpoint query. + return parsedEndpointQuery } // TODO_IN_THIS_PR: define/allow customization of sanctions for endpoint errors: e.g. malformed response. // // parseEndpointPayload parses the payload from an endpoint and handles empty responses and parse errors. // It returns the result and a boolean indicating whether processing should continue (true) or stop (false). -func (ctx *EndpointResultContext) parseEndpointPayload() (*EndpointQueryResult, bool) { +func (ctx *EndpointResultContext) parseEndpointPayload(endpointQuery *endpointQuery) (*endpointQuery, bool) { // Check for empty response - if len(call.ReceivedData) == 0 { - result := buildResultForEmptyResponse(eq) - return result, false + if len(endpointQuery.receivedData) == 0 { + endpointQuery.result = buildResultForEmptyResponse(endpointQuery) + return endpointQuery, false } // Parse JSONRPC response var jsonrpcResp jsonrpc.JsonRpcResponse - if err := json.Unmarshal(eq.receivedData, &jsonrpcResp); err != nil { - result := buildResultForErrorUnmarshalingEndpointReturnedData(call, err) - return result, false + if err := json.Unmarshal(endpointQuery.receivedData, &jsonrpcResp); err != nil { + endpointQuery.result = buildResultForErrorUnmarshalingEndpointReturnedData(endpointQuery, err) + return endpointQuery, false } // Validate the JSONRPC response if err := jsonrpcResp.Validate(eq.request.ID); err != nil { // TODO_IN_THIS_PR: define a separate method for JSONRPC response validation errors. - result := buildResultForErrorUnmarshalingEndpointReturnedData(eq, err) - return result, false + endpointQuery.result = buildResultForErrorUnmarshalingEndpointReturnedData(endpointQuery, err) + return endpointQuery, false } // Store the parsed result - ctx.parsedResponse = jsonrpcResp + endpointQuery.parsedResponse = jsonrpcResp // Return true to signal that parsing was successful. // Processing will continue to the next step. - return nil, true + return endpointQuery, true } // Success creates a success result with the given value. diff --git a/qos/framework/jsonrpc/context_request.go b/qos/framework/jsonrpc/context_request.go index 12abb9ef6..450b23924 100644 --- a/qos/framework/jsonrpc/context_request.go +++ b/qos/framework/jsonrpc/context_request.go @@ -21,13 +21,6 @@ import ( // TODO_TECHDEBT: Need to add a Validate() method here to allow the caller (e.g. gateway) // determine whether the endpoint's response was valid, and whether a retry makes sense. // - -const ( - // TODO_MVP(@adshmh): Support individual configuration of timeout for every service that uses EVM QoS. - // The default timeout when sending a request to an EVM blockchain endpoint. - defaultServiceRequestTimeoutMillisec = 10000 -) - // requestContext provides the support required by the gateway // package for handling service requests. var _ gateway.RequestQoSContext = &requestContext{} @@ -55,54 +48,43 @@ const ( type requestQoSContext struct { Logger polylog.Logger - // Service Identification fields - ServiceID ServiceID - - // Request is the JSONRPC request that was sent - Request *jsonrpc.Request - - // Error response to return if validation failed - JSONRPCErrorResponse *jsonrpc.Response + // Tracks all data related to the current request context: + // - client's request + // - endpoint query result(s) + journal *requestJournal // Read-only form of the service state. // Used to instantiate EndpointQueryResultContext and EndpointSelectionContext. serviceState *ServiceStateReadOnly + // TODO_IN_THIS_PR: move endpoint_store under service_state. + // // Used to instantiate the EndpointSelectionContext, to select an endpoint for serving the client's request. endpointStore *endpointStore // Used to instantiate the EndpointSelectionContext customEndpointSelector EndpointSelector - // Used to instantiate the EndpointResultContext, to build an endpoint result from an endpoint query. - jsonrpcMethodResultBuilders map[string]EndpointResultBuilder + // ======> instantiate in requestProcessingContextBuilder. + // instantiate a result context to process the endpointQuery. + resultCtx := &EndpointQueryResultContext{ + ReadonlyServiceState: rc.readonlyServiceState, + // Used to instantiate the EndpointResultContext, to build an endpoint result from an endpoint query. + jsonrpcMethodResultBuilders map[string]EndpointResultBuilder + jsonrpcMethodResultBuilders: rc.jsonrpcMethodResultBuilders, + } + // ======> instantiate in requestProcessingContextBuilder. + return &EndpointSelectionContext{ + *ReadonlyServiceState: rc.serviceState, + endpointStore: rc.endpointStore, + customSelector: rc.customEndpointSelector, + } - // Tracks results processed in the current request context. - processedResults []*EndpointQueryResult } // TODO_MVP(@adshmh): Ensure the JSONRPC request struct can handle all valid service requests. func (rc requestQoSContext) GetServicePayload() protocol.Payload { - reqBz, err := json.Marshal(*rc.Request) - if err != nil { - // TODO_MVP(@adshmh): find a way to guarantee this never happens, - // e.g. by storing the serialized form of the JSONRPC request - // at the time of creating the request context. - return protocol.Payload{} - } - - return protocol.Payload{ - Data: string(reqBz), - // Method is alway POST for EVM-based blockchains. - Method: http.MethodPost, - - // Path field is not used for JSONRPC services. - - // TODO_IMPROVE: adjust the timeout based on the request method: - // An endpoint may need more time to process certain requests, - // as indicated by the request's method and/or parameters. - TimeoutMillisec: defaultServiceRequestTimeoutMillisec, - } + return rc.journal.getServicePayload() } // UpdateWithResponse is NOT safe for concurrent use @@ -112,25 +94,14 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // indicating the validity of the request when calling on QoS instance's ParseHTTPRequest // // Instantiate an endpointQuery to capture the interaction with the service endpoint. - endpointQuery := &endpointQuery{ - serviceID: rc.ServiceID, - request: rc.Request, - endpointAddr: endpointAddr, - receivedData: receivedData, - } - // instantiate a result context to process the endpointQuery. - resultCtx := &EndpointQueryResultContext{ - ReadonlyServiceState: rc.readonlyServiceState, - jsonrpcMethodResultBuilders: rc.jsonrpcMethodResultBuilders, - endpointQuery: endpointQuery, - } + endpointQuery := rc.journal.buildEndpointQuery(endpointAddr, receivedData) // Process the endpointQuery using the correct context. - endpointQueryResult := resultCtx.buildEndpointResult() + processedEndpointQuery := rc.resultCtx.buildEndpointQueryResult(endpointQuery) - // Track the processed result - p.processedResults = append(p.processedResults, endpointQueryResult) + // Track the processed result in the request journal + rc.journal.reportProcessedEndpointQuery(processedEndpointQuery) } // TODO_TECHDEBT: support batch JSONRPC requests by breaking them into single JSONRPC requests and tracking endpoints' response(s) to each. @@ -141,51 +112,20 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // GetHTTPResponse builds the HTTP response that should be returned for a JSONRPC service request. // Implements the gateway.RequestQoSContext interface. func (rc requestQoSContext) GetHTTPResponse() gateway.HTTPResponse { - if rc.JSONRPCErrorResponse != nil { - return buildHTTPResponse(rc.Logger, rc.JSONRPCErrorResponse) - } - - return buildHTTPResponse(rc.Logger, rc.getJSONRPCResponse()) + return rc.journal.getHTTPResponse() } -// GetObservations returns QoS observations from all processed results. -// GetObservations returns all endpoint observations from the request context. +// GetObservations uses the request's journal to build and return all observations. // Implements gateway.RequestQoSContext interface. func (rc requestContext) GetObservations() qosobservations.Observations { - /* - return qosobservations.Observations { - RequestObservations: rc. resut???? .GetObservation(), - EndpointObservations: rc.EndpointCallsProcessor.GetObservations(), - // TODO_IN_THIS_PR: Implement this method in observations.go. - // Return basic observations for now - ServiceId: p.ServiceID, - ServiceDescription: p.ServiceDescription, - RequestObservation: p.RequestObservation, - } - */ + // Use the request journal to generate the observations. + return rc.journal.getObservations() } // Build and returns an instance EndpointSelectionContext to perform endpoint selection for the client request. // Implements the gateway.RequestQoSContext func (rc *requestQoSContext) GetEndpointSelector() protocol.EndpointSelector { - return &EndpointSelectionContext{ - *ReadonlyServiceState: rc.serviceState, - Request: rc.Request, - endpointStore: rc.endpointStore, - customSelector: rc.customEndpointSelector, - } -} -// TODO_FUTURE(@adshmh): A retry mechanism would require support from this struct to determine if the most recent endpoint call has been successful. -// -// getJSONRPCResponse simply returns the result associated with the most recently reported EndpointCall. -func (rc requestContext) getJSONRPCResponse() *jsonrpc.Response { - // Check if we received any endpoint results - if len(rc.processedResults) == 0 { - // If no results were processed, handle it as a protocol error - return buildResultForNoResponse(rc.Request) - } + return rc.endpointSelectionContext.buildSelectorForRequest(rc.journal) - // Return the latest result. - return rc.processedResults[len(rc.processedResults)-1] } diff --git a/qos/framework/jsonrpc/context_request_builder.go b/qos/framework/jsonrpc/context_request_builder.go index 1260afcb0..2510633a2 100644 --- a/qos/framework/jsonrpc/context_request_builder.go +++ b/qos/framework/jsonrpc/context_request_builder.go @@ -30,9 +30,7 @@ const maxErrMessageLen = 1000 // requestBuilder handles the construction of requestQoSContext objects type requestBuilder struct { Logger polylog.Logger - ServiceID ServiceID - EndpointCallProcessor endpointCallProcessor - EndpointSelector endpointSelector + ServiceName ServiceName context *requestQoSContext } @@ -56,6 +54,8 @@ func (rb *requestBuilder) ParseHTTPRequest(httpReq *http.Request) *requestBuilde // Handle read error (internal server error) rb.Logger.Error().Err(err).Msg("Failed to read request body") + rb.context = newRequestErrorContextInternalError(err) + return rb // Create error response for read failure errResp := newErrResponseInternalReadError(err) diff --git a/qos/framework/jsonrpc/context_request_errors.go b/qos/framework/jsonrpc/context_request_errors.go new file mode 100644 index 000000000..5ae7281a9 --- /dev/null +++ b/qos/framework/jsonrpc/context_request_errors.go @@ -0,0 +1,115 @@ + +//// THIS MAY NOT BE NEEDED with the ServiceRequestJournal +/// +package jsonrpc + +import ( + "encoding/json" + "errors" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/gateway" + qosobservations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +var ( + // Error recording that endpoint selection was attempted but failed due to an invalid request + errInvalidSelectorUsage = errors.New("endpoint selection attempted on failed request") +) + +// errorContext provides the support required by the gateway package for handling service requests. +var _ gateway.RequestQoSContext = &errorContext{} + +func newRequestErrorContextInternalError(logger polylog.Logger, err error) *errorContext { + return &errorContext{ + logger: logger, + + // Create error response for read failure + errResp := newErrResponseInternalReadError(err) + + requestCtx.JSONRPCErrorResponse = &errResp + rb.context = requestCtx + return rb +} + + +// errorContext terminates EVM request processing on errors (internal failures or invalid requests). +// Provides: +// 1. Detailed error response to the user +// 2. Observation: feed into Metrics and data pipeline. +// +// Implements gateway.RequestQoSContext +type errorContext struct { + logger polylog.Logger + + // The observation to return, to be processed by the metrics and data pipeline components. + observations *qosobservations.Observations + + // The response to be returned to the user. + response jsonrpc.Response +} + +// GetHTTPResponse formats the stored JSONRPC error as an HTTP response +// Implements the gateway.RequestQoSContext interface. +func (ec *errorContext) GetHTTPResponse() gateway.HTTPResponse { + return buildHTTPResponse(ec.Logger, ec.errorResponse) +} + +// GetObservation returns the QoS observation set for the error context. +// Implements the gateway.RequestQoSContext interface. +func (ec *errorContext) GetObservations() qosobservations.Observations { + return qosobservations.Observations{ + ServiceObservations: ec.evmObservations, + } +} + +// GetServicePayload should never be called. +// It logs a warning and returns nil. +// Implements the gateway.RequestQoSContext interface. +func (ec *errorContext) GetServicePayload() protocol.Payload { + ec.logger.Warn().Msg("Invalid usage: errorContext.GetServicePayload() should never be called.") + return protocol.Payload{} +} + +// UpdateWithResponse should never be called. +// Only logs a warning. +// Implements the gateway.RequestQoSContext interface. +func (ec *errorContext) UpdateWithResponse(endpointAddr protocol.EndpointAddr, endpointSerializedResponse []byte) { + ec.logger.With( + "endpoint_addr", endpointAddr, + "endpoint_response_len", len(endpointSerializedResponse), + ).Warn().Msg("Invalid usage: errorContext.UpdateWithResponse() should never be called.") +} + +// UpdateWithResponse should never be called. +// It logs a warning and returns a failing selector that logs a warning on all selection attempts. +// Implements the gateway.RequestQoSContext interface. +func (ec *errorContext) GetEndpointSelector() protocol.EndpointSelector { + ec.logger.Warn().Msg("Invalid usage: errorContext.GetEndpointSelector() should never be called.") + + return errorTrackingSelector{ + logger: ec.logger, + } +} + +// errorTrackingSelector prevents panics in request handling goroutines by: +// - Intentionally failing all endpoint selection attempts +// - Logging diagnostic information when endpoint selection is incorrectly attempted on failed requests +// Acts as a failsafe mechanism for request handling. +type errorTrackingSelector struct { + logger polylog.Logger +} + +// Select method of an errorTrackingSelector should never be called. +// It logs a warning and returns an invalid usage error. +// Implements the protocol.EndpointSelector interface. +func (ets errorTrackingSelector) Select(endpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { + ets.logger.With( + "num_endpoints", len(endpoints), + ).Warn().Msg("Invalid usage: errorTrackingSelector.Select() should never be called.") + + return protocol.EndpointAddr(""), errInvalidSelectorUsage +} diff --git a/qos/framework/jsonrpc/endpoint_query.go b/qos/framework/jsonrpc/endpoint_query.go index 2fd57db29..6a6a6cb76 100644 --- a/qos/framework/jsonrpc/endpoint_query.go +++ b/qos/framework/jsonrpc/endpoint_query.go @@ -4,8 +4,6 @@ package jsonrpc // Instantiated by: RequestQoSContext. // Used in EndpointQueryResultContext. type endpointQuery struct { - serviceName ServiceName - // request is the JSONRPC request that was sent request *jsonrpc.Request @@ -14,4 +12,10 @@ type endpointQuery struct { // receivedData is the raw response data received from the endpoint (may be nil) receivedData []byte + + // JSONRPC response, parsed from the data received from the endpoint. + parsedResponse *jsonrpc.Response + + // the result of processing the endpoint query. + result *EndpointQueryResult } diff --git a/qos/framework/jsonrpc/request_journal.go b/qos/framework/jsonrpc/request_journal.go new file mode 100644 index 000000000..84d1fba39 --- /dev/null +++ b/qos/framework/jsonrpc/request_journal.go @@ -0,0 +1,107 @@ +package jsonrpc + +const ( + // TODO_MVP(@adshmh): Support individual configuration of timeout for every service that uses EVM QoS. + // The default timeout when sending a request to an EVM blockchain endpoint. + defaultServiceRequestTimeoutMillisec = 10000 +) + +// requestJournal holds the data for a complete JSONRPC request lifecycle. +type requestJournal struct { + logger polylog.Logger + + // Service identification + serviceName string + + // The client's JSONRPC request + request *jsonrpc.Request + + // Error response to return if a request parsing error occurred: + // - error reading HTTP request's body. + // - error parsing the request's payload into a jsonrpc.Request struct. + errorResponse *jsonrpc.Response + + // All endpoint interactions that occurred during processing. + endpointQueries []*endpointQuery +} + +func (rj *requestJournal) buildEndpointQuery(endpointAddr protocol.EndpointAddr, receivedData []byte) *endpointQuery { + return &endpointQuery{ + request: rj.request, + endpointAddr: endpointAddr, + receivedData: receivedData, + } +} + +func (rj *requestJournal) reportProcessedEndpointQuery(processedEndpointQuery endpointQuery) { + rj.endpointQueries = append(rj.endpointQueries, processedEndpointQuery) +} + +func (rj *requestJournal) getServicePayload() protocol.Payload { + // TODO_IN_THIS_PR: update this code + reqBz, err := json.Marshal(*rc.Request) + if err != nil { + // TODO_MVP(@adshmh): find a way to guarantee this never happens, + // e.g. by storing the serialized form of the JSONRPC request + // at the time of creating the request context. + return protocol.Payload{} + } + + return protocol.Payload{ + Data: string(reqBz), + // Method is alway POST for EVM-based blockchains. + Method: http.MethodPost, + + // Path field is not used for JSONRPC services. + + // TODO_IMPROVE: adjust the timeout based on the request method: + // An endpoint may need more time to process certain requests, + // as indicated by the request's method and/or parameters. + TimeoutMillisec: defaultServiceRequestTimeoutMillisec, + } +} + +func (rj *requestJournal) getHTTPResponse() gateway.HTTPResponse { + if rj.JSONRPCErrorResponse != nil { + return buildHTTPResponse(rj.Logger, rj.JSONRPCErrorResponse) + } + + return buildHTTPResponse(rj.Logger, rj.getJSONRPCResponse()) +} + +func (rj *requestJournal) getObservations() qosobservations.Observations { + /* + // Service identification + ServiceName: + ServiceDescription: + + // Observation of the client request + RequestObservation *RequestObservation + // Observations from endpoint(s) + EndpointObservations []*EndpointObservation + + return qosobservations.Observations { + RequestObservations: rc. resut???? .GetObservation(), + EndpointObservations: rc.EndpointCallsProcessor.GetObservations(), + // TODO_IN_THIS_PR: Implement this method in observations.go. + // Return basic observations for now + ServiceId: p.ServiceID, + ServiceDescription: p.ServiceDescription, + RequestObservation: p.RequestObservation, + } + */ +} + +// TODO_FUTURE(@adshmh): A retry mechanism would require support from this struct to determine if the most recent endpoint query has been successful. +// +// getJSONRPCResponse simply returns the result associated with the most recently reported endpointQuery. +func (rj *requestJournal) getJSONRPCResponse() *jsonrpc.Response { + // Check if we received any endpoint results + if len(rc.processedResults) == 0 { + // If no results were processed, handle it as a protocol error + return buildResultForNoResponse(rc.Request) + } + + // Return the latest result. + return rc.processedResults[len(rc.processedResults)-1] +} From 416944763a5247183204c7761b023514a568db76 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 09:50:36 -0400 Subject: [PATCH 03/26] Refactor: merge endpointAttribute and endpointQueryResult --- qos/framework/jsonrpc/endpoint.go | 61 +++++++++++-------- qos/framework/jsonrpc/endpoint_attribute.go | 45 -------------- .../jsonrpc/endpoint_attribute_error.go | 16 ----- .../jsonrpc/endpoint_query_result.go | 47 +++++++++++--- 4 files changed, 77 insertions(+), 92 deletions(-) delete mode 100644 qos/framework/jsonrpc/endpoint_attribute.go delete mode 100644 qos/framework/jsonrpc/endpoint_attribute_error.go diff --git a/qos/framework/jsonrpc/endpoint.go b/qos/framework/jsonrpc/endpoint.go index 9eb53a84c..accd3c7f3 100644 --- a/qos/framework/jsonrpc/endpoint.go +++ b/qos/framework/jsonrpc/endpoint.go @@ -1,50 +1,63 @@ package jsonrpc // TODO_TECHDEBT(@adshmh): Persist this state (which may include sanctions) across restarts to maintain endpoint exclusions. -// TODO_MVP(@adshmh): add an ExpiryTime field and the required support for removing expired items. +// TODO_MVP(@adshmh): add support for removing expired query results. // // Endpoint represents a service endpoint with its associated attributes. // - Read-only for client code // - All attributes are set internally by the framework type Endpoint struct { - attributes map[string]EndpointAttribute + // queryResults maps keys to query results for this endpoint. + // Keys are defined by the QoS service implementation (typically JSONRPC method names). + // Examples: + // - "eth_blockNumber": &EndpointQueryResult{IntValues: {"blockNumber": 0x1234}} + // - "eth_getBalance": &EndpointQueryResult{ + // StringValues: {"address": "0x8D97..."}, + // IntValues: {"balance": 133456789}, + // } + queryResults map[string]*EndpointQueryResult } -// GetStringAttribute retrieves an attribute's string value, using its key. -func (e *Endpoint) GetStringAttribute(attrName string) (string, bool) { - attr, exists := e.attributes[attrName] - if !exists { +// GetQueryResultStringValue retrieves a string attribute of a result by key. +// DEV_NOTE: This design pattern: +// - Prevents map leaking and unauthorized modifications through pointers +// - Avoids expensive struct cloning +// - Maintains proper encapsulation +func (e *Endpoint) GetQueryResultStringValue(resultKey, valueKey string) (string, bool) { + result, exists := e.queryResults[resultKey] + if !exists || result == nil { return "", false } - return attr.GetStringValue() + return result.StringValues[valueKey] } -// GetIntAttribute retrieves an attribute's integer value, using its key. -func (e *Endpoint) GetIntAttribute(attrName string) (int, bool) { - attr, exists := e.attributes[attrName] - if !exists { - return 0, false +// GetQueryResultStringValue retrieves an integer attribute of a result by key. +// See the comment on GetQueryResultStringValue for notes on this pattern. +func (e *Endpoint) GetQueryResultIntValue(resultKey, valueKey string) (int, bool) { + result, exists := e.queryResults[resultKey] + if !exists || result == nil { + return "", false } - return attr.GetIntValue() + return result.IntValues[valueKey] +} + +// TODO_IN_THIS_PR: implement. +func (e *Endpoint) HasActiveSanction() (Sanction, bool) { + } // ApplyQueryResult updates the endpoint's attributes with attributes from the query result. // It merges the EndpointAttributes from the query result into the endpoint's attributes map. -func (e *Endpoint) ApplyQueryResult(queryResult *EndpointQueryResult) { - // Initialize attributes map if it doesn't exist - if e.attributes == nil { - e.attributes = make(map[string]EndpointAttribute) +func (e *Endpoint) ApplyQueryResults(result map[string]EndpointQueryResult) { + // Initialize the results map if nil. + if e.queryResults == nil { + e.queryResults = make(map[string]*EndpointQueryResult) } // Add or update attributes from the query result - for key, attr := range queryResult.EndpointAttributes { - e.attributes[key] = attr + for key, result := range results { + e.queryResult[key] = result } } - -// TODO_IN_THIS_PR: implement. -func (e *Endpoint) HasActiveSanction() (Sanction, bool) { - -} diff --git a/qos/framework/jsonrpc/endpoint_attribute.go b/qos/framework/jsonrpc/endpoint_attribute.go deleted file mode 100644 index 740dcf87a..000000000 --- a/qos/framework/jsonrpc/endpoint_attribute.go +++ /dev/null @@ -1,45 +0,0 @@ -package jsonrpc - -import ( - "errors" - "time" -) - -// TODO_IMPROVE(@adshmh): Enhance EndpointAttribute to support data types commonly used for endpoint attributes. -// TODO_MVP(@adshmh): Add and support an ExpiryTime field. -// -// EndpointAttribute represents a single data item extracted from an endpoint response. -// - Stores either a string or integer value (not both) -// - Contains error/sanction information if associated with a failure -type EndpointAttribute struct { - // Separate typed fields for different attribute types - stringValue *string - intValue *int - - // Error information if this attribute is associated with a failure. - // To be set and handled by framework only. - // QoS services should use helper functions to set it, e.g. from the EndpointAttributeContext struct. - err *EndpointAttributeError -} - -// GetStringValue returns the attribute value as a string -// Returns false if the attribute does not have a string value set: -// It is either an integer attribute, or has an error value set. -func (a EndpointAttribute) GetStringValue() (string, bool) { - if a.stringValue == nil { - return "", false - } - - return *a.stringValue, true -} - -// GetIntValue returns the attribute value as an int -// Returns false if the attribute does not have an integer value set: -// It is either an string attribute, or has an error value set. -func (a EndpointAttribute) GetIntValue() (int, bool) { - if a.intValue == nil { - return 0, false - } - - return *a.intValue, true -} diff --git a/qos/framework/jsonrpc/endpoint_attribute_error.go b/qos/framework/jsonrpc/endpoint_attribute_error.go deleted file mode 100644 index 956f06f23..000000000 --- a/qos/framework/jsonrpc/endpoint_attribute_error.go +++ /dev/null @@ -1,16 +0,0 @@ -package jsonrpc - -// EndpointAttributeError contains error details for endpoint queries. -// An EndpointError is always associated with an Endpoint Attribute struct. -type EndpointAttributeError struct { - // Description is set by the custom service implementation - Description string - - // RecommendedSanction is set by the custom service implementation - // It is under ResultError to clarify the reason a sanction was recommended. - RecommendedSanction *Sanction - - // Internal fields used by the framework - kind endpointErrorKind - rawPayload []byte -} diff --git a/qos/framework/jsonrpc/endpoint_query_result.go b/qos/framework/jsonrpc/endpoint_query_result.go index b7fb00d26..3fd10be09 100644 --- a/qos/framework/jsonrpc/endpoint_query_result.go +++ b/qos/framework/jsonrpc/endpoint_query_result.go @@ -1,19 +1,52 @@ package jsonrpc -// EndpointQueryResult captures information extracted from a service endpoint query. +import ( + "errors" + "time" +) + +// TODO_IMPROVE(@adshmh): Enhance EndpointQueryResult to support data types commonly stored for endpoints. +// +// EndpointQueryResult captures data extracted from an endpoint query. +// - Stores one or more string/integer values. +// - Contains error/sanction information on endpoint error. type EndpointQueryResult struct { // The endpointQuery from which this result was built. // It can be used, e.g. to retrieve the JSONRPC request and its method. *endpointQuery - // The set of endpoint attributes, extracted from the endpoint query and the endpoint's parsed JSONRPC response. + // The set of values/attributes extracted from the endpoint query and the endpoint's parsed JSONRPC response. // e.g. for a Solana `getEpochInfo` request, the custom service could derive two endpoint attributes as follows: - // - "BlockHeight": "0x1234" - // - "Epoch": "5" + // - "BlockHeight": 0x1234 + // - "Epoch": 5 + StringValues map[string]string + IntValues map[string]int + + // Captures the queried endpoint's error. + // Only set if the query result indicates an endpoint error. // It could also include sanctions: - // e.g. for a returned value of "invalid" to an EVM `eth_blockNumber` request, the custom service could set: - // - "eth_blockNumber": EndpointAtribute{error: EndpointError:{RecommendedSanction: {Duration: 5 * time.Minute}}} - EndpointAttributes map[string]EndpointAttribute + // e.g. for an invalid value returned for an EVM `eth_blockNumber` request, the custom service could set: + // Error: + // - Description: "invalid response to eth_blockNumber" + // - RecommendedSanction: {Duration: 5 * time.Minute} + Error *EndpointError + + // The time at which the query result is expired. + // Expired results will be ignored, including in: + // - endpoint selection, e.g. sanctions. + // - state update: e.g. archival state of the QoS service. + ExpiryTime time.Time // TODO_FUTURE(@adshmh): add a JSONRPCErrorResponse to allow a result builder to supply its custom JSONRPC response. } + +// EndpointError contains error details for endpoint queries. +// An EndpointError is always associated with an Endpoint Attribute struct. +type EndpointError struct { + // Description is set by the custom service implementation + Description string + + // RecommendedSanction is set by the custom service implementation + // It is under ResultError to clarify the reason a sanction was recommended. + RecommendedSanction *Sanction +} From 6f10e48359298b1226b5e2c4baccaf08834854a8 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 11:47:05 -0400 Subject: [PATCH 04/26] Make QoS struct the context builder --- qos/framework/jsonrpc/endpoint_query.go | 2 +- qos/framework/jsonrpc/framework.go | 12 +++-- qos/framework/jsonrpc/qos.go | 63 +++++++++++++++++-------- qos/framework/jsonrpc/service_info.go | 12 ----- qos/framework/jsonrpc/service_state.go | 1 + 5 files changed, 53 insertions(+), 37 deletions(-) delete mode 100644 qos/framework/jsonrpc/service_info.go diff --git a/qos/framework/jsonrpc/endpoint_query.go b/qos/framework/jsonrpc/endpoint_query.go index 6a6a6cb76..a1872d40b 100644 --- a/qos/framework/jsonrpc/endpoint_query.go +++ b/qos/framework/jsonrpc/endpoint_query.go @@ -4,7 +4,7 @@ package jsonrpc // Instantiated by: RequestQoSContext. // Used in EndpointQueryResultContext. type endpointQuery struct { - // request is the JSONRPC request that was sent + // request is the JSONRPC request that was sent. request *jsonrpc.Request // endpointAddr identifies the endpoint diff --git a/qos/framework/jsonrpc/framework.go b/qos/framework/jsonrpc/framework.go index 7e7da8914..34a999970 100644 --- a/qos/framework/jsonrpc/framework.go +++ b/qos/framework/jsonrpc/framework.go @@ -22,7 +22,7 @@ type QoSDefinition struct { ServiceInfo ServiceInfo // ResultBuilders maps JSONRPC methods to custom result processing logic - ResultBuilders map[string]EndpointResultBuilder + ResultBuilders map[string]EndpointQueryResultBuilder // StateUpdater defines how endpoint results affect service state StateUpdater StateUpdater @@ -30,8 +30,10 @@ type QoSDefinition struct { // EndpointSelector defines custom endpoint selection logic EndpointSelector EndpointSelector - // Optional: do request validation on a parsed JSONRPC request. - RequestValidator RequestValidator + // TODO_MVP(@adshmh): Enable custom service QoS implementations to provide a list of allowed methods which the requestValidator needs to enforce: + // - Uncomment the following line. + // - Use the supplied request validator in the framework. + // RequestValidator RequestValidator // TODO_FUTURE(@adshmh): Add additional configuration options: // - InitialState: Starting values for service state @@ -42,8 +44,8 @@ type QoSDefinition struct { // - MetricsCollection: Settings for performance metrics } -// EndpointResultBuilder processes a response and extracts relevant data -type EndpointResultBuilder func(ctx *EndpointResultContext) *ResultData +// EndpointQueryResultBuilder processes a response and extracts the relevant result. +type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult // StateUpdater updates service state based on endpoint results type StateUpdater func(ctx *StateUpdateContext) map[string]string diff --git a/qos/framework/jsonrpc/qos.go b/qos/framework/jsonrpc/qos.go index 7c8efae88..05b1c0c38 100644 --- a/qos/framework/jsonrpc/qos.go +++ b/qos/framework/jsonrpc/qos.go @@ -12,27 +12,19 @@ import ( "github.com/buildwithgrove/path/qos/jsonrpc" ) +// TODO_TECHDEBT(@adshmh): Simplify the qos package by refactoring gateway.QoSContextBuilder. +// Proposed change: Create a new ServiceRequest type containing raw payload data ([]byte) +// Benefits: Decouples the qos package from HTTP-specific error handling. + // QoS represents a service that processes JSONRPC requests and applies QoS policies based on data returned by endpoints. type QoS struct { // Logger for diagnostics logger polylog.Logger - // Service identification - serviceID ServiceID - - endpointCallProcessor endpointCallProcessor - endpointSelector endpointSelector - serviceState serviceState - - // TODO_MVP(@adshmh): Enable custom service QoS implementations to provide - // a list of allowed methods which the requestValidator needs to enforce. -} + serviceState *serviceState -// Stored all the fields required for identification of the service. -// Used when interpreting observations. -type ServiceID struct { - ID string - Description string + // The definitoin of QoS behavior, supplied by the custom QoS service. + qosDefinition QoSDefinition } // ParseHTTPRequest handles parsing an HTTP request and validating its content @@ -40,10 +32,12 @@ type ServiceID struct { func (s *QoSService) ParseHTTPRequest( ctx context.Context, httpReq *http.Request, -) (*requestQoSContext, bool) { - builder := requestContextBuilder{ - Logger: s.Logger, - ServiceID: s.ServiceID, +) (*requestContext, bool) { + requestCtx := requestContext { + logger: s.logger, + journal: &requestJournal{ + + }, } // Parse the HTTP request @@ -70,3 +64,34 @@ func (s *QoSService) ApplyObservations(observations *qosobservations.Observation endpointResults := extractEndpointResultsFromObservations(jsonrpcSvcObservations) return s.serviceState.UpdateFromEndpointResults(endpointResults) } + +// buildEndpointQueryResultContext creates a context for processing endpoint queries +// The context provides: +// - Read-only access to current service state +// - Mapping of JSONRPC methods to their corresponding result builders. +func (q *QoS) buildEndpointQueryResultContext() *EndpointQueryResultContext { + // instantiate a result context to process an endpointQuery. + return &EndpointQueryResultContext{ + // Service State (read-only) + // Allows the custom QoS service to base the query results on current state if needed. + ReadonlyServiceState: q.serviceState, + + // Map of JSONRPC request method to the corresponding query result builders. + jsonrpcMethodResultBuilders: q.qosDefinition.ResultBuilders, + } +} + +// buildEndpointSelectionContext creates a context for endpoint validation and selection +// The context provides: +// - Read-only access to current service state and endpoint store +// - Custom endpoint selector logic from QoS service definition +func (q *QoS) buildEndpointSelectionContext() *EndpointSelectionContext { + return &EndpointSelectionContext{ + // Service State (read-only) + // Allows the custom QoS service to base the validation/selection of endpoints on current state. + // Includes the endpoint store in read-only mode. + *ReadonlyServiceState: rc.serviceState, + // The endpoint selector logic defined by the custom QoS service defintion. + customSelector: q.qosDefinition.EndpointSelector, + } +} diff --git a/qos/framework/jsonrpc/service_info.go b/qos/framework/jsonrpc/service_info.go deleted file mode 100644 index c9b2b5227..000000000 --- a/qos/framework/jsonrpc/service_info.go +++ /dev/null @@ -1,12 +0,0 @@ -package jsonrpc - -// ServiceInfo identifies and describes a service -type ServiceInfo struct { - // Name of the QoS service: e.g. "EVM-QoS" - Name string - - // Description provides detailed information about the QoS service. - Description string -} - - diff --git a/qos/framework/jsonrpc/service_state.go b/qos/framework/jsonrpc/service_state.go index 322e3f954..0c25f4b5d 100644 --- a/qos/framework/jsonrpc/service_state.go +++ b/qos/framework/jsonrpc/service_state.go @@ -8,6 +8,7 @@ import ( "github.com/pokt-network/poktroll/pkg/polylog" ) +// TODO_IN_THIS_PR: move endpoint_store under service_state. // TODO_IN_THIS_PR: Add a Consensus type as a StateParamValue type (and find better names): // e.g. ReadonlyServiceState.GetConsensusValue(key string) Consensus (map[string]int) ==> for Archival checks (and maybe blockNumber) From cf94a950381488c48eaf2f72469d375fe0412ce3 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 14:01:38 -0400 Subject: [PATCH 05/26] Introduce request struct --- .../jsonrpc/context_endpoint_result.go | 2 + qos/framework/jsonrpc/context_request.go | 41 ++--- .../jsonrpc/context_request_builder.go | 150 ------------------ qos/framework/jsonrpc/observations.go | 40 +++-- .../jsonrpc/observations_endpoints.go | 8 + qos/framework/jsonrpc/observations_request.go | 23 +++ qos/framework/jsonrpc/qos.go | 28 ++-- qos/framework/jsonrpc/request.go | 117 ++++++++++++++ qos/framework/jsonrpc/request_errors.go | 61 +++++++ qos/framework/jsonrpc/request_journal.go | 8 +- 10 files changed, 263 insertions(+), 215 deletions(-) delete mode 100644 qos/framework/jsonrpc/context_request_builder.go create mode 100644 qos/framework/jsonrpc/observations_endpoints.go create mode 100644 qos/framework/jsonrpc/observations_request.go create mode 100644 qos/framework/jsonrpc/request.go create mode 100644 qos/framework/jsonrpc/request_errors.go diff --git a/qos/framework/jsonrpc/context_endpoint_result.go b/qos/framework/jsonrpc/context_endpoint_result.go index 2a6cd5191..10eb1488a 100644 --- a/qos/framework/jsonrpc/context_endpoint_result.go +++ b/qos/framework/jsonrpc/context_endpoint_result.go @@ -6,6 +6,8 @@ package jsonrpc // For example if the result field cannot be parsed into a number in a response to an `eth_blockNumber` request. type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult +// TODO_IN_THIS_PR: add hydratedLoggers. +// // TODO_FUTURE(@adshmh): Support overriding the JSONRPC response through the EndpointQueryResultContext, IFF there is a use case for it. // // EndpointQueryResultContext provides context for processing a result with the service state. diff --git a/qos/framework/jsonrpc/context_request.go b/qos/framework/jsonrpc/context_request.go index 450b23924..50fef6315 100644 --- a/qos/framework/jsonrpc/context_request.go +++ b/qos/framework/jsonrpc/context_request.go @@ -53,33 +53,10 @@ type requestQoSContext struct { // - endpoint query result(s) journal *requestJournal - // Read-only form of the service state. - // Used to instantiate EndpointQueryResultContext and EndpointSelectionContext. - serviceState *ServiceStateReadOnly - - // TODO_IN_THIS_PR: move endpoint_store under service_state. - // - // Used to instantiate the EndpointSelectionContext, to select an endpoint for serving the client's request. - endpointStore *endpointStore - - // Used to instantiate the EndpointSelectionContext - customEndpointSelector EndpointSelector - - // ======> instantiate in requestProcessingContextBuilder. - // instantiate a result context to process the endpointQuery. - resultCtx := &EndpointQueryResultContext{ - ReadonlyServiceState: rc.readonlyServiceState, - // Used to instantiate the EndpointResultContext, to build an endpoint result from an endpoint query. - jsonrpcMethodResultBuilders map[string]EndpointResultBuilder - jsonrpcMethodResultBuilders: rc.jsonrpcMethodResultBuilders, - } - // ======> instantiate in requestProcessingContextBuilder. - return &EndpointSelectionContext{ - *ReadonlyServiceState: rc.serviceState, - endpointStore: rc.endpointStore, - customSelector: rc.customEndpointSelector, - } - + // QoS service will be used to build the required contexts: + // - EndpointSelectionContext + // - EndpointQueryResultContext + contextBuilder *QoS } // TODO_MVP(@adshmh): Ensure the JSONRPC request struct can handle all valid service requests. @@ -94,11 +71,12 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // indicating the validity of the request when calling on QoS instance's ParseHTTPRequest // // Instantiate an endpointQuery to capture the interaction with the service endpoint. - endpointQuery := rc.journal.buildEndpointQuery(endpointAddr, receivedData) + resultCtx := rc.contextBuilder.buildEndpointQueryResultContext() + // Process the endpointQuery using the correct context. - processedEndpointQuery := rc.resultCtx.buildEndpointQueryResult(endpointQuery) + processedEndpointQuery := resultCtx.buildEndpointQueryResult(endpointQuery) // Track the processed result in the request journal rc.journal.reportProcessedEndpointQuery(processedEndpointQuery) @@ -125,7 +103,8 @@ func (rc requestContext) GetObservations() qosobservations.Observations { // Build and returns an instance EndpointSelectionContext to perform endpoint selection for the client request. // Implements the gateway.RequestQoSContext func (rc *requestQoSContext) GetEndpointSelector() protocol.EndpointSelector { + selectorCtx := rc.contextBuilder.buildEndpointSelectionContext() + return selectorCtx.buildSelectorForRequest(rc.journal) +} - return rc.endpointSelectionContext.buildSelectorForRequest(rc.journal) -} diff --git a/qos/framework/jsonrpc/context_request_builder.go b/qos/framework/jsonrpc/context_request_builder.go deleted file mode 100644 index 2510633a2..000000000 --- a/qos/framework/jsonrpc/context_request_builder.go +++ /dev/null @@ -1,150 +0,0 @@ -package framework - -import ( - "context" - "encoding/json" - "io" - "net/http" - - "github.com/pokt-network/poktroll/pkg/polylog" - - "github.com/buildwithgrove/path/qos/jsonrpc" -) - -// TODO_IN_THIS_PR: ensure the observations will contain: -// - HTTP Status code: e.g. httpStatusRequestValidationFailureUnmarshalFailure, -// - Validation error: e.g. qosobservations.EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE, -// - Error details. - - -// TODO_TECHDEBT(@adshmh): Simplify the qos package by refactoring gateway.QoSContextBuilder. -// Proposed change: Create a new ServiceRequest type containing raw payload data ([]byte) -// Benefits: Decouples the qos package from HTTP-specific error handling. - -// maximum length of the error message stored in request validation failure observations and logs. -// This is used to prevent overly verbose error messages from being stored in logs and metrics leading to excessive memory usage and cost. -const maxErrMessageLen = 1000 - -// TODO_IN_THIS_PR: add hydratedLoggers. - -// requestBuilder handles the construction of requestQoSContext objects -type requestBuilder struct { - Logger polylog.Logger - ServiceName ServiceName - - context *requestQoSContext -} - -// parseHTTPRequest reads and processes the HTTP request -// validates an HTTP request, extracting and validating its EVM JSONRPC payload. -func (rb *requestBuilder) ParseHTTPRequest(httpReq *http.Request) *requestBuilder { - requestCtx := requestQoSContext { - Logger: rb.Logger, - ServiceID: rb.ServiceID, - } - - // Read the HTTP request body - body, err := io.ReadAll(httpReq.Body) - defer httpReq.Body.Close() - - // TODO_IMPROVE(@adshmh): Propagate a request ID parameter on internal errors - // that occur after successful request parsing. - // There are no such cases as of PR #186. - if err != nil { - // Handle read error (internal server error) - rb.Logger.Error().Err(err).Msg("Failed to read request body") - - rb.context = newRequestErrorContextInternalError(err) - return rb - // Create error response for read failure - errResp := newErrResponseInternalReadError(err) - - requestCtx.JSONRPCErrorResponse = &errResp - rb.context = requestCtx - return rb - } - - // Parse the JSON-RPC request - var jsonrpcReq jsonrpc.JsonRpcRequest - if err := json.Unmarshal(body, &jsonrpcReq); err != nil { - // TODO_IN_THIS_PR: log the first 1K bytes of the body. - // Handle parse error (client error) - rb.Logger.Error().Err(err).Msg("Failed to parse JSON-RPC request") - - // Create error response for parse failure - errResp := newErrResponseParseError(err) - - requestCtx.JSONRPCErrorResponse = &errResp - rb.context = requestCtx - return rb - } - - // Store the parsed request - requestCtx.Request = &jsonrpcReq - rb.context = requestCtx - return rb -} - -// validateRequest validates the JSONRPC request using the default validator -func (rb *requestBuilder) ValidateRequest() *requestBuilder { - // Skip validation if we already have an error - if rb.context.errorResponse != nil || rb.context.request == nil { - return rb - } - - // Validate the request - errResp, isValid := validateRequest(rb.context.request) - if !isValid { - // Request is invalid according to the validator - rb.Logger.Warn(). - Str("method", rb.context.Request.Method). - Msg("Request validation failed") - - rb.context.JSONRPCErrorResponse = errResp - return rb - } - - rb.context.EndpointCallsProcessor = rb.EndpointCallProcessor - rb.context.EndpointSelector = rb.EndpointSelector - - // Request is valid - rb.Logger.Info(). - Str("method", rb.context.request.Method). - Msg("Request validation successful") - - return rb -} - -// build finalizes and returns the request context -func (rb *requestBuilder) Build() (*requestQoSContext, bool) { - return rb.context, rb.context.errorResponse == nil -} - -// TODO_MVP(@adshmh): Allow custom QoS services to supply custom request validation logic. -// Example use case: specifying a list of allowed JSONRPC request methods. -// This would require: -// 1. Declaring a public RequestValidator interface. -// 2. Helper functions, e.g. BuildRequestValidatorForAllowedMethods. -// -// validateRequest provides a basic validation of JSONRPC requests. -// It checks: -// - JSONRPC version (must be "2.0") -// - Method presence -// -// It returns a JSONRPC response if validation fails and a boolean indicating whether request processing should continue. -func validateRequest(request *jsonrpc.JsonRpcRequest, allowedMethods map[string]bool) (*jsonrpc.Response, bool) { - // Check JSONRPC version - if request.Jsonrpc != jsonrpc.Version2 { - resp := newErrResponseInvalidVersionError(request.Id) - return &resp, false - } - - // Check method presence - if request.Method == "" { - resp := newErrResponseMissingMethodError(request.Id) - return &resp, false - } - - // Request is valid - return nil, true -} diff --git a/qos/framework/jsonrpc/observations.go b/qos/framework/jsonrpc/observations.go index 7b5e438d6..af420dd38 100644 --- a/qos/framework/jsonrpc/observations.go +++ b/qos/framework/jsonrpc/observations.go @@ -6,21 +6,39 @@ import ( "github.com/buildwithgrove/path/qos/jsonrpc" ) -func (eq *endpointQuery) buildObservations() *observations.Observations { - return observations.Observations{ - // Service info - ServiceName: - ServiceDescription: +// getObservations returns the set of observations for the requestJournal. +// This includes: +// - Successful requests +// - Failed requests: internal error +// - Failed requests: invalid request +// - Failed requests: protcol error, i.e. no endpoint data received. +// requestJournal is the top-level struct in the chain of observation generators. +func (rj *requestJournal) getObservations() qosobservations.Observations { + // initialize the observations to include: + // - service name + // - observations related to the request: + observations := qosobservations.Observations { + ServiceName: rj.serviceName, + // request observations: + // - parsed JSONRPC (if successful) + // - validation error (if invalid) + RequestObservation: rj.requestDetails.buildObservation(), + } - // Observation of the client request - RequestObservation *RequestObservation - // Observations from endpoint(s) - EndpointObservations []*EndpointObservation + // No endpoint queries were performed: skip adding endpoint observations. + // e.g. for invalid requests. + if len(rj.endpointQueries) == 0 { + return observations + } + endpointObservations := make([]*qosobservations.EndpointObservation, len(rj.endpointQueries)) + for index, endpointQuery := range rj.endpointQueries { + endpointObservations[index] = endpointQuery.buildObservation() } -} -func (eqr *EndpointQueryResult) buildObservations() *observations.EndpointQueryResult + observations.EndpointObservations = endpointObservations + return observations +} func extractEndpointQueryResults(observations *observations.Observations) []*EndpointQueryResult diff --git a/qos/framework/jsonrpc/observations_endpoints.go b/qos/framework/jsonrpc/observations_endpoints.go new file mode 100644 index 000000000..660a193ef --- /dev/null +++ b/qos/framework/jsonrpc/observations_endpoints.go @@ -0,0 +1,8 @@ +package jsonrpc + +func (eq *endpointQuery) buildObservations() *qosobservations.EndpointObservation { +} + +func (eqr *EndpointQueryResult) buildObservations() *observations.EndpointQueryResult + + diff --git a/qos/framework/jsonrpc/observations_request.go b/qos/framework/jsonrpc/observations_request.go new file mode 100644 index 000000000..05ee6c809 --- /dev/null +++ b/qos/framework/jsonrpc/observations_request.go @@ -0,0 +1,23 @@ +package jsonrpc + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// TODO_IN_THIS_PR: ensure the observations will contain: +// - HTTP Status code: e.g. httpStatusRequestValidationFailureUnmarshalFailure, +// - Validation error: e.g. qosobservations.EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE, +// - Error details. +// +// buildObservations builds and returns request observations of of the requestDetails struct. +func (rd *requestDetails) buildObservations() *qosobservations.RequestObservation { + + // Only set if validation was successful + JsonrpcRequest *JsonRpcRequest + // Only set if validation failed + ValidationFailure *ValidationFailure + ErrorType RequestValidationError + ValidationErrorDetails + ErrorResponse *jsonrpc.Response +} diff --git a/qos/framework/jsonrpc/qos.go b/qos/framework/jsonrpc/qos.go index 05b1c0c38..8e0f897fd 100644 --- a/qos/framework/jsonrpc/qos.go +++ b/qos/framework/jsonrpc/qos.go @@ -15,7 +15,7 @@ import ( // TODO_TECHDEBT(@adshmh): Simplify the qos package by refactoring gateway.QoSContextBuilder. // Proposed change: Create a new ServiceRequest type containing raw payload data ([]byte) // Benefits: Decouples the qos package from HTTP-specific error handling. - +// // QoS represents a service that processes JSONRPC requests and applies QoS policies based on data returned by endpoints. type QoS struct { // Logger for diagnostics @@ -30,31 +30,27 @@ type QoS struct { // ParseHTTPRequest handles parsing an HTTP request and validating its content // It returns a RequestQoSContext and a boolean indicating if processing should continue func (s *QoSService) ParseHTTPRequest( - ctx context.Context, + _ context.Context, httpReq *http.Request, ) (*requestContext, bool) { - requestCtx := requestContext { - logger: s.logger, - journal: &requestJournal{ + // isRequestValid signals whether the request processing flow should continue. + requestDetails := buildRequestDetailsFromHTTP(s.logger, httpReq) + // initialize a context for processing the HTTP request. + requestCtx := &requestContext{ + logger: logger, + // initialize the request journal to track all data on the request. + journal: &requestJournal{ + requestDetails: requestDetails, }, } - - // Parse the HTTP request - builder.ParseHTTPRequest(httpReq) - - // Validate the JSONRPC request - builder.ValidateRequest() - - // Build and return the final context - reqContext, shouldContinue := builder.Build() - return reqContext, shouldContinue + + return requestCtx, requestDetails.isValid() } // TODO_IN_THIS_PR: implement this method // func (qos *QoS) ParseWebsocketRequest(_ context.Context) (gateway.RequestQoSContext, bool) - func (s *QoSService) ApplyObservations(observations *qosobservations.Observations) error ) { // -> Framework updates the endpoints + state as part of ApplyObservations diff --git a/qos/framework/jsonrpc/request.go b/qos/framework/jsonrpc/request.go new file mode 100644 index 000000000..d60df9e77 --- /dev/null +++ b/qos/framework/jsonrpc/request.go @@ -0,0 +1,117 @@ +package framework + +import ( + "context" + "encoding/json" + "io" + "net/http" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// TODO_MVP(@adshmh): Allow custom QoS services to supply custom request validation logic. +// Example use case: specifying a list of allowed JSONRPC request methods. +// This would require: +// 1. Declaring a public RequestValidator interface. +// 2. Helper functions, e.g. BuildRequestValidatorForAllowedMethods. +// +// maximum length of the error message stored in request validation failure observations and logs. +// This is used to prevent overly verbose error messages from being stored in logs and metrics leading to excessive memory usage and cost. +const maxErrMessageLen = 1000 + +type requestDetails struct { + // The client's JSONRPC request + // Only set if the request was successfully parsed. + request *jsonrpc.Request + + // Request error, if any. + requestError *requestError +} + +func (rd *requestDetails) isValid() bool { + return rd.requestError == nil +} + +// buildRequestContextFromHTTPRequest builds and returns a context for processing the HTTP request: +// - Reads and processes the HTTP request +// - Parses a JSONRPC request from the HTTP request's payload. +// - Validates the resulting JSONRPC request. +// - Initializes the context for processing the request in the following scenarios: +// - Internal errors: e.g. reading the HTTP request. +// - Invalid request: e.g. malformed payload. +// - Valid request: proper JSONRPC request. +func buildRequestDetailsFromHTTP( + logger polylog.Logger, + httpReq *http.Request, +) *requestDetails { + // Read the HTTP request body + body, err := io.ReadAll(httpReq.Body) + defer httpReq.Body.Close() + + // TODO_IMPROVE(@adshmh): Propagate a request ID parameter on internal errors that occur after successful request parsing. + // There are no such cases as of PR #186. + if err != nil { + // Handle read error (internal server error) + logger.Error().Err(err).Msg("Failed to read request body") + + // return the error details to be stored in the request journal. + return buildRequestDetailsForInternalErrHTTPRead(err) + } + + // Parse the JSON-RPC request + var jsonrpcReq jsonrpc.JsonRpcRequest + if err := json.Unmarshal(body, &jsonrpcReq); err != nil { + // TODO_IN_THIS_PR: log the first 1K bytes of the body. + // Handle parse error (client error) + logger.Error().Err(err).Msg("Failed to parse JSON-RPC request") + + return buildRequestDetailsForParseError(err) + } + + // Validate the request + requestErr := validateRequest(jsonrpcReq) + if requestErr != nil { + // Request is invalid according to the validator + logger.Info(). + Str("method", jsonrpcReq.Method). + Msg("Request validation failed") + + return requestDetails{ + request: jsonrpcReq, + requestError: requestErr, + } + } + + // Request is valid + logger.Debug(). + Str("method", jsonrpcReq.Method). + Msg("Request validation successful") + + return requestDetails { + request: &jsonrpcReq, + } + +} + +// validateRequest provides a basic validation of JSONRPC requests. +// It checks: +// - JSONRPC version (must be "2.0") +// - Method presence +// +// Returns a non-nil requestError if validation fails. +func validateRequest(request *jsonrpc.Request) *requestError { + // Check JSONRPC version + if request.Jsonrpc != jsonrpc.Version2 { + return buildRequestErrorJSONRPCErrInvalidVersion(request.ID) + } + + // Check method presence + if request.Method == "" { + return buildRequestErrorJSONRPCErrMissingMethod(request.ID) + } + + // Request is valid + return nil +} diff --git a/qos/framework/jsonrpc/request_errors.go b/qos/framework/jsonrpc/request_errors.go new file mode 100644 index 000000000..04bc57087 --- /dev/null +++ b/qos/framework/jsonrpc/request_errors.go @@ -0,0 +1,61 @@ +package jsonrpc + +type requestErrorKind int +const ( + _ requestErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. + requestErrKindInternalErrReadyHTTPBody + requestErrKindJSONRPCParsingErr + requestErrKindRequestValidationErr +) + +type requestError struct { + // Captures the kind of error the request encountered. + // e.g. error parsing HTTP payload into a JSONRPC request. + errorKind requestErrorKind + + // Stores a description of the request error. + errorDetails string + + // Error response to return if a request parsing error occurred: + // - error reading HTTP request's body. + // - error parsing the request's payload into a jsonrpc.Request struct. + jsonrpcErrorResponse jsonrpc.Response +} + +func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { + return &requestError { + errorKind: requestErrKindInternalErrReadyHTTPBody, + errorDetails: fmt.Sprintf("error reading HTTP request body: %v", err), + // Create JSONRPC error response for read failure + jsonrpcErrorResponse: newJSONRPCErrResponseInternalReadError(err), + } +} + +func buildRequestErrorForParseError(err error) *requestError { + return &requestError { + errorKind: requestErrKindJSONRPCParsingErr, + errorDetails: fmt.Sprintf("error parsing HTTP request into JSONRPC: %v", err), + // Create JSONRPC error response for parse failure + jsonrpcErrorResponse: newJSONRPCErrResponseParseError(err), + } +} + +func buildRequestErrorJSONRPCErrInvalidVersion(requestID jsonrpc.ID, version jsonrpc.Version) *requestError { + err := fmt.Errorf("invalid version in JSONRPC request: %s", version) + + return &requestError { + errorKind: requestErrKindJSONRPCInvalidVersion, + errorDetails: err.Error(), + // Create JSONRPC error response for parse failure + jsonrpcErrorResponse: newJSONRPCErrResponseInvalidVersion(err, requestID), + } +} + +func buildRequestErrorJSONRPCErrMissingMethod(requestID jsonrpc.Request) *requestError { + return &requestError { + errorKind: requestErrKindJSONRPCMissingMethod, + errorDetails: "No method specified by the JSONRPC request", + // Create JSONRPC error response for parse failure + jsonrpcErrorResponse: newJSONRPCErrResponseMissingMethod(requestID), + } +} diff --git a/qos/framework/jsonrpc/request_journal.go b/qos/framework/jsonrpc/request_journal.go index 84d1fba39..eb76d948a 100644 --- a/qos/framework/jsonrpc/request_journal.go +++ b/qos/framework/jsonrpc/request_journal.go @@ -13,13 +13,7 @@ type requestJournal struct { // Service identification serviceName string - // The client's JSONRPC request - request *jsonrpc.Request - - // Error response to return if a request parsing error occurred: - // - error reading HTTP request's body. - // - error parsing the request's payload into a jsonrpc.Request struct. - errorResponse *jsonrpc.Response + requestDetails *requestDetails // All endpoint interactions that occurred during processing. endpointQueries []*endpointQuery From 6284daf56cbff221bb69164f537aae0f02ccf7b6 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 14:03:11 -0400 Subject: [PATCH 06/26] Remove now-obsolete requestErrorContext --- .../jsonrpc/context_request_errors.go | 115 ------------------ 1 file changed, 115 deletions(-) delete mode 100644 qos/framework/jsonrpc/context_request_errors.go diff --git a/qos/framework/jsonrpc/context_request_errors.go b/qos/framework/jsonrpc/context_request_errors.go deleted file mode 100644 index 5ae7281a9..000000000 --- a/qos/framework/jsonrpc/context_request_errors.go +++ /dev/null @@ -1,115 +0,0 @@ - -//// THIS MAY NOT BE NEEDED with the ServiceRequestJournal -/// -package jsonrpc - -import ( - "encoding/json" - "errors" - - "github.com/pokt-network/poktroll/pkg/polylog" - - "github.com/buildwithgrove/path/gateway" - qosobservations "github.com/buildwithgrove/path/observation/qos/framework" - "github.com/buildwithgrove/path/protocol" - "github.com/buildwithgrove/path/qos/jsonrpc" -) - -var ( - // Error recording that endpoint selection was attempted but failed due to an invalid request - errInvalidSelectorUsage = errors.New("endpoint selection attempted on failed request") -) - -// errorContext provides the support required by the gateway package for handling service requests. -var _ gateway.RequestQoSContext = &errorContext{} - -func newRequestErrorContextInternalError(logger polylog.Logger, err error) *errorContext { - return &errorContext{ - logger: logger, - - // Create error response for read failure - errResp := newErrResponseInternalReadError(err) - - requestCtx.JSONRPCErrorResponse = &errResp - rb.context = requestCtx - return rb -} - - -// errorContext terminates EVM request processing on errors (internal failures or invalid requests). -// Provides: -// 1. Detailed error response to the user -// 2. Observation: feed into Metrics and data pipeline. -// -// Implements gateway.RequestQoSContext -type errorContext struct { - logger polylog.Logger - - // The observation to return, to be processed by the metrics and data pipeline components. - observations *qosobservations.Observations - - // The response to be returned to the user. - response jsonrpc.Response -} - -// GetHTTPResponse formats the stored JSONRPC error as an HTTP response -// Implements the gateway.RequestQoSContext interface. -func (ec *errorContext) GetHTTPResponse() gateway.HTTPResponse { - return buildHTTPResponse(ec.Logger, ec.errorResponse) -} - -// GetObservation returns the QoS observation set for the error context. -// Implements the gateway.RequestQoSContext interface. -func (ec *errorContext) GetObservations() qosobservations.Observations { - return qosobservations.Observations{ - ServiceObservations: ec.evmObservations, - } -} - -// GetServicePayload should never be called. -// It logs a warning and returns nil. -// Implements the gateway.RequestQoSContext interface. -func (ec *errorContext) GetServicePayload() protocol.Payload { - ec.logger.Warn().Msg("Invalid usage: errorContext.GetServicePayload() should never be called.") - return protocol.Payload{} -} - -// UpdateWithResponse should never be called. -// Only logs a warning. -// Implements the gateway.RequestQoSContext interface. -func (ec *errorContext) UpdateWithResponse(endpointAddr protocol.EndpointAddr, endpointSerializedResponse []byte) { - ec.logger.With( - "endpoint_addr", endpointAddr, - "endpoint_response_len", len(endpointSerializedResponse), - ).Warn().Msg("Invalid usage: errorContext.UpdateWithResponse() should never be called.") -} - -// UpdateWithResponse should never be called. -// It logs a warning and returns a failing selector that logs a warning on all selection attempts. -// Implements the gateway.RequestQoSContext interface. -func (ec *errorContext) GetEndpointSelector() protocol.EndpointSelector { - ec.logger.Warn().Msg("Invalid usage: errorContext.GetEndpointSelector() should never be called.") - - return errorTrackingSelector{ - logger: ec.logger, - } -} - -// errorTrackingSelector prevents panics in request handling goroutines by: -// - Intentionally failing all endpoint selection attempts -// - Logging diagnostic information when endpoint selection is incorrectly attempted on failed requests -// Acts as a failsafe mechanism for request handling. -type errorTrackingSelector struct { - logger polylog.Logger -} - -// Select method of an errorTrackingSelector should never be called. -// It logs a warning and returns an invalid usage error. -// Implements the protocol.EndpointSelector interface. -func (ets errorTrackingSelector) Select(endpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { - ets.logger.With( - "num_endpoints", len(endpoints), - ).Warn().Msg("Invalid usage: errorTrackingSelector.Select() should never be called.") - - return protocol.EndpointAddr(""), errInvalidSelectorUsage -} From bcf5c11c5018401d8058fc02be8f6abd29740b23 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 14:30:40 -0400 Subject: [PATCH 07/26] Implement request observations. --- qos/framework/jsonrpc/observations_request.go | 24 ++++++++++++------ .../jsonrpc/observations_request_error.go | 25 +++++++++++++++++++ qos/framework/jsonrpc/request_errors.go | 3 ++- 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 qos/framework/jsonrpc/observations_request_error.go diff --git a/qos/framework/jsonrpc/observations_request.go b/qos/framework/jsonrpc/observations_request.go index 05ee6c809..3dd749d6b 100644 --- a/qos/framework/jsonrpc/observations_request.go +++ b/qos/framework/jsonrpc/observations_request.go @@ -12,12 +12,22 @@ import ( // // buildObservations builds and returns request observations of of the requestDetails struct. func (rd *requestDetails) buildObservations() *qosobservations.RequestObservation { + // build a JSONRPC request observation, if one was parsed. + var jsonrpcRequestObs *qosobservations.JsonrpcRequest + if rd.request != nil { + jsonrpcRequestObs = rd.request.GetObservation(), + } - // Only set if validation was successful - JsonrpcRequest *JsonRpcRequest - // Only set if validation failed - ValidationFailure *ValidationFailure - ErrorType RequestValidationError - ValidationErrorDetails - ErrorResponse *jsonrpc.Response + // build request failure observation, if the request parsing failed. + var validationErrorObs *qosobservations.ValidationError + if rd.requestError != nil { + validationErrorObs = rd.requestError.buildObservations() + } + + return &qosobservations.RequestObservation { + // Only set if validation was successful + JsonrpcRequest: jsonrpcRequestObs, + // Only set if validation failed + ValidationError: validationErrorObs, + } } diff --git a/qos/framework/jsonrpc/observations_request_error.go b/qos/framework/jsonrpc/observations_request_error.go new file mode 100644 index 000000000..2a2b45d58 --- /dev/null +++ b/qos/framework/jsonrpc/observations_request_error.go @@ -0,0 +1,25 @@ +package jsonrpc + +func (re *requestError) buildObservation() *qosobservations.ValidationError { + return &qosobservations.ValidationError { + ErrorType: translateToRequestValidationError(re.errorKind), + ValidationErrorDetails: re.errorDetails, + // HTTP status code returned to the client. + HttpStatusCode: re.jsonrpcErrorResponse.GetRecommendedHTTPStatusCode(), + } +} + +func translateToRequestValidationError(errKind requestErrorKind) qosobservations.RequestValidationErrorKind { + switch errKind { + case requestErrKindInternalErrReadyHTTPBody: + return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_BODY_READ_FAILURE + case requestErrKindJSONRPCParsingErr: + return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_UNMARSHALING_FAILURE + requestErrKindJSONRPCInvalidVersion + return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_INVALID_VERSION + case requestErrKindJSONRPCMissingMethod: + return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_MISSING_METHOD + default: + return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_UNSPECIFIED + } +} diff --git a/qos/framework/jsonrpc/request_errors.go b/qos/framework/jsonrpc/request_errors.go index 04bc57087..8f62aeaf4 100644 --- a/qos/framework/jsonrpc/request_errors.go +++ b/qos/framework/jsonrpc/request_errors.go @@ -5,7 +5,8 @@ const ( _ requestErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. requestErrKindInternalErrReadyHTTPBody requestErrKindJSONRPCParsingErr - requestErrKindRequestValidationErr + requestErrKindJSONRPCInvalidVersion + requestErrKindJSONRPCMissingMethod ) type requestError struct { From 99aa007334cbe410f33f11c3d61e853d6a6b4ab4 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 21:11:35 -0400 Subject: [PATCH 08/26] Handle NoResponse, i.e. Protocol-level error --- qos/framework/jsonrpc/context_request.go | 17 +++++- qos/framework/jsonrpc/jsonrpc_errors.go | 14 +++++ qos/framework/jsonrpc/qos.go | 11 +++- qos/framework/jsonrpc/request.go | 19 ++++++- qos/framework/jsonrpc/request_errors.go | 21 ++++++++ qos/framework/jsonrpc/request_journal.go | 66 ++++++++++-------------- 6 files changed, 103 insertions(+), 45 deletions(-) diff --git a/qos/framework/jsonrpc/context_request.go b/qos/framework/jsonrpc/context_request.go index 50fef6315..c457ddf99 100644 --- a/qos/framework/jsonrpc/context_request.go +++ b/qos/framework/jsonrpc/context_request.go @@ -90,12 +90,19 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // GetHTTPResponse builds the HTTP response that should be returned for a JSONRPC service request. // Implements the gateway.RequestQoSContext interface. func (rc requestQoSContext) GetHTTPResponse() gateway.HTTPResponse { + // check if a protocol-level error has occurred. + rc.checkForProtocolLevelError() + + // use the request journal to build the client's HTTP response. return rc.journal.getHTTPResponse() } // GetObservations uses the request's journal to build and return all observations. // Implements gateway.RequestQoSContext interface. func (rc requestContext) GetObservations() qosobservations.Observations { + // check if a protocol-level error has occurred. + rc.checkForProtocolLevelError() + // Use the request journal to generate the observations. return rc.journal.getObservations() } @@ -107,4 +114,12 @@ func (rc *requestQoSContext) GetEndpointSelector() protocol.EndpointSelector { return selectorCtx.buildSelectorForRequest(rc.journal) } - +// Declares the request as failed with protocol-level error if no data from any endpoints has been reported to the request context. +func (rc *requestContext) checkForProtocolLevelError() { + // TODO_IMPROVE(@adshmh): consider using the journal directly for setting protocol failure error. + // + // Assume protocol-level error if no endpoint responses have been received yet. + if len(rc.journal.processedEndpointQueries) == 0 { + rc.journal.requestDetails.setProtocolLevelError() + } +} diff --git a/qos/framework/jsonrpc/jsonrpc_errors.go b/qos/framework/jsonrpc/jsonrpc_errors.go index 030fe051d..a5cd19c90 100644 --- a/qos/framework/jsonrpc/jsonrpc_errors.go +++ b/qos/framework/jsonrpc/jsonrpc_errors.go @@ -90,6 +90,20 @@ func newErrResponseInternalError(requestID jsonrpc.ID) jsonrpc.Response { ) } +func newJSONRPCErrResponseInternalProtocolError(requestID jsonrpc.ID) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html + "internal error: protocol-level error has occurred", // Error Message + map[string]string{ + "error_type": "protocol", + // Custom extension - not part of the official JSON-RPC spec + // Marks the error as retryable to allow clients to safely retry their request + "retryable": "true", + }, + ) +} + // newErrResponseMarshalError creates a JSON-RPC error response for marshaling errors. // This response: // - Preserves the original request ID if available diff --git a/qos/framework/jsonrpc/qos.go b/qos/framework/jsonrpc/qos.go index 8e0f897fd..1dbee4ec4 100644 --- a/qos/framework/jsonrpc/qos.go +++ b/qos/framework/jsonrpc/qos.go @@ -33,7 +33,6 @@ func (s *QoSService) ParseHTTPRequest( _ context.Context, httpReq *http.Request, ) (*requestContext, bool) { - // isRequestValid signals whether the request processing flow should continue. requestDetails := buildRequestDetailsFromHTTP(s.logger, httpReq) // initialize a context for processing the HTTP request. @@ -44,8 +43,11 @@ func (s *QoSService) ParseHTTPRequest( requestDetails: requestDetails, }, } + + // check if the request processing flow should continue. + shouldContinue := requestDetails.getRequestErrorJSONRPCResponse() != nil - return requestCtx, requestDetails.isValid() + rturn requestCtx, shouldContinue } // TODO_IN_THIS_PR: implement this method @@ -91,3 +93,8 @@ func (q *QoS) buildEndpointSelectionContext() *EndpointSelectionContext { customSelector: q.qosDefinition.EndpointSelector, } } + +// TODO_IN_THIS_PR: implement this method. +func (q *QoS) buildServiceStateUpdateContext() *ServiceStateUpdateContext { + +} diff --git a/qos/framework/jsonrpc/request.go b/qos/framework/jsonrpc/request.go index d60df9e77..3fddecf7a 100644 --- a/qos/framework/jsonrpc/request.go +++ b/qos/framework/jsonrpc/request.go @@ -30,8 +30,23 @@ type requestDetails struct { requestError *requestError } -func (rd *requestDetails) isValid() bool { - return rd.requestError == nil +func (rd *requestDetails) getRequestErrorJSONRPCResponse() *jsonrpc.Response { + if rd.requestError == nil { + return nil + } + + return rd.requestError.getJSONRPCResponse() +} + +func (rd *requestDetails) setProtocolLevelError() { + // request already marked as failed. + // skip setting an error. + if rd.requestError != nil { + return + } + + // set the request as failed with protocol-level error. + rd.requestError = buildRequestErrorForInternalErrProtocolErr(rd.request.ID) } // buildRequestContextFromHTTPRequest builds and returns a context for processing the HTTP request: diff --git a/qos/framework/jsonrpc/request_errors.go b/qos/framework/jsonrpc/request_errors.go index 8f62aeaf4..be0930f35 100644 --- a/qos/framework/jsonrpc/request_errors.go +++ b/qos/framework/jsonrpc/request_errors.go @@ -23,6 +23,10 @@ type requestError struct { jsonrpcErrorResponse jsonrpc.Response } +func (re *requestError) getJSONRPCResponse() *jsonrpc.Response { + return &re.jsonrpcErrorResponse +} + func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { return &requestError { errorKind: requestErrKindInternalErrReadyHTTPBody, @@ -32,6 +36,23 @@ func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { } } +// TODO_TECHDEBT(@adshmh): Report the protocol-level error to the QoS system to use here. +// Use these steps: +// - Update gateway.RequestQoSContext interface: add a ReportProtocolError(error) method. +// - Update requestContext: add ReportProtocolError to pass the error to the requestCtx.journal.request object. +// +// Protocol-level error: e.g. endpoint timeout has occurred. +// This is an internal error, causing a valid request to fail. +// The exact error is not known here: see the TODO_TECHDEBT above. +func buildRequestErrorForInternalErrProtocolErr(requestID jsonrpc.ID) *requestError { + return &requestError { + errorKind: requestErrKindInternalErrProtocolError, + errorDetails: "error handling the request due to protocol-level error.", + // Create JSONRPC error response for protocol error. + jsonrpcErrorResponse: newJSONRPCErrResponseInternalProtocolError(requestID), + } +} + func buildRequestErrorForParseError(err error) *requestError { return &requestError { errorKind: requestErrKindJSONRPCParsingErr, diff --git a/qos/framework/jsonrpc/request_journal.go b/qos/framework/jsonrpc/request_journal.go index eb76d948a..099da3651 100644 --- a/qos/framework/jsonrpc/request_journal.go +++ b/qos/framework/jsonrpc/request_journal.go @@ -1,5 +1,10 @@ package jsonrpc +// TODO_IN_THIS_PR: verify the EmptyResponse and NoResponse scenarios: +// - EmptyResponse is an EndpointQueryResult, because the endpoint did return an empty payload. +// - NoReponse is a requestError: e.g. there may have been ZERO ENDPOINTS available at the PROTOCOL-LEVEL. +// - It is an INTERNAL error: like failing to read HTTP request's body. + const ( // TODO_MVP(@adshmh): Support individual configuration of timeout for every service that uses EVM QoS. // The default timeout when sending a request to an EVM blockchain endpoint. @@ -16,7 +21,8 @@ type requestJournal struct { requestDetails *requestDetails // All endpoint interactions that occurred during processing. - endpointQueries []*endpointQuery + // These are all expected to be processed: i.e. have a non-nil result pointer and a client JSONRPC response. + processedEndpointQueries []*endpointQuery } func (rj *requestJournal) buildEndpointQuery(endpointAddr protocol.EndpointAddr, receivedData []byte) *endpointQuery { @@ -55,47 +61,27 @@ func (rj *requestJournal) getServicePayload() protocol.Payload { } } -func (rj *requestJournal) getHTTPResponse() gateway.HTTPResponse { - if rj.JSONRPCErrorResponse != nil { - return buildHTTPResponse(rj.Logger, rj.JSONRPCErrorResponse) - } - - return buildHTTPResponse(rj.Logger, rj.getJSONRPCResponse()) -} - -func (rj *requestJournal) getObservations() qosobservations.Observations { - /* - // Service identification - ServiceName: - ServiceDescription: - - // Observation of the client request - RequestObservation *RequestObservation - // Observations from endpoint(s) - EndpointObservations []*EndpointObservation - - return qosobservations.Observations { - RequestObservations: rc. resut???? .GetObservation(), - EndpointObservations: rc.EndpointCallsProcessor.GetObservations(), - // TODO_IN_THIS_PR: Implement this method in observations.go. - // Return basic observations for now - ServiceId: p.ServiceID, - ServiceDescription: p.ServiceDescription, - RequestObservation: p.RequestObservation, - } - */ -} - // TODO_FUTURE(@adshmh): A retry mechanism would require support from this struct to determine if the most recent endpoint query has been successful. // -// getJSONRPCResponse simply returns the result associated with the most recently reported endpointQuery. -func (rj *requestJournal) getJSONRPCResponse() *jsonrpc.Response { - // Check if we received any endpoint results - if len(rc.processedResults) == 0 { - // If no results were processed, handle it as a protocol error - return buildResultForNoResponse(rc.Request) +// getHTTPResponse returns the client's HTTP response: +// - Uses the request error if set +// - Uses the most recent endpoint query if the request has no errors set. +func (rj *requestJournal) getHTTPResponse() gateway.HTTPResponse { + // For failed requests, return the preset JSONRPC error response. + // - Invalid request: e.g. malformed payload from client. + // - Internal error: error reading HTTP request's body + // - Internal error: Protocol-level error, e.g. selected endpoint timed out. + if requestErrorJSONRPCResponse := rj.requestDetails.getRequestErrorJSONRPCResponse(); requestErrorJSONRPCResponse != nil { + return buildHTTPResponse(rj.Logger, requestErrorJSONRPCResponse) } - // Return the latest result. - return rc.processedResults[len(rc.processedResults)-1] + // TODO_IMPROVE(@adshmh): find a refactor: + // Goal: guarantee that valid request -> at least 1 endpoint query. + // Constraint: Such a refactor should keep the requestJournal as a data container. + // + // Use the most recently reported endpoint query. + // There MUST be an entry if the request has no error set. + selectedQuery := rj.processedEndpointQueries[len(rj.processedEndpointQueries)-1] + jsonrpcResponse := selectedQuery.result.clientJSONRPCResponse + return buildHTTPResponse(rj.Logger, jsonrpcResponse) } From 80df05f5c62e19afc64b44c491212a2142237d7b Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 8 Apr 2025 21:50:17 -0400 Subject: [PATCH 09/26] Update proto definitions to match endpoint structs --- .../qos/framework/endpoint_attribute.proto | 20 -------------- ...ibute_error.proto => endpoint_error.proto} | 6 ++--- .../qos/framework/endpoint_query_result.proto | 27 +++++++++++++++++-- 3 files changed, 27 insertions(+), 26 deletions(-) delete mode 100644 proto/path/qos/framework/endpoint_attribute.proto rename proto/path/qos/framework/{endpoint_attribute_error.proto => endpoint_error.proto} (79%) diff --git a/proto/path/qos/framework/endpoint_attribute.proto b/proto/path/qos/framework/endpoint_attribute.proto deleted file mode 100644 index 5b05d0e65..000000000 --- a/proto/path/qos/framework/endpoint_attribute.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; -package path.qos.framework; - -option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; - -import "path/qos/framework/endpoint_attribute_error.proto"; // import EndpointAttributeError - -// EndpointAttribute represents a single data item extracted from an endpoint response. -// Maps to EndpointAttribute in endpoint_attribute.go -message EndpointAttribute { - // We use oneof to represent the mutually exclusive types (string or int) - oneof value { - string string_value = 1; - int32 int_value = 2; - } - - // Error information if this attribute is associated with a failure - // Optional field - optional EndpointAttributeError error = 3; -} diff --git a/proto/path/qos/framework/endpoint_attribute_error.proto b/proto/path/qos/framework/endpoint_error.proto similarity index 79% rename from proto/path/qos/framework/endpoint_attribute_error.proto rename to proto/path/qos/framework/endpoint_error.proto index 41753ee2f..0824f7da8 100644 --- a/proto/path/qos/framework/endpoint_attribute_error.proto +++ b/proto/path/qos/framework/endpoint_error.proto @@ -8,15 +8,13 @@ import "path/qos/framework/endpoint_sanction.proto"; // import Sanction definiti // EndpointErrorKind identifies different kinds of endpoint data errors. enum EndpointErrorKind { ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED = 0; - ENDPOINT_DATA_ERROR_KIND_NO_INTERACTION = 1; // No endpoint interaction occurred or no payload received ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD = 2; // Empty payload from endpoint ENDPOINT_DATA_ERROR_KIND_UNMARSHALING = 3; // Could not parse endpoint payload ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT = 4; // Payload result doesn't match expected value } -// EndpointAttributeError contains error details for endpoint queries. -// Maps to EndpointAttributeError in endpoint_attribute_error.go -message EndpointAttributeError { +// EndpointError contains error details for endpoint queries. +message EndpointError { // Specifies the kind of endpoint eror observed. // Example: Returned payload cannot be parsed into a JSONRPC response. EndpointErrorKind error_kind = 1; diff --git a/proto/path/qos/framework/endpoint_query_result.proto b/proto/path/qos/framework/endpoint_query_result.proto index 38bb91752..2935e519f 100644 --- a/proto/path/qos/framework/endpoint_query_result.proto +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -3,11 +3,34 @@ package path.qos.framework; option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; -import "path/qos/framework/endpoint_attribute.proto"; // import EndpointAttribute +import "google/protobuf/timestamp.proto"; +import "path/qos/framework/endpoint_error.proto"; // Import endpoint error definitions. -// EndpointQueryResult captures information extracted from a service endpoint query. +// EndpointQueryResult captures data extracted from a service endpoint query. // Maps to EndpointQueryResult in endpoint_query_result.go message EndpointQueryResult { // The set of endpoint attributes map endpoint_attributes = 1; } + +// EndpointQueryResult captures data extracted from an endpoint query. +// - Stores one or more string/integer values. +// - Contains error/sanction information on endpoint error. +message EndpointQueryResult { + // HTTP response returned to the client. + int32 client_http_response = 1; + + // The set of values/attributes extracted from the endpoint query + // and the endpoint's parsed JSONRPC response + map string_values = 2; + map int_values = 3; + + // Captures the queried endpoint's error + // Only set if the query result indicates an endpoint error + optional EndpointError error = 5; + + // The time at which the query result is expired + google.protobuf.Timestamp expiry_time = 6; +} + + From d67cc05165736d9038af095140434de54dff80c0 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 9 Apr 2025 07:43:55 -0400 Subject: [PATCH 10/26] Implement observation logic --- .../qos/framework/endpoint_query_result.proto | 7 - proto/path/qos/framework/observations.proto | 9 +- proto/path/qos/framework/request.proto | 30 ++-- qos/framework/jsonrpc/endpoint_errors.go | 23 +++ .../jsonrpc/endpoint_query_result.go | 15 +- qos/framework/jsonrpc/observations.go | 26 ++- .../jsonrpc/observations_endpoint.go | 20 +++ .../jsonrpc/observations_endpoint_error.go | 124 +++++++++++++ .../jsonrpc/observations_endpoint_result.go | 169 +++++++----------- .../jsonrpc/observations_endpoints.go | 8 - qos/framework/jsonrpc/observations_request.go | 11 +- .../jsonrpc/observations_request_error.go | 18 +- qos/framework/jsonrpc/request_journal.go | 2 +- 13 files changed, 297 insertions(+), 165 deletions(-) create mode 100644 qos/framework/jsonrpc/endpoint_errors.go create mode 100644 qos/framework/jsonrpc/observations_endpoint.go create mode 100644 qos/framework/jsonrpc/observations_endpoint_error.go delete mode 100644 qos/framework/jsonrpc/observations_endpoints.go diff --git a/proto/path/qos/framework/endpoint_query_result.proto b/proto/path/qos/framework/endpoint_query_result.proto index 2935e519f..ee170f528 100644 --- a/proto/path/qos/framework/endpoint_query_result.proto +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -6,13 +6,6 @@ option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; import "google/protobuf/timestamp.proto"; import "path/qos/framework/endpoint_error.proto"; // Import endpoint error definitions. -// EndpointQueryResult captures data extracted from a service endpoint query. -// Maps to EndpointQueryResult in endpoint_query_result.go -message EndpointQueryResult { - // The set of endpoint attributes - map endpoint_attributes = 1; -} - // EndpointQueryResult captures data extracted from an endpoint query. // - Stores one or more string/integer values. // - Contains error/sanction information on endpoint error. diff --git a/proto/path/qos/framework/observations.proto b/proto/path/qos/framework/observations.proto index fd51d7640..dbd25b790 100644 --- a/proto/path/qos/framework/observations.proto +++ b/proto/path/qos/framework/observations.proto @@ -9,12 +9,11 @@ import "path/qos/framework/endpoint.proto"; // Import EndpointObservation // Observations is the top-level container for all QoS observations for a request. message Observations { // Service identification - string service_id = 1; - string service_description = 2; // e.g. EVM, Solana, CometBFT - + string service_name = 1; // e.g. EVM, Solana, CometBFT + // Observation of the client request - RequestObservation request_observation = 3; + RequestObservation request_observation = 2; // Observations from endpoint(s) - repeated EndpointObservation endpoint_observations = 4; + repeated EndpointObservation endpoint_observations = 3; } diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto index a73df250e..532bd0b13 100644 --- a/proto/path/qos/framework/request.proto +++ b/proto/path/qos/framework/request.proto @@ -5,23 +5,25 @@ option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; import "path/qos/framework/jsonrpc.proto"; // Import basic JSONRPC messages. -// RequestValidationError identifies the type of validation error encountered. -enum RequestValidationError { - REQUEST_VALIDATION_ERROR_UNSPECIFIED = 0; - REQUEST_VALIDATION_ERROR_BODY_READ_FAILURE = 1; // Error reading HTTP request body - REQUEST_VALIDATION_ERROR_UNMARSHALING_FAILURE = 2; // Error parsing JSON-RPC request - REQUEST_VALIDATION_ERROR_INVALID_REQUEST = 3; // Request format is incorrect - REQUEST_VALIDATION_ERROR_INVALID_METHOD = 4; // Method is not supported - REQUEST_VALIDATION_ERROR_INVALID_PARAMS = 5; // Parameters are incorrect +// RequestErrorKind identifies the type of validation error encountered. +enum RequestErrorKind { + REQUEST_ERROR_UNSPECIFIED = 0; + REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE = 1; // Error reading HTTP request body + REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR = 2; // Protocol error: e.g. endpoint timed out. + REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE = 3; // Error parsing JSON-RPC request + REQUEST_ERROR_VALIDATION_INVALID_VERSION = 4; // JSONRPC request has invalid version. + REQUEST_ERROR_VALIDATION_MISSING_METHOD = 5; // JSONRPC request does not specify method. + REQUEST_ERROR_VALIDATION_INVALID_PARAMS = 6; // Parameters are incorrect } // TODO_IN_THIS_PR: Consider removing the http_status_code. -// ValidationFailure contains details about a request validation failure -message ValidationFailure { - RequestValidationError error_type = 1; +// RequestError contains details about a request error +message RequestError { + RequestErrorKind error_kind = 1; + // HTTP status code returned to the client. int32 http_status_code = 2; - string validation_error_details = 3; + string error_details = 3; // Only set for validation failures on valid JSONRPC structures // e.g. missing params field when required. @@ -34,6 +36,6 @@ message RequestObservation { // Only set if validation was successful optional JsonRpcRequest jsonrpc_request = 1; - // Only set if validation failed - optional ValidationFailure validation_failure = 2; + // Only set if the request failed + optional RequestError request_error = 2; } diff --git a/qos/framework/jsonrpc/endpoint_errors.go b/qos/framework/jsonrpc/endpoint_errors.go new file mode 100644 index 000000000..a0289baa3 --- /dev/null +++ b/qos/framework/jsonrpc/endpoint_errors.go @@ -0,0 +1,23 @@ +package jsonrpc + +type EndpointErrorKind int +const ( + _ EndpointErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. + EndpointErrKindEmptyPayload // Empty payload from endpoint + EndpointErrKindParseErr // Could not parse endpoint payload + EndpointErrKindInvalidResult // Payload result doesn't match expected value: e.g. invalid chainID value +) + +// EndpointError contains error details for endpoint queries. +// An EndpointError is always associated with an Endpoint Attribute struct. +type EndpointError struct { + // The category of endpoint error + ErrorKind EndpointErrorKind + + // Description is set by the custom service implementation + Description string + + // RecommendedSanction is set by the custom service implementation + // It is under ResultError to clarify the reason a sanction was recommended. + RecommendedSanction *Sanction +} diff --git a/qos/framework/jsonrpc/endpoint_query_result.go b/qos/framework/jsonrpc/endpoint_query_result.go index 3fd10be09..82f0de2f8 100644 --- a/qos/framework/jsonrpc/endpoint_query_result.go +++ b/qos/framework/jsonrpc/endpoint_query_result.go @@ -15,6 +15,12 @@ type EndpointQueryResult struct { // It can be used, e.g. to retrieve the JSONRPC request and its method. *endpointQuery + // TODO_IN_THIS_PR: verify this is set by all result builders. + + // The JSONRPC response to be returned to the client. + // MUST be set. + clientResponse *jsonrpc.Response + // The set of values/attributes extracted from the endpoint query and the endpoint's parsed JSONRPC response. // e.g. for a Solana `getEpochInfo` request, the custom service could derive two endpoint attributes as follows: // - "BlockHeight": 0x1234 @@ -40,13 +46,4 @@ type EndpointQueryResult struct { // TODO_FUTURE(@adshmh): add a JSONRPCErrorResponse to allow a result builder to supply its custom JSONRPC response. } -// EndpointError contains error details for endpoint queries. -// An EndpointError is always associated with an Endpoint Attribute struct. -type EndpointError struct { - // Description is set by the custom service implementation - Description string - // RecommendedSanction is set by the custom service implementation - // It is under ResultError to clarify the reason a sanction was recommended. - RecommendedSanction *Sanction -} diff --git a/qos/framework/jsonrpc/observations.go b/qos/framework/jsonrpc/observations.go index af420dd38..1cb1f4449 100644 --- a/qos/framework/jsonrpc/observations.go +++ b/qos/framework/jsonrpc/observations.go @@ -9,9 +9,12 @@ import ( // getObservations returns the set of observations for the requestJournal. // This includes: // - Successful requests -// - Failed requests: internal error -// - Failed requests: invalid request -// - Failed requests: protcol error, i.e. no endpoint data received. +// - Failed requests, due to: +// - internal error: +// - error reading HTTP request's body. +// - any protocol-level error: e.g. endpoint timed out. +// - invalid request +// // requestJournal is the top-level struct in the chain of observation generators. func (rj *requestJournal) getObservations() qosobservations.Observations { // initialize the observations to include: @@ -31,8 +34,9 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { return observations } - endpointObservations := make([]*qosobservations.EndpointObservation, len(rj.endpointQueries)) - for index, endpointQuery := range rj.endpointQueries { + // Add one endpoint observation entry per processed enpoint query stored in the journal. + endpointObservations := make([]*qosobservations.EndpointObservation, len(rj.processedEndpointQueries)) + for index, endpointQuery := range rj.processedEndpointQueries { endpointObservations[index] = endpointQuery.buildObservation() } @@ -40,5 +44,15 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { return observations } -func extractEndpointQueryResults(observations *observations.Observations) []*EndpointQueryResult +// TODO_IN_THIS_PR: check the observations have the correct service name. +func (rj *requestJournal) extractEndpointQueriesFromObservations(observations *observations.Observations) []*endpointQuery { + // fetch endpoint observations + endpointObservations := observations.GetEndpointObservations() + endpointQueries := make(*endpointQuery, len(endpointObservations)) + for index, endpointObservation := range endpointObservations { + endpointQueries[index] = extractEndpointQueryFromObservation(endpointObservation) + } + + return endpointQueries +} diff --git a/qos/framework/jsonrpc/observations_endpoint.go b/qos/framework/jsonrpc/observations_endpoint.go new file mode 100644 index 000000000..cc67661b0 --- /dev/null +++ b/qos/framework/jsonrpc/observations_endpoint.go @@ -0,0 +1,20 @@ +package jsonrpc + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +func (eq *endpointQuery) buildObservations() *qosobservations.EndpointObservation { + return &qosobservations.EndpointObservation{ + EndpointAddr: string(eq.endpointAddr), + EndpointQueryResult: result.buildObservation(), + } +} + +func extractEndpointQueryFromObservation(observation *qosobservations.EndpointObservation) *endpointQuery { + return &endpointQuery { + endpointAddr: observation.GetEndpointAddr(), + // Single result item extracted from this endpoint query. + result: extractEndpointQueryResultFromObservation(observation.GetResult()), + } +} diff --git a/qos/framework/jsonrpc/observations_endpoint_error.go b/qos/framework/jsonrpc/observations_endpoint_error.go new file mode 100644 index 000000000..9a5c06634 --- /dev/null +++ b/qos/framework/jsonrpc/observations_endpoint_error.go @@ -0,0 +1,124 @@ +package framework + +import ( + "time" + + "google.golang.org/protobuf/types/known/timestamppb" + + observations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// buildObservation converts an EndpointError to an observations.EndpointError +func (ee *EndpointError) buildObservation() *observations.EndpointError { + if ee == nil { + return nil + } + + observationError := &observations.EndpointError{ + Description: ee.Description, + ErrorKind: translateToObservationErrorKind(ee.ErrorKind), + } + + // Include sanction information if available + if ee.RecommendedSanction != nil { + observationError.Sanction = &observations.Sanction{ + Reason: ee.Description, + Type: observations.SanctionType_SANCTION_TYPE_TEMPORARY, + } + + // Convert expiry timestamp if available + if !ee.RecommendedSanction.Duration.IsZero() { + // Convert Go time.Duration to proto timestamp + observationError.Sanction.ExpiryTimestamp = timestampProto(time.Now().Add(ee.RecommendedSanction.Duration)) + } + } + + return observationError +} + +// extractEndpointErrorFromObservation extracts an EndpointError from an observations.EndpointError +func extractEndpointErrorFromObservation(obsError *observations.EndpointError) *EndpointError { + if obsError == nil { + return nil + } + + err := &EndpointError{ + Description: obsError.Description, + ErrorKind: translateFromObservationErrorKind(obsError.ErrorKind), + } + + // Include sanction information if available + if obsError.Sanction != nil { + err.RecommendedSanction = &Sanction{} + + // Convert sanction expiry timestamp to Duration + if obsError.Sanction.ExpiryTimestamp != nil { + sanctionExpiry := timeFromProto(obsError.Sanction.ExpiryTimestamp) + err.RecommendedSanction.Duration = sanctionExpiry.Sub(time.Now()) + } + } + + return err +} + +// TODO_IN_THIS_PR: verify errorKind conversion to/from proto. +// +// DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. +func translateToObservationErrorKind(errKind EndpointErrorKind) observations.ErrorKind { + switch errKind { + case EndpointErrorKindResponseMalformed: + return observations.ErrorKind_ERROR_KIND_RESPONSE_MALFORMED + case EndpointErrorKindResponseUnexpectedError: + return observations.ErrorKind_ERROR_KIND_RESPONSE_UNEXPECTED_ERROR + case EndpointErrorKindResponseInvalidValue: + return observations.ErrorKind_ERROR_KIND_RESPONSE_INVALID_VALUE + case EndpointErrorKindRequestTimedOut: + return observations.ErrorKind_ERROR_KIND_REQUEST_TIMED_OUT + case EndpointErrorKindInsufficientFunds: + return observations.ErrorKind_ERROR_KIND_INSUFFICIENT_FUNDS + case EndpointErrorKindRateLimited: + return observations.ErrorKind_ERROR_KIND_RATE_LIMITED + case EndpointErrorKindInternalError: + return observations.ErrorKind_ERROR_KIND_INTERNAL_ERROR + default: + return observations.ErrorKind_ERROR_KIND_UNSPECIFIED + } +} + +// DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. +func translateFromObservationErrorKind(errKind observations.ErrorKind) EndpointErrorKind { + switch errKind { + case observations.ErrorKind_ERROR_KIND_RESPONSE_MALFORMED: + return EndpointErrorKindResponseMalformed + case observations.ErrorKind_ERROR_KIND_RESPONSE_UNEXPECTED_ERROR: + return EndpointErrorKindResponseUnexpectedError + case observations.ErrorKind_ERROR_KIND_RESPONSE_INVALID_VALUE: + return EndpointErrorKindResponseInvalidValue + case observations.ErrorKind_ERROR_KIND_REQUEST_TIMED_OUT: + return EndpointErrorKindRequestTimedOut + case observations.ErrorKind_ERROR_KIND_INSUFFICIENT_FUNDS: + return EndpointErrorKindInsufficientFunds + case observations.ErrorKind_ERROR_KIND_RATE_LIMITED: + return EndpointErrorKindRateLimited + case observations.ErrorKind_ERROR_KIND_INTERNAL_ERROR: + return EndpointErrorKindInternalError + default: + return EndpointErrorKindUnspecified + } +} + +// Helper functions for proto timestamp conversion +func timestampProto(t time.Time) *timestamppb.Timestamp { + if t.IsZero() { + return nil + } + return timestamppb.New(t) +} + +func timeFromProto(ts *timestamppb.Timestamp) time.Time { + if ts == nil { + return time.Time{} + } + return ts.AsTime() +} diff --git a/qos/framework/jsonrpc/observations_endpoint_result.go b/qos/framework/jsonrpc/observations_endpoint_result.go index e198f2250..89cdb5f8c 100644 --- a/qos/framework/jsonrpc/observations_endpoint_result.go +++ b/qos/framework/jsonrpc/observations_endpoint_result.go @@ -1,117 +1,80 @@ package framework import ( - "github.com/buildwithgrove/path/observation/qos/jsonrpc" - jsonrpcobservations "github.com/buildwithgrove/path/observation/qos/framework" + observations "github.com/buildwithgrove/path/observation/qos/framework" "github.com/buildwithgrove/path/qos/jsonrpc" + "github.com/buildwithgrove/path/protocol" ) -// buildObservations converts an EndpointQueryResult to jsonrpc.Observations -// for reporting metrics and analysis. -func (eqr *EndpointQueryResult) buildObservations() *observations.EndpointQueryResult { - // Create the endpoint attributes map for the protobuf message - protoAttributes := make(map[string]*observations.EndpointAttribute) +// buildObservation converts an EndpointQueryResult to observations.EndpointQueryResult +// Used for reporting metrics. +func (eqr *EndpointQueryResult) buildObservation() *observations.EndpointQueryResult { + if eqr == nil { + return nil + } - // Convert each EndpointAttribute to its protobuf representation - for key, attr := range eqr.EndpointAttributes { - protoAttr := &observations.EndpointAttribute{} - - // Convert the value based on its type (string or int) - if strVal, ok := attr.GetStringValue(); ok { - protoAttr.Value = &observations.EndpointAttribute_StringValue{ - StringValue: strVal, - } - } else if intVal, ok := attr.GetIntValue(); ok { - protoAttr.Value = &observations.EndpointAttribute_IntValue{ - IntValue: int32(intVal), - } - } - - // Add error information if present - if attr.err != nil { - protoAttr.Error = &observations.EndpointAttributeError{ - ErrorKind: observations.EndpointErrorKind(attr.err.kind), - Description: attr.err.Description, - } - - // Add sanction information if present - if attr.err.RecommendedSanction != nil { - protoAttr.Error.RecommendedSanction = &observations.Sanction{ - Type: observations.SanctionType(attr.err.RecommendedSanction.Type), - Reason: attr.err.RecommendedSanction.Reason, - } - - // Convert expiry time if it's not zero - if !attr.err.RecommendedSanction.ExpiryTime.IsZero() { - ts, _ := ptypes.TimestampProto(attr.err.RecommendedSanction.ExpiryTime) - protoAttr.Error.RecommendedSanction.ExpiryTimestamp = ts - } - } - } - - protoAttributes[key] = protoAttr + // Create the observation result structure + observationResult := &observations.EndpointQueryResult{ + StringValues: make(map[string]string), + IntValues: make(map[string]int64), } - - // Create and return the EndpointQueryResult proto - return &observations.EndpointQueryResult{ - EndpointAttributes: protoAttributes, + + // Copy string values + for key, value := range eqr.StringValues { + observationResult.StringValues[key] = value + } + + // Copy int values + for key, value := range eqr.IntValues { + observationResult.IntValues[key] = int64(value) + } + + // Convert error information if available + if eqr.Error != nil { + observationResult.Error = eqr.Error.buildObservation() + } + + // Set expiry time + if !eqr.ExpiryTime.IsZero() { + observationResult.ExpiryTime = timestampProto(eqr.ExpiryTime) + } + + // Set HTTP response code if available from client response + if eqr.clientResponse != nil && eqr.clientResponse.HTTPCode != 0 { + observationResult.ClientHttpResponse = int32(eqr.clientResponse.HTTPCode) } + + return observationResult } -// extractEndpointAttributes converts protobuf Observations to []*EndpointQueryResult -// This allows the framework to recreate EndpointQueryResults from serialized observations -func extractEndpointQueryResults(observations *observations.Observations) []*EndpointQueryResult { - results := make([]*EndpointQueryResult, 0, len(observations.EndpointObservations)) - - // Extract data from each endpoint observation - for _, observation := range observations.EndpointObservations { - // Create a new EndpointQueryResult - result := &EndpointQueryResult{ - EndpointAttributes: make(map[string]jsonrpc.EndpointAttribute), - } - - // Convert protobuf attributes to EndpointAttribute objects - for key, protoAttr := range observation.Result.EndpointAttributes { - attr := jsonrpc.EndpointAttribute{} - - // Convert value based on type - switch v := protoAttr.Value.(type) { - case *observations.EndpointAttribute_StringValue: - strVal := v.StringValue - attr.stringValue = &strVal - case *observations.EndpointAttribute_IntValue: - intVal := int(v.IntValue) - attr.intValue = &intVal - } - - // Convert error information if present - if protoAttr.Error != nil { - attr.err = &jsonrpc.EndpointAttributeError{ - Description: protoAttr.Error.Description, - kind: jsonrpc.endpointErrorKind(protoAttr.Error.ErrorKind), - } - - // Convert sanction information if present - if protoAttr.Error.RecommendedSanction != nil { - attr.err.RecommendedSanction = &jsonrpc.Sanction{ - Type: jsonrpc.SanctionType(protoAttr.Error.RecommendedSanction.Type), - Reason: protoAttr.Error.RecommendedSanction.Reason, - } - - // Convert expiry timestamp if present - if protoAttr.Error.RecommendedSanction.ExpiryTimestamp != nil { - t, _ := ptypes.Timestamp(protoAttr.Error.RecommendedSanction.ExpiryTimestamp) - attr.err.RecommendedSanction.ExpiryTime = t - } - } - } - - // Add attribute to the result - result.EndpointAttributes[key] = attr - } - - results = append(results, result) +// extractEndpointQueryResultFromObservation extracts a single EndpointQueryResult from an observation's EndpointQueryResult +// Ignores the HTTP stauts code: it is only required when responding to the client. +func extractEndpointQueryResultFromObservation(obsResult *observations.EndpointQueryResult) *EndpointQueryResult { + if obsResult == nil { + return nil } - return results + // Create a new result and populate it from the observation + result := &EndpointQueryResult{ + StringValues: make(map[string]string), + IntValues: make(map[string]int), + ExpiryTime: timeFromProto(obsResult.ExpiryTime), + } + + // Copy string values + for key, value := range obsResult.StringValues { + result.StringValues[key] = value + } + + // Copy int values + for key, value := range obsResult.IntValues { + result.IntValues[key] = int(value) + } + + // Convert error information + if obsResult.Error != nil { + result.Error = extractEndpointErrorFromObservation(obsResult.Error) + } + + return result } diff --git a/qos/framework/jsonrpc/observations_endpoints.go b/qos/framework/jsonrpc/observations_endpoints.go deleted file mode 100644 index 660a193ef..000000000 --- a/qos/framework/jsonrpc/observations_endpoints.go +++ /dev/null @@ -1,8 +0,0 @@ -package jsonrpc - -func (eq *endpointQuery) buildObservations() *qosobservations.EndpointObservation { -} - -func (eqr *EndpointQueryResult) buildObservations() *observations.EndpointQueryResult - - diff --git a/qos/framework/jsonrpc/observations_request.go b/qos/framework/jsonrpc/observations_request.go index 3dd749d6b..c11fc0f98 100644 --- a/qos/framework/jsonrpc/observations_request.go +++ b/qos/framework/jsonrpc/observations_request.go @@ -19,15 +19,18 @@ func (rd *requestDetails) buildObservations() *qosobservations.RequestObservatio } // build request failure observation, if the request parsing failed. - var validationErrorObs *qosobservations.ValidationError + var errorObs *qosobservations.RequestError if rd.requestError != nil { - validationErrorObs = rd.requestError.buildObservations() + errorObs = rd.requestError.buildObservations() } return &qosobservations.RequestObservation { // Only set if validation was successful JsonrpcRequest: jsonrpcRequestObs, - // Only set if validation failed - ValidationError: validationErrorObs, + // Only set if the request failed for any reason. + // A valid request can still fail due to a gateway internal error, e.g.: + // - error reading HTTP request's body. + // - protocol-level error: e.g. selected endpoint timed out. + RequestError: errorObs, } } diff --git a/qos/framework/jsonrpc/observations_request_error.go b/qos/framework/jsonrpc/observations_request_error.go index 2a2b45d58..4d722c7ef 100644 --- a/qos/framework/jsonrpc/observations_request_error.go +++ b/qos/framework/jsonrpc/observations_request_error.go @@ -8,18 +8,20 @@ func (re *requestError) buildObservation() *qosobservations.ValidationError { HttpStatusCode: re.jsonrpcErrorResponse.GetRecommendedHTTPStatusCode(), } } - -func translateToRequestValidationError(errKind requestErrorKind) qosobservations.RequestValidationErrorKind { +// DEV_NOTE: you MUST update this function when changing the set of request errors. +func translateToRequestError(errKind requestErrorKind) qosobservations.RequestErrorKind { switch errKind { - case requestErrKindInternalErrReadyHTTPBody: - return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_BODY_READ_FAILURE + case requestErrKindInternalReadyHTTPBody: + return RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE + case requestErrKindInternalProtocolError: + return RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR case requestErrKindJSONRPCParsingErr: - return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_UNMARSHALING_FAILURE + return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE requestErrKindJSONRPCInvalidVersion - return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_INVALID_VERSION + return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION case requestErrKindJSONRPCMissingMethod: - return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_MISSING_METHOD + return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD default: - return RequestValidationErrorKind_REQUEST_VALIDATION_ERROR_UNSPECIFIED + return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNSPECIFIED } } diff --git a/qos/framework/jsonrpc/request_journal.go b/qos/framework/jsonrpc/request_journal.go index 099da3651..f67168a70 100644 --- a/qos/framework/jsonrpc/request_journal.go +++ b/qos/framework/jsonrpc/request_journal.go @@ -21,7 +21,7 @@ type requestJournal struct { requestDetails *requestDetails // All endpoint interactions that occurred during processing. - // These are all expected to be processed: i.e. have a non-nil result pointer and a client JSONRPC response. + // These are expected to be processed: i.e. have a non-nil result pointer and a client JSONRPC response. processedEndpointQueries []*endpointQuery } From 09bdbff972bef4c35e17ca0e0cdceb9e0b237f18 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 9 Apr 2025 15:49:01 -0400 Subject: [PATCH 11/26] Drop obsolete proto files --- makefiles/proto.mk | 3 +- observation/qos/cometbft.pb.go | 561 ------- observation/qos/evm.pb.go | 1319 ----------------- observation/qos/framework/endpoint.pb.go | 169 +++ .../qos/framework/endpoint_error.pb.go | 257 ++++ .../qos/framework/endpoint_query_result.pb.go | 234 +++ .../qos/framework/endpoint_sanction.pb.go | 237 +++ observation/qos/{ => framework}/jsonrpc.pb.go | 130 +- observation/qos/framework/observations.pb.go | 189 +++ observation/qos/framework/request.pb.go | 370 +++++ observation/qos/observations.pb.go | 115 +- observation/qos/solana.pb.go | 544 ------- proto/path/qos/cometbft.proto | 70 - proto/path/qos/evm.proto | 230 --- proto/path/qos/jsonrpc.proto | 45 - proto/path/qos/observations.proto | 21 +- proto/path/qos/solana.proto | 59 - qos/framework/jsonrpc/context_state_update.go | 76 +- qos/framework/jsonrpc/endpoint_store.go | 11 + .../example_evm/state_update_archival.go | 54 + qos/framework/jsonrpc/framework.go | 2 +- qos/framework/jsonrpc/qos.go | 22 +- qos/framework/jsonrpc/service_state.go | 192 --- qos/framework/jsonrpc/state.go | 86 ++ qos/framework/jsonrpc/state_parameter.go | 56 + qos/framework/jsonrpc/state_update.go | 6 + 26 files changed, 1837 insertions(+), 3221 deletions(-) delete mode 100644 observation/qos/cometbft.pb.go delete mode 100644 observation/qos/evm.pb.go create mode 100644 observation/qos/framework/endpoint.pb.go create mode 100644 observation/qos/framework/endpoint_error.pb.go create mode 100644 observation/qos/framework/endpoint_query_result.pb.go create mode 100644 observation/qos/framework/endpoint_sanction.pb.go rename observation/qos/{ => framework}/jsonrpc.pb.go (56%) create mode 100644 observation/qos/framework/observations.pb.go create mode 100644 observation/qos/framework/request.pb.go delete mode 100644 observation/qos/solana.pb.go delete mode 100644 proto/path/qos/cometbft.proto delete mode 100644 proto/path/qos/evm.proto delete mode 100644 proto/path/qos/jsonrpc.proto delete mode 100644 proto/path/qos/solana.proto create mode 100644 qos/framework/jsonrpc/example_evm/state_update_archival.go delete mode 100644 qos/framework/jsonrpc/service_state.go create mode 100644 qos/framework/jsonrpc/state.go create mode 100644 qos/framework/jsonrpc/state_parameter.go create mode 100644 qos/framework/jsonrpc/state_update.go diff --git a/makefiles/proto.mk b/makefiles/proto.mk index 430087c11..602181b31 100644 --- a/makefiles/proto.mk +++ b/makefiles/proto.mk @@ -16,7 +16,8 @@ proto_gen_observation: ## Generate observation protobuf artifacts ./proto/path/*.proto \ ./proto/path/metadata/*.proto \ ./proto/path/protocol/*.proto \ - ./proto/path/qos/*.proto + ./proto/path/qos/*.proto \ + ./proto/path/qos/framework/*.proto .PHONY: proto_clean proto_clean: ## Delete existing protobuf artifacts (i.e. .pb.go files) diff --git a/observation/qos/cometbft.pb.go b/observation/qos/cometbft.pb.go deleted file mode 100644 index 415799a1b..000000000 --- a/observation/qos/cometbft.pb.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.28.3 -// source: path/qos/cometbft.proto - -package qos - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// CometBFTRequestObservations captures all observations made while serving a single CometBFT blockchain service request. -type CometBFTRequestObservations struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The CometBFT blockchain service's route request, including all params - RouteRequest string `protobuf:"bytes,1,opt,name=route_request,json=routeRequest,proto3" json:"route_request,omitempty"` - // CometBFT-specific observations from endpoint(s) that responded to the service request. - // Multiple observations may occur when: - // * Original endpoint fails - // * Request is sent to additional endpoints for data collection - EndpointObservations []*CometBFTEndpointObservation `protobuf:"bytes,2,rep,name=endpoint_observations,json=endpointObservations,proto3" json:"endpoint_observations,omitempty"` -} - -func (x *CometBFTRequestObservations) Reset() { - *x = CometBFTRequestObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_cometbft_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CometBFTRequestObservations) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CometBFTRequestObservations) ProtoMessage() {} - -func (x *CometBFTRequestObservations) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_cometbft_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CometBFTRequestObservations.ProtoReflect.Descriptor instead. -func (*CometBFTRequestObservations) Descriptor() ([]byte, []int) { - return file_path_qos_cometbft_proto_rawDescGZIP(), []int{0} -} - -func (x *CometBFTRequestObservations) GetRouteRequest() string { - if x != nil { - return x.RouteRequest - } - return "" -} - -func (x *CometBFTRequestObservations) GetEndpointObservations() []*CometBFTEndpointObservation { - if x != nil { - return x.EndpointObservations - } - return nil -} - -// CometBFTEndpointObservation stores a single observation from an endpoint servicing the protocol response. -// Example: A Pocket node on Shannon backed by an Ethereum data node servicing an `eth_getBlockNumber` request. -type CometBFTEndpointObservation struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Address of the endpoint handling the request (e.g., onchain address of a Pocket Morse/Shannon node) - EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` - // Details of the response received from the endpoint - // - // Types that are assignable to ResponseObservation: - // - // *CometBFTEndpointObservation_HealthResponse - // *CometBFTEndpointObservation_StatusResponse - // *CometBFTEndpointObservation_UnrecognizedResponse - ResponseObservation isCometBFTEndpointObservation_ResponseObservation `protobuf_oneof:"response_observation"` -} - -func (x *CometBFTEndpointObservation) Reset() { - *x = CometBFTEndpointObservation{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_cometbft_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CometBFTEndpointObservation) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CometBFTEndpointObservation) ProtoMessage() {} - -func (x *CometBFTEndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_cometbft_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CometBFTEndpointObservation.ProtoReflect.Descriptor instead. -func (*CometBFTEndpointObservation) Descriptor() ([]byte, []int) { - return file_path_qos_cometbft_proto_rawDescGZIP(), []int{1} -} - -func (x *CometBFTEndpointObservation) GetEndpointAddr() string { - if x != nil { - return x.EndpointAddr - } - return "" -} - -func (m *CometBFTEndpointObservation) GetResponseObservation() isCometBFTEndpointObservation_ResponseObservation { - if m != nil { - return m.ResponseObservation - } - return nil -} - -func (x *CometBFTEndpointObservation) GetHealthResponse() *CometBFTHealthResponse { - if x, ok := x.GetResponseObservation().(*CometBFTEndpointObservation_HealthResponse); ok { - return x.HealthResponse - } - return nil -} - -func (x *CometBFTEndpointObservation) GetStatusResponse() *CometBFTStatusResponse { - if x, ok := x.GetResponseObservation().(*CometBFTEndpointObservation_StatusResponse); ok { - return x.StatusResponse - } - return nil -} - -func (x *CometBFTEndpointObservation) GetUnrecognizedResponse() *CometBFTUnrecognizedResponse { - if x, ok := x.GetResponseObservation().(*CometBFTEndpointObservation_UnrecognizedResponse); ok { - return x.UnrecognizedResponse - } - return nil -} - -type isCometBFTEndpointObservation_ResponseObservation interface { - isCometBFTEndpointObservation_ResponseObservation() -} - -type CometBFTEndpointObservation_HealthResponse struct { - // Response to `/health` request - HealthResponse *CometBFTHealthResponse `protobuf:"bytes,2,opt,name=health_response,json=healthResponse,proto3,oneof"` -} - -type CometBFTEndpointObservation_StatusResponse struct { - // Response to `/status` request - StatusResponse *CometBFTStatusResponse `protobuf:"bytes,3,opt,name=status_response,json=statusResponse,proto3,oneof"` -} - -type CometBFTEndpointObservation_UnrecognizedResponse struct { - // Responses not used in endpoint validation - UnrecognizedResponse *CometBFTUnrecognizedResponse `protobuf:"bytes,4,opt,name=unrecognized_response,json=unrecognizedResponse,proto3,oneof"` -} - -func (*CometBFTEndpointObservation_HealthResponse) isCometBFTEndpointObservation_ResponseObservation() { -} - -func (*CometBFTEndpointObservation_StatusResponse) isCometBFTEndpointObservation_ResponseObservation() { -} - -func (*CometBFTEndpointObservation_UnrecognizedResponse) isCometBFTEndpointObservation_ResponseObservation() { -} - -// CometBFTHealthResponse stores the response to a `health` request -// Reference: https://docs.cometbft.com/v1.0/spec/rpc/#health -type CometBFTHealthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - HealthStatusResponse bool `protobuf:"varint,1,opt,name=health_status_response,json=healthStatusResponse,proto3" json:"health_status_response,omitempty"` -} - -func (x *CometBFTHealthResponse) Reset() { - *x = CometBFTHealthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_cometbft_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CometBFTHealthResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CometBFTHealthResponse) ProtoMessage() {} - -func (x *CometBFTHealthResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_cometbft_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CometBFTHealthResponse.ProtoReflect.Descriptor instead. -func (*CometBFTHealthResponse) Descriptor() ([]byte, []int) { - return file_path_qos_cometbft_proto_rawDescGZIP(), []int{2} -} - -func (x *CometBFTHealthResponse) GetHealthStatusResponse() bool { - if x != nil { - return x.HealthStatusResponse - } - return false -} - -// CometBFTStatusResponse stores the latest block number from a `/status` request -// Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status -type CometBFTStatusResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Chain ID of the endpoint. Comes from the `NodeInfo.Network` field in the `/status` response. - // Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status - ChainIdResponse string `protobuf:"bytes,1,opt,name=chain_id_response,json=chainIdResponse,proto3" json:"chain_id_response,omitempty"` - // Indicates if the endpoint is catching up to the network. - // Comes from the `SyncInfo.CatchingUp` field in the `/status` response. - // Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status - CatchingUpResponse bool `protobuf:"varint,2,opt,name=catching_up_response,json=catchingUpResponse,proto3" json:"catching_up_response,omitempty"` - // Latest block height of the endpoint. - // Comes from the `SyncInfo.LatestBlockHeight` field in the `/status` response. - // Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status - LatestBlockHeightResponse string `protobuf:"bytes,3,opt,name=latest_block_height_response,json=latestBlockHeightResponse,proto3" json:"latest_block_height_response,omitempty"` -} - -func (x *CometBFTStatusResponse) Reset() { - *x = CometBFTStatusResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_cometbft_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CometBFTStatusResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CometBFTStatusResponse) ProtoMessage() {} - -func (x *CometBFTStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_cometbft_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CometBFTStatusResponse.ProtoReflect.Descriptor instead. -func (*CometBFTStatusResponse) Descriptor() ([]byte, []int) { - return file_path_qos_cometbft_proto_rawDescGZIP(), []int{3} -} - -func (x *CometBFTStatusResponse) GetChainIdResponse() string { - if x != nil { - return x.ChainIdResponse - } - return "" -} - -func (x *CometBFTStatusResponse) GetCatchingUpResponse() bool { - if x != nil { - return x.CatchingUpResponse - } - return false -} - -func (x *CometBFTStatusResponse) GetLatestBlockHeightResponse() string { - if x != nil { - return x.LatestBlockHeightResponse - } - return "" -} - -// CometBFTUnrecognizedResponse handles requests with methods ignored by state update -// and endpoint validation -type CometBFTUnrecognizedResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - JsonrpcResponse *JsonRpcResponse `protobuf:"bytes,1,opt,name=jsonrpc_response,json=jsonrpcResponse,proto3" json:"jsonrpc_response,omitempty"` -} - -func (x *CometBFTUnrecognizedResponse) Reset() { - *x = CometBFTUnrecognizedResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_cometbft_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CometBFTUnrecognizedResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CometBFTUnrecognizedResponse) ProtoMessage() {} - -func (x *CometBFTUnrecognizedResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_cometbft_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CometBFTUnrecognizedResponse.ProtoReflect.Descriptor instead. -func (*CometBFTUnrecognizedResponse) Descriptor() ([]byte, []int) { - return file_path_qos_cometbft_proto_rawDescGZIP(), []int{4} -} - -func (x *CometBFTUnrecognizedResponse) GetJsonrpcResponse() *JsonRpcResponse { - if x != nil { - return x.JsonrpcResponse - } - return nil -} - -var File_path_qos_cometbft_proto protoreflect.FileDescriptor - -var file_path_qos_cometbft_proto_rawDesc = []byte{ - 0x0a, 0x17, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x65, 0x74, - 0x62, 0x66, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x61, 0x74, 0x68, 0x2e, - 0x71, 0x6f, 0x73, 0x1a, 0x16, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x6a, 0x73, - 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9e, 0x01, 0x0a, 0x1b, - 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x5a, 0x0a, 0x15, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x65, 0x74, - 0x42, 0x46, 0x54, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xd3, 0x02, 0x0a, - 0x1b, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x12, 0x4b, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0e, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, - 0x6f, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x15, 0x75, - 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x55, 0x6e, - 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x48, 0x00, 0x52, 0x14, 0x75, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x16, 0x0a, 0x14, 0x72, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x4e, 0x0a, 0x16, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x16, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x72, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0xb7, 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, - 0x11, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x61, 0x74, - 0x63, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x63, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, - 0x67, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x6c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x19, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x64, 0x0a, 0x1c, - 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x55, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, - 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x10, - 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, - 0x73, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x52, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, - 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_path_qos_cometbft_proto_rawDescOnce sync.Once - file_path_qos_cometbft_proto_rawDescData = file_path_qos_cometbft_proto_rawDesc -) - -func file_path_qos_cometbft_proto_rawDescGZIP() []byte { - file_path_qos_cometbft_proto_rawDescOnce.Do(func() { - file_path_qos_cometbft_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_cometbft_proto_rawDescData) - }) - return file_path_qos_cometbft_proto_rawDescData -} - -var file_path_qos_cometbft_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_path_qos_cometbft_proto_goTypes = []any{ - (*CometBFTRequestObservations)(nil), // 0: path.qos.CometBFTRequestObservations - (*CometBFTEndpointObservation)(nil), // 1: path.qos.CometBFTEndpointObservation - (*CometBFTHealthResponse)(nil), // 2: path.qos.CometBFTHealthResponse - (*CometBFTStatusResponse)(nil), // 3: path.qos.CometBFTStatusResponse - (*CometBFTUnrecognizedResponse)(nil), // 4: path.qos.CometBFTUnrecognizedResponse - (*JsonRpcResponse)(nil), // 5: path.qos.JsonRpcResponse -} -var file_path_qos_cometbft_proto_depIdxs = []int32{ - 1, // 0: path.qos.CometBFTRequestObservations.endpoint_observations:type_name -> path.qos.CometBFTEndpointObservation - 2, // 1: path.qos.CometBFTEndpointObservation.health_response:type_name -> path.qos.CometBFTHealthResponse - 3, // 2: path.qos.CometBFTEndpointObservation.status_response:type_name -> path.qos.CometBFTStatusResponse - 4, // 3: path.qos.CometBFTEndpointObservation.unrecognized_response:type_name -> path.qos.CometBFTUnrecognizedResponse - 5, // 4: path.qos.CometBFTUnrecognizedResponse.jsonrpc_response:type_name -> path.qos.JsonRpcResponse - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name -} - -func init() { file_path_qos_cometbft_proto_init() } -func file_path_qos_cometbft_proto_init() { - if File_path_qos_cometbft_proto != nil { - return - } - file_path_qos_jsonrpc_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_cometbft_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*CometBFTRequestObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_cometbft_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*CometBFTEndpointObservation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_cometbft_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*CometBFTHealthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_cometbft_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*CometBFTStatusResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_cometbft_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*CometBFTUnrecognizedResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_path_qos_cometbft_proto_msgTypes[1].OneofWrappers = []any{ - (*CometBFTEndpointObservation_HealthResponse)(nil), - (*CometBFTEndpointObservation_StatusResponse)(nil), - (*CometBFTEndpointObservation_UnrecognizedResponse)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_path_qos_cometbft_proto_rawDesc, - NumEnums: 0, - NumMessages: 5, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_path_qos_cometbft_proto_goTypes, - DependencyIndexes: file_path_qos_cometbft_proto_depIdxs, - MessageInfos: file_path_qos_cometbft_proto_msgTypes, - }.Build() - File_path_qos_cometbft_proto = out.File - file_path_qos_cometbft_proto_rawDesc = nil - file_path_qos_cometbft_proto_goTypes = nil - file_path_qos_cometbft_proto_depIdxs = nil -} diff --git a/observation/qos/evm.pb.go b/observation/qos/evm.pb.go deleted file mode 100644 index d6b658079..000000000 --- a/observation/qos/evm.pb.go +++ /dev/null @@ -1,1319 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.28.3 -// source: path/qos/evm.proto - -// TODO_MVP(@adshmh): Address linter warning on all the .proto files: -// Package name "path.qos" should be suffixed with a correctly formed version, such as "path.qos.v1" -// -// Buf used as linter for proto files: -// https://buf.build/docs/lint/overview/ - -package qos - -import ( - reflect "reflect" - sync "sync" - - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - - _ "github.com/buildwithgrove/path/observation/metadata" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// EVMRequestValidationError enumerates possible causes for EVM request rejection: -// Invalid request types (as of PR #186): -// 1. Internal server error while reading the HTTP request body -// 2. Unmarshal error when parsing request into the expected format -type EVMRequestValidationError int32 - -const ( - EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_UNSPECIFIED EVMRequestValidationError = 0 - EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_HTTP_BODY_READ_FAILURE EVMRequestValidationError = 1 - EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE EVMRequestValidationError = 2 -) - -// Enum value maps for EVMRequestValidationError. -var ( - EVMRequestValidationError_name = map[int32]string{ - 0: "EVM_REQUEST_VALIDATION_ERROR_UNSPECIFIED", - 1: "EVM_REQUEST_VALIDATION_ERROR_HTTP_BODY_READ_FAILURE", - 2: "EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE", - } - EVMRequestValidationError_value = map[string]int32{ - "EVM_REQUEST_VALIDATION_ERROR_UNSPECIFIED": 0, - "EVM_REQUEST_VALIDATION_ERROR_HTTP_BODY_READ_FAILURE": 1, - "EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE": 2, - } -) - -func (x EVMRequestValidationError) Enum() *EVMRequestValidationError { - p := new(EVMRequestValidationError) - *p = x - return p -} - -func (x EVMRequestValidationError) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (EVMRequestValidationError) Descriptor() protoreflect.EnumDescriptor { - return file_path_qos_evm_proto_enumTypes[0].Descriptor() -} - -func (EVMRequestValidationError) Type() protoreflect.EnumType { - return &file_path_qos_evm_proto_enumTypes[0] -} - -func (x EVMRequestValidationError) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use EVMRequestValidationError.Descriptor instead. -func (EVMRequestValidationError) EnumDescriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{0} -} - -// TODO_DOCUMENT(@adshmh): Create a design document for the feature described below. -// TODO_MVP(@adshmh): Add EVMUserErrorType enum -// -// Purpose: Distinguish between endpoint technical failures and user input errors -// -// Background: -// - Currently we only track endpoint/technical failures -// - Need to identify when request seems valid but fails due to user input issues (e.g., non-existent hash) -// -// Implementation: -// 1. Create new EVMUserErrorType enum with categories like RESOURCE_NOT_FOUND, INVALID_PARAMETER -// 2. Add user_error field to appropriate response types -// 3. Update HTTP status code selection logic to consider user errors -// -// Benefits: -// - More accurate error reporting to clients -// - Appropriate HTTP status codes (e.g., 404 vs 500) -// - Better client debugging experience -// -// EVMResponseValidationError defines why an endpoint response was rejected. -// Current invalid response types (as of PR #186): -// 1. EmptyResponse - endpoint returned no data -// 2. UnmarshalErr - response failed to parse into expected format -// 3. NoResponse - no responses recorded by the QoS service: probably caused by protocol-level errors -type EVMResponseValidationError int32 - -const ( - EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED EVMResponseValidationError = 0 - EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_EMPTY EVMResponseValidationError = 1 // Response with no data. - EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNMARSHAL EVMResponseValidationError = 2 // Response parsing failed - EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_NO_RESPONSE EVMResponseValidationError = 3 // No response received from any endpoint -) - -// Enum value maps for EVMResponseValidationError. -var ( - EVMResponseValidationError_name = map[int32]string{ - 0: "EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED", - 1: "EVM_RESPONSE_VALIDATION_ERROR_EMPTY", - 2: "EVM_RESPONSE_VALIDATION_ERROR_UNMARSHAL", - 3: "EVM_RESPONSE_VALIDATION_ERROR_NO_RESPONSE", - } - EVMResponseValidationError_value = map[string]int32{ - "EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED": 0, - "EVM_RESPONSE_VALIDATION_ERROR_EMPTY": 1, - "EVM_RESPONSE_VALIDATION_ERROR_UNMARSHAL": 2, - "EVM_RESPONSE_VALIDATION_ERROR_NO_RESPONSE": 3, - } -) - -func (x EVMResponseValidationError) Enum() *EVMResponseValidationError { - p := new(EVMResponseValidationError) - *p = x - return p -} - -func (x EVMResponseValidationError) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (EVMResponseValidationError) Descriptor() protoreflect.EnumDescriptor { - return file_path_qos_evm_proto_enumTypes[1].Descriptor() -} - -func (EVMResponseValidationError) Type() protoreflect.EnumType { - return &file_path_qos_evm_proto_enumTypes[1] -} - -func (x EVMResponseValidationError) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use EVMResponseValidationError.Descriptor instead. -func (EVMResponseValidationError) EnumDescriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{1} -} - -// EVMRequestObservations captures all observations made while serving a single EVM blockchain service request. -type EVMRequestObservations struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // chain_id is the blockchain identifier is the blockchain identifier for the evm QoS implementation. - // This is preset by the processor and not determined by the request. - // Expected as the `Result` field in eth_chainId responses. - ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` - // If set with one of the validation failure types: - // - Indicates the request failed validation - // - Contains details about the specific failure type - // - The HTTP status code in the selected failure type overrides any status codes from - // endpoint observations and should be returned to the client - // - // If this oneof is NOT set: - // - The request passed validation - // - The HTTP status code from the most recent endpoint observation should be used instead - // - // Note: If there is an error reading the HTTP request, there will be no jsonrpc_request. - // - // Types that are assignable to RequestValidationFailure: - // - // *EVMRequestObservations_EvmHttpBodyReadFailure - // *EVMRequestObservations_EvmRequestUnmarshalingFailure - RequestValidationFailure isEVMRequestObservations_RequestValidationFailure `protobuf_oneof:"request_validation_failure"` - // The EVM blockchain service's JSON-RPC request. - // This field will be populated only if request validation succeeds. - // TODO_TECHDEBT: Assumes EVM chains only support JSON-RPC. May need refactoring to support other protocols. - JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,4,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3" json:"jsonrpc_request,omitempty"` - // EVM-specific observations from endpoint(s) that responded to the service request. - // Multiple observations may occur when: - // * Original endpoint fails - // * Request is sent to additional endpoints for data collection - // This field will only be populated if request validation succeeds. - EndpointObservations []*EVMEndpointObservation `protobuf:"bytes,5,rep,name=endpoint_observations,json=endpointObservations,proto3" json:"endpoint_observations,omitempty"` -} - -func (x *EVMRequestObservations) Reset() { - *x = EVMRequestObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMRequestObservations) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMRequestObservations) ProtoMessage() {} - -func (x *EVMRequestObservations) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMRequestObservations.ProtoReflect.Descriptor instead. -func (*EVMRequestObservations) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{0} -} - -func (x *EVMRequestObservations) GetChainId() string { - if x != nil { - return x.ChainId - } - return "" -} - -func (m *EVMRequestObservations) GetRequestValidationFailure() isEVMRequestObservations_RequestValidationFailure { - if m != nil { - return m.RequestValidationFailure - } - return nil -} - -func (x *EVMRequestObservations) GetEvmHttpBodyReadFailure() *EVMHTTPBodyReadFailure { - if x, ok := x.GetRequestValidationFailure().(*EVMRequestObservations_EvmHttpBodyReadFailure); ok { - return x.EvmHttpBodyReadFailure - } - return nil -} - -func (x *EVMRequestObservations) GetEvmRequestUnmarshalingFailure() *EVMRequestUnmarshalingFailure { - if x, ok := x.GetRequestValidationFailure().(*EVMRequestObservations_EvmRequestUnmarshalingFailure); ok { - return x.EvmRequestUnmarshalingFailure - } - return nil -} - -func (x *EVMRequestObservations) GetJsonrpcRequest() *JsonRpcRequest { - if x != nil { - return x.JsonrpcRequest - } - return nil -} - -func (x *EVMRequestObservations) GetEndpointObservations() []*EVMEndpointObservation { - if x != nil { - return x.EndpointObservations - } - return nil -} - -type isEVMRequestObservations_RequestValidationFailure interface { - isEVMRequestObservations_RequestValidationFailure() -} - -type EVMRequestObservations_EvmHttpBodyReadFailure struct { - // Indicates a failure to read the HTTP request body - EvmHttpBodyReadFailure *EVMHTTPBodyReadFailure `protobuf:"bytes,2,opt,name=evm_http_body_read_failure,json=evmHttpBodyReadFailure,proto3,oneof"` -} - -type EVMRequestObservations_EvmRequestUnmarshalingFailure struct { - // Indicates a failure to unmarshal/parse the request - EvmRequestUnmarshalingFailure *EVMRequestUnmarshalingFailure `protobuf:"bytes,3,opt,name=evm_request_unmarshaling_failure,json=evmRequestUnmarshalingFailure,proto3,oneof"` -} - -func (*EVMRequestObservations_EvmHttpBodyReadFailure) isEVMRequestObservations_RequestValidationFailure() { -} - -func (*EVMRequestObservations_EvmRequestUnmarshalingFailure) isEVMRequestObservations_RequestValidationFailure() { -} - -// TODO_MVP(@adshmh): Remove HTTP body read validation once QoS interface is updated to receive request payload directly rather than reading from the HTTP request body. -// -// EVMHTTPBodyReadFailure represents a validation failure due to internal server error -// while attempting to read the HTTP request body. -type EVMHTTPBodyReadFailure struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code to return to the client - typically 500 Internal Server Error - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // The specific type of request validation error - ValidationError EVMRequestValidationError `protobuf:"varint,2,opt,name=validation_error,json=validationError,proto3,enum=path.qos.EVMRequestValidationError" json:"validation_error,omitempty"` - // Additional error details if available - ErrorDetails *string `protobuf:"bytes,3,opt,name=error_details,json=errorDetails,proto3,oneof" json:"error_details,omitempty"` -} - -func (x *EVMHTTPBodyReadFailure) Reset() { - *x = EVMHTTPBodyReadFailure{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMHTTPBodyReadFailure) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMHTTPBodyReadFailure) ProtoMessage() {} - -func (x *EVMHTTPBodyReadFailure) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMHTTPBodyReadFailure.ProtoReflect.Descriptor instead. -func (*EVMHTTPBodyReadFailure) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{1} -} - -func (x *EVMHTTPBodyReadFailure) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMHTTPBodyReadFailure) GetValidationError() EVMRequestValidationError { - if x != nil { - return x.ValidationError - } - return EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_UNSPECIFIED -} - -func (x *EVMHTTPBodyReadFailure) GetErrorDetails() string { - if x != nil && x.ErrorDetails != nil { - return *x.ErrorDetails - } - return "" -} - -// EVMRequestUnmarshalingFailure represents a validation failure due to being unable -// to parse the incoming request into the expected format. -type EVMRequestUnmarshalingFailure struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code to return to the client - typically 400 Bad Request - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // The specific type of request validation error - ValidationError EVMRequestValidationError `protobuf:"varint,2,opt,name=validation_error,json=validationError,proto3,enum=path.qos.EVMRequestValidationError" json:"validation_error,omitempty"` - // Additional error details if available - ErrorDetails *string `protobuf:"bytes,3,opt,name=error_details,json=errorDetails,proto3,oneof" json:"error_details,omitempty"` -} - -func (x *EVMRequestUnmarshalingFailure) Reset() { - *x = EVMRequestUnmarshalingFailure{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMRequestUnmarshalingFailure) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMRequestUnmarshalingFailure) ProtoMessage() {} - -func (x *EVMRequestUnmarshalingFailure) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMRequestUnmarshalingFailure.ProtoReflect.Descriptor instead. -func (*EVMRequestUnmarshalingFailure) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{2} -} - -func (x *EVMRequestUnmarshalingFailure) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMRequestUnmarshalingFailure) GetValidationError() EVMRequestValidationError { - if x != nil { - return x.ValidationError - } - return EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_UNSPECIFIED -} - -func (x *EVMRequestUnmarshalingFailure) GetErrorDetails() string { - if x != nil && x.ErrorDetails != nil { - return *x.ErrorDetails - } - return "" -} - -// EVMEndpointObservation stores a single observation from an endpoint servicing the protocol response. -// Example: A Pocket node on Shannon backed by an Ethereum data node servicing an `eth_getBlockNumber` request. -type EVMEndpointObservation struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Address of the endpoint handling the request (e.g., onchain address of a Pocket Morse/Shannon node) - EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` - // Details of the response received from the endpoint - // - // Types that are assignable to ResponseObservation: - // - // *EVMEndpointObservation_ChainIdResponse - // *EVMEndpointObservation_BlockNumberResponse - // *EVMEndpointObservation_UnrecognizedResponse - // *EVMEndpointObservation_EmptyResponse - // *EVMEndpointObservation_NoResponse - ResponseObservation isEVMEndpointObservation_ResponseObservation `protobuf_oneof:"response_observation"` -} - -func (x *EVMEndpointObservation) Reset() { - *x = EVMEndpointObservation{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMEndpointObservation) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMEndpointObservation) ProtoMessage() {} - -func (x *EVMEndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMEndpointObservation.ProtoReflect.Descriptor instead. -func (*EVMEndpointObservation) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{3} -} - -func (x *EVMEndpointObservation) GetEndpointAddr() string { - if x != nil { - return x.EndpointAddr - } - return "" -} - -func (m *EVMEndpointObservation) GetResponseObservation() isEVMEndpointObservation_ResponseObservation { - if m != nil { - return m.ResponseObservation - } - return nil -} - -func (x *EVMEndpointObservation) GetChainIdResponse() *EVMChainIDResponse { - if x, ok := x.GetResponseObservation().(*EVMEndpointObservation_ChainIdResponse); ok { - return x.ChainIdResponse - } - return nil -} - -func (x *EVMEndpointObservation) GetBlockNumberResponse() *EVMBlockNumberResponse { - if x, ok := x.GetResponseObservation().(*EVMEndpointObservation_BlockNumberResponse); ok { - return x.BlockNumberResponse - } - return nil -} - -func (x *EVMEndpointObservation) GetUnrecognizedResponse() *EVMUnrecognizedResponse { - if x, ok := x.GetResponseObservation().(*EVMEndpointObservation_UnrecognizedResponse); ok { - return x.UnrecognizedResponse - } - return nil -} - -func (x *EVMEndpointObservation) GetEmptyResponse() *EVMEmptyResponse { - if x, ok := x.GetResponseObservation().(*EVMEndpointObservation_EmptyResponse); ok { - return x.EmptyResponse - } - return nil -} - -func (x *EVMEndpointObservation) GetNoResponse() *EVMNoResponse { - if x, ok := x.GetResponseObservation().(*EVMEndpointObservation_NoResponse); ok { - return x.NoResponse - } - return nil -} - -type isEVMEndpointObservation_ResponseObservation interface { - isEVMEndpointObservation_ResponseObservation() -} - -type EVMEndpointObservation_ChainIdResponse struct { - // Response to `eth_chainId` request - // Reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_chainid - ChainIdResponse *EVMChainIDResponse `protobuf:"bytes,2,opt,name=chain_id_response,json=chainIdResponse,proto3,oneof"` -} - -type EVMEndpointObservation_BlockNumberResponse struct { - // Response to `eth_blockNumber` request - // References: - // * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber - // * Chain IDs: https://chainlist.org - BlockNumberResponse *EVMBlockNumberResponse `protobuf:"bytes,3,opt,name=block_number_response,json=blockNumberResponse,proto3,oneof"` -} - -type EVMEndpointObservation_UnrecognizedResponse struct { - // Responses not used in endpoint validation (e.g., JSONRPC ID field from `eth_call`) - UnrecognizedResponse *EVMUnrecognizedResponse `protobuf:"bytes,4,opt,name=unrecognized_response,json=unrecognizedResponse,proto3,oneof"` -} - -type EVMEndpointObservation_EmptyResponse struct { - // EVMEmptyResponse indicates an endpoint returned no data. - // Used to: - // - Disqualify endpoints that return empty responses - // - Track metrics for empty response patterns - EmptyResponse *EVMEmptyResponse `protobuf:"bytes,5,opt,name=empty_response,json=emptyResponse,proto3,oneof"` -} - -type EVMEndpointObservation_NoResponse struct { - // EVMNoResponse indicates no response was received from any endpoint. - // This differs from EVMEmptyResponse as no response was reported by the protocol. - NoResponse *EVMNoResponse `protobuf:"bytes,6,opt,name=no_response,json=noResponse,proto3,oneof"` -} - -func (*EVMEndpointObservation_ChainIdResponse) isEVMEndpointObservation_ResponseObservation() {} - -func (*EVMEndpointObservation_BlockNumberResponse) isEVMEndpointObservation_ResponseObservation() {} - -func (*EVMEndpointObservation_UnrecognizedResponse) isEVMEndpointObservation_ResponseObservation() {} - -func (*EVMEndpointObservation_EmptyResponse) isEVMEndpointObservation_ResponseObservation() {} - -func (*EVMEndpointObservation_NoResponse) isEVMEndpointObservation_ResponseObservation() {} - -// TODO_MVP(@adshmh): Implement a consolidated SanctionObservation message structure that: -// 1. Contains both SanctionType enum and RecommendedSanction field -// 2. Can be embedded as a single field within all qos/Response.proto messages -// 3. Ensures sanction policies are explicitly documented within message definitions -// 4. Maintains alignment with the Morse protocol sanction specifications -// 5. Search for all instances of RecommendedSanction in the codebase and use this new structure instead -// -// EVMChainIDResponse stores the response to an `eth_chainId` request -// https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_chainid -type EVMChainIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code received from the endpoint - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // The chain ID value returned in the response - ChainIdResponse string `protobuf:"bytes,2,opt,name=chain_id_response,json=chainIdResponse,proto3" json:"chain_id_response,omitempty"` - // Why the response failed QoS validation - // If not set, the response is considered valid - ResponseValidationError *EVMResponseValidationError `protobuf:"varint,3,opt,name=response_validation_error,json=responseValidationError,proto3,enum=path.qos.EVMResponseValidationError,oneof" json:"response_validation_error,omitempty"` -} - -func (x *EVMChainIDResponse) Reset() { - *x = EVMChainIDResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMChainIDResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMChainIDResponse) ProtoMessage() {} - -func (x *EVMChainIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMChainIDResponse.ProtoReflect.Descriptor instead. -func (*EVMChainIDResponse) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{4} -} - -func (x *EVMChainIDResponse) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMChainIDResponse) GetChainIdResponse() string { - if x != nil { - return x.ChainIdResponse - } - return "" -} - -func (x *EVMChainIDResponse) GetResponseValidationError() EVMResponseValidationError { - if x != nil && x.ResponseValidationError != nil { - return *x.ResponseValidationError - } - return EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED -} - -// EVMBlockNumberResponse stores the response to an `eth_getBlockNumber` request -// https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber -type EVMBlockNumberResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code received from the endpoint - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // The block number value returned in the response - BlockNumberResponse string `protobuf:"bytes,2,opt,name=block_number_response,json=blockNumberResponse,proto3" json:"block_number_response,omitempty"` - // Why the response failed QoS validation - // If not set, the response is considered valid - ResponseValidationError *EVMResponseValidationError `protobuf:"varint,3,opt,name=response_validation_error,json=responseValidationError,proto3,enum=path.qos.EVMResponseValidationError,oneof" json:"response_validation_error,omitempty"` -} - -func (x *EVMBlockNumberResponse) Reset() { - *x = EVMBlockNumberResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMBlockNumberResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMBlockNumberResponse) ProtoMessage() {} - -func (x *EVMBlockNumberResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMBlockNumberResponse.ProtoReflect.Descriptor instead. -func (*EVMBlockNumberResponse) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{5} -} - -func (x *EVMBlockNumberResponse) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMBlockNumberResponse) GetBlockNumberResponse() string { - if x != nil { - return x.BlockNumberResponse - } - return "" -} - -func (x *EVMBlockNumberResponse) GetResponseValidationError() EVMResponseValidationError { - if x != nil && x.ResponseValidationError != nil { - return *x.ResponseValidationError - } - return EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED -} - -// EVMUnrecognizedResponse handles requests with methods ignored by state update and endpoint validation -// Example: As of PR #72, `eth_call` requests are not used for endpoint validation -type EVMUnrecognizedResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code received from the endpoint - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // The JSON-RPC response received - JsonrpcResponse *JsonRpcResponse `protobuf:"bytes,2,opt,name=jsonrpc_response,json=jsonrpcResponse,proto3" json:"jsonrpc_response,omitempty"` - // Why the response failed QoS validation - // If not set, the response is considered valid - ResponseValidationError *EVMResponseValidationError `protobuf:"varint,3,opt,name=response_validation_error,json=responseValidationError,proto3,enum=path.qos.EVMResponseValidationError,oneof" json:"response_validation_error,omitempty"` -} - -func (x *EVMUnrecognizedResponse) Reset() { - *x = EVMUnrecognizedResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMUnrecognizedResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMUnrecognizedResponse) ProtoMessage() {} - -func (x *EVMUnrecognizedResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMUnrecognizedResponse.ProtoReflect.Descriptor instead. -func (*EVMUnrecognizedResponse) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{6} -} - -func (x *EVMUnrecognizedResponse) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMUnrecognizedResponse) GetJsonrpcResponse() *JsonRpcResponse { - if x != nil { - return x.JsonrpcResponse - } - return nil -} - -func (x *EVMUnrecognizedResponse) GetResponseValidationError() EVMResponseValidationError { - if x != nil && x.ResponseValidationError != nil { - return *x.ResponseValidationError - } - return EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED -} - -// EVMEmptyResponse represents an endpoint's empty response, which triggers -// automatic endpoint disqualification by EVM QoS processors. -type EVMEmptyResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code represents the status code sent to the client when the chosen endpoint returns an empty response. - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // Always set to EMPTY for empty responses - ResponseValidationError EVMResponseValidationError `protobuf:"varint,2,opt,name=response_validation_error,json=responseValidationError,proto3,enum=path.qos.EVMResponseValidationError" json:"response_validation_error,omitempty"` -} - -func (x *EVMEmptyResponse) Reset() { - *x = EVMEmptyResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMEmptyResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMEmptyResponse) ProtoMessage() {} - -func (x *EVMEmptyResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMEmptyResponse.ProtoReflect.Descriptor instead. -func (*EVMEmptyResponse) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{7} -} - -func (x *EVMEmptyResponse) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMEmptyResponse) GetResponseValidationError() EVMResponseValidationError { - if x != nil { - return x.ResponseValidationError - } - return EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED -} - -// EVMNoResponse represents a situation where no responses were reported to QoS by the protocol. -// This is due to protocol failures, e.g. if the selected endpoint was maxed out. -type EVMNoResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The HTTP status code to return, typically 503 Service Unavailable - HttpStatusCode int32 `protobuf:"varint,1,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - // Always set to NO_RESPONSE for this scenario - ResponseValidationError EVMResponseValidationError `protobuf:"varint,2,opt,name=response_validation_error,json=responseValidationError,proto3,enum=path.qos.EVMResponseValidationError" json:"response_validation_error,omitempty"` -} - -func (x *EVMNoResponse) Reset() { - *x = EVMNoResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_evm_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *EVMNoResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EVMNoResponse) ProtoMessage() {} - -func (x *EVMNoResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_evm_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EVMNoResponse.ProtoReflect.Descriptor instead. -func (*EVMNoResponse) Descriptor() ([]byte, []int) { - return file_path_qos_evm_proto_rawDescGZIP(), []int{8} -} - -func (x *EVMNoResponse) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - -func (x *EVMNoResponse) GetResponseValidationError() EVMResponseValidationError { - if x != nil { - return x.ResponseValidationError - } - return EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED -} - -var File_path_qos_evm_proto protoreflect.FileDescriptor - -var file_path_qos_evm_proto_rawDesc = []byte{ - 0x0a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x65, 0x76, 0x6d, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x1a, 0x16, - 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x03, 0x0a, 0x16, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x5e, 0x0a, 0x1a, 0x65, 0x76, - 0x6d, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x5f, 0x72, 0x65, 0x61, 0x64, - 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x48, 0x54, 0x54, - 0x50, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x65, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x48, 0x00, 0x52, 0x16, 0x65, 0x76, 0x6d, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x52, - 0x65, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x72, 0x0a, 0x20, 0x65, 0x76, - 0x6d, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x75, 0x6e, 0x6d, 0x61, 0x72, 0x73, - 0x68, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, - 0x45, 0x56, 0x4d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, - 0x68, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x48, 0x00, 0x52, - 0x1d, 0x65, 0x76, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x55, 0x6e, 0x6d, 0x61, 0x72, - 0x73, 0x68, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x41, - 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, - 0x6f, 0x73, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x55, 0x0a, 0x15, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, - 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x1c, 0x0a, 0x1a, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0xce, 0x01, 0x0a, 0x16, 0x45, 0x56, 0x4d, 0x48, 0x54, - 0x54, 0x50, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x65, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, - 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x4e, 0x0a, 0x10, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, - 0x2e, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x0d, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, - 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x1d, 0x45, 0x56, 0x4d, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x69, - 0x6e, 0x67, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x74, 0x74, - 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, - 0x6f, 0x64, 0x65, 0x12, 0x4e, 0x0a, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, - 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, - 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, - 0xd4, 0x03, 0x0a, 0x16, 0x45, 0x56, 0x4d, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, - 0x4a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x44, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x13, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x15, 0x75, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, - 0x7a, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, - 0x4d, 0x55, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x14, 0x75, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, - 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, - 0x0e, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, - 0x2e, 0x45, 0x56, 0x4d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x48, 0x00, 0x52, 0x0d, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0b, 0x6e, 0x6f, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, - 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x4e, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x48, 0x00, 0x52, 0x0a, 0x6e, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x16, - 0x0a, 0x14, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x8d, 0x02, 0x0a, 0x12, 0x45, 0x56, 0x4d, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x19, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, - 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x1b, 0x8a, - 0xb5, 0x18, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x1c, 0x0a, 0x1a, 0x5f, 0x72, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x97, 0x02, 0x0a, 0x16, 0x45, 0x56, 0x4d, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, - 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x80, 0x01, 0x0a, 0x19, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, - 0x56, 0x4d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x19, 0x8a, 0xb5, 0x18, 0x15, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x20, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, - 0x01, 0x01, 0x42, 0x1c, 0x0a, 0x1a, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x22, 0xaa, 0x02, 0x0a, 0x17, 0x45, 0x56, 0x4d, 0x55, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, - 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, - 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, - 0x63, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, - 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0f, 0x6a, 0x73, 0x6f, - 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, - 0x19, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x19, 0x8a, 0xb5, 0x18, 0x15, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x69, 0x74, 0x79, 0x20, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x20, 0x74, 0x79, 0x70, - 0x65, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, - 0x1c, 0x0a, 0x1a, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb9, 0x01, - 0x0a, 0x10, 0x45, 0x56, 0x4d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, - 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x7b, 0x0a, 0x19, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x19, 0x8a, 0xb5, 0x18, 0x15, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x69, 0x74, 0x79, 0x20, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, - 0x52, 0x17, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xb6, 0x01, 0x0a, 0x0d, 0x45, 0x56, - 0x4d, 0x4e, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x68, - 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x7b, 0x0a, 0x19, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, - 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x19, - 0x8a, 0xb5, 0x18, 0x15, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x20, 0x66, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x52, 0x17, 0x72, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x2a, 0xc1, 0x01, 0x0a, 0x19, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x2c, 0x0a, 0x28, 0x45, 0x56, 0x4d, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, - 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x37, - 0x0a, 0x33, 0x45, 0x56, 0x4d, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x48, - 0x54, 0x54, 0x50, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x46, 0x41, - 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x3d, 0x0a, 0x39, 0x45, 0x56, 0x4d, 0x5f, 0x52, - 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, - 0x55, 0x4e, 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, - 0x4c, 0x55, 0x52, 0x45, 0x10, 0x02, 0x2a, 0xd0, 0x01, 0x0a, 0x1a, 0x45, 0x56, 0x4d, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2d, 0x0a, 0x29, 0x45, 0x56, 0x4d, 0x5f, 0x52, 0x45, 0x53, - 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x27, 0x0a, 0x23, 0x45, 0x56, 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x50, - 0x4f, 0x4e, 0x53, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x10, 0x01, 0x12, 0x2b, 0x0a, - 0x27, 0x45, 0x56, 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x5f, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, - 0x4e, 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x2d, 0x0a, 0x29, 0x45, 0x56, - 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, - 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x03, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, - 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, - 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_path_qos_evm_proto_rawDescOnce sync.Once - file_path_qos_evm_proto_rawDescData = file_path_qos_evm_proto_rawDesc -) - -func file_path_qos_evm_proto_rawDescGZIP() []byte { - file_path_qos_evm_proto_rawDescOnce.Do(func() { - file_path_qos_evm_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_evm_proto_rawDescData) - }) - return file_path_qos_evm_proto_rawDescData -} - -var file_path_qos_evm_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_path_qos_evm_proto_msgTypes = make([]protoimpl.MessageInfo, 9) -var file_path_qos_evm_proto_goTypes = []any{ - (EVMRequestValidationError)(0), // 0: path.qos.EVMRequestValidationError - (EVMResponseValidationError)(0), // 1: path.qos.EVMResponseValidationError - (*EVMRequestObservations)(nil), // 2: path.qos.EVMRequestObservations - (*EVMHTTPBodyReadFailure)(nil), // 3: path.qos.EVMHTTPBodyReadFailure - (*EVMRequestUnmarshalingFailure)(nil), // 4: path.qos.EVMRequestUnmarshalingFailure - (*EVMEndpointObservation)(nil), // 5: path.qos.EVMEndpointObservation - (*EVMChainIDResponse)(nil), // 6: path.qos.EVMChainIDResponse - (*EVMBlockNumberResponse)(nil), // 7: path.qos.EVMBlockNumberResponse - (*EVMUnrecognizedResponse)(nil), // 8: path.qos.EVMUnrecognizedResponse - (*EVMEmptyResponse)(nil), // 9: path.qos.EVMEmptyResponse - (*EVMNoResponse)(nil), // 10: path.qos.EVMNoResponse - (*JsonRpcRequest)(nil), // 11: path.qos.JsonRpcRequest - (*JsonRpcResponse)(nil), // 12: path.qos.JsonRpcResponse -} -var file_path_qos_evm_proto_depIdxs = []int32{ - 3, // 0: path.qos.EVMRequestObservations.evm_http_body_read_failure:type_name -> path.qos.EVMHTTPBodyReadFailure - 4, // 1: path.qos.EVMRequestObservations.evm_request_unmarshaling_failure:type_name -> path.qos.EVMRequestUnmarshalingFailure - 11, // 2: path.qos.EVMRequestObservations.jsonrpc_request:type_name -> path.qos.JsonRpcRequest - 5, // 3: path.qos.EVMRequestObservations.endpoint_observations:type_name -> path.qos.EVMEndpointObservation - 0, // 4: path.qos.EVMHTTPBodyReadFailure.validation_error:type_name -> path.qos.EVMRequestValidationError - 0, // 5: path.qos.EVMRequestUnmarshalingFailure.validation_error:type_name -> path.qos.EVMRequestValidationError - 6, // 6: path.qos.EVMEndpointObservation.chain_id_response:type_name -> path.qos.EVMChainIDResponse - 7, // 7: path.qos.EVMEndpointObservation.block_number_response:type_name -> path.qos.EVMBlockNumberResponse - 8, // 8: path.qos.EVMEndpointObservation.unrecognized_response:type_name -> path.qos.EVMUnrecognizedResponse - 9, // 9: path.qos.EVMEndpointObservation.empty_response:type_name -> path.qos.EVMEmptyResponse - 10, // 10: path.qos.EVMEndpointObservation.no_response:type_name -> path.qos.EVMNoResponse - 1, // 11: path.qos.EVMChainIDResponse.response_validation_error:type_name -> path.qos.EVMResponseValidationError - 1, // 12: path.qos.EVMBlockNumberResponse.response_validation_error:type_name -> path.qos.EVMResponseValidationError - 12, // 13: path.qos.EVMUnrecognizedResponse.jsonrpc_response:type_name -> path.qos.JsonRpcResponse - 1, // 14: path.qos.EVMUnrecognizedResponse.response_validation_error:type_name -> path.qos.EVMResponseValidationError - 1, // 15: path.qos.EVMEmptyResponse.response_validation_error:type_name -> path.qos.EVMResponseValidationError - 1, // 16: path.qos.EVMNoResponse.response_validation_error:type_name -> path.qos.EVMResponseValidationError - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name -} - -func init() { file_path_qos_evm_proto_init() } -func file_path_qos_evm_proto_init() { - if File_path_qos_evm_proto != nil { - return - } - file_path_qos_jsonrpc_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_evm_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*EVMRequestObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*EVMHTTPBodyReadFailure); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*EVMRequestUnmarshalingFailure); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*EVMEndpointObservation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*EVMChainIDResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*EVMBlockNumberResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*EVMUnrecognizedResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*EVMEmptyResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_evm_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*EVMNoResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_path_qos_evm_proto_msgTypes[0].OneofWrappers = []any{ - (*EVMRequestObservations_EvmHttpBodyReadFailure)(nil), - (*EVMRequestObservations_EvmRequestUnmarshalingFailure)(nil), - } - file_path_qos_evm_proto_msgTypes[1].OneofWrappers = []any{} - file_path_qos_evm_proto_msgTypes[2].OneofWrappers = []any{} - file_path_qos_evm_proto_msgTypes[3].OneofWrappers = []any{ - (*EVMEndpointObservation_ChainIdResponse)(nil), - (*EVMEndpointObservation_BlockNumberResponse)(nil), - (*EVMEndpointObservation_UnrecognizedResponse)(nil), - (*EVMEndpointObservation_EmptyResponse)(nil), - (*EVMEndpointObservation_NoResponse)(nil), - } - file_path_qos_evm_proto_msgTypes[4].OneofWrappers = []any{} - file_path_qos_evm_proto_msgTypes[5].OneofWrappers = []any{} - file_path_qos_evm_proto_msgTypes[6].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_path_qos_evm_proto_rawDesc, - NumEnums: 2, - NumMessages: 9, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_path_qos_evm_proto_goTypes, - DependencyIndexes: file_path_qos_evm_proto_depIdxs, - EnumInfos: file_path_qos_evm_proto_enumTypes, - MessageInfos: file_path_qos_evm_proto_msgTypes, - }.Build() - File_path_qos_evm_proto = out.File - file_path_qos_evm_proto_rawDesc = nil - file_path_qos_evm_proto_goTypes = nil - file_path_qos_evm_proto_depIdxs = nil -} diff --git a/observation/qos/framework/endpoint.pb.go b/observation/qos/framework/endpoint.pb.go new file mode 100644 index 000000000..0fa5bbb6e --- /dev/null +++ b/observation/qos/framework/endpoint.pb.go @@ -0,0 +1,169 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.3 +// source: path/qos/framework/endpoint.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// EndpointObservation captures details about an endpoint query. +type EndpointObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Address of the endpoint that handled the request + EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` + // Single result item extracted from this endpoint query. + Result *EndpointQueryResult `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` +} + +func (x *EndpointObservation) Reset() { + *x = EndpointObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EndpointObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EndpointObservation) ProtoMessage() {} + +func (x *EndpointObservation) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EndpointObservation.ProtoReflect.Descriptor instead. +func (*EndpointObservation) Descriptor() ([]byte, []int) { + return file_path_qos_framework_endpoint_proto_rawDescGZIP(), []int{0} +} + +func (x *EndpointObservation) GetEndpointAddr() string { + if x != nil { + return x.EndpointAddr + } + return "" +} + +func (x *EndpointObservation) GetResult() *EndpointQueryResult { + if x != nil { + return x.Result + } + return nil +} + +var File_path_qos_framework_endpoint_proto protoreflect.FileDescriptor + +var file_path_qos_framework_endpoint_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, + 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, + 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x13, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, + 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, + 0x64, 0x64, 0x72, 0x12, 0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, + 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_endpoint_proto_rawDescOnce sync.Once + file_path_qos_framework_endpoint_proto_rawDescData = file_path_qos_framework_endpoint_proto_rawDesc +) + +func file_path_qos_framework_endpoint_proto_rawDescGZIP() []byte { + file_path_qos_framework_endpoint_proto_rawDescOnce.Do(func() { + file_path_qos_framework_endpoint_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_endpoint_proto_rawDescData) + }) + return file_path_qos_framework_endpoint_proto_rawDescData +} + +var file_path_qos_framework_endpoint_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_path_qos_framework_endpoint_proto_goTypes = []any{ + (*EndpointObservation)(nil), // 0: path.qos.framework.EndpointObservation + (*EndpointQueryResult)(nil), // 1: path.qos.framework.EndpointQueryResult +} +var file_path_qos_framework_endpoint_proto_depIdxs = []int32{ + 1, // 0: path.qos.framework.EndpointObservation.result:type_name -> path.qos.framework.EndpointQueryResult + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_endpoint_proto_init() } +func file_path_qos_framework_endpoint_proto_init() { + if File_path_qos_framework_endpoint_proto != nil { + return + } + file_path_qos_framework_endpoint_query_result_proto_init() + if !protoimpl.UnsafeEnabled { + file_path_qos_framework_endpoint_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*EndpointObservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_endpoint_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_endpoint_proto_goTypes, + DependencyIndexes: file_path_qos_framework_endpoint_proto_depIdxs, + MessageInfos: file_path_qos_framework_endpoint_proto_msgTypes, + }.Build() + File_path_qos_framework_endpoint_proto = out.File + file_path_qos_framework_endpoint_proto_rawDesc = nil + file_path_qos_framework_endpoint_proto_goTypes = nil + file_path_qos_framework_endpoint_proto_depIdxs = nil +} diff --git a/observation/qos/framework/endpoint_error.pb.go b/observation/qos/framework/endpoint_error.pb.go new file mode 100644 index 000000000..4f521e7b2 --- /dev/null +++ b/observation/qos/framework/endpoint_error.pb.go @@ -0,0 +1,257 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.3 +// source: path/qos/framework/endpoint_error.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// EndpointErrorKind identifies different kinds of endpoint data errors. +type EndpointErrorKind int32 + +const ( + EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED EndpointErrorKind = 0 + EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD EndpointErrorKind = 2 // Empty payload from endpoint + EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_UNMARSHALING EndpointErrorKind = 3 // Could not parse endpoint payload + EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT EndpointErrorKind = 4 // Payload result doesn't match expected value +) + +// Enum value maps for EndpointErrorKind. +var ( + EndpointErrorKind_name = map[int32]string{ + 0: "ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED", + 2: "ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD", + 3: "ENDPOINT_DATA_ERROR_KIND_UNMARSHALING", + 4: "ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT", + } + EndpointErrorKind_value = map[string]int32{ + "ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED": 0, + "ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD": 2, + "ENDPOINT_DATA_ERROR_KIND_UNMARSHALING": 3, + "ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT": 4, + } +) + +func (x EndpointErrorKind) Enum() *EndpointErrorKind { + p := new(EndpointErrorKind) + *p = x + return p +} + +func (x EndpointErrorKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EndpointErrorKind) Descriptor() protoreflect.EnumDescriptor { + return file_path_qos_framework_endpoint_error_proto_enumTypes[0].Descriptor() +} + +func (EndpointErrorKind) Type() protoreflect.EnumType { + return &file_path_qos_framework_endpoint_error_proto_enumTypes[0] +} + +func (x EndpointErrorKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EndpointErrorKind.Descriptor instead. +func (EndpointErrorKind) EnumDescriptor() ([]byte, []int) { + return file_path_qos_framework_endpoint_error_proto_rawDescGZIP(), []int{0} +} + +// EndpointError contains error details for endpoint queries. +type EndpointError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Specifies the kind of endpoint eror observed. + // Example: Returned payload cannot be parsed into a JSONRPC response. + ErrorKind EndpointErrorKind `protobuf:"varint,1,opt,name=error_kind,json=errorKind,proto3,enum=path.qos.framework.EndpointErrorKind" json:"error_kind,omitempty"` + // Description set by the custom service implementation + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // RecommendedSanction set by the custom service implementation + // Only set if the endpoint error has fetched it a sanction. + RecommendedSanction *Sanction `protobuf:"bytes,3,opt,name=recommended_sanction,json=recommendedSanction,proto3,oneof" json:"recommended_sanction,omitempty"` +} + +func (x *EndpointError) Reset() { + *x = EndpointError{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_endpoint_error_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EndpointError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EndpointError) ProtoMessage() {} + +func (x *EndpointError) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_endpoint_error_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EndpointError.ProtoReflect.Descriptor instead. +func (*EndpointError) Descriptor() ([]byte, []int) { + return file_path_qos_framework_endpoint_error_proto_rawDescGZIP(), []int{0} +} + +func (x *EndpointError) GetErrorKind() EndpointErrorKind { + if x != nil { + return x.ErrorKind + } + return EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED +} + +func (x *EndpointError) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *EndpointError) GetRecommendedSanction() *Sanction { + if x != nil { + return x.RecommendedSanction + } + return nil +} + +var File_path_qos_framework_endpoint_error_proto protoreflect.FileDescriptor + +var file_path_qos_framework_endpoint_error_proto_rawDesc = []byte{ + 0x0a, 0x27, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, + 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x2a, 0x70, + 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, + 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x6e, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe6, 0x01, 0x0a, 0x0d, 0x45, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x44, 0x0a, 0x0a, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x25, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, + 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x14, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, + 0x65, 0x64, 0x5f, 0x73, 0x61, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x53, 0x61, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x00, 0x52, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x53, 0x61, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x72, 0x65, + 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2a, 0xc1, 0x01, 0x0a, 0x11, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x28, 0x0a, 0x24, 0x45, 0x4e, 0x44, 0x50, + 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x2a, 0x0a, 0x26, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, + 0x41, 0x54, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x45, + 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x29, + 0x0a, 0x25, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x52, + 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x2b, 0x0a, 0x27, 0x45, 0x4e, 0x44, + 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, + 0x53, 0x55, 0x4c, 0x54, 0x10, 0x04, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, + 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_endpoint_error_proto_rawDescOnce sync.Once + file_path_qos_framework_endpoint_error_proto_rawDescData = file_path_qos_framework_endpoint_error_proto_rawDesc +) + +func file_path_qos_framework_endpoint_error_proto_rawDescGZIP() []byte { + file_path_qos_framework_endpoint_error_proto_rawDescOnce.Do(func() { + file_path_qos_framework_endpoint_error_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_endpoint_error_proto_rawDescData) + }) + return file_path_qos_framework_endpoint_error_proto_rawDescData +} + +var file_path_qos_framework_endpoint_error_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_path_qos_framework_endpoint_error_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_path_qos_framework_endpoint_error_proto_goTypes = []any{ + (EndpointErrorKind)(0), // 0: path.qos.framework.EndpointErrorKind + (*EndpointError)(nil), // 1: path.qos.framework.EndpointError + (*Sanction)(nil), // 2: path.qos.framework.Sanction +} +var file_path_qos_framework_endpoint_error_proto_depIdxs = []int32{ + 0, // 0: path.qos.framework.EndpointError.error_kind:type_name -> path.qos.framework.EndpointErrorKind + 2, // 1: path.qos.framework.EndpointError.recommended_sanction:type_name -> path.qos.framework.Sanction + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_endpoint_error_proto_init() } +func file_path_qos_framework_endpoint_error_proto_init() { + if File_path_qos_framework_endpoint_error_proto != nil { + return + } + file_path_qos_framework_endpoint_sanction_proto_init() + if !protoimpl.UnsafeEnabled { + file_path_qos_framework_endpoint_error_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*EndpointError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_path_qos_framework_endpoint_error_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_endpoint_error_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_endpoint_error_proto_goTypes, + DependencyIndexes: file_path_qos_framework_endpoint_error_proto_depIdxs, + EnumInfos: file_path_qos_framework_endpoint_error_proto_enumTypes, + MessageInfos: file_path_qos_framework_endpoint_error_proto_msgTypes, + }.Build() + File_path_qos_framework_endpoint_error_proto = out.File + file_path_qos_framework_endpoint_error_proto_rawDesc = nil + file_path_qos_framework_endpoint_error_proto_goTypes = nil + file_path_qos_framework_endpoint_error_proto_depIdxs = nil +} diff --git a/observation/qos/framework/endpoint_query_result.pb.go b/observation/qos/framework/endpoint_query_result.pb.go new file mode 100644 index 000000000..aed9f67f0 --- /dev/null +++ b/observation/qos/framework/endpoint_query_result.pb.go @@ -0,0 +1,234 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.3 +// source: path/qos/framework/endpoint_query_result.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// EndpointQueryResult captures data extracted from an endpoint query. +// - Stores one or more string/integer values. +// - Contains error/sanction information on endpoint error. +type EndpointQueryResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // HTTP response returned to the client. + ClientHttpResponse int32 `protobuf:"varint,1,opt,name=client_http_response,json=clientHttpResponse,proto3" json:"client_http_response,omitempty"` + // The set of values/attributes extracted from the endpoint query + // and the endpoint's parsed JSONRPC response + StringValues map[string]string `protobuf:"bytes,2,rep,name=string_values,json=stringValues,proto3" json:"string_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + IntValues map[string]int64 `protobuf:"bytes,3,rep,name=int_values,json=intValues,proto3" json:"int_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + // Captures the queried endpoint's error + // Only set if the query result indicates an endpoint error + Error *EndpointError `protobuf:"bytes,5,opt,name=error,proto3,oneof" json:"error,omitempty"` + // The time at which the query result is expired + ExpiryTime *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=expiry_time,json=expiryTime,proto3" json:"expiry_time,omitempty"` +} + +func (x *EndpointQueryResult) Reset() { + *x = EndpointQueryResult{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_endpoint_query_result_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EndpointQueryResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EndpointQueryResult) ProtoMessage() {} + +func (x *EndpointQueryResult) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_endpoint_query_result_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EndpointQueryResult.ProtoReflect.Descriptor instead. +func (*EndpointQueryResult) Descriptor() ([]byte, []int) { + return file_path_qos_framework_endpoint_query_result_proto_rawDescGZIP(), []int{0} +} + +func (x *EndpointQueryResult) GetClientHttpResponse() int32 { + if x != nil { + return x.ClientHttpResponse + } + return 0 +} + +func (x *EndpointQueryResult) GetStringValues() map[string]string { + if x != nil { + return x.StringValues + } + return nil +} + +func (x *EndpointQueryResult) GetIntValues() map[string]int64 { + if x != nil { + return x.IntValues + } + return nil +} + +func (x *EndpointQueryResult) GetError() *EndpointError { + if x != nil { + return x.Error + } + return nil +} + +func (x *EndpointQueryResult) GetExpiryTime() *timestamppb.Timestamp { + if x != nil { + return x.ExpiryTime + } + return nil +} + +var File_path_qos_framework_endpoint_query_result_proto protoreflect.FileDescriptor + +var file_path_qos_framework_endpoint_query_result_proto_rawDesc = []byte{ + 0x0a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x82, + 0x04, 0x0a, 0x13, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x74, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x39, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, + 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, + 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, + 0x3c, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, + 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, + 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3c, 0x0a, 0x0e, 0x49, + 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, + 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_endpoint_query_result_proto_rawDescOnce sync.Once + file_path_qos_framework_endpoint_query_result_proto_rawDescData = file_path_qos_framework_endpoint_query_result_proto_rawDesc +) + +func file_path_qos_framework_endpoint_query_result_proto_rawDescGZIP() []byte { + file_path_qos_framework_endpoint_query_result_proto_rawDescOnce.Do(func() { + file_path_qos_framework_endpoint_query_result_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_endpoint_query_result_proto_rawDescData) + }) + return file_path_qos_framework_endpoint_query_result_proto_rawDescData +} + +var file_path_qos_framework_endpoint_query_result_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_path_qos_framework_endpoint_query_result_proto_goTypes = []any{ + (*EndpointQueryResult)(nil), // 0: path.qos.framework.EndpointQueryResult + nil, // 1: path.qos.framework.EndpointQueryResult.StringValuesEntry + nil, // 2: path.qos.framework.EndpointQueryResult.IntValuesEntry + (*EndpointError)(nil), // 3: path.qos.framework.EndpointError + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp +} +var file_path_qos_framework_endpoint_query_result_proto_depIdxs = []int32{ + 1, // 0: path.qos.framework.EndpointQueryResult.string_values:type_name -> path.qos.framework.EndpointQueryResult.StringValuesEntry + 2, // 1: path.qos.framework.EndpointQueryResult.int_values:type_name -> path.qos.framework.EndpointQueryResult.IntValuesEntry + 3, // 2: path.qos.framework.EndpointQueryResult.error:type_name -> path.qos.framework.EndpointError + 4, // 3: path.qos.framework.EndpointQueryResult.expiry_time:type_name -> google.protobuf.Timestamp + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_endpoint_query_result_proto_init() } +func file_path_qos_framework_endpoint_query_result_proto_init() { + if File_path_qos_framework_endpoint_query_result_proto != nil { + return + } + file_path_qos_framework_endpoint_error_proto_init() + if !protoimpl.UnsafeEnabled { + file_path_qos_framework_endpoint_query_result_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*EndpointQueryResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_path_qos_framework_endpoint_query_result_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_endpoint_query_result_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_endpoint_query_result_proto_goTypes, + DependencyIndexes: file_path_qos_framework_endpoint_query_result_proto_depIdxs, + MessageInfos: file_path_qos_framework_endpoint_query_result_proto_msgTypes, + }.Build() + File_path_qos_framework_endpoint_query_result_proto = out.File + file_path_qos_framework_endpoint_query_result_proto_rawDesc = nil + file_path_qos_framework_endpoint_query_result_proto_goTypes = nil + file_path_qos_framework_endpoint_query_result_proto_depIdxs = nil +} diff --git a/observation/qos/framework/endpoint_sanction.pb.go b/observation/qos/framework/endpoint_sanction.pb.go new file mode 100644 index 000000000..31a810530 --- /dev/null +++ b/observation/qos/framework/endpoint_sanction.pb.go @@ -0,0 +1,237 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.3 +// source: path/qos/framework/endpoint_sanction.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SanctionType identifies different types of endpoint sanctions. +type SanctionType int32 + +const ( + SanctionType_SANCTION_TYPE_UNSPECIFIED SanctionType = 0 + SanctionType_SANCTION_TYPE_TEMPORARY SanctionType = 1 // Time-limited exclusion + SanctionType_SANCTION_TYPE_PERMANENT SanctionType = 2 // Permanent exclusion +) + +// Enum value maps for SanctionType. +var ( + SanctionType_name = map[int32]string{ + 0: "SANCTION_TYPE_UNSPECIFIED", + 1: "SANCTION_TYPE_TEMPORARY", + 2: "SANCTION_TYPE_PERMANENT", + } + SanctionType_value = map[string]int32{ + "SANCTION_TYPE_UNSPECIFIED": 0, + "SANCTION_TYPE_TEMPORARY": 1, + "SANCTION_TYPE_PERMANENT": 2, + } +) + +func (x SanctionType) Enum() *SanctionType { + p := new(SanctionType) + *p = x + return p +} + +func (x SanctionType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SanctionType) Descriptor() protoreflect.EnumDescriptor { + return file_path_qos_framework_endpoint_sanction_proto_enumTypes[0].Descriptor() +} + +func (SanctionType) Type() protoreflect.EnumType { + return &file_path_qos_framework_endpoint_sanction_proto_enumTypes[0] +} + +func (x SanctionType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SanctionType.Descriptor instead. +func (SanctionType) EnumDescriptor() ([]byte, []int) { + return file_path_qos_framework_endpoint_sanction_proto_rawDescGZIP(), []int{0} +} + +// Sanction represents a recommendation to limit endpoint usage. +type Sanction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type SanctionType `protobuf:"varint,1,opt,name=type,proto3,enum=path.qos.framework.SanctionType" json:"type,omitempty"` // Type of sanction + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` // Reason for the sanction + ExpiryTimestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expiry_timestamp,json=expiryTimestamp,proto3" json:"expiry_timestamp,omitempty"` +} + +func (x *Sanction) Reset() { + *x = Sanction{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_endpoint_sanction_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Sanction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sanction) ProtoMessage() {} + +func (x *Sanction) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_endpoint_sanction_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sanction.ProtoReflect.Descriptor instead. +func (*Sanction) Descriptor() ([]byte, []int) { + return file_path_qos_framework_endpoint_sanction_proto_rawDescGZIP(), []int{0} +} + +func (x *Sanction) GetType() SanctionType { + if x != nil { + return x.Type + } + return SanctionType_SANCTION_TYPE_UNSPECIFIED +} + +func (x *Sanction) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *Sanction) GetExpiryTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.ExpiryTimestamp + } + return nil +} + +var File_path_qos_framework_endpoint_sanction_proto protoreflect.FileDescriptor + +var file_path_qos_framework_endpoint_sanction_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x61, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, + 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, + 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x9f, 0x01, 0x0a, 0x08, 0x53, 0x61, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, + 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, + 0x6b, 0x2e, 0x53, 0x61, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x10, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2a, 0x67, 0x0a, 0x0c, 0x53, 0x61, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x41, 0x4e, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x41, 0x4e, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x10, 0x01, 0x12, + 0x1b, 0x0a, 0x17, 0x53, 0x41, 0x4e, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x42, 0x3a, 0x5a, 0x38, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_endpoint_sanction_proto_rawDescOnce sync.Once + file_path_qos_framework_endpoint_sanction_proto_rawDescData = file_path_qos_framework_endpoint_sanction_proto_rawDesc +) + +func file_path_qos_framework_endpoint_sanction_proto_rawDescGZIP() []byte { + file_path_qos_framework_endpoint_sanction_proto_rawDescOnce.Do(func() { + file_path_qos_framework_endpoint_sanction_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_endpoint_sanction_proto_rawDescData) + }) + return file_path_qos_framework_endpoint_sanction_proto_rawDescData +} + +var file_path_qos_framework_endpoint_sanction_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_path_qos_framework_endpoint_sanction_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_path_qos_framework_endpoint_sanction_proto_goTypes = []any{ + (SanctionType)(0), // 0: path.qos.framework.SanctionType + (*Sanction)(nil), // 1: path.qos.framework.Sanction + (*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp +} +var file_path_qos_framework_endpoint_sanction_proto_depIdxs = []int32{ + 0, // 0: path.qos.framework.Sanction.type:type_name -> path.qos.framework.SanctionType + 2, // 1: path.qos.framework.Sanction.expiry_timestamp:type_name -> google.protobuf.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_endpoint_sanction_proto_init() } +func file_path_qos_framework_endpoint_sanction_proto_init() { + if File_path_qos_framework_endpoint_sanction_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_path_qos_framework_endpoint_sanction_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Sanction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_endpoint_sanction_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_endpoint_sanction_proto_goTypes, + DependencyIndexes: file_path_qos_framework_endpoint_sanction_proto_depIdxs, + EnumInfos: file_path_qos_framework_endpoint_sanction_proto_enumTypes, + MessageInfos: file_path_qos_framework_endpoint_sanction_proto_msgTypes, + }.Build() + File_path_qos_framework_endpoint_sanction_proto = out.File + file_path_qos_framework_endpoint_sanction_proto_rawDesc = nil + file_path_qos_framework_endpoint_sanction_proto_goTypes = nil + file_path_qos_framework_endpoint_sanction_proto_depIdxs = nil +} diff --git a/observation/qos/jsonrpc.pb.go b/observation/qos/framework/jsonrpc.pb.go similarity index 56% rename from observation/qos/jsonrpc.pb.go rename to observation/qos/framework/jsonrpc.pb.go index 7cf0ac91a..2bbfbed09 100644 --- a/observation/qos/jsonrpc.pb.go +++ b/observation/qos/framework/jsonrpc.pb.go @@ -2,9 +2,9 @@ // versions: // protoc-gen-go v1.34.2 // protoc v5.28.3 -// source: path/qos/jsonrpc.proto +// source: path/qos/framework/jsonrpc.proto -package qos +package framework import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -36,7 +36,7 @@ type JsonRpcRequest struct { func (x *JsonRpcRequest) Reset() { *x = JsonRpcRequest{} if protoimpl.UnsafeEnabled { - mi := &file_path_qos_jsonrpc_proto_msgTypes[0] + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -49,7 +49,7 @@ func (x *JsonRpcRequest) String() string { func (*JsonRpcRequest) ProtoMessage() {} func (x *JsonRpcRequest) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_jsonrpc_proto_msgTypes[0] + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -62,7 +62,7 @@ func (x *JsonRpcRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use JsonRpcRequest.ProtoReflect.Descriptor instead. func (*JsonRpcRequest) Descriptor() ([]byte, []int) { - return file_path_qos_jsonrpc_proto_rawDescGZIP(), []int{0} + return file_path_qos_framework_jsonrpc_proto_rawDescGZIP(), []int{0} } func (x *JsonRpcRequest) GetId() string { @@ -97,7 +97,7 @@ type JsonRpcResponse struct { func (x *JsonRpcResponse) Reset() { *x = JsonRpcResponse{} if protoimpl.UnsafeEnabled { - mi := &file_path_qos_jsonrpc_proto_msgTypes[1] + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -110,7 +110,7 @@ func (x *JsonRpcResponse) String() string { func (*JsonRpcResponse) ProtoMessage() {} func (x *JsonRpcResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_jsonrpc_proto_msgTypes[1] + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -123,7 +123,7 @@ func (x *JsonRpcResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use JsonRpcResponse.ProtoReflect.Descriptor instead. func (*JsonRpcResponse) Descriptor() ([]byte, []int) { - return file_path_qos_jsonrpc_proto_rawDescGZIP(), []int{1} + return file_path_qos_framework_jsonrpc_proto_rawDescGZIP(), []int{1} } func (x *JsonRpcResponse) GetId() string { @@ -165,7 +165,7 @@ type JsonRpcResponseError struct { func (x *JsonRpcResponseError) Reset() { *x = JsonRpcResponseError{} if protoimpl.UnsafeEnabled { - mi := &file_path_qos_jsonrpc_proto_msgTypes[2] + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -178,7 +178,7 @@ func (x *JsonRpcResponseError) String() string { func (*JsonRpcResponseError) ProtoMessage() {} func (x *JsonRpcResponseError) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_jsonrpc_proto_msgTypes[2] + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -191,7 +191,7 @@ func (x *JsonRpcResponseError) ProtoReflect() protoreflect.Message { // Deprecated: Use JsonRpcResponseError.ProtoReflect.Descriptor instead. func (*JsonRpcResponseError) Descriptor() ([]byte, []int) { - return file_path_qos_jsonrpc_proto_rawDescGZIP(), []int{2} + return file_path_qos_framework_jsonrpc_proto_rawDescGZIP(), []int{2} } func (x *JsonRpcResponseError) GetCode() int64 { @@ -208,53 +208,55 @@ func (x *JsonRpcResponseError) GetMessage() string { return "" } -var File_path_qos_jsonrpc_proto protoreflect.FileDescriptor - -var file_path_qos_jsonrpc_proto_rawDesc = []byte{ - 0x0a, 0x16, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, - 0x6f, 0x73, 0x22, 0x38, 0x0a, 0x0e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x78, 0x0a, 0x0f, - 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x35, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, - 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x03, 0x65, 0x72, 0x72, 0x88, 0x01, 0x01, 0x42, 0x06, - 0x0a, 0x04, 0x5f, 0x65, 0x72, 0x72, 0x22, 0x44, 0x0a, 0x14, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, - 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, - 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, - 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x30, 0x5a, 0x2e, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +var File_path_qos_framework_jsonrpc_proto protoreflect.FileDescriptor + +var file_path_qos_framework_jsonrpc_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x38, 0x0a, 0x0e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, + 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x22, 0x82, 0x01, 0x0a, 0x0f, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3f, 0x0a, 0x03, + 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x61, 0x74, 0x68, + 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, + 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x03, 0x65, 0x72, 0x72, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, + 0x04, 0x5f, 0x65, 0x72, 0x72, 0x22, 0x44, 0x0a, 0x14, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, + 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x3a, 0x5a, 0x38, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, + 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, + 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_path_qos_jsonrpc_proto_rawDescOnce sync.Once - file_path_qos_jsonrpc_proto_rawDescData = file_path_qos_jsonrpc_proto_rawDesc + file_path_qos_framework_jsonrpc_proto_rawDescOnce sync.Once + file_path_qos_framework_jsonrpc_proto_rawDescData = file_path_qos_framework_jsonrpc_proto_rawDesc ) -func file_path_qos_jsonrpc_proto_rawDescGZIP() []byte { - file_path_qos_jsonrpc_proto_rawDescOnce.Do(func() { - file_path_qos_jsonrpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_jsonrpc_proto_rawDescData) +func file_path_qos_framework_jsonrpc_proto_rawDescGZIP() []byte { + file_path_qos_framework_jsonrpc_proto_rawDescOnce.Do(func() { + file_path_qos_framework_jsonrpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_jsonrpc_proto_rawDescData) }) - return file_path_qos_jsonrpc_proto_rawDescData + return file_path_qos_framework_jsonrpc_proto_rawDescData } -var file_path_qos_jsonrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_path_qos_jsonrpc_proto_goTypes = []any{ - (*JsonRpcRequest)(nil), // 0: path.qos.JsonRpcRequest - (*JsonRpcResponse)(nil), // 1: path.qos.JsonRpcResponse - (*JsonRpcResponseError)(nil), // 2: path.qos.JsonRpcResponseError +var file_path_qos_framework_jsonrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_path_qos_framework_jsonrpc_proto_goTypes = []any{ + (*JsonRpcRequest)(nil), // 0: path.qos.framework.JsonRpcRequest + (*JsonRpcResponse)(nil), // 1: path.qos.framework.JsonRpcResponse + (*JsonRpcResponseError)(nil), // 2: path.qos.framework.JsonRpcResponseError } -var file_path_qos_jsonrpc_proto_depIdxs = []int32{ - 2, // 0: path.qos.JsonRpcResponse.err:type_name -> path.qos.JsonRpcResponseError +var file_path_qos_framework_jsonrpc_proto_depIdxs = []int32{ + 2, // 0: path.qos.framework.JsonRpcResponse.err:type_name -> path.qos.framework.JsonRpcResponseError 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name @@ -262,13 +264,13 @@ var file_path_qos_jsonrpc_proto_depIdxs = []int32{ 0, // [0:1] is the sub-list for field type_name } -func init() { file_path_qos_jsonrpc_proto_init() } -func file_path_qos_jsonrpc_proto_init() { - if File_path_qos_jsonrpc_proto != nil { +func init() { file_path_qos_framework_jsonrpc_proto_init() } +func file_path_qos_framework_jsonrpc_proto_init() { + if File_path_qos_framework_jsonrpc_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_path_qos_jsonrpc_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_path_qos_framework_jsonrpc_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*JsonRpcRequest); i { case 0: return &v.state @@ -280,7 +282,7 @@ func file_path_qos_jsonrpc_proto_init() { return nil } } - file_path_qos_jsonrpc_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_path_qos_framework_jsonrpc_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*JsonRpcResponse); i { case 0: return &v.state @@ -292,7 +294,7 @@ func file_path_qos_jsonrpc_proto_init() { return nil } } - file_path_qos_jsonrpc_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_path_qos_framework_jsonrpc_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*JsonRpcResponseError); i { case 0: return &v.state @@ -305,23 +307,23 @@ func file_path_qos_jsonrpc_proto_init() { } } } - file_path_qos_jsonrpc_proto_msgTypes[1].OneofWrappers = []any{} + file_path_qos_framework_jsonrpc_proto_msgTypes[1].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_path_qos_jsonrpc_proto_rawDesc, + RawDescriptor: file_path_qos_framework_jsonrpc_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_path_qos_jsonrpc_proto_goTypes, - DependencyIndexes: file_path_qos_jsonrpc_proto_depIdxs, - MessageInfos: file_path_qos_jsonrpc_proto_msgTypes, + GoTypes: file_path_qos_framework_jsonrpc_proto_goTypes, + DependencyIndexes: file_path_qos_framework_jsonrpc_proto_depIdxs, + MessageInfos: file_path_qos_framework_jsonrpc_proto_msgTypes, }.Build() - File_path_qos_jsonrpc_proto = out.File - file_path_qos_jsonrpc_proto_rawDesc = nil - file_path_qos_jsonrpc_proto_goTypes = nil - file_path_qos_jsonrpc_proto_depIdxs = nil + File_path_qos_framework_jsonrpc_proto = out.File + file_path_qos_framework_jsonrpc_proto_rawDesc = nil + file_path_qos_framework_jsonrpc_proto_goTypes = nil + file_path_qos_framework_jsonrpc_proto_depIdxs = nil } diff --git a/observation/qos/framework/observations.pb.go b/observation/qos/framework/observations.pb.go new file mode 100644 index 000000000..ebfdffed7 --- /dev/null +++ b/observation/qos/framework/observations.pb.go @@ -0,0 +1,189 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.3 +// source: path/qos/framework/observations.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Observations is the top-level container for all QoS observations for a request. +type Observations struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Service identification + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // e.g. EVM, Solana, CometBFT + // Observation of the client request + RequestObservation *RequestObservation `protobuf:"bytes,2,opt,name=request_observation,json=requestObservation,proto3" json:"request_observation,omitempty"` + // Observations from endpoint(s) + EndpointObservations []*EndpointObservation `protobuf:"bytes,3,rep,name=endpoint_observations,json=endpointObservations,proto3" json:"endpoint_observations,omitempty"` +} + +func (x *Observations) Reset() { + *x = Observations{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_observations_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Observations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Observations) ProtoMessage() {} + +func (x *Observations) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_observations_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Observations.ProtoReflect.Descriptor instead. +func (*Observations) Descriptor() ([]byte, []int) { + return file_path_qos_framework_observations_proto_rawDescGZIP(), []int{0} +} + +func (x *Observations) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *Observations) GetRequestObservation() *RequestObservation { + if x != nil { + return x.RequestObservation + } + return nil +} + +func (x *Observations) GetEndpointObservations() []*EndpointObservation { + if x != nil { + return x.EndpointObservations + } + return nil +} + +var File_path_qos_framework_observations_proto protoreflect.FileDescriptor + +var file_path_qos_framework_observations_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, + 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x20, 0x70, 0x61, 0x74, + 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x70, + 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, + 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0xe8, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57, 0x0a, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, + 0x15, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, + 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, + 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x3a, 0x5a, 0x38, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, + 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, + 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_observations_proto_rawDescOnce sync.Once + file_path_qos_framework_observations_proto_rawDescData = file_path_qos_framework_observations_proto_rawDesc +) + +func file_path_qos_framework_observations_proto_rawDescGZIP() []byte { + file_path_qos_framework_observations_proto_rawDescOnce.Do(func() { + file_path_qos_framework_observations_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_observations_proto_rawDescData) + }) + return file_path_qos_framework_observations_proto_rawDescData +} + +var file_path_qos_framework_observations_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_path_qos_framework_observations_proto_goTypes = []any{ + (*Observations)(nil), // 0: path.qos.framework.Observations + (*RequestObservation)(nil), // 1: path.qos.framework.RequestObservation + (*EndpointObservation)(nil), // 2: path.qos.framework.EndpointObservation +} +var file_path_qos_framework_observations_proto_depIdxs = []int32{ + 1, // 0: path.qos.framework.Observations.request_observation:type_name -> path.qos.framework.RequestObservation + 2, // 1: path.qos.framework.Observations.endpoint_observations:type_name -> path.qos.framework.EndpointObservation + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_observations_proto_init() } +func file_path_qos_framework_observations_proto_init() { + if File_path_qos_framework_observations_proto != nil { + return + } + file_path_qos_framework_request_proto_init() + file_path_qos_framework_endpoint_proto_init() + if !protoimpl.UnsafeEnabled { + file_path_qos_framework_observations_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Observations); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_observations_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_observations_proto_goTypes, + DependencyIndexes: file_path_qos_framework_observations_proto_depIdxs, + MessageInfos: file_path_qos_framework_observations_proto_msgTypes, + }.Build() + File_path_qos_framework_observations_proto = out.File + file_path_qos_framework_observations_proto_rawDesc = nil + file_path_qos_framework_observations_proto_goTypes = nil + file_path_qos_framework_observations_proto_depIdxs = nil +} diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go new file mode 100644 index 000000000..780baadbb --- /dev/null +++ b/observation/qos/framework/request.pb.go @@ -0,0 +1,370 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.28.3 +// source: path/qos/framework/request.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RequestErrorKind identifies the type of validation error encountered. +type RequestErrorKind int32 + +const ( + RequestErrorKind_REQUEST_ERROR_UNSPECIFIED RequestErrorKind = 0 + RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE RequestErrorKind = 1 // Error reading HTTP request body + RequestErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR RequestErrorKind = 2 // Protocol error: e.g. endpoint timed out. + RequestErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE RequestErrorKind = 3 // Error parsing JSON-RPC request + RequestErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION RequestErrorKind = 4 // JSONRPC request has invalid version. + RequestErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD RequestErrorKind = 5 // JSONRPC request does not specify method. + RequestErrorKind_REQUEST_ERROR_VALIDATION_INVALID_PARAMS RequestErrorKind = 6 // Parameters are incorrect +) + +// Enum value maps for RequestErrorKind. +var ( + RequestErrorKind_name = map[int32]string{ + 0: "REQUEST_ERROR_UNSPECIFIED", + 1: "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE", + 2: "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR", + 3: "REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE", + 4: "REQUEST_ERROR_VALIDATION_INVALID_VERSION", + 5: "REQUEST_ERROR_VALIDATION_MISSING_METHOD", + 6: "REQUEST_ERROR_VALIDATION_INVALID_PARAMS", + } + RequestErrorKind_value = map[string]int32{ + "REQUEST_ERROR_UNSPECIFIED": 0, + "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE": 1, + "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR": 2, + "REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE": 3, + "REQUEST_ERROR_VALIDATION_INVALID_VERSION": 4, + "REQUEST_ERROR_VALIDATION_MISSING_METHOD": 5, + "REQUEST_ERROR_VALIDATION_INVALID_PARAMS": 6, + } +) + +func (x RequestErrorKind) Enum() *RequestErrorKind { + p := new(RequestErrorKind) + *p = x + return p +} + +func (x RequestErrorKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RequestErrorKind) Descriptor() protoreflect.EnumDescriptor { + return file_path_qos_framework_request_proto_enumTypes[0].Descriptor() +} + +func (RequestErrorKind) Type() protoreflect.EnumType { + return &file_path_qos_framework_request_proto_enumTypes[0] +} + +func (x RequestErrorKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RequestErrorKind.Descriptor instead. +func (RequestErrorKind) EnumDescriptor() ([]byte, []int) { + return file_path_qos_framework_request_proto_rawDescGZIP(), []int{0} +} + +// RequestError contains details about a request error +type RequestError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ErrorKind RequestErrorKind `protobuf:"varint,1,opt,name=error_kind,json=errorKind,proto3,enum=path.qos.framework.RequestErrorKind" json:"error_kind,omitempty"` + // HTTP status code returned to the client. + HttpStatusCode int32 `protobuf:"varint,2,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` + ErrorDetails string `protobuf:"bytes,3,opt,name=error_details,json=errorDetails,proto3" json:"error_details,omitempty"` + // Only set for validation failures on valid JSONRPC structures + // e.g. missing params field when required. + JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,4,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3,oneof" json:"jsonrpc_request,omitempty"` +} + +func (x *RequestError) Reset() { + *x = RequestError{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestError) ProtoMessage() {} + +func (x *RequestError) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_request_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestError.ProtoReflect.Descriptor instead. +func (*RequestError) Descriptor() ([]byte, []int) { + return file_path_qos_framework_request_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestError) GetErrorKind() RequestErrorKind { + if x != nil { + return x.ErrorKind + } + return RequestErrorKind_REQUEST_ERROR_UNSPECIFIED +} + +func (x *RequestError) GetHttpStatusCode() int32 { + if x != nil { + return x.HttpStatusCode + } + return 0 +} + +func (x *RequestError) GetErrorDetails() string { + if x != nil { + return x.ErrorDetails + } + return "" +} + +func (x *RequestError) GetJsonrpcRequest() *JsonRpcRequest { + if x != nil { + return x.JsonrpcRequest + } + return nil +} + +// TODO_IN_THIS_PR: use `oneof` below: either the request is set, or the validationerror +// RequestObservation captures details about the original client request. +type RequestObservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Only set if validation was successful + JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,1,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3,oneof" json:"jsonrpc_request,omitempty"` + // Only set if the request failed + RequestError *RequestError `protobuf:"bytes,2,opt,name=request_error,json=requestError,proto3,oneof" json:"request_error,omitempty"` +} + +func (x *RequestObservation) Reset() { + *x = RequestObservation{} + if protoimpl.UnsafeEnabled { + mi := &file_path_qos_framework_request_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestObservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestObservation) ProtoMessage() {} + +func (x *RequestObservation) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_request_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestObservation.ProtoReflect.Descriptor instead. +func (*RequestObservation) Descriptor() ([]byte, []int) { + return file_path_qos_framework_request_proto_rawDescGZIP(), []int{1} +} + +func (x *RequestObservation) GetJsonrpcRequest() *JsonRpcRequest { + if x != nil { + return x.JsonrpcRequest + } + return nil +} + +func (x *RequestObservation) GetRequestError() *RequestError { + if x != nil { + return x.RequestError + } + return nil +} + +var File_path_qos_framework_request_proto protoreflect.FileDescriptor + +var file_path_qos_framework_request_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, + 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x88, 0x02, 0x0a, 0x0c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x43, 0x0a, 0x0a, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, + 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, + 0x69, 0x6e, 0x64, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x28, + 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x50, 0x0a, + 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, + 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, + 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, + 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x42, + 0x12, 0x0a, 0x10, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0xd8, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x0f, 0x6a, 0x73, + 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, + 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x0d, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6a, 0x73, 0x6f, + 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x10, 0x0a, 0x0e, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2a, 0xc5, + 0x02, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, + 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x42, 0x4f, 0x44, + 0x59, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, + 0x12, 0x29, 0x0a, 0x25, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x43, 0x4f, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x31, 0x0a, 0x2d, 0x52, + 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, + 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x03, 0x12, 0x2c, + 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, + 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x2b, 0x0a, 0x27, + 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, + 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, + 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, 0x05, 0x12, 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, + 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, + 0x52, 0x41, 0x4d, 0x53, 0x10, 0x06, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, + 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_request_proto_rawDescOnce sync.Once + file_path_qos_framework_request_proto_rawDescData = file_path_qos_framework_request_proto_rawDesc +) + +func file_path_qos_framework_request_proto_rawDescGZIP() []byte { + file_path_qos_framework_request_proto_rawDescOnce.Do(func() { + file_path_qos_framework_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_request_proto_rawDescData) + }) + return file_path_qos_framework_request_proto_rawDescData +} + +var file_path_qos_framework_request_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_path_qos_framework_request_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_path_qos_framework_request_proto_goTypes = []any{ + (RequestErrorKind)(0), // 0: path.qos.framework.RequestErrorKind + (*RequestError)(nil), // 1: path.qos.framework.RequestError + (*RequestObservation)(nil), // 2: path.qos.framework.RequestObservation + (*JsonRpcRequest)(nil), // 3: path.qos.framework.JsonRpcRequest +} +var file_path_qos_framework_request_proto_depIdxs = []int32{ + 0, // 0: path.qos.framework.RequestError.error_kind:type_name -> path.qos.framework.RequestErrorKind + 3, // 1: path.qos.framework.RequestError.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest + 3, // 2: path.qos.framework.RequestObservation.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest + 1, // 3: path.qos.framework.RequestObservation.request_error:type_name -> path.qos.framework.RequestError + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_request_proto_init() } +func file_path_qos_framework_request_proto_init() { + if File_path_qos_framework_request_proto != nil { + return + } + file_path_qos_framework_jsonrpc_proto_init() + if !protoimpl.UnsafeEnabled { + file_path_qos_framework_request_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*RequestError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_path_qos_framework_request_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*RequestObservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_path_qos_framework_request_proto_msgTypes[0].OneofWrappers = []any{} + file_path_qos_framework_request_proto_msgTypes[1].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_request_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_request_proto_goTypes, + DependencyIndexes: file_path_qos_framework_request_proto_depIdxs, + EnumInfos: file_path_qos_framework_request_proto_enumTypes, + MessageInfos: file_path_qos_framework_request_proto_msgTypes, + }.Build() + File_path_qos_framework_request_proto = out.File + file_path_qos_framework_request_proto_rawDesc = nil + file_path_qos_framework_request_proto_goTypes = nil + file_path_qos_framework_request_proto_depIdxs = nil +} diff --git a/observation/qos/observations.pb.go b/observation/qos/observations.pb.go index 6013bce88..741040bb7 100644 --- a/observation/qos/observations.pb.go +++ b/observation/qos/observations.pb.go @@ -7,6 +7,7 @@ package qos import ( + framework "github.com/buildwithgrove/path/observation/qos/framework" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,9 +22,7 @@ const ( ) // Observations contains QoS measurements for a single service request. -// Currently supports: -// - Solana blockchain service -// - EVM blockchains service +// Currently only supports JSONRPC-based service observations. type Observations struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -33,9 +32,7 @@ type Observations struct { // // Types that are assignable to ServiceObservations: // - // *Observations_Solana - // *Observations_Evm - // *Observations_Cometbft + // *Observations_JsonrpcService ServiceObservations isObservations_ServiceObservations `protobuf_oneof:"service_observations"` } @@ -78,23 +75,9 @@ func (m *Observations) GetServiceObservations() isObservations_ServiceObservatio return nil } -func (x *Observations) GetSolana() *SolanaRequestObservations { - if x, ok := x.GetServiceObservations().(*Observations_Solana); ok { - return x.Solana - } - return nil -} - -func (x *Observations) GetEvm() *EVMRequestObservations { - if x, ok := x.GetServiceObservations().(*Observations_Evm); ok { - return x.Evm - } - return nil -} - -func (x *Observations) GetCometbft() *CometBFTRequestObservations { - if x, ok := x.GetServiceObservations().(*Observations_Cometbft); ok { - return x.Cometbft +func (x *Observations) GetJsonrpcService() *framework.Observations { + if x, ok := x.GetServiceObservations().(*Observations_JsonrpcService); ok { + return x.JsonrpcService } return nil } @@ -103,55 +86,32 @@ type isObservations_ServiceObservations interface { isObservations_ServiceObservations() } -type Observations_Solana struct { - // solana contains QoS measurements for a single Solana blockchain request - Solana *SolanaRequestObservations `protobuf:"bytes,1,opt,name=solana,proto3,oneof"` -} - -type Observations_Evm struct { - // evm contains QoS measurements for a single EVM blockchain request - Evm *EVMRequestObservations `protobuf:"bytes,2,opt,name=evm,proto3,oneof"` +type Observations_JsonrpcService struct { + // jsonrpc contains QoS measurements for a JSON-RPC based service request + JsonrpcService *framework.Observations `protobuf:"bytes,1,opt,name=jsonrpc_service,json=jsonrpcService,proto3,oneof"` } -type Observations_Cometbft struct { - // cometbft contains QoS measurements for a single CometBFT blockchain request - Cometbft *CometBFTRequestObservations `protobuf:"bytes,3,opt,name=cometbft,proto3,oneof"` -} - -func (*Observations_Solana) isObservations_ServiceObservations() {} - -func (*Observations_Evm) isObservations_ServiceObservations() {} - -func (*Observations_Cometbft) isObservations_ServiceObservations() {} +func (*Observations_JsonrpcService) isObservations_ServiceObservations() {} var File_path_qos_observations_proto protoreflect.FileDescriptor var file_path_qos_observations_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, - 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x1a, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, - 0x73, 0x2f, 0x65, 0x76, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x70, 0x61, 0x74, - 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x1a, 0x17, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x6d, - 0x65, 0x74, 0x62, 0x66, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x01, 0x0a, 0x0c, - 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3d, 0x0a, 0x06, - 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, - 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x53, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x48, 0x00, 0x52, 0x06, 0x73, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x12, 0x34, 0x0a, 0x03, 0x65, - 0x76, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, - 0x71, 0x6f, 0x73, 0x2e, 0x45, 0x56, 0x4d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x03, 0x65, 0x76, - 0x6d, 0x12, 0x43, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x62, 0x66, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x43, - 0x6f, 0x6d, 0x65, 0x74, 0x42, 0x46, 0x54, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, - 0x6d, 0x65, 0x74, 0x62, 0x66, 0x74, 0x42, 0x16, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x30, - 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, - 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x1a, 0x25, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, + 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x73, + 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4b, + 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, + 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, + 0x6e, 0x72, 0x70, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x16, 0x0a, 0x14, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, + 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -168,20 +128,16 @@ func file_path_qos_observations_proto_rawDescGZIP() []byte { var file_path_qos_observations_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_path_qos_observations_proto_goTypes = []any{ - (*Observations)(nil), // 0: path.qos.Observations - (*SolanaRequestObservations)(nil), // 1: path.qos.SolanaRequestObservations - (*EVMRequestObservations)(nil), // 2: path.qos.EVMRequestObservations - (*CometBFTRequestObservations)(nil), // 3: path.qos.CometBFTRequestObservations + (*Observations)(nil), // 0: path.qos.Observations + (*framework.Observations)(nil), // 1: path.qos.framework.Observations } var file_path_qos_observations_proto_depIdxs = []int32{ - 1, // 0: path.qos.Observations.solana:type_name -> path.qos.SolanaRequestObservations - 2, // 1: path.qos.Observations.evm:type_name -> path.qos.EVMRequestObservations - 3, // 2: path.qos.Observations.cometbft:type_name -> path.qos.CometBFTRequestObservations - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 1, // 0: path.qos.Observations.jsonrpc_service:type_name -> path.qos.framework.Observations + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_path_qos_observations_proto_init() } @@ -189,9 +145,6 @@ func file_path_qos_observations_proto_init() { if File_path_qos_observations_proto != nil { return } - file_path_qos_evm_proto_init() - file_path_qos_solana_proto_init() - file_path_qos_cometbft_proto_init() if !protoimpl.UnsafeEnabled { file_path_qos_observations_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Observations); i { @@ -207,9 +160,7 @@ func file_path_qos_observations_proto_init() { } } file_path_qos_observations_proto_msgTypes[0].OneofWrappers = []any{ - (*Observations_Solana)(nil), - (*Observations_Evm)(nil), - (*Observations_Cometbft)(nil), + (*Observations_JsonrpcService)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/observation/qos/solana.pb.go b/observation/qos/solana.pb.go deleted file mode 100644 index 9577c94c9..000000000 --- a/observation/qos/solana.pb.go +++ /dev/null @@ -1,544 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.28.3 -// source: path/qos/solana.proto - -package qos - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// SolanaRequestObservations captures QoS data for a single Solana blockchain service request, -// including all observations made during potential retries. -type SolanaRequestObservations struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // JSON-RPC request to the Solana blockchain service - // TODO_TECHDEBT: This assumes all SolanaVM blockchains only (and always) support JSON-RPC. - // May need expansion/refactoring for future blockchain support. - JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,1,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3" json:"jsonrpc_request,omitempty"` - // Multiple observations possible if: - // - Original endpoint returns invalid response - // - Retry mechanism activates - EndpointObservations []*SolanaEndpointObservation `protobuf:"bytes,2,rep,name=endpoint_observations,json=endpointObservations,proto3" json:"endpoint_observations,omitempty"` -} - -func (x *SolanaRequestObservations) Reset() { - *x = SolanaRequestObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_solana_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SolanaRequestObservations) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SolanaRequestObservations) ProtoMessage() {} - -func (x *SolanaRequestObservations) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_solana_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SolanaRequestObservations.ProtoReflect.Descriptor instead. -func (*SolanaRequestObservations) Descriptor() ([]byte, []int) { - return file_path_qos_solana_proto_rawDescGZIP(), []int{0} -} - -func (x *SolanaRequestObservations) GetJsonrpcRequest() *JsonRpcRequest { - if x != nil { - return x.JsonrpcRequest - } - return nil -} - -func (x *SolanaRequestObservations) GetEndpointObservations() []*SolanaEndpointObservation { - if x != nil { - return x.EndpointObservations - } - return nil -} - -// SolanaEndpointObservation captures a single endpoint's response to a request -type SolanaEndpointObservation struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Address of the endpoint handling the request - EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` - // Types that are assignable to ResponseObservation: - // - // *SolanaEndpointObservation_GetEpochInfoResponse - // *SolanaEndpointObservation_GetHealthResponse - // *SolanaEndpointObservation_UnrecognizedResponse - ResponseObservation isSolanaEndpointObservation_ResponseObservation `protobuf_oneof:"response_observation"` -} - -func (x *SolanaEndpointObservation) Reset() { - *x = SolanaEndpointObservation{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_solana_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SolanaEndpointObservation) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SolanaEndpointObservation) ProtoMessage() {} - -func (x *SolanaEndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_solana_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SolanaEndpointObservation.ProtoReflect.Descriptor instead. -func (*SolanaEndpointObservation) Descriptor() ([]byte, []int) { - return file_path_qos_solana_proto_rawDescGZIP(), []int{1} -} - -func (x *SolanaEndpointObservation) GetEndpointAddr() string { - if x != nil { - return x.EndpointAddr - } - return "" -} - -func (m *SolanaEndpointObservation) GetResponseObservation() isSolanaEndpointObservation_ResponseObservation { - if m != nil { - return m.ResponseObservation - } - return nil -} - -func (x *SolanaEndpointObservation) GetGetEpochInfoResponse() *SolanaGetEpochInfoResponse { - if x, ok := x.GetResponseObservation().(*SolanaEndpointObservation_GetEpochInfoResponse); ok { - return x.GetEpochInfoResponse - } - return nil -} - -func (x *SolanaEndpointObservation) GetGetHealthResponse() *SolanaGetHealthResponse { - if x, ok := x.GetResponseObservation().(*SolanaEndpointObservation_GetHealthResponse); ok { - return x.GetHealthResponse - } - return nil -} - -func (x *SolanaEndpointObservation) GetUnrecognizedResponse() *SolanaUnrecognizedResponse { - if x, ok := x.GetResponseObservation().(*SolanaEndpointObservation_UnrecognizedResponse); ok { - return x.UnrecognizedResponse - } - return nil -} - -type isSolanaEndpointObservation_ResponseObservation interface { - isSolanaEndpointObservation_ResponseObservation() -} - -type SolanaEndpointObservation_GetEpochInfoResponse struct { - // Response from getEpochInfo - // Docs: https://solana.com/docs/rpc/http/getepochinfo - GetEpochInfoResponse *SolanaGetEpochInfoResponse `protobuf:"bytes,2,opt,name=get_epoch_info_response,json=getEpochInfoResponse,proto3,oneof"` -} - -type SolanaEndpointObservation_GetHealthResponse struct { - // Response from getHealth - // Docs: https://solana.com/docs/rpc/http/gethealth - GetHealthResponse *SolanaGetHealthResponse `protobuf:"bytes,3,opt,name=get_health_response,json=getHealthResponse,proto3,oneof"` -} - -type SolanaEndpointObservation_UnrecognizedResponse struct { - // Responses not used in endpoint validation (e.g., getAccountInfo) - UnrecognizedResponse *SolanaUnrecognizedResponse `protobuf:"bytes,4,opt,name=unrecognized_response,json=unrecognizedResponse,proto3,oneof"` -} - -func (*SolanaEndpointObservation_GetEpochInfoResponse) isSolanaEndpointObservation_ResponseObservation() { -} - -func (*SolanaEndpointObservation_GetHealthResponse) isSolanaEndpointObservation_ResponseObservation() { -} - -func (*SolanaEndpointObservation_UnrecognizedResponse) isSolanaEndpointObservation_ResponseObservation() { -} - -// SolanaEpochInfoResponse stores getEpochInfo response data -// Docs: https://solana.com/docs/rpc/http/getepochinfo -type SolanaGetEpochInfoResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Stored as uint64 for cross-instance validation - BlockHeight uint64 `protobuf:"varint,1,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` - Epoch uint64 `protobuf:"varint,2,opt,name=epoch,proto3" json:"epoch,omitempty"` -} - -func (x *SolanaGetEpochInfoResponse) Reset() { - *x = SolanaGetEpochInfoResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_solana_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SolanaGetEpochInfoResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SolanaGetEpochInfoResponse) ProtoMessage() {} - -func (x *SolanaGetEpochInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_solana_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SolanaGetEpochInfoResponse.ProtoReflect.Descriptor instead. -func (*SolanaGetEpochInfoResponse) Descriptor() ([]byte, []int) { - return file_path_qos_solana_proto_rawDescGZIP(), []int{2} -} - -func (x *SolanaGetEpochInfoResponse) GetBlockHeight() uint64 { - if x != nil { - return x.BlockHeight - } - return 0 -} - -func (x *SolanaGetEpochInfoResponse) GetEpoch() uint64 { - if x != nil { - return x.Epoch - } - return 0 -} - -// SolanaGetHealthResponse stores getHealth response data -// Docs: https://solana.com/docs/rpc/http/gethealth -type SolanaGetHealthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` -} - -func (x *SolanaGetHealthResponse) Reset() { - *x = SolanaGetHealthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_solana_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SolanaGetHealthResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SolanaGetHealthResponse) ProtoMessage() {} - -func (x *SolanaGetHealthResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_solana_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SolanaGetHealthResponse.ProtoReflect.Descriptor instead. -func (*SolanaGetHealthResponse) Descriptor() ([]byte, []int) { - return file_path_qos_solana_proto_rawDescGZIP(), []int{3} -} - -func (x *SolanaGetHealthResponse) GetResult() string { - if x != nil { - return x.Result - } - return "" -} - -// SolanaUnrecognizedResponse stores responses from methods not used in validation -// Examples: getTokenSupply, getTransaction -type SolanaUnrecognizedResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - JsonrpcResponse *JsonRpcResponse `protobuf:"bytes,1,opt,name=jsonrpc_response,json=jsonrpcResponse,proto3" json:"jsonrpc_response,omitempty"` -} - -func (x *SolanaUnrecognizedResponse) Reset() { - *x = SolanaUnrecognizedResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_solana_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SolanaUnrecognizedResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SolanaUnrecognizedResponse) ProtoMessage() {} - -func (x *SolanaUnrecognizedResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_solana_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SolanaUnrecognizedResponse.ProtoReflect.Descriptor instead. -func (*SolanaUnrecognizedResponse) Descriptor() ([]byte, []int) { - return file_path_qos_solana_proto_rawDescGZIP(), []int{4} -} - -func (x *SolanaUnrecognizedResponse) GetJsonrpcResponse() *JsonRpcResponse { - if x != nil { - return x.JsonrpcResponse - } - return nil -} - -var File_path_qos_solana_proto protoreflect.FileDescriptor - -var file_path_qos_solana_proto_rawDesc = []byte{ - 0x0a, 0x15, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x73, 0x6f, 0x6c, 0x61, 0x6e, - 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, - 0x73, 0x1a, 0x16, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb8, 0x01, 0x0a, 0x19, 0x53, 0x6f, - 0x6c, 0x61, 0x6e, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, - 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, - 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, - 0x72, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x58, 0x0a, 0x15, 0x65, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x61, 0x74, 0x68, - 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x53, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe9, 0x02, 0x0a, 0x19, 0x53, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x5d, 0x0a, 0x17, 0x67, 0x65, 0x74, 0x5f, 0x65, - 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, - 0x71, 0x6f, 0x73, 0x2e, 0x53, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x47, 0x65, 0x74, 0x45, 0x70, 0x6f, - 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, - 0x52, 0x14, 0x67, 0x65, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x13, 0x67, 0x65, 0x74, 0x5f, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x53, - 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x11, 0x67, 0x65, 0x74, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x15, 0x75, - 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x53, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x55, 0x6e, 0x72, 0x65, - 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x48, 0x00, 0x52, 0x14, 0x75, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x16, 0x0a, 0x14, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x55, 0x0a, 0x1a, 0x53, 0x6f, 0x6c, 0x61, 0x6e, 0x61, 0x47, 0x65, 0x74, 0x45, 0x70, 0x6f, - 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x22, 0x31, 0x0a, 0x17, 0x53, 0x6f, 0x6c, 0x61, 0x6e, - 0x61, 0x47, 0x65, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x62, 0x0a, 0x1a, 0x53, 0x6f, - 0x6c, 0x61, 0x6e, 0x61, 0x55, 0x6e, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x10, 0x6a, 0x73, 0x6f, 0x6e, - 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x4a, 0x73, - 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0f, 0x6a, - 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, - 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, - 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_path_qos_solana_proto_rawDescOnce sync.Once - file_path_qos_solana_proto_rawDescData = file_path_qos_solana_proto_rawDesc -) - -func file_path_qos_solana_proto_rawDescGZIP() []byte { - file_path_qos_solana_proto_rawDescOnce.Do(func() { - file_path_qos_solana_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_solana_proto_rawDescData) - }) - return file_path_qos_solana_proto_rawDescData -} - -var file_path_qos_solana_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_path_qos_solana_proto_goTypes = []any{ - (*SolanaRequestObservations)(nil), // 0: path.qos.SolanaRequestObservations - (*SolanaEndpointObservation)(nil), // 1: path.qos.SolanaEndpointObservation - (*SolanaGetEpochInfoResponse)(nil), // 2: path.qos.SolanaGetEpochInfoResponse - (*SolanaGetHealthResponse)(nil), // 3: path.qos.SolanaGetHealthResponse - (*SolanaUnrecognizedResponse)(nil), // 4: path.qos.SolanaUnrecognizedResponse - (*JsonRpcRequest)(nil), // 5: path.qos.JsonRpcRequest - (*JsonRpcResponse)(nil), // 6: path.qos.JsonRpcResponse -} -var file_path_qos_solana_proto_depIdxs = []int32{ - 5, // 0: path.qos.SolanaRequestObservations.jsonrpc_request:type_name -> path.qos.JsonRpcRequest - 1, // 1: path.qos.SolanaRequestObservations.endpoint_observations:type_name -> path.qos.SolanaEndpointObservation - 2, // 2: path.qos.SolanaEndpointObservation.get_epoch_info_response:type_name -> path.qos.SolanaGetEpochInfoResponse - 3, // 3: path.qos.SolanaEndpointObservation.get_health_response:type_name -> path.qos.SolanaGetHealthResponse - 4, // 4: path.qos.SolanaEndpointObservation.unrecognized_response:type_name -> path.qos.SolanaUnrecognizedResponse - 6, // 5: path.qos.SolanaUnrecognizedResponse.jsonrpc_response:type_name -> path.qos.JsonRpcResponse - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name -} - -func init() { file_path_qos_solana_proto_init() } -func file_path_qos_solana_proto_init() { - if File_path_qos_solana_proto != nil { - return - } - file_path_qos_jsonrpc_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_solana_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*SolanaRequestObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_solana_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*SolanaEndpointObservation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_solana_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*SolanaGetEpochInfoResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_solana_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*SolanaGetHealthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_solana_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*SolanaUnrecognizedResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_path_qos_solana_proto_msgTypes[1].OneofWrappers = []any{ - (*SolanaEndpointObservation_GetEpochInfoResponse)(nil), - (*SolanaEndpointObservation_GetHealthResponse)(nil), - (*SolanaEndpointObservation_UnrecognizedResponse)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_path_qos_solana_proto_rawDesc, - NumEnums: 0, - NumMessages: 5, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_path_qos_solana_proto_goTypes, - DependencyIndexes: file_path_qos_solana_proto_depIdxs, - MessageInfos: file_path_qos_solana_proto_msgTypes, - }.Build() - File_path_qos_solana_proto = out.File - file_path_qos_solana_proto_rawDesc = nil - file_path_qos_solana_proto_goTypes = nil - file_path_qos_solana_proto_depIdxs = nil -} diff --git a/proto/path/qos/cometbft.proto b/proto/path/qos/cometbft.proto deleted file mode 100644 index ef2ba2eaf..000000000 --- a/proto/path/qos/cometbft.proto +++ /dev/null @@ -1,70 +0,0 @@ -syntax = "proto3"; - -package path.qos; - -option go_package = "github.com/buildwithgrove/path/observation/qos"; - -import "path/qos/jsonrpc.proto"; - -// CometBFTRequestObservations captures all observations made while serving a single CometBFT blockchain service request. -message CometBFTRequestObservations { - // The CometBFT blockchain service's route request, including all params - string route_request = 1; - - // CometBFT-specific observations from endpoint(s) that responded to the service request. - // Multiple observations may occur when: - // * Original endpoint fails - // * Request is sent to additional endpoints for data collection - repeated CometBFTEndpointObservation endpoint_observations = 2; -} - -// CometBFTEndpointObservation stores a single observation from an endpoint servicing the protocol response. -// Example: A Pocket node on Shannon backed by an Ethereum data node servicing an `eth_getBlockNumber` request. -message CometBFTEndpointObservation { - // Address of the endpoint handling the request (e.g., onchain address of a Pocket Morse/Shannon node) - string endpoint_addr = 1; - - // Details of the response received from the endpoint - oneof response_observation { - // Response to `/health` request - CometBFTHealthResponse health_response = 2; - - // Response to `/status` request - CometBFTStatusResponse status_response = 3; - - // Responses not used in endpoint validation - CometBFTUnrecognizedResponse unrecognized_response = 4; - } - - // TODO_IMPROVE(@adshmh, @commoddity): Add other observations (archival, more endpoints, etc) -} - -// CometBFTHealthResponse stores the response to a `health` request -// Reference: https://docs.cometbft.com/v1.0/spec/rpc/#health -message CometBFTHealthResponse { - bool health_status_response = 1; -} - -// CometBFTStatusResponse stores the latest block number from a `/status` request -// Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status -message CometBFTStatusResponse { - // Chain ID of the endpoint. Comes from the `NodeInfo.Network` field in the `/status` response. - // Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status - string chain_id_response = 1; - - // Indicates if the endpoint is catching up to the network. - // Comes from the `SyncInfo.CatchingUp` field in the `/status` response. - // Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status - bool catching_up_response = 2; - - // Latest block height of the endpoint. - // Comes from the `SyncInfo.LatestBlockHeight` field in the `/status` response. - // Reference: https://docs.cometbft.com/v1.0/spec/rpc/#status - string latest_block_height_response = 3; -} - -// CometBFTUnrecognizedResponse handles requests with methods ignored by state update -// and endpoint validation -message CometBFTUnrecognizedResponse { - JsonRpcResponse jsonrpc_response = 1; -} diff --git a/proto/path/qos/evm.proto b/proto/path/qos/evm.proto deleted file mode 100644 index 13b06628b..000000000 --- a/proto/path/qos/evm.proto +++ /dev/null @@ -1,230 +0,0 @@ -syntax = "proto3"; - -// TODO_TECHDEBT(@adshmh): Address linter warning on all the .proto files. -// TODO_TECHDEBT(@adshmh): Package name "path.qos" should be suffixed with a correctly formed version, such as "path.qos.v1" -// -// Buf used as linter for proto files: -// https://buf.build/docs/lint/overview/ -package path.qos; - -// TODO_UPNEXT(@adshmh): Organize qos & observation code + structures like so: -// - Separate out `evm.proto` into `evm_qos.proto` and `evm_observation.proto` -// - Organize code under the `observations` package into appropriate `observation/qos/evm/**` files like it's done in `qos/evm/**` -// Why the TODO? -// As a reader of the code, the separation of concerns yet simultaneous overlap of data & primitives -// leads to very confusing conflation between qos (i.e. request/responses) with observations (i.e. metrics/data). - -option go_package = "github.com/buildwithgrove/path/observation/qos"; - -import "path/qos/jsonrpc.proto"; -import "path/metadata/metadata.proto"; - -// EVMRequestValidationError enumerates possible causes for EVM request rejection: -// Invalid request types (as of PR #186): -// 1. Internal server error while reading the HTTP request body -// 2. Unmarshal error when parsing request into the expected format -enum EVMRequestValidationError { - EVM_REQUEST_VALIDATION_ERROR_UNSPECIFIED = 0; - EVM_REQUEST_VALIDATION_ERROR_HTTP_BODY_READ_FAILURE = 1; - EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE = 2; -} - -// TODO_DOCUMENT(@adshmh): Create a design document for the feature described below. -// TODO_MVP(@adshmh): Add EVMUserErrorType enum -// -// Purpose: Distinguish between endpoint technical failures and user input errors -// -// Background: -// - Currently we only track endpoint/technical failures -// - Need to identify when request seems valid but fails due to user input issues (e.g., non-existent hash) -// -// Implementation: -// 1. Create new EVMUserErrorType enum with categories like RESOURCE_NOT_FOUND, INVALID_PARAMETER -// 2. Add user_error field to appropriate response types -// 3. Update HTTP status code selection logic to consider user errors -// -// Benefits: -// - More accurate error reporting to clients -// - Appropriate HTTP status codes (e.g., 404 vs 500) -// - Better client debugging experience -// -// EVMResponseValidationError defines why an endpoint response was rejected. -// Current invalid response types (as of PR #186): -// 1. EmptyResponse - endpoint returned no data -// 2. UnmarshalErr - response failed to parse into expected format -// 3. NoResponse - no responses recorded by the QoS service: probably caused by protocol-level errors -enum EVMResponseValidationError { - EVM_RESPONSE_VALIDATION_ERROR_UNSPECIFIED = 0; - EVM_RESPONSE_VALIDATION_ERROR_EMPTY = 1; // Response with no data. - EVM_RESPONSE_VALIDATION_ERROR_UNMARSHAL = 2; // Response parsing failed - EVM_RESPONSE_VALIDATION_ERROR_NO_RESPONSE = 3; // No response received from any endpoint -} - -// EVMRequestObservations captures all observations made while serving a single EVM blockchain service request. -message EVMRequestObservations { - // chain_id is the blockchain identifier for the evm QoS implementation. - // This is preset by the processor and not determined by the request. - // Expected as the `Result` field in eth_chainId responses. - string chain_id = 1; - - // If this oneof IS SET, then one of the following validation failures happened: - // - Indicates the request failed validation - // - Contains details about the specific failure type - // - The HTTP status code in the selected failure type overrides any status codes from - // endpoint observations and should be returned to the client - // If this oneof IS NOT SET, then one of the following occurred: - // - The request passed validation - // - The HTTP status code from the most recent endpoint observation should be used instead - oneof request_validation_failure { - // Indicates a failure to read the HTTP request body - EVMHTTPBodyReadFailure evm_http_body_read_failure = 2; - - // Indicates a failure to unmarshal/parse the request - EVMRequestUnmarshalingFailure evm_request_unmarshaling_failure = 3; - } - - // The EVM blockchain service's JSON-RPC request. - // This field will be populated only if request validation succeeds. - // If there is an error reading the HTTP request, there will be no jsonrpc_request. - // TODO_TECHDEBT: Assumes EVM chains only support JSON-RPC. May need refactoring to support other protocols. - JsonRpcRequest jsonrpc_request = 4; - - // EVM-specific observations from endpoint(s) that responded to the service request. - // Multiple observations may occur when: - // * Original endpoint fails - // * Request is sent to additional endpoints for data collection - // This field will only be populated if request validation succeeds. - repeated EVMEndpointObservation endpoint_observations = 5; -} - -// TODO_MVP(@adshmh): Remove HTTP body read validation once QoS interface is updated -// to receive request payload directly rather than reading from the HTTP request body. -// -// EVMHTTPBodyReadFailure represents a validation failure due to internal server error -// while attempting to read the HTTP request body. -message EVMHTTPBodyReadFailure { - // The HTTP status code to return to the client - typically 500 Internal Server Error - int32 http_status_code = 1; - - // The specific type of request validation error - EVMRequestValidationError validation_error = 2; - - // Additional error details if available - optional string error_details = 3; -} - -// EVMRequestUnmarshalingFailure represents a validation failure due to being unable -// to parse the incoming request into the expected format. -message EVMRequestUnmarshalingFailure { - // The HTTP status code to return to the client - typically 400 Bad Request - int32 http_status_code = 1; - - // The specific type of request validation error - EVMRequestValidationError validation_error = 2; - - // Additional error details if available - optional string error_details = 3; -} - -// EVMEndpointObservation stores a single observation from an endpoint servicing the protocol response. -// Example: A Pocket node on Shannon backed by an Ethereum data node servicing an `eth_getBlockNumber` request. -message EVMEndpointObservation { - // Address of the endpoint handling the request (e.g., onchain address of a Pocket Morse/Shannon node) - string endpoint_addr = 1; - - // Details of the response received from the endpoint - oneof response_observation { - // Response to `eth_chainId` request - // Reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_chainid - EVMChainIDResponse chain_id_response = 2; - - // Response to `eth_blockNumber` request - // References: - // * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber - // * Chain IDs: https://chainlist.org - EVMBlockNumberResponse block_number_response = 3; - - // Responses not used in endpoint validation (e.g., JSONRPC ID field from `eth_call`) - EVMUnrecognizedResponse unrecognized_response = 4; - - // EVMEmptyResponse indicates an endpoint returned no data. - // Used to: - // - Disqualify endpoints that return empty responses - // - Track metrics for empty response patterns - EVMEmptyResponse empty_response = 5; - - // EVMNoResponse indicates no response was received from any endpoint. - // This differs from EVMEmptyResponse as no response was reported by the protocol. - EVMNoResponse no_response = 6; - } - // TODO_MVP(@commoddity): add observations for archival checks. -} - -// TODO_MVP(@adshmh): Implement a consolidated SanctionObservation message structure that: -// 1. Contains both SanctionType enum and RecommendedSanction field -// 2. Can be embedded as a single field within all qos/Response.proto messages -// 3. Ensures sanction policies are explicitly documented within message definitions -// 4. Maintains alignment with the Morse protocol sanction specifications -// 5. Search for all instances of RecommendedSanction in the codebase and use this new structure instead -// -// EVMChainIDResponse stores the response to an `eth_chainId` request -// https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_chainid -message EVMChainIDResponse { - // The HTTP status code received from the endpoint - int32 http_status_code = 1; - - // The chain ID value returned in the response - string chain_id_response = 2; - - // Why the response failed QoS validation - // If not set, the response is considered valid - optional EVMResponseValidationError response_validation_error = 3 [(metadata.semantic_meaning) = "Validation failure type"]; -} - -// EVMBlockNumberResponse stores the response to an `eth_getBlockNumber` request -// https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber -message EVMBlockNumberResponse { - // The HTTP status code received from the endpoint - int32 http_status_code = 1; - - // The block number value returned in the response - string block_number_response = 2; - - // Why the response failed QoS validation - // If not set, the response is considered valid - optional EVMResponseValidationError response_validation_error = 3 [(metadata.semantic_meaning) = "Validity failure type"]; -} - -// EVMUnrecognizedResponse handles requests with methods ignored by state update and endpoint validation -// Example: As of PR #72, `eth_call` requests are not used for endpoint validation -message EVMUnrecognizedResponse { - // The HTTP status code received from the endpoint - int32 http_status_code = 1; - - // The JSON-RPC response received - JsonRpcResponse jsonrpc_response = 2; - - // Why the response failed QoS validation - // If not set, the response is considered valid - optional EVMResponseValidationError response_validation_error = 3 [(metadata.semantic_meaning) = "Validity failure type"]; -} - -// EVMEmptyResponse represents an endpoint's empty response, which triggers -// automatic endpoint disqualification by EVM QoS processors. -message EVMEmptyResponse { - // The HTTP status code represents the status code sent to the client when the chosen endpoint returns an empty response. - int32 http_status_code = 1; - - // Always set to EMPTY for empty responses - EVMResponseValidationError response_validation_error = 2 [(metadata.semantic_meaning) = "Validity failure type"]; -} - -// EVMNoResponse represents a situation where no responses were reported to QoS by the protocol. -// This is due to protocol failures, e.g. if the selected endpoint was maxed out. -message EVMNoResponse { - // The HTTP status code to return, typically 503 Service Unavailable - int32 http_status_code = 1; - - // Always set to NO_RESPONSE for this scenario - EVMResponseValidationError response_validation_error = 2 [(metadata.semantic_meaning) = "Validity failure type"]; -} diff --git a/proto/path/qos/jsonrpc.proto b/proto/path/qos/jsonrpc.proto deleted file mode 100644 index 7645060d0..000000000 --- a/proto/path/qos/jsonrpc.proto +++ /dev/null @@ -1,45 +0,0 @@ -syntax = "proto3"; -package path.qos; - -option go_package = "github.com/buildwithgrove/path/observation/qos"; - -// JsonRpcRequest represents essential fields of a JSON-RPC request for observation purposes. -// Reference: https://www.jsonrpc.org/specification#request_object -message JsonRpcRequest { - // Client-established identifier. Must be a String, Number, or NULL if present. - string id = 1; - - // Name of the JSON-RPC method being called (e.g., eth_chainId for EVM chains) - string method = 2; - - // Note: This message captures only essential JSON-RPC fields. - // Add fields as needed. -} - -// JsonRpcResponse represents essential fields of a JSON-RPC response for observation purposes. -// Reference: https://www.jsonrpc.org/specification#response_object -message JsonRpcResponse { - // Must match the id value from the corresponding request - string id = 1; - - // JSON-serializable response data - string result = 2; - - // Error details, if the request failed - optional JsonRpcResponseError err = 3; - - // Note: This message captures only essential JSON-RPC fields. - // Add fields as needed. -} - -// JsonRpcResponseError represents core error fields from a JSON-RPC response. -// Reference: https://www.jsonrpc.org/specification#error_object -// -// Only includes fields required for QoS observations. -message JsonRpcResponseError { - // Error code indicating the type of failure - int64 code = 1; - - // Human-readable error description - string message = 2; -} \ No newline at end of file diff --git a/proto/path/qos/observations.proto b/proto/path/qos/observations.proto index f0ad3832d..2fa3019c0 100644 --- a/proto/path/qos/observations.proto +++ b/proto/path/qos/observations.proto @@ -3,24 +3,19 @@ package path.qos; option go_package = "github.com/buildwithgrove/path/observation/qos"; -import "path/qos/evm.proto"; -import "path/qos/solana.proto"; -import "path/qos/cometbft.proto"; +import "path/qos/framework/observations.proto"; // Import JSONRPC Framework observations // Observations contains QoS measurements for a single service request. -// Currently supports: -// - Solana blockchain service -// - EVM blockchains service +// Currently only supports JSONRPC-based service observations. message Observations { // service_observations contains QoS measurements specific to the service type oneof service_observations { - // solana contains QoS measurements for a single Solana blockchain request - SolanaRequestObservations solana = 1; + // jsonrpc contains QoS measurements for a JSON-RPC based service request + path.qos.framework.Observations jsonrpc_service = 1; - // evm contains QoS measurements for a single EVM blockchain request - EVMRequestObservations evm = 2; - - // cometbft contains QoS measurements for a single CometBFT blockchain request - CometBFTRequestObservations cometbft = 3; + // Additional service types can be added here in the future + // For example: + // RESTObservations rest = 2; + // GraphQLObservations graphql = 3; } } diff --git a/proto/path/qos/solana.proto b/proto/path/qos/solana.proto deleted file mode 100644 index dbdcd819c..000000000 --- a/proto/path/qos/solana.proto +++ /dev/null @@ -1,59 +0,0 @@ -syntax = "proto3"; -package path.qos; - -option go_package = "github.com/buildwithgrove/path/observation/qos"; - -import "path/qos/jsonrpc.proto"; - -// SolanaRequestObservations captures QoS data for a single Solana blockchain service request, -// including all observations made during potential retries. -message SolanaRequestObservations { - // JSON-RPC request to the Solana blockchain service - // TODO_TECHDEBT: This assumes all SolanaVM blockchains only (and always) support JSON-RPC. - // May need expansion/refactoring for future blockchain support. - JsonRpcRequest jsonrpc_request = 1; - - // Multiple observations possible if: - // - Original endpoint returns invalid response - // - Retry mechanism activates - repeated SolanaEndpointObservation endpoint_observations = 2; -} - -// SolanaEndpointObservation captures a single endpoint's response to a request -message SolanaEndpointObservation { - // Address of the endpoint handling the request - string endpoint_addr = 1; - - oneof response_observation { - // Response from getEpochInfo - // Docs: https://solana.com/docs/rpc/http/getepochinfo - SolanaGetEpochInfoResponse get_epoch_info_response = 2; - - // Response from getHealth - // Docs: https://solana.com/docs/rpc/http/gethealth - SolanaGetHealthResponse get_health_response = 3; - - // Responses not used in endpoint validation (e.g., getAccountInfo) - SolanaUnrecognizedResponse unrecognized_response = 4; - } -} - -// SolanaEpochInfoResponse stores getEpochInfo response data -// Docs: https://solana.com/docs/rpc/http/getepochinfo -message SolanaGetEpochInfoResponse { - // Stored as uint64 for cross-instance validation - uint64 block_height = 1; - uint64 epoch = 2; -} - -// SolanaGetHealthResponse stores getHealth response data -// Docs: https://solana.com/docs/rpc/http/gethealth -message SolanaGetHealthResponse { - string result = 1; -} - -// SolanaUnrecognizedResponse stores responses from methods not used in validation -// Examples: getTokenSupply, getTransaction -message SolanaUnrecognizedResponse { - JsonRpcResponse jsonrpc_response = 1; -} \ No newline at end of file diff --git a/qos/framework/jsonrpc/context_state_update.go b/qos/framework/jsonrpc/context_state_update.go index 395781943..a810dfbfa 100644 --- a/qos/framework/jsonrpc/context_state_update.go +++ b/qos/framework/jsonrpc/context_state_update.go @@ -1,50 +1,58 @@ -package jsonrpc - -import ( - -) +package framework // StateUpdateContext provides context and helper methods for updating service state. type StateUpdateContext struct { - // The result data observations - Results []*ResultData - // Current service state (read-only copy) - currentState map[string]string - - // New state being built - newState map[string]string -} + // Provides direct access to the read-only state + *ServiceState -// CopyCurrentState creates a copy of the current state as the starting point. -func (ctx *StateUpdateContext) CopyCurrentState() { - ctx.newState = make(map[string]string, len(ctx.currentState)) - for k, v := range ctx.currentState { - ctx.newState[k] = v - } + // custom state updater function to be called from the context. + stateUpdater StateUpdater + + // updated endpoints on which the state update should be based. + updatedEndpoints []*Endpoint + + // tracks the set of params set for update through the context. + paramsToUpdate *StateParameterUpdateSet } -// SetValue sets a value in the new state. -func (ctx *StateUpdateContext) SetValue(key, value string) *StateUpdateContext { - ctx.newState[key] = value - return ctx +func (ctx *StateUpdateContext) updateFromEndpoints(updatedEndpoints) error { + // get the list of params to update by calling the custom state updater. + paramsToUpdate := ctx.stateUpdater(ctx) + + // Update the state parameters through the service state. + return ctx.ServiceState.updateParameters(paramsToUpdate) } -// DeleteValue removes a value from the new state. -func (ctx *StateUpdateContext) DeleteValue(key string) *StateUpdateContext { - delete(ctx.newState, key) - return ctx +func (ctx *StateUpdateContext) GetUpdatedEndpoints() []*Endpoint { + return ctx.updatedEndpoints } -// GetValue gets a value from the current state. -func (ctx *StateUpdateContext) GetValue(key string) (string, bool) { - value, exists := ctx.currentState[key] - return value, exists +func (ctx *StateUpdateContext) SetIntParam(paramName string, value int) { + param := &StateParameter{ + intValue: &value, + } + + ctx.paramsToUpdate.Set(paramName, param) } -// GetState returns the new state map. -func (ctx *StateUpdateContext) GetState() map[string]string { - return ctx.newState +func (ctx *StateUpdateContext) SetStrParam(paramName, value string) { + param := &StateParameter{ + strValue: &value, + } + + ctx.paramsToUpdate.Set(paramName, param) } +func (ctx *StateUpdateContext) SetConsensusParam(paramName string, consensusValues map[string]int) { + param := &StateParameter{ + consensValues: consensusValues, + } + ctx.paramsToUpdate.Set(paramName, param) +} + +// Return the set of updated state parameters. +func (ctx *StateUpdateContext) BuildStateParameterUpdateSet() StateParameterUpdateSet { + return ctx.paramsToUpdate +} diff --git a/qos/framework/jsonrpc/endpoint_store.go b/qos/framework/jsonrpc/endpoint_store.go index b1df20657..47b75b474 100644 --- a/qos/framework/jsonrpc/endpoint_store.go +++ b/qos/framework/jsonrpc/endpoint_store.go @@ -13,6 +13,17 @@ type endpointStore struct { endpoints map[protocol.EndpointAddr]Endpoint } +func (es *endpointStore) updateStoredEndpoints(endpointQueries []*endpointQuery) []Endpoint { + es.endpointsMu.Lock() + defer es.endpointsMu.Unlock() + + endpoints := make([]Endpoint, len(endpointQueries)) + for index, endpointQuery := range endpointQueries { + endpoint := es.endpoints[endpointQuery.endpointAddr] + if + } +} + // storeEndpoint stores or updates an endpoint in the store. func (es *endpointStore) storeEndpoint(addr protocol.EndpointAddr, endpoint Endpoint) { es.endpointsMu.Lock() diff --git a/qos/framework/jsonrpc/example_evm/state_update_archival.go b/qos/framework/jsonrpc/example_evm/state_update_archival.go new file mode 100644 index 000000000..3d9aa817b --- /dev/null +++ b/qos/framework/jsonrpc/example_evm/state_update_archival.go @@ -0,0 +1,54 @@ +const ( + stateParameterNameBlockNumber = "blockNumber" + endpointResultNameBlockNumber = stateParameterNameBlockNumber +) + +func updateArchivalState(ctx ServiceStateUpdateContext) *StateParamUpdates { + // TODO_IMPROVE(@commoddity): apply an expiry time to the `expectedBalance`. + // + expectedBalance, found := ctx.GetStrParam(paramArchivalBalance) + // expectedBalance already set: skip the rest of the processing + if found { + return nil + } + + perceivedBlockNumber, found := ctx.GetIntParam(paramBlockNumber) + // no perceived block number set yet: skip archival state update. + if !found { + return nil + } + + // Set the archival block if not set already. + archivalBlock, found := ctx.GetStrParam(paramArchivalBlock) + if !found { + archivalBlock = calculateArchivalBlock(config, perceivedBlockNumber) + ctx.SetStrParam(paramArchivalBlock, archivalBlock) + } + + // fetch the latest archival balance consensus map. + balanceConsensus := ctx.GetConsensusParam(paramArchivalBalanceConsensus) + + // Update the archival balance consensus map from the endpoint results. + for _, updatedEndpoint := range ctx.GetUpdatedEndpoints() { + // skip out of sync endpoints + endpointBlockNumber, found := updatedEndpoint.GetIntResult(resultBlockNumber) + if !found || endpointBlockNumber < perceivedBlockNumber { + continue + } + + // skip endpoints with no archival balance result. + endpointArchivalBalance, found := updatedEndpoint.GetStrResult(resultArchivalBalance) + if !found { + continue + } + + // update the balance consensus with the endpoint's result. + balanceConsensus[endpointArchivalBalance]++ + } + + // Set the archival balance concensus parameter on the context. + ctx.SetConsensusParam(paramArchivalBalanceConcensus, balanceConsensus) + + // Build and return the set of updated state parameters. + return ctx.BuildStateParameterUpdateSet() +} diff --git a/qos/framework/jsonrpc/framework.go b/qos/framework/jsonrpc/framework.go index 34a999970..908f0ddc8 100644 --- a/qos/framework/jsonrpc/framework.go +++ b/qos/framework/jsonrpc/framework.go @@ -48,7 +48,7 @@ type QoSDefinition struct { type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult // StateUpdater updates service state based on endpoint results -type StateUpdater func(ctx *StateUpdateContext) map[string]string +type StateUpdater func(ctx *StateUpdateContext) *StateParameterUpdateSet // EndpointSelector chooses an endpoint for a request based on service state type EndpointSelector func(ctx *EndpointSelectionContext) (protocol.EndpointAddr, error) diff --git a/qos/framework/jsonrpc/qos.go b/qos/framework/jsonrpc/qos.go index 1dbee4ec4..92e9ad4f3 100644 --- a/qos/framework/jsonrpc/qos.go +++ b/qos/framework/jsonrpc/qos.go @@ -55,12 +55,17 @@ func (s *QoSService) ParseHTTPRequest( func (s *QoSService) ApplyObservations(observations *qosobservations.Observations) error ) { -// -> Framework updates the endpoints + state as part of ApplyObservations -// -> custom ResultBuilders return the set of attributes for the endpoint. -// --> + expiryTime to drop endpoint attributes after expiry. jsonrpcSvcObservations := observations.GetJsonrpc() - endpointResults := extractEndpointResultsFromObservations(jsonrpcSvcObservations) - return s.serviceState.UpdateFromEndpointResults(endpointResults) + endpointQueries := extractEndpointQueriesFromObservations(jsonrpcSvcObservations) + + // update the stored endpoints + updatedEndpoints := s.serviceState.updateStoredEndpoints(endpointQueries) + + // instantiate a state update context. + stateUpdateCtx := s.buildServiceStateUpdateContext() + + // update the service state through the context, using stored endpoints. + return stateUpdateCtx.updateFromEndpoints(updatedEndpoints) } // buildEndpointQueryResultContext creates a context for processing endpoint queries @@ -88,7 +93,7 @@ func (q *QoS) buildEndpointSelectionContext() *EndpointSelectionContext { // Service State (read-only) // Allows the custom QoS service to base the validation/selection of endpoints on current state. // Includes the endpoint store in read-only mode. - *ReadonlyServiceState: rc.serviceState, + *ReadonlyServiceState: q.serviceState, // The endpoint selector logic defined by the custom QoS service defintion. customSelector: q.qosDefinition.EndpointSelector, } @@ -96,5 +101,10 @@ func (q *QoS) buildEndpointSelectionContext() *EndpointSelectionContext { // TODO_IN_THIS_PR: implement this method. func (q *QoS) buildServiceStateUpdateContext() *ServiceStateUpdateContext { + return &ServiceStateUpdateContext { + ServiceState: q.ServiceState, + // the custom service's State Updater function. + stateUpdater: q.qosDefinition.StateUpdater, + } } diff --git a/qos/framework/jsonrpc/service_state.go b/qos/framework/jsonrpc/service_state.go deleted file mode 100644 index 0c25f4b5d..000000000 --- a/qos/framework/jsonrpc/service_state.go +++ /dev/null @@ -1,192 +0,0 @@ -package jsonrpc - -import ( - "sync" - "time" - - "github.com/buildwithgrove/path/protocol" - "github.com/pokt-network/poktroll/pkg/polylog" -) - -// TODO_IN_THIS_PR: move endpoint_store under service_state. - -// TODO_IN_THIS_PR: Add a Consensus type as a StateParamValue type (and find better names): -// e.g. ReadonlyServiceState.GetConsensusValue(key string) Consensus (map[string]int) ==> for Archival checks (and maybe blockNumber) - -// TODO_IN_THIS_PR: find the best fitting file for this. -// ====> framework.go ???? -// EndpointSelector is called to select eligible endpoints based on the service state. -type EndpointSelector func(ctx *EndpointSelectionContext, []protocol.Endpoint) (protocol.EndpointAddr, error) - -// EndpointResultBuilder is called to process a result based on the current service state. -type EndpointResultBuilder func(ctx *ResultContext) *ResultData - -// StateUpdater is called to update the service state based on observed results. -type StateUpdater func(ctx *StateUpdateContext) *FullState - -// TODO_IN_THIS_PR: choose a name for this struct. -type FullState struct { - -} - - - -// serviceState provides the functionality required for selecting an endpoint from the set of available ones. -var _ protocol.EndpointSelector = &serviceState{} - - -// serviceState maintains the state for a QoS service. -// It provides methods for updating endpoint result data, applying observations, and filtering endpoints based on the service's state. -// It implements the protocol.EndpointSelector interface. -type serviceState struct { - // mu protects the state map from concurrent access - mu sync.RWMutex - - // TODO_IN_THIS_PR: add the data type required for archival checks ONLY (e.g. a map[string]json.RawMessage) - // state is a simple key-value store for the service state - // This is intentionally kept as string-to-string for simplicity - state map[string]string - - // logger for diagnostics - logger polylog.Logger - - // Custom service callbacks - resultProcessor ResultProcessor - stateUpdater StateUpdater - endpointSelector EndpointSelector - - - endpointStore *endpointStore - - // Map of method-specific result builders, specified by the custom QoS service. - // The framework will utilize a default result builder for JSONRC methods not specified by the custom QoS service. - MethodResultBuilders map[jsonrpc.Method]EndpointResultBuilder - -} - - -func (s *serviceState) buildResult(parsedResult *EndpointResult) *EndpointResult { - // Create result context for the processor - ctx := &EndpointResultContext{ - EndpointAddr: call.EndpointAddr, /// <<< remove: the Result should already have an endpointCall pointer in it. - Request: call.Request, - Response: result.parseResult.Response, - - // Process valid JSONRPC response based on method. - // The context will use a default builder if a builder matching the method is not found. - resultBuilder: s.methodResultBuilders[call.Request.Method] - } - - return ctx.buildResult() -} - - -// ProcessResult calls the custom processor with the result data -// and the current service state, returning the updated result data. -func (s *ServiceState) ProcessResult(resultData *ResultData, endpointCtx *EndpointContext) *ResultData { - stateCopy := s.GetStateCopy() - - // Create the result context - ctx := &ResultContext{ - ResultData: resultData, - EndpointCtx: endpointCtx, - state: stateCopy, - } - - // Call the custom processor with the context - return s.resultProcessor(ctx) -} - -// UpdateStateFromResults calls the custom updater with the results -// and the current service state, updating the service state. -func (s *ServiceState) UpdateStateFromResults(results []*ResultData) { - // No need to process if no custom state updater is provided. - if stateUpdater == nil { - return - } - - // No need to process if no results - if len(results) == 0 { - return - } - - stateCopy := s.GetStateCopy() - - // Create the state update context - ctx := &StateUpdateContext{ - Results: results, - currentState: stateCopy, - newState: make(map[string]string), - } - - // Initialize with a copy of the current state - ctx.CopyCurrentState() - - // Call the custom updater with the context - updatedState := s.stateUpdater(ctx) - - // Acquire write lock to update the state - s.mu.Lock() - defer s.mu.Unlock() - - // Replace the state with the updated state - s.state = updatedState -} - -// Select initializes an instance of EndpointSelectionContext to which it delegates the endpoint selection. -func (s *ServiceState) Select(request *jsonrpc.Request, endpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { - stateCopy := s.GetStateCopy() - - // Create the endpoint selection context - ctx := &EndpointSelectionContext{ - logger: s.logger, - Request: request, - Endpoints: unsanctionedEndpoints, - State: stateCopy, - - endpointStore: s.endpointStore, - customSelector: s.endpointSelector, - selected: make([]Endpoint, 0), - } - - return ctx.selectEndpoint() -} - -// GetStateValue retrieves a value from the service state by key. -// Returns the value and a boolean indicating if the key was found. -func (s *ServiceState) GetStateValue(key string) (string, bool) { - s.mu.RLock() - defer s.mu.RUnlock() - - value, exists := s.state[key] - return value, exists -} - -// SetStateValue sets a value in the service state by key. -func (s *ServiceState) SetStateValue(key, value string) { - s.mu.Lock() - defer s.mu.Unlock() - - s.state[key] = value -} - -// DeleteStateValue removes a value from the service state by key. -func (s *ServiceState) DeleteStateValue(key string) { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.state, key) -} - -// GetStateCopy returns a copy of the entire service state. -func (s *ServiceState) GetStateCopy() map[string]string { - s.mu.RLock() - defer s.mu.RUnlock() - - stateCopy := make(map[string]string, len(s.state)) - for k, v := range s.state { - stateCopy[k] = v - } - - return stateCopy -} diff --git a/qos/framework/jsonrpc/state.go b/qos/framework/jsonrpc/state.go new file mode 100644 index 000000000..e18f27535 --- /dev/null +++ b/qos/framework/jsonrpc/state.go @@ -0,0 +1,86 @@ +package jsonrpc + +import ( + "sync" + "time" + + "github.com/buildwithgrove/path/protocol" + "github.com/pokt-network/poktroll/pkg/polylog" +) + +// serviceState maintains the state for a QoS service. +// It provides methods for: +// - Updating state parameters using observations. +// - Updating endpoint results using observations +// - Reading state parameters and endpoints (Read Only) for: +// - building endpoint results +// - endpoint selection +// +type ServiceState struct { + // logger for diagnostics + logger polylog.Logger + + // mu protects the state map from concurrent access + mu sync.RWMutex + + // stateParameters + parameters map[string]*StateParameter + + endpointStore *endpointStore +} + +func (s *ServiceState) GetStrParam(paramName string) (string, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + + param, ok := s.parameters[paramName] + if !ok { + return "", false + } + + return param.GetStr() +} + +func (s *ServiceState) GetIntParam(paramName string) (int, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + + param, ok := s.parameters[paramName] + if !ok { + return 0, false + } + + return param.GetInt() + +} + +func (s *ServiceState) GetConsensusParam(paramName string) (map[string]int, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + + param, ok := s.parameters[paramName] + if !ok { + return nil, false + } + + return param.GetConsensus() +} + +// Returns the stored Endpoint structs matching the endpoint queries. +func (s *serviceState) updateStoredEndpoints(endpointQueries []*endpointQuery) []Endpoint { + s.mu.Lock() + defer s.mu.Unlock() + + return s.endpointStore.updateEndpoints(endpointQueries) +} + +func (s *ServiceState) updateParameters(updates StateParameterUpdateSet) error { + s.mu.Lock() + defer s.mu.Unlock() + + for paramName, param := range updates.Updates { + s.parameters[paramName] = param + } + + return nil +} diff --git a/qos/framework/jsonrpc/state_parameter.go b/qos/framework/jsonrpc/state_parameter.go new file mode 100644 index 000000000..002130916 --- /dev/null +++ b/qos/framework/jsonrpc/state_parameter.go @@ -0,0 +1,56 @@ +package framework + +// StateParameter stores related values for a single QoS service component +// Example: archival state: +// - contract address +// - count of each reported balance +// +// Supported value types (PR #210): +// - string: e.g., contract address for archival checks +// - integer: e.g., block number from blockchain service +// - consensus: e.g., balance reports as map[string]int{"0x1234": 5} +// +// Examples: +// - BlockNumber: IntValues{"blockNumber": 12345} +// - ArchivalState: +// - StringValues{"contractAddress": "0xADDR"} +// - ConsensusValues{"0x12345": 5, "0x5678": 8} +type StateParameter struct { + // stores string type state value + strValue *string + + // stores integer type state value + intValue *int + + // stores consensus type state value + consensusValues map[string]int +} + +func (sp *StateParameter) GetStr() (string, bool) { + if sp.strValue == nil { + return "", false + } + + return *sp.strValue, true +} + +func (sp *StateParameter) GetInt() (int, bool) { + if sp.intValue == nil { + return 0, false + } + + return *sp.intValue, true +} + +func (sp *StateParameter) GetConsensus() (map[string]int, bool) { + if sp.consensusValues == nil { + return nil, false + } + + consensusCopy := make(map[string]int) + for k, v := range sp.consensusValues { + consensusCopy[k] = v + } + + return consensusCopy, true +} diff --git a/qos/framework/jsonrpc/state_update.go b/qos/framework/jsonrpc/state_update.go new file mode 100644 index 000000000..52bbef6e6 --- /dev/null +++ b/qos/framework/jsonrpc/state_update.go @@ -0,0 +1,6 @@ +package framework + +// TODO_FUTURE(@adshmh): Support deleting StateParameters by adding a `ToDelete` field +type StateParameterUpdateSet struct { + Updates map[string]*StateParameter +} From 57e84f85a9a800e4d3d853273b8b9549a2bb7919 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sat, 26 Apr 2025 11:23:04 -0400 Subject: [PATCH 12/26] Move all files to framework directory --- qos/framework/{jsonrpc => }/client_http_response.go | 0 qos/framework/{jsonrpc => }/context_endpoint_checks.go | 0 qos/framework/{jsonrpc => }/context_endpoint_result.go | 0 qos/framework/{jsonrpc => }/context_endpoint_selection.go | 0 qos/framework/{jsonrpc => }/context_request.go | 0 qos/framework/{jsonrpc => }/context_state_update.go | 0 qos/framework/{jsonrpc => }/endpoint.go | 0 qos/framework/{jsonrpc => }/endpoint_errors.go | 0 qos/framework/{jsonrpc => }/endpoint_query.go | 0 qos/framework/{jsonrpc => }/endpoint_query_result.go | 0 qos/framework/{jsonrpc => }/endpoint_query_result_defaults.go | 0 qos/framework/{jsonrpc => }/endpoint_sanction.go | 0 qos/framework/{jsonrpc => }/endpoint_sanction_defaults.go | 0 qos/framework/{jsonrpc => }/endpoint_store.go | 0 qos/framework/{jsonrpc => }/example_evm/check.go | 0 qos/framework/{jsonrpc => }/example_evm/config.go | 0 .../{jsonrpc => }/example_evm/endpoint_result_blocknumber.go | 0 .../{jsonrpc => }/example_evm/endpoint_result_chainid.go | 0 qos/framework/{jsonrpc => }/example_evm/endpoint_selection.go | 0 qos/framework/{jsonrpc => }/example_evm/qos.go | 0 qos/framework/{jsonrpc => }/example_evm/state_update.go | 0 qos/framework/{jsonrpc => }/example_evm/state_update_archival.go | 0 qos/framework/{jsonrpc => }/framework.go | 0 qos/framework/{jsonrpc => }/jsonrpc_errors.go | 0 qos/framework/{jsonrpc => }/observations.go | 0 qos/framework/{jsonrpc => }/observations_endpoint.go | 0 qos/framework/{jsonrpc => }/observations_endpoint_error.go | 0 qos/framework/{jsonrpc => }/observations_endpoint_result.go | 0 qos/framework/{jsonrpc => }/observations_request.go | 0 qos/framework/{jsonrpc => }/observations_request_error.go | 0 qos/framework/{jsonrpc => }/qos.go | 0 qos/framework/{jsonrpc => }/request.go | 0 qos/framework/{jsonrpc => }/request_errors.go | 0 qos/framework/{jsonrpc => }/request_journal.go | 0 qos/framework/{jsonrpc => }/state.go | 0 qos/framework/{jsonrpc => }/state_parameter.go | 0 qos/framework/{jsonrpc => }/state_update.go | 0 37 files changed, 0 insertions(+), 0 deletions(-) rename qos/framework/{jsonrpc => }/client_http_response.go (100%) rename qos/framework/{jsonrpc => }/context_endpoint_checks.go (100%) rename qos/framework/{jsonrpc => }/context_endpoint_result.go (100%) rename qos/framework/{jsonrpc => }/context_endpoint_selection.go (100%) rename qos/framework/{jsonrpc => }/context_request.go (100%) rename qos/framework/{jsonrpc => }/context_state_update.go (100%) rename qos/framework/{jsonrpc => }/endpoint.go (100%) rename qos/framework/{jsonrpc => }/endpoint_errors.go (100%) rename qos/framework/{jsonrpc => }/endpoint_query.go (100%) rename qos/framework/{jsonrpc => }/endpoint_query_result.go (100%) rename qos/framework/{jsonrpc => }/endpoint_query_result_defaults.go (100%) rename qos/framework/{jsonrpc => }/endpoint_sanction.go (100%) rename qos/framework/{jsonrpc => }/endpoint_sanction_defaults.go (100%) rename qos/framework/{jsonrpc => }/endpoint_store.go (100%) rename qos/framework/{jsonrpc => }/example_evm/check.go (100%) rename qos/framework/{jsonrpc => }/example_evm/config.go (100%) rename qos/framework/{jsonrpc => }/example_evm/endpoint_result_blocknumber.go (100%) rename qos/framework/{jsonrpc => }/example_evm/endpoint_result_chainid.go (100%) rename qos/framework/{jsonrpc => }/example_evm/endpoint_selection.go (100%) rename qos/framework/{jsonrpc => }/example_evm/qos.go (100%) rename qos/framework/{jsonrpc => }/example_evm/state_update.go (100%) rename qos/framework/{jsonrpc => }/example_evm/state_update_archival.go (100%) rename qos/framework/{jsonrpc => }/framework.go (100%) rename qos/framework/{jsonrpc => }/jsonrpc_errors.go (100%) rename qos/framework/{jsonrpc => }/observations.go (100%) rename qos/framework/{jsonrpc => }/observations_endpoint.go (100%) rename qos/framework/{jsonrpc => }/observations_endpoint_error.go (100%) rename qos/framework/{jsonrpc => }/observations_endpoint_result.go (100%) rename qos/framework/{jsonrpc => }/observations_request.go (100%) rename qos/framework/{jsonrpc => }/observations_request_error.go (100%) rename qos/framework/{jsonrpc => }/qos.go (100%) rename qos/framework/{jsonrpc => }/request.go (100%) rename qos/framework/{jsonrpc => }/request_errors.go (100%) rename qos/framework/{jsonrpc => }/request_journal.go (100%) rename qos/framework/{jsonrpc => }/state.go (100%) rename qos/framework/{jsonrpc => }/state_parameter.go (100%) rename qos/framework/{jsonrpc => }/state_update.go (100%) diff --git a/qos/framework/jsonrpc/client_http_response.go b/qos/framework/client_http_response.go similarity index 100% rename from qos/framework/jsonrpc/client_http_response.go rename to qos/framework/client_http_response.go diff --git a/qos/framework/jsonrpc/context_endpoint_checks.go b/qos/framework/context_endpoint_checks.go similarity index 100% rename from qos/framework/jsonrpc/context_endpoint_checks.go rename to qos/framework/context_endpoint_checks.go diff --git a/qos/framework/jsonrpc/context_endpoint_result.go b/qos/framework/context_endpoint_result.go similarity index 100% rename from qos/framework/jsonrpc/context_endpoint_result.go rename to qos/framework/context_endpoint_result.go diff --git a/qos/framework/jsonrpc/context_endpoint_selection.go b/qos/framework/context_endpoint_selection.go similarity index 100% rename from qos/framework/jsonrpc/context_endpoint_selection.go rename to qos/framework/context_endpoint_selection.go diff --git a/qos/framework/jsonrpc/context_request.go b/qos/framework/context_request.go similarity index 100% rename from qos/framework/jsonrpc/context_request.go rename to qos/framework/context_request.go diff --git a/qos/framework/jsonrpc/context_state_update.go b/qos/framework/context_state_update.go similarity index 100% rename from qos/framework/jsonrpc/context_state_update.go rename to qos/framework/context_state_update.go diff --git a/qos/framework/jsonrpc/endpoint.go b/qos/framework/endpoint.go similarity index 100% rename from qos/framework/jsonrpc/endpoint.go rename to qos/framework/endpoint.go diff --git a/qos/framework/jsonrpc/endpoint_errors.go b/qos/framework/endpoint_errors.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_errors.go rename to qos/framework/endpoint_errors.go diff --git a/qos/framework/jsonrpc/endpoint_query.go b/qos/framework/endpoint_query.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_query.go rename to qos/framework/endpoint_query.go diff --git a/qos/framework/jsonrpc/endpoint_query_result.go b/qos/framework/endpoint_query_result.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_query_result.go rename to qos/framework/endpoint_query_result.go diff --git a/qos/framework/jsonrpc/endpoint_query_result_defaults.go b/qos/framework/endpoint_query_result_defaults.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_query_result_defaults.go rename to qos/framework/endpoint_query_result_defaults.go diff --git a/qos/framework/jsonrpc/endpoint_sanction.go b/qos/framework/endpoint_sanction.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_sanction.go rename to qos/framework/endpoint_sanction.go diff --git a/qos/framework/jsonrpc/endpoint_sanction_defaults.go b/qos/framework/endpoint_sanction_defaults.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_sanction_defaults.go rename to qos/framework/endpoint_sanction_defaults.go diff --git a/qos/framework/jsonrpc/endpoint_store.go b/qos/framework/endpoint_store.go similarity index 100% rename from qos/framework/jsonrpc/endpoint_store.go rename to qos/framework/endpoint_store.go diff --git a/qos/framework/jsonrpc/example_evm/check.go b/qos/framework/example_evm/check.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/check.go rename to qos/framework/example_evm/check.go diff --git a/qos/framework/jsonrpc/example_evm/config.go b/qos/framework/example_evm/config.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/config.go rename to qos/framework/example_evm/config.go diff --git a/qos/framework/jsonrpc/example_evm/endpoint_result_blocknumber.go b/qos/framework/example_evm/endpoint_result_blocknumber.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/endpoint_result_blocknumber.go rename to qos/framework/example_evm/endpoint_result_blocknumber.go diff --git a/qos/framework/jsonrpc/example_evm/endpoint_result_chainid.go b/qos/framework/example_evm/endpoint_result_chainid.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/endpoint_result_chainid.go rename to qos/framework/example_evm/endpoint_result_chainid.go diff --git a/qos/framework/jsonrpc/example_evm/endpoint_selection.go b/qos/framework/example_evm/endpoint_selection.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/endpoint_selection.go rename to qos/framework/example_evm/endpoint_selection.go diff --git a/qos/framework/jsonrpc/example_evm/qos.go b/qos/framework/example_evm/qos.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/qos.go rename to qos/framework/example_evm/qos.go diff --git a/qos/framework/jsonrpc/example_evm/state_update.go b/qos/framework/example_evm/state_update.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/state_update.go rename to qos/framework/example_evm/state_update.go diff --git a/qos/framework/jsonrpc/example_evm/state_update_archival.go b/qos/framework/example_evm/state_update_archival.go similarity index 100% rename from qos/framework/jsonrpc/example_evm/state_update_archival.go rename to qos/framework/example_evm/state_update_archival.go diff --git a/qos/framework/jsonrpc/framework.go b/qos/framework/framework.go similarity index 100% rename from qos/framework/jsonrpc/framework.go rename to qos/framework/framework.go diff --git a/qos/framework/jsonrpc/jsonrpc_errors.go b/qos/framework/jsonrpc_errors.go similarity index 100% rename from qos/framework/jsonrpc/jsonrpc_errors.go rename to qos/framework/jsonrpc_errors.go diff --git a/qos/framework/jsonrpc/observations.go b/qos/framework/observations.go similarity index 100% rename from qos/framework/jsonrpc/observations.go rename to qos/framework/observations.go diff --git a/qos/framework/jsonrpc/observations_endpoint.go b/qos/framework/observations_endpoint.go similarity index 100% rename from qos/framework/jsonrpc/observations_endpoint.go rename to qos/framework/observations_endpoint.go diff --git a/qos/framework/jsonrpc/observations_endpoint_error.go b/qos/framework/observations_endpoint_error.go similarity index 100% rename from qos/framework/jsonrpc/observations_endpoint_error.go rename to qos/framework/observations_endpoint_error.go diff --git a/qos/framework/jsonrpc/observations_endpoint_result.go b/qos/framework/observations_endpoint_result.go similarity index 100% rename from qos/framework/jsonrpc/observations_endpoint_result.go rename to qos/framework/observations_endpoint_result.go diff --git a/qos/framework/jsonrpc/observations_request.go b/qos/framework/observations_request.go similarity index 100% rename from qos/framework/jsonrpc/observations_request.go rename to qos/framework/observations_request.go diff --git a/qos/framework/jsonrpc/observations_request_error.go b/qos/framework/observations_request_error.go similarity index 100% rename from qos/framework/jsonrpc/observations_request_error.go rename to qos/framework/observations_request_error.go diff --git a/qos/framework/jsonrpc/qos.go b/qos/framework/qos.go similarity index 100% rename from qos/framework/jsonrpc/qos.go rename to qos/framework/qos.go diff --git a/qos/framework/jsonrpc/request.go b/qos/framework/request.go similarity index 100% rename from qos/framework/jsonrpc/request.go rename to qos/framework/request.go diff --git a/qos/framework/jsonrpc/request_errors.go b/qos/framework/request_errors.go similarity index 100% rename from qos/framework/jsonrpc/request_errors.go rename to qos/framework/request_errors.go diff --git a/qos/framework/jsonrpc/request_journal.go b/qos/framework/request_journal.go similarity index 100% rename from qos/framework/jsonrpc/request_journal.go rename to qos/framework/request_journal.go diff --git a/qos/framework/jsonrpc/state.go b/qos/framework/state.go similarity index 100% rename from qos/framework/jsonrpc/state.go rename to qos/framework/state.go diff --git a/qos/framework/jsonrpc/state_parameter.go b/qos/framework/state_parameter.go similarity index 100% rename from qos/framework/jsonrpc/state_parameter.go rename to qos/framework/state_parameter.go diff --git a/qos/framework/jsonrpc/state_update.go b/qos/framework/state_update.go similarity index 100% rename from qos/framework/jsonrpc/state_update.go rename to qos/framework/state_update.go From 19677b0ed941a78191e353b14e97f074cb586d79 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 27 Apr 2025 09:24:07 -0400 Subject: [PATCH 13/26] Add ObservationsInterpreter placeholder --- qos/framework/endpoint.go | 3 +++ qos/framework/endpoint_store.go | 6 ++--- qos/framework/observations_interpreter.go | 8 +++++++ qos/framework/qos.go | 29 +++++++++++++++++++++-- qos/framework/state.go | 4 ++++ 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 qos/framework/observations_interpreter.go diff --git a/qos/framework/endpoint.go b/qos/framework/endpoint.go index accd3c7f3..2eaea37a4 100644 --- a/qos/framework/endpoint.go +++ b/qos/framework/endpoint.go @@ -16,6 +16,9 @@ type Endpoint struct { // IntValues: {"balance": 133456789}, // } queryResults map[string]*EndpointQueryResult + + // mutex for query results + resultsMu sync.Mutex } // GetQueryResultStringValue retrieves a string attribute of a result by key. diff --git a/qos/framework/endpoint_store.go b/qos/framework/endpoint_store.go index 47b75b474..8c815373f 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/framework/endpoint_store.go @@ -10,7 +10,7 @@ import ( // It is package-private and not meant to be used directly by any entity outside the jsonrpc package. type endpointStore struct { endpointsMu sync.RWMutex - endpoints map[protocol.EndpointAddr]Endpoint + endpoints map[protocol.EndpointAddr]*Endpoint } func (es *endpointStore) updateStoredEndpoints(endpointQueries []*endpointQuery) []Endpoint { @@ -33,11 +33,11 @@ func (es *endpointStore) storeEndpoint(addr protocol.EndpointAddr, endpoint Endp es.endpoints = make(map[protocol.EndpointAddr]Endpoint) } - es.endpoints[addr] = endpoint + es.endpoints[addr] = &endpoint } // getEndpoint retrieves an endpoint by its address. -func (es *endpointStore) getEndpoint(addr protocol.EndpointAddr) (Endpoint, bool) { +func (es *endpointStore) getEndpoint(addr protocol.EndpointAddr) (*Endpoint, bool) { es.endpointsMu.RLock() defer es.endpointsMu.RUnlock() diff --git a/qos/framework/observations_interpreter.go b/qos/framework/observations_interpreter.go new file mode 100644 index 000000000..7114cb1b7 --- /dev/null +++ b/qos/framework/observations_interpreter.go @@ -0,0 +1,8 @@ +package framework + +// ObservationsInterpreter is the reference implementation for interpreting observations generated by the framework. +// e.g. IsServiceRequestSuccessful() returns true if the service request succeeded. +type ObservationsInterpreter struct { + +} + diff --git a/qos/framework/qos.go b/qos/framework/qos.go index 92e9ad4f3..37006d74f 100644 --- a/qos/framework/qos.go +++ b/qos/framework/qos.go @@ -29,7 +29,7 @@ type QoS struct { // ParseHTTPRequest handles parsing an HTTP request and validating its content // It returns a RequestQoSContext and a boolean indicating if processing should continue -func (s *QoSService) ParseHTTPRequest( +func (s *QoS) ParseHTTPRequest( _ context.Context, httpReq *http.Request, ) (*requestContext, bool) { @@ -53,7 +53,7 @@ func (s *QoSService) ParseHTTPRequest( // TODO_IN_THIS_PR: implement this method // func (qos *QoS) ParseWebsocketRequest(_ context.Context) (gateway.RequestQoSContext, bool) -func (s *QoSService) ApplyObservations(observations *qosobservations.Observations) error +func (s *QoS) ApplyObservations(observations *qosobservations.Observations) error ) { jsonrpcSvcObservations := observations.GetJsonrpc() endpointQueries := extractEndpointQueriesFromObservations(jsonrpcSvcObservations) @@ -68,6 +68,12 @@ func (s *QoSService) ApplyObservations(observations *qosobservations.Observation return stateUpdateCtx.updateFromEndpoints(updatedEndpoints) } +// Implements gateway.QoSEndpointCheckGenerator interface +func (q *QoS) GetRequiredQualityChecks(endpointAddr protocol.EndpointAddr) []RequestQoSContext { + endpointChecksCtx := q.buildEndpointChecksContext(endpointAddr) + return endpointChecksCtx.BuildRequests() +} + // buildEndpointQueryResultContext creates a context for processing endpoint queries // The context provides: // - Read-only access to current service state @@ -99,6 +105,25 @@ func (q *QoS) buildEndpointSelectionContext() *EndpointSelectionContext { } } +// TODO_IN_THIS_PR: implement this method. +func (q *QoS) buildEndpointChecksContext(endpointAddr protocol.EndpointAddr) *EndpointChecksContext { + // Ignore the second return value: an empty endpoint is a valid value when determining the required endpoint checks. + endpoint, _ := q.serviceState.GetEndpoint(endpointAddr) + + return &EndpointChecksContext{ + // Service State (read-only) + // Allows the custom QoS service to base the endpoint checks on current state. + // Includes the endpoint store in read-only mode. + ReadonlyServiceState: q.serviceState, + + // Endpoint loaded from the endpoint store. + Endpoint: endpoint, + + // Custom service's Endpoint Checks function + endpointChecksBuilder: q.qosDefinition.EndpointChecksBuilder, + } +} + // TODO_IN_THIS_PR: implement this method. func (q *QoS) buildServiceStateUpdateContext() *ServiceStateUpdateContext { return &ServiceStateUpdateContext { diff --git a/qos/framework/state.go b/qos/framework/state.go index e18f27535..5d8030998 100644 --- a/qos/framework/state.go +++ b/qos/framework/state.go @@ -29,6 +29,10 @@ type ServiceState struct { endpointStore *endpointStore } +func (s *ServiceState) GetEndpoint(endpointAddr protocol.EndpointAddr) (Endpoint, bool) { + return s.endpointStore.getEndpoint(endpointAddr) +} + func (s *ServiceState) GetStrParam(paramName string) (string, bool) { s.mu.RLock() defer s.mu.RUnlock() From b57082d50f6936a841aa56b94f44c21ffbad5dbd Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 27 Apr 2025 09:25:20 -0400 Subject: [PATCH 14/26] Update framework initialization in EVM --- .../example_evm/endpoint_result_chainid.go | 13 ++++++--- qos/framework/example_evm/qos.go | 27 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/qos/framework/example_evm/endpoint_result_chainid.go b/qos/framework/example_evm/endpoint_result_chainid.go index d39090bc0..43f29f83e 100644 --- a/qos/framework/example_evm/endpoint_result_chainid.go +++ b/qos/framework/example_evm/endpoint_result_chainid.go @@ -27,12 +27,17 @@ var _ framework.EndpointResultBuilder = endpointAttributeBuilderChainID // TODO_TECHDEBT(@adshmh): validate the `eth_chainId` request that was sent to the endpoint. // // endpointResultBuilderChainID handles attribute building and sanctioning of endpoints based on the data returned to `eth_chainId` requests. -func endpointResultBuilderChainID(ctx *framework.EndpointResultContext) *framework.ResultData { - // TODO_MVP(@adshmh): implement the framework's RequestValidator interface to filter out invalid `eth_chainId` requests. +func endpointResultBuilderChainID( + ctx *framework.EndpointQueryResultContext, + config EVMQoSServiceConfig, +) *framework.ResultData { + // TODO_MVP(@adshmh): Sanction endpoints that fail to respond to `eth_chainId` requests: + // 1. Implement the framework's RequestValidator interface to filter out invalid `eth_chainId` requests. + // 2. Sanction an endpoint that returns an error, as the requests are guaranteed to be valid. // // The endpoint returned an error response: no further processing needed. if ctx.IsJSONRPCError() { - return ctx.Error("endpoint returned a JSONRPC error response.") + return ctx.Error("endpoint returned a JSONRPC error response to an eth_chainId request.") } // TODO_MVP(@adshmh): use the contents of the result field to determine the validity of the response. @@ -52,5 +57,5 @@ func endpointResultBuilderChainID(ctx *framework.EndpointResultContext) *framewo // Store the endpoint's reported chainID as its attribute. // This attribute will be used in: // - endpoint selection: to drop misconfigured endpoints. - return ctx.Success(ctx.BuildStringAttribute(ethChainID, chainID) + return ctx.Success(ctx.BuildStringAttribute(ethChainID, chainID)) } diff --git a/qos/framework/example_evm/qos.go b/qos/framework/example_evm/qos.go index c3a3b38c2..35c0e0763 100644 --- a/qos/framework/example_evm/qos.go +++ b/qos/framework/example_evm/qos.go @@ -7,10 +7,10 @@ import ( ) // NewQoSInstance builds and returns an instance of the EVM QoS service. -func NewQoSInstance(logger polylog.Logger, evmChainID string) *QoS { +func NewQoSInstance(logger polylog.Logger, config EVMQoSServiceConfig) *QoS { logger = logger.With( "qos_instance", "evm", - "evm_chain_id", evmChainID, + "evm_chain_id", config.GetEVMChainID(), ) // Setup the QoS definitions for EVM blockchains QoS service @@ -24,8 +24,9 @@ func NewQoSInstance(logger polylog.Logger, evmChainID string) *QoS { }, // ResultBuilders for JSONRPC request methods used for endpoint attributes. - ResultBuilders: getJSONRPCMethodEndpointResultBuilders(), + ResultBuilders: getJSONRPCMethodEndpointResultBuilders(config), +/* // StateUpdater uses the endpoint attributes to update the service state. StateUpdater: @@ -36,6 +37,8 @@ func NewQoSInstance(logger polylog.Logger, evmChainID string) *QoS { // // Use the framework's default request validator: i.e. accept any valid JSONRPC request. RequestValidator: nil, +*/ + } return framework.NewQoSService(qosDefinition) @@ -43,11 +46,23 @@ func NewQoSInstance(logger polylog.Logger, evmChainID string) *QoS { // Return the set of endpoint result builders to be called by the framework. // A result builder will be called if it matches the method of the JSONRPC request from the client. -func getJSONRPCMethodEndpointResultBuilders() map[jsonrpc.Method]EndpointResultBuilder { +func getJSONRPCMethodEndpointResultBuilders(config EVMQoSServiceConfig) map[jsonrpc.Method]EndpointResultBuilder { // EVM QoS service collects endpoint attributes based on responses to the following methods. return map[jsonrpc.Method]EndpointResultBuilder { - jsonrpc.Method(methodETHChainID): endpointResultBuilderChainID, - jsonrpc.Method(methodETHBlockNumber): endpointResultBuilderBlockNumber, + jsonrpc.Method(methodETHChainID): setupResultBuilderChainID(config), + jsonrpc.Method(methodETHBlockNumber): setupResultBuilderBlockNumber(config), // TODO_IN_THIS_PR: add eth_getBalance } } + +func setupResultBuilderChainID(config EVMQoSServiceConfig) framework.EndpointQueryResultBuilder { + return func(ctx *framework.EndpointQueryResultContext) *framework.EndpointQueryResult { + return endpointResultBuilderChainID(ctx, config) + } +} + +func setupResultBuilderBlockNumber(config EVMQoSServiceConfig) framework.EndpointQueryResultBuilder { + return func(ctx *framework.EndpointQueryResultContext) *framework.EndpointQueryResult { + return endpointResultBuilderBlockNumber(ctx, config) + } +} From f5cc4eb1ca2eeabd2b4cd8e061d022738caf8757 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 27 Apr 2025 09:25:45 -0400 Subject: [PATCH 15/26] Update package name to framework --- qos/framework/context_endpoint_result.go | 2 +- qos/framework/context_endpoint_selection.go | 2 +- qos/framework/context_request.go | 2 +- qos/framework/endpoint.go | 2 +- qos/framework/endpoint_errors.go | 2 +- qos/framework/endpoint_query.go | 2 +- qos/framework/endpoint_query_result.go | 2 +- qos/framework/endpoint_sanction.go | 2 +- qos/framework/endpoint_store.go | 2 +- qos/framework/framework.go | 2 +- qos/framework/observations_endpoint.go | 2 +- qos/framework/observations_request.go | 2 +- qos/framework/observations_request_error.go | 2 +- qos/framework/qos.go | 2 +- qos/framework/request_errors.go | 2 +- qos/framework/request_journal.go | 2 +- qos/framework/state.go | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/qos/framework/context_endpoint_result.go b/qos/framework/context_endpoint_result.go index 10eb1488a..d4930bdb9 100644 --- a/qos/framework/context_endpoint_result.go +++ b/qos/framework/context_endpoint_result.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework // ResultBuilder is implemented by custom service implementations to build an EndpointResult from an endpointCall. // It processes a valid JSONRPC response for a specific method and extracts the relevant data or error information. diff --git a/qos/framework/context_endpoint_selection.go b/qos/framework/context_endpoint_selection.go index 5b00a5565..f150fbbea 100644 --- a/qos/framework/context_endpoint_selection.go +++ b/qos/framework/context_endpoint_selection.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( "github.com/buildwithgrove/path/protocol" diff --git a/qos/framework/context_request.go b/qos/framework/context_request.go index c457ddf99..88c90a3fa 100644 --- a/qos/framework/context_request.go +++ b/qos/framework/context_request.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( "encoding/json" diff --git a/qos/framework/endpoint.go b/qos/framework/endpoint.go index 2eaea37a4..a4cf57c8f 100644 --- a/qos/framework/endpoint.go +++ b/qos/framework/endpoint.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework // TODO_TECHDEBT(@adshmh): Persist this state (which may include sanctions) across restarts to maintain endpoint exclusions. // TODO_MVP(@adshmh): add support for removing expired query results. diff --git a/qos/framework/endpoint_errors.go b/qos/framework/endpoint_errors.go index a0289baa3..de2f881a5 100644 --- a/qos/framework/endpoint_errors.go +++ b/qos/framework/endpoint_errors.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework type EndpointErrorKind int const ( diff --git a/qos/framework/endpoint_query.go b/qos/framework/endpoint_query.go index a1872d40b..2d5d588d9 100644 --- a/qos/framework/endpoint_query.go +++ b/qos/framework/endpoint_query.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework // endpointQuery represents a raw communication attempt with an endpoint. // Instantiated by: RequestQoSContext. diff --git a/qos/framework/endpoint_query_result.go b/qos/framework/endpoint_query_result.go index 82f0de2f8..cd925e287 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/framework/endpoint_query_result.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( "errors" diff --git a/qos/framework/endpoint_sanction.go b/qos/framework/endpoint_sanction.go index b5df1db97..f1952134b 100644 --- a/qos/framework/endpoint_sanction.go +++ b/qos/framework/endpoint_sanction.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework // ====================== // Sanction Types diff --git a/qos/framework/endpoint_store.go b/qos/framework/endpoint_store.go index 8c815373f..8a69d4beb 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/framework/endpoint_store.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( "sync" diff --git a/qos/framework/framework.go b/qos/framework/framework.go index 908f0ddc8..2184acaad 100644 --- a/qos/framework/framework.go +++ b/qos/framework/framework.go @@ -8,7 +8,7 @@ // // Users implement the QoSDefinition interface to create custom QoS services that // leverage the framework's request handling, endpoint management, and state tracking. -package jsonrpc +package framework // TODO_FUTURE(@adshmh): Provide reasonable defaults for components to enable a no-config JSONRPC service QoS. // diff --git a/qos/framework/observations_endpoint.go b/qos/framework/observations_endpoint.go index cc67661b0..c08f1bcd2 100644 --- a/qos/framework/observations_endpoint.go +++ b/qos/framework/observations_endpoint.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( observations "github.com/buildwithgrove/path/observation/qos/framework" diff --git a/qos/framework/observations_request.go b/qos/framework/observations_request.go index c11fc0f98..af5eaf2df 100644 --- a/qos/framework/observations_request.go +++ b/qos/framework/observations_request.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( observations "github.com/buildwithgrove/path/observation/qos/framework" diff --git a/qos/framework/observations_request_error.go b/qos/framework/observations_request_error.go index 4d722c7ef..01e1c17ae 100644 --- a/qos/framework/observations_request_error.go +++ b/qos/framework/observations_request_error.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework func (re *requestError) buildObservation() *qosobservations.ValidationError { return &qosobservations.ValidationError { diff --git a/qos/framework/qos.go b/qos/framework/qos.go index 37006d74f..6263b2427 100644 --- a/qos/framework/qos.go +++ b/qos/framework/qos.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( "context" diff --git a/qos/framework/request_errors.go b/qos/framework/request_errors.go index be0930f35..f06167f5d 100644 --- a/qos/framework/request_errors.go +++ b/qos/framework/request_errors.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework type requestErrorKind int const ( diff --git a/qos/framework/request_journal.go b/qos/framework/request_journal.go index f67168a70..f9b2a00c6 100644 --- a/qos/framework/request_journal.go +++ b/qos/framework/request_journal.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework // TODO_IN_THIS_PR: verify the EmptyResponse and NoResponse scenarios: // - EmptyResponse is an EndpointQueryResult, because the endpoint did return an empty payload. diff --git a/qos/framework/state.go b/qos/framework/state.go index 5d8030998..c7a82fa11 100644 --- a/qos/framework/state.go +++ b/qos/framework/state.go @@ -1,4 +1,4 @@ -package jsonrpc +package framework import ( "sync" From e1b37e9fdb5a34cd7c4a9b6fb4e8295a5885332f Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 27 Apr 2025 09:27:05 -0400 Subject: [PATCH 16/26] Move example EVM up one level --- qos/{framework => }/example_evm/check.go | 0 qos/{framework => }/example_evm/config.go | 0 qos/{framework => }/example_evm/endpoint_result_blocknumber.go | 0 qos/{framework => }/example_evm/endpoint_result_chainid.go | 0 qos/{framework => }/example_evm/endpoint_selection.go | 0 qos/{framework => }/example_evm/qos.go | 0 qos/{framework => }/example_evm/state_update.go | 0 qos/{framework => }/example_evm/state_update_archival.go | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename qos/{framework => }/example_evm/check.go (100%) rename qos/{framework => }/example_evm/config.go (100%) rename qos/{framework => }/example_evm/endpoint_result_blocknumber.go (100%) rename qos/{framework => }/example_evm/endpoint_result_chainid.go (100%) rename qos/{framework => }/example_evm/endpoint_selection.go (100%) rename qos/{framework => }/example_evm/qos.go (100%) rename qos/{framework => }/example_evm/state_update.go (100%) rename qos/{framework => }/example_evm/state_update_archival.go (100%) diff --git a/qos/framework/example_evm/check.go b/qos/example_evm/check.go similarity index 100% rename from qos/framework/example_evm/check.go rename to qos/example_evm/check.go diff --git a/qos/framework/example_evm/config.go b/qos/example_evm/config.go similarity index 100% rename from qos/framework/example_evm/config.go rename to qos/example_evm/config.go diff --git a/qos/framework/example_evm/endpoint_result_blocknumber.go b/qos/example_evm/endpoint_result_blocknumber.go similarity index 100% rename from qos/framework/example_evm/endpoint_result_blocknumber.go rename to qos/example_evm/endpoint_result_blocknumber.go diff --git a/qos/framework/example_evm/endpoint_result_chainid.go b/qos/example_evm/endpoint_result_chainid.go similarity index 100% rename from qos/framework/example_evm/endpoint_result_chainid.go rename to qos/example_evm/endpoint_result_chainid.go diff --git a/qos/framework/example_evm/endpoint_selection.go b/qos/example_evm/endpoint_selection.go similarity index 100% rename from qos/framework/example_evm/endpoint_selection.go rename to qos/example_evm/endpoint_selection.go diff --git a/qos/framework/example_evm/qos.go b/qos/example_evm/qos.go similarity index 100% rename from qos/framework/example_evm/qos.go rename to qos/example_evm/qos.go diff --git a/qos/framework/example_evm/state_update.go b/qos/example_evm/state_update.go similarity index 100% rename from qos/framework/example_evm/state_update.go rename to qos/example_evm/state_update.go diff --git a/qos/framework/example_evm/state_update_archival.go b/qos/example_evm/state_update_archival.go similarity index 100% rename from qos/framework/example_evm/state_update_archival.go rename to qos/example_evm/state_update_archival.go From 47642d5d4a8c9d227ded712ae96989315e7e98d1 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sun, 27 Apr 2025 20:12:43 -0400 Subject: [PATCH 17/26] Regenerate observation package --- observation/gateway.pb.go | 26 +-- observation/http.pb.go | 26 +-- observation/metadata/metadata.pb.go | 2 +- observation/observations.pb.go | 26 +-- observation/protocol/morse.pb.go | 70 ++----- observation/protocol/observations.pb.go | 26 +-- observation/protocol/shannon.pb.go | 48 +---- observation/qos/evm_errors.go | 47 ----- observation/qos/evm_interpreter.go | 172 ------------------ observation/qos/evm_response_interpreters.go | 161 ---------------- observation/qos/framework/endpoint.pb.go | 26 +-- .../qos/framework/endpoint_error.pb.go | 26 +-- .../qos/framework/endpoint_query_result.pb.go | 26 +-- .../qos/framework/endpoint_sanction.pb.go | 26 +-- observation/qos/framework/jsonrpc.pb.go | 70 ++----- observation/qos/framework/observations.pb.go | 26 +-- observation/qos/framework/request.pb.go | 48 +---- observation/qos/observations.pb.go | 26 +-- qos/jsonrpc/observation.go | 14 -- 19 files changed, 95 insertions(+), 797 deletions(-) delete mode 100644 observation/qos/evm_errors.go delete mode 100644 observation/qos/evm_interpreter.go delete mode 100644 observation/qos/evm_response_interpreters.go delete mode 100644 qos/jsonrpc/observation.go diff --git a/observation/gateway.pb.go b/observation/gateway.pb.go index 74bc65e84..8a1079ad4 100644 --- a/observation/gateway.pb.go +++ b/observation/gateway.pb.go @@ -3,7 +3,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/gateway.proto @@ -104,11 +104,9 @@ type GatewayObservations struct { func (x *GatewayObservations) Reset() { *x = GatewayObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_gateway_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_gateway_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GatewayObservations) String() string { @@ -119,7 +117,7 @@ func (*GatewayObservations) ProtoMessage() {} func (x *GatewayObservations) ProtoReflect() protoreflect.Message { mi := &file_path_gateway_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -249,20 +247,6 @@ func file_path_gateway_proto_init() { if File_path_gateway_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_gateway_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*GatewayObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/http.pb.go b/observation/http.pb.go index 253f59037..192604193 100644 --- a/observation/http.pb.go +++ b/observation/http.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/http.proto @@ -35,11 +35,9 @@ type HTTPRequestObservations struct { func (x *HTTPRequestObservations) Reset() { *x = HTTPRequestObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_http_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_http_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HTTPRequestObservations) String() string { @@ -50,7 +48,7 @@ func (*HTTPRequestObservations) ProtoMessage() {} func (x *HTTPRequestObservations) ProtoReflect() protoreflect.Message { mi := &file_path_http_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -129,20 +127,6 @@ func file_path_http_proto_init() { if File_path_http_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_http_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*HTTPRequestObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/metadata/metadata.pb.go b/observation/metadata/metadata.pb.go index 52567bfcb..df178c20b 100644 --- a/observation/metadata/metadata.pb.go +++ b/observation/metadata/metadata.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/metadata/metadata.proto diff --git a/observation/observations.pb.go b/observation/observations.pb.go index c9d7f21e6..244d7688b 100644 --- a/observation/observations.pb.go +++ b/observation/observations.pb.go @@ -3,7 +3,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/observations.proto @@ -50,11 +50,9 @@ type RequestResponseObservations struct { func (x *RequestResponseObservations) Reset() { *x = RequestResponseObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_observations_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_observations_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestResponseObservations) String() string { @@ -65,7 +63,7 @@ func (*RequestResponseObservations) ProtoMessage() {} func (x *RequestResponseObservations) ProtoReflect() protoreflect.Message { mi := &file_path_observations_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -189,20 +187,6 @@ func file_path_observations_proto_init() { } file_path_http_proto_init() file_path_gateway_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_observations_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*RequestResponseObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/protocol/morse.pb.go b/observation/protocol/morse.pb.go index c540c67e6..5b4b70883 100644 --- a/observation/protocol/morse.pb.go +++ b/observation/protocol/morse.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/protocol/morse.proto @@ -157,11 +157,9 @@ type MorseRequestObservations struct { func (x *MorseRequestObservations) Reset() { *x = MorseRequestObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_protocol_morse_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_protocol_morse_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MorseRequestObservations) String() string { @@ -172,7 +170,7 @@ func (*MorseRequestObservations) ProtoMessage() {} func (x *MorseRequestObservations) ProtoReflect() protoreflect.Message { mi := &file_path_protocol_morse_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -225,11 +223,9 @@ type MorseEndpointObservation struct { func (x *MorseEndpointObservation) Reset() { *x = MorseEndpointObservation{} - if protoimpl.UnsafeEnabled { - mi := &file_path_protocol_morse_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_protocol_morse_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MorseEndpointObservation) String() string { @@ -240,7 +236,7 @@ func (*MorseEndpointObservation) ProtoMessage() {} func (x *MorseEndpointObservation) ProtoReflect() protoreflect.Message { mi := &file_path_protocol_morse_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -323,11 +319,9 @@ type MorseObservationsList struct { func (x *MorseObservationsList) Reset() { *x = MorseObservationsList{} - if protoimpl.UnsafeEnabled { - mi := &file_path_protocol_morse_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_protocol_morse_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MorseObservationsList) String() string { @@ -338,7 +332,7 @@ func (*MorseObservationsList) ProtoMessage() {} func (x *MorseObservationsList) ProtoReflect() protoreflect.Message { mi := &file_path_protocol_morse_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -482,44 +476,6 @@ func file_path_protocol_morse_proto_init() { if File_path_protocol_morse_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_protocol_morse_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*MorseRequestObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_protocol_morse_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*MorseEndpointObservation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_protocol_morse_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*MorseObservationsList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_protocol_morse_proto_msgTypes[1].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/observation/protocol/observations.pb.go b/observation/protocol/observations.pb.go index 60e56ca31..be9215d23 100644 --- a/observation/protocol/observations.pb.go +++ b/observation/protocol/observations.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/protocol/observations.proto @@ -40,11 +40,9 @@ type Observations struct { func (x *Observations) Reset() { *x = Observations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_protocol_observations_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_protocol_observations_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Observations) String() string { @@ -55,7 +53,7 @@ func (*Observations) ProtoMessage() {} func (x *Observations) ProtoReflect() protoreflect.Message { mi := &file_path_protocol_observations_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -179,20 +177,6 @@ func file_path_protocol_observations_proto_init() { } file_path_protocol_shannon_proto_init() file_path_protocol_morse_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_protocol_observations_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Observations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_protocol_observations_proto_msgTypes[0].OneofWrappers = []any{ (*Observations_Morse)(nil), (*Observations_Shannon)(nil), diff --git a/observation/protocol/shannon.pb.go b/observation/protocol/shannon.pb.go index b8b6b0e1a..009eeff73 100644 --- a/observation/protocol/shannon.pb.go +++ b/observation/protocol/shannon.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/protocol/shannon.proto @@ -33,11 +33,9 @@ type ShannonRequestObservations struct { func (x *ShannonRequestObservations) Reset() { *x = ShannonRequestObservations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_protocol_shannon_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_protocol_shannon_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ShannonRequestObservations) String() string { @@ -48,7 +46,7 @@ func (*ShannonRequestObservations) ProtoMessage() {} func (x *ShannonRequestObservations) ProtoReflect() protoreflect.Message { mi := &file_path_protocol_shannon_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -82,11 +80,9 @@ type ShannonObservationsList struct { func (x *ShannonObservationsList) Reset() { *x = ShannonObservationsList{} - if protoimpl.UnsafeEnabled { - mi := &file_path_protocol_shannon_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_protocol_shannon_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ShannonObservationsList) String() string { @@ -97,7 +93,7 @@ func (*ShannonObservationsList) ProtoMessage() {} func (x *ShannonObservationsList) ProtoReflect() protoreflect.Message { mi := &file_path_protocol_shannon_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -173,32 +169,6 @@ func file_path_protocol_shannon_proto_init() { if File_path_protocol_shannon_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_protocol_shannon_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*ShannonRequestObservations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_protocol_shannon_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*ShannonObservationsList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/qos/evm_errors.go b/observation/qos/evm_errors.go deleted file mode 100644 index 327ac6b77..000000000 --- a/observation/qos/evm_errors.go +++ /dev/null @@ -1,47 +0,0 @@ -package qos - -// EVMRequestError represents a failure in processing an EVM request or response -// Contains information extracted from error types defined in evm.proto -type EVMRequestError struct { - // For request validation errors: non-nil indicates a request error - requestValidationError *EVMRequestValidationError - // For response validation errors: non-nil indicates a response error - responseValidationError *EVMResponseValidationError -} - -// GetError returns the error type string representation. -// As of #186, this is limited to request, response or unknown error. -func (e *EVMRequestError) GetError() string { - // Request error - if e.IsRequestError() { - return e.requestValidationError.String() - } - - // Response error - if e.IsResponseError() { - return e.responseValidationError.String() - } - - // This should never happen. - return "UNKNOWN_ERROR" -} - -// IsRequestError returns true if this is a request validation error -func (e *EVMRequestError) IsRequestError() bool { - return e.requestValidationError != nil -} - -// IsResponseError returns true if this is a response validation error -func (e *EVMRequestError) IsResponseError() bool { - return e.responseValidationError != nil -} - -// String returns a string representation of the error -func (e *EVMRequestError) String() string { - return e.GetError() -} - -// Error implements the error interface -func (e *EVMRequestError) Error() string { - return e.GetError() -} diff --git a/observation/qos/evm_interpreter.go b/observation/qos/evm_interpreter.go deleted file mode 100644 index 98db3dc12..000000000 --- a/observation/qos/evm_interpreter.go +++ /dev/null @@ -1,172 +0,0 @@ -package qos - -// TODO_REFACTOR(@adshmh): Extract patterns from this package into a shared location to enable reuse across other observation interpreters (e.g., solana, cometbft). -// This would establish a consistent interpretation pattern across all QoS services while maintaining service-specific interpreters. - -import ( - "errors" -) - -var ( - // TODO_REFACTOR(@adshmh): Consider consolidating all errors in the qos package into a single file. - ErrEVMNoObservations = errors.New("no observations available") - ErrEVMNoEndpointObservationsFound = errors.New("no endpoint observations listed") -) - -// EVMObservationInterpreter provides interpretation helpers for EVM QoS observations. -// It serves as a utility layer for the EVMRequestObservations protobuf type, making -// the relationships and meaning of different observation fields clear while shielding -// the rest of the codebase from proto type details. -// -// The EVMRequestObservations type contains: -// - Various metadata (e.g., ChainID) -// - A single JSON-RPC request (exactly one) -// - A list of endpoint observations (zero or more) -// -// This interpreter allows the rest of the code to draw conclusions about the observations -// without needing to understand the structure of the proto-generated types. -type EVMObservationInterpreter struct { - Observations *EVMRequestObservations -} - -// GetRequestMethod extracts the JSON-RPC method from the request. -// Returns (method, true) if extraction succeeded -// Returns ("", false) if request is invalid or missing -func (i *EVMObservationInterpreter) GetRequestMethod() (string, bool) { - if i.Observations == nil { - return "", false - } - - // Check for validation failures using the shared method - if _, reqError := i.checkRequestValidationFailures(); reqError != nil { - return "", false - } - - // Get the JSON-RPC request from the observations - req := i.Observations.GetJsonrpcRequest() - if req == nil { - return "", false - } - - // Extract the method from the request - method := req.GetMethod() - if method == "" { - return "", false - } - - // Return the method and success flag - return method, true -} - -// GetChainID extracts the chain ID associated with the EVM observations. -// Returns (chainID, true) if available -// Returns ("", false) if chain ID is missing or observations are nil -// -// DEV_NOTE: If adapting this for other QoS observations, chainID may need to be -// renamed to ServiceID for non-blockchain services. -func (i *EVMObservationInterpreter) GetChainID() (string, bool) { - if i.Observations == nil { - return "", false - } - - chainID := i.Observations.GetChainId() - if chainID == "" { - return "", false - } - - return chainID, true -} - -// GetRequestStatus interprets the observations to determine request status information: -// - httpStatusCode: the suggested HTTP status code to return to the client -// - requestError: error details (nil if successful) -// - err: error if interpreter cannot determine status (e.g., nil observations) -func (i *EVMObservationInterpreter) GetRequestStatus() (httpStatusCode int, requestError *EVMRequestError, err error) { - // Unknown status if no observations are available - if i.Observations == nil { - return 0, nil, ErrEVMNoObservations - } - - // First, check for request validation failures - if httpStatusCode, requestError := i.checkRequestValidationFailures(); requestError != nil { - return httpStatusCode, requestError, nil - } - - // Then, interpret endpoint response status - return i.getEndpointResponseStatus() -} - -// GetEndpointObservations extracts endpoint observations and indicates success -// Returns (nil, false) if observations are missing or validation failed -// Returns (observations, true) if observations are available -func (i *EVMObservationInterpreter) GetEndpointObservations() ([]*EVMEndpointObservation, bool) { - if i.Observations == nil { - return nil, false - } - - // Check for validation failures using the shared method - if _, reqError := i.checkRequestValidationFailures(); reqError != nil { - return nil, false - } - - observations := i.Observations.GetEndpointObservations() - if len(observations) == 0 { - return nil, false - } - - return observations, true -} - -// checkRequestValidationFailures examines observations for request validation failures -// Returns (httpStatusCode, requestError) where requestError is non-nil if a validation failure was found -func (i *EVMObservationInterpreter) checkRequestValidationFailures() (int, *EVMRequestError) { - // Check for HTTP body read failure - if failure := i.Observations.GetEvmHttpBodyReadFailure(); failure != nil { - errType := EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_HTTP_BODY_READ_FAILURE - return int(failure.GetHttpStatusCode()), &EVMRequestError{ - requestValidationError: &errType, - } - } - - // Check for unmarshaling failure - if failure := i.Observations.GetEvmRequestUnmarshalingFailure(); failure != nil { - errType := EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE - return int(failure.GetHttpStatusCode()), &EVMRequestError{ - requestValidationError: &errType, - } - } - - // No validation failures found - return 0, nil -} - -// getEndpointResponseStatus interprets endpoint response observations to extract status information -// Returns (httpStatusCode, requestError, error) tuple -func (i *EVMObservationInterpreter) getEndpointResponseStatus() (int, *EVMRequestError, error) { - observations := i.Observations.GetEndpointObservations() - - // No endpoint observations indicates no responses were received - if len(observations) == 0 { - return 0, nil, ErrEVMNoEndpointObservationsFound - } - - // Use only the last observation (latest response) - lastObs := observations[len(observations)-1] - responseInterpreter, err := getEVMResponseInterpreter(lastObs) - if err != nil { - return 0, nil, err - } - - // Extract the status code and error type - statusCode, errType := responseInterpreter.extractValidityStatus(lastObs) - if errType == nil { - return statusCode, nil, nil - } - - // Create appropriate EVMRequestError based on the observed error type - reqError := &EVMRequestError{ - responseValidationError: errType, - } - - return statusCode, reqError, nil -} diff --git a/observation/qos/evm_response_interpreters.go b/observation/qos/evm_response_interpreters.go deleted file mode 100644 index 84ed8c254..000000000 --- a/observation/qos/evm_response_interpreters.go +++ /dev/null @@ -1,161 +0,0 @@ -package qos - -import ( - "errors" -) - -// errInvalidResponseType is returned when an observation doesn't match any registered type -var errInvalidResponseType = errors.New("endpoint response observation does not match any registered response type") - -// evmResponseInterpreter defines an interpreter interface for EVM response observations. -// This interface decouples the rest of the codebase from proto-generated types by -// providing a consistent way to extract status and error information from various -// response type observations. -type evmResponseInterpreter interface { - // extractValidityStatus interprets an observation and extracts standardized status information. - // This method serves as a translation layer between proto-generated types and the rest of the system. - // It's only used by other methods/functions within this package. - // - // Parameters: - // - obs: The typed observation from an EVM endpoint response - // - // Returns: - // - statusCode: The HTTP status code to return to the client - // - errorType: The specific error type from the proto definition (nil if no error) - extractValidityStatus(obs *EVMEndpointObservation) (statusCode int, errorType *EVMResponseValidationError) -} - -// responseInterpreters maps response type identifiers to their respective interpreter implementations. -// Each implementation translates a specific proto-generated response type into standardized -// status codes and error types, decoupling the rest of the codebase from these details. -// -// DEV_NOTE: To add a new response type, you MUST: -// 1. Add a new interpreter struct that implements the evmResponseInterpreter interface -// 2. Add a new entry to this responseInterpreters map -// 3. Add a new case in the getEVMResponseInterpreter function to recognize the type -// Example: To support a new eth_getBalance response, implement all three steps above -var responseInterpreters = map[string]evmResponseInterpreter{ - "chain_id": &chainIDEVMResponseInterpreter{}, - "block_number": &blockNumberEVMResponseInterpreter{}, - "unrecognized": &unrecognizedEVMResponseInterpreter{}, - "empty": &emptyEVMResponseInterpreter{}, - "no_response": &noEVMResponseInterpreter{}, -} - -// getEVMResponseInterpreter returns the appropriate interpreter for a given observation type. -// This function selects the correct interpreter implementation based on the observation's -// proto-generated type, serving as part of the translation layer that shields the rest -// of the codebase from proto type details. -// -// Parameters: -// - obs: The EVM endpoint observation to be interpreted -// -// Returns: -// - An evmResponseInterpreter implementation specific to the observation type -// - An error if the observation does not match any registered endpoint response type -func getEVMResponseInterpreter(obs *EVMEndpointObservation) (evmResponseInterpreter, error) { - switch { - case obs.GetChainIdResponse() != nil: - return responseInterpreters["chain_id"], nil - case obs.GetBlockNumberResponse() != nil: - return responseInterpreters["block_number"], nil - case obs.GetUnrecognizedResponse() != nil: - return responseInterpreters["unrecognized"], nil - case obs.GetEmptyResponse() != nil: - return responseInterpreters["empty"], nil - case obs.GetNoResponse() != nil: - return responseInterpreters["no_response"], nil - default: - return nil, errInvalidResponseType - } -} - -// chainIDEVMResponseInterpreter interprets eth_chainId response observations. -// It implements the evmResponseInterpreter interface to translate proto-generated -// chain ID response types into standardized status codes and error types. -type chainIDEVMResponseInterpreter struct{} - -// extractValidityStatus extracts status information from chain ID response observations. -// It interprets the chain ID-specific proto type and translates it into standardized -// HTTP status codes and error types for the rest of the system. -func (i *chainIDEVMResponseInterpreter) extractValidityStatus(obs *EVMEndpointObservation) (int, *EVMResponseValidationError) { - response := obs.GetChainIdResponse() - validationErr := response.GetResponseValidationError() - - if validationErr != 0 { - errType := EVMResponseValidationError(validationErr) - return int(response.GetHttpStatusCode()), &errType - } - - return int(response.GetHttpStatusCode()), nil -} - -// blockNumberEVMResponseInterpreter interprets eth_blockNumber response observations. -// It implements the evmResponseInterpreter interface to translate proto-generated -// block number response types into standardized status codes and error types. -type blockNumberEVMResponseInterpreter struct{} - -// extractValidityStatus extracts status information from block number response observations. -// It interprets the block number-specific proto type and translates it into standardized -// HTTP status codes and error types for the rest of the system. -func (i *blockNumberEVMResponseInterpreter) extractValidityStatus(obs *EVMEndpointObservation) (int, *EVMResponseValidationError) { - response := obs.GetBlockNumberResponse() - validationErr := response.GetResponseValidationError() - - if validationErr != 0 { - errType := EVMResponseValidationError(validationErr) - return int(response.GetHttpStatusCode()), &errType - } - - return int(response.GetHttpStatusCode()), nil -} - -// unrecognizedEVMResponseInterpreter interprets unrecognized response observations. -// It implements the evmResponseInterpreter interface to translate proto-generated -// unrecognized response types into standardized status codes and error types. -type unrecognizedEVMResponseInterpreter struct{} - -// extractValidityStatus extracts status information from unrecognized response observations. -// It interprets the unrecognized response-specific proto type and translates it into -// standardized HTTP status codes and error types for the rest of the system. -func (i *unrecognizedEVMResponseInterpreter) extractValidityStatus(obs *EVMEndpointObservation) (int, *EVMResponseValidationError) { - response := obs.GetUnrecognizedResponse() - validationErr := response.GetResponseValidationError() - - if validationErr != 0 { - errType := EVMResponseValidationError(validationErr) - return int(response.GetHttpStatusCode()), &errType - } - - return int(response.GetHttpStatusCode()), nil -} - -// emptyEVMResponseInterpreter interprets empty response observations. -// It implements the evmResponseInterpreter interface to translate proto-generated -// empty response types into standardized status codes and error types. -type emptyEVMResponseInterpreter struct{} - -// extractValidityStatus extracts status information from empty response observations. -// It interprets the empty response-specific proto type and provides a standardized -// error type that indicates an empty response was received. -func (i *emptyEVMResponseInterpreter) extractValidityStatus(obs *EVMEndpointObservation) (int, *EVMResponseValidationError) { - response := obs.GetEmptyResponse() - // Empty responses are always errors - errType := EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_EMPTY - return int(response.GetHttpStatusCode()), &errType -} - -// noEVMResponseInterpreter interprets no-response observations. -// It implements the evmResponseInterpreter interface to translate proto-generated -// no-response types into standardized status codes and error types. -type noEVMResponseInterpreter struct{} - -// extractValidityStatus extracts status information from no-response observations. -// It interprets the no-response-specific proto type and provides a standardized -// error type that indicates no response was received. -func (i *noEVMResponseInterpreter) extractValidityStatus(obs *EVMEndpointObservation) (int, *EVMResponseValidationError) { - response := obs.GetNoResponse() - // No response is always an error - errType := EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_NO_RESPONSE - return int(response.GetHttpStatusCode()), &errType -} diff --git a/observation/qos/framework/endpoint.pb.go b/observation/qos/framework/endpoint.pb.go index 0fa5bbb6e..c9c016848 100644 --- a/observation/qos/framework/endpoint.pb.go +++ b/observation/qos/framework/endpoint.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/endpoint.proto @@ -34,11 +34,9 @@ type EndpointObservation struct { func (x *EndpointObservation) Reset() { *x = EndpointObservation{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EndpointObservation) String() string { @@ -49,7 +47,7 @@ func (*EndpointObservation) ProtoMessage() {} func (x *EndpointObservation) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -134,20 +132,6 @@ func file_path_qos_framework_endpoint_proto_init() { return } file_path_qos_framework_endpoint_query_result_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_endpoint_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*EndpointObservation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/qos/framework/endpoint_error.pb.go b/observation/qos/framework/endpoint_error.pb.go index 4f521e7b2..da52ac21b 100644 --- a/observation/qos/framework/endpoint_error.pb.go +++ b/observation/qos/framework/endpoint_error.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/endpoint_error.proto @@ -91,11 +91,9 @@ type EndpointError struct { func (x *EndpointError) Reset() { *x = EndpointError{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_endpoint_error_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_endpoint_error_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EndpointError) String() string { @@ -106,7 +104,7 @@ func (*EndpointError) ProtoMessage() {} func (x *EndpointError) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_endpoint_error_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -220,20 +218,6 @@ func file_path_qos_framework_endpoint_error_proto_init() { return } file_path_qos_framework_endpoint_sanction_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_endpoint_error_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*EndpointError); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_qos_framework_endpoint_error_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/observation/qos/framework/endpoint_query_result.pb.go b/observation/qos/framework/endpoint_query_result.pb.go index aed9f67f0..46606deed 100644 --- a/observation/qos/framework/endpoint_query_result.pb.go +++ b/observation/qos/framework/endpoint_query_result.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/endpoint_query_result.proto @@ -44,11 +44,9 @@ type EndpointQueryResult struct { func (x *EndpointQueryResult) Reset() { *x = EndpointQueryResult{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_endpoint_query_result_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_endpoint_query_result_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EndpointQueryResult) String() string { @@ -59,7 +57,7 @@ func (*EndpointQueryResult) ProtoMessage() {} func (x *EndpointQueryResult) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_endpoint_query_result_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -198,20 +196,6 @@ func file_path_qos_framework_endpoint_query_result_proto_init() { return } file_path_qos_framework_endpoint_error_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_endpoint_query_result_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*EndpointQueryResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_qos_framework_endpoint_query_result_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/observation/qos/framework/endpoint_sanction.pb.go b/observation/qos/framework/endpoint_sanction.pb.go index 31a810530..c0a2d83ee 100644 --- a/observation/qos/framework/endpoint_sanction.pb.go +++ b/observation/qos/framework/endpoint_sanction.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/endpoint_sanction.proto @@ -84,11 +84,9 @@ type Sanction struct { func (x *Sanction) Reset() { *x = Sanction{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_endpoint_sanction_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_endpoint_sanction_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Sanction) String() string { @@ -99,7 +97,7 @@ func (*Sanction) ProtoMessage() {} func (x *Sanction) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_endpoint_sanction_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -201,20 +199,6 @@ func file_path_qos_framework_endpoint_sanction_proto_init() { if File_path_qos_framework_endpoint_sanction_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_endpoint_sanction_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Sanction); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/qos/framework/jsonrpc.pb.go b/observation/qos/framework/jsonrpc.pb.go index 2bbfbed09..22fa507ef 100644 --- a/observation/qos/framework/jsonrpc.pb.go +++ b/observation/qos/framework/jsonrpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/jsonrpc.proto @@ -35,11 +35,9 @@ type JsonRpcRequest struct { func (x *JsonRpcRequest) Reset() { *x = JsonRpcRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *JsonRpcRequest) String() string { @@ -50,7 +48,7 @@ func (*JsonRpcRequest) ProtoMessage() {} func (x *JsonRpcRequest) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -96,11 +94,9 @@ type JsonRpcResponse struct { func (x *JsonRpcResponse) Reset() { *x = JsonRpcResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *JsonRpcResponse) String() string { @@ -111,7 +107,7 @@ func (*JsonRpcResponse) ProtoMessage() {} func (x *JsonRpcResponse) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -164,11 +160,9 @@ type JsonRpcResponseError struct { func (x *JsonRpcResponseError) Reset() { *x = JsonRpcResponseError{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *JsonRpcResponseError) String() string { @@ -179,7 +173,7 @@ func (*JsonRpcResponseError) ProtoMessage() {} func (x *JsonRpcResponseError) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -269,44 +263,6 @@ func file_path_qos_framework_jsonrpc_proto_init() { if File_path_qos_framework_jsonrpc_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_jsonrpc_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*JsonRpcRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_framework_jsonrpc_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*JsonRpcResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_framework_jsonrpc_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*JsonRpcResponseError); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_qos_framework_jsonrpc_proto_msgTypes[1].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/observation/qos/framework/observations.pb.go b/observation/qos/framework/observations.pb.go index ebfdffed7..8d651be51 100644 --- a/observation/qos/framework/observations.pb.go +++ b/observation/qos/framework/observations.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/observations.proto @@ -36,11 +36,9 @@ type Observations struct { func (x *Observations) Reset() { *x = Observations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_observations_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_observations_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Observations) String() string { @@ -51,7 +49,7 @@ func (*Observations) ProtoMessage() {} func (x *Observations) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_observations_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -154,20 +152,6 @@ func file_path_qos_framework_observations_proto_init() { } file_path_qos_framework_request_proto_init() file_path_qos_framework_endpoint_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_observations_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Observations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go index 780baadbb..be759fe07 100644 --- a/observation/qos/framework/request.pb.go +++ b/observation/qos/framework/request.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/framework/request.proto @@ -99,11 +99,9 @@ type RequestError struct { func (x *RequestError) Reset() { *x = RequestError{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_request_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestError) String() string { @@ -114,7 +112,7 @@ func (*RequestError) ProtoMessage() {} func (x *RequestError) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_request_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -172,11 +170,9 @@ type RequestObservation struct { func (x *RequestObservation) Reset() { *x = RequestObservation{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_framework_request_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_framework_request_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestObservation) String() string { @@ -187,7 +183,7 @@ func (*RequestObservation) ProtoMessage() {} func (x *RequestObservation) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_request_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -320,32 +316,6 @@ func file_path_qos_framework_request_proto_init() { return } file_path_qos_framework_jsonrpc_proto_init() - if !protoimpl.UnsafeEnabled { - file_path_qos_framework_request_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*RequestError); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_path_qos_framework_request_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*RequestObservation); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_qos_framework_request_proto_msgTypes[0].OneofWrappers = []any{} file_path_qos_framework_request_proto_msgTypes[1].OneofWrappers = []any{} type x struct{} diff --git a/observation/qos/observations.pb.go b/observation/qos/observations.pb.go index 741040bb7..48fffaa53 100644 --- a/observation/qos/observations.pb.go +++ b/observation/qos/observations.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.2 // protoc v5.28.3 // source: path/qos/observations.proto @@ -38,11 +38,9 @@ type Observations struct { func (x *Observations) Reset() { *x = Observations{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_observations_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_path_qos_observations_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Observations) String() string { @@ -53,7 +51,7 @@ func (*Observations) ProtoMessage() {} func (x *Observations) ProtoReflect() protoreflect.Message { mi := &file_path_qos_observations_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -145,20 +143,6 @@ func file_path_qos_observations_proto_init() { if File_path_qos_observations_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_path_qos_observations_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Observations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_path_qos_observations_proto_msgTypes[0].OneofWrappers = []any{ (*Observations_JsonrpcService)(nil), } diff --git a/qos/jsonrpc/observation.go b/qos/jsonrpc/observation.go deleted file mode 100644 index 8b35fb322..000000000 --- a/qos/jsonrpc/observation.go +++ /dev/null @@ -1,14 +0,0 @@ -package jsonrpc - -import ( - "github.com/buildwithgrove/path/observation/qos" -) - -// GetObservation returns a qos.JsonRpcRequest struct that can be used by QoS services -// to populate observation fields. -func (r Request) GetObservation() *qos.JsonRpcRequest { - return &qos.JsonRpcRequest{ - Id: r.ID.String(), - Method: string(r.Method), - } -} From ed28bc81366dcdef60cdb6c75f916907e01140ff Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 28 Apr 2025 19:55:03 -0400 Subject: [PATCH 18/26] Refactor proto files to drop redundant endpoint.proto --- observation/qos/framework/endpoint.pb.go | 153 ------------------ .../qos/framework/endpoint_query_result.pb.go | 91 ++++++----- observation/qos/framework/observations.pb.go | 57 +++---- observation/qos/framework/request.pb.go | 7 +- proto/path/qos/framework/endpoint.proto | 15 -- .../qos/framework/endpoint_query_result.proto | 9 +- proto/path/qos/framework/observations.proto | 4 +- proto/path/qos/framework/request.proto | 7 +- .../endpoint_result_blocknumber.go | 4 +- qos/framework/client_http_response.go | 18 +-- qos/framework/context_endpoint_checks.go | 2 +- qos/framework/context_endpoint_result.go | 120 +++++--------- qos/framework/context_endpoint_selection.go | 5 +- qos/framework/context_request.go | 17 +- qos/framework/context_state_update.go | 4 +- qos/framework/endpoint.go | 61 +++++-- qos/framework/endpoint_query.go | 13 +- qos/framework/endpoint_query_result.go | 118 ++++++++++++++ qos/framework/endpoint_sanction.go | 4 +- qos/framework/endpoint_store.go | 39 ++++- qos/framework/framework.go | 15 +- qos/framework/jsonrpc_errors.go | 20 +-- qos/framework/observations.go | 2 - qos/framework/observations_endpoint.go | 14 +- qos/framework/observations_endpoint_result.go | 9 +- qos/framework/observations_request.go | 21 ++- qos/framework/qos.go | 23 ++- qos/framework/request_errors.go | 4 + qos/framework/request_journal.go | 37 ++++- qos/framework/state.go | 4 +- qos/jsonrpc/response.go | 14 +- 31 files changed, 488 insertions(+), 423 deletions(-) delete mode 100644 observation/qos/framework/endpoint.pb.go delete mode 100644 proto/path/qos/framework/endpoint.proto diff --git a/observation/qos/framework/endpoint.pb.go b/observation/qos/framework/endpoint.pb.go deleted file mode 100644 index c9c016848..000000000 --- a/observation/qos/framework/endpoint.pb.go +++ /dev/null @@ -1,153 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.35.2 -// protoc v5.28.3 -// source: path/qos/framework/endpoint.proto - -package framework - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// EndpointObservation captures details about an endpoint query. -type EndpointObservation struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Address of the endpoint that handled the request - EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` - // Single result item extracted from this endpoint query. - Result *EndpointQueryResult `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` -} - -func (x *EndpointObservation) Reset() { - *x = EndpointObservation{} - mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *EndpointObservation) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*EndpointObservation) ProtoMessage() {} - -func (x *EndpointObservation) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_framework_endpoint_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use EndpointObservation.ProtoReflect.Descriptor instead. -func (*EndpointObservation) Descriptor() ([]byte, []int) { - return file_path_qos_framework_endpoint_proto_rawDescGZIP(), []int{0} -} - -func (x *EndpointObservation) GetEndpointAddr() string { - if x != nil { - return x.EndpointAddr - } - return "" -} - -func (x *EndpointObservation) GetResult() *EndpointQueryResult { - if x != nil { - return x.Result - } - return nil -} - -var File_path_qos_framework_endpoint_proto protoreflect.FileDescriptor - -var file_path_qos_framework_endpoint_proto_rawDesc = []byte{ - 0x0a, 0x21, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, - 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, - 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, - 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x13, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, - 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, - 0x64, 0x64, 0x72, 0x12, 0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, - 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_path_qos_framework_endpoint_proto_rawDescOnce sync.Once - file_path_qos_framework_endpoint_proto_rawDescData = file_path_qos_framework_endpoint_proto_rawDesc -) - -func file_path_qos_framework_endpoint_proto_rawDescGZIP() []byte { - file_path_qos_framework_endpoint_proto_rawDescOnce.Do(func() { - file_path_qos_framework_endpoint_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_endpoint_proto_rawDescData) - }) - return file_path_qos_framework_endpoint_proto_rawDescData -} - -var file_path_qos_framework_endpoint_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_path_qos_framework_endpoint_proto_goTypes = []any{ - (*EndpointObservation)(nil), // 0: path.qos.framework.EndpointObservation - (*EndpointQueryResult)(nil), // 1: path.qos.framework.EndpointQueryResult -} -var file_path_qos_framework_endpoint_proto_depIdxs = []int32{ - 1, // 0: path.qos.framework.EndpointObservation.result:type_name -> path.qos.framework.EndpointQueryResult - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_path_qos_framework_endpoint_proto_init() } -func file_path_qos_framework_endpoint_proto_init() { - if File_path_qos_framework_endpoint_proto != nil { - return - } - file_path_qos_framework_endpoint_query_result_proto_init() - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_path_qos_framework_endpoint_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_path_qos_framework_endpoint_proto_goTypes, - DependencyIndexes: file_path_qos_framework_endpoint_proto_depIdxs, - MessageInfos: file_path_qos_framework_endpoint_proto_msgTypes, - }.Build() - File_path_qos_framework_endpoint_proto = out.File - file_path_qos_framework_endpoint_proto_rawDesc = nil - file_path_qos_framework_endpoint_proto_goTypes = nil - file_path_qos_framework_endpoint_proto_depIdxs = nil -} diff --git a/observation/qos/framework/endpoint_query_result.pb.go b/observation/qos/framework/endpoint_query_result.pb.go index 46606deed..20f0d904a 100644 --- a/observation/qos/framework/endpoint_query_result.pb.go +++ b/observation/qos/framework/endpoint_query_result.pb.go @@ -29,12 +29,14 @@ type EndpointQueryResult struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // Address of the endpoint that handled the request + EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` // HTTP response returned to the client. - ClientHttpResponse int32 `protobuf:"varint,1,opt,name=client_http_response,json=clientHttpResponse,proto3" json:"client_http_response,omitempty"` + ClientHttpResponse int32 `protobuf:"varint,2,opt,name=client_http_response,json=clientHttpResponse,proto3" json:"client_http_response,omitempty"` // The set of values/attributes extracted from the endpoint query // and the endpoint's parsed JSONRPC response - StringValues map[string]string `protobuf:"bytes,2,rep,name=string_values,json=stringValues,proto3" json:"string_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - IntValues map[string]int64 `protobuf:"bytes,3,rep,name=int_values,json=intValues,proto3" json:"int_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + StringValues map[string]string `protobuf:"bytes,3,rep,name=string_values,json=stringValues,proto3" json:"string_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + IntValues map[string]int64 `protobuf:"bytes,4,rep,name=int_values,json=intValues,proto3" json:"int_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` // Captures the queried endpoint's error // Only set if the query result indicates an endpoint error Error *EndpointError `protobuf:"bytes,5,opt,name=error,proto3,oneof" json:"error,omitempty"` @@ -72,6 +74,13 @@ func (*EndpointQueryResult) Descriptor() ([]byte, []int) { return file_path_qos_framework_endpoint_query_result_proto_rawDescGZIP(), []int{0} } +func (x *EndpointQueryResult) GetEndpointAddr() string { + if x != nil { + return x.EndpointAddr + } + return "" +} + func (x *EndpointQueryResult) GetClientHttpResponse() int32 { if x != nil { return x.ClientHttpResponse @@ -118,44 +127,46 @@ var file_path_qos_framework_endpoint_query_result_proto_rawDesc = []byte{ 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x82, + 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa7, 0x04, 0x0a, 0x13, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x74, 0x70, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x39, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, - 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, - 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, - 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, - 0x3c, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, - 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, - 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, - 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3c, 0x0a, 0x0e, 0x49, - 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, - 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, + 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x55, 0x0a, + 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, + 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x1a, + 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x1a, 0x3c, 0x0a, 0x0e, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, + 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/observation/qos/framework/observations.pb.go b/observation/qos/framework/observations.pb.go index 8d651be51..84d5e6a68 100644 --- a/observation/qos/framework/observations.pb.go +++ b/observation/qos/framework/observations.pb.go @@ -31,7 +31,7 @@ type Observations struct { // Observation of the client request RequestObservation *RequestObservation `protobuf:"bytes,2,opt,name=request_observation,json=requestObservation,proto3" json:"request_observation,omitempty"` // Observations from endpoint(s) - EndpointObservations []*EndpointObservation `protobuf:"bytes,3,rep,name=endpoint_observations,json=endpointObservations,proto3" json:"endpoint_observations,omitempty"` + EndpointQueyResultObservations []*EndpointQueryResult `protobuf:"bytes,3,rep,name=endpoint_quey_result_observations,json=endpointQueyResultObservations,proto3" json:"endpoint_quey_result_observations,omitempty"` } func (x *Observations) Reset() { @@ -78,9 +78,9 @@ func (x *Observations) GetRequestObservation() *RequestObservation { return nil } -func (x *Observations) GetEndpointObservations() []*EndpointObservation { +func (x *Observations) GetEndpointQueyResultObservations() []*EndpointQueryResult { if x != nil { - return x.EndpointObservations + return x.EndpointQueyResultObservations } return nil } @@ -93,28 +93,31 @@ var file_path_qos_framework_observations_proto_rawDesc = []byte{ 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x70, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, - 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x22, 0xe8, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57, 0x0a, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, - 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, - 0x15, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, - 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, - 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x3a, 0x5a, 0x38, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, - 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, - 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfe, 0x01, + 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x57, 0x0a, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, + 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x72, 0x0a, 0x21, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, + 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1e, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x79, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x3a, + 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, + 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, + 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -133,11 +136,11 @@ var file_path_qos_framework_observations_proto_msgTypes = make([]protoimpl.Messa var file_path_qos_framework_observations_proto_goTypes = []any{ (*Observations)(nil), // 0: path.qos.framework.Observations (*RequestObservation)(nil), // 1: path.qos.framework.RequestObservation - (*EndpointObservation)(nil), // 2: path.qos.framework.EndpointObservation + (*EndpointQueryResult)(nil), // 2: path.qos.framework.EndpointQueryResult } var file_path_qos_framework_observations_proto_depIdxs = []int32{ 1, // 0: path.qos.framework.Observations.request_observation:type_name -> path.qos.framework.RequestObservation - 2, // 1: path.qos.framework.Observations.endpoint_observations:type_name -> path.qos.framework.EndpointObservation + 2, // 1: path.qos.framework.Observations.endpoint_quey_result_observations:type_name -> path.qos.framework.EndpointQueryResult 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name @@ -151,7 +154,7 @@ func file_path_qos_framework_observations_proto_init() { return } file_path_qos_framework_request_proto_init() - file_path_qos_framework_endpoint_proto_init() + file_path_qos_framework_endpoint_query_result_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go index be759fe07..e8631b368 100644 --- a/observation/qos/framework/request.pb.go +++ b/observation/qos/framework/request.pb.go @@ -155,16 +155,17 @@ func (x *RequestError) GetJsonrpcRequest() *JsonRpcRequest { return nil } -// TODO_IN_THIS_PR: use `oneof` below: either the request is set, or the validationerror // RequestObservation captures details about the original client request. type RequestObservation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Only set if validation was successful + // Only set if parsing and basic JSONRPC validation was successful. JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,1,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3,oneof" json:"jsonrpc_request,omitempty"` - // Only set if the request failed + // Only set if the request failed. + // A parsed request can still have error set: + // e.g. if the QoS service does not support the JSONRPC request's method. RequestError *RequestError `protobuf:"bytes,2,opt,name=request_error,json=requestError,proto3,oneof" json:"request_error,omitempty"` } diff --git a/proto/path/qos/framework/endpoint.proto b/proto/path/qos/framework/endpoint.proto deleted file mode 100644 index 6200db72b..000000000 --- a/proto/path/qos/framework/endpoint.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; -package path.qos.framework; - -option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; - -import "path/qos/framework/endpoint_query_result.proto"; // Import RequestObservation - -// EndpointObservation captures details about an endpoint query. -message EndpointObservation { - // Address of the endpoint that handled the request - string endpoint_addr = 1; - - // Single result item extracted from this endpoint query. - EndpointQueryResult result = 2; -} diff --git a/proto/path/qos/framework/endpoint_query_result.proto b/proto/path/qos/framework/endpoint_query_result.proto index ee170f528..dec4fb005 100644 --- a/proto/path/qos/framework/endpoint_query_result.proto +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -10,13 +10,16 @@ import "path/qos/framework/endpoint_error.proto"; // Import endpoint error defi // - Stores one or more string/integer values. // - Contains error/sanction information on endpoint error. message EndpointQueryResult { + // Address of the endpoint that handled the request + string endpoint_addr = 1; + // HTTP response returned to the client. - int32 client_http_response = 1; + int32 client_http_response = 2; // The set of values/attributes extracted from the endpoint query // and the endpoint's parsed JSONRPC response - map string_values = 2; - map int_values = 3; + map string_values = 3; + map int_values = 4; // Captures the queried endpoint's error // Only set if the query result indicates an endpoint error diff --git a/proto/path/qos/framework/observations.proto b/proto/path/qos/framework/observations.proto index dbd25b790..6cbf7c013 100644 --- a/proto/path/qos/framework/observations.proto +++ b/proto/path/qos/framework/observations.proto @@ -4,7 +4,7 @@ package path.qos.framework; option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; import "path/qos/framework/request.proto"; // Import RequestObservation -import "path/qos/framework/endpoint.proto"; // Import EndpointObservation +import "path/qos/framework/endpoint_query_result.proto"; // Import EndpointQueryResult // Observations is the top-level container for all QoS observations for a request. message Observations { @@ -15,5 +15,5 @@ message Observations { RequestObservation request_observation = 2; // Observations from endpoint(s) - repeated EndpointObservation endpoint_observations = 3; + repeated EndpointQueryResult endpoint_quey_result_observations = 3; } diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto index 532bd0b13..c8b5c0ddb 100644 --- a/proto/path/qos/framework/request.proto +++ b/proto/path/qos/framework/request.proto @@ -30,12 +30,13 @@ message RequestError { optional JsonRpcRequest jsonrpc_request = 4; } -// TODO_IN_THIS_PR: use `oneof` below: either the request is set, or the validationerror // RequestObservation captures details about the original client request. message RequestObservation { - // Only set if validation was successful + // Only set if parsing and basic JSONRPC validation was successful. optional JsonRpcRequest jsonrpc_request = 1; - // Only set if the request failed + // Only set if the request failed. + // A parsed request can still have error set: + // e.g. if the QoS service does not support the JSONRPC request's method. optional RequestError request_error = 2; } diff --git a/qos/example_evm/endpoint_result_blocknumber.go b/qos/example_evm/endpoint_result_blocknumber.go index 08c491bfc..fce1288b0 100644 --- a/qos/example_evm/endpoint_result_blocknumber.go +++ b/qos/example_evm/endpoint_result_blocknumber.go @@ -19,7 +19,7 @@ var _ framework.EndpointResultBuilder = responseBuilderBlockNumber // TODO_TECHDEBT(@adshmh): validate the `eth_blockNumber` request that was sent to the endpoint. // // responseBuilderBlockNumber handles attribute building and sanctioning of endpoints based on the data returned to `eth_blockNumber` requests. -func responseBuilderBlockNumber(ctx *framework.EndpointResultContext) *framework.ResultData { +func responseBuilderBlockNumber(ctx *framework.EndpointQueryResultContext) *framework.EndpointQueryResult { // TODO_MVP(@adshmh): implement the framework's RequestValidator interface to filter out invalid `eth_blockNumber` requests. // // The endpoint returned an error response: no further processing needed. @@ -33,7 +33,7 @@ func responseBuilderBlockNumber(ctx *framework.EndpointResultContext) *framework // The endpoint returned an error: no need to do further processing of the response. blockNumber, err := ctx.GetResultAsInt() if err != nil { - return ctx.SanctionEndpoint(5 * time.Minute, "endpoint returned malformed response to eth_blockNumber") + return ctx.SanctionEndpoint(5 * time.Minute, fmt.Sprintf("endpoint returned malformed response to eth_blockNumber: %v", err)) } // Sanction the endpoint if it returned an invalid block number. diff --git a/qos/framework/client_http_response.go b/qos/framework/client_http_response.go index f370967c7..bfccc85f5 100644 --- a/qos/framework/client_http_response.go +++ b/qos/framework/client_http_response.go @@ -1,7 +1,10 @@ package framework import ( + "github.com/pokt-network/poktroll/pkg/polylog" + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/qos/jsonrpc" ) // ClientHTTPResponse implements the gateway.HTTPResponse interface @@ -88,7 +91,7 @@ func BuildInternalErrorHTTPResponse(logger polylog.Logger) gateway.HTTPResponse } // BuildErrorHTTPResponse creates an HTTP response for an error response. -func BuildErrorHTTPResponse(logger polylog.Logger, errResp *jsonrpc.ErrorResponse) gateway.HTTPResponse { +func BuildErrorHTTPResponse(logger polylog.Logger, errResp *jsonrpc.ResponseError) gateway.HTTPResponse { payload, err := MarshalErrorResponse(logger, errResp) if err != nil { logger.Error().Err(err).Msg("Failed to marshal error response") @@ -97,7 +100,7 @@ func BuildErrorHTTPResponse(logger polylog.Logger, errResp *jsonrpc.ErrorRespons } // BuildSuccessHTTPResponse creates an HTTP response for a successful JSONRPC response. -func BuildSuccessHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.JsonRpcResponse) gateway.HTTPResponse { +func BuildSuccessHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.Response) gateway.HTTPResponse { response := jsonrpc.Response{ ID: jsonrpcResp.Id, JSONRPC: jsonrpc.Version2, @@ -107,21 +110,12 @@ func BuildSuccessHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.JsonRp if err != nil { logger.Error().Err(err).Msg("Failed to marshal JSONRPC response") errResp := newErrResponseMarshalError(response.ID, err) - errPayload, _ := MarshalErrorResponse(logger, errResp) + errPayload, _ := marshalErrorResponse(logger, errResp) return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) } return NewHTTPResponse(response.GetRecommendedHTTPStatusCode(), payload) } -// MarshalErrorResponse marshals an error response to JSON. -func MarshalErrorResponse(logger polylog.Logger, errResp *jsonrpc.ErrorResponse) ([]byte, error) { - payload, err := errResp.MarshalJSON() - if err != nil && logger != nil { - logger.Error().Err(err).Msg("Failed to marshal error response") - } - return payload, err -} - // NewHTTPResponse creates a new HTTP response with the given status code and payload. func NewHTTPResponse(statusCode int, payload []byte) gateway.HTTPResponse { return gateway.HTTPResponse{ diff --git a/qos/framework/context_endpoint_checks.go b/qos/framework/context_endpoint_checks.go index 2fc873244..7584b079d 100644 --- a/qos/framework/context_endpoint_checks.go +++ b/qos/framework/context_endpoint_checks.go @@ -1,4 +1,4 @@ -package evm +package framework // TODO_IN_THIS_PR: define, in the framework package, a context for adding QoS endpoint quality checks: // - Struct name: QualityCheckContext diff --git a/qos/framework/context_endpoint_result.go b/qos/framework/context_endpoint_result.go index d4930bdb9..37a6676fb 100644 --- a/qos/framework/context_endpoint_result.go +++ b/qos/framework/context_endpoint_result.go @@ -1,10 +1,8 @@ package framework -// ResultBuilder is implemented by custom service implementations to build an EndpointResult from an endpointCall. -// It processes a valid JSONRPC response for a specific method and extracts the relevant data or error information. -// It can potentially makr a JSONRPC response as invalid: -// For example if the result field cannot be parsed into a number in a response to an `eth_blockNumber` request. -type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult +import ( + "github.com/buildwithgrove/path/qos/jsonrpc" +) // TODO_IN_THIS_PR: add hydratedLoggers. // @@ -14,25 +12,37 @@ type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQ // Provides a fluent API for custom service implementations to create endpoint query results without directly constructing types. type EndpointQueryResultContext struct { // Allows direct Get calls on the current service state. - // It is read only: this is not the context for updating service state. - *ReadonlyServiceState + // ServiceState's public methods provide read only access: this is not the context for updating service state. + *ServiceState + + // Tracks the result of the endpoint query. + // Declared public to expose EndpointQueryResult's setter/getter methods. + *EndpointQueryResult // Custom result builders, supplied by the QoS Definition. - jsonrpcMethodResultBuilders map[string]EndpointQueryResultBuilder + jsonrpcMethodResultBuilders map[jsonrpc.Method]EndpointQueryResultBuilder +} - // Response is the parsed JSON-RPC response received from the endpoint. - response *jsonrpc.JsonRpcResponse +// ===> TODO_IN_THIS_PR: find a better name to signal to the client they should call this when done with updating the EndpointQueryResult. +func (ctx *EndpointQueryResultContext) Success() *EndpointQueryResult { + return &ctx.endpointQueryResult } + + // buildResult uses the supplied method builder to build the EndpointResult for the supplied endpointQuery. // A default builder is used if no matches were found for the request method. // Returns the endpointQuery augmented with the endpoint result. -func (ctx *EndpointResultContext) buildEndpointQueryResult(endpointQuery *endpointQuery) *endpointQuery { - parsedEndpointQuery, shouldContinue := ctx.parseEndpointPayload(endpointQuery) +func (ctx *EndpointQueryResultContext) buildEndpointQueryResult() *EndpointQueryResult { + // Parse the endpoint's payload into JSONRPC response. + // Stores the parsed JSONRPC response in the endpointQuery. + shouldContinue := ctx.updateEndpointQueryWithParsedResponse() + + // Parsing failed: skip the rest of the processing. if !shouldContinue { // parsing the request failed: stop the request processing flow. // Return a failure result for building the client's response and observations. - return parsedEndpointQuery + return ctx.EndpointQueryResult } // Use the custom endpoint query result builder, if one is found matching the JSONRPC request's method. @@ -42,20 +52,26 @@ func (ctx *EndpointResultContext) buildEndpointQueryResult(endpointQuery *endpoi builder = defaultResultBuilder } - // Process the result using service-specific processor with the context - parsedEndpointQuery.result = builder(ctx) + // Process the result using custom service's result processor. + // Pass the context to the builder to provide helper methods. + queryResult := builder(ctx) - // Return the updated endpoint query. - return parsedEndpointQuery + // Return the endpoint query result. + return queryResult } // TODO_IN_THIS_PR: define/allow customization of sanctions for endpoint errors: e.g. malformed response. // -// parseEndpointPayload parses the payload from an endpoint and handles empty responses and parse errors. -// It returns the result and a boolean indicating whether processing should continue (true) or stop (false). -func (ctx *EndpointResultContext) parseEndpointPayload(endpointQuery *endpointQuery) (*endpointQuery, bool) { +// parseEndpointQuery parses the payload from an endpoint and handles empty responses and parse errors. +// It returns a boolean indicating whether processing should continue (true) or stop (false). +func (ctx *EndpointQueryResultContext) updateEndpointQueryWithParsedResponse() bool { + logger := ctx.getHydratedLogger() + + endpointQuery := ctx.EndpointQueryResult.endpointQuery + // Check for empty response if len(endpointQuery.receivedData) == 0 { + ctx.logger.Info() endpointQuery.result = buildResultForEmptyResponse(endpointQuery) return endpointQuery, false } @@ -82,66 +98,8 @@ func (ctx *EndpointResultContext) parseEndpointPayload(endpointQuery *endpointQu return endpointQuery, true } -// Success creates a success result with the given value. -func (ctx *ResultBuilderContext) Success(value string) *ResultData { - valuePtr := &value - return &ResultData{ - Type: ctx.Method, - Value: valuePtr, - } -} - -// ErrorResult creates an error result with the given message and no sanction. -func (ctx *ResultBuilderContext) ErrorResult(description string) *ResultData { - return &ResultData{ - Type: ctx.Method, - Error: &ResultError{ - Description: description, - kind: EndpointDataErrorKindInvalidResult, - }, - CreatedTime: time.Now(), - } -} - -// SanctionEndpoint creates an error result with a temporary sanction. -func (ctx *ResultBuilderContext) SanctionEndpoint(description, reason string, duration time.Duration) *ResultData { - return &ResultData{ - Type: ctx.Method, - Error: &ResultError{ - Description: description, - RecommendedSanction: &SanctionRecommendation{ - Sanction: Sanction{ - Type: SanctionTypeTemporary, - Reason: reason, - ExpiryTime: time.Now().Add(duration), - CreatedTime: time.Now(), - }, - SourceDataType: ctx.Method, - TriggerDetails: description, - }, - kind: EndpointDataErrorKindInvalidResult, - }, - CreatedTime: time.Now(), - } -} +// TODO_IN_THIS_PR: implement. +func (ctx *EndpointQueryResultContext) getHydratedLogger() polylog.Logger() { + // hydrate the logger with endpointQuery fields. -// PermanentSanction creates an error result with a permanent sanction. -func (ctx *ResultBuilderContext) PermanentSanction(description, reason string) *ResultData { - return &ResultData{ - Type: ctx.Method, - Error: &ResultError{ - Description: description, - RecommendedSanction: &SanctionRecommendation{ - Sanction: Sanction{ - Type: SanctionTypePermanent, - Reason: reason, - CreatedTime: time.Now(), - }, - SourceDataType: ctx.Method, - TriggerDetails: description, - }, - kind: EndpointDataErrorKindInvalidResult, - }, - CreatedTime: time.Now(), - } } diff --git a/qos/framework/context_endpoint_selection.go b/qos/framework/context_endpoint_selection.go index f150fbbea..e90e19237 100644 --- a/qos/framework/context_endpoint_selection.go +++ b/qos/framework/context_endpoint_selection.go @@ -2,6 +2,7 @@ package framework import ( "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" ) // TODO_FUTURE: consider ranking filtered endpoints, e.g. based on latency, rather than randomization. @@ -13,7 +14,7 @@ import ( type EndpointSelectionContext struct { // Allows direct Get calls on the current service state. // It is read only: this is not the context for updating service state. - *ReadonlyServiceState + *ServiceState // Supplied from the Custom QoS service definition. // Used to select an endpoint among those without an active sanction. @@ -65,7 +66,7 @@ func (ctx *EndpointSelectionContext) Select(availableEndpoints []protocol.Endpoi } // Select marks an endpoint as selected. -func (ctx *EndpointSelectionContext) Select(endpoint Endpoint) *EndpointSelectionContext { +func (ctx *EndpointSelectionContext) SelectEndpoint(endpoint Endpoint) *EndpointSelectionContext { ctx.selected = append(ctx.selected, endpoint) return ctx } diff --git a/qos/framework/context_request.go b/qos/framework/context_request.go index 88c90a3fa..982e4b061 100644 --- a/qos/framework/context_request.go +++ b/qos/framework/context_request.go @@ -5,10 +5,12 @@ import ( "fmt" "time" + "github.com/pokt-network/poktroll/pkg/polylog" + "github.com/buildwithgrove/path/gateway" + qosobservations "github.com/buildwithgrove/path/observation/qos/framework" "github.com/buildwithgrove/path/protocol" "github.com/buildwithgrove/path/qos/jsonrpc" - "github.com/pokt-network/poktroll/pkg/polylog" ) // TODO_REFACTOR: Improve naming clarity by distinguishing between interfaces and adapters @@ -73,13 +75,14 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // Instantiate an endpointQuery to capture the interaction with the service endpoint. endpointQuery := rc.journal.buildEndpointQuery(endpointAddr, receivedData) - resultCtx := rc.contextBuilder.buildEndpointQueryResultContext() + // Instantiate a result context using the endpointQuery. + resultCtx := rc.contextBuilder.buildEndpointQueryResultContext(endpointQuery) - // Process the endpointQuery using the correct context. - processedEndpointQuery := resultCtx.buildEndpointQueryResult(endpointQuery) + // Build an endpoint query result using the context. + endpointQueryResult := resultCtx.buildEndpointQueryResult() - // Track the processed result in the request journal - rc.journal.reportProcessedEndpointQuery(processedEndpointQuery) + // Track the result in the request journal. + rc.journal.reportEndpointQueryResult(endpointQueryResult) } // TODO_TECHDEBT: support batch JSONRPC requests by breaking them into single JSONRPC requests and tracking endpoints' response(s) to each. @@ -104,7 +107,7 @@ func (rc requestContext) GetObservations() qosobservations.Observations { rc.checkForProtocolLevelError() // Use the request journal to generate the observations. - return rc.journal.getObservations() + return rc.journal.buildObservations() } // Build and returns an instance EndpointSelectionContext to perform endpoint selection for the client request. diff --git a/qos/framework/context_state_update.go b/qos/framework/context_state_update.go index a810dfbfa..cd594456c 100644 --- a/qos/framework/context_state_update.go +++ b/qos/framework/context_state_update.go @@ -16,9 +16,9 @@ type StateUpdateContext struct { paramsToUpdate *StateParameterUpdateSet } -func (ctx *StateUpdateContext) updateFromEndpoints(updatedEndpoints) error { +func (ctx *StateUpdateContext) updateFromEndpoints(updatedEndpoints []*Endpoint) error { // get the list of params to update by calling the custom state updater. - paramsToUpdate := ctx.stateUpdater(ctx) + paramsToUpdate := ctx.stateUpdater(ctx, updatedEndpoints) // Update the state parameters through the service state. return ctx.ServiceState.updateParameters(paramsToUpdate) diff --git a/qos/framework/endpoint.go b/qos/framework/endpoint.go index a4cf57c8f..8f4bd1912 100644 --- a/qos/framework/endpoint.go +++ b/qos/framework/endpoint.go @@ -1,5 +1,13 @@ package framework +import ( + "sync" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + // TODO_TECHDEBT(@adshmh): Persist this state (which may include sanctions) across restarts to maintain endpoint exclusions. // TODO_MVP(@adshmh): add support for removing expired query results. // @@ -7,18 +15,20 @@ package framework // - Read-only for client code // - All attributes are set internally by the framework type Endpoint struct { + logger polylog.Logger + // queryResults maps keys to query results for this endpoint. - // Keys are defined by the QoS service implementation (typically JSONRPC method names). + // The map key is the method of the JSONRPC request for which the query result was built. // Examples: // - "eth_blockNumber": &EndpointQueryResult{IntValues: {"blockNumber": 0x1234}} // - "eth_getBalance": &EndpointQueryResult{ - // StringValues: {"address": "0x8D97..."}, + // StringValues: {"address": "0x8d97..."}, // IntValues: {"balance": 133456789}, // } - queryResults map[string]*EndpointQueryResult + queryResults map[jsonrpc.Method]*EndpointQueryResult // mutex for query results - resultsMu sync.Mutex + resultsMu sync.RWMutex } // GetQueryResultStringValue retrieves a string attribute of a result by key. @@ -26,7 +36,10 @@ type Endpoint struct { // - Prevents map leaking and unauthorized modifications through pointers // - Avoids expensive struct cloning // - Maintains proper encapsulation -func (e *Endpoint) GetQueryResultStringValue(resultKey, valueKey string) (string, bool) { +func (e *Endpoint) GetQueryResultStringValue(resultKey jsonrpc.Method, valueKey string) (string, bool) { + e.resultsMu.RLock() + defer e.resultsMu.RUnlock() + result, exists := e.queryResults[resultKey] if !exists || result == nil { return "", false @@ -37,7 +50,10 @@ func (e *Endpoint) GetQueryResultStringValue(resultKey, valueKey string) (string // GetQueryResultStringValue retrieves an integer attribute of a result by key. // See the comment on GetQueryResultStringValue for notes on this pattern. -func (e *Endpoint) GetQueryResultIntValue(resultKey, valueKey string) (int, bool) { +func (e *Endpoint) GetQueryResultIntValue(resultKey jsonrpc.Method, valueKey string) (int, bool) { + e.resultsMu.RLock() + defer e.resultsMu.RUnlock() + result, exists := e.queryResults[resultKey] if !exists || result == nil { return "", false @@ -53,14 +69,39 @@ func (e *Endpoint) HasActiveSanction() (Sanction, bool) { // ApplyQueryResult updates the endpoint's attributes with attributes from the query result. // It merges the EndpointAttributes from the query result into the endpoint's attributes map. -func (e *Endpoint) ApplyQueryResults(result map[string]EndpointQueryResult) { +func (e *Endpoint) applyQueryResults(queryResults []*EndpointQueryResult) { + e.resultsMu.Lock() + defer e.resultsMu.Unlock() + // Initialize the results map if nil. if e.queryResults == nil { - e.queryResults = make(map[string]*EndpointQueryResult) + e.queryResults = make(map[jsonrpc.Method]*EndpointQueryResult) } // Add or update attributes from the query result - for key, result := range results { - e.queryResult[key] = result + for _, result := range queryResults { + query := result.endpointQuery + + // Validate the supplied query results. + if query == nil { + e.logger.Warn().Msg("Endpoint received query result with no query data: skipping update.") + return + } + + queryRequest := query.request + if queryRequest == nil { + e.logger.Warn().Msg("Endpoint received query result with no query request data: skipping update.") + return + } + + if queryRequest.Method == "" { + e.logger.Warn().Msg("Endpoint received query result with no JSONRPC method set: skipping update.") + return + } + + // Update the endpoint result matching the JSONRPC request. + e.queryResult[queryRequest.Method] = result + + e.logger.With("request_method", queryRequest.Method).Debug().Msg("Updated endpoint with query result.") } } diff --git a/qos/framework/endpoint_query.go b/qos/framework/endpoint_query.go index 2d5d588d9..4188a7e44 100644 --- a/qos/framework/endpoint_query.go +++ b/qos/framework/endpoint_query.go @@ -1,12 +1,19 @@ package framework -// endpointQuery represents a raw communication attempt with an endpoint. +import ( + "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// EndpointQuery represents a raw communication attempt with an endpoint. // Instantiated by: RequestQoSContext. // Used in EndpointQueryResultContext. type endpointQuery struct { // request is the JSONRPC request that was sent. request *jsonrpc.Request + // TODO_IN_THIS_PR: REMOVE this field to be consistent with the proto files. + // // endpointAddr identifies the endpoint endpointAddr protocol.EndpointAddr @@ -14,8 +21,6 @@ type endpointQuery struct { receivedData []byte // JSONRPC response, parsed from the data received from the endpoint. + // Only set if the data received from the endpoint could be parsed into a JSONRPC response. parsedResponse *jsonrpc.Response - - // the result of processing the endpoint query. - result *EndpointQueryResult } diff --git a/qos/framework/endpoint_query_result.go b/qos/framework/endpoint_query_result.go index cd925e287..0eb18ddb7 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/framework/endpoint_query_result.go @@ -3,8 +3,22 @@ package framework import ( "errors" "time" + + "github.com/buildwithgrove/path/qos/jsonrpc" ) +// TODO_IN_THIS_PR: make all the fields private, and provide Concurrency-safe methods to access Int and String values. +// This will allow the Endpoint struct to return the EndpointQueryResult struct as a whole, and simplify the client code. +// e.g.: +// Instead of: +// - endpoint.GetQueryResultIntValue("getEpochInfo", "epoch") +// - endpoint.GetQueryResultIntValue("getEpochInfo", "blockHeight") +// We can write: +// - epochInfoResult := endpoint.GetQueryResult("getEpochInfo") +// - epoch := epochInfoResult.GetIntValue("epoch") +// - blockHeight := epochInfoResult.GetIntValue("blockHeight") + + // TODO_IMPROVE(@adshmh): Enhance EndpointQueryResult to support data types commonly stored for endpoints. // // EndpointQueryResult captures data extracted from an endpoint query. @@ -46,4 +60,108 @@ type EndpointQueryResult struct { // TODO_FUTURE(@adshmh): add a JSONRPCErrorResponse to allow a result builder to supply its custom JSONRPC response. } +// ===> These are moved from EndpointQueryResultContext --> it will have a public EndpointQueryResult field to allow direct access to the methods below. +func (eqr *EndpointQueryResult) IsJSONRPCError() bool { + parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() + if err != nil { + return false + } + + return parsedJSONRPCResponse.IsError() +} + +func (eqr *EndpointQueryResult) GetResultAsInt() (int, error) { + parsedJSONRPCResponse := eqr.getParsedJSONRPCResponse() + if err != nil { + return 0, err + } + + return parsedJSONRPCResponse.GetResultAsInt() +} + +func (ctx *EndpointQueryResultContext) GetResultAsStr() (string, error) { + parsedJSONRPCResponse := eqr.getParsedJSONRPCResponse() + if err != nil { + return "", err + } + + return parsedJSONRPCResponse.GetResultAsStr() +} + +func (eqr *EndpointQueryResult) getParsedJSONRPCResponse() (*jsonrpc.Response, error) { + parsedJSONRPCResponse := eqr.endpointQuery.parsedJSONRPCResponse + // Endpoint payload failed to parse as JSONRPC response. + // This is not considered a JSONRPC error response. + if parsedJSONRPCResponse == nil { + return nil, fmt.Errorf("endpoint payload failed to parse as JSONRPC.") + } + + return parsedJSONRPCResponse, nil +} + +func (eqr *EndpointQueryResult) AddIntValue(key string, value int) { + eqr.endpointQueryResult.AddIntValue(key, value) +} + +func (ctx *EndpointQueryResultContext) AddStrValue(key, value string) { + ctx.endpointQueryResult.AddStrValue(key, value) +} + + +// ErrorResult creates an error result with the given message and no sanction. +func (ctx *EndpointQueryResultContext) ErrorResult(description string) *EndpointQueryResult { + + return &ResultData{ + Type: ctx.Method, + Error: &ResultError{ + Description: description, + kind: EndpointDataErrorKindInvalidResult, + }, + } +} + +// SanctionEndpoint creates an error result with a temporary sanction. +func (ctx *EndpointQueryResultContext) SanctionEndpoint(description, reason string, duration time.Duration) *ResultData { + return &ResultData{ + Type: ctx.Method, + Error: &ResultError{ + Description: description, + RecommendedSanction: &SanctionRecommendation{ + Sanction: Sanction{ + Type: SanctionTypeTemporary, + Reason: reason, + ExpiryTime: time.Now().Add(duration), + CreatedTime: time.Now(), + }, + SourceDataType: ctx.Method, + TriggerDetails: description, + }, + kind: EndpointDataErrorKindInvalidResult, + }, + CreatedTime: time.Now(), + } +} + +// PermanentSanction creates an error result with a permanent sanction. +func (ctx *EndpointQueryResultContext) PermanentSanction(description, reason string) *ResultData { + return &ResultData{ + Type: ctx.Method, + Error: &ResultError{ + Description: description, + RecommendedSanction: &SanctionRecommendation{ + Sanction: Sanction{ + Type: SanctionTypePermanent, + Reason: reason, + CreatedTime: time.Now(), + }, + SourceDataType: ctx.Method, + TriggerDetails: description, + }, + kind: EndpointDataErrorKindInvalidResult, + }, + CreatedTime: time.Now(), + } +} + + diff --git a/qos/framework/endpoint_sanction.go b/qos/framework/endpoint_sanction.go index f1952134b..3aa843337 100644 --- a/qos/framework/endpoint_sanction.go +++ b/qos/framework/endpoint_sanction.go @@ -1,9 +1,11 @@ package framework +import ( + "time" +) // ====================== // Sanction Types // ====================== - // SanctionType identifies different types of endpoint sanctions. type SanctionType int diff --git a/qos/framework/endpoint_store.go b/qos/framework/endpoint_store.go index 8a69d4beb..9e388826a 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/framework/endpoint_store.go @@ -3,25 +3,54 @@ package framework import ( "sync" + "github.com/pokt-network/poktroll/pkg/polylog" + "github.com/buildwithgrove/path/protocol" ) // endpointStore maintains data on the set of available endpoints. // It is package-private and not meant to be used directly by any entity outside the jsonrpc package. type endpointStore struct { + logger polylog.Logger endpointsMu sync.RWMutex endpoints map[protocol.EndpointAddr]*Endpoint } -func (es *endpointStore) updateStoredEndpoints(endpointQueries []*endpointQuery) []Endpoint { +func (es *endpointStore) updateStoredEndpoints(endpointQueryResults []*EndpointQueryResult) []*Endpoint { es.endpointsMu.Lock() defer es.endpointsMu.Unlock() - endpoints := make([]Endpoint, len(endpointQueries)) - for index, endpointQuery := range endpointQueries { - endpoint := es.endpoints[endpointQuery.endpointAddr] - if + groupedEndpointResults := groupResultsByEndpointAddr(endpointQueryResults) + + // Track the updated endpoints + var updatedEndpoints []Endpoint + // Loop over query results, grouped by endpoint address, and update the corresponding stored endpoint. + for endpointAddr, queryResults := range groupedEndpointResults { + endpoint, found := es.endpoints[endpointQueryResult.endpointAddr] + if !found { + endpoint = &Endpoint{} + } + + endpoint.applyQueryResults(queryResults) + + // Store the updated endpoint + es.endpoints[endpointQueryResult.endpointAddr] + + // Add the updated endpoint to the list to be returned. + updatedEndpoints = append(updatedEndpoints, endpoint) } + + return updatedEndpoints +} + +func groupResultsByEndpointAddr(endpointQueryResults []*EndpointQueryResult) map[protocol.EndpointAddr][]*EndpointQueryResult { + resultsByEndpoint := make(map[protocol.EndpointAddr][]*EndpointQueryResult) + + for _, queryResult := range endpointQueryResults { + resultsByEndpoint[queryResult.endpointAddr] = append(resultsByEndpoint[queryResult.endpointAddr], queryResult) + } + + return resultsByEndpoint } // storeEndpoint stores or updates an endpoint in the store. diff --git a/qos/framework/framework.go b/qos/framework/framework.go index 2184acaad..b59b5fa9d 100644 --- a/qos/framework/framework.go +++ b/qos/framework/framework.go @@ -10,6 +10,12 @@ // leverage the framework's request handling, endpoint management, and state tracking. package framework +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/protocol" +) + // TODO_FUTURE(@adshmh): Provide reasonable defaults for components to enable a no-config JSONRPC service QoS. // // QoSDefinition contains all custom behavior for a JSONRPC QoS service. @@ -18,8 +24,9 @@ type QoSDefinition struct { // Logger for service logs. If nil, a default logger is used Logger polylog.Logger - // ServiceInfo identifies and describes the service - ServiceInfo ServiceInfo + // ServiceName identifies and describes the service. + // e.g. "ETH" + ServiceName string // ResultBuilders maps JSONRPC methods to custom result processing logic ResultBuilders map[string]EndpointQueryResultBuilder @@ -45,6 +52,10 @@ type QoSDefinition struct { } // EndpointQueryResultBuilder processes a response and extracts the relevant result. +// It is implemented by the custom service implementations to extract result(s) from an endpoint query. +// It processes a valid JSONRPC response for a specific method and extracts the relevant data or error information. +// It can potentially mark a JSONRPC response as invalid: +// For example if the result field cannot be parsed into a number in an endpoint's response to an `eth_blockNumber` request. type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult // StateUpdater updates service state based on endpoint results diff --git a/qos/framework/jsonrpc_errors.go b/qos/framework/jsonrpc_errors.go index a5cd19c90..fa01823e0 100644 --- a/qos/framework/jsonrpc_errors.go +++ b/qos/framework/jsonrpc_errors.go @@ -141,22 +141,6 @@ func newErrResponseInvalidRequest(err error, requestID jsonrpc.ID) jsonrpc.Respo ) } -// newErrResponseMarshalError creates a JSON-RPC error response for marshaling errors. -// This response: -// - Preserves the original request ID if available -// - Marks error as retryable -// - Indicates the response couldn't be serialized -func newErrResponseMarshalError(requestID jsonrpc.ID, marshalErr error) jsonrpc.Response { - return jsonrpc.GetErrorResponse( - requestID, - ErrorCodeInternalError, - fmt.Sprintf("Failed to marshal response: %s", marshalErr.Error()), - map[string]string{ - "retryable": "true", - }, - ) -} - // newErrResponseInvalidVersionError creates a JSON-RPC error response for invalid version errors. // This response: // - Preserves the original request ID @@ -223,9 +207,9 @@ func newErrResponseParseRequestError(parseErr error) jsonrpc.Response { ) } -// MarshalErrorResponse marshals a JSONRPC error response to JSON. +// marshalErrorResponse marshals a JSONRPC error response to JSON. // This handles the serialization of the error response to bytes. -func MarshalErrorResponse( +func marshalErrorResponse( logger polylog.Logger, response jsonrpc.Response, ) ([]byte, error) { diff --git a/qos/framework/observations.go b/qos/framework/observations.go index 1cb1f4449..ec1fef6b0 100644 --- a/qos/framework/observations.go +++ b/qos/framework/observations.go @@ -1,9 +1,7 @@ package framework import ( - "github.com/buildwithgrove/path/observation/qos/jsonrpc" observations "github.com/buildwithgrove/path/observation/qos/framework" - "github.com/buildwithgrove/path/qos/jsonrpc" ) // getObservations returns the set of observations for the requestJournal. diff --git a/qos/framework/observations_endpoint.go b/qos/framework/observations_endpoint.go index c08f1bcd2..4c5a2d7b2 100644 --- a/qos/framework/observations_endpoint.go +++ b/qos/framework/observations_endpoint.go @@ -4,17 +4,9 @@ import ( observations "github.com/buildwithgrove/path/observation/qos/framework" ) -func (eq *endpointQuery) buildObservations() *qosobservations.EndpointObservation { - return &qosobservations.EndpointObservation{ - EndpointAddr: string(eq.endpointAddr), - EndpointQueryResult: result.buildObservation(), - } -} - -func extractEndpointQueryFromObservation(observation *qosobservations.EndpointObservation) *endpointQuery { +func extractEndpointQueryFromObservation(observation *qosobservations.Observations) *endpointQuery { return &endpointQuery { - endpointAddr: observation.GetEndpointAddr(), - // Single result item extracted from this endpoint query. - result: extractEndpointQueryResultFromObservation(observation.GetResult()), + // Extract the JSONRPC request corresponding to the observation. + request: extractJSONRPCRequestFromObservation(observation.GetRequestObservation()), } } diff --git a/qos/framework/observations_endpoint_result.go b/qos/framework/observations_endpoint_result.go index 89cdb5f8c..19ceffb81 100644 --- a/qos/framework/observations_endpoint_result.go +++ b/qos/framework/observations_endpoint_result.go @@ -49,13 +49,20 @@ func (eqr *EndpointQueryResult) buildObservation() *observations.EndpointQueryRe // extractEndpointQueryResultFromObservation extracts a single EndpointQueryResult from an observation's EndpointQueryResult // Ignores the HTTP stauts code: it is only required when responding to the client. -func extractEndpointQueryResultFromObservation(obsResult *observations.EndpointQueryResult) *EndpointQueryResult { +func extractEndpointQueryResultFromObservation( + endpointQuery *endpointQuery, + obsResult *observations.EndpointQueryResult, +) *EndpointQueryResult { if obsResult == nil { return nil } // Create a new result and populate it from the observation result := &EndpointQueryResult{ + // Set the endpointQuery underlying the observations. + endpointQuery: endpointQuery, + + // Set the result values to be copied from the observations. StringValues: make(map[string]string), IntValues: make(map[string]int), ExpiryTime: timeFromProto(obsResult.ExpiryTime), diff --git a/qos/framework/observations_request.go b/qos/framework/observations_request.go index af5eaf2df..f1976dc87 100644 --- a/qos/framework/observations_request.go +++ b/qos/framework/observations_request.go @@ -15,7 +15,7 @@ func (rd *requestDetails) buildObservations() *qosobservations.RequestObservatio // build a JSONRPC request observation, if one was parsed. var jsonrpcRequestObs *qosobservations.JsonrpcRequest if rd.request != nil { - jsonrpcRequestObs = rd.request.GetObservation(), + jsonrpcRequestObs = rd.request.GetObservation() } // build request failure observation, if the request parsing failed. @@ -34,3 +34,22 @@ func (rd *requestDetails) buildObservations() *qosobservations.RequestObservatio RequestError: errorObs, } } + +func extractJSONRPCRequestFromObservation( + observation *qosobservations.RequestObservation, +) *jsonrpc.Request { + // Nil + if observation == nil { + return nil + } + + jsonrpcRequestObs := observation.GetJsonRpcRequest() + if jsonrpcRequestObs == nil { + return nil + } + + // The only field required in applying the observations is the request's method. + return &jsonrpc.Request{ + Method: jsonrpcRequestObs.GetMethod(), + } +} diff --git a/qos/framework/qos.go b/qos/framework/qos.go index 6263b2427..ebf452118 100644 --- a/qos/framework/qos.go +++ b/qos/framework/qos.go @@ -21,7 +21,7 @@ type QoS struct { // Logger for diagnostics logger polylog.Logger - serviceState *serviceState + serviceState *ServiceState // The definitoin of QoS behavior, supplied by the custom QoS service. qosDefinition QoSDefinition @@ -47,19 +47,28 @@ func (s *QoS) ParseHTTPRequest( // check if the request processing flow should continue. shouldContinue := requestDetails.getRequestErrorJSONRPCResponse() != nil - rturn requestCtx, shouldContinue + return requestCtx, shouldContinue } // TODO_IN_THIS_PR: implement this method // func (qos *QoS) ParseWebsocketRequest(_ context.Context) (gateway.RequestQoSContext, bool) -func (s *QoS) ApplyObservations(observations *qosobservations.Observations) error -) { - jsonrpcSvcObservations := observations.GetJsonrpc() - endpointQueries := extractEndpointQueriesFromObservations(jsonrpcSvcObservations) +func (q *QoS) ApplyObservations(observations *qosobservations.Observations) error { + serviceRequestObservations := observations.GetJsonrpc() + + // Validate the Service Name + if serviceRequestObservations.ServiceName != q.qosDefinition.ServiceName { + return fmt.Errorf("Reported observations mismatch: service name %q, expected %q", serviceRequestObservations.ServiceName, q.qosDefinitions.ServiceName) + } + + // Construct the endpoint query underlying the observations + endpointQuery := extractEndpointQueryFromObservations(serviceRequestObservations) + + // Construct the query results using the observations. + endpointQueryResults := extractEndpointQueryResultsFromObservations(endpointQuery, serviceRequestObservations.GetEndpointQueryResultObservations()) // update the stored endpoints - updatedEndpoints := s.serviceState.updateStoredEndpoints(endpointQueries) + updatedEndpoints := s.serviceState.updateStoredEndpoints(endpointQueryResults) // instantiate a state update context. stateUpdateCtx := s.buildServiceStateUpdateContext() diff --git a/qos/framework/request_errors.go b/qos/framework/request_errors.go index f06167f5d..973eb8a0f 100644 --- a/qos/framework/request_errors.go +++ b/qos/framework/request_errors.go @@ -1,5 +1,9 @@ package framework +import ( + "github.com/buildwithgrove/path/qos/jsonrpc" +) + type requestErrorKind int const ( _ requestErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. diff --git a/qos/framework/request_journal.go b/qos/framework/request_journal.go index f9b2a00c6..3a6edd40e 100644 --- a/qos/framework/request_journal.go +++ b/qos/framework/request_journal.go @@ -1,5 +1,11 @@ package framework +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + qosobservations "github.com/buildwithgrove/path/observation/qos/framework" +) + // TODO_IN_THIS_PR: verify the EmptyResponse and NoResponse scenarios: // - EmptyResponse is an EndpointQueryResult, because the endpoint did return an empty payload. // - NoReponse is a requestError: e.g. there may have been ZERO ENDPOINTS available at the PROTOCOL-LEVEL. @@ -21,20 +27,22 @@ type requestJournal struct { requestDetails *requestDetails // All endpoint interactions that occurred during processing. - // These are expected to be processed: i.e. have a non-nil result pointer and a client JSONRPC response. - processedEndpointQueries []*endpointQuery + endpointQueryResults []*EndpointQueryResult } func (rj *requestJournal) buildEndpointQuery(endpointAddr protocol.EndpointAddr, receivedData []byte) *endpointQuery { return &endpointQuery{ - request: rj.request, + // JSONRPC request underlying the endpoint query. + request: rj.requestDetails.request, + // Address of the queried endpoint. endpointAddr: endpointAddr, + // Data received from the endpoint. receivedData: receivedData, } } -func (rj *requestJournal) reportProcessedEndpointQuery(processedEndpointQuery endpointQuery) { - rj.endpointQueries = append(rj.endpointQueries, processedEndpointQuery) +func (rj *requestJournal) reportEndpointQueryResult(endpointQueryResult *EndpointQueryResult) { + rj.endpointQueryResults = append(rj.endpointQueryResults, endpointQueryResult) } func (rj *requestJournal) getServicePayload() protocol.Payload { @@ -85,3 +93,22 @@ func (rj *requestJournal) getHTTPResponse() gateway.HTTPResponse { jsonrpcResponse := selectedQuery.result.clientJSONRPCResponse return buildHTTPResponse(rj.Logger, jsonrpcResponse) } + +func (rj *requestJournal) buildObservations() qosobservations.Observations { + observations := qosobservations.Observations { + ServiceName: rj.serviceName, + RequestObservation: rj.requestDetails.buildObservations(), + } + + if len(rj.endpointQueryResults) == 0 { + return observations + } + + endpointObservations := make([]*qosobservations.EndpointObservation, len(rj.endpointQueryResults)) + for index, endpointQueryResult := range rj.endpointQueryResults { + endpointObservation[index] = endpointQueryResult.buildObservations() + } + + observations.EndpointQueryResultObservations = endpointObservations + return observations +} diff --git a/qos/framework/state.go b/qos/framework/state.go index c7a82fa11..309286332 100644 --- a/qos/framework/state.go +++ b/qos/framework/state.go @@ -71,11 +71,11 @@ func (s *ServiceState) GetConsensusParam(paramName string) (map[string]int, bool } // Returns the stored Endpoint structs matching the endpoint queries. -func (s *serviceState) updateStoredEndpoints(endpointQueries []*endpointQuery) []Endpoint { +func (s *serviceState) updateStoredEndpoints(endpointQueryResults []*EndpointQueryResult) []*Endpoint { s.mu.Lock() defer s.mu.Unlock() - return s.endpointStore.updateEndpoints(endpointQueries) + return s.endpointStore.updateStoredEndpoints(endpointQueryResults) } func (s *ServiceState) updateParameters(updates StateParameterUpdateSet) error { diff --git a/qos/jsonrpc/response.go b/qos/jsonrpc/response.go index e46418671..fceb39368 100644 --- a/qos/jsonrpc/response.go +++ b/qos/jsonrpc/response.go @@ -18,7 +18,7 @@ type Response struct { // Result captures the result field of the JSONRPC spec. // It is allowed to be any arbitrary value as permitted by the spec. // It is required on success and must not exist if there was an error invoking the method. - Result any `json:"result,omitempty"` + Result []byte `json:"result,omitempty"` // Error captures the error field of the JSONRPC spec. // Is is required on error and must not exist if there was no error triggered during invocation. Error *ResponseError `json:"error,omitempty"` @@ -44,6 +44,18 @@ func (r Response) GetResultAsBytes() ([]byte, error) { return json.Marshal(r.Result) } +func (r Response) GetResultAsInt() (int, error) { + var intValue int + err := json.Unmarshal(&intValue, r.Result) + return intValue, err +} + +func (r Response) GetResultAsStr() (string, error) { + var strValue string + err := json.Unmarshal(&strValue, r.Result) + return strValue, err +} + // GetErrorResponse is a helper function that builds a JSONRPC Response using the supplied ID and error values. func GetErrorResponse(id ID, errCode int, errMsg string, errData map[string]string) Response { return Response{ From ba649eb03fdf7086eb5e997831294f719c73a6b5 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 28 Apr 2025 20:33:01 -0400 Subject: [PATCH 19/26] Refactor: consolidate all details/events of a request in requestJournal --- qos/framework/request_journal.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qos/framework/request_journal.go b/qos/framework/request_journal.go index 3a6edd40e..6e14174c6 100644 --- a/qos/framework/request_journal.go +++ b/qos/framework/request_journal.go @@ -24,7 +24,12 @@ type requestJournal struct { // Service identification serviceName string - requestDetails *requestDetails + // The client's JSONRPC request + // Only set if the request was successfully parsed. + request *jsonrpc.Request + + // Request error, if any. + requestError *requestError // All endpoint interactions that occurred during processing. endpointQueryResults []*EndpointQueryResult From 848fe1c08adb6d950822be8d8513dab0c17d4457 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 29 Apr 2025 17:08:19 -0400 Subject: [PATCH 20/26] Refactor: make requestJournal top level struct in observations --- observation/qos/framework/observations.pb.go | 176 --------------- observation/qos/framework/request.pb.go | 146 +++---------- .../qos/framework/request_journal.pb.go | 200 ++++++++++++++++++ observation/qos/observations.pb.go | 51 ++--- .../qos/framework/endpoint_query_result.proto | 3 + proto/path/qos/framework/observations.proto | 19 -- proto/path/qos/framework/request.proto | 25 +-- .../path/qos/framework/request_journal.proto | 25 +++ proto/path/qos/observations.proto | 4 +- .../endpoint_result_blocknumber.go | 2 +- qos/framework/context_endpoint_checks.go | 3 +- qos/framework/context_endpoint_result.go | 35 ++- qos/framework/context_request.go | 74 ++++++- qos/framework/context_state_update.go | 2 +- qos/framework/endpoint.go | 24 +-- qos/framework/endpoint_errors.go | 9 +- qos/framework/endpoint_query.go | 26 --- qos/framework/endpoint_query_result.go | 146 +++++++------ .../endpoint_query_result_defaults.go | 41 ++-- qos/framework/endpoint_sanction.go | 13 +- qos/framework/endpoint_sanction_defaults.go | 4 +- qos/framework/endpoint_store.go | 2 +- qos/framework/framework.go | 8 +- qos/framework/observations.go | 102 +++++++-- qos/framework/observations_endpoint.go | 2 +- qos/framework/observations_endpoint_error.go | 26 +-- qos/framework/observations_endpoint_result.go | 31 ++- qos/framework/observations_interpreter.go | 2 - qos/framework/observations_jsonrpc.go | 38 ++++ qos/framework/observations_request.go | 21 +- qos/framework/observations_request_error.go | 17 +- qos/framework/qos.go | 35 ++- qos/framework/request.go | 119 +---------- qos/framework/request_errors.go | 25 +-- qos/framework/request_journal.go | 66 ++++-- qos/framework/state.go | 1 - qos/jsonrpc/request.go | 25 +++ 37 files changed, 764 insertions(+), 784 deletions(-) delete mode 100644 observation/qos/framework/observations.pb.go create mode 100644 observation/qos/framework/request_journal.pb.go delete mode 100644 proto/path/qos/framework/observations.proto create mode 100644 proto/path/qos/framework/request_journal.proto delete mode 100644 qos/framework/endpoint_query.go create mode 100644 qos/framework/observations_jsonrpc.go diff --git a/observation/qos/framework/observations.pb.go b/observation/qos/framework/observations.pb.go deleted file mode 100644 index 84d5e6a68..000000000 --- a/observation/qos/framework/observations.pb.go +++ /dev/null @@ -1,176 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.35.2 -// protoc v5.28.3 -// source: path/qos/framework/observations.proto - -package framework - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Observations is the top-level container for all QoS observations for a request. -type Observations struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Service identification - ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // e.g. EVM, Solana, CometBFT - // Observation of the client request - RequestObservation *RequestObservation `protobuf:"bytes,2,opt,name=request_observation,json=requestObservation,proto3" json:"request_observation,omitempty"` - // Observations from endpoint(s) - EndpointQueyResultObservations []*EndpointQueryResult `protobuf:"bytes,3,rep,name=endpoint_quey_result_observations,json=endpointQueyResultObservations,proto3" json:"endpoint_quey_result_observations,omitempty"` -} - -func (x *Observations) Reset() { - *x = Observations{} - mi := &file_path_qos_framework_observations_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Observations) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Observations) ProtoMessage() {} - -func (x *Observations) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_framework_observations_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Observations.ProtoReflect.Descriptor instead. -func (*Observations) Descriptor() ([]byte, []int) { - return file_path_qos_framework_observations_proto_rawDescGZIP(), []int{0} -} - -func (x *Observations) GetServiceName() string { - if x != nil { - return x.ServiceName - } - return "" -} - -func (x *Observations) GetRequestObservation() *RequestObservation { - if x != nil { - return x.RequestObservation - } - return nil -} - -func (x *Observations) GetEndpointQueyResultObservations() []*EndpointQueryResult { - if x != nil { - return x.EndpointQueyResultObservations - } - return nil -} - -var File_path_qos_framework_observations_proto protoreflect.FileDescriptor - -var file_path_qos_framework_observations_proto_rawDesc = []byte{ - 0x0a, 0x25, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, - 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, - 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x20, 0x70, 0x61, 0x74, - 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, - 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, - 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfe, 0x01, - 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x57, 0x0a, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, - 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, - 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x72, 0x0a, 0x21, 0x65, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, - 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1e, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x79, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x3a, - 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, - 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, - 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, -} - -var ( - file_path_qos_framework_observations_proto_rawDescOnce sync.Once - file_path_qos_framework_observations_proto_rawDescData = file_path_qos_framework_observations_proto_rawDesc -) - -func file_path_qos_framework_observations_proto_rawDescGZIP() []byte { - file_path_qos_framework_observations_proto_rawDescOnce.Do(func() { - file_path_qos_framework_observations_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_observations_proto_rawDescData) - }) - return file_path_qos_framework_observations_proto_rawDescData -} - -var file_path_qos_framework_observations_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_path_qos_framework_observations_proto_goTypes = []any{ - (*Observations)(nil), // 0: path.qos.framework.Observations - (*RequestObservation)(nil), // 1: path.qos.framework.RequestObservation - (*EndpointQueryResult)(nil), // 2: path.qos.framework.EndpointQueryResult -} -var file_path_qos_framework_observations_proto_depIdxs = []int32{ - 1, // 0: path.qos.framework.Observations.request_observation:type_name -> path.qos.framework.RequestObservation - 2, // 1: path.qos.framework.Observations.endpoint_quey_result_observations:type_name -> path.qos.framework.EndpointQueryResult - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name -} - -func init() { file_path_qos_framework_observations_proto_init() } -func file_path_qos_framework_observations_proto_init() { - if File_path_qos_framework_observations_proto != nil { - return - } - file_path_qos_framework_request_proto_init() - file_path_qos_framework_endpoint_query_result_proto_init() - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_path_qos_framework_observations_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_path_qos_framework_observations_proto_goTypes, - DependencyIndexes: file_path_qos_framework_observations_proto_depIdxs, - MessageInfos: file_path_qos_framework_observations_proto_msgTypes, - }.Build() - File_path_qos_framework_observations_proto = out.File - file_path_qos_framework_observations_proto_rawDesc = nil - file_path_qos_framework_observations_proto_goTypes = nil - file_path_qos_framework_observations_proto_depIdxs = nil -} diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go index e8631b368..72e1ac71e 100644 --- a/observation/qos/framework/request.pb.go +++ b/observation/qos/framework/request.pb.go @@ -155,64 +155,6 @@ func (x *RequestError) GetJsonrpcRequest() *JsonRpcRequest { return nil } -// RequestObservation captures details about the original client request. -type RequestObservation struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Only set if parsing and basic JSONRPC validation was successful. - JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,1,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3,oneof" json:"jsonrpc_request,omitempty"` - // Only set if the request failed. - // A parsed request can still have error set: - // e.g. if the QoS service does not support the JSONRPC request's method. - RequestError *RequestError `protobuf:"bytes,2,opt,name=request_error,json=requestError,proto3,oneof" json:"request_error,omitempty"` -} - -func (x *RequestObservation) Reset() { - *x = RequestObservation{} - mi := &file_path_qos_framework_request_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RequestObservation) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RequestObservation) ProtoMessage() {} - -func (x *RequestObservation) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_framework_request_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RequestObservation.ProtoReflect.Descriptor instead. -func (*RequestObservation) Descriptor() ([]byte, []int) { - return file_path_qos_framework_request_proto_rawDescGZIP(), []int{1} -} - -func (x *RequestObservation) GetJsonrpcRequest() *JsonRpcRequest { - if x != nil { - return x.JsonrpcRequest - } - return nil -} - -func (x *RequestObservation) GetRequestError() *RequestError { - if x != nil { - return x.RequestError - } - return nil -} - var File_path_qos_framework_request_proto protoreflect.FileDescriptor var file_path_qos_framework_request_proto_rawDesc = []byte{ @@ -238,45 +180,31 @@ var file_path_qos_framework_request_proto_rawDesc = []byte{ 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0xd8, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x0f, 0x6a, 0x73, - 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, - 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x0d, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6a, 0x73, 0x6f, - 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x10, 0x0a, 0x0e, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2a, 0xc5, - 0x02, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, - 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x42, 0x4f, 0x44, - 0x59, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, - 0x12, 0x29, 0x0a, 0x25, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, - 0x43, 0x4f, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x31, 0x0a, 0x2d, 0x52, - 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, - 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x03, 0x12, 0x2c, - 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, + 0x65, 0x73, 0x74, 0x2a, 0xc5, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, 0x55, + 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, + 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, + 0x4c, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x29, 0x0a, 0x25, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, + 0x12, 0x31, 0x0a, 0x2d, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4d, + 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, + 0x04, 0x12, 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, + 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, 0x05, 0x12, 0x2b, + 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x2b, 0x0a, 0x27, - 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, - 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, 0x05, 0x12, 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, - 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, - 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, - 0x52, 0x41, 0x4d, 0x53, 0x10, 0x06, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, - 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, - 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x53, 0x10, 0x06, 0x42, 0x3a, 0x5a, 0x38, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, + 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, + 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -292,23 +220,20 @@ func file_path_qos_framework_request_proto_rawDescGZIP() []byte { } var file_path_qos_framework_request_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_path_qos_framework_request_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_path_qos_framework_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_path_qos_framework_request_proto_goTypes = []any{ - (RequestErrorKind)(0), // 0: path.qos.framework.RequestErrorKind - (*RequestError)(nil), // 1: path.qos.framework.RequestError - (*RequestObservation)(nil), // 2: path.qos.framework.RequestObservation - (*JsonRpcRequest)(nil), // 3: path.qos.framework.JsonRpcRequest + (RequestErrorKind)(0), // 0: path.qos.framework.RequestErrorKind + (*RequestError)(nil), // 1: path.qos.framework.RequestError + (*JsonRpcRequest)(nil), // 2: path.qos.framework.JsonRpcRequest } var file_path_qos_framework_request_proto_depIdxs = []int32{ 0, // 0: path.qos.framework.RequestError.error_kind:type_name -> path.qos.framework.RequestErrorKind - 3, // 1: path.qos.framework.RequestError.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest - 3, // 2: path.qos.framework.RequestObservation.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest - 1, // 3: path.qos.framework.RequestObservation.request_error:type_name -> path.qos.framework.RequestError - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 2, // 1: path.qos.framework.RequestError.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_path_qos_framework_request_proto_init() } @@ -318,14 +243,13 @@ func file_path_qos_framework_request_proto_init() { } file_path_qos_framework_jsonrpc_proto_init() file_path_qos_framework_request_proto_msgTypes[0].OneofWrappers = []any{} - file_path_qos_framework_request_proto_msgTypes[1].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_path_qos_framework_request_proto_rawDesc, NumEnums: 1, - NumMessages: 2, + NumMessages: 1, NumExtensions: 0, NumServices: 0, }, diff --git a/observation/qos/framework/request_journal.pb.go b/observation/qos/framework/request_journal.pb.go new file mode 100644 index 000000000..62df1b271 --- /dev/null +++ b/observation/qos/framework/request_journal.pb.go @@ -0,0 +1,200 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc v5.28.3 +// source: path/qos/framework/request_journal.proto + +package framework + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RequestJournal is the top-level container for all QoS observations for a request. +type RequestJournal struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Service identification + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // e.g. EVM, Solana, CometBFT + // Only set if parsing and basic JSONRPC validation was successful. + JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,2,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3,oneof" json:"jsonrpc_request,omitempty"` + // Only set if the request failed. + // A parsed request can still have error set: + // e.g. if the QoS service does not support the JSONRPC request's method. + RequestError *RequestError `protobuf:"bytes,3,opt,name=request_error,json=requestError,proto3,oneof" json:"request_error,omitempty"` + // Observations from endpoint(s) + EndpointQueyResultObservations []*EndpointQueryResult `protobuf:"bytes,4,rep,name=endpoint_quey_result_observations,json=endpointQueyResultObservations,proto3" json:"endpoint_quey_result_observations,omitempty"` +} + +func (x *RequestJournal) Reset() { + *x = RequestJournal{} + mi := &file_path_qos_framework_request_journal_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequestJournal) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestJournal) ProtoMessage() {} + +func (x *RequestJournal) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_request_journal_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestJournal.ProtoReflect.Descriptor instead. +func (*RequestJournal) Descriptor() ([]byte, []int) { + return file_path_qos_framework_request_journal_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestJournal) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *RequestJournal) GetJsonrpcRequest() *JsonRpcRequest { + if x != nil { + return x.JsonrpcRequest + } + return nil +} + +func (x *RequestJournal) GetRequestError() *RequestError { + if x != nil { + return x.RequestError + } + return nil +} + +func (x *RequestJournal) GetEndpointQueyResultObservations() []*EndpointQueryResult { + if x != nil { + return x.EndpointQueyResultObservations + } + return nil +} + +var File_path_qos_framework_request_journal_proto protoreflect.FileDescriptor + +var file_path_qos_framework_request_journal_proto_rawDesc = []byte{ + 0x0a, 0x28, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6a, 0x6f, 0x75, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, + 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x20, + 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xeb, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, + 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, + 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x72, 0x0a, 0x21, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1e, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6a, + 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x10, + 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, + 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, + 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_path_qos_framework_request_journal_proto_rawDescOnce sync.Once + file_path_qos_framework_request_journal_proto_rawDescData = file_path_qos_framework_request_journal_proto_rawDesc +) + +func file_path_qos_framework_request_journal_proto_rawDescGZIP() []byte { + file_path_qos_framework_request_journal_proto_rawDescOnce.Do(func() { + file_path_qos_framework_request_journal_proto_rawDescData = protoimpl.X.CompressGZIP(file_path_qos_framework_request_journal_proto_rawDescData) + }) + return file_path_qos_framework_request_journal_proto_rawDescData +} + +var file_path_qos_framework_request_journal_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_path_qos_framework_request_journal_proto_goTypes = []any{ + (*RequestJournal)(nil), // 0: path.qos.framework.RequestJournal + (*JsonRpcRequest)(nil), // 1: path.qos.framework.JsonRpcRequest + (*RequestError)(nil), // 2: path.qos.framework.RequestError + (*EndpointQueryResult)(nil), // 3: path.qos.framework.EndpointQueryResult +} +var file_path_qos_framework_request_journal_proto_depIdxs = []int32{ + 1, // 0: path.qos.framework.RequestJournal.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest + 2, // 1: path.qos.framework.RequestJournal.request_error:type_name -> path.qos.framework.RequestError + 3, // 2: path.qos.framework.RequestJournal.endpoint_quey_result_observations:type_name -> path.qos.framework.EndpointQueryResult + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_path_qos_framework_request_journal_proto_init() } +func file_path_qos_framework_request_journal_proto_init() { + if File_path_qos_framework_request_journal_proto != nil { + return + } + file_path_qos_framework_jsonrpc_proto_init() + file_path_qos_framework_request_proto_init() + file_path_qos_framework_endpoint_query_result_proto_init() + file_path_qos_framework_request_journal_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_path_qos_framework_request_journal_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_path_qos_framework_request_journal_proto_goTypes, + DependencyIndexes: file_path_qos_framework_request_journal_proto_depIdxs, + MessageInfos: file_path_qos_framework_request_journal_proto_msgTypes, + }.Build() + File_path_qos_framework_request_journal_proto = out.File + file_path_qos_framework_request_journal_proto_rawDesc = nil + file_path_qos_framework_request_journal_proto_goTypes = nil + file_path_qos_framework_request_journal_proto_depIdxs = nil +} diff --git a/observation/qos/observations.pb.go b/observation/qos/observations.pb.go index 48fffaa53..0dae857dc 100644 --- a/observation/qos/observations.pb.go +++ b/observation/qos/observations.pb.go @@ -32,7 +32,7 @@ type Observations struct { // // Types that are assignable to ServiceObservations: // - // *Observations_JsonrpcService + // *Observations_RequestJournal ServiceObservations isObservations_ServiceObservations `protobuf_oneof:"service_observations"` } @@ -73,9 +73,9 @@ func (m *Observations) GetServiceObservations() isObservations_ServiceObservatio return nil } -func (x *Observations) GetJsonrpcService() *framework.Observations { - if x, ok := x.GetServiceObservations().(*Observations_JsonrpcService); ok { - return x.JsonrpcService +func (x *Observations) GetRequestJournal() *framework.RequestJournal { + if x, ok := x.GetServiceObservations().(*Observations_RequestJournal); ok { + return x.RequestJournal } return nil } @@ -84,32 +84,33 @@ type isObservations_ServiceObservations interface { isObservations_ServiceObservations() } -type Observations_JsonrpcService struct { +type Observations_RequestJournal struct { // jsonrpc contains QoS measurements for a JSON-RPC based service request - JsonrpcService *framework.Observations `protobuf:"bytes,1,opt,name=jsonrpc_service,json=jsonrpcService,proto3,oneof"` + RequestJournal *framework.RequestJournal `protobuf:"bytes,1,opt,name=request_journal,json=requestJournal,proto3,oneof"` } -func (*Observations_JsonrpcService) isObservations_ServiceObservations() {} +func (*Observations_RequestJournal) isObservations_ServiceObservations() {} var File_path_qos_observations_proto protoreflect.FileDescriptor var file_path_qos_observations_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, - 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x1a, 0x25, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, - 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6f, 0x62, 0x73, 0x65, - 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x73, - 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4b, - 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, - 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, - 0x6e, 0x72, 0x70, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x16, 0x0a, 0x14, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, - 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x1a, 0x28, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, + 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x6a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x75, 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x4d, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6a, 0x6f, 0x75, + 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, + 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x48, 0x00, + 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, + 0x42, 0x16, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, + 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -126,11 +127,11 @@ func file_path_qos_observations_proto_rawDescGZIP() []byte { var file_path_qos_observations_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_path_qos_observations_proto_goTypes = []any{ - (*Observations)(nil), // 0: path.qos.Observations - (*framework.Observations)(nil), // 1: path.qos.framework.Observations + (*Observations)(nil), // 0: path.qos.Observations + (*framework.RequestJournal)(nil), // 1: path.qos.framework.RequestJournal } var file_path_qos_observations_proto_depIdxs = []int32{ - 1, // 0: path.qos.Observations.jsonrpc_service:type_name -> path.qos.framework.Observations + 1, // 0: path.qos.Observations.request_journal:type_name -> path.qos.framework.RequestJournal 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name @@ -144,7 +145,7 @@ func file_path_qos_observations_proto_init() { return } file_path_qos_observations_proto_msgTypes[0].OneofWrappers = []any{ - (*Observations_JsonrpcService)(nil), + (*Observations_RequestJournal)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/proto/path/qos/framework/endpoint_query_result.proto b/proto/path/qos/framework/endpoint_query_result.proto index dec4fb005..48156f42f 100644 --- a/proto/path/qos/framework/endpoint_query_result.proto +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -13,6 +13,9 @@ message EndpointQueryResult { // Address of the endpoint that handled the request string endpoint_addr = 1; + // TODO_IN_THIS_PR: REMOVE: it is 200 IFF endpointError == nil, and otherwise determined by the EndpointErr (which contains the JSONRPC response error code) + // + // HTTP response returned to the client. int32 client_http_response = 2; diff --git a/proto/path/qos/framework/observations.proto b/proto/path/qos/framework/observations.proto deleted file mode 100644 index 6cbf7c013..000000000 --- a/proto/path/qos/framework/observations.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; -package path.qos.framework; - -option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; - -import "path/qos/framework/request.proto"; // Import RequestObservation -import "path/qos/framework/endpoint_query_result.proto"; // Import EndpointQueryResult - -// Observations is the top-level container for all QoS observations for a request. -message Observations { - // Service identification - string service_name = 1; // e.g. EVM, Solana, CometBFT - - // Observation of the client request - RequestObservation request_observation = 2; - - // Observations from endpoint(s) - repeated EndpointQueryResult endpoint_quey_result_observations = 3; -} diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto index c8b5c0ddb..2d193400c 100644 --- a/proto/path/qos/framework/request.proto +++ b/proto/path/qos/framework/request.proto @@ -16,27 +16,14 @@ enum RequestErrorKind { REQUEST_ERROR_VALIDATION_INVALID_PARAMS = 6; // Parameters are incorrect } -// TODO_IN_THIS_PR: Consider removing the http_status_code. - // RequestError contains details about a request error message RequestError { + // enumeration of the kind of error the request encountered. RequestErrorKind error_kind = 1; - // HTTP status code returned to the client. - int32 http_status_code = 2; - string error_details = 3; - - // Only set for validation failures on valid JSONRPC structures - // e.g. missing params field when required. - optional JsonRpcRequest jsonrpc_request = 4; -} -// RequestObservation captures details about the original client request. -message RequestObservation { - // Only set if parsing and basic JSONRPC validation was successful. - optional JsonRpcRequest jsonrpc_request = 1; - - // Only set if the request failed. - // A parsed request can still have error set: - // e.g. if the QoS service does not support the JSONRPC request's method. - optional RequestError request_error = 2; + // Details of the request error. + string error_details = 2; + + // The response returned to the client. + JsonRpcResponse json_rpc_response = 3; } diff --git a/proto/path/qos/framework/request_journal.proto b/proto/path/qos/framework/request_journal.proto new file mode 100644 index 000000000..0b40e0698 --- /dev/null +++ b/proto/path/qos/framework/request_journal.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package path.qos.framework; + +option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; + +import "path/qos/framework/jsonrpc.proto"; // Import basic JSONRPC messages. +import "path/qos/framework/request.proto"; // Import Request-related messages. +import "path/qos/framework/endpoint_query_result.proto"; // Import EndpointQueryResult + +// RequestJournal is the top-level container for all QoS observations for a request. +message RequestJournal { + // Service identification + string service_name = 1; // e.g. EVM, Solana, CometBFT + + // Only set if parsing and basic JSONRPC validation was successful. + optional JsonRpcRequest jsonrpc_request = 2; + + // Only set if the request failed. + // A parsed request can still have error set: + // e.g. if the QoS service does not support the JSONRPC request's method. + optional RequestError request_error = 3; + + // Observations from endpoint(s) + repeated EndpointQueryResult endpoint_quey_result_observations = 4; +} diff --git a/proto/path/qos/observations.proto b/proto/path/qos/observations.proto index 2fa3019c0..3d1cddb0a 100644 --- a/proto/path/qos/observations.proto +++ b/proto/path/qos/observations.proto @@ -3,7 +3,7 @@ package path.qos; option go_package = "github.com/buildwithgrove/path/observation/qos"; -import "path/qos/framework/observations.proto"; // Import JSONRPC Framework observations +import "path/qos/framework/request_journal.proto"; // Import JSONRPC Framework observations // Observations contains QoS measurements for a single service request. // Currently only supports JSONRPC-based service observations. @@ -11,7 +11,7 @@ message Observations { // service_observations contains QoS measurements specific to the service type oneof service_observations { // jsonrpc contains QoS measurements for a JSON-RPC based service request - path.qos.framework.Observations jsonrpc_service = 1; + path.qos.framework.RequestJournal request_journal = 1; // Additional service types can be added here in the future // For example: diff --git a/qos/example_evm/endpoint_result_blocknumber.go b/qos/example_evm/endpoint_result_blocknumber.go index fce1288b0..5d70c7588 100644 --- a/qos/example_evm/endpoint_result_blocknumber.go +++ b/qos/example_evm/endpoint_result_blocknumber.go @@ -24,7 +24,7 @@ func responseBuilderBlockNumber(ctx *framework.EndpointQueryResultContext) *fram // // The endpoint returned an error response: no further processing needed. if ctx.IsJSONRPCError() { - return ctx.Error("endpoint returned a valid JSONRPC error response.") + return ctx.Error("endpoint returned a JSONRPC error response.") } // TODO_MVP(@adshmh): use the contents of the result field to determine the validity of the response. diff --git a/qos/framework/context_endpoint_checks.go b/qos/framework/context_endpoint_checks.go index 7584b079d..44ffbd576 100644 --- a/qos/framework/context_endpoint_checks.go +++ b/qos/framework/context_endpoint_checks.go @@ -2,9 +2,8 @@ package framework // TODO_IN_THIS_PR: define, in the framework package, a context for adding QoS endpoint quality checks: // - Struct name: QualityCheckContext -// - Struct Methods: +// - Struct Methods: // - GetEndpoint(): to skip unnecessary checks. // - GetState(): e.g. for Archival checks // - AddCheck(jsonrpc.Request) // - diff --git a/qos/framework/context_endpoint_result.go b/qos/framework/context_endpoint_result.go index 37a6676fb..246353928 100644 --- a/qos/framework/context_endpoint_result.go +++ b/qos/framework/context_endpoint_result.go @@ -23,20 +23,13 @@ type EndpointQueryResultContext struct { jsonrpcMethodResultBuilders map[jsonrpc.Method]EndpointQueryResultBuilder } -// ===> TODO_IN_THIS_PR: find a better name to signal to the client they should call this when done with updating the EndpointQueryResult. -func (ctx *EndpointQueryResultContext) Success() *EndpointQueryResult { - return &ctx.endpointQueryResult -} - - - // buildResult uses the supplied method builder to build the EndpointResult for the supplied endpointQuery. // A default builder is used if no matches were found for the request method. // Returns the endpointQuery augmented with the endpoint result. func (ctx *EndpointQueryResultContext) buildEndpointQueryResult() *EndpointQueryResult { // Parse the endpoint's payload into JSONRPC response. // Stores the parsed JSONRPC response in the endpointQuery. - shouldContinue := ctx.updateEndpointQueryWithParsedResponse() + shouldContinue := ctx.updateEndpointQueryResultWithParsedResponse() // Parsing failed: skip the rest of the processing. if !shouldContinue { @@ -64,38 +57,38 @@ func (ctx *EndpointQueryResultContext) buildEndpointQueryResult() *EndpointQuery // // parseEndpointQuery parses the payload from an endpoint and handles empty responses and parse errors. // It returns a boolean indicating whether processing should continue (true) or stop (false). -func (ctx *EndpointQueryResultContext) updateEndpointQueryWithParsedResponse() bool { +func (ctx *EndpointQueryResultContext) updateEndpointQueryResultWithParsedResponse() bool { logger := ctx.getHydratedLogger() - endpointQuery := ctx.EndpointQueryResult.endpointQuery - // Check for empty response - if len(endpointQuery.receivedData) == 0 { - ctx.logger.Info() - endpointQuery.result = buildResultForEmptyResponse(endpointQuery) - return endpointQuery, false + if len(ctx.EndpointQueryResult.endpointPayload) == 0 { + ctx.logger.Info().Msg("Received payload with 0 length from the endpoint. Service request will fail.") + + ctx.EndpointQueryResult = buildResultForEmptyResponse(ctx.EndpointQueryResult) + return false } // Parse JSONRPC response var jsonrpcResp jsonrpc.JsonRpcResponse - if err := json.Unmarshal(endpointQuery.receivedData, &jsonrpcResp); err != nil { - endpointQuery.result = buildResultForErrorUnmarshalingEndpointReturnedData(endpointQuery, err) - return endpointQuery, false + if err := json.Unmarshal(ctx.EndpointQueryResult.endpointPayload, &jsonrpcResp); err != nil { + // Error parsing the endpoint payload: return generic response to the client. + ctx.EndpointQueryResult = buildResultForErrorUnmarshalingEndpointReturnedData(ctx.EndpointQueryResult, err) + return false } // Validate the JSONRPC response if err := jsonrpcResp.Validate(eq.request.ID); err != nil { // TODO_IN_THIS_PR: define a separate method for JSONRPC response validation errors. - endpointQuery.result = buildResultForErrorUnmarshalingEndpointReturnedData(endpointQuery, err) + ctx.EndpointQueryResult = buildResultForErrorUnmarshalingEndpointPayload(ctx.EndpointQueryResult, err) return endpointQuery, false } // Store the parsed result - endpointQuery.parsedResponse = jsonrpcResp + ctx.EndpointQueryResult.ParsedJSONRPCResponse = jsonrpcResp // Return true to signal that parsing was successful. // Processing will continue to the next step. - return endpointQuery, true + return true } // TODO_IN_THIS_PR: implement. diff --git a/qos/framework/context_request.go b/qos/framework/context_request.go index 982e4b061..85790955e 100644 --- a/qos/framework/context_request.go +++ b/qos/framework/context_request.go @@ -73,10 +73,10 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // indicating the validity of the request when calling on QoS instance's ParseHTTPRequest // // Instantiate an endpointQuery to capture the interaction with the service endpoint. - endpointQuery := rc.journal.buildEndpointQuery(endpointAddr, receivedData) + endpointQueryResult := rc.journal.buildEndpointQueryResult(endpointAddr, receivedData) // Instantiate a result context using the endpointQuery. - resultCtx := rc.contextBuilder.buildEndpointQueryResultContext(endpointQuery) + resultCtx := rc.contextBuilder.buildEndpointQueryResultContext(endpointQueryResult) // Build an endpoint query result using the context. endpointQueryResult := resultCtx.buildEndpointQueryResult() @@ -119,10 +119,72 @@ func (rc *requestQoSContext) GetEndpointSelector() protocol.EndpointSelector { // Declares the request as failed with protocol-level error if no data from any endpoints has been reported to the request context. func (rc *requestContext) checkForProtocolLevelError() { - // TODO_IMPROVE(@adshmh): consider using the journal directly for setting protocol failure error. - // // Assume protocol-level error if no endpoint responses have been received yet. - if len(rc.journal.processedEndpointQueries) == 0 { - rc.journal.requestDetails.setProtocolLevelError() + if len(rc.journal.endpointQueryResults) == 0 { + rc.journal.setProtocolLevelError() + } +} + + +func (ctx *requestContext) initFromHTTP(httpReq *http.Request) bool { + jsonrpcReq, reqErr := parseHTTPRequest(ctx.logger, httpReq) + + // initialize the request journal to track all request data and events. + journal: &requestJournal{ + jsonrpcRequest: jsonrpcReq, + requestErr: reqErr, + }, + + // Only proceed with next steps if there were no errors parsing the HTTP request into a JSONRPC request. + return reqErr == nil +} + +// parseHTTPRequest builds and returns a context for processing the HTTP request: +// - Reads and processes the HTTP request +// - Parses a JSONRPC request from the HTTP request's payload. +// - Validates the resulting JSONRPC request. +func parseHTTPRequest( + logger polylog.Logger, + httpReq *http.Request, +) (*jsonrpc.Request, *requestError) { + // Read the HTTP request body + body, err := io.ReadAll(httpReq.Body) + defer httpReq.Body.Close() + + // TODO_IMPROVE(@adshmh): Propagate a request ID parameter on internal errors that occur after successful request parsing. + // There are no such cases as of PR #186. + if err != nil { + // Handle read error (internal server error) + logger.Error().Err(err).Msg("Failed to read request body") + + // return the error details to be stored in the request journal. + return nil, buildRequestErrorForInternalErrHTTPRead(err) } + + // Parse the JSON-RPC request + var jsonrpcReq jsonrpc.JsonRpcRequest + if err := json.Unmarshal(body, &jsonrpcReq); err != nil { + // TODO_IN_THIS_PR: log the first 1K bytes of the body. + // Handle parse error (client error) + logger.Error().Err(err).Msg("Failed to parse JSON-RPC request") + + return nil, buildRequestErrorForParseError(err) + } + + // Validate the request + if validationErr := jsonrpcReq.Validate(); validationErr != nil { + // Request failed basic JSONRPC request validation. + logger.Info().Err(validationErr). + Str("method", jsonrpcReq.Method). + Msg("JSONRPC Request validation failed") + + return jsonrpcReq, buildRequestErrorJSONRPCValidationError(jsonrpcReq.ID, validationErr) + } + + // Request is valid + logger.Debug(). + Str("method", jsonrpcReq.Method). + Msg("Request validation successful") + + return jsonrpcReq, nil } diff --git a/qos/framework/context_state_update.go b/qos/framework/context_state_update.go index cd594456c..6119668b1 100644 --- a/qos/framework/context_state_update.go +++ b/qos/framework/context_state_update.go @@ -13,7 +13,7 @@ type StateUpdateContext struct { updatedEndpoints []*Endpoint // tracks the set of params set for update through the context. - paramsToUpdate *StateParameterUpdateSet + paramsToUpdate *StateParameterUpdateSet } func (ctx *StateUpdateContext) updateFromEndpoints(updatedEndpoints []*Endpoint) error { diff --git a/qos/framework/endpoint.go b/qos/framework/endpoint.go index 8f4bd1912..5ead2d882 100644 --- a/qos/framework/endpoint.go +++ b/qos/framework/endpoint.go @@ -69,7 +69,7 @@ func (e *Endpoint) HasActiveSanction() (Sanction, bool) { // ApplyQueryResult updates the endpoint's attributes with attributes from the query result. // It merges the EndpointAttributes from the query result into the endpoint's attributes map. -func (e *Endpoint) applyQueryResults(queryResults []*EndpointQueryResult) { +func (e *Endpoint) applyQueryResults(endpointQueryResults []*EndpointQueryResult) { e.resultsMu.Lock() defer e.resultsMu.Unlock() @@ -79,29 +79,17 @@ func (e *Endpoint) applyQueryResults(queryResults []*EndpointQueryResult) { } // Add or update attributes from the query result - for _, result := range queryResults { - query := result.endpointQuery + for _, endpointQueryResult := range endpointQueryResults { + jsonrpcRequestMethod := endpointQueryResult.getJSONRPCRequestMethod() - // Validate the supplied query results. - if query == nil { - e.logger.Warn().Msg("Endpoint received query result with no query data: skipping update.") - return - } - - queryRequest := query.request - if queryRequest == nil { - e.logger.Warn().Msg("Endpoint received query result with no query request data: skipping update.") - return - } - - if queryRequest.Method == "" { + if jsonrpcRequestMethod == "" { e.logger.Warn().Msg("Endpoint received query result with no JSONRPC method set: skipping update.") return } // Update the endpoint result matching the JSONRPC request. - e.queryResult[queryRequest.Method] = result + e.queryResult[jsonrpcRequestMethod] = result - e.logger.With("request_method", queryRequest.Method).Debug().Msg("Updated endpoint with query result.") + e.logger.With("jsonrpc_request_method", jsonrpcRequestMethod).Debug().Msg("Updated endpoint with query result.") } } diff --git a/qos/framework/endpoint_errors.go b/qos/framework/endpoint_errors.go index de2f881a5..fd44b4857 100644 --- a/qos/framework/endpoint_errors.go +++ b/qos/framework/endpoint_errors.go @@ -1,11 +1,12 @@ package framework type EndpointErrorKind int + const ( - _ EndpointErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. - EndpointErrKindEmptyPayload // Empty payload from endpoint - EndpointErrKindParseErr // Could not parse endpoint payload - EndpointErrKindInvalidResult // Payload result doesn't match expected value: e.g. invalid chainID value + _ EndpointErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. + EndpointErrKindEmptyPayload // Empty payload from endpoint + EndpointErrKindParseErr // Could not parse endpoint payload + EndpointErrKindInvalidResult // Payload result doesn't match expected value: e.g. invalid chainID value ) // EndpointError contains error details for endpoint queries. diff --git a/qos/framework/endpoint_query.go b/qos/framework/endpoint_query.go deleted file mode 100644 index 4188a7e44..000000000 --- a/qos/framework/endpoint_query.go +++ /dev/null @@ -1,26 +0,0 @@ -package framework - -import ( - "github.com/buildwithgrove/path/protocol" - "github.com/buildwithgrove/path/qos/jsonrpc" -) - -// EndpointQuery represents a raw communication attempt with an endpoint. -// Instantiated by: RequestQoSContext. -// Used in EndpointQueryResultContext. -type endpointQuery struct { - // request is the JSONRPC request that was sent. - request *jsonrpc.Request - - // TODO_IN_THIS_PR: REMOVE this field to be consistent with the proto files. - // - // endpointAddr identifies the endpoint - endpointAddr protocol.EndpointAddr - - // receivedData is the raw response data received from the endpoint (may be nil) - receivedData []byte - - // JSONRPC response, parsed from the data received from the endpoint. - // Only set if the data received from the endpoint could be parsed into a JSONRPC response. - parsedResponse *jsonrpc.Response -} diff --git a/qos/framework/endpoint_query_result.go b/qos/framework/endpoint_query_result.go index 0eb18ddb7..f8247db43 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/framework/endpoint_query_result.go @@ -25,15 +25,33 @@ import ( // - Stores one or more string/integer values. // - Contains error/sanction information on endpoint error. type EndpointQueryResult struct { - // The endpointQuery from which this result was built. - // It can be used, e.g. to retrieve the JSONRPC request and its method. - *endpointQuery + // The request's journal. + // Used to retrieve details of the JSONRPC request, e.g. the JSONRPC method. + // Declared embedded to allow direct access by other members of the `judge` package. + *requestJournal + + // Tracks the address of the endpoint for which the result is built. + endpointAddr protocol.EndpointAddr + + // Tracks the payload received from the endpoint in response to the JSONRPC request. + // Custom QoS service does NOT have access to this: it can only act on a parsed JSONRPC response. + endpointPayload []byte + + // Captures the queried endpoint's error and the response to return to the client. + // Can be set by either: + // - JUDGE: e.g. if the endpoint's payload failed to parse as a JSONRPC response. + // - Custom QoS: e.g. if the endpoint returned an unexpected block height. + // Only set if the query result indicates an endpoint error. + // It could also include sanctions: + // e.g. for an invalid value returned for an EVM `eth_blockNumber` request, the custom service could set: + // Error: + // - Description: "invalid response to eth_blockNumber" + // - RecommendedSanction: {Duration: 5 * time.Minute} + EndpointError *EndpointError - // TODO_IN_THIS_PR: verify this is set by all result builders. + // Only set if the endpoint's returned payload could be parsed into a JSONRPC response. + parsedJSONRPCResponse *jsonrpc.Response - // The JSONRPC response to be returned to the client. - // MUST be set. - clientResponse *jsonrpc.Response // The set of values/attributes extracted from the endpoint query and the endpoint's parsed JSONRPC response. // e.g. for a Solana `getEpochInfo` request, the custom service could derive two endpoint attributes as follows: @@ -42,15 +60,6 @@ type EndpointQueryResult struct { StringValues map[string]string IntValues map[string]int - // Captures the queried endpoint's error. - // Only set if the query result indicates an endpoint error. - // It could also include sanctions: - // e.g. for an invalid value returned for an EVM `eth_blockNumber` request, the custom service could set: - // Error: - // - Description: "invalid response to eth_blockNumber" - // - RecommendedSanction: {Duration: 5 * time.Minute} - Error *EndpointError - // The time at which the query result is expired. // Expired results will be ignored, including in: // - endpoint selection, e.g. sanctions. @@ -60,7 +69,10 @@ type EndpointQueryResult struct { // TODO_FUTURE(@adshmh): add a JSONRPCErrorResponse to allow a result builder to supply its custom JSONRPC response. } -// ===> These are moved from EndpointQueryResultContext --> it will have a public EndpointQueryResult field to allow direct access to the methods below. +func (eqr *EndpointQueryResult) GetEndpointAddr() protocol.EndpointAddr { + return eqr.endpointAddr +} + func (eqr *EndpointQueryResult) IsJSONRPCError() bool { parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() if err != nil { @@ -79,7 +91,7 @@ func (eqr *EndpointQueryResult) GetResultAsInt() (int, error) { return parsedJSONRPCResponse.GetResultAsInt() } -func (ctx *EndpointQueryResultContext) GetResultAsStr() (string, error) { +func (eqr *EndpointQueryResult) GetResultAsStr() (string, error) { parsedJSONRPCResponse := eqr.getParsedJSONRPCResponse() if err != nil { return "", err @@ -99,69 +111,73 @@ func (eqr *EndpointQueryResult) getParsedJSONRPCResponse() (*jsonrpc.Response, e return parsedJSONRPCResponse, nil } -func (eqr *EndpointQueryResult) AddIntValue(key string, value int) { - eqr.endpointQueryResult.AddIntValue(key, value) -} +func (eqr *EndpointQueryResult) Success( + resultBuilders ...EndpointQueryResultBuilder +) *EndpointQueryResult { + for _, builder := range resultBuilders { + builder(eqr) + } -func (ctx *EndpointQueryResultContext) AddStrValue(key, value string) { - ctx.endpointQueryResult.AddStrValue(key, value) + return eqr } - // ErrorResult creates an error result with the given message and no sanction. -func (ctx *EndpointQueryResultContext) ErrorResult(description string) *EndpointQueryResult { - - return &ResultData{ - Type: ctx.Method, - Error: &ResultError{ - Description: description, - kind: EndpointDataErrorKindInvalidResult, - }, +// Returns a self-reference for a fluent API. +func (eqr *EndpointQueryResult) Error(description string) *EndpointQueryResult { + eqr.EndpointError = &EndpointError { + ErrorKind: EndpointErrKindInvalidResult, + // Description is set by the custom service implementation + Description: description, } + + return eqr } // SanctionEndpoint creates an error result with a temporary sanction. -func (ctx *EndpointQueryResultContext) SanctionEndpoint(description, reason string, duration time.Duration) *ResultData { - return &ResultData{ - Type: ctx.Method, - Error: &ResultError{ - Description: description, - RecommendedSanction: &SanctionRecommendation{ - Sanction: Sanction{ - Type: SanctionTypeTemporary, - Reason: reason, - ExpiryTime: time.Now().Add(duration), - CreatedTime: time.Now(), - }, - SourceDataType: ctx.Method, - TriggerDetails: description, - }, - kind: EndpointDataErrorKindInvalidResult, +func (eqr *EndpointQueryResult) SanctionEndpoint(description, reason string, duration time.Duration) *EndpointQueryResult { + eqr.EndpointError = &EndpointError { + ErrorKind: EndpointDataErrorKindInvalidResult, + Description: description, + RecommendedSanction: &Sanction{ + Type: SanctionTypeTemporary, + Reason: reason, + ExpiryTime: time.Now().Add(duration), }, - CreatedTime: time.Now(), } + + return eqr } // PermanentSanction creates an error result with a permanent sanction. -func (ctx *EndpointQueryResultContext) PermanentSanction(description, reason string) *ResultData { - return &ResultData{ - Type: ctx.Method, - Error: &ResultError{ - Description: description, - RecommendedSanction: &SanctionRecommendation{ - Sanction: Sanction{ - Type: SanctionTypePermanent, - Reason: reason, - CreatedTime: time.Now(), - }, - SourceDataType: ctx.Method, - TriggerDetails: description, - }, - kind: EndpointDataErrorKindInvalidResult, +func (eqr *EndpointQueryResult) PermanentSanction(description, reason string) *EndpointQueryResult { + eqr.EndpointError = &EndpointError { + ErrorKind: EndpointDataErrorKindInvalidResult, + Description: description, + RecommendedSanction: &Sanction{ + Type: SanctionTypePermanent, + Reason: reason, }, - CreatedTime: time.Now(), } + + return eqr } +type EndpointQueryResultBuilder func(*EndpointQueryResultBuilder) +func (eqr *EndpointQueryResult) AddIntResult(key string, value int) EndpointQueryResultBuilder { + return func(r *EndpointQueryResult) { + if r.IntValues == nil { + r.IntValues = make(map[string]int) + } + r.IntValues[key] = value + } +} +func (eqr *EndpointQueryResult) AddStrResult(key, value string) EndpointQueryResultBuilder { + return func(r *EndpointQueryResult) { + if r.StrValues == nil { + r.StrValues = make(map[string]string) + } + r.StrValues[key] = value + } +} diff --git a/qos/framework/endpoint_query_result_defaults.go b/qos/framework/endpoint_query_result_defaults.go index e7b7c14fd..de97e29e7 100644 --- a/qos/framework/endpoint_query_result_defaults.go +++ b/qos/framework/endpoint_query_result_defaults.go @@ -13,15 +13,14 @@ import ( func defaultResultBuilder(ctx *EndpointQueryResultContext) *EndpointQueryResult { //TODO_IN_THIS_PR: implement this function: /* - JsonrpcResponse: &qosobservations.JsonRpcResponse{ - Id: r.jsonRPCResponse.ID.String(), - }, - ResponseValidationError: r.validationError, - HttpStatusCode: int32(r.getHTTPStatusCode()), + JsonrpcResponse: &qosobservations.JsonRpcResponse{ + Id: r.jsonRPCResponse.ID.String(), + }, + ResponseValidationError: r.validationError, + HttpStatusCode: int32(r.getHTTPStatusCode()), */ } - // TODO_IN_THIS_PR: clarify that the following happens for a NoResponse: // - NoResponse's underlying getHTTPStatusCode always returns a 500 Internal error. // - NoResponse is always an invalid response. @@ -40,21 +39,21 @@ func buildResultForNoResponse(request *jsonrpc.JsonRpcRequest) *EndpointQueryRes parseError: fmt.Errorf("no endpoint responses received"), }, } - + // Create result context for creating the result ctx := &ResultContext{ Request: request, } - + // Apply default sanction for no response result.ResultData = applySanctionForNoResponse(ctx) - + // Set error kind result.ResultData.Error.kind = EndpointDataErrorKindNoInteraction - + // Set error response result.ErrorResponse = newErrResponseNoEndpointResponses(request.Id) - + return result } @@ -78,22 +77,22 @@ func buildResultForEmptyResponse(call *EndpointCall) *EndpointQueryResult { parseError: fmt.Errorf("empty response from endpoint"), }, } - + // Create result context for creating the result ctx := &ResultContext{ EndpointAddr: call.EndpointAddr, Request: call.Request, } - + // Apply default sanction for empty response result.ResultData = applySanctionForEmptyResponse(ctx) - + // Set error kind result.ResultData.Error.kind = EndpointDataErrorKindEmptyPayload - + // Set error response result.ErrorResponse = newErrResponseEmptyResponse(call.Request.Id) - + return result } @@ -109,22 +108,22 @@ func buildResultForErrorUnmarshalingEndpointReturnedData( parseError: parseError, }, } - + // Create result context for creating the result ctx := &ResultContext{ EndpointAddr: call.EndpointAddr, Request: call.Request, } - + // Apply default sanction for parse error result.ResultData = applySanctionForUnmarshalingError(ctx, parseError) - + // Set error kind and raw payload result.ResultData.Error.kind = EndpointDataErrorKindUnmarshaling result.ResultData.Error.rawPayload = call.ReceivedData - + // Set error response result.ErrorResponse = newErrResponseParseError(call.Request.Id, parseError) - + return result } diff --git a/qos/framework/endpoint_sanction.go b/qos/framework/endpoint_sanction.go index 3aa843337..75001bccb 100644 --- a/qos/framework/endpoint_sanction.go +++ b/qos/framework/endpoint_sanction.go @@ -3,6 +3,7 @@ package framework import ( "time" ) + // ====================== // Sanction Types // ====================== @@ -10,14 +11,14 @@ import ( type SanctionType int const ( - _ SanctionType = iota - SanctionTypeTemporary // Time-limited exclusion - SanctionTypePermanent // Permanent exclusion + _ SanctionType = iota + SanctionTypeTemporary // Time-limited exclusion + SanctionTypePermanent // Permanent exclusion ) // Sanction represents a recommendation to limit endpoint usage. type Sanction struct { - Type SanctionType - Reason string - ExpiryTime time.Time // Zero time means permanent + Type SanctionType + Reason string + ExpiryTime time.Time // Zero time means permanent } diff --git a/qos/framework/endpoint_sanction_defaults.go b/qos/framework/endpoint_sanction_defaults.go index 6d24195f6..7958cc762 100644 --- a/qos/framework/endpoint_sanction_defaults.go +++ b/qos/framework/endpoint_sanction_defaults.go @@ -10,10 +10,10 @@ import ( const ( // Default sanction duration for empty responses DefaultEmptyResponseSanctionDuration = 5 * time.Minute - + // Default sanction duration for parse errors DefaultParseErrorSanctionDuration = 5 * time.Minute - + // Default sanction duration for no responses DefaultNoResponseSanctionDuration = 5 * time.Minute ) diff --git a/qos/framework/endpoint_store.go b/qos/framework/endpoint_store.go index 9e388826a..f9f97b4fe 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/framework/endpoint_store.go @@ -11,7 +11,7 @@ import ( // endpointStore maintains data on the set of available endpoints. // It is package-private and not meant to be used directly by any entity outside the jsonrpc package. type endpointStore struct { - logger polylog.Logger + logger polylog.Logger endpointsMu sync.RWMutex endpoints map[protocol.EndpointAddr]*Endpoint } diff --git a/qos/framework/framework.go b/qos/framework/framework.go index b59b5fa9d..96021c078 100644 --- a/qos/framework/framework.go +++ b/qos/framework/framework.go @@ -1,5 +1,5 @@ // Package jsonrpc provides a framework for implementing Quality of Service (QoS) for JSONRPC-based services. -// +// // Key components: // - Context-based processing for standardizing service interactions // - Custom endpoint selection based on service state @@ -16,6 +16,12 @@ import ( "github.com/buildwithgrove/path/protocol" ) +// TODO_MVP(@adshmh): Allow custom QoS services to supply custom request validation logic. +// Example use case: specifying a list of allowed JSONRPC request methods. +// This would require: +// 1. Declaring a public RequestValidator interface. +// 2. Helper functions, e.g. BuildRequestValidatorForAllowedMethods. +// // TODO_FUTURE(@adshmh): Provide reasonable defaults for components to enable a no-config JSONRPC service QoS. // // QoSDefinition contains all custom behavior for a JSONRPC QoS service. diff --git a/qos/framework/observations.go b/qos/framework/observations.go index ec1fef6b0..a5af5cf4c 100644 --- a/qos/framework/observations.go +++ b/qos/framework/observations.go @@ -18,39 +18,101 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // initialize the observations to include: // - service name // - observations related to the request: - observations := qosobservations.Observations { + observations := qosobservations.RequestJournal { ServiceName: rj.serviceName, - // request observations: - // - parsed JSONRPC (if successful) - // - validation error (if invalid) - RequestObservation: rj.requestDetails.buildObservation(), } - // No endpoint queries were performed: skip adding endpoint observations. + // observation for parsed JSONRPC (if parsed) + if rj.jsonrpcRequest != nil { + observations.JsonRpcRequest = buildJSONRPCRequestObservation(rj.jsonrpcRequest) + } + + // observation for request error (if set) + if rj.requestErr != nil { + observations.RequestError = buildRequestErrorObservations(rj.requestErr) + } + + // No endpoint query results. // e.g. for invalid requests. - if len(rj.endpointQueries) == 0 { + // Skip adding endpoint observations. + if len(rj.endpointQueryResults) == 0 { return observations } - // Add one endpoint observation entry per processed enpoint query stored in the journal. - endpointObservations := make([]*qosobservations.EndpointObservation, len(rj.processedEndpointQueries)) - for index, endpointQuery := range rj.processedEndpointQueries { - endpointObservations[index] = endpointQuery.buildObservation() + endpointObservations := make([]*qosobservations.EndpointQueryResultObservation, len(rj.endpointQueryResults)) + for index, endpointQueryResult := range rj.endpointQueryResults { + endpointObservation[index] = endpointQueryResult.buildObservations() } - observations.EndpointObservations = endpointObservations + observations.EndpointQueryResultObservations = endpointObservations return observations } -// TODO_IN_THIS_PR: check the observations have the correct service name. -func (rj *requestJournal) extractEndpointQueriesFromObservations(observations *observations.Observations) []*endpointQuery { - // fetch endpoint observations - endpointObservations := observations.GetEndpointObservations() - endpointQueries := make(*endpointQuery, len(endpointObservations)) - for index, endpointObservation := range endpointObservations { - endpointQueries[index] = extractEndpointQueryFromObservation(endpointObservation) +func buildRequestJournalFromObservations( + logger polylog.Logger, + observations *qosobservations.Observations, +) (*requestJournal, error) { + // hydrate the logger + logger := logger.With("method", "buildRequestJournalFromObservations") + + // sanity check the observations. + if observations == nil { + errMsg := "Received nil observation: skip the processing." + logger.Warn().Msg(errMsg) + return nil, errors.New(errMsg) + } + + reqObs := observations.GetRequestObservation() + // No request observation present: skip the processing. + if reqObs == nil { + errMsg := "Received nil request observation: skip the processing." + logger.Warn().Msg(errMsg) + return nil, errors.New(errMsg) + } + + // construct the request and any errors from the observations. + jsonrpcRequest := buildJSONRPCRequestFromObservation(reqObs) + requestErr := buildRequestErrorFromObservations(reqObs) + + // Instantiate the request journal. + requestJournal := &requestJournal{ + logger: logger, + jsonrpcRequest: jsonrpcRequest, + requestErr: requestErr, + } + + // request had an error: internal, parsing, validation, etc. + // no further processing required. + if requestErr != nil { + logger.With("num_endpoint_observations", len(observations.GetEndpointQueryResultObservatios()). + Info().Msg("Request had an error: no endpoint observations expected.") + + return requestJournal + } + + // reconstruct endpoint query results. + endpointsObs := observations.GetEndpointQueyResultObservations() + // No endpoint observation present: skip the processing. + if endpointsObs == nil || len(endpointsObs) == 0 { + logger.Warn().Msg("Received nil/empty endpoint observation: skip the processing.") + return nil + } + + // Initialize the endpoint query results of the request journal. + requestJournal.endpointQueryResults = make([]*EndpointQueryResult, len(endpointsObs)) + + // add one endpoint query result per endpoint observation. + for index, endpointObs := range endpointsObs { + // Construct the query result from the endpoint observation. + endpointQueryResult := extractEndpointQueryResultsFromObservations(endpointObs) + + // add a reference to the request journal: e.g. for retrieving the JSONRPC request method. + endpointQueryResult.requestJournal = requestJournal + + // add the endpoint query result to the request journal. + requestJournal.endpointQueryResults[index] = endpointQueryResult } - return endpointQueries + return requestJournal, nil } diff --git a/qos/framework/observations_endpoint.go b/qos/framework/observations_endpoint.go index 4c5a2d7b2..36940d4ee 100644 --- a/qos/framework/observations_endpoint.go +++ b/qos/framework/observations_endpoint.go @@ -5,7 +5,7 @@ import ( ) func extractEndpointQueryFromObservation(observation *qosobservations.Observations) *endpointQuery { - return &endpointQuery { + return &endpointQuery{ // Extract the JSONRPC request corresponding to the observation. request: extractJSONRPCRequestFromObservation(observation.GetRequestObservation()), } diff --git a/qos/framework/observations_endpoint_error.go b/qos/framework/observations_endpoint_error.go index 9a5c06634..64805e3d6 100644 --- a/qos/framework/observations_endpoint_error.go +++ b/qos/framework/observations_endpoint_error.go @@ -2,9 +2,9 @@ package framework import ( "time" - + "google.golang.org/protobuf/types/known/timestamppb" - + observations "github.com/buildwithgrove/path/observation/qos/framework" "github.com/buildwithgrove/path/qos/jsonrpc" ) @@ -14,26 +14,26 @@ func (ee *EndpointError) buildObservation() *observations.EndpointError { if ee == nil { return nil } - + observationError := &observations.EndpointError{ Description: ee.Description, - ErrorKind: translateToObservationErrorKind(ee.ErrorKind), + ErrorKind: translateToObservationErrorKind(ee.ErrorKind), } - + // Include sanction information if available if ee.RecommendedSanction != nil { observationError.Sanction = &observations.Sanction{ Reason: ee.Description, - Type: observations.SanctionType_SANCTION_TYPE_TEMPORARY, + Type: observations.SanctionType_SANCTION_TYPE_TEMPORARY, } - + // Convert expiry timestamp if available if !ee.RecommendedSanction.Duration.IsZero() { // Convert Go time.Duration to proto timestamp observationError.Sanction.ExpiryTimestamp = timestampProto(time.Now().Add(ee.RecommendedSanction.Duration)) } } - + return observationError } @@ -42,23 +42,23 @@ func extractEndpointErrorFromObservation(obsError *observations.EndpointError) * if obsError == nil { return nil } - + err := &EndpointError{ Description: obsError.Description, - ErrorKind: translateFromObservationErrorKind(obsError.ErrorKind), + ErrorKind: translateFromObservationErrorKind(obsError.ErrorKind), } - + // Include sanction information if available if obsError.Sanction != nil { err.RecommendedSanction = &Sanction{} - + // Convert sanction expiry timestamp to Duration if obsError.Sanction.ExpiryTimestamp != nil { sanctionExpiry := timeFromProto(obsError.Sanction.ExpiryTimestamp) err.RecommendedSanction.Duration = sanctionExpiry.Sub(time.Now()) } } - + return err } diff --git a/qos/framework/observations_endpoint_result.go b/qos/framework/observations_endpoint_result.go index 19ceffb81..bff0d4b6f 100644 --- a/qos/framework/observations_endpoint_result.go +++ b/qos/framework/observations_endpoint_result.go @@ -2,8 +2,8 @@ package framework import ( observations "github.com/buildwithgrove/path/observation/qos/framework" - "github.com/buildwithgrove/path/qos/jsonrpc" "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" ) // buildObservation converts an EndpointQueryResult to observations.EndpointQueryResult @@ -48,39 +48,34 @@ func (eqr *EndpointQueryResult) buildObservation() *observations.EndpointQueryRe } // extractEndpointQueryResultFromObservation extracts a single EndpointQueryResult from an observation's EndpointQueryResult -// Ignores the HTTP stauts code: it is only required when responding to the client. -func extractEndpointQueryResultFromObservation( - endpointQuery *endpointQuery, - obsResult *observations.EndpointQueryResult, -) *EndpointQueryResult { - if obsResult == nil { - return nil - } - +func extractEndpointQueryResultsFromObservations( + logger polylog.Logger, + observation *observations.EndpointQueryResult, +) []*EndpointQueryResult { + // hydrate the logger + logger := logger.With("method", "extractEndpointQueryResultFromObservation") + // Create a new result and populate it from the observation result := &EndpointQueryResult{ - // Set the endpointQuery underlying the observations. - endpointQuery: endpointQuery, - // Set the result values to be copied from the observations. StringValues: make(map[string]string), IntValues: make(map[string]int), - ExpiryTime: timeFromProto(obsResult.ExpiryTime), + ExpiryTime: timeFromProto(observation.ExpiryTime), } // Copy string values - for key, value := range obsResult.StringValues { + for key, value := range observation.StringValues { result.StringValues[key] = value } // Copy int values - for key, value := range obsResult.IntValues { + for key, value := range observation.IntValues { result.IntValues[key] = int(value) } // Convert error information - if obsResult.Error != nil { - result.Error = extractEndpointErrorFromObservation(obsResult.Error) + if endpointErr := observation.GetEndpointError(); endpointError != nil { + result.EndpointError = extractEndpointErrorFromObservation(endpointError) } return result diff --git a/qos/framework/observations_interpreter.go b/qos/framework/observations_interpreter.go index 7114cb1b7..76924a7f9 100644 --- a/qos/framework/observations_interpreter.go +++ b/qos/framework/observations_interpreter.go @@ -3,6 +3,4 @@ package framework // ObservationsInterpreter is the reference implementation for interpreting observations generated by the framework. // e.g. IsServiceRequestSuccessful() returns true if the service request succeeded. type ObservationsInterpreter struct { - } - diff --git a/qos/framework/observations_jsonrpc.go b/qos/framework/observations_jsonrpc.go new file mode 100644 index 000000000..1b42d5d69 --- /dev/null +++ b/qos/framework/observations_jsonrpc.go @@ -0,0 +1,38 @@ +package framework + +func buildJSONRPCRequestObservation(jsonrpcReq jsonrpc.Request) *qosobservations.JsonRpcRequest { + return &qosobservations.JsonRpcRequest { + Id: jsonrpcReq.ID.String(), + Method: jsonrpcReq.Method, + } +} + +// TODO_IN_THIS_PR: implement. +func buildJSONRPCResponseObservation(jsonrpcResp jsonrpc.Response) *qosobservations.JsonRpcResponse { + return nil +} + +func extractJSONRPCRequestFromObservation( + observation *qosobservations.RequestObservation, +) *jsonrpc.Request { + // Nil + if observation == nil { + return nil + } + + jsonrpcRequestObs := observation.GetJsonRpcRequest() + if jsonrpcRequestObs == nil { + return nil + } + + // The only field required in applying the observations is the request's method. + return &jsonrpc.Request{ + Method: jsonrpcRequestObs.GetMethod(), + } +} + +func extractJSONRPCResponseFRomObservation( + observation *qosobservations. +) *jsonrpc.Response { + +} diff --git a/qos/framework/observations_request.go b/qos/framework/observations_request.go index f1976dc87..83dde82a2 100644 --- a/qos/framework/observations_request.go +++ b/qos/framework/observations_request.go @@ -24,7 +24,7 @@ func (rd *requestDetails) buildObservations() *qosobservations.RequestObservatio errorObs = rd.requestError.buildObservations() } - return &qosobservations.RequestObservation { + return &qosobservations.RequestObservation{ // Only set if validation was successful JsonrpcRequest: jsonrpcRequestObs, // Only set if the request failed for any reason. @@ -34,22 +34,3 @@ func (rd *requestDetails) buildObservations() *qosobservations.RequestObservatio RequestError: errorObs, } } - -func extractJSONRPCRequestFromObservation( - observation *qosobservations.RequestObservation, -) *jsonrpc.Request { - // Nil - if observation == nil { - return nil - } - - jsonrpcRequestObs := observation.GetJsonRpcRequest() - if jsonrpcRequestObs == nil { - return nil - } - - // The only field required in applying the observations is the request's method. - return &jsonrpc.Request{ - Method: jsonrpcRequestObs.GetMethod(), - } -} diff --git a/qos/framework/observations_request_error.go b/qos/framework/observations_request_error.go index 01e1c17ae..1b7c57d8e 100644 --- a/qos/framework/observations_request_error.go +++ b/qos/framework/observations_request_error.go @@ -1,13 +1,14 @@ package framework -func (re *requestError) buildObservation() *qosobservations.ValidationError { - return &qosobservations.ValidationError { - ErrorType: translateToRequestValidationError(re.errorKind), - ValidationErrorDetails: re.errorDetails, - // HTTP status code returned to the client. - HttpStatusCode: re.jsonrpcErrorResponse.GetRecommendedHTTPStatusCode(), +func (re *requestError) buildObservation() *qosobservations.RequestError { + return &qosobservations.RequestError{ + ErrorKind: translateToRequestError(re.errorKind), + ErrorDetails: re.errorDetails, + // The JSONRPC response returned to the client. + JsonRpcResponse: buildJSONRPCResponseObservation(re.jsonrpcResponse), } } + // DEV_NOTE: you MUST update this function when changing the set of request errors. func translateToRequestError(errKind requestErrorKind) qosobservations.RequestErrorKind { switch errKind { @@ -17,10 +18,10 @@ func translateToRequestError(errKind requestErrorKind) qosobservations.RequestEr return RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR case requestErrKindJSONRPCParsingErr: return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE - requestErrKindJSONRPCInvalidVersion + requestErrKindJSONRPCInvalidVersion return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION case requestErrKindJSONRPCMissingMethod: - return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD + return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD default: return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNSPECIFIED } diff --git a/qos/framework/qos.go b/qos/framework/qos.go index ebf452118..f1947bdbd 100644 --- a/qos/framework/qos.go +++ b/qos/framework/qos.go @@ -33,19 +33,13 @@ func (s *QoS) ParseHTTPRequest( _ context.Context, httpReq *http.Request, ) (*requestContext, bool) { - requestDetails := buildRequestDetailsFromHTTP(s.logger, httpReq) - - // initialize a context for processing the HTTP request. + // Context for processing the HTTP request. requestCtx := &requestContext{ - logger: logger, - // initialize the request journal to track all data on the request. - journal: &requestJournal{ - requestDetails: requestDetails, - }, + logger: s.logger, } - - // check if the request processing flow should continue. - shouldContinue := requestDetails.getRequestErrorJSONRPCResponse() != nil + + // Initialize the request context from the HTTP request. + shouldContinue := requestCtx.initFromHTTP(httpReq) return requestCtx, shouldContinue } @@ -61,14 +55,15 @@ func (q *QoS) ApplyObservations(observations *qosobservations.Observations) erro return fmt.Errorf("Reported observations mismatch: service name %q, expected %q", serviceRequestObservations.ServiceName, q.qosDefinitions.ServiceName) } - // Construct the endpoint query underlying the observations - endpointQuery := extractEndpointQueryFromObservations(serviceRequestObservations) - - // Construct the query results using the observations. - endpointQueryResults := extractEndpointQueryResultsFromObservations(endpointQuery, serviceRequestObservations.GetEndpointQueryResultObservations()) + // reconstruct the request journal matching the observations. + requestJournal, err := buildRequestJournalFromObservations(q.logger, serviceRequestObservations) + if err != nil { + q.logger.Error().Err(err).Msg("Error building the request journal from observations: skipping the application of observations.")a + return err + } // update the stored endpoints - updatedEndpoints := s.serviceState.updateStoredEndpoints(endpointQueryResults) + updatedEndpoints := s.serviceState.updateStoredEndpoints(requestJournal.endpointQueryResults) // instantiate a state update context. stateUpdateCtx := s.buildServiceStateUpdateContext() @@ -87,13 +82,15 @@ func (q *QoS) GetRequiredQualityChecks(endpointAddr protocol.EndpointAddr) []Req // The context provides: // - Read-only access to current service state // - Mapping of JSONRPC methods to their corresponding result builders. -func (q *QoS) buildEndpointQueryResultContext() *EndpointQueryResultContext { +func (q *QoS) buildEndpointQueryResultContext(endpointQueryResult *EndpointQueryResult) *EndpointQueryResultContext { // instantiate a result context to process an endpointQuery. return &EndpointQueryResultContext{ // Service State (read-only) // Allows the custom QoS service to base the query results on current state if needed. - ReadonlyServiceState: q.serviceState, + ServiceState: q.serviceState, + // Tracks the result of the endpoint query. + EndpointQueryResult: endpointQueryResult, // Map of JSONRPC request method to the corresponding query result builders. jsonrpcMethodResultBuilders: q.qosDefinition.ResultBuilders, } diff --git a/qos/framework/request.go b/qos/framework/request.go index 3fddecf7a..7632de04a 100644 --- a/qos/framework/request.go +++ b/qos/framework/request.go @@ -11,122 +11,5 @@ import ( "github.com/buildwithgrove/path/qos/jsonrpc" ) -// TODO_MVP(@adshmh): Allow custom QoS services to supply custom request validation logic. -// Example use case: specifying a list of allowed JSONRPC request methods. -// This would require: -// 1. Declaring a public RequestValidator interface. -// 2. Helper functions, e.g. BuildRequestValidatorForAllowedMethods. -// -// maximum length of the error message stored in request validation failure observations and logs. -// This is used to prevent overly verbose error messages from being stored in logs and metrics leading to excessive memory usage and cost. -const maxErrMessageLen = 1000 - -type requestDetails struct { - // The client's JSONRPC request - // Only set if the request was successfully parsed. - request *jsonrpc.Request - - // Request error, if any. - requestError *requestError -} - -func (rd *requestDetails) getRequestErrorJSONRPCResponse() *jsonrpc.Response { - if rd.requestError == nil { - return nil - } - - return rd.requestError.getJSONRPCResponse() -} - -func (rd *requestDetails) setProtocolLevelError() { - // request already marked as failed. - // skip setting an error. - if rd.requestError != nil { - return - } - - // set the request as failed with protocol-level error. - rd.requestError = buildRequestErrorForInternalErrProtocolErr(rd.request.ID) -} - -// buildRequestContextFromHTTPRequest builds and returns a context for processing the HTTP request: -// - Reads and processes the HTTP request -// - Parses a JSONRPC request from the HTTP request's payload. -// - Validates the resulting JSONRPC request. -// - Initializes the context for processing the request in the following scenarios: -// - Internal errors: e.g. reading the HTTP request. -// - Invalid request: e.g. malformed payload. -// - Valid request: proper JSONRPC request. -func buildRequestDetailsFromHTTP( - logger polylog.Logger, - httpReq *http.Request, -) *requestDetails { - // Read the HTTP request body - body, err := io.ReadAll(httpReq.Body) - defer httpReq.Body.Close() - - // TODO_IMPROVE(@adshmh): Propagate a request ID parameter on internal errors that occur after successful request parsing. - // There are no such cases as of PR #186. - if err != nil { - // Handle read error (internal server error) - logger.Error().Err(err).Msg("Failed to read request body") - - // return the error details to be stored in the request journal. - return buildRequestDetailsForInternalErrHTTPRead(err) - } - - // Parse the JSON-RPC request - var jsonrpcReq jsonrpc.JsonRpcRequest - if err := json.Unmarshal(body, &jsonrpcReq); err != nil { - // TODO_IN_THIS_PR: log the first 1K bytes of the body. - // Handle parse error (client error) - logger.Error().Err(err).Msg("Failed to parse JSON-RPC request") - - return buildRequestDetailsForParseError(err) - } - - // Validate the request - requestErr := validateRequest(jsonrpcReq) - if requestErr != nil { - // Request is invalid according to the validator - logger.Info(). - Str("method", jsonrpcReq.Method). - Msg("Request validation failed") - - return requestDetails{ - request: jsonrpcReq, - requestError: requestErr, - } - } - - // Request is valid - logger.Debug(). - Str("method", jsonrpcReq.Method). - Msg("Request validation successful") - - return requestDetails { - request: &jsonrpcReq, - } - -} - -// validateRequest provides a basic validation of JSONRPC requests. -// It checks: -// - JSONRPC version (must be "2.0") -// - Method presence -// -// Returns a non-nil requestError if validation fails. -func validateRequest(request *jsonrpc.Request) *requestError { - // Check JSONRPC version - if request.Jsonrpc != jsonrpc.Version2 { - return buildRequestErrorJSONRPCErrInvalidVersion(request.ID) - } - - // Check method presence - if request.Method == "" { - return buildRequestErrorJSONRPCErrMissingMethod(request.ID) - } - - // Request is valid - return nil +type requestDetails struct { } diff --git a/qos/framework/request_errors.go b/qos/framework/request_errors.go index 973eb8a0f..b1657a92f 100644 --- a/qos/framework/request_errors.go +++ b/qos/framework/request_errors.go @@ -5,6 +5,7 @@ import ( ) type requestErrorKind int + const ( _ requestErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. requestErrKindInternalErrReadyHTTPBody @@ -27,13 +28,9 @@ type requestError struct { jsonrpcErrorResponse jsonrpc.Response } -func (re *requestError) getJSONRPCResponse() *jsonrpc.Response { - return &re.jsonrpcErrorResponse -} - func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { - return &requestError { - errorKind: requestErrKindInternalErrReadyHTTPBody, + return &requestError{ + errorKind: requestErrKindInternalErrReadyHTTPBody, errorDetails: fmt.Sprintf("error reading HTTP request body: %v", err), // Create JSONRPC error response for read failure jsonrpcErrorResponse: newJSONRPCErrResponseInternalReadError(err), @@ -49,8 +46,8 @@ func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { // This is an internal error, causing a valid request to fail. // The exact error is not known here: see the TODO_TECHDEBT above. func buildRequestErrorForInternalErrProtocolErr(requestID jsonrpc.ID) *requestError { - return &requestError { - errorKind: requestErrKindInternalErrProtocolError, + return &requestError{ + errorKind: requestErrKindInternalErrProtocolError, errorDetails: "error handling the request due to protocol-level error.", // Create JSONRPC error response for protocol error. jsonrpcErrorResponse: newJSONRPCErrResponseInternalProtocolError(requestID), @@ -58,8 +55,8 @@ func buildRequestErrorForInternalErrProtocolErr(requestID jsonrpc.ID) *requestEr } func buildRequestErrorForParseError(err error) *requestError { - return &requestError { - errorKind: requestErrKindJSONRPCParsingErr, + return &requestError{ + errorKind: requestErrKindJSONRPCParsingErr, errorDetails: fmt.Sprintf("error parsing HTTP request into JSONRPC: %v", err), // Create JSONRPC error response for parse failure jsonrpcErrorResponse: newJSONRPCErrResponseParseError(err), @@ -69,8 +66,8 @@ func buildRequestErrorForParseError(err error) *requestError { func buildRequestErrorJSONRPCErrInvalidVersion(requestID jsonrpc.ID, version jsonrpc.Version) *requestError { err := fmt.Errorf("invalid version in JSONRPC request: %s", version) - return &requestError { - errorKind: requestErrKindJSONRPCInvalidVersion, + return &requestError{ + errorKind: requestErrKindJSONRPCInvalidVersion, errorDetails: err.Error(), // Create JSONRPC error response for parse failure jsonrpcErrorResponse: newJSONRPCErrResponseInvalidVersion(err, requestID), @@ -78,8 +75,8 @@ func buildRequestErrorJSONRPCErrInvalidVersion(requestID jsonrpc.ID, version jso } func buildRequestErrorJSONRPCErrMissingMethod(requestID jsonrpc.Request) *requestError { - return &requestError { - errorKind: requestErrKindJSONRPCMissingMethod, + return &requestError{ + errorKind: requestErrKindJSONRPCMissingMethod, errorDetails: "No method specified by the JSONRPC request", // Create JSONRPC error response for parse failure jsonrpcErrorResponse: newJSONRPCErrResponseMissingMethod(requestID), diff --git a/qos/framework/request_journal.go b/qos/framework/request_journal.go index 6e14174c6..74f007af5 100644 --- a/qos/framework/request_journal.go +++ b/qos/framework/request_journal.go @@ -15,6 +15,10 @@ const ( // TODO_MVP(@adshmh): Support individual configuration of timeout for every service that uses EVM QoS. // The default timeout when sending a request to an EVM blockchain endpoint. defaultServiceRequestTimeoutMillisec = 10000 + + // maximum length of the error message stored in request validation failure observations and logs. + // This is used to prevent overly verbose error messages from being stored in logs and metrics leading to excessive memory usage and cost. + maxErrMessageLen = 1000 ) // requestJournal holds the data for a complete JSONRPC request lifecycle. @@ -26,7 +30,7 @@ type requestJournal struct { // The client's JSONRPC request // Only set if the request was successfully parsed. - request *jsonrpc.Request + jsonrpcRequest *jsonrpc.Request // Request error, if any. requestError *requestError @@ -35,14 +39,30 @@ type requestJournal struct { endpointQueryResults []*EndpointQueryResult } -func (rj *requestJournal) buildEndpointQuery(endpointAddr protocol.EndpointAddr, receivedData []byte) *endpointQuery { - return &endpointQuery{ +func (rj *requestJournal) setProtocolLevelError() { + // request already marked as failed. + // skip setting an error. + if rj.requestError != nil { + return + } + + // set the request as failed with protocol-level error. + rj.requestError = buildRequestErrorForInternalErrProtocolErr(rj.jsonrpcRequest.ID) +} + +func (rj *requestJournal) buildEndpointQueryResult(endpointAddr protocol.EndpointAddr, receivedData []byte) *EndpointQueryResult { + return &EndpointQueryResult{ + requestJournal: rj, // JSONRPC request underlying the endpoint query. - request: rj.requestDetails.request, + request: rj.jsonrpcRequest, // Address of the queried endpoint. endpointAddr: endpointAddr, // Data received from the endpoint. - receivedData: receivedData, + endpointPayload: receivedData, + + // Initialize attribute maps + IntValues: make(map[string]int), + StrValues: make(map[string]string), } } @@ -51,6 +71,13 @@ func (rj *requestJournal) reportEndpointQueryResult(endpointQueryResult *Endpoin } func (rj *requestJournal) getServicePayload() protocol.Payload { + // This should never happen. + // A non-nil requestErr indicates the request failed to parse/validate. + if rj.requestErr != nil { + rj.logger.With("request_error", js.requestErr).Error().Msg("Error: getServicePayload() called for invalid/failed request. This is a bug.") + return protocol.Payload{} + } + // TODO_IN_THIS_PR: update this code reqBz, err := json.Marshal(*rc.Request) if err != nil { @@ -84,36 +111,29 @@ func (rj *requestJournal) getHTTPResponse() gateway.HTTPResponse { // - Invalid request: e.g. malformed payload from client. // - Internal error: error reading HTTP request's body // - Internal error: Protocol-level error, e.g. selected endpoint timed out. - if requestErrorJSONRPCResponse := rj.requestDetails.getRequestErrorJSONRPCResponse(); requestErrorJSONRPCResponse != nil { - return buildHTTPResponse(rj.Logger, requestErrorJSONRPCResponse) + if requestErr := rj.requestErr; requestErr != nil { + return buildHTTPResponse(rj.logger, requestErr.jsonrpcErrorResponse) } + // TODO_IN_THIS_PR: verify the implementation here. + // + // // TODO_IMPROVE(@adshmh): find a refactor: // Goal: guarantee that valid request -> at least 1 endpoint query. // Constraint: Such a refactor should keep the requestJournal as a data container. // // Use the most recently reported endpoint query. // There MUST be an entry if the request has no error set. - selectedQuery := rj.processedEndpointQueries[len(rj.processedEndpointQueries)-1] + selectedEndpointQueryResult := rj.endpointQueryResults[len(rj.endpointQueryResults)-1] jsonrpcResponse := selectedQuery.result.clientJSONRPCResponse return buildHTTPResponse(rj.Logger, jsonrpcResponse) } -func (rj *requestJournal) buildObservations() qosobservations.Observations { - observations := qosobservations.Observations { - ServiceName: rj.serviceName, - RequestObservation: rj.requestDetails.buildObservations(), - } - - if len(rj.endpointQueryResults) == 0 { - return observations - } - - endpointObservations := make([]*qosobservations.EndpointObservation, len(rj.endpointQueryResults)) - for index, endpointQueryResult := range rj.endpointQueryResults { - endpointObservation[index] = endpointQueryResult.buildObservations() +func (rj *requestJournal) getJSONRPCRequestMethod() jsonrpc.Method { + request := rj.jsonrpcRequest + if request == nil { + return jsonrpc.Method("") } - observations.EndpointQueryResultObservations = endpointObservations - return observations + return request.Method } diff --git a/qos/framework/state.go b/qos/framework/state.go index 309286332..a93890929 100644 --- a/qos/framework/state.go +++ b/qos/framework/state.go @@ -15,7 +15,6 @@ import ( // - Reading state parameters and endpoints (Read Only) for: // - building endpoint results // - endpoint selection -// type ServiceState struct { // logger for diagnostics logger polylog.Logger diff --git a/qos/jsonrpc/request.go b/qos/jsonrpc/request.go index b26626f2c..7f6c612ee 100644 --- a/qos/jsonrpc/request.go +++ b/qos/jsonrpc/request.go @@ -12,6 +12,10 @@ type Version string const Version2 = Version("2.0") +var ( + ErrInvalidRequestInvalidVersion = errors.New("Invalid JSONRPC Request: invalid version.") + ErrInvalidRequestMissingMethod = errors.New("Invalid JSONRPC Request: missing method.") +) // Request represents a request as specificed // by the JSONRPC spec. // See the following link for more details: @@ -51,3 +55,24 @@ func (r Request) MarshalJSON() ([]byte, error) { // Marshal and return the serializable version of the request return json.Marshal(out) } + +// Validate provides a basic validation of JSONRPC requests. +// It checks: +// - JSONRPC version (must be "2.0") +// - Method presence +// +// Returns a non-nil requestError if validation fails. +func (r Request) Validate() error { + // Check JSONRPC version + if request.Jsonrpc != jsonrpc.Version2 { + return fmt.Errorf("%w: invalid version: %s", ErrInvalidRequestInvalidVersion, r.Jsonrpc) + } + + // Check method presence + if request.Method == "" { + return ErrInvalidRequestMissingMethod + } + + // Request is valid + return nil +} From 4b2d70d011cb35f5cc0ee308abd53382cbeaafa8 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 29 Apr 2025 19:44:34 -0400 Subject: [PATCH 21/26] Fix syntax errors --- observation/qos/framework/request.pb.go | 103 ++++++++++------------- qos/framework/context_endpoint_result.go | 4 +- qos/framework/context_request.go | 6 +- qos/jsonrpc/request.go | 8 +- qos/jsonrpc/response.go | 4 +- 5 files changed, 57 insertions(+), 68 deletions(-) diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go index 72e1ac71e..99ff7e4b0 100644 --- a/observation/qos/framework/request.pb.go +++ b/observation/qos/framework/request.pb.go @@ -88,13 +88,12 @@ type RequestError struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // enumeration of the kind of error the request encountered. ErrorKind RequestErrorKind `protobuf:"varint,1,opt,name=error_kind,json=errorKind,proto3,enum=path.qos.framework.RequestErrorKind" json:"error_kind,omitempty"` - // HTTP status code returned to the client. - HttpStatusCode int32 `protobuf:"varint,2,opt,name=http_status_code,json=httpStatusCode,proto3" json:"http_status_code,omitempty"` - ErrorDetails string `protobuf:"bytes,3,opt,name=error_details,json=errorDetails,proto3" json:"error_details,omitempty"` - // Only set for validation failures on valid JSONRPC structures - // e.g. missing params field when required. - JsonrpcRequest *JsonRpcRequest `protobuf:"bytes,4,opt,name=jsonrpc_request,json=jsonrpcRequest,proto3,oneof" json:"jsonrpc_request,omitempty"` + // Details of the request error. + ErrorDetails string `protobuf:"bytes,2,opt,name=error_details,json=errorDetails,proto3" json:"error_details,omitempty"` + // The response returned to the client. + JsonRpcResponse *JsonRpcResponse `protobuf:"bytes,3,opt,name=json_rpc_response,json=jsonRpcResponse,proto3" json:"json_rpc_response,omitempty"` } func (x *RequestError) Reset() { @@ -134,13 +133,6 @@ func (x *RequestError) GetErrorKind() RequestErrorKind { return RequestErrorKind_REQUEST_ERROR_UNSPECIFIED } -func (x *RequestError) GetHttpStatusCode() int32 { - if x != nil { - return x.HttpStatusCode - } - return 0 -} - func (x *RequestError) GetErrorDetails() string { if x != nil { return x.ErrorDetails @@ -148,9 +140,9 @@ func (x *RequestError) GetErrorDetails() string { return "" } -func (x *RequestError) GetJsonrpcRequest() *JsonRpcRequest { +func (x *RequestError) GetJsonRpcResponse() *JsonRpcResponse { if x != nil { - return x.JsonrpcRequest + return x.JsonRpcResponse } return nil } @@ -163,48 +155,44 @@ var file_path_qos_framework_request_proto_rawDesc = []byte{ 0x74, 0x6f, 0x12, 0x12, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x88, 0x02, 0x0a, 0x0c, 0x52, 0x65, 0x71, + 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc9, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x43, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, - 0x69, 0x6e, 0x64, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x28, - 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x50, 0x0a, - 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, - 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, - 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, - 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x42, - 0x12, 0x0a, 0x10, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x2a, 0xc5, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, 0x55, - 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, - 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, - 0x4c, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, - 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x29, 0x0a, 0x25, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, - 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, - 0x12, 0x31, 0x0a, 0x2d, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4d, - 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, - 0x04, 0x12, 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, - 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, 0x05, 0x12, 0x2b, - 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, - 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x53, 0x10, 0x06, 0x42, 0x3a, 0x5a, 0x38, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, - 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, - 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6e, 0x64, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x23, + 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x12, 0x4f, 0x0a, 0x11, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x72, 0x70, 0x63, 0x5f, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, + 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xc5, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, + 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, + 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, + 0x41, 0x4c, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x46, 0x41, 0x49, + 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x29, 0x0a, 0x25, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, + 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, + 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x02, 0x12, 0x31, 0x0a, 0x2d, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, + 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, + 0x10, 0x04, 0x12, 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, + 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, 0x05, 0x12, + 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, + 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x53, 0x10, 0x06, 0x42, 0x3a, 0x5a, 0x38, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -222,13 +210,13 @@ func file_path_qos_framework_request_proto_rawDescGZIP() []byte { var file_path_qos_framework_request_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_path_qos_framework_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_path_qos_framework_request_proto_goTypes = []any{ - (RequestErrorKind)(0), // 0: path.qos.framework.RequestErrorKind - (*RequestError)(nil), // 1: path.qos.framework.RequestError - (*JsonRpcRequest)(nil), // 2: path.qos.framework.JsonRpcRequest + (RequestErrorKind)(0), // 0: path.qos.framework.RequestErrorKind + (*RequestError)(nil), // 1: path.qos.framework.RequestError + (*JsonRpcResponse)(nil), // 2: path.qos.framework.JsonRpcResponse } var file_path_qos_framework_request_proto_depIdxs = []int32{ 0, // 0: path.qos.framework.RequestError.error_kind:type_name -> path.qos.framework.RequestErrorKind - 2, // 1: path.qos.framework.RequestError.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest + 2, // 1: path.qos.framework.RequestError.json_rpc_response:type_name -> path.qos.framework.JsonRpcResponse 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name @@ -242,7 +230,6 @@ func file_path_qos_framework_request_proto_init() { return } file_path_qos_framework_jsonrpc_proto_init() - file_path_qos_framework_request_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/qos/framework/context_endpoint_result.go b/qos/framework/context_endpoint_result.go index 246353928..7be659cd0 100644 --- a/qos/framework/context_endpoint_result.go +++ b/qos/framework/context_endpoint_result.go @@ -92,7 +92,7 @@ func (ctx *EndpointQueryResultContext) updateEndpointQueryResultWithParsedRespon } // TODO_IN_THIS_PR: implement. -func (ctx *EndpointQueryResultContext) getHydratedLogger() polylog.Logger() { +func (ctx *EndpointQueryResultContext) getHydratedLogger() polylog.Logger { // hydrate the logger with endpointQuery fields. - + return ctx.logger } diff --git a/qos/framework/context_request.go b/qos/framework/context_request.go index 85790955e..894612076 100644 --- a/qos/framework/context_request.go +++ b/qos/framework/context_request.go @@ -130,13 +130,13 @@ func (ctx *requestContext) initFromHTTP(httpReq *http.Request) bool { jsonrpcReq, reqErr := parseHTTPRequest(ctx.logger, httpReq) // initialize the request journal to track all request data and events. - journal: &requestJournal{ + ctx.journal = &requestJournal{ jsonrpcRequest: jsonrpcReq, requestErr: reqErr, - }, + } // Only proceed with next steps if there were no errors parsing the HTTP request into a JSONRPC request. - return reqErr == nil + return (reqErr == nil) } // parseHTTPRequest builds and returns a context for processing the HTTP request: diff --git a/qos/jsonrpc/request.go b/qos/jsonrpc/request.go index 7f6c612ee..6d98ed5ac 100644 --- a/qos/jsonrpc/request.go +++ b/qos/jsonrpc/request.go @@ -2,6 +2,8 @@ package jsonrpc import ( "encoding/json" + "errors" + "fmt" ) // Method is the method specified by a JSONRPC request. @@ -64,12 +66,12 @@ func (r Request) MarshalJSON() ([]byte, error) { // Returns a non-nil requestError if validation fails. func (r Request) Validate() error { // Check JSONRPC version - if request.Jsonrpc != jsonrpc.Version2 { - return fmt.Errorf("%w: invalid version: %s", ErrInvalidRequestInvalidVersion, r.Jsonrpc) + if r.JSONRPC != Version2 { + return fmt.Errorf("%w: invalid version: %s", ErrInvalidRequestInvalidVersion, r.JSONRPC) } // Check method presence - if request.Method == "" { + if r.Method == "" { return ErrInvalidRequestMissingMethod } diff --git a/qos/jsonrpc/response.go b/qos/jsonrpc/response.go index fceb39368..981f092fc 100644 --- a/qos/jsonrpc/response.go +++ b/qos/jsonrpc/response.go @@ -46,13 +46,13 @@ func (r Response) GetResultAsBytes() ([]byte, error) { func (r Response) GetResultAsInt() (int, error) { var intValue int - err := json.Unmarshal(&intValue, r.Result) + err := json.Unmarshal(r.Result, &intValue) return intValue, err } func (r Response) GetResultAsStr() (string, error) { var strValue string - err := json.Unmarshal(&strValue, r.Result) + err := json.Unmarshal(r.Result, &strValue) return strValue, err } From c3375c74577eb179a1be6a0ae1f75080f7839b37 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 30 Apr 2025 14:09:45 -0400 Subject: [PATCH 22/26] Fix syntax errors --- .../qos/framework/endpoint_error.pb.go | 64 +++---- observation/qos/framework/request.pb.go | 35 ++-- proto/path/qos/framework/endpoint_error.proto | 9 +- proto/path/qos/framework/request.proto | 4 +- qos/example_evm/endpoint_selection.go | 70 +++----- qos/framework/client_http_response.go | 64 +------ qos/framework/context_endpoint_result.go | 25 ++- qos/framework/context_endpoint_selection.go | 163 +++++++++++------- qos/framework/context_request.go | 70 ++++---- qos/framework/context_state_update.go | 7 +- qos/framework/endpoint.go | 26 +-- qos/framework/endpoint_errors.go | 3 +- qos/framework/endpoint_query_result.go | 22 +-- .../endpoint_query_result_defaults.go | 124 +++++-------- qos/framework/endpoint_sanction.go | 2 +- qos/framework/endpoint_sanction_defaults.go | 53 +++--- qos/framework/endpoint_store.go | 8 +- qos/framework/jsonrpc_errors.go | 18 ++ qos/framework/observations.go | 21 ++- qos/framework/observations_endpoint.go | 12 -- qos/framework/observations_endpoint_error.go | 50 ++---- qos/framework/observations_endpoint_result.go | 2 + qos/framework/observations_jsonrpc.go | 39 +++-- qos/framework/observations_request.go | 36 ---- qos/framework/observations_request_error.go | 24 +-- qos/framework/qos.go | 9 +- qos/framework/request_errors.go | 28 ++- qos/framework/request_journal.go | 18 +- qos/framework/state.go | 13 +- qos/framework/state_update.go | 8 + qos/jsonrpc/response_error.go | 3 +- 31 files changed, 490 insertions(+), 540 deletions(-) delete mode 100644 qos/framework/observations_endpoint.go delete mode 100644 qos/framework/observations_request.go diff --git a/observation/qos/framework/endpoint_error.pb.go b/observation/qos/framework/endpoint_error.pb.go index da52ac21b..a430edefa 100644 --- a/observation/qos/framework/endpoint_error.pb.go +++ b/observation/qos/framework/endpoint_error.pb.go @@ -24,25 +24,28 @@ const ( type EndpointErrorKind int32 const ( - EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED EndpointErrorKind = 0 - EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD EndpointErrorKind = 2 // Empty payload from endpoint - EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_UNMARSHALING EndpointErrorKind = 3 // Could not parse endpoint payload - EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT EndpointErrorKind = 4 // Payload result doesn't match expected value + EndpointErrorKind_ENDPOINT_ERROR_KIND_UNSPECIFIED EndpointErrorKind = 0 + EndpointErrorKind_ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD EndpointErrorKind = 1 // Empty payload from endpoint. + EndpointErrorKind_ENDPOINT_ERROR_KIND_UNMARSHALING EndpointErrorKind = 2 // Could not parse endpoint payload. + EndpointErrorKind_ENDPOINT_ERROR_KIND_VALIDATION_ERR EndpointErrorKind = 3 // Parsed endpoint payload failed JSONRPC response validation. + EndpointErrorKind_ENDPOINT_ERROR_KIND_INVALID_RESULT EndpointErrorKind = 4 // Payload result doesn't match expected value: to be used by the Custom QoS implementation. ) // Enum value maps for EndpointErrorKind. var ( EndpointErrorKind_name = map[int32]string{ - 0: "ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED", - 2: "ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD", - 3: "ENDPOINT_DATA_ERROR_KIND_UNMARSHALING", - 4: "ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT", + 0: "ENDPOINT_ERROR_KIND_UNSPECIFIED", + 1: "ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD", + 2: "ENDPOINT_ERROR_KIND_UNMARSHALING", + 3: "ENDPOINT_ERROR_KIND_VALIDATION_ERR", + 4: "ENDPOINT_ERROR_KIND_INVALID_RESULT", } EndpointErrorKind_value = map[string]int32{ - "ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED": 0, - "ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD": 2, - "ENDPOINT_DATA_ERROR_KIND_UNMARSHALING": 3, - "ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT": 4, + "ENDPOINT_ERROR_KIND_UNSPECIFIED": 0, + "ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD": 1, + "ENDPOINT_ERROR_KIND_UNMARSHALING": 2, + "ENDPOINT_ERROR_KIND_VALIDATION_ERR": 3, + "ENDPOINT_ERROR_KIND_INVALID_RESULT": 4, } ) @@ -123,7 +126,7 @@ func (x *EndpointError) GetErrorKind() EndpointErrorKind { if x != nil { return x.ErrorKind } - return EndpointErrorKind_ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED + return EndpointErrorKind_ENDPOINT_ERROR_KIND_UNSPECIFIED } func (x *EndpointError) GetDescription() string { @@ -164,23 +167,24 @@ var file_path_qos_framework_endpoint_error_proto_rawDesc = []byte{ 0x00, 0x52, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x53, 0x61, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x6e, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x2a, 0xc1, 0x01, 0x0a, 0x11, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x28, 0x0a, 0x24, 0x45, 0x4e, 0x44, 0x50, - 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, - 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x2a, 0x0a, 0x26, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, - 0x41, 0x54, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x45, - 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x02, 0x12, 0x29, - 0x0a, 0x25, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x52, - 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x2b, 0x0a, 0x27, 0x45, 0x4e, 0x44, - 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, - 0x53, 0x55, 0x4c, 0x54, 0x10, 0x04, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, - 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, - 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x2a, 0xd5, 0x01, 0x0a, 0x11, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x23, 0x0a, 0x1f, 0x45, 0x4e, 0x44, 0x50, + 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x25, 0x0a, + 0x21, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, + 0x41, 0x44, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x4d, 0x41, + 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x45, 0x4e, + 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, + 0x44, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, + 0x10, 0x03, 0x12, 0x26, 0x0a, 0x22, 0x45, 0x4e, 0x44, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, + 0x44, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x10, 0x04, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, + 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go index 99ff7e4b0..20e4ebd01 100644 --- a/observation/qos/framework/request.pb.go +++ b/observation/qos/framework/request.pb.go @@ -28,9 +28,7 @@ const ( RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE RequestErrorKind = 1 // Error reading HTTP request body RequestErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR RequestErrorKind = 2 // Protocol error: e.g. endpoint timed out. RequestErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE RequestErrorKind = 3 // Error parsing JSON-RPC request - RequestErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION RequestErrorKind = 4 // JSONRPC request has invalid version. - RequestErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD RequestErrorKind = 5 // JSONRPC request does not specify method. - RequestErrorKind_REQUEST_ERROR_VALIDATION_INVALID_PARAMS RequestErrorKind = 6 // Parameters are incorrect + RequestErrorKind_REQUEST_ERROR_JSONRPC_VALIDATION_ERR RequestErrorKind = 4 // JSONRPC request has failed validation: e.g. missing `method` field. ) // Enum value maps for RequestErrorKind. @@ -40,18 +38,14 @@ var ( 1: "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE", 2: "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR", 3: "REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE", - 4: "REQUEST_ERROR_VALIDATION_INVALID_VERSION", - 5: "REQUEST_ERROR_VALIDATION_MISSING_METHOD", - 6: "REQUEST_ERROR_VALIDATION_INVALID_PARAMS", + 4: "REQUEST_ERROR_JSONRPC_VALIDATION_ERR", } RequestErrorKind_value = map[string]int32{ "REQUEST_ERROR_UNSPECIFIED": 0, "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE": 1, "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR": 2, "REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE": 3, - "REQUEST_ERROR_VALIDATION_INVALID_VERSION": 4, - "REQUEST_ERROR_VALIDATION_MISSING_METHOD": 5, - "REQUEST_ERROR_VALIDATION_INVALID_PARAMS": 6, + "REQUEST_ERROR_JSONRPC_VALIDATION_ERR": 4, } ) @@ -168,7 +162,7 @@ var file_path_qos_framework_request_proto_rawDesc = []byte{ 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xc5, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xe7, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, @@ -180,19 +174,14 @@ var file_path_qos_framework_request_proto_rawDesc = []byte{ 0x02, 0x12, 0x31, 0x0a, 0x2d, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, - 0x10, 0x04, 0x12, 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, - 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x10, 0x05, 0x12, - 0x2b, 0x0a, 0x27, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, - 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x53, 0x10, 0x06, 0x42, 0x3a, 0x5a, 0x38, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x45, 0x10, 0x03, 0x12, 0x28, 0x0a, 0x24, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x52, 0x50, 0x43, 0x5f, 0x56, 0x41, + 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x04, 0x42, 0x3a, + 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, + 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, + 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/proto/path/qos/framework/endpoint_error.proto b/proto/path/qos/framework/endpoint_error.proto index 0824f7da8..4b7f127d0 100644 --- a/proto/path/qos/framework/endpoint_error.proto +++ b/proto/path/qos/framework/endpoint_error.proto @@ -7,10 +7,11 @@ import "path/qos/framework/endpoint_sanction.proto"; // import Sanction definiti // EndpointErrorKind identifies different kinds of endpoint data errors. enum EndpointErrorKind { - ENDPOINT_DATA_ERROR_KIND_UNSPECIFIED = 0; - ENDPOINT_DATA_ERROR_KIND_EMPTY_PAYLOAD = 2; // Empty payload from endpoint - ENDPOINT_DATA_ERROR_KIND_UNMARSHALING = 3; // Could not parse endpoint payload - ENDPOINT_DATA_ERROR_KIND_INVALID_RESULT = 4; // Payload result doesn't match expected value + ENDPOINT_ERROR_KIND_UNSPECIFIED = 0; + ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD = 1; // Empty payload from endpoint. + ENDPOINT_ERROR_KIND_UNMARSHALING = 2; // Could not parse endpoint payload. + ENDPOINT_ERROR_KIND_VALIDATION_ERR = 3; // Parsed endpoint payload failed JSONRPC response validation. + ENDPOINT_ERROR_KIND_INVALID_RESULT = 4; // Payload result doesn't match expected value: to be used by the Custom QoS implementation. } // EndpointError contains error details for endpoint queries. diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto index 2d193400c..a58c2bf0a 100644 --- a/proto/path/qos/framework/request.proto +++ b/proto/path/qos/framework/request.proto @@ -11,9 +11,7 @@ enum RequestErrorKind { REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE = 1; // Error reading HTTP request body REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR = 2; // Protocol error: e.g. endpoint timed out. REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE = 3; // Error parsing JSON-RPC request - REQUEST_ERROR_VALIDATION_INVALID_VERSION = 4; // JSONRPC request has invalid version. - REQUEST_ERROR_VALIDATION_MISSING_METHOD = 5; // JSONRPC request does not specify method. - REQUEST_ERROR_VALIDATION_INVALID_PARAMS = 6; // Parameters are incorrect + REQUEST_ERROR_JSONRPC_VALIDATION_ERR = 4; // JSONRPC request has failed validation: e.g. missing `method` field. } // RequestError contains details about a request error diff --git a/qos/example_evm/endpoint_selection.go b/qos/example_evm/endpoint_selection.go index f344bc8e7..0e1a80214 100644 --- a/qos/example_evm/endpoint_selection.go +++ b/qos/example_evm/endpoint_selection.go @@ -18,50 +18,36 @@ var _ framework.EndpointSelector = evmEndpointSelector // evmEndpointSelector selects an endpoint from the set of available ones. // It uses the configuration and service state to filter out misconfigured/out-of-sync/invalid endpoints. -func evmEndpointSelector(ctx *EndpointSelectionContext) (protocol.EndpointAddr, error) { - // TODO_IN_THIS_PR: the framework should log an entry if the state attribute is not set. +func evmEndpointSelector( + ctx *EndpointSelectionContext, + config EVMConfig, +) (protocol.EndpointAddr, error) { // Fetch latest block number from the service state. - perceivedBlockNumber := ctx.GetState().GetIntAttribute(attrETHBlockNumber) + perceivedBlockNumber, _ := ctx.GetIntParam(methodETHBlockNumber) - // TODO_FUTURE(@adshmh): use service-specific metrics to add an endpoint ranking method. - // e.g. use latency to break the tie between valid endpoints. - for _, endpoint := range ctx.GetAvailableEndpoints() { - endpointChainID, err := endpoint.GetStringAttribute(attrETHChainID) - // ChainID attribute not set: Disqualify the endpoint. - if err != nil { - ctx.DisqualifyEndpoint(endpoint, err) - continue - } - - // TODO_MVP(@adshmh): pass the EVM config to endpoint selector. - // Invalid ChainID returned by the endpoint: Disqualify. - if endpointChainID != config.GetChainID() { - ctx.DisqualifyEndpoint(endpoint, fmt.Errorf("invalid chain ID %s, expected: %s", endpointChainID, config.GetChainID())) - continue - } - - endpointBlockNumber, err := endpoint.GetIntAttribute(attrETHBlockNumber) - // BlockNumber attribute not set: Disqualify the endpoint. - if err != nil { - ctx.DisqualifyEndpoint(endpoint, err) - continue - } - - // endpoint will only be disqualified if the State has reported a block number. - if perceivedBlockNumber <= 0 { - continue - } - - // endpoint is out-of-sync: Disqualify. - if endpointBlockNumber < perceivedBlockNumber { - ctx.DisqualifyEndpoint(endpoint, fmt.Errorf("endpoint out of sync got %d block number, expected: %d", endpointBlockNumber, perceivedBlockNumber)) - continue - } - - // TODO_IN_THIS_PR: validate archival state. + // The perceived block number not set yet: return a random endpoint + if perceivedBlockNumber <= 0 { + return ctx.SelectRandomQualifiedEndpoint() } - // All invalid endpoints have been marked as disqualified. - // Return a randomly selected Qualified endpoint. - return ctx.SelectRandomQualifiedEndpoint() + return ctx.SelectRandomQualifiedEndpoint( + func(endpoint *Endpoint) error { + endpointChainID, _ := endpoint.GetStrResult(methodETHChainID) + // Endpoint's chain ID does not match the expected value. + // Disqualify the endpoint. + if endpointChainID != config.GetChainID() { + return ctx.DisqualifyEndpoint(endpoint, fmt.Sprintf("invalid chain ID %s, expected: %s", endpointChainID, config.GetChainID())) + } + + endpointBlockNumber, _ := endpoint.GetIntResult(methodETHBlockNumber) + // TODO_IN_THIS_PR: add slack from the configuration. + // endpoint is out-of-sync: Disqualify. + if endpointBlockNumber < perceivedBlockNumber { + return ctx.DisqualifyEndpoint(endpoint, fmt.Errorf("out of sync: %d block number, perceived: %d", endpointBlockNumber, perceivedBlockNumber)) + } + + // TODO_IN_THIS_PR: validate archival state. + return nil + } + ) } diff --git a/qos/framework/client_http_response.go b/qos/framework/client_http_response.go index bfccc85f5..1b8483ca1 100644 --- a/qos/framework/client_http_response.go +++ b/qos/framework/client_http_response.go @@ -1,6 +1,8 @@ package framework import ( + "encoding/json" + "github.com/pokt-network/poktroll/pkg/polylog" "github.com/buildwithgrove/path/gateway" @@ -41,13 +43,16 @@ func newHTTPResponse(statusCode int, payload []byte) *ClientHTTPResponse { // buildHTTPResponse creates an HTTP response from a JSONRPC response. // It performs logging only if errors occur during the process. -func buildHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.Response) gateway.HTTPResponse { +func buildHTTPResponse( + logger polylog.Logger, + jsonrpcResp *jsonrpc.Response, +) gateway.HTTPResponse { if jsonrpcResp == nil { logger.Error().Msg("Received nil JSONRPC response") return buildErrorResponse(logger, jsonrpc.ID{}) } - payload, err := jsonrpcResp.MarshalJSON() + payload, err := json.Marshal(jsonrpcResp) if err != nil { logger.Error().Err(err).Msg("Failed to marshal JSONRPC response") return buildErrorResponse(logger, jsonrpcResp.ID) @@ -63,63 +68,10 @@ func buildHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.Response) gat // buildErrorResponse creates an internal error HTTP response with the given ID. func buildErrorResponse(logger polylog.Logger, id jsonrpc.ID) gateway.HTTPResponse { errResp := newErrResponseInternalError(id) - errPayload, _ := errResp.MarshalJSON() + errPayload, _ := json.Marshal(errResp) return &ClientHTTPResponse{ StatusCode: errResp.GetRecommendedHTTPStatusCode(), Headers: map[string]string{"Content-Type": "application/json"}, Payload: errPayload, } } - -// ====================> DROP/REFACTOR these methods - -// BuildInternalErrorResponse creates a generic internal error HTTP response. -func BuildInternalErrorResponse() gateway.HTTPResponse { - errResp := newErrResponseInternalError(jsonrpc.ID{}) - errPayload, _ := MarshalErrorResponse(nil, errResp) - return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) -} - -// BuildInternalErrorHTTPResponse creates an internal error HTTP response with logging. -func BuildInternalErrorHTTPResponse(logger polylog.Logger) gateway.HTTPResponse { - errResp := newErrResponseInternalError(jsonrpc.ID{}) - errPayload, err := MarshalErrorResponse(logger, errResp) - if err != nil { - logger.Error().Err(err).Msg("Failed to marshal internal error response") - } - return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) -} - -// BuildErrorHTTPResponse creates an HTTP response for an error response. -func BuildErrorHTTPResponse(logger polylog.Logger, errResp *jsonrpc.ResponseError) gateway.HTTPResponse { - payload, err := MarshalErrorResponse(logger, errResp) - if err != nil { - logger.Error().Err(err).Msg("Failed to marshal error response") - } - return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), payload) -} - -// BuildSuccessHTTPResponse creates an HTTP response for a successful JSONRPC response. -func BuildSuccessHTTPResponse(logger polylog.Logger, jsonrpcResp *jsonrpc.Response) gateway.HTTPResponse { - response := jsonrpc.Response{ - ID: jsonrpcResp.Id, - JSONRPC: jsonrpc.Version2, - Result: jsonrpcResp.Result, - } - payload, err := response.MarshalJSON() - if err != nil { - logger.Error().Err(err).Msg("Failed to marshal JSONRPC response") - errResp := newErrResponseMarshalError(response.ID, err) - errPayload, _ := marshalErrorResponse(logger, errResp) - return NewHTTPResponse(errResp.GetRecommendedHTTPStatusCode(), errPayload) - } - return NewHTTPResponse(response.GetRecommendedHTTPStatusCode(), payload) -} - -// NewHTTPResponse creates a new HTTP response with the given status code and payload. -func NewHTTPResponse(statusCode int, payload []byte) gateway.HTTPResponse { - return gateway.HTTPResponse{ - StatusCode: statusCode, - Body: payload, - } -} diff --git a/qos/framework/context_endpoint_result.go b/qos/framework/context_endpoint_result.go index 7be659cd0..cf433b7e5 100644 --- a/qos/framework/context_endpoint_result.go +++ b/qos/framework/context_endpoint_result.go @@ -1,6 +1,10 @@ package framework import ( + "encoding/json" + + "github.com/pokt-network/poktroll/pkg/polylog" + "github.com/buildwithgrove/path/qos/jsonrpc" ) @@ -39,7 +43,7 @@ func (ctx *EndpointQueryResultContext) buildEndpointQueryResult() *EndpointQuery } // Use the custom endpoint query result builder, if one is found matching the JSONRPC request's method. - builder, found := ctx.jsonrpcMethodResultBuilders[parsedEndpointQuery.request.Method] + builder, found := ctx.jsonrpcMethodResultBuilders[ctx.getJSONRPCRequestMethod()] if !found { // Use default processor for methods not specified by the custom QoS service definition. builder = defaultResultBuilder @@ -62,29 +66,32 @@ func (ctx *EndpointQueryResultContext) updateEndpointQueryResultWithParsedRespon // Check for empty response if len(ctx.EndpointQueryResult.endpointPayload) == 0 { - ctx.logger.Info().Msg("Received payload with 0 length from the endpoint. Service request will fail.") - + logger.Info().Msg("Received payload with 0 length from the endpoint. Service request will fail.") ctx.EndpointQueryResult = buildResultForEmptyResponse(ctx.EndpointQueryResult) return false } // Parse JSONRPC response - var jsonrpcResp jsonrpc.JsonRpcResponse + var jsonrpcResp jsonrpc.Response if err := json.Unmarshal(ctx.EndpointQueryResult.endpointPayload, &jsonrpcResp); err != nil { + logger.Info().Err(err).Msg("Endpoint payload failed to parse into a JSONRPC response.") // Error parsing the endpoint payload: return generic response to the client. ctx.EndpointQueryResult = buildResultForErrorUnmarshalingEndpointReturnedData(ctx.EndpointQueryResult, err) return false } // Validate the JSONRPC response - if err := jsonrpcResp.Validate(eq.request.ID); err != nil { - // TODO_IN_THIS_PR: define a separate method for JSONRPC response validation errors. - ctx.EndpointQueryResult = buildResultForErrorUnmarshalingEndpointPayload(ctx.EndpointQueryResult, err) - return endpointQuery, false + if err := jsonrpcResp.Validate(ctx.getJSONRPCRequestID()); err != nil { + logger.Info().Err(err).Msg("Parsed endpoint payload failed validation as JSONRPC response.") + // JSONRPC response failed validation: return generic response to the client. + ctx.EndpointQueryResult = buildResultForErrorValidatingEndpointResponse(ctx.EndpointQueryResult, err) + return false } + logger.Debug().Msg("Successfully validated endpoint payload as JSONRPC response.") + // Store the parsed result - ctx.EndpointQueryResult.ParsedJSONRPCResponse = jsonrpcResp + ctx.EndpointQueryResult.parsedJSONRPCResponse = &jsonrpcResp // Return true to signal that parsing was successful. // Processing will continue to the next step. diff --git a/qos/framework/context_endpoint_selection.go b/qos/framework/context_endpoint_selection.go index e90e19237..de4ef8506 100644 --- a/qos/framework/context_endpoint_selection.go +++ b/qos/framework/context_endpoint_selection.go @@ -1,17 +1,29 @@ package framework import ( + "errors" + "fmt" + "math/rand" + + "github.com/pokt-network/poktroll/pkg/polylog" + "github.com/buildwithgrove/path/protocol" "github.com/buildwithgrove/path/qos/jsonrpc" ) -// TODO_FUTURE: consider ranking filtered endpoints, e.g. based on latency, rather than randomization. +// TODO_FUTURE(@adshmh): Rank qualified endpoints, e.g. based on latency, for selection. // TODO_MVP(@adshmh): Remove expired Sanctions from endpoints' results. // // EndpointSelectionContext provides context for selecting endpoints. type EndpointSelectionContext struct { + logger polylog.Logger + + // request's journal. + // Provide read-only access to the request, e.g. the JSONRPC method. + *requestJournal + // Allows direct Get calls on the current service state. // It is read only: this is not the context for updating service state. *ServiceState @@ -21,99 +33,132 @@ type EndpointSelectionContext struct { // e.g. EVM QoS implements a selector that disqualifies endpoints that are out of sync. customSelector EndpointSelector - // Used to retrieve stored endpoints to examine their attributes during selection. - endpointStore *endpointStore - - // TODO_TECHDEBT(@adshmh): make this readonly and allow access through a getter method to prevent accidental modification by user. - // The JSONRPC request for which an endpoint is to be selected. - Request *jsonrpc.Request - // Endpoints disqualified from current selection context. disqualifiedEndpoints map[protocol.EndpointAddr]struct{} + + candidateEndpoints map[protocol.EndpointAddr]*Endpoint } -// Entry method into the endpoint selection context. -// Called by gateway.requestContext. -// Implements protocol.EndpointSelector interface. -func (ctx *EndpointSelectionContext) Select(availableEndpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { +func (ctx *EndpointSelectionContext) buildCandidateEndpointSet(availableEndpoints []protocol.Endpoint) { // Retrieve all the available endpoints from the endpoint store. - candidates := make(map[protocol.EndpointAddr]Endpoint) + candidates := make(map[protocol.EndpointAddr]*Endpoint) for _, availableEndpoint := range availableEndpoints { endpointAddr := availableEndpoint.Addr() // Use an empty endpoint struct for initialization if no entry was found in the store. - candidates[endpointAddr] = ctx.endpointStore.GetEndpoint(endpointAddr) + candidates[endpointAddr] = ctx.getEndpoint(endpointAddr) + } + + // Store the candidate endpoints. + ctx.candidateEndpoints = candidates +} + +// Entry method into the endpoint selection context. +// Called by gateway.requestContext. +// Implements protocol.EndpointSelector interface. +func (ctx *EndpointSelectionContext) Select(availableEndpoints []protocol.Endpoint) (protocol.EndpointAddr, error) { + // No endpoints available: error out. + if len(availableEndpoints) == 0 { + errMsg := "No endpoints available for selection. Service request will fail." + ctx.logger.Warn().Msg(errMsg) + return protocol.EndpointAddr(""), errors.New(errMsg) } + // Build a map of candidate endpoints for efficient lookup based on endpoint address. + ctx.buildCandidateEndpointSet(availableEndpoints) + // Drop endpoints with active sanctions from the list. - filteredEndpoints := ctx.filterSanctionedEndpoints(candidates) + ctx.disqualifySanctionedEndpoints() // If all endpoints were sanctioned, return early - if len(unsanctionedEndpoints) == 0 { + if len(ctx.candidateEndpoints) == len(ctx.disqualifiedEndpoints) { // TODO_IN_THIS_PR: should we error out here instead? - s.logger.Info().Msg("All endpoints are currently sanctioned: returning a random endpoint.") - return ctx.selectRandomEndpoint(availableEndpoints) + ctx.logger.Info().Msg("All endpoints are currently sanctioned: returning a random endpoint.") + return ctx.selectRandomEndpoint() } - // TODO_IN_THIS_PR: implement this in the EndpointSelectionContext struct. - // If no custom selector is provided, use a random selector. - if s.customSelector == nil { - return ctx.SelectRandom() + if ctx.customSelector == nil { + return protocol.EndpointAddr(""), fmt.Errorf("Endpoint selection failed: custom QoS endpoint selection must be provided.") } - // Call the custom selector with the context - return s.customSelector(ctx) + // Call the custom selector. + // Pass the context to provide helper methods. + return ctx.customSelector(ctx) } -// Select marks an endpoint as selected. -func (ctx *EndpointSelectionContext) SelectEndpoint(endpoint Endpoint) *EndpointSelectionContext { - ctx.selected = append(ctx.selected, endpoint) - return ctx -} +type EndpointFilter func(*Endpoint) error -// SelectAll marks all available endpoints as selected. -func (ctx *EndpointSelectionContext) SelectAll() *EndpointSelectionContext { - ctx.selected = append(ctx.selected[:0], ctx.Endpoints...) - return ctx -} +// Returns a random endpoint from the context. +// Only considers endpoints not dropped from the context. +func (ctx *EndpointSelectionContext) SelectRandomQualifiedEndpoint(endpointFilters ...EndpointFilter) (protocol.EndpointAddr, error) { + for endpointAddr, endpoint := range ctx.candidateEndpoints { + if _, isDisqualified := ctx.disqualifiedEndpoints[endpointAddr]; isDisqualified { + // endpoint already disqualified. Skip further processing. + continue + } -// SelectIf selects endpoints that match a predicate function. -func (ctx *EndpointSelectionContext) SelectIf(predicate func(Endpoint) bool) *EndpointSelectionContext { - for _, endpoint := range ctx.Endpoints { - if predicate(endpoint) { - ctx.selected = append(ctx.selected, endpoint) + // Call the endpoint filters on the endpoint. + // Endpoint will be disqualified if any filter reutrns an error. + for _, filter := range endpointFilters { + err := filter(endpoint) + if err != nil { + ctx.logger.With("endpoint_addr", endpointAddr).Debug().Err(err).Msg("endpoint has been disqualified.") + // Mark the disqualified endpoint + ctx.disqualifiedEndpoints[endpointAddr] = struct{}{} + } } } - return ctx + + // Disqualified endpoints have been marked. + // return a random qualified endpoint. + return ctx.selectRandomEndpoint() } -// Selected returns the selected endpoints. -func (ctx *EndpointSelectionContext) Selected() []Endpoint { - return ctx.selected +func (ctx *EndpointSelectionContext) selectRandomEndpoint() (protocol.EndpointAddr, error) { + // build the slice with addresses of qualified endpoints. + var allEndpointsAddrs []protocol.EndpointAddr + for endpointAddr := range ctx.candidateEndpoints { + // disqualified endpoint: skip. + if _, isDisqualified := ctx.disqualifiedEndpoints[endpointAddr]; isDisqualified { + continue + } + + allEndpointsAddrs = append(allEndpointsAddrs, endpointAddr) + } + + // all endpoints have been disqualified: log a message. + if len(allEndpointsAddrs) == 0 { + ctx.logger.With("num_endpoints", len(ctx.candidateEndpoints)).Warn().Msg("all endpoints have been disqualified: returning a random endpoint.") + // build the slice for random selection + for endpointAddr := range ctx.candidateEndpoints { + allEndpointsAddrs = append(allEndpointsAddrs, endpointAddr) + } + } + + // return a random endpoint from the slice. + return allEndpointsAddrs[rand.Intn(len(allEndpointsAddrs))], nil } -// filterSanctionedEndpoints removes endpoints with active sanctions -// This is an internal function used by the framework before passing -// endpoints to the custom service -func (ctx *EndpointSelectionContext) filterSanctionedEndpoints(endpoints map[protocol.EndpointAddr]Endpoint) []Endpoint { - filteredEndpoints := make([]Endpoint, 0, len(endpoints)) +// disqualifySanctionedEndpoints marks endpoints with active sanctions as disqualified. +func (ctx *EndpointSelectionContext) disqualifySanctionedEndpoints() { + if ctx.disqualifiedEndpoints == nil { + ctx.disqualifiedEndpoints = make(map[protocol.EndpointAddr]struct{}) + } - for endpointAddr, endpoint := range endpoints { + for endpointAddr, endpoint := range ctx.candidateEndpoints { // Check if the endpoint is sanctioned. activeSanction, isSanctioned := endpoint.GetActiveSanction() - // Skip sanctioned endpoints. - if isSanctioned { - ctx.logger.With( - "endpoint_addr", string(endpointAddr), - "sanction", activeSanction.String(), - ).Debug().Msg("Dropping sanctioned endpoint") - + // endpoint has no active sanctions: skip further processing. + if !isSanctioned { continue } - filteredEndpoints = append(filteredEndpoints, endpoint) - } + ctx.logger.With( + "endpoint_addr", string(endpointAddr), + "sanction", activeSanction, + ).Debug().Msg("Dropping sanctioned endpoint") - return filteredEndpoints + ctx.disqualifiedEndpoints[endpointAddr] = struct{}{} + } } diff --git a/qos/framework/context_request.go b/qos/framework/context_request.go index 894612076..9a4a60071 100644 --- a/qos/framework/context_request.go +++ b/qos/framework/context_request.go @@ -3,12 +3,14 @@ package framework import ( "encoding/json" "fmt" + "io" + "net/http" "time" "github.com/pokt-network/poktroll/pkg/polylog" "github.com/buildwithgrove/path/gateway" - qosobservations "github.com/buildwithgrove/path/observation/qos/framework" + qosobservations "github.com/buildwithgrove/path/observation/qos" "github.com/buildwithgrove/path/protocol" "github.com/buildwithgrove/path/qos/jsonrpc" ) @@ -23,32 +25,16 @@ import ( // TODO_TECHDEBT: Need to add a Validate() method here to allow the caller (e.g. gateway) // determine whether the endpoint's response was valid, and whether a retry makes sense. // -// requestContext provides the support required by the gateway +// requestQoSContext provides the support required by the gateway // package for handling service requests. -var _ gateway.RequestQoSContext = &requestContext{} - -// TODO_IN_THIS_PR: change the errorKind to private + find the correct file for it. - -// TODO_FUTURE(@adshmh): implement custom, typed result extractors that are commonly used by custom QoS implementations. -// Example: -// ResultContext. -// endpointErrorKind identifies different kinds of endpoint data errors. -type endpointErrorKind int - -const ( - EndpointDataErrorKindUnspecified endpointErrorKind = iota - EndpointDataErrorKindNoInteraction // No endpoint interaction occurred or no payload received - EndpointDataErrorKindEmptyPayload // Empty payload from endpoint - EndpointDataErrorKindUnmarshaling // Could not parse endpoint payload - EndpointDataErrorKindInvalidResult // Payload result doesn't match expected format -) +var _ gateway.RequestQoSContext = &requestQoSContext{} // TODO_IN_THIS_PR: sort out the scope of fields and methods: private/public on private structs. // // requestQoSContext holds the context for a request through its lifecycl. // It contains all the state needed to process the request, build responses, and generate observations. type requestQoSContext struct { - Logger polylog.Logger + logger polylog.Logger // Tracks all data related to the current request context: // - client's request @@ -79,10 +65,10 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd resultCtx := rc.contextBuilder.buildEndpointQueryResultContext(endpointQueryResult) // Build an endpoint query result using the context. - endpointQueryResult := resultCtx.buildEndpointQueryResult() + processedEndpointQueryResult := resultCtx.buildEndpointQueryResult() // Track the result in the request journal. - rc.journal.reportEndpointQueryResult(endpointQueryResult) + rc.journal.reportEndpointQueryResult(processedEndpointQueryResult) } // TODO_TECHDEBT: support batch JSONRPC requests by breaking them into single JSONRPC requests and tracking endpoints' response(s) to each. @@ -94,6 +80,7 @@ func (rc *requestQoSContext) UpdateWithResponse(endpointAddr protocol.EndpointAd // Implements the gateway.RequestQoSContext interface. func (rc requestQoSContext) GetHTTPResponse() gateway.HTTPResponse { // check if a protocol-level error has occurred. + // A protocol-level error means no endpoint responses were received. rc.checkForProtocolLevelError() // use the request journal to build the client's HTTP response. @@ -102,37 +89,46 @@ func (rc requestQoSContext) GetHTTPResponse() gateway.HTTPResponse { // GetObservations uses the request's journal to build and return all observations. // Implements gateway.RequestQoSContext interface. -func (rc requestContext) GetObservations() qosobservations.Observations { +func (rc requestQoSContext) GetObservations() qosobservations.Observations { // check if a protocol-level error has occurred. + // A protocol-level error means no endpoint responses were received. rc.checkForProtocolLevelError() // Use the request journal to generate the observations. - return rc.journal.buildObservations() + return rc.journal.getObservations() } // Build and returns an instance EndpointSelectionContext to perform endpoint selection for the client request. // Implements the gateway.RequestQoSContext func (rc *requestQoSContext) GetEndpointSelector() protocol.EndpointSelector { - selectorCtx := rc.contextBuilder.buildEndpointSelectionContext() - return selectorCtx.buildSelectorForRequest(rc.journal) + endpointSelectionCtx := rc.contextBuilder.buildEndpointSelectionContext(rc.journal) + return endpointSelectionCtx } // Declares the request as failed with protocol-level error if no data from any endpoints has been reported to the request context. -func (rc *requestContext) checkForProtocolLevelError() { - // Assume protocol-level error if no endpoint responses have been received yet. - if len(rc.journal.endpointQueryResults) == 0 { - rc.journal.setProtocolLevelError() +func (rc *requestQoSContext) checkForProtocolLevelError() { + // One or more endpoint results were received: no protocol error has occurred. + if len(rc.journal.endpointQueryResults) > 0 { + return } + + // Assume protocol-level error if no endpoint responses have been received yet. + // + // Build a request error. + // Include the cluent JSONRPC request's ID if available. + reqErr := buildRequestErrorForInternalErrProtocolErr(rc.journal.getJSONRPCRequestID()) + // Set the request error in the journal. + rc.journal.setRequestError(reqErr) } -func (ctx *requestContext) initFromHTTP(httpReq *http.Request) bool { +func (ctx *requestQoSContext) initFromHTTP(httpReq *http.Request) bool { jsonrpcReq, reqErr := parseHTTPRequest(ctx.logger, httpReq) // initialize the request journal to track all request data and events. ctx.journal = &requestJournal{ jsonrpcRequest: jsonrpcReq, - requestErr: reqErr, + requestError: reqErr, } // Only proceed with next steps if there were no errors parsing the HTTP request into a JSONRPC request. @@ -162,7 +158,7 @@ func parseHTTPRequest( } // Parse the JSON-RPC request - var jsonrpcReq jsonrpc.JsonRpcRequest + var jsonrpcReq jsonrpc.Request if err := json.Unmarshal(body, &jsonrpcReq); err != nil { // TODO_IN_THIS_PR: log the first 1K bytes of the body. // Handle parse error (client error) @@ -175,16 +171,16 @@ func parseHTTPRequest( if validationErr := jsonrpcReq.Validate(); validationErr != nil { // Request failed basic JSONRPC request validation. logger.Info().Err(validationErr). - Str("method", jsonrpcReq.Method). + Str("method", string(jsonrpcReq.Method)). Msg("JSONRPC Request validation failed") - return jsonrpcReq, buildRequestErrorJSONRPCValidationError(jsonrpcReq.ID, validationErr) + return &jsonrpcReq, buildRequestErrorJSONRPCValidationError(jsonrpcReq.ID, validationErr) } // Request is valid logger.Debug(). - Str("method", jsonrpcReq.Method). + Str("method", string(jsonrpcReq.Method)). Msg("Request validation successful") - return jsonrpcReq, nil + return &jsonrpcReq, nil } diff --git a/qos/framework/context_state_update.go b/qos/framework/context_state_update.go index 6119668b1..4ba465e5f 100644 --- a/qos/framework/context_state_update.go +++ b/qos/framework/context_state_update.go @@ -18,7 +18,7 @@ type StateUpdateContext struct { func (ctx *StateUpdateContext) updateFromEndpoints(updatedEndpoints []*Endpoint) error { // get the list of params to update by calling the custom state updater. - paramsToUpdate := ctx.stateUpdater(ctx, updatedEndpoints) + paramsToUpdate := ctx.stateUpdater(ctx) // Update the state parameters through the service state. return ctx.ServiceState.updateParameters(paramsToUpdate) @@ -44,15 +44,16 @@ func (ctx *StateUpdateContext) SetStrParam(paramName, value string) { ctx.paramsToUpdate.Set(paramName, param) } +// TODO_IN_THIS_PR: copy the map to prevent reference leaks func (ctx *StateUpdateContext) SetConsensusParam(paramName string, consensusValues map[string]int) { param := &StateParameter{ - consensValues: consensusValues, + consensusValues: consensusValues, } ctx.paramsToUpdate.Set(paramName, param) } // Return the set of updated state parameters. -func (ctx *StateUpdateContext) BuildStateParameterUpdateSet() StateParameterUpdateSet { +func (ctx *StateUpdateContext) BuildStateParameterUpdateSet() *StateParameterUpdateSet { return ctx.paramsToUpdate } diff --git a/qos/framework/endpoint.go b/qos/framework/endpoint.go index 5ead2d882..39c600167 100644 --- a/qos/framework/endpoint.go +++ b/qos/framework/endpoint.go @@ -22,7 +22,7 @@ type Endpoint struct { // Examples: // - "eth_blockNumber": &EndpointQueryResult{IntValues: {"blockNumber": 0x1234}} // - "eth_getBalance": &EndpointQueryResult{ - // StringValues: {"address": "0x8d97..."}, + // StrValues: {"address": "0x8d97..."}, // IntValues: {"balance": 133456789}, // } queryResults map[jsonrpc.Method]*EndpointQueryResult @@ -31,12 +31,12 @@ type Endpoint struct { resultsMu sync.RWMutex } -// GetQueryResultStringValue retrieves a string attribute of a result by key. +// GetStrResult retrieves a string attribute of a result by key. // DEV_NOTE: This design pattern: // - Prevents map leaking and unauthorized modifications through pointers // - Avoids expensive struct cloning // - Maintains proper encapsulation -func (e *Endpoint) GetQueryResultStringValue(resultKey jsonrpc.Method, valueKey string) (string, bool) { +func (e *Endpoint) GetStrResult(resultKey jsonrpc.Method, valueKey string) (string, bool) { e.resultsMu.RLock() defer e.resultsMu.RUnlock() @@ -45,26 +45,28 @@ func (e *Endpoint) GetQueryResultStringValue(resultKey jsonrpc.Method, valueKey return "", false } - return result.StringValues[valueKey] + strValue, found := result.StrValues[valueKey] + return strValue, found } -// GetQueryResultStringValue retrieves an integer attribute of a result by key. -// See the comment on GetQueryResultStringValue for notes on this pattern. -func (e *Endpoint) GetQueryResultIntValue(resultKey jsonrpc.Method, valueKey string) (int, bool) { +// GetIntResult retrieves an integer attribute of a result by key. +// See the comment on GetStrResult for notes on this pattern. +func (e *Endpoint) GetIntResult(resultKey jsonrpc.Method, valueKey string) (int, bool) { e.resultsMu.RLock() defer e.resultsMu.RUnlock() result, exists := e.queryResults[resultKey] if !exists || result == nil { - return "", false + return 0, false } - return result.IntValues[valueKey] + intValue, found := result.IntValues[valueKey] + return intValue, found } // TODO_IN_THIS_PR: implement. -func (e *Endpoint) HasActiveSanction() (Sanction, bool) { - +func (e *Endpoint) GetActiveSanction() (Sanction, bool) { + return Sanction{}, false } // ApplyQueryResult updates the endpoint's attributes with attributes from the query result. @@ -88,7 +90,7 @@ func (e *Endpoint) applyQueryResults(endpointQueryResults []*EndpointQueryResult } // Update the endpoint result matching the JSONRPC request. - e.queryResult[jsonrpcRequestMethod] = result + e.queryResults[jsonrpcRequestMethod] = endpointQueryResult e.logger.With("jsonrpc_request_method", jsonrpcRequestMethod).Debug().Msg("Updated endpoint with query result.") } diff --git a/qos/framework/endpoint_errors.go b/qos/framework/endpoint_errors.go index fd44b4857..62a9cf9eb 100644 --- a/qos/framework/endpoint_errors.go +++ b/qos/framework/endpoint_errors.go @@ -3,9 +3,10 @@ package framework type EndpointErrorKind int const ( - _ EndpointErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. + EndpointErrKindUnspecified EndpointErrorKind = iota // matches the "UNSPECIFIED" enum value in proto definitions. EndpointErrKindEmptyPayload // Empty payload from endpoint EndpointErrKindParseErr // Could not parse endpoint payload + EndpointErrKindValidationErr // Parsed endpoint payload, in the form of JSONRPC response, failed validation. EndpointErrKindInvalidResult // Payload result doesn't match expected value: e.g. invalid chainID value ) diff --git a/qos/framework/endpoint_query_result.go b/qos/framework/endpoint_query_result.go index f8247db43..b03eb6ac6 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/framework/endpoint_query_result.go @@ -2,8 +2,10 @@ package framework import ( "errors" + "fmt" "time" + "github.com/buildwithgrove/path/protocol" "github.com/buildwithgrove/path/qos/jsonrpc" ) @@ -57,7 +59,7 @@ type EndpointQueryResult struct { // e.g. for a Solana `getEpochInfo` request, the custom service could derive two endpoint attributes as follows: // - "BlockHeight": 0x1234 // - "Epoch": 5 - StringValues map[string]string + StrValues map[string]string IntValues map[string]int // The time at which the query result is expired. @@ -83,7 +85,7 @@ func (eqr *EndpointQueryResult) IsJSONRPCError() bool { } func (eqr *EndpointQueryResult) GetResultAsInt() (int, error) { - parsedJSONRPCResponse := eqr.getParsedJSONRPCResponse() + parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() if err != nil { return 0, err } @@ -92,7 +94,7 @@ func (eqr *EndpointQueryResult) GetResultAsInt() (int, error) { } func (eqr *EndpointQueryResult) GetResultAsStr() (string, error) { - parsedJSONRPCResponse := eqr.getParsedJSONRPCResponse() + parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() if err != nil { return "", err } @@ -101,7 +103,7 @@ func (eqr *EndpointQueryResult) GetResultAsStr() (string, error) { } func (eqr *EndpointQueryResult) getParsedJSONRPCResponse() (*jsonrpc.Response, error) { - parsedJSONRPCResponse := eqr.endpointQuery.parsedJSONRPCResponse + parsedJSONRPCResponse := eqr.parsedJSONRPCResponse // Endpoint payload failed to parse as JSONRPC response. // This is not considered a JSONRPC error response. if parsedJSONRPCResponse == nil { @@ -112,7 +114,7 @@ func (eqr *EndpointQueryResult) getParsedJSONRPCResponse() (*jsonrpc.Response, e } func (eqr *EndpointQueryResult) Success( - resultBuilders ...EndpointQueryResultBuilder + resultBuilders ...ResultBuilder, ) *EndpointQueryResult { for _, builder := range resultBuilders { builder(eqr) @@ -136,7 +138,7 @@ func (eqr *EndpointQueryResult) Error(description string) *EndpointQueryResult { // SanctionEndpoint creates an error result with a temporary sanction. func (eqr *EndpointQueryResult) SanctionEndpoint(description, reason string, duration time.Duration) *EndpointQueryResult { eqr.EndpointError = &EndpointError { - ErrorKind: EndpointDataErrorKindInvalidResult, + ErrorKind: EndpointErrKindInvalidResult, Description: description, RecommendedSanction: &Sanction{ Type: SanctionTypeTemporary, @@ -151,7 +153,7 @@ func (eqr *EndpointQueryResult) SanctionEndpoint(description, reason string, dur // PermanentSanction creates an error result with a permanent sanction. func (eqr *EndpointQueryResult) PermanentSanction(description, reason string) *EndpointQueryResult { eqr.EndpointError = &EndpointError { - ErrorKind: EndpointDataErrorKindInvalidResult, + ErrorKind: EndpointErrKindInvalidResult, Description: description, RecommendedSanction: &Sanction{ Type: SanctionTypePermanent, @@ -162,9 +164,9 @@ func (eqr *EndpointQueryResult) PermanentSanction(description, reason string) *E return eqr } -type EndpointQueryResultBuilder func(*EndpointQueryResultBuilder) +type ResultBuilder func(*EndpointQueryResult) -func (eqr *EndpointQueryResult) AddIntResult(key string, value int) EndpointQueryResultBuilder { +func (eqr *EndpointQueryResult) AddIntResult(key string, value int) ResultBuilder { return func(r *EndpointQueryResult) { if r.IntValues == nil { r.IntValues = make(map[string]int) @@ -173,7 +175,7 @@ func (eqr *EndpointQueryResult) AddIntResult(key string, value int) EndpointQuer } } -func (eqr *EndpointQueryResult) AddStrResult(key, value string) EndpointQueryResultBuilder { +func (eqr *EndpointQueryResult) AddStrResult(key, value string) ResultBuilder { return func(r *EndpointQueryResult) { if r.StrValues == nil { r.StrValues = make(map[string]string) diff --git a/qos/framework/endpoint_query_result_defaults.go b/qos/framework/endpoint_query_result_defaults.go index de97e29e7..23fa821c7 100644 --- a/qos/framework/endpoint_query_result_defaults.go +++ b/qos/framework/endpoint_query_result_defaults.go @@ -8,8 +8,11 @@ import ( ) // TODO_IN_THIS_PR: reword/rename the method and the comment. -// defaultResultBuilder is applied by the endpointCallProcessor on EndpointCalls not matching any of the JSONRPC methods specified by the custom service QoS. -// It builds an EndpointResult to track JSONRPC requests/responses which are not utilized by the custom QoS service for updating the service state or endpoint selection. +// defaultResultBuilder is applied by the endpointCallProcessor on endpoint responses not matching any of the JSONRPC methods specified by the custom service QoS. +// It builds an EndpointQueryResult to track JSONRPC requests/responses not utilized by the custom QoS service for updating the service state or endpoint selection. +// Used in generating observations for: +// - Metrics +// - Data Pipeline func defaultResultBuilder(ctx *EndpointQueryResultContext) *EndpointQueryResult { //TODO_IN_THIS_PR: implement this function: /* @@ -19,42 +22,7 @@ func defaultResultBuilder(ctx *EndpointQueryResultContext) *EndpointQueryResult ResponseValidationError: r.validationError, HttpStatusCode: int32(r.getHTTPStatusCode()), */ -} - -// TODO_IN_THIS_PR: clarify that the following happens for a NoResponse: -// - NoResponse's underlying getHTTPStatusCode always returns a 500 Internal error. -// - NoResponse is always an invalid response. -// -// buildResultForNoResponse handles the case when no endpoint response was received. -// This can occur due to protocol-level failures or when no endpoint was selected. -// This is not the same as empty responses (where an endpoint responded with empty data). -func buildResultForNoResponse(request *jsonrpc.JsonRpcRequest) *EndpointQueryResult { - // Create a synthetic result for protocol error - result := &EndpointResult{ - Call: &EndpointCall{ - Request: request, - ReceivedAt: time.Now(), - }, - parseResult: &parseResult{ - parseError: fmt.Errorf("no endpoint responses received"), - }, - } - - // Create result context for creating the result - ctx := &ResultContext{ - Request: request, - } - - // Apply default sanction for no response - result.ResultData = applySanctionForNoResponse(ctx) - - // Set error kind - result.ResultData.Error.kind = EndpointDataErrorKindNoInteraction - - // Set error response - result.ErrorResponse = newErrResponseNoEndpointResponses(request.Id) - - return result + return nil } // TODO_MVP(@adshmh): Implement request retry support: @@ -68,62 +36,56 @@ func buildResultForNoResponse(request *jsonrpc.JsonRpcRequest) *EndpointQueryRes // An empty response is always invalid: e.g. EVMResponseValidationError_EVM_RESPONSE_VALIDATION_ERROR_EMPTY // // buildResultForEmptyResponse handles the case when an endpoint returned an empty response. -func buildResultForEmptyResponse(call *EndpointCall) *EndpointQueryResult { - // Create a new result with the call - result := &EndpointResult{ - Call: call, - parseResult: &parseResult{ - isEmpty: true, - parseError: fmt.Errorf("empty response from endpoint"), - }, - } - - // Create result context for creating the result - ctx := &ResultContext{ - EndpointAddr: call.EndpointAddr, - Request: call.Request, +func buildResultForEmptyResponse(endpointQueryResult *EndpointQueryResult) *EndpointQueryResult { + endpointError := &EndpointError { + ErrorKind: EndpointErrKindEmptyPayload, + Description: "endpoint returned an empty response", + // Set the recommended sanction based on the error + RecommendedSanction: getRecommendedSanction(EndpointErrKindEmptyPayload, nil), } - // Apply default sanction for empty response - result.ResultData = applySanctionForEmptyResponse(ctx) + // Set a generic response. + endpointQueryResult.Response = newErrResponseEmptyEndpointResponse(endpointQueryResult.getJSONRPCRequestID()) + // Set the endpoint error + endpointQueryResult.EndpointError = endpointError - // Set error kind - result.ResultData.Error.kind = EndpointDataErrorKindEmptyPayload - - // Set error response - result.ErrorResponse = newErrResponseEmptyResponse(call.Request.Id) - - return result + return endpointQueryResult } // buildResultForErrorUnmarshalingEndpointReturnedData handles the case when parsing the endpoint's returned data failed. func buildResultForErrorUnmarshalingEndpointReturnedData( - call *EndpointCall, + endpointQueryResult *EndpointQueryResult, parseError error, ) *EndpointQueryResult { - // Create a new result with the call - result := &EndpointResult{ - Call: call, - parseResult: &parseResult{ - parseError: parseError, - }, + endpointError := &EndpointError { + ErrorKind: EndpointErrKindUnmarshaling, + Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), + RecommendedSanction: getRecommendedSanction(EndpointErrKindUnmarshaling, parseError), } - // Create result context for creating the result - ctx := &ResultContext{ - EndpointAddr: call.EndpointAddr, - Request: call.Request, - } + // Set a generic response. + endpointQueryResult.Response = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) + // Set the endpoint error + endpointQueryResult.EndpointError = endpointError - // Apply default sanction for parse error - result.ResultData = applySanctionForUnmarshalingError(ctx, parseError) + return endpointQueryResult +} - // Set error kind and raw payload - result.ResultData.Error.kind = EndpointDataErrorKindUnmarshaling - result.ResultData.Error.rawPayload = call.ReceivedData +// buildResultForErrorValidatingEndpointResponse handles the case when validating the unmarshaled endpoint's JSONRPC response has failed. +func buildResultForErrorValidatingEndpointResponse( + endpointQueryResult *EndpointQueryResult, + parseError error, +) *EndpointQueryResult { + endpointError := &EndpointError { + ErrorKind: EndpointErrKindUnmarshaling, + Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), + RecommendedSanction: getRecommendedSanction(EndpointErrKindUnmarshaling, parseError), + } - // Set error response - result.ErrorResponse = newErrResponseParseError(call.Request.Id, parseError) + // Set a generic response. + endpointQueryResult.Response = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) + // Set the endpoint error + endpointQueryResult.EndpointError = endpointError - return result + return endpointQueryResult } diff --git a/qos/framework/endpoint_sanction.go b/qos/framework/endpoint_sanction.go index 75001bccb..c6a6df31c 100644 --- a/qos/framework/endpoint_sanction.go +++ b/qos/framework/endpoint_sanction.go @@ -11,7 +11,7 @@ import ( type SanctionType int const ( - _ SanctionType = iota + _ SanctionType = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. SanctionTypeTemporary // Time-limited exclusion SanctionTypePermanent // Permanent exclusion ) diff --git a/qos/framework/endpoint_sanction_defaults.go b/qos/framework/endpoint_sanction_defaults.go index 7958cc762..a93d7e351 100644 --- a/qos/framework/endpoint_sanction_defaults.go +++ b/qos/framework/endpoint_sanction_defaults.go @@ -5,8 +5,8 @@ import ( "time" ) -// TODO_FUTURE(@adshmh): Support one or both of the following: -// - ake these sanction durations/types configurable through service config, +// TODO_FUTURE(@adshmh): Add capability to override default sanctions via the QoSDefinition struct. +// TODO_FUTURE(@adshmh): make these sanction durations/types configurable through service config, const ( // Default sanction duration for empty responses DefaultEmptyResponseSanctionDuration = 5 * time.Minute @@ -18,34 +18,31 @@ const ( DefaultNoResponseSanctionDuration = 5 * time.Minute ) -// applySanctionForNoResponse applies the default sanction for when no endpoint responded. -// This can occur due to protocol-level failures: e.g. the selected endpoint was temporarily unavailable. -// This is not the same as empty responses (where an endpoint responded with empty data). -func applySanctionForNoResponse(ctx *ResultContext) *ResultData { - return ctx.SanctionEndpoint( - "No endpoint responses received", - "Protocol error", - DefaultNoResponseSanctionDuration, - ) +func getRecommendedSanction(endpointErrKind EndpointErrorKind, err error) *Sanction { + switch endpointErrKind { + case EndpointDataErrorKindEmptyPayload: + return newSanctionForEmptyResponse() + case EndpointDataErrorKindUnmarshaling: + return newSanctionForUnmarshalingError(err) + default: + return nil + } } -// applySanctionForEmptyResponse applies the default sanction for empty responses. -func applySanctionForEmptyResponse(ctx *ResultContext) *ResultData { - return ctx.SanctionEndpoint( - "Empty response from endpoint", - "Empty response", - DefaultEmptyResponseSanctionDuration, - ) +// newSanctionForEmptyResponse returns the default sanction for empty responses. +func newSanctionForEmptyResponse() *Sanction { + return &Sanction{ + Type: SanctionTypeTemporary, + Reason: "Empty response from the endpoint", + ExpiryTime: time.Now().Add(DefaultEmptyResponseSanctionDuration), + } } -// applySanctionForUnmarshalingError applies the default sanction for parse errors. -func applySanctionForUnmarshalingError(ctx *ResultContext, err error) *ResultData { - return ctx.SanctionEndpoint( - fmt.Sprintf("Failed to parse endpoint response: %v", err), - "Parse error", - DefaultParseErrorSanctionDuration, - ) +// newSanctionForUnmarshalingError returns the default sanction for parse errors. +func newSanctionForUnmarshalingError(err error) *Sanction { + return &Sanction{ + Type: SanctionTypeTemporary, + Reason: fmt.Sprintf("Endpoint payload failed to parse into JSONRPC response: %s", err.Error()), + ExpiryTime: time.Now().Add(DefaultParseErrorSanctionDuration), + } } - -// TODO_FUTURE: Add capability to override default sanctions and/or make them configurable -// through service configuration or dynamic policy updates. diff --git a/qos/framework/endpoint_store.go b/qos/framework/endpoint_store.go index f9f97b4fe..83467bfae 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/framework/endpoint_store.go @@ -66,14 +66,14 @@ func (es *endpointStore) storeEndpoint(addr protocol.EndpointAddr, endpoint Endp } // getEndpoint retrieves an endpoint by its address. -func (es *endpointStore) getEndpoint(addr protocol.EndpointAddr) (*Endpoint, bool) { +func (es *endpointStore) getEndpoint(addr protocol.EndpointAddr) *Endpoint { es.endpointsMu.RLock() defer es.endpointsMu.RUnlock() if es.endpoints == nil { - return Endpoint{}, false + return Endpoint{} } - endpoint, found := es.endpoints[addr] - return endpoint, found + endpoint := es.endpoints[addr] + return endpoint } diff --git a/qos/framework/jsonrpc_errors.go b/qos/framework/jsonrpc_errors.go index fa01823e0..1eb925a64 100644 --- a/qos/framework/jsonrpc_errors.go +++ b/qos/framework/jsonrpc_errors.go @@ -3,9 +3,13 @@ package framework import ( "fmt" + "github.com/pokt-network/poktroll/pkg/polylog" + "github.com/buildwithgrove/path/qos/jsonrpc" ) +// TODO_IN_THIS_PR: verify returned JSONRPC error codes. + const ( // Standard JSONRPC 2.0 error codes ErrorCodeParseError int64 = -32700 @@ -104,6 +108,20 @@ func newJSONRPCErrResponseInternalProtocolError(requestID jsonrpc.ID) jsonrpc.Re ) } +func newJSONRPCErrResponseJSONRPCRequestValidationError(requestID jsonrpc.ID, validationErr error) jsonrpc.Response { + return jsonrpc.GetErrorResponse( + requestID, + -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html + fmt.Sprintf("invalid request: %s", validationErr.Error()), // Error Message + map[string]string{ + "error": validationErr.Error(), + // Custom extension - not part of the official JSON-RPC spec + // Indicates this error is permanent - the request must be corrected as retrying will not succeed + "retryable": "false", + }, + ) +} + // newErrResponseMarshalError creates a JSON-RPC error response for marshaling errors. // This response: // - Preserves the original request ID if available diff --git a/qos/framework/observations.go b/qos/framework/observations.go index a5af5cf4c..af8bb6bf4 100644 --- a/qos/framework/observations.go +++ b/qos/framework/observations.go @@ -1,6 +1,9 @@ package framework import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + qosobservations "github.com/buildwithgrove/path/observation/qos" observations "github.com/buildwithgrove/path/observation/qos/framework" ) @@ -18,25 +21,27 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // initialize the observations to include: // - service name // - observations related to the request: - observations := qosobservations.RequestJournal { + journalObservations := observations.RequestJournal { ServiceName: rj.serviceName, } // observation for parsed JSONRPC (if parsed) if rj.jsonrpcRequest != nil { - observations.JsonRpcRequest = buildJSONRPCRequestObservation(rj.jsonrpcRequest) + journalObservations.JsonRpcRequest = buildJSONRPCRequestObservation(rj.jsonrpcRequest) } // observation for request error (if set) if rj.requestErr != nil { - observations.RequestError = buildRequestErrorObservations(rj.requestErr) + journalObservations.RequestError = buildRequestErrorObservations(rj.requestErr) } // No endpoint query results. // e.g. for invalid requests. // Skip adding endpoint observations. if len(rj.endpointQueryResults) == 0 { - return observations + return qosobservations.Observations { + ServiceObservations: journalObservations, + } } endpointObservations := make([]*qosobservations.EndpointQueryResultObservation, len(rj.endpointQueryResults)) @@ -44,8 +49,10 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { endpointObservation[index] = endpointQueryResult.buildObservations() } - observations.EndpointQueryResultObservations = endpointObservations - return observations + journalObservations.EndpointQueryResultObservations = endpointObservations + return qosobservations.Observations { + ServiceObservations: journalObservations, + } } @@ -85,7 +92,7 @@ func buildRequestJournalFromObservations( // request had an error: internal, parsing, validation, etc. // no further processing required. if requestErr != nil { - logger.With("num_endpoint_observations", len(observations.GetEndpointQueryResultObservatios()). + logger.With("num_endpoint_observations", len(observations.GetEndpointQueryResultObservatios())). Info().Msg("Request had an error: no endpoint observations expected.") return requestJournal diff --git a/qos/framework/observations_endpoint.go b/qos/framework/observations_endpoint.go deleted file mode 100644 index 36940d4ee..000000000 --- a/qos/framework/observations_endpoint.go +++ /dev/null @@ -1,12 +0,0 @@ -package framework - -import ( - observations "github.com/buildwithgrove/path/observation/qos/framework" -) - -func extractEndpointQueryFromObservation(observation *qosobservations.Observations) *endpointQuery { - return &endpointQuery{ - // Extract the JSONRPC request corresponding to the observation. - request: extractJSONRPCRequestFromObservation(observation.GetRequestObservation()), - } -} diff --git a/qos/framework/observations_endpoint_error.go b/qos/framework/observations_endpoint_error.go index 64805e3d6..20532a16b 100644 --- a/qos/framework/observations_endpoint_error.go +++ b/qos/framework/observations_endpoint_error.go @@ -65,44 +65,32 @@ func extractEndpointErrorFromObservation(obsError *observations.EndpointError) * // TODO_IN_THIS_PR: verify errorKind conversion to/from proto. // // DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. -func translateToObservationErrorKind(errKind EndpointErrorKind) observations.ErrorKind { +func translateToObservationErrorKind(errKind EndpointErrorKind) observations.EndpointErrorKind { switch errKind { - case EndpointErrorKindResponseMalformed: - return observations.ErrorKind_ERROR_KIND_RESPONSE_MALFORMED - case EndpointErrorKindResponseUnexpectedError: - return observations.ErrorKind_ERROR_KIND_RESPONSE_UNEXPECTED_ERROR - case EndpointErrorKindResponseInvalidValue: - return observations.ErrorKind_ERROR_KIND_RESPONSE_INVALID_VALUE - case EndpointErrorKindRequestTimedOut: - return observations.ErrorKind_ERROR_KIND_REQUEST_TIMED_OUT - case EndpointErrorKindInsufficientFunds: - return observations.ErrorKind_ERROR_KIND_INSUFFICIENT_FUNDS - case EndpointErrorKindRateLimited: - return observations.ErrorKind_ERROR_KIND_RATE_LIMITED - case EndpointErrorKindInternalError: - return observations.ErrorKind_ERROR_KIND_INTERNAL_ERROR + case EndpointErrKindEmptyPayload: + return observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD + case EndpointErrKindParseErr: + return observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_UNMARSHALING + case EndpointErrKindValidationErr: + return observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_VALIDATION_ERR + case EndpointErrKindInvalidResult: + return observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_INVALID_RESULT default: - return observations.ErrorKind_ERROR_KIND_UNSPECIFIED + return observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_UNSPECIFIED } } // DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. -func translateFromObservationErrorKind(errKind observations.ErrorKind) EndpointErrorKind { +func translateFromObservationErrorKind(errKind observations.EndpointErrorKind) EndpointErrorKind { switch errKind { - case observations.ErrorKind_ERROR_KIND_RESPONSE_MALFORMED: - return EndpointErrorKindResponseMalformed - case observations.ErrorKind_ERROR_KIND_RESPONSE_UNEXPECTED_ERROR: - return EndpointErrorKindResponseUnexpectedError - case observations.ErrorKind_ERROR_KIND_RESPONSE_INVALID_VALUE: - return EndpointErrorKindResponseInvalidValue - case observations.ErrorKind_ERROR_KIND_REQUEST_TIMED_OUT: - return EndpointErrorKindRequestTimedOut - case observations.ErrorKind_ERROR_KIND_INSUFFICIENT_FUNDS: - return EndpointErrorKindInsufficientFunds - case observations.ErrorKind_ERROR_KIND_RATE_LIMITED: - return EndpointErrorKindRateLimited - case observations.ErrorKind_ERROR_KIND_INTERNAL_ERROR: - return EndpointErrorKindInternalError + case observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD: + return EndpointErrKindEmptyPayload + case observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_UNMARSHALING: + return EndpointErrKindParseErr + case observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_VALIDATION_ERR: + return EndpointErrKindValidationErr + case observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_INVALID_RESULT: + return EndpointErrKindInvalidResult default: return EndpointErrorKindUnspecified } diff --git a/qos/framework/observations_endpoint_result.go b/qos/framework/observations_endpoint_result.go index bff0d4b6f..a075f207c 100644 --- a/qos/framework/observations_endpoint_result.go +++ b/qos/framework/observations_endpoint_result.go @@ -1,6 +1,8 @@ package framework import ( + "github.com/pokt-network/poktroll/pkg/polylog" + observations "github.com/buildwithgrove/path/observation/qos/framework" "github.com/buildwithgrove/path/protocol" "github.com/buildwithgrove/path/qos/jsonrpc" diff --git a/qos/framework/observations_jsonrpc.go b/qos/framework/observations_jsonrpc.go index 1b42d5d69..8c3a6b403 100644 --- a/qos/framework/observations_jsonrpc.go +++ b/qos/framework/observations_jsonrpc.go @@ -1,26 +1,25 @@ package framework -func buildJSONRPCRequestObservation(jsonrpcReq jsonrpc.Request) *qosobservations.JsonRpcRequest { - return &qosobservations.JsonRpcRequest { +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +func buildJSONRPCRequestObservation(jsonrpcReq jsonrpc.Request) *observations.JsonRpcRequest { + return &observations.JsonRpcRequest { Id: jsonrpcReq.ID.String(), Method: jsonrpcReq.Method, } } // TODO_IN_THIS_PR: implement. -func buildJSONRPCResponseObservation(jsonrpcResp jsonrpc.Response) *qosobservations.JsonRpcResponse { +func buildJSONRPCResponseObservation(jsonrpcResp jsonrpc.Response) *observations.JsonRpcResponse { return nil } func extractJSONRPCRequestFromObservation( - observation *qosobservations.RequestObservation, + jsonrpcRequestObs *observations.JsonRpcRequest, ) *jsonrpc.Request { - // Nil - if observation == nil { - return nil - } - - jsonrpcRequestObs := observation.GetJsonRpcRequest() if jsonrpcRequestObs == nil { return nil } @@ -31,8 +30,24 @@ func extractJSONRPCRequestFromObservation( } } -func extractJSONRPCResponseFRomObservation( - observation *qosobservations. +func extractJSONRPCResponseFromObservation( + observation *observations.JsonRpcResponse, ) *jsonrpc.Response { + if observation == nil { + return nil + } + + jsonrpcResp := &jsonrpc.Response{ + ID: jsonrpc.IDFromStr(observation.GetId()), + // TODO_MVP(@adshmh): consider capturing the result. + } + + if jsonrpcErr := observation.GetErr(); jsonrpcErr != nil { + jsonrpcResp.Error = &jsonrpc.ResponseError{ + Code: jsonrpcErr.GetCode(), + Message: jsonrpcErr.GetMessage(), + } + } + return jsonrpcResp } diff --git a/qos/framework/observations_request.go b/qos/framework/observations_request.go deleted file mode 100644 index 83dde82a2..000000000 --- a/qos/framework/observations_request.go +++ /dev/null @@ -1,36 +0,0 @@ -package framework - -import ( - observations "github.com/buildwithgrove/path/observation/qos/framework" - "github.com/buildwithgrove/path/qos/jsonrpc" -) - -// TODO_IN_THIS_PR: ensure the observations will contain: -// - HTTP Status code: e.g. httpStatusRequestValidationFailureUnmarshalFailure, -// - Validation error: e.g. qosobservations.EVMRequestValidationError_EVM_REQUEST_VALIDATION_ERROR_REQUEST_UNMARSHALING_FAILURE, -// - Error details. -// -// buildObservations builds and returns request observations of of the requestDetails struct. -func (rd *requestDetails) buildObservations() *qosobservations.RequestObservation { - // build a JSONRPC request observation, if one was parsed. - var jsonrpcRequestObs *qosobservations.JsonrpcRequest - if rd.request != nil { - jsonrpcRequestObs = rd.request.GetObservation() - } - - // build request failure observation, if the request parsing failed. - var errorObs *qosobservations.RequestError - if rd.requestError != nil { - errorObs = rd.requestError.buildObservations() - } - - return &qosobservations.RequestObservation{ - // Only set if validation was successful - JsonrpcRequest: jsonrpcRequestObs, - // Only set if the request failed for any reason. - // A valid request can still fail due to a gateway internal error, e.g.: - // - error reading HTTP request's body. - // - protocol-level error: e.g. selected endpoint timed out. - RequestError: errorObs, - } -} diff --git a/qos/framework/observations_request_error.go b/qos/framework/observations_request_error.go index 1b7c57d8e..913764224 100644 --- a/qos/framework/observations_request_error.go +++ b/qos/framework/observations_request_error.go @@ -1,7 +1,11 @@ package framework -func (re *requestError) buildObservation() *qosobservations.RequestError { - return &qosobservations.RequestError{ +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +func (re *requestError) buildObservation() *observations.RequestError { + return &observations.RequestError{ ErrorKind: translateToRequestError(re.errorKind), ErrorDetails: re.errorDetails, // The JSONRPC response returned to the client. @@ -10,19 +14,19 @@ func (re *requestError) buildObservation() *qosobservations.RequestError { } // DEV_NOTE: you MUST update this function when changing the set of request errors. -func translateToRequestError(errKind requestErrorKind) qosobservations.RequestErrorKind { +func translateToRequestError(errKind requestErrorKind) observations.RequestErrorKind { switch errKind { case requestErrKindInternalReadyHTTPBody: - return RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE + return observations.RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE case requestErrKindInternalProtocolError: - return RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR + return observations.RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR case requestErrKindJSONRPCParsingErr: - return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE - requestErrKindJSONRPCInvalidVersion - return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION + return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE + case requestErrKindJSONRPCInvalidVersion: + return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION case requestErrKindJSONRPCMissingMethod: - return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD + return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD default: - return RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNSPECIFIED + return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNSPECIFIED } } diff --git a/qos/framework/qos.go b/qos/framework/qos.go index f1947bdbd..a6af7ab1e 100644 --- a/qos/framework/qos.go +++ b/qos/framework/qos.go @@ -58,7 +58,7 @@ func (q *QoS) ApplyObservations(observations *qosobservations.Observations) erro // reconstruct the request journal matching the observations. requestJournal, err := buildRequestJournalFromObservations(q.logger, serviceRequestObservations) if err != nil { - q.logger.Error().Err(err).Msg("Error building the request journal from observations: skipping the application of observations.")a + q.logger.Error().Err(err).Msg("Error building the request journal from observations: skipping the application of observations.") return err } @@ -100,12 +100,15 @@ func (q *QoS) buildEndpointQueryResultContext(endpointQueryResult *EndpointQuery // The context provides: // - Read-only access to current service state and endpoint store // - Custom endpoint selector logic from QoS service definition -func (q *QoS) buildEndpointSelectionContext() *EndpointSelectionContext { +func (q *QoS) buildEndpointSelectionContext(requestJournal *requestJournal) *EndpointSelectionContext { return &EndpointSelectionContext{ + logger: q.Logger, + + requestJournal: requestJournal, // Service State (read-only) // Allows the custom QoS service to base the validation/selection of endpoints on current state. // Includes the endpoint store in read-only mode. - *ReadonlyServiceState: q.serviceState, + *ServiceState: q.serviceState, // The endpoint selector logic defined by the custom QoS service defintion. customSelector: q.qosDefinition.EndpointSelector, } diff --git a/qos/framework/request_errors.go b/qos/framework/request_errors.go index b1657a92f..77bb2d2e2 100644 --- a/qos/framework/request_errors.go +++ b/qos/framework/request_errors.go @@ -9,11 +9,15 @@ type requestErrorKind int const ( _ requestErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. requestErrKindInternalErrReadyHTTPBody + requestErrKindInternalProtocolErr requestErrKindJSONRPCParsingErr - requestErrKindJSONRPCInvalidVersion - requestErrKindJSONRPCMissingMethod + requestErrKindJSONRPCValidationErr ) +// TODO_FUTURE(@adshmh): Consider making requestError public. +// This would allow custom QoS to reject valid JSONRPC requests. +// e.g. reject a JSONRPC request with an unsupported method. +// type requestError struct { // Captures the kind of error the request encountered. // e.g. error parsing HTTP payload into a JSONRPC request. @@ -43,6 +47,7 @@ func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { // - Update requestContext: add ReportProtocolError to pass the error to the requestCtx.journal.request object. // // Protocol-level error: e.g. endpoint timeout has occurred. +// No endpoint responses are reported to the QoS. // This is an internal error, causing a valid request to fail. // The exact error is not known here: see the TODO_TECHDEBT above. func buildRequestErrorForInternalErrProtocolErr(requestID jsonrpc.ID) *requestError { @@ -63,22 +68,11 @@ func buildRequestErrorForParseError(err error) *requestError { } } -func buildRequestErrorJSONRPCErrInvalidVersion(requestID jsonrpc.ID, version jsonrpc.Version) *requestError { - err := fmt.Errorf("invalid version in JSONRPC request: %s", version) - - return &requestError{ - errorKind: requestErrKindJSONRPCInvalidVersion, - errorDetails: err.Error(), - // Create JSONRPC error response for parse failure - jsonrpcErrorResponse: newJSONRPCErrResponseInvalidVersion(err, requestID), - } -} - -func buildRequestErrorJSONRPCErrMissingMethod(requestID jsonrpc.Request) *requestError { +func buildRequestErrorJSONRPCValidationError(requestID jsonrpc.ID, validationErr error) *requestError { return &requestError{ - errorKind: requestErrKindJSONRPCMissingMethod, - errorDetails: "No method specified by the JSONRPC request", + errorKind: requestErrKindJSONRPCValidationErr, + errorDetails: fmt.Sprintf("JSONRPC request failed validation: %s", validationErr.Error()), // Create JSONRPC error response for parse failure - jsonrpcErrorResponse: newJSONRPCErrResponseMissingMethod(requestID), + jsonrpcErrorResponse: newJSONRPCErrResponseJSONRPCRequestValidationError(requestID, validationErr), } } diff --git a/qos/framework/request_journal.go b/qos/framework/request_journal.go index 74f007af5..264f09ce3 100644 --- a/qos/framework/request_journal.go +++ b/qos/framework/request_journal.go @@ -3,7 +3,9 @@ package framework import ( "github.com/pokt-network/poktroll/pkg/polylog" - qosobservations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" ) // TODO_IN_THIS_PR: verify the EmptyResponse and NoResponse scenarios: @@ -70,6 +72,11 @@ func (rj *requestJournal) reportEndpointQueryResult(endpointQueryResult *Endpoin rj.endpointQueryResults = append(rj.endpointQueryResults, endpointQueryResult) } +// Example: setting protocol-level error, i.e. no endpoint responses were received. +func (rj *requestJournal) setRequestError(requestErr *requestError) { + rj.request.Error = requestErr +} + func (rj *requestJournal) getServicePayload() protocol.Payload { // This should never happen. // A non-nil requestErr indicates the request failed to parse/validate. @@ -137,3 +144,12 @@ func (rj *requestJournal) getJSONRPCRequestMethod() jsonrpc.Method { return request.Method } + +func (rj *requestJournal) getJSONRPCRequestID() jsonrpc.ID { + request := rj.jsonrpcRequest + if request == nil { + return jsonrpc.ID{} + } + + return request.ID +} diff --git a/qos/framework/state.go b/qos/framework/state.go index a93890929..486e419d1 100644 --- a/qos/framework/state.go +++ b/qos/framework/state.go @@ -25,11 +25,9 @@ type ServiceState struct { // stateParameters parameters map[string]*StateParameter - endpointStore *endpointStore -} - -func (s *ServiceState) GetEndpoint(endpointAddr protocol.EndpointAddr) (Endpoint, bool) { - return s.endpointStore.getEndpoint(endpointAddr) + // endpoint store maintained by the service state. + // declared embedded to allow direct access to the store methods, e.g. getEndpoint + *endpointStore } func (s *ServiceState) GetStrParam(paramName string) (string, bool) { @@ -70,14 +68,15 @@ func (s *ServiceState) GetConsensusParam(paramName string) (map[string]int, bool } // Returns the stored Endpoint structs matching the endpoint queries. -func (s *serviceState) updateStoredEndpoints(endpointQueryResults []*EndpointQueryResult) []*Endpoint { +func (s *ServiceState) updateStoredEndpoints(endpointQueryResults []*EndpointQueryResult) []*Endpoint { s.mu.Lock() defer s.mu.Unlock() return s.endpointStore.updateStoredEndpoints(endpointQueryResults) } -func (s *ServiceState) updateParameters(updates StateParameterUpdateSet) error { +// TODO_IN_THIS_PR: copy the supplied parameter values to prevent reference leaks. +func (s *ServiceState) updateParameters(updates *StateParameterUpdateSet) error { s.mu.Lock() defer s.mu.Unlock() diff --git a/qos/framework/state_update.go b/qos/framework/state_update.go index 52bbef6e6..6b8dab5ed 100644 --- a/qos/framework/state_update.go +++ b/qos/framework/state_update.go @@ -4,3 +4,11 @@ package framework type StateParameterUpdateSet struct { Updates map[string]*StateParameter } + +func (spu *StateParameterUpdateSet) Set(paramName string, param *StateParameter) { + if spu.Updates == nil { + spu.Updates = make(map[string]*StateParameter) + } + + spu.Updates[paramName] = param +} diff --git a/qos/jsonrpc/response_error.go b/qos/jsonrpc/response_error.go index bd2d7cf3f..8b01c2348 100644 --- a/qos/jsonrpc/response_error.go +++ b/qos/jsonrpc/response_error.go @@ -8,8 +8,9 @@ type ResponseError struct { Code int `json:"code"` // A String providing a short description of the error. Message string `json:"message"` + // TODO_MVP(@adshmh): support more concrete data types as needed. // A Primitive or Structured value that contains additional information about the error. // This may be omitted. // The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.). - Data any `json:"data,omitempty"` + Data map[string]string `json:"data,omitempty"` } From 3c3b500406ecbdd1ed551bf556e665e3955e5357 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 30 Apr 2025 16:46:01 -0400 Subject: [PATCH 23/26] Fix syntax errors --- .../qos/framework/request_journal.pb.go | 40 +++++++++---------- .../path/qos/framework/request_journal.proto | 2 +- qos/framework/endpoint_query_result.go | 1 - .../endpoint_query_result_defaults.go | 14 +++---- qos/framework/endpoint_sanction_defaults.go | 4 +- qos/framework/endpoint_store.go | 10 ++--- qos/framework/jsonrpc_errors.go | 24 ++++++----- qos/framework/observations.go | 20 ++++++---- qos/framework/observations_jsonrpc.go | 6 ++- qos/jsonrpc/response.go | 2 +- qos/jsonrpc/response_error.go | 2 +- 11 files changed, 69 insertions(+), 56 deletions(-) diff --git a/observation/qos/framework/request_journal.pb.go b/observation/qos/framework/request_journal.pb.go index 62df1b271..3091aa271 100644 --- a/observation/qos/framework/request_journal.pb.go +++ b/observation/qos/framework/request_journal.pb.go @@ -35,7 +35,7 @@ type RequestJournal struct { // e.g. if the QoS service does not support the JSONRPC request's method. RequestError *RequestError `protobuf:"bytes,3,opt,name=request_error,json=requestError,proto3,oneof" json:"request_error,omitempty"` // Observations from endpoint(s) - EndpointQueyResultObservations []*EndpointQueryResult `protobuf:"bytes,4,rep,name=endpoint_quey_result_observations,json=endpointQueyResultObservations,proto3" json:"endpoint_quey_result_observations,omitempty"` + EndpointQueryResultObservations []*EndpointQueryResult `protobuf:"bytes,4,rep,name=endpoint_query_result_observations,json=endpointQueryResultObservations,proto3" json:"endpoint_query_result_observations,omitempty"` } func (x *RequestJournal) Reset() { @@ -89,9 +89,9 @@ func (x *RequestJournal) GetRequestError() *RequestError { return nil } -func (x *RequestJournal) GetEndpointQueyResultObservations() []*EndpointQueryResult { +func (x *RequestJournal) GetEndpointQueryResultObservations() []*EndpointQueryResult { if x != nil { - return x.EndpointQueyResultObservations + return x.EndpointQueryResultObservations } return nil } @@ -110,7 +110,7 @@ var file_path_qos_framework_request_journal_proto_rawDesc = []byte{ 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xeb, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, + 0x74, 0x6f, 0x22, 0xed, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, @@ -123,21 +123,21 @@ var file_path_qos_framework_request_journal_proto_rawDesc = []byte{ 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x72, 0x0a, 0x21, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x6f, - 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, - 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1e, 0x65, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x4f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6a, - 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x10, - 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, - 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, - 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x74, 0x0a, 0x22, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, + 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1f, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x12, 0x0a, 0x10, + 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, + 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -162,7 +162,7 @@ var file_path_qos_framework_request_journal_proto_goTypes = []any{ var file_path_qos_framework_request_journal_proto_depIdxs = []int32{ 1, // 0: path.qos.framework.RequestJournal.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest 2, // 1: path.qos.framework.RequestJournal.request_error:type_name -> path.qos.framework.RequestError - 3, // 2: path.qos.framework.RequestJournal.endpoint_quey_result_observations:type_name -> path.qos.framework.EndpointQueryResult + 3, // 2: path.qos.framework.RequestJournal.endpoint_query_result_observations:type_name -> path.qos.framework.EndpointQueryResult 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name diff --git a/proto/path/qos/framework/request_journal.proto b/proto/path/qos/framework/request_journal.proto index 0b40e0698..de51d7970 100644 --- a/proto/path/qos/framework/request_journal.proto +++ b/proto/path/qos/framework/request_journal.proto @@ -21,5 +21,5 @@ message RequestJournal { optional RequestError request_error = 3; // Observations from endpoint(s) - repeated EndpointQueryResult endpoint_quey_result_observations = 4; + repeated EndpointQueryResult endpoint_query_result_observations = 4; } diff --git a/qos/framework/endpoint_query_result.go b/qos/framework/endpoint_query_result.go index b03eb6ac6..b51de576e 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/framework/endpoint_query_result.go @@ -54,7 +54,6 @@ type EndpointQueryResult struct { // Only set if the endpoint's returned payload could be parsed into a JSONRPC response. parsedJSONRPCResponse *jsonrpc.Response - // The set of values/attributes extracted from the endpoint query and the endpoint's parsed JSONRPC response. // e.g. for a Solana `getEpochInfo` request, the custom service could derive two endpoint attributes as follows: // - "BlockHeight": 0x1234 diff --git a/qos/framework/endpoint_query_result_defaults.go b/qos/framework/endpoint_query_result_defaults.go index 23fa821c7..8d55a2022 100644 --- a/qos/framework/endpoint_query_result_defaults.go +++ b/qos/framework/endpoint_query_result_defaults.go @@ -45,7 +45,7 @@ func buildResultForEmptyResponse(endpointQueryResult *EndpointQueryResult) *Endp } // Set a generic response. - endpointQueryResult.Response = newErrResponseEmptyEndpointResponse(endpointQueryResult.getJSONRPCRequestID()) + endpointQueryResult.parsedJSONRPCResponse = newErrResponseEmptyEndpointResponse(endpointQueryResult.getJSONRPCRequestID()) // Set the endpoint error endpointQueryResult.EndpointError = endpointError @@ -58,13 +58,13 @@ func buildResultForErrorUnmarshalingEndpointReturnedData( parseError error, ) *EndpointQueryResult { endpointError := &EndpointError { - ErrorKind: EndpointErrKindUnmarshaling, + ErrorKind: EndpointErrKindParseErr, Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), - RecommendedSanction: getRecommendedSanction(EndpointErrKindUnmarshaling, parseError), + RecommendedSanction: getRecommendedSanction(EndpointErrKindParseErr, parseError), } // Set a generic response. - endpointQueryResult.Response = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) + endpointQueryResult.parsedJSONRPCResponse = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) // Set the endpoint error endpointQueryResult.EndpointError = endpointError @@ -77,13 +77,13 @@ func buildResultForErrorValidatingEndpointResponse( parseError error, ) *EndpointQueryResult { endpointError := &EndpointError { - ErrorKind: EndpointErrKindUnmarshaling, + ErrorKind: EndpointErrKindValidationErr, Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), - RecommendedSanction: getRecommendedSanction(EndpointErrKindUnmarshaling, parseError), + RecommendedSanction: getRecommendedSanction(EndpointErrKindValidationErr, parseError), } // Set a generic response. - endpointQueryResult.Response = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) + endpointQueryResult.parsedJSONRPCResponse = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) // Set the endpoint error endpointQueryResult.EndpointError = endpointError diff --git a/qos/framework/endpoint_sanction_defaults.go b/qos/framework/endpoint_sanction_defaults.go index a93d7e351..59b21cba1 100644 --- a/qos/framework/endpoint_sanction_defaults.go +++ b/qos/framework/endpoint_sanction_defaults.go @@ -20,9 +20,9 @@ const ( func getRecommendedSanction(endpointErrKind EndpointErrorKind, err error) *Sanction { switch endpointErrKind { - case EndpointDataErrorKindEmptyPayload: + case EndpointErrKindEmptyPayload: return newSanctionForEmptyResponse() - case EndpointDataErrorKindUnmarshaling: + case EndpointErrKindParseErr: return newSanctionForUnmarshalingError(err) default: return nil diff --git a/qos/framework/endpoint_store.go b/qos/framework/endpoint_store.go index 83467bfae..69e5101a4 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/framework/endpoint_store.go @@ -23,10 +23,10 @@ func (es *endpointStore) updateStoredEndpoints(endpointQueryResults []*EndpointQ groupedEndpointResults := groupResultsByEndpointAddr(endpointQueryResults) // Track the updated endpoints - var updatedEndpoints []Endpoint + var updatedEndpoints []*Endpoint // Loop over query results, grouped by endpoint address, and update the corresponding stored endpoint. for endpointAddr, queryResults := range groupedEndpointResults { - endpoint, found := es.endpoints[endpointQueryResult.endpointAddr] + endpoint, found := es.endpoints[endpointAddr] if !found { endpoint = &Endpoint{} } @@ -34,7 +34,7 @@ func (es *endpointStore) updateStoredEndpoints(endpointQueryResults []*EndpointQ endpoint.applyQueryResults(queryResults) // Store the updated endpoint - es.endpoints[endpointQueryResult.endpointAddr] + es.endpoints[endpointAddr] = endpoint // Add the updated endpoint to the list to be returned. updatedEndpoints = append(updatedEndpoints, endpoint) @@ -59,7 +59,7 @@ func (es *endpointStore) storeEndpoint(addr protocol.EndpointAddr, endpoint Endp defer es.endpointsMu.Unlock() if es.endpoints == nil { - es.endpoints = make(map[protocol.EndpointAddr]Endpoint) + es.endpoints = make(map[protocol.EndpointAddr]*Endpoint) } es.endpoints[addr] = &endpoint @@ -71,7 +71,7 @@ func (es *endpointStore) getEndpoint(addr protocol.EndpointAddr) *Endpoint { defer es.endpointsMu.RUnlock() if es.endpoints == nil { - return Endpoint{} + return &Endpoint{} } endpoint := es.endpoints[addr] diff --git a/qos/framework/jsonrpc_errors.go b/qos/framework/jsonrpc_errors.go index 1eb925a64..f9e7d16ed 100644 --- a/qos/framework/jsonrpc_errors.go +++ b/qos/framework/jsonrpc_errors.go @@ -1,6 +1,7 @@ package framework import ( + "encoding/json" "fmt" "github.com/pokt-network/poktroll/pkg/polylog" @@ -25,8 +26,8 @@ const ( // newErrResponseEmptyEndpointResponse creates a JSON-RPC error response for empty endpoint responses: // - Preserves original request ID // - Marks error as retryable for safe client retry -func newErrResponseEmptyEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response { - return jsonrpc.GetErrorResponse( +func newErrResponseEmptyEndpointResponse(requestID jsonrpc.ID) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( requestID, // Use request's original ID if present -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html "Endpoint (data/service node error): Received an empty response. The endpoint will be dropped from the selection pool. Please try again.", // Error Response Message @@ -36,6 +37,8 @@ func newErrResponseEmptyEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response "retryable": "true", }, ) + + return &jsonrpcResp } // newErrResponseParseError creates a JSON-RPC error response for parse errors. @@ -43,8 +46,8 @@ func newErrResponseEmptyEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response // - Preserves the original request ID // - Marks error as retryable // - Indicates the endpoint response couldn't be parsed -func newErrResponseParseError(requestID jsonrpc.ID, parseErr error) jsonrpc.Response { - return jsonrpc.GetErrorResponse( +func newErrResponseParseError(requestID jsonrpc.ID, parseErr error) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( requestID, ErrorCodeParseError, "Failed to parse endpoint response", @@ -53,6 +56,7 @@ func newErrResponseParseError(requestID jsonrpc.ID, parseErr error) jsonrpc.Resp "retryable": "true", }, ) + return &jsonrpcResp } // newErrResponseNoEndpointResponse creates a JSON-RPC error response for the case @@ -61,8 +65,8 @@ func newErrResponseParseError(requestID jsonrpc.ID, parseErr error) jsonrpc.Resp // - Preserves the original request ID // - Marks error as retryable for safe client retry // - Provides actionable message for clients -func newErrResponseNoEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response { - return jsonrpc.GetErrorResponse( +func newErrResponseNoEndpointResponse(requestID jsonrpc.ID) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( requestID, // Use request's original ID if present -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html "Failed to receive any response from endpoints. This could be due to network issues or high load. Please try again.", // Error Response Message @@ -72,6 +76,8 @@ func newErrResponseNoEndpointResponse(requestID jsonrpc.ID) jsonrpc.Response { "retryable": "true", }, ) + + return &jsonrpcResp } // newErrResponseInternalError creates a JSON-RPC error response for internal server errors. @@ -84,9 +90,9 @@ func newErrResponseInternalError(requestID jsonrpc.ID) jsonrpc.Response { return jsonrpc.GetErrorResponse( requestID, -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html - fmt.Sprintf("internal error: %s", err.Error()), // Error Message + "internal error", // Error Message map[string]string{ - "error": err.Error(), + "error_type": "internal", // Custom extension - not part of the official JSON-RPC spec // Marks the error as retryable to allow clients to safely retry their request "retryable": "true", @@ -231,7 +237,7 @@ func marshalErrorResponse( logger polylog.Logger, response jsonrpc.Response, ) ([]byte, error) { - payload, err := response.MarshalJSON() + payload, err := json.Marshal(response) if err != nil { // Create a simple fallback error response as raw JSON fallback := fmt.Sprintf(`{"jsonrpc":"2.0","id":"%v","error":{"code":%d,"message":"%s"}}`, diff --git a/qos/framework/observations.go b/qos/framework/observations.go index af8bb6bf4..087a1ebc6 100644 --- a/qos/framework/observations.go +++ b/qos/framework/observations.go @@ -27,12 +27,12 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // observation for parsed JSONRPC (if parsed) if rj.jsonrpcRequest != nil { - journalObservations.JsonRpcRequest = buildJSONRPCRequestObservation(rj.jsonrpcRequest) + journalObservations.JsonrpcRequest = buildJSONRPCRequestObservation(rj.jsonrpcRequest) } // observation for request error (if set) - if rj.requestErr != nil { - journalObservations.RequestError = buildRequestErrorObservations(rj.requestErr) + if rj.requestError != nil { + journalObservations.RequestError = rj.requestError.buildObservation() } // No endpoint query results. @@ -40,18 +40,22 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // Skip adding endpoint observations. if len(rj.endpointQueryResults) == 0 { return qosobservations.Observations { - ServiceObservations: journalObservations, + ServiceObservations: &qosobservations.Observations_RequestJournal { + RequestJournal: &journalObservations, + }, } } - endpointObservations := make([]*qosobservations.EndpointQueryResultObservation, len(rj.endpointQueryResults)) + endpointObservations := make([]*observations.EndpointQueryResult, len(rj.endpointQueryResults)) for index, endpointQueryResult := range rj.endpointQueryResults { - endpointObservation[index] = endpointQueryResult.buildObservations() + endpointObservations[index] = endpointQueryResult.buildObservation() } journalObservations.EndpointQueryResultObservations = endpointObservations return qosobservations.Observations { - ServiceObservations: journalObservations, + ServiceObservations: &qosobservations.Observations_RequestJournal { + RequestJournal: &journalObservations, + }, } } @@ -61,7 +65,7 @@ func buildRequestJournalFromObservations( observations *qosobservations.Observations, ) (*requestJournal, error) { // hydrate the logger - logger := logger.With("method", "buildRequestJournalFromObservations") + logger = logger.With("method", "buildRequestJournalFromObservations") // sanity check the observations. if observations == nil { diff --git a/qos/framework/observations_jsonrpc.go b/qos/framework/observations_jsonrpc.go index 8c3a6b403..12da0fa5e 100644 --- a/qos/framework/observations_jsonrpc.go +++ b/qos/framework/observations_jsonrpc.go @@ -5,7 +5,11 @@ import ( "github.com/buildwithgrove/path/qos/jsonrpc" ) -func buildJSONRPCRequestObservation(jsonrpcReq jsonrpc.Request) *observations.JsonRpcRequest { +func buildJSONRPCRequestObservation(jsonrpcReq *jsonrpc.Request) *observations.JsonRpcRequest { + if jsonrpcReq == nil { + return nil + } + return &observations.JsonRpcRequest { Id: jsonrpcReq.ID.String(), Method: jsonrpcReq.Method, diff --git a/qos/jsonrpc/response.go b/qos/jsonrpc/response.go index 981f092fc..fba879a94 100644 --- a/qos/jsonrpc/response.go +++ b/qos/jsonrpc/response.go @@ -57,7 +57,7 @@ func (r Response) GetResultAsStr() (string, error) { } // GetErrorResponse is a helper function that builds a JSONRPC Response using the supplied ID and error values. -func GetErrorResponse(id ID, errCode int, errMsg string, errData map[string]string) Response { +func GetErrorResponse(id ID, errCode int64, errMsg string, errData map[string]string) Response { return Response{ ID: id, Version: Version2, diff --git a/qos/jsonrpc/response_error.go b/qos/jsonrpc/response_error.go index 8b01c2348..41afd2766 100644 --- a/qos/jsonrpc/response_error.go +++ b/qos/jsonrpc/response_error.go @@ -5,7 +5,7 @@ package jsonrpc // https://www.jsonrpc.org/specification#error_object type ResponseError struct { // A Number that indicates the error type that occurred. - Code int `json:"code"` + Code int64 `json:"code"` // A String providing a short description of the error. Message string `json:"message"` // TODO_MVP(@adshmh): support more concrete data types as needed. From 56c0535f4dd33ed6d9eb3c065028f7678a368f8c Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 1 May 2025 13:01:28 -0400 Subject: [PATCH 24/26] Refactor observations: requestError --- .../qos/framework/request_journal.pb.go | 97 ++++++++++--------- observation/qos/observations.pb.go | 48 ++++----- .../path/qos/framework/request_journal.proto | 4 +- proto/path/qos/observations.proto | 2 +- qos/framework/endpoint_errors.go | 5 + qos/framework/observations.go | 64 ++++++++---- qos/framework/observations_endpoint_error.go | 31 +----- qos/framework/observations_endpoint_result.go | 14 ++- .../observations_endpoint_sanction.go | 31 ++++++ qos/framework/observations_jsonrpc.go | 4 +- qos/framework/observations_request_error.go | 12 ++- qos/framework/observations_time.go | 22 +++++ 12 files changed, 209 insertions(+), 125 deletions(-) create mode 100644 qos/framework/observations_endpoint_sanction.go create mode 100644 qos/framework/observations_time.go diff --git a/observation/qos/framework/request_journal.pb.go b/observation/qos/framework/request_journal.pb.go index 3091aa271..3c31e5775 100644 --- a/observation/qos/framework/request_journal.pb.go +++ b/observation/qos/framework/request_journal.pb.go @@ -20,8 +20,8 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// RequestJournal is the top-level container for all QoS observations for a request. -type RequestJournal struct { +// RequestJournalObservations is the top-level container for all QoS observations for a request. +type RequestJournalObservations struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -38,20 +38,20 @@ type RequestJournal struct { EndpointQueryResultObservations []*EndpointQueryResult `protobuf:"bytes,4,rep,name=endpoint_query_result_observations,json=endpointQueryResultObservations,proto3" json:"endpoint_query_result_observations,omitempty"` } -func (x *RequestJournal) Reset() { - *x = RequestJournal{} +func (x *RequestJournalObservations) Reset() { + *x = RequestJournalObservations{} mi := &file_path_qos_framework_request_journal_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RequestJournal) String() string { +func (x *RequestJournalObservations) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RequestJournal) ProtoMessage() {} +func (*RequestJournalObservations) ProtoMessage() {} -func (x *RequestJournal) ProtoReflect() protoreflect.Message { +func (x *RequestJournalObservations) ProtoReflect() protoreflect.Message { mi := &file_path_qos_framework_request_journal_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -63,33 +63,33 @@ func (x *RequestJournal) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RequestJournal.ProtoReflect.Descriptor instead. -func (*RequestJournal) Descriptor() ([]byte, []int) { +// Deprecated: Use RequestJournalObservations.ProtoReflect.Descriptor instead. +func (*RequestJournalObservations) Descriptor() ([]byte, []int) { return file_path_qos_framework_request_journal_proto_rawDescGZIP(), []int{0} } -func (x *RequestJournal) GetServiceName() string { +func (x *RequestJournalObservations) GetServiceName() string { if x != nil { return x.ServiceName } return "" } -func (x *RequestJournal) GetJsonrpcRequest() *JsonRpcRequest { +func (x *RequestJournalObservations) GetJsonrpcRequest() *JsonRpcRequest { if x != nil { return x.JsonrpcRequest } return nil } -func (x *RequestJournal) GetRequestError() *RequestError { +func (x *RequestJournalObservations) GetRequestError() *RequestError { if x != nil { return x.RequestError } return nil } -func (x *RequestJournal) GetEndpointQueryResultObservations() []*EndpointQueryResult { +func (x *RequestJournalObservations) GetEndpointQueryResultObservations() []*EndpointQueryResult { if x != nil { return x.EndpointQueryResultObservations } @@ -110,34 +110,35 @@ var file_path_qos_framework_request_journal_proto_rawDesc = []byte{ 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xed, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, - 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, - 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, - 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, - 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x74, 0x0a, 0x22, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, - 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, - 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1f, 0x65, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x12, 0x0a, 0x10, - 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, - 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x6f, 0x22, 0xf9, 0x02, 0x0a, 0x1a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, + 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, + 0x01, 0x52, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, + 0x01, 0x01, 0x12, 0x74, 0x0a, 0x22, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, + 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x1f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x4f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6a, 0x73, 0x6f, + 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x10, 0x0a, 0x0e, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x3a, + 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, + 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, + 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -154,15 +155,15 @@ func file_path_qos_framework_request_journal_proto_rawDescGZIP() []byte { var file_path_qos_framework_request_journal_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_path_qos_framework_request_journal_proto_goTypes = []any{ - (*RequestJournal)(nil), // 0: path.qos.framework.RequestJournal - (*JsonRpcRequest)(nil), // 1: path.qos.framework.JsonRpcRequest - (*RequestError)(nil), // 2: path.qos.framework.RequestError - (*EndpointQueryResult)(nil), // 3: path.qos.framework.EndpointQueryResult + (*RequestJournalObservations)(nil), // 0: path.qos.framework.RequestJournalObservations + (*JsonRpcRequest)(nil), // 1: path.qos.framework.JsonRpcRequest + (*RequestError)(nil), // 2: path.qos.framework.RequestError + (*EndpointQueryResult)(nil), // 3: path.qos.framework.EndpointQueryResult } var file_path_qos_framework_request_journal_proto_depIdxs = []int32{ - 1, // 0: path.qos.framework.RequestJournal.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest - 2, // 1: path.qos.framework.RequestJournal.request_error:type_name -> path.qos.framework.RequestError - 3, // 2: path.qos.framework.RequestJournal.endpoint_query_result_observations:type_name -> path.qos.framework.EndpointQueryResult + 1, // 0: path.qos.framework.RequestJournalObservations.jsonrpc_request:type_name -> path.qos.framework.JsonRpcRequest + 2, // 1: path.qos.framework.RequestJournalObservations.request_error:type_name -> path.qos.framework.RequestError + 3, // 2: path.qos.framework.RequestJournalObservations.endpoint_query_result_observations:type_name -> path.qos.framework.EndpointQueryResult 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name diff --git a/observation/qos/observations.pb.go b/observation/qos/observations.pb.go index 0dae857dc..ebffb12e0 100644 --- a/observation/qos/observations.pb.go +++ b/observation/qos/observations.pb.go @@ -32,7 +32,7 @@ type Observations struct { // // Types that are assignable to ServiceObservations: // - // *Observations_RequestJournal + // *Observations_RequestJournalObservations ServiceObservations isObservations_ServiceObservations `protobuf_oneof:"service_observations"` } @@ -73,9 +73,9 @@ func (m *Observations) GetServiceObservations() isObservations_ServiceObservatio return nil } -func (x *Observations) GetRequestJournal() *framework.RequestJournal { - if x, ok := x.GetServiceObservations().(*Observations_RequestJournal); ok { - return x.RequestJournal +func (x *Observations) GetRequestJournalObservations() *framework.RequestJournalObservations { + if x, ok := x.GetServiceObservations().(*Observations_RequestJournalObservations); ok { + return x.RequestJournalObservations } return nil } @@ -84,12 +84,12 @@ type isObservations_ServiceObservations interface { isObservations_ServiceObservations() } -type Observations_RequestJournal struct { +type Observations_RequestJournalObservations struct { // jsonrpc contains QoS measurements for a JSON-RPC based service request - RequestJournal *framework.RequestJournal `protobuf:"bytes,1,opt,name=request_journal,json=requestJournal,proto3,oneof"` + RequestJournalObservations *framework.RequestJournalObservations `protobuf:"bytes,1,opt,name=request_journal_observations,json=requestJournalObservations,proto3,oneof"` } -func (*Observations_RequestJournal) isObservations_ServiceObservations() {} +func (*Observations_RequestJournalObservations) isObservations_ServiceObservations() {} var File_path_qos_observations_proto protoreflect.FileDescriptor @@ -99,18 +99,20 @@ var file_path_qos_observations_proto_rawDesc = []byte{ 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x1a, 0x28, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x75, 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x4d, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6a, 0x6f, 0x75, - 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x48, 0x00, - 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, - 0x42, 0x16, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, - 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, - 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x22, 0x9a, 0x01, 0x0a, 0x0c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x72, 0x0a, 0x1c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6a, 0x6f, + 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, + 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x1a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x16, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x30, + 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, + 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -127,11 +129,11 @@ func file_path_qos_observations_proto_rawDescGZIP() []byte { var file_path_qos_observations_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_path_qos_observations_proto_goTypes = []any{ - (*Observations)(nil), // 0: path.qos.Observations - (*framework.RequestJournal)(nil), // 1: path.qos.framework.RequestJournal + (*Observations)(nil), // 0: path.qos.Observations + (*framework.RequestJournalObservations)(nil), // 1: path.qos.framework.RequestJournalObservations } var file_path_qos_observations_proto_depIdxs = []int32{ - 1, // 0: path.qos.Observations.request_journal:type_name -> path.qos.framework.RequestJournal + 1, // 0: path.qos.Observations.request_journal_observations:type_name -> path.qos.framework.RequestJournalObservations 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name @@ -145,7 +147,7 @@ func file_path_qos_observations_proto_init() { return } file_path_qos_observations_proto_msgTypes[0].OneofWrappers = []any{ - (*Observations_RequestJournal)(nil), + (*Observations_RequestJournalObservations)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/proto/path/qos/framework/request_journal.proto b/proto/path/qos/framework/request_journal.proto index de51d7970..77f09c06f 100644 --- a/proto/path/qos/framework/request_journal.proto +++ b/proto/path/qos/framework/request_journal.proto @@ -7,8 +7,8 @@ import "path/qos/framework/jsonrpc.proto"; // Import basic JSONRPC messages. import "path/qos/framework/request.proto"; // Import Request-related messages. import "path/qos/framework/endpoint_query_result.proto"; // Import EndpointQueryResult -// RequestJournal is the top-level container for all QoS observations for a request. -message RequestJournal { +// RequestJournalObservations is the top-level container for all QoS observations for a request. +message RequestJournalObservations { // Service identification string service_name = 1; // e.g. EVM, Solana, CometBFT diff --git a/proto/path/qos/observations.proto b/proto/path/qos/observations.proto index 3d1cddb0a..a4981a4d1 100644 --- a/proto/path/qos/observations.proto +++ b/proto/path/qos/observations.proto @@ -11,7 +11,7 @@ message Observations { // service_observations contains QoS measurements specific to the service type oneof service_observations { // jsonrpc contains QoS measurements for a JSON-RPC based service request - path.qos.framework.RequestJournal request_journal = 1; + path.qos.framework.RequestJournalObservations request_journal_observations = 1; // Additional service types can be added here in the future // For example: diff --git a/qos/framework/endpoint_errors.go b/qos/framework/endpoint_errors.go index 62a9cf9eb..25502e9e6 100644 --- a/qos/framework/endpoint_errors.go +++ b/qos/framework/endpoint_errors.go @@ -10,6 +10,11 @@ const ( EndpointErrKindInvalidResult // Payload result doesn't match expected value: e.g. invalid chainID value ) +// TODO_FUTURE(@adshmh): Allow custom QoS implementations to provide a custom JSONRPC response: +// - Add a CustomJSONRPCResponse field to EndpointError struct. +// - Support setting the above by custom QoS implementations. +// - If set, the above should be returned to the client instead of the JSONRPC response parsed from endpoint's returned payload. +// // EndpointError contains error details for endpoint queries. // An EndpointError is always associated with an Endpoint Attribute struct. type EndpointError struct { diff --git a/qos/framework/observations.go b/qos/framework/observations.go index 087a1ebc6..abe66ed6e 100644 --- a/qos/framework/observations.go +++ b/qos/framework/observations.go @@ -1,6 +1,8 @@ package framework import ( + "errors" + "github.com/pokt-network/poktroll/pkg/polylog" qosobservations "github.com/buildwithgrove/path/observation/qos" @@ -21,7 +23,7 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // initialize the observations to include: // - service name // - observations related to the request: - journalObservations := observations.RequestJournal { + journalObservations := observations.RequestJournalObservations { ServiceName: rj.serviceName, } @@ -40,8 +42,8 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // Skip adding endpoint observations. if len(rj.endpointQueryResults) == 0 { return qosobservations.Observations { - ServiceObservations: &qosobservations.Observations_RequestJournal { - RequestJournal: &journalObservations, + ServiceObservations: &qosobservations.Observations_RequestJournalObservations { + RequestJournalObservations: &journalObservations, }, } } @@ -53,8 +55,8 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { journalObservations.EndpointQueryResultObservations = endpointObservations return qosobservations.Observations { - ServiceObservations: &qosobservations.Observations_RequestJournal { - RequestJournal: &journalObservations, + ServiceObservations: &qosobservations.Observations_RequestJournalObservations { + RequestJournalObservations: &journalObservations, }, } } @@ -74,40 +76,66 @@ func buildRequestJournalFromObservations( return nil, errors.New(errMsg) } - reqObs := observations.GetRequestObservation() + journalObs := observations.GetRequestJournalObservations() // No request observation present: skip the processing. - if reqObs == nil { - errMsg := "Received nil request observation: skip the processing." + if journalObs == nil { + errMsg := "Should happen very rarely: received nil journal observation: skip the processing." logger.Warn().Msg(errMsg) return nil, errors.New(errMsg) } // construct the request and any errors from the observations. + reqObs := journalObs.GetJsonrpcRequest() + // nil request observation: no further processing can be done. + if reqObs == nil { + errMsg := "Should happen very rarely: received nil JSONRPC request observation: skip the processing." + logger.Warn().Msg(errMsg) + return nil, errors.New(errMsg) + } + + // Construct the JSONRPC request from the observation. + // Only the JSONRPC request method is required: to build endpoint query result. jsonrpcRequest := buildJSONRPCRequestFromObservation(reqObs) - requestErr := buildRequestErrorFromObservations(reqObs) // Instantiate the request journal. requestJournal := &requestJournal{ logger: logger, jsonrpcRequest: jsonrpcRequest, - requestErr: requestErr, } + + // hydrate the logger with endpoint observations count. + numEndpointObservations := len(journalObs.GetEndpointQueryResultObservations()) + logger = logger.With("num_endpoint_observations", numEndpointObservations) + + requestErrObs := journalObs.GetRequestError() // request had an error: internal, parsing, validation, etc. // no further processing required. - if requestErr != nil { - logger.With("num_endpoint_observations", len(observations.GetEndpointQueryResultObservatios())). - Info().Msg("Request had an error: no endpoint observations expected.") + if requestErrObs != nil { + requestJournal.requestError = buildRequestErrorFromObservation(requestErrObs) + + // hydrate the logger with request error kind. + logger := logger.With("request_error_kind", requestJournal.requestError.errorKind) + + // Request with an error had one or more endpoint observations: this should not happen. + if numEndpointObservations > 0 { + errMsg := "Should happen very rarely: received request with both an error and non-zero observations: skip the processing." + logger.Warn().Msg(errMsg) + return nil, errors.New(errMsg) + } - return requestJournal + logger.Debug().Msg("Successfully parsed the request journal from observations.") + return requestJournal, nil } // reconstruct endpoint query results. - endpointsObs := observations.GetEndpointQueyResultObservations() + endpointsObs := journalObs.GetEndpointQueryResultObservations() + // No endpoint observation present: skip the processing. if endpointsObs == nil || len(endpointsObs) == 0 { - logger.Warn().Msg("Received nil/empty endpoint observation: skip the processing.") - return nil + errMsg := "Should happen very rarely: received nil endpoint observations, but the request has no error set: skip the processing." + logger.Warn().Msg(errMsg) + return nil, errors.New(errMsg) } // Initialize the endpoint query results of the request journal. @@ -116,7 +144,7 @@ func buildRequestJournalFromObservations( // add one endpoint query result per endpoint observation. for index, endpointObs := range endpointsObs { // Construct the query result from the endpoint observation. - endpointQueryResult := extractEndpointQueryResultsFromObservations(endpointObs) + endpointQueryResult := buildEndpointQueryResultFromObservation(logger, endpointObs) // add a reference to the request journal: e.g. for retrieving the JSONRPC request method. endpointQueryResult.requestJournal = requestJournal diff --git a/qos/framework/observations_endpoint_error.go b/qos/framework/observations_endpoint_error.go index 20532a16b..4c92f5f00 100644 --- a/qos/framework/observations_endpoint_error.go +++ b/qos/framework/observations_endpoint_error.go @@ -3,8 +3,6 @@ package framework import ( "time" - "google.golang.org/protobuf/types/known/timestamppb" - observations "github.com/buildwithgrove/path/observation/qos/framework" "github.com/buildwithgrove/path/qos/jsonrpc" ) @@ -16,22 +14,16 @@ func (ee *EndpointError) buildObservation() *observations.EndpointError { } observationError := &observations.EndpointError{ - Description: ee.Description, - ErrorKind: translateToObservationErrorKind(ee.ErrorKind), + ErrorKind: translateToObservationRequestErrorKind(re.errorKind), + ErrorDetails: re.errorDetails, + // The JSONRPC response returned to the client. + JsonRpcResponse: buildJSONRPCResponseObservation(re.jsonrpcResponse), } // Include sanction information if available if ee.RecommendedSanction != nil { - observationError.Sanction = &observations.Sanction{ - Reason: ee.Description, - Type: observations.SanctionType_SANCTION_TYPE_TEMPORARY, - } + observationError.Sanction = ee.RecommendedSanction.buildObservation() - // Convert expiry timestamp if available - if !ee.RecommendedSanction.Duration.IsZero() { - // Convert Go time.Duration to proto timestamp - observationError.Sanction.ExpiryTimestamp = timestampProto(time.Now().Add(ee.RecommendedSanction.Duration)) - } } return observationError @@ -96,17 +88,4 @@ func translateFromObservationErrorKind(errKind observations.EndpointErrorKind) E } } -// Helper functions for proto timestamp conversion -func timestampProto(t time.Time) *timestamppb.Timestamp { - if t.IsZero() { - return nil - } - return timestamppb.New(t) -} -func timeFromProto(ts *timestamppb.Timestamp) time.Time { - if ts == nil { - return time.Time{} - } - return ts.AsTime() -} diff --git a/qos/framework/observations_endpoint_result.go b/qos/framework/observations_endpoint_result.go index a075f207c..12a6fa7ff 100644 --- a/qos/framework/observations_endpoint_result.go +++ b/qos/framework/observations_endpoint_result.go @@ -8,6 +8,14 @@ import ( "github.com/buildwithgrove/path/qos/jsonrpc" ) + +=======>>>>>> + // Convert expiry timestamp if available + if !ee.RecommendedSanction.ExpiryTimestamp.IsZero() { + // Convert Go time.Duration to proto timestamp + observationError.Sanction.ExpiryTimestamp = timestampProto(time.Now().Add(ee.RecommendedSanction.Duration)) + } + // buildObservation converts an EndpointQueryResult to observations.EndpointQueryResult // Used for reporting metrics. func (eqr *EndpointQueryResult) buildObservation() *observations.EndpointQueryResult { @@ -49,11 +57,11 @@ func (eqr *EndpointQueryResult) buildObservation() *observations.EndpointQueryRe return observationResult } -// extractEndpointQueryResultFromObservation extracts a single EndpointQueryResult from an observation's EndpointQueryResult -func extractEndpointQueryResultsFromObservations( +// buildEndpointQueryResultFromObservation builds a single EndpointQueryResult from an observation's EndpointQueryResult +func buildEndpointQueryResultFromObservation( logger polylog.Logger, observation *observations.EndpointQueryResult, -) []*EndpointQueryResult { +) *EndpointQueryResult { // hydrate the logger logger := logger.With("method", "extractEndpointQueryResultFromObservation") diff --git a/qos/framework/observations_endpoint_sanction.go b/qos/framework/observations_endpoint_sanction.go new file mode 100644 index 000000000..66d6f7a91 --- /dev/null +++ b/qos/framework/observations_endpoint_sanction.go @@ -0,0 +1,31 @@ +package framework + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +// TODO_IN_THIS_PR: change all `*Kind*` enum names to `*Type*`. + + Type SanctionType + Reason string + ExpiryTime time.Time // Zero time means permanent + +func (s *Sanction) buildObservation() *observations.Sanction { + return &observations.Sanction{ + Type: translateToObservationSanctionType(s.Type), + Reason: s.Reason, + ExpiryTimestamp: timestampProto(s.ExpiryTime), + } + + +} + +func buildRequestErrorFromObservation(obs *observations.RequestError) *requestError { + return &requestErro { + errorKind: translateFromObservationRequestErrorKind(obs.ErrorKind()), + errorDetails: obs.GetErrorDetails(), + jsonrpcErrorResponse: buildJSONRPCResponseFromObservation(obs.GetJsonRpcResponse()), + } +} + + diff --git a/qos/framework/observations_jsonrpc.go b/qos/framework/observations_jsonrpc.go index 12da0fa5e..8befa6c1b 100644 --- a/qos/framework/observations_jsonrpc.go +++ b/qos/framework/observations_jsonrpc.go @@ -21,7 +21,7 @@ func buildJSONRPCResponseObservation(jsonrpcResp jsonrpc.Response) *observations return nil } -func extractJSONRPCRequestFromObservation( +func buildJSONRPCRequestFromObservation( jsonrpcRequestObs *observations.JsonRpcRequest, ) *jsonrpc.Request { if jsonrpcRequestObs == nil { @@ -34,7 +34,7 @@ func extractJSONRPCRequestFromObservation( } } -func extractJSONRPCResponseFromObservation( +func buildJSONRPCResponseFromObservation( observation *observations.JsonRpcResponse, ) *jsonrpc.Response { if observation == nil { diff --git a/qos/framework/observations_request_error.go b/qos/framework/observations_request_error.go index 913764224..1af300f54 100644 --- a/qos/framework/observations_request_error.go +++ b/qos/framework/observations_request_error.go @@ -6,15 +6,23 @@ import ( func (re *requestError) buildObservation() *observations.RequestError { return &observations.RequestError{ - ErrorKind: translateToRequestError(re.errorKind), + ErrorKind: translateToObservationRequestErrorKind(re.errorKind), ErrorDetails: re.errorDetails, // The JSONRPC response returned to the client. JsonRpcResponse: buildJSONRPCResponseObservation(re.jsonrpcResponse), } } +func buildRequestErrorFromObservation(obs *observations.RequestError) *requestError { + return &requestErro { + errorKind: translateFromObservationRequestErrorKind(obs.ErrorKind()), + errorDetails: obs.GetErrorDetails(), + jsonrpcErrorResponse: buildJSONRPCResponseFromObservation(obs.GetJsonRpcResponse()), + } +} + // DEV_NOTE: you MUST update this function when changing the set of request errors. -func translateToRequestError(errKind requestErrorKind) observations.RequestErrorKind { +func translateToObservationRequestErrorKind(errKind requestErrorKind) observations.RequestErrorKind { switch errKind { case requestErrKindInternalReadyHTTPBody: return observations.RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE diff --git a/qos/framework/observations_time.go b/qos/framework/observations_time.go new file mode 100644 index 000000000..9744ed87f --- /dev/null +++ b/qos/framework/observations_time.go @@ -0,0 +1,22 @@ +package framework + +import ( + "time" + + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Helper functions for proto timestamp conversion +func timestampProto(t time.Time) *timestamppb.Timestamp { + if t.IsZero() { + return nil + } + return timestamppb.New(t) +} + +func timeFromProto(ts *timestamppb.Timestamp) time.Time { + if ts == nil { + return time.Time{} + } + return ts.AsTime() +} From 0996e830f9e9a04ef908e485e721a60aa3fe30c7 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 1 May 2025 20:59:59 -0400 Subject: [PATCH 25/26] Implement the EndpointQualityChecks context --- .../qos/framework/endpoint_query_result.pb.go | 129 ++++++++++-------- observation/qos/framework/request.pb.go | 47 ++++--- .../qos/framework/endpoint_query_result.proto | 11 +- proto/path/qos/framework/request.proto | 4 +- qos/framework/client_http_response.go | 9 +- qos/framework/context_endpoint_checks.go | 59 +++++++- qos/framework/context_endpoint_selection.go | 3 +- qos/framework/context_request.go | 13 +- qos/framework/endpoint.go | 2 +- qos/framework/endpoint_errors.go | 2 +- qos/framework/endpoint_query_result.go | 24 ++-- .../endpoint_query_result_defaults.go | 20 ++- qos/framework/endpoint_sanction.go | 6 +- qos/framework/endpoint_sanction_defaults.go | 8 +- qos/framework/framework.go | 47 +++++-- qos/framework/jsonrpc_errors.go | 72 ++++------ qos/framework/observations.go | 44 ++---- qos/framework/observations_endpoint_error.go | 57 +++----- qos/framework/observations_endpoint_result.go | 78 ++++++----- .../observations_endpoint_sanction.go | 42 ++++-- qos/framework/observations_jsonrpc.go | 12 +- qos/framework/observations_request_error.go | 44 ++++-- qos/framework/qos.go | 74 +++++----- qos/framework/request.go | 15 -- qos/framework/request_errors.go | 21 +-- qos/framework/request_journal.go | 38 +++--- qos/framework/state.go | 2 - 27 files changed, 477 insertions(+), 406 deletions(-) delete mode 100644 qos/framework/request.go diff --git a/observation/qos/framework/endpoint_query_result.pb.go b/observation/qos/framework/endpoint_query_result.pb.go index 20f0d904a..17f56aa2d 100644 --- a/observation/qos/framework/endpoint_query_result.pb.go +++ b/observation/qos/framework/endpoint_query_result.pb.go @@ -31,15 +31,16 @@ type EndpointQueryResult struct { // Address of the endpoint that handled the request EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,omitempty"` - // HTTP response returned to the client. - ClientHttpResponse int32 `protobuf:"varint,2,opt,name=client_http_response,json=clientHttpResponse,proto3" json:"client_http_response,omitempty"` + // JSONRPC response to send to client. + // Parsed from service endpoint's payload. + JsonrpcResponse *JsonRpcResponse `protobuf:"bytes,2,opt,name=jsonrpc_response,json=jsonrpcResponse,proto3,oneof" json:"jsonrpc_response,omitempty"` // The set of values/attributes extracted from the endpoint query // and the endpoint's parsed JSONRPC response StringValues map[string]string `protobuf:"bytes,3,rep,name=string_values,json=stringValues,proto3" json:"string_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` IntValues map[string]int64 `protobuf:"bytes,4,rep,name=int_values,json=intValues,proto3" json:"int_values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` // Captures the queried endpoint's error // Only set if the query result indicates an endpoint error - Error *EndpointError `protobuf:"bytes,5,opt,name=error,proto3,oneof" json:"error,omitempty"` + EndpointError *EndpointError `protobuf:"bytes,5,opt,name=endpoint_error,json=endpointError,proto3,oneof" json:"endpoint_error,omitempty"` // The time at which the query result is expired ExpiryTime *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=expiry_time,json=expiryTime,proto3" json:"expiry_time,omitempty"` } @@ -81,11 +82,11 @@ func (x *EndpointQueryResult) GetEndpointAddr() string { return "" } -func (x *EndpointQueryResult) GetClientHttpResponse() int32 { +func (x *EndpointQueryResult) GetJsonrpcResponse() *JsonRpcResponse { if x != nil { - return x.ClientHttpResponse + return x.JsonrpcResponse } - return 0 + return nil } func (x *EndpointQueryResult) GetStringValues() map[string]string { @@ -102,9 +103,9 @@ func (x *EndpointQueryResult) GetIntValues() map[string]int64 { return nil } -func (x *EndpointQueryResult) GetError() *EndpointError { +func (x *EndpointQueryResult) GetEndpointError() *EndpointError { if x != nil { - return x.Error + return x.EndpointError } return nil } @@ -127,46 +128,53 @@ var file_path_qos_framework_endpoint_query_result_proto_rawDesc = []byte{ 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa7, - 0x04, 0x0a, 0x13, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, - 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, - 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x55, 0x0a, - 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, - 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, - 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x1a, - 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x1a, 0x3c, 0x0a, 0x0e, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08, - 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, - 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, - 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, + 0x70, 0x61, 0x74, 0x68, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, + 0x72, 0x6b, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0xf9, 0x04, 0x0a, 0x13, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x53, 0x0a, + 0x10, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, + 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, + 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0f, + 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x88, + 0x01, 0x01, 0x12, 0x5e, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x70, 0x61, 0x74, 0x68, + 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x12, 0x55, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, + 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, + 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, + 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x0d, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3c, 0x0a, 0x0e, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x70, 0x63, + 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x65, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x3a, 0x5a, 0x38, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -186,19 +194,21 @@ var file_path_qos_framework_endpoint_query_result_proto_goTypes = []any{ (*EndpointQueryResult)(nil), // 0: path.qos.framework.EndpointQueryResult nil, // 1: path.qos.framework.EndpointQueryResult.StringValuesEntry nil, // 2: path.qos.framework.EndpointQueryResult.IntValuesEntry - (*EndpointError)(nil), // 3: path.qos.framework.EndpointError - (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp + (*JsonRpcResponse)(nil), // 3: path.qos.framework.JsonRpcResponse + (*EndpointError)(nil), // 4: path.qos.framework.EndpointError + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp } var file_path_qos_framework_endpoint_query_result_proto_depIdxs = []int32{ - 1, // 0: path.qos.framework.EndpointQueryResult.string_values:type_name -> path.qos.framework.EndpointQueryResult.StringValuesEntry - 2, // 1: path.qos.framework.EndpointQueryResult.int_values:type_name -> path.qos.framework.EndpointQueryResult.IntValuesEntry - 3, // 2: path.qos.framework.EndpointQueryResult.error:type_name -> path.qos.framework.EndpointError - 4, // 3: path.qos.framework.EndpointQueryResult.expiry_time:type_name -> google.protobuf.Timestamp - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 3, // 0: path.qos.framework.EndpointQueryResult.jsonrpc_response:type_name -> path.qos.framework.JsonRpcResponse + 1, // 1: path.qos.framework.EndpointQueryResult.string_values:type_name -> path.qos.framework.EndpointQueryResult.StringValuesEntry + 2, // 2: path.qos.framework.EndpointQueryResult.int_values:type_name -> path.qos.framework.EndpointQueryResult.IntValuesEntry + 4, // 3: path.qos.framework.EndpointQueryResult.endpoint_error:type_name -> path.qos.framework.EndpointError + 5, // 4: path.qos.framework.EndpointQueryResult.expiry_time:type_name -> google.protobuf.Timestamp + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_path_qos_framework_endpoint_query_result_proto_init() } @@ -207,6 +217,7 @@ func file_path_qos_framework_endpoint_query_result_proto_init() { return } file_path_qos_framework_endpoint_error_proto_init() + file_path_qos_framework_jsonrpc_proto_init() file_path_qos_framework_endpoint_query_result_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ diff --git a/observation/qos/framework/request.pb.go b/observation/qos/framework/request.pb.go index 20e4ebd01..bb246f816 100644 --- a/observation/qos/framework/request.pb.go +++ b/observation/qos/framework/request.pb.go @@ -24,11 +24,11 @@ const ( type RequestErrorKind int32 const ( - RequestErrorKind_REQUEST_ERROR_UNSPECIFIED RequestErrorKind = 0 - RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE RequestErrorKind = 1 // Error reading HTTP request body - RequestErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR RequestErrorKind = 2 // Protocol error: e.g. endpoint timed out. - RequestErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE RequestErrorKind = 3 // Error parsing JSON-RPC request - RequestErrorKind_REQUEST_ERROR_JSONRPC_VALIDATION_ERR RequestErrorKind = 4 // JSONRPC request has failed validation: e.g. missing `method` field. + RequestErrorKind_REQUEST_ERROR_UNSPECIFIED RequestErrorKind = 0 + RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE RequestErrorKind = 1 // Error reading HTTP request body + RequestErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR RequestErrorKind = 2 // Protocol error: e.g. endpoint timed out. + RequestErrorKind_REQUEST_ERROR_UNMARSHALING_ERROR RequestErrorKind = 3 // Error parsing JSON-RPC request + RequestErrorKind_REQUEST_ERROR_JSONRPC_VALIDATION_ERROR RequestErrorKind = 4 // JSONRPC request has failed validation: e.g. missing `method` field. ) // Enum value maps for RequestErrorKind. @@ -37,15 +37,15 @@ var ( 0: "REQUEST_ERROR_UNSPECIFIED", 1: "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE", 2: "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR", - 3: "REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE", - 4: "REQUEST_ERROR_JSONRPC_VALIDATION_ERR", + 3: "REQUEST_ERROR_UNMARSHALING_ERROR", + 4: "REQUEST_ERROR_JSONRPC_VALIDATION_ERROR", } RequestErrorKind_value = map[string]int32{ - "REQUEST_ERROR_UNSPECIFIED": 0, - "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE": 1, - "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR": 2, - "REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE": 3, - "REQUEST_ERROR_JSONRPC_VALIDATION_ERR": 4, + "REQUEST_ERROR_UNSPECIFIED": 0, + "REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE": 1, + "REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR": 2, + "REQUEST_ERROR_UNMARSHALING_ERROR": 3, + "REQUEST_ERROR_JSONRPC_VALIDATION_ERROR": 4, } ) @@ -162,7 +162,7 @@ var file_path_qos_framework_request_proto_rawDesc = []byte{ 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x71, 0x6f, 0x73, 0x2e, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x4a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0f, 0x6a, 0x73, 0x6f, 0x6e, 0x52, 0x70, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xe7, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xdc, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x2c, 0x0a, 0x28, 0x52, 0x45, 0x51, 0x55, @@ -171,17 +171,16 @@ var file_path_qos_framework_request_proto_rawDesc = []byte{ 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x29, 0x0a, 0x25, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x02, 0x12, 0x31, 0x0a, 0x2d, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, - 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x10, 0x03, 0x12, 0x28, 0x0a, 0x24, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x52, 0x50, 0x43, 0x5f, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x04, 0x42, 0x3a, - 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, - 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x71, 0x6f, 0x73, - 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x02, 0x12, 0x24, 0x0a, 0x20, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4d, 0x41, 0x52, 0x53, 0x48, 0x41, 0x4c, 0x49, 0x4e, 0x47, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2a, 0x0a, 0x26, 0x52, 0x45, 0x51, 0x55, 0x45, + 0x53, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x52, 0x50, 0x43, + 0x5f, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x04, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x77, 0x69, 0x74, 0x68, 0x67, 0x72, 0x6f, 0x76, 0x65, + 0x2f, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2f, 0x71, 0x6f, 0x73, 0x2f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/path/qos/framework/endpoint_query_result.proto b/proto/path/qos/framework/endpoint_query_result.proto index 48156f42f..e40b5ef3f 100644 --- a/proto/path/qos/framework/endpoint_query_result.proto +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -5,6 +5,7 @@ option go_package = "github.com/buildwithgrove/path/observation/qos/framework"; import "google/protobuf/timestamp.proto"; import "path/qos/framework/endpoint_error.proto"; // Import endpoint error definitions. +import "path/qos/framework/jsonrpc.proto"; // Import jsonrpc definitions. // EndpointQueryResult captures data extracted from an endpoint query. // - Stores one or more string/integer values. @@ -13,11 +14,9 @@ message EndpointQueryResult { // Address of the endpoint that handled the request string endpoint_addr = 1; - // TODO_IN_THIS_PR: REMOVE: it is 200 IFF endpointError == nil, and otherwise determined by the EndpointErr (which contains the JSONRPC response error code) - // - - // HTTP response returned to the client. - int32 client_http_response = 2; + // JSONRPC response to send to client. + // Parsed from service endpoint's payload. + optional JsonRpcResponse jsonrpc_response = 2; // The set of values/attributes extracted from the endpoint query // and the endpoint's parsed JSONRPC response @@ -26,7 +25,7 @@ message EndpointQueryResult { // Captures the queried endpoint's error // Only set if the query result indicates an endpoint error - optional EndpointError error = 5; + optional EndpointError endpoint_error = 5; // The time at which the query result is expired google.protobuf.Timestamp expiry_time = 6; diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto index a58c2bf0a..9488e066b 100644 --- a/proto/path/qos/framework/request.proto +++ b/proto/path/qos/framework/request.proto @@ -10,8 +10,8 @@ enum RequestErrorKind { REQUEST_ERROR_UNSPECIFIED = 0; REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE = 1; // Error reading HTTP request body REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR = 2; // Protocol error: e.g. endpoint timed out. - REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE = 3; // Error parsing JSON-RPC request - REQUEST_ERROR_JSONRPC_VALIDATION_ERR = 4; // JSONRPC request has failed validation: e.g. missing `method` field. + REQUEST_ERROR_UNMARSHALING_ERROR = 3; // Error parsing JSON-RPC request + REQUEST_ERROR_JSONRPC_VALIDATION_ERROR = 4; // JSONRPC request has failed validation: e.g. missing `method` field. } // RequestError contains details about a request error diff --git a/qos/framework/client_http_response.go b/qos/framework/client_http_response.go index 1b8483ca1..212f4b404 100644 --- a/qos/framework/client_http_response.go +++ b/qos/framework/client_http_response.go @@ -2,6 +2,7 @@ package framework import ( "encoding/json" + "errors" "github.com/pokt-network/poktroll/pkg/polylog" @@ -49,13 +50,13 @@ func buildHTTPResponse( ) gateway.HTTPResponse { if jsonrpcResp == nil { logger.Error().Msg("Received nil JSONRPC response") - return buildErrorResponse(logger, jsonrpc.ID{}) + return buildErrorResponse(jsonrpc.ID{}, errors.New("internal error: empy JSONRPC response")) } payload, err := json.Marshal(jsonrpcResp) if err != nil { logger.Error().Err(err).Msg("Failed to marshal JSONRPC response") - return buildErrorResponse(logger, jsonrpcResp.ID) + return buildErrorResponse(jsonrpcResp.ID, err) } return &ClientHTTPResponse{ @@ -66,8 +67,8 @@ func buildHTTPResponse( } // buildErrorResponse creates an internal error HTTP response with the given ID. -func buildErrorResponse(logger polylog.Logger, id jsonrpc.ID) gateway.HTTPResponse { - errResp := newErrResponseInternalError(id) +func buildErrorResponse(id jsonrpc.ID, err error) gateway.HTTPResponse { + errResp := newJSONRPCErrResponseMarshalError(id, err) errPayload, _ := json.Marshal(errResp) return &ClientHTTPResponse{ StatusCode: errResp.GetRecommendedHTTPStatusCode(), diff --git a/qos/framework/context_endpoint_checks.go b/qos/framework/context_endpoint_checks.go index 44ffbd576..97142f9d4 100644 --- a/qos/framework/context_endpoint_checks.go +++ b/qos/framework/context_endpoint_checks.go @@ -1,9 +1,62 @@ package framework -// TODO_IN_THIS_PR: define, in the framework package, a context for adding QoS endpoint quality checks: +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + // - Struct name: QualityCheckContext // - Struct Methods: -// - GetEndpoint(): to skip unnecessary checks. // - GetState(): e.g. for Archival checks // - AddCheck(jsonrpc.Request) -// + +type EndpointQualityChecksContext struct { + logger polylog.Logger + + // Service State (read-only) + // Allows the custom QoS service to base the endpoint checks on current state. + // Includes the endpoint store in read-only mode. + *ServiceState + + // Endpoint loaded from the endpoint store. + endpoint *Endpoint + + // Custom service's Endpoint Checks function + endpointChecksBuilder EndpointQualityChecksBuilder + + endpointChecksToPerform []*jsonrpc.Request +} + +func (ctx *EndpointQualityChecksContext) buildEndpointQualityCheckContexts() []gateway.RequestQoSContext { + jsonrpcRequestsToSend := ctx.endpointChecksBuilder(ctx) + + var qualityCheckContexts []gateway.RequestQoSContext + for _, jsonrpcReq := range jsonrpcRequestsToSend { + // new request context for the quality check + requestCtx := &requestQoSContext{ + logger: ctx.logger, + } + + // initialize the context using the JSONRPC request required for endpoint quality check. + requestCtx.initFromJSONRPCRequest(jsonrpcReq) + + qualityCheckContexts = append(qualityCheckContexts, requestCtx) + } + + return qualityCheckContexts +} + +func (ctx *EndpointQualityChecksContext) GetEndpoint() *Endpoint { + return ctx.endpoint +} + +func (ctx *EndpointQualityChecksContext) AddQualityCheck(jsonrpcReq *jsonrpc.Request) { + ctx.endpointChecksToPerform = append(ctx.endpointChecksToPerform, jsonrpcReq) +} + +// TODO_IN_THIS_PR: pick a more descriptive/fluent API name. +func (ctx *EndpointQualityChecksContext) Build() []*jsonrpc.Request { + return ctx.endpointChecksToPerform +} diff --git a/qos/framework/context_endpoint_selection.go b/qos/framework/context_endpoint_selection.go index de4ef8506..385b08c55 100644 --- a/qos/framework/context_endpoint_selection.go +++ b/qos/framework/context_endpoint_selection.go @@ -8,7 +8,6 @@ import ( "github.com/pokt-network/poktroll/pkg/polylog" "github.com/buildwithgrove/path/protocol" - "github.com/buildwithgrove/path/qos/jsonrpc" ) // TODO_FUTURE(@adshmh): Rank qualified endpoints, e.g. based on latency, for selection. @@ -108,7 +107,7 @@ func (ctx *EndpointSelectionContext) SelectRandomQualifiedEndpoint(endpointFilte } } } - + // Disqualified endpoints have been marked. // return a random qualified endpoint. return ctx.selectRandomEndpoint() diff --git a/qos/framework/context_request.go b/qos/framework/context_request.go index 9a4a60071..280929b47 100644 --- a/qos/framework/context_request.go +++ b/qos/framework/context_request.go @@ -2,10 +2,8 @@ package framework import ( "encoding/json" - "fmt" "io" "net/http" - "time" "github.com/pokt-network/poktroll/pkg/polylog" @@ -121,20 +119,27 @@ func (rc *requestQoSContext) checkForProtocolLevelError() { rc.journal.setRequestError(reqErr) } - func (ctx *requestQoSContext) initFromHTTP(httpReq *http.Request) bool { jsonrpcReq, reqErr := parseHTTPRequest(ctx.logger, httpReq) // initialize the request journal to track all request data and events. ctx.journal = &requestJournal{ jsonrpcRequest: jsonrpcReq, - requestError: reqErr, + requestError: reqErr, } // Only proceed with next steps if there were no errors parsing the HTTP request into a JSONRPC request. return (reqErr == nil) } +// Used for building request contexts for synthetic requests, i.e. endpoint quality checks. +func (ctx *requestQoSContext) initFromJSONRPCRequest(jsonrpcReq *jsonrpc.Request) { + // initialize the request journal to track all request data and events. + ctx.journal = &requestJournal{ + jsonrpcRequest: jsonrpcReq, + } +} + // parseHTTPRequest builds and returns a context for processing the HTTP request: // - Reads and processes the HTTP request // - Parses a JSONRPC request from the HTTP request's payload. diff --git a/qos/framework/endpoint.go b/qos/framework/endpoint.go index 39c600167..a173530b9 100644 --- a/qos/framework/endpoint.go +++ b/qos/framework/endpoint.go @@ -90,7 +90,7 @@ func (e *Endpoint) applyQueryResults(endpointQueryResults []*EndpointQueryResult } // Update the endpoint result matching the JSONRPC request. - e.queryResults[jsonrpcRequestMethod] = endpointQueryResult + e.queryResults[jsonrpcRequestMethod] = endpointQueryResult e.logger.With("jsonrpc_request_method", jsonrpcRequestMethod).Debug().Msg("Updated endpoint with query result.") } diff --git a/qos/framework/endpoint_errors.go b/qos/framework/endpoint_errors.go index 25502e9e6..854dfddb6 100644 --- a/qos/framework/endpoint_errors.go +++ b/qos/framework/endpoint_errors.go @@ -3,7 +3,7 @@ package framework type EndpointErrorKind int const ( - EndpointErrKindUnspecified EndpointErrorKind = iota // matches the "UNSPECIFIED" enum value in proto definitions. + EndpointErrKindUnspecified EndpointErrorKind = iota // matches the "UNSPECIFIED" enum value in proto definitions. EndpointErrKindEmptyPayload // Empty payload from endpoint EndpointErrKindParseErr // Could not parse endpoint payload EndpointErrKindValidationErr // Parsed endpoint payload, in the form of JSONRPC response, failed validation. diff --git a/qos/framework/endpoint_query_result.go b/qos/framework/endpoint_query_result.go index b51de576e..331585fe2 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/framework/endpoint_query_result.go @@ -1,7 +1,6 @@ package framework import ( - "errors" "fmt" "time" @@ -20,7 +19,6 @@ import ( // - epoch := epochInfoResult.GetIntValue("epoch") // - blockHeight := epochInfoResult.GetIntValue("blockHeight") - // TODO_IMPROVE(@adshmh): Enhance EndpointQueryResult to support data types commonly stored for endpoints. // // EndpointQueryResult captures data extracted from an endpoint query. @@ -59,7 +57,7 @@ type EndpointQueryResult struct { // - "BlockHeight": 0x1234 // - "Epoch": 5 StrValues map[string]string - IntValues map[string]int + IntValues map[string]int // The time at which the query result is expired. // Expired results will be ignored, including in: @@ -125,7 +123,7 @@ func (eqr *EndpointQueryResult) Success( // ErrorResult creates an error result with the given message and no sanction. // Returns a self-reference for a fluent API. func (eqr *EndpointQueryResult) Error(description string) *EndpointQueryResult { - eqr.EndpointError = &EndpointError { + eqr.EndpointError = &EndpointError{ ErrorKind: EndpointErrKindInvalidResult, // Description is set by the custom service implementation Description: description, @@ -136,13 +134,13 @@ func (eqr *EndpointQueryResult) Error(description string) *EndpointQueryResult { // SanctionEndpoint creates an error result with a temporary sanction. func (eqr *EndpointQueryResult) SanctionEndpoint(description, reason string, duration time.Duration) *EndpointQueryResult { - eqr.EndpointError = &EndpointError { - ErrorKind: EndpointErrKindInvalidResult, + eqr.EndpointError = &EndpointError{ + ErrorKind: EndpointErrKindInvalidResult, Description: description, RecommendedSanction: &Sanction{ - Type: SanctionTypeTemporary, - Reason: reason, - ExpiryTime: time.Now().Add(duration), + Type: SanctionTypeTemporary, + Reason: reason, + ExpiryTime: time.Now().Add(duration), }, } @@ -151,12 +149,12 @@ func (eqr *EndpointQueryResult) SanctionEndpoint(description, reason string, dur // PermanentSanction creates an error result with a permanent sanction. func (eqr *EndpointQueryResult) PermanentSanction(description, reason string) *EndpointQueryResult { - eqr.EndpointError = &EndpointError { - ErrorKind: EndpointErrKindInvalidResult, + eqr.EndpointError = &EndpointError{ + ErrorKind: EndpointErrKindInvalidResult, Description: description, RecommendedSanction: &Sanction{ - Type: SanctionTypePermanent, - Reason: reason, + Type: SanctionTypePermanent, + Reason: reason, }, } diff --git a/qos/framework/endpoint_query_result_defaults.go b/qos/framework/endpoint_query_result_defaults.go index 8d55a2022..849d528a6 100644 --- a/qos/framework/endpoint_query_result_defaults.go +++ b/qos/framework/endpoint_query_result_defaults.go @@ -2,12 +2,10 @@ package framework import ( "fmt" - "time" - - "github.com/buildwithgrove/path/qos/jsonrpc" ) // TODO_IN_THIS_PR: reword/rename the method and the comment. +// // defaultResultBuilder is applied by the endpointCallProcessor on endpoint responses not matching any of the JSONRPC methods specified by the custom service QoS. // It builds an EndpointQueryResult to track JSONRPC requests/responses not utilized by the custom QoS service for updating the service state or endpoint selection. // Used in generating observations for: @@ -37,8 +35,8 @@ func defaultResultBuilder(ctx *EndpointQueryResultContext) *EndpointQueryResult // // buildResultForEmptyResponse handles the case when an endpoint returned an empty response. func buildResultForEmptyResponse(endpointQueryResult *EndpointQueryResult) *EndpointQueryResult { - endpointError := &EndpointError { - ErrorKind: EndpointErrKindEmptyPayload, + endpointError := &EndpointError{ + ErrorKind: EndpointErrKindEmptyPayload, Description: "endpoint returned an empty response", // Set the recommended sanction based on the error RecommendedSanction: getRecommendedSanction(EndpointErrKindEmptyPayload, nil), @@ -57,9 +55,9 @@ func buildResultForErrorUnmarshalingEndpointReturnedData( endpointQueryResult *EndpointQueryResult, parseError error, ) *EndpointQueryResult { - endpointError := &EndpointError { - ErrorKind: EndpointErrKindParseErr, - Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), + endpointError := &EndpointError{ + ErrorKind: EndpointErrKindParseErr, + Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), RecommendedSanction: getRecommendedSanction(EndpointErrKindParseErr, parseError), } @@ -76,9 +74,9 @@ func buildResultForErrorValidatingEndpointResponse( endpointQueryResult *EndpointQueryResult, parseError error, ) *EndpointQueryResult { - endpointError := &EndpointError { - ErrorKind: EndpointErrKindValidationErr, - Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), + endpointError := &EndpointError{ + ErrorKind: EndpointErrKindValidationErr, + Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), RecommendedSanction: getRecommendedSanction(EndpointErrKindValidationErr, parseError), } diff --git a/qos/framework/endpoint_sanction.go b/qos/framework/endpoint_sanction.go index c6a6df31c..89dccb401 100644 --- a/qos/framework/endpoint_sanction.go +++ b/qos/framework/endpoint_sanction.go @@ -11,9 +11,9 @@ import ( type SanctionType int const ( - _ SanctionType = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. - SanctionTypeTemporary // Time-limited exclusion - SanctionTypePermanent // Permanent exclusion + SanctionTypeUnspecified SanctionType = iota // matches the "UNSPECIFIED" enum value in proto definitions. + SanctionTypeTemporary // Time-limited exclusion + SanctionTypePermanent // Permanent exclusion ) // Sanction represents a recommendation to limit endpoint usage. diff --git a/qos/framework/endpoint_sanction_defaults.go b/qos/framework/endpoint_sanction_defaults.go index 59b21cba1..1e27107d8 100644 --- a/qos/framework/endpoint_sanction_defaults.go +++ b/qos/framework/endpoint_sanction_defaults.go @@ -32,8 +32,8 @@ func getRecommendedSanction(endpointErrKind EndpointErrorKind, err error) *Sanct // newSanctionForEmptyResponse returns the default sanction for empty responses. func newSanctionForEmptyResponse() *Sanction { return &Sanction{ - Type: SanctionTypeTemporary, - Reason: "Empty response from the endpoint", + Type: SanctionTypeTemporary, + Reason: "Empty response from the endpoint", ExpiryTime: time.Now().Add(DefaultEmptyResponseSanctionDuration), } } @@ -41,8 +41,8 @@ func newSanctionForEmptyResponse() *Sanction { // newSanctionForUnmarshalingError returns the default sanction for parse errors. func newSanctionForUnmarshalingError(err error) *Sanction { return &Sanction{ - Type: SanctionTypeTemporary, - Reason: fmt.Sprintf("Endpoint payload failed to parse into JSONRPC response: %s", err.Error()), + Type: SanctionTypeTemporary, + Reason: fmt.Sprintf("Endpoint payload failed to parse into JSONRPC response: %s", err.Error()), ExpiryTime: time.Now().Add(DefaultParseErrorSanctionDuration), } } diff --git a/qos/framework/framework.go b/qos/framework/framework.go index 96021c078..4fcc77aed 100644 --- a/qos/framework/framework.go +++ b/qos/framework/framework.go @@ -14,6 +14,7 @@ import ( "github.com/pokt-network/poktroll/pkg/polylog" "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" ) // TODO_MVP(@adshmh): Allow custom QoS services to supply custom request validation logic. @@ -34,14 +35,17 @@ type QoSDefinition struct { // e.g. "ETH" ServiceName string + // Constructs JSONRPC requests to assess endpoint eligibility to handle service requests. + EndpointQualityChecksBuilder + // ResultBuilders maps JSONRPC methods to custom result processing logic - ResultBuilders map[string]EndpointQueryResultBuilder + ResultBuilders map[jsonrpc.Method]EndpointQueryResultBuilder // StateUpdater defines how endpoint results affect service state - StateUpdater StateUpdater + StateUpdater // EndpointSelector defines custom endpoint selection logic - EndpointSelector EndpointSelector + EndpointSelector // TODO_MVP(@adshmh): Enable custom service QoS implementations to provide a list of allowed methods which the requestValidator needs to enforce: // - Uncomment the following line. @@ -49,12 +53,28 @@ type QoSDefinition struct { // RequestValidator RequestValidator // TODO_FUTURE(@adshmh): Add additional configuration options: - // - InitialState: Starting values for service state // - AllowedMethods: Restrict which JSONRPC methods can be processed // - RequestTimeout: Custom timeout for requests // - RetryPolicy: Configuration for request retries // - StateExpiryPolicy: Rules for expiring state entries - // - MetricsCollection: Settings for performance metrics +} + +// NewQoSService creates a new QoS service with the given definition +func (qd *QoSDefinition) NewQoSService() *QoS { + return &QoS{ + logger: qd.Logger, + // set the definitions required for building different contexts. + qosDefinition: qd, + // initialize the service state and endpoint store. + serviceState: &ServiceState{ + // hydrate the logger with component name: service state. + logger: qd.Logger.With("component", "serviceState"), + // initialize the endpoint store + endpointStore: &endpointStore{ + logger: qd.Logger.With("component", "endpointStore"), + }, + }, + } } // EndpointQueryResultBuilder processes a response and extracts the relevant result. @@ -62,15 +82,18 @@ type QoSDefinition struct { // It processes a valid JSONRPC response for a specific method and extracts the relevant data or error information. // It can potentially mark a JSONRPC response as invalid: // For example if the result field cannot be parsed into a number in an endpoint's response to an `eth_blockNumber` request. -type EndpointQueryResultBuilder func(ctx *EndpointQueryResultContext) *EndpointQueryResult +type EndpointQueryResultBuilder func(*EndpointQueryResultContext) *EndpointQueryResult // StateUpdater updates service state based on endpoint results -type StateUpdater func(ctx *StateUpdateContext) *StateParameterUpdateSet +type StateUpdater func(*StateUpdateContext) *StateParameterUpdateSet // EndpointSelector chooses an endpoint for a request based on service state -type EndpointSelector func(ctx *EndpointSelectionContext) (protocol.EndpointAddr, error) +type EndpointSelector func(*EndpointSelectionContext) (protocol.EndpointAddr, error) -// NewQoSService creates a new QoS service with the given definition -func NewQoSService(def QoSDefinition) *QoS { - // TODO_IN_THIS_PR: instantiate the framrwork using the QoSDeinition struct. -} +// EndpointQualityChecksBuilder constructs JSONRPC requests. +// Used to assess endpoint eligibility to handle service requests. +// Custom QoS service implements this. +// Determines what data points are needed on an endpoint, considering: +// - The existing observations on the endpoint +// - Current service state. +type EndpointQualityChecksBuilder func(*EndpointQualityChecksContext) []*jsonrpc.Request diff --git a/qos/framework/jsonrpc_errors.go b/qos/framework/jsonrpc_errors.go index f9e7d16ed..b48561967 100644 --- a/qos/framework/jsonrpc_errors.go +++ b/qos/framework/jsonrpc_errors.go @@ -80,42 +80,43 @@ func newErrResponseNoEndpointResponse(requestID jsonrpc.ID) *jsonrpc.Response { return &jsonrpcResp } -// newErrResponseInternalError creates a JSON-RPC error response for internal server errors. -// e.g. error reading HTTP request's body. -// This response: -// - Preserves the original request ID if available -// - Marks error as retryable for safe client retry -// - Provides a generic internal error message -func newErrResponseInternalError(requestID jsonrpc.ID) jsonrpc.Response { - return jsonrpc.GetErrorResponse( +func newJSONRPCErrResponseInternalProtocolError(requestID jsonrpc.ID) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( requestID, - -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html - "internal error", // Error Message + ErrorCodeInternalError, + "internal error: protocol-level error has occurred", // Error Message map[string]string{ - "error_type": "internal", + "error_type": "protocol", // Custom extension - not part of the official JSON-RPC spec // Marks the error as retryable to allow clients to safely retry their request "retryable": "true", }, ) + + return &jsonrpcResp } -func newJSONRPCErrResponseInternalProtocolError(requestID jsonrpc.ID) jsonrpc.Response { - return jsonrpc.GetErrorResponse( - requestID, - -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html - "internal error: protocol-level error has occurred", // Error Message +// newJSONRPCErrResponseInternalReadError creates a JSON-RPC error response for HTTP request read errors. +// This response: +// - Uses an empty ID since the request couldn't be read +// - Marks error as retryable since it's likely a server issue +// - Provides the specific read error message +func newJSONRPCErrResponseInternalReadError(readErr error) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( + jsonrpc.ID{}, // No ID for read errors + ErrorCodeInternalError, + "Internal server error: failed to read request", map[string]string{ - "error_type": "protocol", - // Custom extension - not part of the official JSON-RPC spec - // Marks the error as retryable to allow clients to safely retry their request + "error": readErr.Error(), "retryable": "true", }, ) + + return &jsonrpcResp } -func newJSONRPCErrResponseJSONRPCRequestValidationError(requestID jsonrpc.ID, validationErr error) jsonrpc.Response { - return jsonrpc.GetErrorResponse( +func newJSONRPCErrResponseJSONRPCRequestValidationError(requestID jsonrpc.ID, validationErr error) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( requestID, -32000, // JSON-RPC standard server error code; https://www.jsonrpc.org/historical/json-rpc-2-0.html fmt.Sprintf("invalid request: %s", validationErr.Error()), // Error Message @@ -126,6 +127,8 @@ func newJSONRPCErrResponseJSONRPCRequestValidationError(requestID jsonrpc.ID, va "retryable": "false", }, ) + + return &jsonrpcResp } // newErrResponseMarshalError creates a JSON-RPC error response for marshaling errors. @@ -133,7 +136,7 @@ func newJSONRPCErrResponseJSONRPCRequestValidationError(requestID jsonrpc.ID, va // - Preserves the original request ID if available // - Marks error as retryable // - Indicates the response couldn't be serialized -func newErrResponseMarshalError(requestID jsonrpc.ID, marshalErr error) jsonrpc.Response { +func newJSONRPCErrResponseMarshalError(requestID jsonrpc.ID, marshalErr error) jsonrpc.Response { return jsonrpc.GetErrorResponse( requestID, ErrorCodeInternalError, @@ -197,30 +200,13 @@ func newErrResponseMissingMethodError(requestID jsonrpc.ID) jsonrpc.Response { ) } -// newErrResponseInternalReadError creates a JSON-RPC error response for HTTP request read errors. -// This response: -// - Uses an empty ID since the request couldn't be read -// - Marks error as retryable since it's likely a server issue -// - Provides the specific read error message -func newErrResponseInternalReadError(readErr error) jsonrpc.Response { - return jsonrpc.GetErrorResponse( - jsonrpc.ID{}, // No ID for read errors - ErrorCodeInternalError, - "Internal server error: failed to read request", - map[string]string{ - "error": readErr.Error(), - "retryable": "true", - }, - ) -} - -// newErrResponseParseRequestError creates a JSON-RPC error response for parse errors. +// newJSONRPCErrResponseParseRequestError creates a JSON-RPC error response for parse errors. // This response: // - Uses an empty ID since we couldn't parse the request to get an ID // - Marks error as non-retryable since it's likely a client issue with the JSONRPC format // - Indicates the request couldn't be parsed -func newErrResponseParseRequestError(parseErr error) jsonrpc.Response { - return jsonrpc.GetErrorResponse( +func newJSONRPCErrResponseParseRequestError(parseErr error) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( jsonrpc.ID{}, // No ID for parse errors ErrorCodeParseError, "Failed to parse JSON-RPC request", @@ -229,6 +215,8 @@ func newErrResponseParseRequestError(parseErr error) jsonrpc.Response { "retryable": "false", }, ) + + return &jsonrpcResp } // marshalErrorResponse marshals a JSONRPC error response to JSON. diff --git a/qos/framework/observations.go b/qos/framework/observations.go index abe66ed6e..2722d73c3 100644 --- a/qos/framework/observations.go +++ b/qos/framework/observations.go @@ -13,17 +13,17 @@ import ( // This includes: // - Successful requests // - Failed requests, due to: -// - internal error: -// - error reading HTTP request's body. -// - any protocol-level error: e.g. endpoint timed out. -// - invalid request +// - internal error: +// - error reading HTTP request's body. +// - any protocol-level error: e.g. endpoint timed out. +// - invalid request // // requestJournal is the top-level struct in the chain of observation generators. func (rj *requestJournal) getObservations() qosobservations.Observations { // initialize the observations to include: // - service name // - observations related to the request: - journalObservations := observations.RequestJournalObservations { + journalObservations := observations.RequestJournalObservations{ ServiceName: rj.serviceName, } @@ -41,8 +41,8 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { // e.g. for invalid requests. // Skip adding endpoint observations. if len(rj.endpointQueryResults) == 0 { - return qosobservations.Observations { - ServiceObservations: &qosobservations.Observations_RequestJournalObservations { + return qosobservations.Observations{ + ServiceObservations: &qosobservations.Observations_RequestJournalObservations{ RequestJournalObservations: &journalObservations, }, } @@ -50,40 +50,21 @@ func (rj *requestJournal) getObservations() qosobservations.Observations { endpointObservations := make([]*observations.EndpointQueryResult, len(rj.endpointQueryResults)) for index, endpointQueryResult := range rj.endpointQueryResults { - endpointObservations[index] = endpointQueryResult.buildObservation() + endpointObservations[index] = endpointQueryResult.buildObservation(rj.logger) } journalObservations.EndpointQueryResultObservations = endpointObservations - return qosobservations.Observations { - ServiceObservations: &qosobservations.Observations_RequestJournalObservations { + return qosobservations.Observations{ + ServiceObservations: &qosobservations.Observations_RequestJournalObservations{ RequestJournalObservations: &journalObservations, }, } } - func buildRequestJournalFromObservations( logger polylog.Logger, - observations *qosobservations.Observations, + journalObs *observations.RequestJournalObservations, ) (*requestJournal, error) { - // hydrate the logger - logger = logger.With("method", "buildRequestJournalFromObservations") - - // sanity check the observations. - if observations == nil { - errMsg := "Received nil observation: skip the processing." - logger.Warn().Msg(errMsg) - return nil, errors.New(errMsg) - } - - journalObs := observations.GetRequestJournalObservations() - // No request observation present: skip the processing. - if journalObs == nil { - errMsg := "Should happen very rarely: received nil journal observation: skip the processing." - logger.Warn().Msg(errMsg) - return nil, errors.New(errMsg) - } - // construct the request and any errors from the observations. reqObs := journalObs.GetJsonrpcRequest() // nil request observation: no further processing can be done. @@ -99,11 +80,10 @@ func buildRequestJournalFromObservations( // Instantiate the request journal. requestJournal := &requestJournal{ - logger: logger, + logger: logger, jsonrpcRequest: jsonrpcRequest, } - // hydrate the logger with endpoint observations count. numEndpointObservations := len(journalObs.GetEndpointQueryResultObservations()) logger = logger.With("num_endpoint_observations", numEndpointObservations) diff --git a/qos/framework/observations_endpoint_error.go b/qos/framework/observations_endpoint_error.go index 4c92f5f00..b419f632a 100644 --- a/qos/framework/observations_endpoint_error.go +++ b/qos/framework/observations_endpoint_error.go @@ -1,63 +1,46 @@ package framework import ( - "time" - observations "github.com/buildwithgrove/path/observation/qos/framework" - "github.com/buildwithgrove/path/qos/jsonrpc" ) // buildObservation converts an EndpointError to an observations.EndpointError func (ee *EndpointError) buildObservation() *observations.EndpointError { - if ee == nil { - return nil - } - - observationError := &observations.EndpointError{ - ErrorKind: translateToObservationRequestErrorKind(re.errorKind), - ErrorDetails: re.errorDetails, - // The JSONRPC response returned to the client. - JsonRpcResponse: buildJSONRPCResponseObservation(re.jsonrpcResponse), + endpointErrorObs := &observations.EndpointError{ + ErrorKind: translateToObservationEndpointErrorKind(ee.ErrorKind), + Description: ee.Description, } // Include sanction information if available if ee.RecommendedSanction != nil { - observationError.Sanction = ee.RecommendedSanction.buildObservation() - + endpointErrorObs.RecommendedSanction = ee.RecommendedSanction.buildObservation() } - return observationError + return endpointErrorObs } -// extractEndpointErrorFromObservation extracts an EndpointError from an observations.EndpointError -func extractEndpointErrorFromObservation(obsError *observations.EndpointError) *EndpointError { - if obsError == nil { - return nil +// buildEndpointErrorFromObservation extracts an EndpointError from an observations.EndpointError +func buildEndpointErrorFromObservation(endpointErrorObs *observations.EndpointError) *EndpointError { + endpointErr := &EndpointError{ + ErrorKind: translateFromObservationEndpointErrorKind(endpointErrorObs.GetErrorKind()), + Description: endpointErrorObs.Description, } - err := &EndpointError{ - Description: obsError.Description, - ErrorKind: translateFromObservationErrorKind(obsError.ErrorKind), + recommendedSanctionObs := endpointErrorObs.GetRecommendedSanction() + // No sanctions: skip the rest of the processing. + if recommendedSanctionObs == nil { + return endpointErr } - // Include sanction information if available - if obsError.Sanction != nil { - err.RecommendedSanction = &Sanction{} + endpointErr.RecommendedSanction = buildSanctionFromObservation(recommendedSanctionObs) - // Convert sanction expiry timestamp to Duration - if obsError.Sanction.ExpiryTimestamp != nil { - sanctionExpiry := timeFromProto(obsError.Sanction.ExpiryTimestamp) - err.RecommendedSanction.Duration = sanctionExpiry.Sub(time.Now()) - } - } - - return err + return endpointErr } // TODO_IN_THIS_PR: verify errorKind conversion to/from proto. // // DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. -func translateToObservationErrorKind(errKind EndpointErrorKind) observations.EndpointErrorKind { +func translateToObservationEndpointErrorKind(errKind EndpointErrorKind) observations.EndpointErrorKind { switch errKind { case EndpointErrKindEmptyPayload: return observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD @@ -73,7 +56,7 @@ func translateToObservationErrorKind(errKind EndpointErrorKind) observations.End } // DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. -func translateFromObservationErrorKind(errKind observations.EndpointErrorKind) EndpointErrorKind { +func translateFromObservationEndpointErrorKind(errKind observations.EndpointErrorKind) EndpointErrorKind { switch errKind { case observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_EMPTY_PAYLOAD: return EndpointErrKindEmptyPayload @@ -84,8 +67,6 @@ func translateFromObservationErrorKind(errKind observations.EndpointErrorKind) E case observations.EndpointErrorKind_ENDPOINT_ERROR_KIND_INVALID_RESULT: return EndpointErrKindInvalidResult default: - return EndpointErrorKindUnspecified + return EndpointErrKindUnspecified } } - - diff --git a/qos/framework/observations_endpoint_result.go b/qos/framework/observations_endpoint_result.go index 12a6fa7ff..e57124a3e 100644 --- a/qos/framework/observations_endpoint_result.go +++ b/qos/framework/observations_endpoint_result.go @@ -4,57 +4,59 @@ import ( "github.com/pokt-network/poktroll/pkg/polylog" observations "github.com/buildwithgrove/path/observation/qos/framework" - "github.com/buildwithgrove/path/protocol" - "github.com/buildwithgrove/path/qos/jsonrpc" ) - -=======>>>>>> - // Convert expiry timestamp if available - if !ee.RecommendedSanction.ExpiryTimestamp.IsZero() { - // Convert Go time.Duration to proto timestamp - observationError.Sanction.ExpiryTimestamp = timestampProto(time.Now().Add(ee.RecommendedSanction.Duration)) - } - // buildObservation converts an EndpointQueryResult to observations.EndpointQueryResult // Used for reporting metrics. -func (eqr *EndpointQueryResult) buildObservation() *observations.EndpointQueryResult { - if eqr == nil { - return nil +func (eqr *EndpointQueryResult) buildObservation(logger polylog.Logger) *observations.EndpointQueryResult { + logger = logger.With("endpoint_addr", eqr.endpointAddr) + + // Create the observation result + obs := &observations.EndpointQueryResult{ + // Store the endpoint address + EndpointAddr: string(eqr.endpointAddr), } - // Create the observation result structure - observationResult := &observations.EndpointQueryResult{ - StringValues: make(map[string]string), - IntValues: make(map[string]int64), + // This should never happen. + // The parsed JSONRPC response is set by the framework, as either: + // - Parsed from the payload returned by the service endpoint. + // - A generic JSONRPC response if the above failed to parse into a JSONRPC response. + if eqr.parsedJSONRPCResponse == nil { + logger.Warn().Msg("Should never happen: EndpointQueryResult has no JSONRPC response set.") + } + + // Set JSONRPC response + if eqr.parsedJSONRPCResponse != nil { + obs.JsonrpcResponse = buildObservationFromJSONRPCResponse(eqr.parsedJSONRPCResponse) } // Copy string values - for key, value := range eqr.StringValues { - observationResult.StringValues[key] = value + if len(eqr.StrValues) > 0 { + obs.StringValues = make(map[string]string) + } + for key, value := range eqr.StrValues { + obs.StringValues[key] = value } // Copy int values + if len(eqr.IntValues) > 0 { + obs.IntValues = make(map[string]int64) + } for key, value := range eqr.IntValues { - observationResult.IntValues[key] = int64(value) + obs.IntValues[key] = int64(value) } // Convert error information if available - if eqr.Error != nil { - observationResult.Error = eqr.Error.buildObservation() + if eqr.EndpointError != nil { + obs.EndpointError = eqr.EndpointError.buildObservation() } // Set expiry time if !eqr.ExpiryTime.IsZero() { - observationResult.ExpiryTime = timestampProto(eqr.ExpiryTime) - } - - // Set HTTP response code if available from client response - if eqr.clientResponse != nil && eqr.clientResponse.HTTPCode != 0 { - observationResult.ClientHttpResponse = int32(eqr.clientResponse.HTTPCode) + obs.ExpiryTime = timestampProto(eqr.ExpiryTime) } - return observationResult + return obs } // buildEndpointQueryResultFromObservation builds a single EndpointQueryResult from an observation's EndpointQueryResult @@ -63,29 +65,33 @@ func buildEndpointQueryResultFromObservation( observation *observations.EndpointQueryResult, ) *EndpointQueryResult { // hydrate the logger - logger := logger.With("method", "extractEndpointQueryResultFromObservation") + logger = logger.With("method", "extractEndpointQueryResultFromObservation") // Create a new result and populate it from the observation result := &EndpointQueryResult{ // Set the result values to be copied from the observations. - StringValues: make(map[string]string), - IntValues: make(map[string]int), - ExpiryTime: timeFromProto(observation.ExpiryTime), + ExpiryTime: timeFromProto(observation.ExpiryTime), } // Copy string values + if len(observation.StringValues) > 0 { + result.StrValues = make(map[string]string) + } for key, value := range observation.StringValues { - result.StringValues[key] = value + result.StrValues[key] = value } // Copy int values + if len(observation.IntValues) > 0 { + result.IntValues = make(map[string]int) + } for key, value := range observation.IntValues { result.IntValues[key] = int(value) } // Convert error information - if endpointErr := observation.GetEndpointError(); endpointError != nil { - result.EndpointError = extractEndpointErrorFromObservation(endpointError) + if endpointErr := observation.GetEndpointError(); endpointErr != nil { + result.EndpointError = buildEndpointErrorFromObservation(endpointErr) } return result diff --git a/qos/framework/observations_endpoint_sanction.go b/qos/framework/observations_endpoint_sanction.go index 66d6f7a91..139ee1ca4 100644 --- a/qos/framework/observations_endpoint_sanction.go +++ b/qos/framework/observations_endpoint_sanction.go @@ -6,26 +6,42 @@ import ( // TODO_IN_THIS_PR: change all `*Kind*` enum names to `*Type*`. - Type SanctionType - Reason string - ExpiryTime time.Time // Zero time means permanent - func (s *Sanction) buildObservation() *observations.Sanction { return &observations.Sanction{ - Type: translateToObservationSanctionType(s.Type), - Reason: s.Reason, + Type: translateToObservationSanctionType(s.Type), + Reason: s.Reason, ExpiryTimestamp: timestampProto(s.ExpiryTime), } - - } -func buildRequestErrorFromObservation(obs *observations.RequestError) *requestError { - return &requestErro { - errorKind: translateFromObservationRequestErrorKind(obs.ErrorKind()), - errorDetails: obs.GetErrorDetails(), - jsonrpcErrorResponse: buildJSONRPCResponseFromObservation(obs.GetJsonRpcResponse()), +func buildSanctionFromObservation(obs *observations.Sanction) *Sanction { + return &Sanction{ + Type: translateFromObservationSanctionType(obs.GetType()), + Reason: obs.GetReason(), + ExpiryTime: timeFromProto(obs.GetExpiryTimestamp()), } } +// DEV_NOTE: you MUST update this function when changing the set of valid sanction types. +func translateToObservationSanctionType(sanctionType SanctionType) observations.SanctionType { + switch sanctionType { + case SanctionTypeTemporary: + return observations.SanctionType_SANCTION_TYPE_TEMPORARY + case SanctionTypePermanent: + return observations.SanctionType_SANCTION_TYPE_PERMANENT + default: + return observations.SanctionType_SANCTION_TYPE_UNSPECIFIED + } +} +// DEV_NOTE: you MUST update this function when changing the set of valid sanction types. +func translateFromObservationSanctionType(sanctionType observations.SanctionType) SanctionType { + switch sanctionType { + case observations.SanctionType_SANCTION_TYPE_TEMPORARY: + return SanctionTypeTemporary + case observations.SanctionType_SANCTION_TYPE_PERMANENT: + return SanctionTypePermanent + default: + return SanctionTypeUnspecified + } +} diff --git a/qos/framework/observations_jsonrpc.go b/qos/framework/observations_jsonrpc.go index 8befa6c1b..153671172 100644 --- a/qos/framework/observations_jsonrpc.go +++ b/qos/framework/observations_jsonrpc.go @@ -10,14 +10,14 @@ func buildJSONRPCRequestObservation(jsonrpcReq *jsonrpc.Request) *observations.J return nil } - return &observations.JsonRpcRequest { - Id: jsonrpcReq.ID.String(), - Method: jsonrpcReq.Method, + return &observations.JsonRpcRequest{ + Id: jsonrpcReq.ID.String(), + Method: string(jsonrpcReq.Method), } } // TODO_IN_THIS_PR: implement. -func buildJSONRPCResponseObservation(jsonrpcResp jsonrpc.Response) *observations.JsonRpcResponse { +func buildObservationFromJSONRPCResponse(jsonrpcResp *jsonrpc.Response) *observations.JsonRpcResponse { return nil } @@ -30,7 +30,7 @@ func buildJSONRPCRequestFromObservation( // The only field required in applying the observations is the request's method. return &jsonrpc.Request{ - Method: jsonrpcRequestObs.GetMethod(), + Method: jsonrpc.Method(jsonrpcRequestObs.GetMethod()), } } @@ -48,7 +48,7 @@ func buildJSONRPCResponseFromObservation( if jsonrpcErr := observation.GetErr(); jsonrpcErr != nil { jsonrpcResp.Error = &jsonrpc.ResponseError{ - Code: jsonrpcErr.GetCode(), + Code: jsonrpcErr.GetCode(), Message: jsonrpcErr.GetMessage(), } } diff --git a/qos/framework/observations_request_error.go b/qos/framework/observations_request_error.go index 1af300f54..da8d811c0 100644 --- a/qos/framework/observations_request_error.go +++ b/qos/framework/observations_request_error.go @@ -9,14 +9,14 @@ func (re *requestError) buildObservation() *observations.RequestError { ErrorKind: translateToObservationRequestErrorKind(re.errorKind), ErrorDetails: re.errorDetails, // The JSONRPC response returned to the client. - JsonRpcResponse: buildJSONRPCResponseObservation(re.jsonrpcResponse), + JsonRpcResponse: buildObservationFromJSONRPCResponse(re.jsonrpcErrorResponse), } } func buildRequestErrorFromObservation(obs *observations.RequestError) *requestError { - return &requestErro { - errorKind: translateFromObservationRequestErrorKind(obs.ErrorKind()), - errorDetails: obs.GetErrorDetails(), + return &requestError{ + errorKind: translateFromObservationRequestErrorKind(obs.GetErrorKind()), + errorDetails: obs.GetErrorDetails(), jsonrpcErrorResponse: buildJSONRPCResponseFromObservation(obs.GetJsonRpcResponse()), } } @@ -24,17 +24,33 @@ func buildRequestErrorFromObservation(obs *observations.RequestError) *requestEr // DEV_NOTE: you MUST update this function when changing the set of request errors. func translateToObservationRequestErrorKind(errKind requestErrorKind) observations.RequestErrorKind { switch errKind { - case requestErrKindInternalReadyHTTPBody: - return observations.RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE + case requestErrKindInternalErrReadyHTTPBody: + return observations.RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE case requestErrKindInternalProtocolError: - return observations.RequestValidationErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR - case requestErrKindJSONRPCParsingErr: - return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNMARSHALING_FAILURE - case requestErrKindJSONRPCInvalidVersion: - return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_INVALID_VERSION - case requestErrKindJSONRPCMissingMethod: - return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_MISSING_METHOD + return observations.RequestErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR + case requestErrKindJSONRPCParsingError: + return observations.RequestErrorKind_REQUEST_ERROR_UNMARSHALING_ERROR + case requestErrKindJSONRPCValidationError: + return observations.RequestErrorKind_REQUEST_ERROR_JSONRPC_VALIDATION_ERROR default: - return observations.RequestValidationErrorKind_REQUEST_ERROR_VALIDATION_UNSPECIFIED + return observations.RequestErrorKind_REQUEST_ERROR_UNSPECIFIED + } +} + +// translateFromObservationRequestErrorKind converts proto enum to Go enum: +// - Maps proto validation error kinds to their local equivalents +// - Handles unknown values with unspecified default +func translateFromObservationRequestErrorKind(errKind observations.RequestErrorKind) requestErrorKind { + switch errKind { + case observations.RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE: + return requestErrKindInternalErrReadyHTTPBody + case observations.RequestErrorKind_REQUEST_ERROR_INTERNAL_PROTOCOL_ERROR: + return requestErrKindInternalProtocolError + case observations.RequestErrorKind_REQUEST_ERROR_UNMARSHALING_ERROR: + return requestErrKindJSONRPCParsingError + case observations.RequestErrorKind_REQUEST_ERROR_JSONRPC_VALIDATION_ERROR: + return requestErrKindJSONRPCValidationError + default: + return requestErrKindUnspecified } } diff --git a/qos/framework/qos.go b/qos/framework/qos.go index a6af7ab1e..6a8c5d713 100644 --- a/qos/framework/qos.go +++ b/qos/framework/qos.go @@ -2,14 +2,15 @@ package framework import ( "context" - "encoding/json" - "io" + "errors" + "fmt" "net/http" "github.com/pokt-network/poktroll/pkg/polylog" - "github.com/buildwithgrove/path/observation/qos" - "github.com/buildwithgrove/path/qos/jsonrpc" + "github.com/buildwithgrove/path/gateway" + qosobservations "github.com/buildwithgrove/path/observation/qos" + "github.com/buildwithgrove/path/protocol" ) // TODO_TECHDEBT(@adshmh): Simplify the qos package by refactoring gateway.QoSContextBuilder. @@ -24,7 +25,7 @@ type QoS struct { serviceState *ServiceState // The definitoin of QoS behavior, supplied by the custom QoS service. - qosDefinition QoSDefinition + qosDefinition *QoSDefinition } // ParseHTTPRequest handles parsing an HTTP request and validating its content @@ -32,9 +33,9 @@ type QoS struct { func (s *QoS) ParseHTTPRequest( _ context.Context, httpReq *http.Request, -) (*requestContext, bool) { +) (*requestQoSContext, bool) { // Context for processing the HTTP request. - requestCtx := &requestContext{ + requestCtx := &requestQoSContext{ logger: s.logger, } @@ -48,34 +49,43 @@ func (s *QoS) ParseHTTPRequest( // func (qos *QoS) ParseWebsocketRequest(_ context.Context) (gateway.RequestQoSContext, bool) func (q *QoS) ApplyObservations(observations *qosobservations.Observations) error { - serviceRequestObservations := observations.GetJsonrpc() + // hydrate the logger + logger := q.logger.With("method", "ApplyObservations") + + // sanity check the observations. + requestJournalObservations := observations.GetRequestJournalObservations() + if requestJournalObservations == nil { + errMsg := "Should never happen: received nil request journal observation: skip the processing." + logger.Warn().Msg(errMsg) + return errors.New(errMsg) + } // Validate the Service Name - if serviceRequestObservations.ServiceName != q.qosDefinition.ServiceName { - return fmt.Errorf("Reported observations mismatch: service name %q, expected %q", serviceRequestObservations.ServiceName, q.qosDefinitions.ServiceName) + if requestJournalObservations.ServiceName != q.qosDefinition.ServiceName { + return fmt.Errorf("Reported observations mismatch: service name %q, expected %q", requestJournalObservations.ServiceName, q.qosDefinition.ServiceName) } // reconstruct the request journal matching the observations. - requestJournal, err := buildRequestJournalFromObservations(q.logger, serviceRequestObservations) + requestJournal, err := buildRequestJournalFromObservations(q.logger, requestJournalObservations) if err != nil { - q.logger.Error().Err(err).Msg("Error building the request journal from observations: skipping the application of observations.") + logger.Error().Err(err).Msg("Error building the request journal from observations: skipping the application of observations.") return err } // update the stored endpoints - updatedEndpoints := s.serviceState.updateStoredEndpoints(requestJournal.endpointQueryResults) + updatedEndpoints := q.serviceState.updateStoredEndpoints(requestJournal.endpointQueryResults) // instantiate a state update context. - stateUpdateCtx := s.buildServiceStateUpdateContext() + stateUpdateCtx := q.buildServiceStateUpdateContext() // update the service state through the context, using stored endpoints. return stateUpdateCtx.updateFromEndpoints(updatedEndpoints) } // Implements gateway.QoSEndpointCheckGenerator interface -func (q *QoS) GetRequiredQualityChecks(endpointAddr protocol.EndpointAddr) []RequestQoSContext { +func (q *QoS) GetRequiredQualityChecks(endpointAddr protocol.EndpointAddr) []gateway.RequestQoSContext { endpointChecksCtx := q.buildEndpointChecksContext(endpointAddr) - return endpointChecksCtx.BuildRequests() + return endpointChecksCtx.buildEndpointQualityCheckContexts() } // buildEndpointQueryResultContext creates a context for processing endpoint queries @@ -87,56 +97,58 @@ func (q *QoS) buildEndpointQueryResultContext(endpointQueryResult *EndpointQuery return &EndpointQueryResultContext{ // Service State (read-only) // Allows the custom QoS service to base the query results on current state if needed. - ServiceState: q.serviceState, + ServiceState: q.serviceState, // Tracks the result of the endpoint query. EndpointQueryResult: endpointQueryResult, // Map of JSONRPC request method to the corresponding query result builders. - jsonrpcMethodResultBuilders: q.qosDefinition.ResultBuilders, + jsonrpcMethodResultBuilders: q.qosDefinition.ResultBuilders, } } -// buildEndpointSelectionContext creates a context for endpoint validation and selection +// buildEndpointSelectionContext creates a context for endpoint validation and selection // The context provides: // - Read-only access to current service state and endpoint store // - Custom endpoint selector logic from QoS service definition func (q *QoS) buildEndpointSelectionContext(requestJournal *requestJournal) *EndpointSelectionContext { return &EndpointSelectionContext{ - logger: q.Logger, + logger: q.logger, requestJournal: requestJournal, // Service State (read-only) // Allows the custom QoS service to base the validation/selection of endpoints on current state. // Includes the endpoint store in read-only mode. - *ServiceState: q.serviceState, + ServiceState: q.serviceState, // The endpoint selector logic defined by the custom QoS service defintion. - customSelector: q.qosDefinition.EndpointSelector, + customSelector: q.qosDefinition.EndpointSelector, } } // TODO_IN_THIS_PR: implement this method. -func (q *QoS) buildEndpointChecksContext(endpointAddr protocol.EndpointAddr) *EndpointChecksContext { +func (q *QoS) buildEndpointChecksContext(endpointAddr protocol.EndpointAddr) *EndpointQualityChecksContext { // Ignore the second return value: an empty endpoint is a valid value when determining the required endpoint checks. - endpoint, _ := q.serviceState.GetEndpoint(endpointAddr) + endpoint := q.serviceState.getEndpoint(endpointAddr) + + return &EndpointQualityChecksContext{ + logger: q.logger.With("context", "endpoint_quality_check_builder"), - return &EndpointChecksContext{ // Service State (read-only) // Allows the custom QoS service to base the endpoint checks on current state. // Includes the endpoint store in read-only mode. - ReadonlyServiceState: q.serviceState, + ServiceState: q.serviceState, // Endpoint loaded from the endpoint store. - Endpoint: endpoint, + endpoint: endpoint, // Custom service's Endpoint Checks function - endpointChecksBuilder: q.qosDefinition.EndpointChecksBuilder, + endpointChecksBuilder: q.qosDefinition.EndpointQualityChecksBuilder, } } // TODO_IN_THIS_PR: implement this method. -func (q *QoS) buildServiceStateUpdateContext() *ServiceStateUpdateContext { - return &ServiceStateUpdateContext { - ServiceState: q.ServiceState, +func (q *QoS) buildServiceStateUpdateContext() *StateUpdateContext { + return &StateUpdateContext{ + ServiceState: q.serviceState, // the custom service's State Updater function. stateUpdater: q.qosDefinition.StateUpdater, } diff --git a/qos/framework/request.go b/qos/framework/request.go deleted file mode 100644 index 7632de04a..000000000 --- a/qos/framework/request.go +++ /dev/null @@ -1,15 +0,0 @@ -package framework - -import ( - "context" - "encoding/json" - "io" - "net/http" - - "github.com/pokt-network/poktroll/pkg/polylog" - - "github.com/buildwithgrove/path/qos/jsonrpc" -) - -type requestDetails struct { -} diff --git a/qos/framework/request_errors.go b/qos/framework/request_errors.go index 77bb2d2e2..4262d8ab2 100644 --- a/qos/framework/request_errors.go +++ b/qos/framework/request_errors.go @@ -1,23 +1,24 @@ package framework import ( + "fmt" + "github.com/buildwithgrove/path/qos/jsonrpc" ) type requestErrorKind int const ( - _ requestErrorKind = iota // skip the 0 value: it matches the "UNSPECIFIED" enum value in proto definitions. + requestErrKindUnspecified requestErrorKind = iota // matches the "UNSPECIFIED" enum value in proto definitions. requestErrKindInternalErrReadyHTTPBody - requestErrKindInternalProtocolErr - requestErrKindJSONRPCParsingErr - requestErrKindJSONRPCValidationErr + requestErrKindInternalProtocolError + requestErrKindJSONRPCParsingError + requestErrKindJSONRPCValidationError ) // TODO_FUTURE(@adshmh): Consider making requestError public. // This would allow custom QoS to reject valid JSONRPC requests. // e.g. reject a JSONRPC request with an unsupported method. -// type requestError struct { // Captures the kind of error the request encountered. // e.g. error parsing HTTP payload into a JSONRPC request. @@ -29,7 +30,7 @@ type requestError struct { // Error response to return if a request parsing error occurred: // - error reading HTTP request's body. // - error parsing the request's payload into a jsonrpc.Request struct. - jsonrpcErrorResponse jsonrpc.Response + jsonrpcErrorResponse *jsonrpc.Response } func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { @@ -52,7 +53,7 @@ func buildRequestErrorForInternalErrHTTPRead(err error) *requestError { // The exact error is not known here: see the TODO_TECHDEBT above. func buildRequestErrorForInternalErrProtocolErr(requestID jsonrpc.ID) *requestError { return &requestError{ - errorKind: requestErrKindInternalErrProtocolError, + errorKind: requestErrKindInternalProtocolError, errorDetails: "error handling the request due to protocol-level error.", // Create JSONRPC error response for protocol error. jsonrpcErrorResponse: newJSONRPCErrResponseInternalProtocolError(requestID), @@ -61,16 +62,16 @@ func buildRequestErrorForInternalErrProtocolErr(requestID jsonrpc.ID) *requestEr func buildRequestErrorForParseError(err error) *requestError { return &requestError{ - errorKind: requestErrKindJSONRPCParsingErr, + errorKind: requestErrKindJSONRPCParsingError, errorDetails: fmt.Sprintf("error parsing HTTP request into JSONRPC: %v", err), // Create JSONRPC error response for parse failure - jsonrpcErrorResponse: newJSONRPCErrResponseParseError(err), + jsonrpcErrorResponse: newJSONRPCErrResponseParseRequestError(err), } } func buildRequestErrorJSONRPCValidationError(requestID jsonrpc.ID, validationErr error) *requestError { return &requestError{ - errorKind: requestErrKindJSONRPCValidationErr, + errorKind: requestErrKindJSONRPCValidationError, errorDetails: fmt.Sprintf("JSONRPC request failed validation: %s", validationErr.Error()), // Create JSONRPC error response for parse failure jsonrpcErrorResponse: newJSONRPCErrResponseJSONRPCRequestValidationError(requestID, validationErr), diff --git a/qos/framework/request_journal.go b/qos/framework/request_journal.go index 264f09ce3..a6c140c59 100644 --- a/qos/framework/request_journal.go +++ b/qos/framework/request_journal.go @@ -1,6 +1,9 @@ package framework import ( + "encoding/json" + "net/http" + "github.com/pokt-network/poktroll/pkg/polylog" "github.com/buildwithgrove/path/gateway" @@ -54,9 +57,10 @@ func (rj *requestJournal) setProtocolLevelError() { func (rj *requestJournal) buildEndpointQueryResult(endpointAddr protocol.EndpointAddr, receivedData []byte) *EndpointQueryResult { return &EndpointQueryResult{ + // request journal reference. + // Used to access the request, e.g. JSONRPC request method. requestJournal: rj, - // JSONRPC request underlying the endpoint query. - request: rj.jsonrpcRequest, + // Address of the queried endpoint. endpointAddr: endpointAddr, // Data received from the endpoint. @@ -74,23 +78,29 @@ func (rj *requestJournal) reportEndpointQueryResult(endpointQueryResult *Endpoin // Example: setting protocol-level error, i.e. no endpoint responses were received. func (rj *requestJournal) setRequestError(requestErr *requestError) { - rj.request.Error = requestErr + rj.requestError = requestErr } func (rj *requestJournal) getServicePayload() protocol.Payload { - // This should never happen. + // Sanity check the request fields. // A non-nil requestErr indicates the request failed to parse/validate. - if rj.requestErr != nil { - rj.logger.With("request_error", js.requestErr).Error().Msg("Error: getServicePayload() called for invalid/failed request. This is a bug.") + if rj.requestError != nil { + rj.logger.With("request_error", rj.requestError).Error().Msg("Should never happen: getServicePayload() called for invalid/failed request.") + return protocol.Payload{} + } + + // JSONRPC request not set on the journal: skip the processing. + if rj.jsonrpcRequest == nil { + rj.logger.Error().Msg("Should never happen: getServicePayload() called with nil JSONRPC request.") return protocol.Payload{} } - // TODO_IN_THIS_PR: update this code - reqBz, err := json.Marshal(*rc.Request) + reqBz, err := json.Marshal(*rj.jsonrpcRequest) if err != nil { // TODO_MVP(@adshmh): find a way to guarantee this never happens, // e.g. by storing the serialized form of the JSONRPC request // at the time of creating the request context. + rj.logger.With("marshal_err", err).Error().Msg("Should never happen: getServicePayload() failed to marshal the JSONRPC request.") return protocol.Payload{} } @@ -118,22 +128,14 @@ func (rj *requestJournal) getHTTPResponse() gateway.HTTPResponse { // - Invalid request: e.g. malformed payload from client. // - Internal error: error reading HTTP request's body // - Internal error: Protocol-level error, e.g. selected endpoint timed out. - if requestErr := rj.requestErr; requestErr != nil { + if requestErr := rj.requestError; requestErr != nil { return buildHTTPResponse(rj.logger, requestErr.jsonrpcErrorResponse) } - // TODO_IN_THIS_PR: verify the implementation here. - // - // - // TODO_IMPROVE(@adshmh): find a refactor: - // Goal: guarantee that valid request -> at least 1 endpoint query. - // Constraint: Such a refactor should keep the requestJournal as a data container. - // // Use the most recently reported endpoint query. // There MUST be an entry if the request has no error set. selectedEndpointQueryResult := rj.endpointQueryResults[len(rj.endpointQueryResults)-1] - jsonrpcResponse := selectedQuery.result.clientJSONRPCResponse - return buildHTTPResponse(rj.Logger, jsonrpcResponse) + return buildHTTPResponse(rj.logger, selectedEndpointQueryResult.parsedJSONRPCResponse) } func (rj *requestJournal) getJSONRPCRequestMethod() jsonrpc.Method { diff --git a/qos/framework/state.go b/qos/framework/state.go index 486e419d1..4a8f0c711 100644 --- a/qos/framework/state.go +++ b/qos/framework/state.go @@ -2,9 +2,7 @@ package framework import ( "sync" - "time" - "github.com/buildwithgrove/path/protocol" "github.com/pokt-network/poktroll/pkg/polylog" ) From bcef0ad19e8ee3265736a57492d72d45ff6befef Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 7 May 2025 13:25:13 -0400 Subject: [PATCH 26/26] WIP commit: adding toolkit to judge --- .../endpoint_result_blocknumber.go | 16 ++++ qos/judge/README.md | 91 +++++++++++++++++++ .../client_http_response.go | 2 +- .../context_endpoint_checks.go | 2 +- .../context_endpoint_result.go | 2 +- .../context_endpoint_selection.go | 2 +- qos/{framework => judge}/context_request.go | 2 +- .../context_state_update.go | 2 +- qos/{framework => judge}/endpoint.go | 2 +- qos/{framework => judge}/endpoint_errors.go | 2 +- .../endpoint_query_result.go | 2 +- .../endpoint_query_result_defaults.go | 2 +- qos/{framework => judge}/endpoint_sanction.go | 2 +- .../endpoint_sanction_defaults.go | 2 +- qos/{framework => judge}/endpoint_store.go | 2 +- qos/{framework => judge}/framework.go | 2 +- qos/{framework => judge}/jsonrpc_errors.go | 2 +- qos/{framework => judge}/observations.go | 2 +- .../observations_endpoint_error.go | 2 +- .../observations_endpoint_result.go | 2 +- .../observations_endpoint_sanction.go | 2 +- .../observations_interpreter.go | 2 +- .../observations_jsonrpc.go | 2 +- .../observations_request_error.go | 2 +- qos/{framework => judge}/observations_time.go | 2 +- qos/{framework => judge}/qos.go | 2 +- qos/{framework => judge}/request_errors.go | 2 +- qos/{framework => judge}/request_journal.go | 2 +- qos/{framework => judge}/state.go | 2 +- qos/{framework => judge}/state_parameter.go | 2 +- qos/{framework => judge}/state_update.go | 2 +- qos/judge/toolkit/probe.go | 8 ++ qos/judge/toolkit/toolkit.go | 47 ++++++++++ 33 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 qos/judge/README.md rename qos/{framework => judge}/client_http_response.go (99%) rename qos/{framework => judge}/context_endpoint_checks.go (99%) rename qos/{framework => judge}/context_endpoint_result.go (99%) rename qos/{framework => judge}/context_endpoint_selection.go (99%) rename qos/{framework => judge}/context_request.go (99%) rename qos/{framework => judge}/context_state_update.go (98%) rename qos/{framework => judge}/endpoint.go (99%) rename qos/{framework => judge}/endpoint_errors.go (98%) rename qos/{framework => judge}/endpoint_query_result.go (99%) rename qos/{framework => judge}/endpoint_query_result_defaults.go (99%) rename qos/{framework => judge}/endpoint_sanction.go (97%) rename qos/{framework => judge}/endpoint_sanction_defaults.go (98%) rename qos/{framework => judge}/endpoint_store.go (99%) rename qos/{framework => judge}/framework.go (99%) rename qos/{framework => judge}/jsonrpc_errors.go (99%) rename qos/{framework => judge}/observations.go (99%) rename qos/{framework => judge}/observations_endpoint_error.go (99%) rename qos/{framework => judge}/observations_endpoint_result.go (99%) rename qos/{framework => judge}/observations_endpoint_sanction.go (98%) rename qos/{framework => judge}/observations_interpreter.go (93%) rename qos/{framework => judge}/observations_jsonrpc.go (98%) rename qos/{framework => judge}/observations_request_error.go (99%) rename qos/{framework => judge}/observations_time.go (95%) rename qos/{framework => judge}/qos.go (99%) rename qos/{framework => judge}/request_errors.go (99%) rename qos/{framework => judge}/request_journal.go (99%) rename qos/{framework => judge}/state.go (99%) rename qos/{framework => judge}/state_parameter.go (98%) rename qos/{framework => judge}/state_update.go (95%) create mode 100644 qos/judge/toolkit/probe.go create mode 100644 qos/judge/toolkit/toolkit.go diff --git a/qos/example_evm/endpoint_result_blocknumber.go b/qos/example_evm/endpoint_result_blocknumber.go index 5d70c7588..942bf81e7 100644 --- a/qos/example_evm/endpoint_result_blocknumber.go +++ b/qos/example_evm/endpoint_result_blocknumber.go @@ -20,6 +20,22 @@ var _ framework.EndpointResultBuilder = responseBuilderBlockNumber // // responseBuilderBlockNumber handles attribute building and sanctioning of endpoints based on the data returned to `eth_blockNumber` requests. func responseBuilderBlockNumber(ctx *framework.EndpointQueryResultContext) *framework.EndpointQueryResult { + // ===> single value from result: eg. eth_blockNumber + result := ctx.BuildIntResult(ethBlockNumber) + switch { + case result.IsJSONRPCError(): + return result.SetError("endpoint returned a JSONRPC error response") + case result.GetValue() <= 0: + return result.SanctionEndpoint(5*time.Minute, "endpoint returned invalid value as block number") + default: + return result + } + + // Complex values: e.g. Solana getEpochInfo: + + + + // TODO_MVP(@adshmh): implement the framework's RequestValidator interface to filter out invalid `eth_blockNumber` requests. // // The endpoint returned an error response: no further processing needed. diff --git a/qos/judge/README.md b/qos/judge/README.md new file mode 100644 index 000000000..b84120613 --- /dev/null +++ b/qos/judge/README.md @@ -0,0 +1,91 @@ +# JUDGE: JSON-RPC Unified Decentralized Gateway Evaluator + +**TLDR:** Build a QoS system in minutes. + +## Quick Start: Fully Functional QoS System in 2 Minutes + +```go +// Create a QoS specification with probes for each method +spec := toolkit.QoSSpec{ + ServiceName: "EVM", + ServiceProbes: map[jsonrpc.Method]toolkit.ServiceProbe{ + // Chain ID must be exactly 0x01 for Ethereum mainnet + "eth_chainId": toolkit.NewServiceProbe( + toolkit.ExactValue("0x01"), + toolkit.WithSanctionOnFail(15 * time.Minute), + ), + + // Block number should be the highest seen (most recent) + "eth_blockNumber": toolkit.NewServiceProbe( + toolkit.AggregatedValue(toolkit.AggregationStrategyMax), + ), + }, +} + +// That's it! Just instantiate and use +qosService := spec.NewQoSService(logger) +``` + +## What You Need + +Building a QoS system with JUDGE takes just three steps: + +1. **Identify key methods** your service needs (e.g., `eth_chainId`, `eth_blockNumber`) + +2. **Choose ready-to-use ServiceProbes** for each method: + - `ExactValue` for responses that must match exactly + - `AggregatedValue` for values that should follow consensus + - Add options like `WithSanctionOnFail(duration)` as needed + +3. **Instantiate your QoS service** and you're done! + +That's it! JUDGE handles all the complexity behind the scenes - request parsing, endpoint management, state tracking, and error handling. + +## What You Get + +A QoS service built with JUDGE provides: + +- **Automatic Endpoint Quality Control**: Bad endpoints are automatically detected and excluded +- **Smart Request Routing**: Requests go to the best available endpoints +- **Service Consistency**: Users get consistent responses despite variable endpoint quality +- **Resilient Service Layer**: Handles edge cases like timeouts and empty responses +- **Rich Monitoring Data**: Generates structured metrics for analytics +- **Zero Maintenance**: Self-healing service that adapts to changing conditions + +## Examples + +See [EXAMPLES.md](EXAMPLES.md) for complete implementations. + +## Behind the Scenes + +JUDGE is a framework for building Quality of Service (QoS) systems for JSON-RPC services. It handles the complex infrastructure so you can focus on service-specific logic. Perfect for decentralized services where endpoint quality varies. + +For more details on the framework architecture, see [ARCHITECTURE.md](ARCHITECTURE.md). + +## Advanced Customization + +For complex scenarios requiring fine-grained control, you can implement the ServiceProbe interface directly. + +JUDGE requires four simple implementations: + +1. **Result Building** + - Extract values from endpoint responses + - Apply sanctions for endpoint failures + +2. **State Update** + - Update service parameters based on endpoint data + - Example: Set consensus block height + +3. **Endpoint Selection** + - Define qualification criteria for endpoints + - Example: Select nodes within sync threshold + +4. **Quality Checks** + - Specify verification requests for endpoints + - Example: Check blockchain sync status + +This gives you complete control over how your QoS system validates, tracks, and selects endpoints while still leveraging JUDGE's powerful infrastructure. + +## License + +[License Information] diff --git a/qos/framework/client_http_response.go b/qos/judge/client_http_response.go similarity index 99% rename from qos/framework/client_http_response.go rename to qos/judge/client_http_response.go index 212f4b404..dd3415fd0 100644 --- a/qos/framework/client_http_response.go +++ b/qos/judge/client_http_response.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "encoding/json" diff --git a/qos/framework/context_endpoint_checks.go b/qos/judge/context_endpoint_checks.go similarity index 99% rename from qos/framework/context_endpoint_checks.go rename to qos/judge/context_endpoint_checks.go index 97142f9d4..8f308ba8d 100644 --- a/qos/framework/context_endpoint_checks.go +++ b/qos/judge/context_endpoint_checks.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "github.com/pokt-network/poktroll/pkg/polylog" diff --git a/qos/framework/context_endpoint_result.go b/qos/judge/context_endpoint_result.go similarity index 99% rename from qos/framework/context_endpoint_result.go rename to qos/judge/context_endpoint_result.go index cf433b7e5..942360e6b 100644 --- a/qos/framework/context_endpoint_result.go +++ b/qos/judge/context_endpoint_result.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "encoding/json" diff --git a/qos/framework/context_endpoint_selection.go b/qos/judge/context_endpoint_selection.go similarity index 99% rename from qos/framework/context_endpoint_selection.go rename to qos/judge/context_endpoint_selection.go index 385b08c55..02670969a 100644 --- a/qos/framework/context_endpoint_selection.go +++ b/qos/judge/context_endpoint_selection.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "errors" diff --git a/qos/framework/context_request.go b/qos/judge/context_request.go similarity index 99% rename from qos/framework/context_request.go rename to qos/judge/context_request.go index 280929b47..302e67ec7 100644 --- a/qos/framework/context_request.go +++ b/qos/judge/context_request.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "encoding/json" diff --git a/qos/framework/context_state_update.go b/qos/judge/context_state_update.go similarity index 98% rename from qos/framework/context_state_update.go rename to qos/judge/context_state_update.go index 4ba465e5f..60d99a004 100644 --- a/qos/framework/context_state_update.go +++ b/qos/judge/context_state_update.go @@ -1,4 +1,4 @@ -package framework +package judge // StateUpdateContext provides context and helper methods for updating service state. type StateUpdateContext struct { diff --git a/qos/framework/endpoint.go b/qos/judge/endpoint.go similarity index 99% rename from qos/framework/endpoint.go rename to qos/judge/endpoint.go index a173530b9..5d22eef2e 100644 --- a/qos/framework/endpoint.go +++ b/qos/judge/endpoint.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "sync" diff --git a/qos/framework/endpoint_errors.go b/qos/judge/endpoint_errors.go similarity index 98% rename from qos/framework/endpoint_errors.go rename to qos/judge/endpoint_errors.go index 854dfddb6..1dff3417c 100644 --- a/qos/framework/endpoint_errors.go +++ b/qos/judge/endpoint_errors.go @@ -1,4 +1,4 @@ -package framework +package judge type EndpointErrorKind int diff --git a/qos/framework/endpoint_query_result.go b/qos/judge/endpoint_query_result.go similarity index 99% rename from qos/framework/endpoint_query_result.go rename to qos/judge/endpoint_query_result.go index 331585fe2..85ce07eea 100644 --- a/qos/framework/endpoint_query_result.go +++ b/qos/judge/endpoint_query_result.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "fmt" diff --git a/qos/framework/endpoint_query_result_defaults.go b/qos/judge/endpoint_query_result_defaults.go similarity index 99% rename from qos/framework/endpoint_query_result_defaults.go rename to qos/judge/endpoint_query_result_defaults.go index 849d528a6..67d3bcbed 100644 --- a/qos/framework/endpoint_query_result_defaults.go +++ b/qos/judge/endpoint_query_result_defaults.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "fmt" diff --git a/qos/framework/endpoint_sanction.go b/qos/judge/endpoint_sanction.go similarity index 97% rename from qos/framework/endpoint_sanction.go rename to qos/judge/endpoint_sanction.go index 89dccb401..f98bbb702 100644 --- a/qos/framework/endpoint_sanction.go +++ b/qos/judge/endpoint_sanction.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "time" diff --git a/qos/framework/endpoint_sanction_defaults.go b/qos/judge/endpoint_sanction_defaults.go similarity index 98% rename from qos/framework/endpoint_sanction_defaults.go rename to qos/judge/endpoint_sanction_defaults.go index 1e27107d8..d4f56d3e3 100644 --- a/qos/framework/endpoint_sanction_defaults.go +++ b/qos/judge/endpoint_sanction_defaults.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "fmt" diff --git a/qos/framework/endpoint_store.go b/qos/judge/endpoint_store.go similarity index 99% rename from qos/framework/endpoint_store.go rename to qos/judge/endpoint_store.go index 69e5101a4..15d3abe11 100644 --- a/qos/framework/endpoint_store.go +++ b/qos/judge/endpoint_store.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "sync" diff --git a/qos/framework/framework.go b/qos/judge/framework.go similarity index 99% rename from qos/framework/framework.go rename to qos/judge/framework.go index 4fcc77aed..8376c1916 100644 --- a/qos/framework/framework.go +++ b/qos/judge/framework.go @@ -8,7 +8,7 @@ // // Users implement the QoSDefinition interface to create custom QoS services that // leverage the framework's request handling, endpoint management, and state tracking. -package framework +package judge import ( "github.com/pokt-network/poktroll/pkg/polylog" diff --git a/qos/framework/jsonrpc_errors.go b/qos/judge/jsonrpc_errors.go similarity index 99% rename from qos/framework/jsonrpc_errors.go rename to qos/judge/jsonrpc_errors.go index b48561967..facae3afc 100644 --- a/qos/framework/jsonrpc_errors.go +++ b/qos/judge/jsonrpc_errors.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "encoding/json" diff --git a/qos/framework/observations.go b/qos/judge/observations.go similarity index 99% rename from qos/framework/observations.go rename to qos/judge/observations.go index 2722d73c3..7096e2beb 100644 --- a/qos/framework/observations.go +++ b/qos/judge/observations.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "errors" diff --git a/qos/framework/observations_endpoint_error.go b/qos/judge/observations_endpoint_error.go similarity index 99% rename from qos/framework/observations_endpoint_error.go rename to qos/judge/observations_endpoint_error.go index b419f632a..6bb2a7591 100644 --- a/qos/framework/observations_endpoint_error.go +++ b/qos/judge/observations_endpoint_error.go @@ -1,4 +1,4 @@ -package framework +package judge import ( observations "github.com/buildwithgrove/path/observation/qos/framework" diff --git a/qos/framework/observations_endpoint_result.go b/qos/judge/observations_endpoint_result.go similarity index 99% rename from qos/framework/observations_endpoint_result.go rename to qos/judge/observations_endpoint_result.go index e57124a3e..d67b33c0e 100644 --- a/qos/framework/observations_endpoint_result.go +++ b/qos/judge/observations_endpoint_result.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "github.com/pokt-network/poktroll/pkg/polylog" diff --git a/qos/framework/observations_endpoint_sanction.go b/qos/judge/observations_endpoint_sanction.go similarity index 98% rename from qos/framework/observations_endpoint_sanction.go rename to qos/judge/observations_endpoint_sanction.go index 139ee1ca4..2df0b3093 100644 --- a/qos/framework/observations_endpoint_sanction.go +++ b/qos/judge/observations_endpoint_sanction.go @@ -1,4 +1,4 @@ -package framework +package judge import ( observations "github.com/buildwithgrove/path/observation/qos/framework" diff --git a/qos/framework/observations_interpreter.go b/qos/judge/observations_interpreter.go similarity index 93% rename from qos/framework/observations_interpreter.go rename to qos/judge/observations_interpreter.go index 76924a7f9..859710cb5 100644 --- a/qos/framework/observations_interpreter.go +++ b/qos/judge/observations_interpreter.go @@ -1,4 +1,4 @@ -package framework +package judge // ObservationsInterpreter is the reference implementation for interpreting observations generated by the framework. // e.g. IsServiceRequestSuccessful() returns true if the service request succeeded. diff --git a/qos/framework/observations_jsonrpc.go b/qos/judge/observations_jsonrpc.go similarity index 98% rename from qos/framework/observations_jsonrpc.go rename to qos/judge/observations_jsonrpc.go index 153671172..6af23ea57 100644 --- a/qos/framework/observations_jsonrpc.go +++ b/qos/judge/observations_jsonrpc.go @@ -1,4 +1,4 @@ -package framework +package judge import ( observations "github.com/buildwithgrove/path/observation/qos/framework" diff --git a/qos/framework/observations_request_error.go b/qos/judge/observations_request_error.go similarity index 99% rename from qos/framework/observations_request_error.go rename to qos/judge/observations_request_error.go index da8d811c0..065eff975 100644 --- a/qos/framework/observations_request_error.go +++ b/qos/judge/observations_request_error.go @@ -1,4 +1,4 @@ -package framework +package judge import ( observations "github.com/buildwithgrove/path/observation/qos/framework" diff --git a/qos/framework/observations_time.go b/qos/judge/observations_time.go similarity index 95% rename from qos/framework/observations_time.go rename to qos/judge/observations_time.go index 9744ed87f..a4cf78be4 100644 --- a/qos/framework/observations_time.go +++ b/qos/judge/observations_time.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "time" diff --git a/qos/framework/qos.go b/qos/judge/qos.go similarity index 99% rename from qos/framework/qos.go rename to qos/judge/qos.go index 6a8c5d713..3c3e29dc4 100644 --- a/qos/framework/qos.go +++ b/qos/judge/qos.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "context" diff --git a/qos/framework/request_errors.go b/qos/judge/request_errors.go similarity index 99% rename from qos/framework/request_errors.go rename to qos/judge/request_errors.go index 4262d8ab2..74f3f2d63 100644 --- a/qos/framework/request_errors.go +++ b/qos/judge/request_errors.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "fmt" diff --git a/qos/framework/request_journal.go b/qos/judge/request_journal.go similarity index 99% rename from qos/framework/request_journal.go rename to qos/judge/request_journal.go index a6c140c59..c2e7da109 100644 --- a/qos/framework/request_journal.go +++ b/qos/judge/request_journal.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "encoding/json" diff --git a/qos/framework/state.go b/qos/judge/state.go similarity index 99% rename from qos/framework/state.go rename to qos/judge/state.go index 4a8f0c711..055e3271d 100644 --- a/qos/framework/state.go +++ b/qos/judge/state.go @@ -1,4 +1,4 @@ -package framework +package judge import ( "sync" diff --git a/qos/framework/state_parameter.go b/qos/judge/state_parameter.go similarity index 98% rename from qos/framework/state_parameter.go rename to qos/judge/state_parameter.go index 002130916..1e8e0426a 100644 --- a/qos/framework/state_parameter.go +++ b/qos/judge/state_parameter.go @@ -1,4 +1,4 @@ -package framework +package judge // StateParameter stores related values for a single QoS service component // Example: archival state: diff --git a/qos/framework/state_update.go b/qos/judge/state_update.go similarity index 95% rename from qos/framework/state_update.go rename to qos/judge/state_update.go index 6b8dab5ed..6056df86a 100644 --- a/qos/framework/state_update.go +++ b/qos/judge/state_update.go @@ -1,4 +1,4 @@ -package framework +package judge // TODO_FUTURE(@adshmh): Support deleting StateParameters by adding a `ToDelete` field type StateParameterUpdateSet struct { diff --git a/qos/judge/toolkit/probe.go b/qos/judge/toolkit/probe.go new file mode 100644 index 000000000..32c3bba62 --- /dev/null +++ b/qos/judge/toolkit/probe.go @@ -0,0 +1,8 @@ +package toolkit + +// TODO_IN_THIS_PR: define the ServiceProbe interface. +type ServiceProbe interface { + +} + + diff --git a/qos/judge/toolkit/toolkit.go b/qos/judge/toolkit/toolkit.go new file mode 100644 index 000000000..a85a00c19 --- /dev/null +++ b/qos/judge/toolkit/toolkit.go @@ -0,0 +1,47 @@ +package toolkit + +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/qos/jsonrpc" + "github.com/buildwithgrove/path/qos/judge" +) + +type QoSSpec struct { + ServiceName string + ServiceProbes map[jsonrpc.Method]ServiceProbe +} + +func (qs *QoSSpec) NewQoSService(logger polylog.Logger) *judge.QoS { + // build judge.QoSDefinition using QoSSpec + qosDefinition := judge.QoSDefinition{ + Logger: logger, + ServiceName: qs.ServiceName, + + // Build the QoS definition components using service probes. + EndpointQualityChecksBuilder: qs.getEndpointQualityChecksBuilder(), + ResultBuilders: qs.getEndpointQueryResultBuilders(), + StateUpdater: qs.getStateUpdater(), + EndpointSelector: qs.getEndpointSelector(), + } + + // Instantiate a new QoS service using the constructed QoS Definition. + return qosDefinition.NewQoSService() +} + +// TODO_IN_THIS_PR: implement. +func (qs *QoSSpec) getEndpointQueryResultBuilders() map[jsonrpc.Method]judge.EndpointQueryResultBuilder { + return nil +} + +func (qs *QoSSpec) getEndpointQualityChecksBuilder() judge.EndpointQualityChecksBuilder { + return nil +} + +func (qs *QoSSpec) getStateUpdater() judge.StateUpdater { + return nil +} + +func (qs *QoSSpec) getEndpointSelector() judge.EndpointSelector { + return nil +}