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/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/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/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_error.pb.go b/observation/qos/framework/endpoint_error.pb.go new file mode 100644 index 000000000..a430edefa --- /dev/null +++ b/observation/qos/framework/endpoint_error.pb.go @@ -0,0 +1,245 @@ +// 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_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_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_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_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, + } +) + +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{} + 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 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_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, 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 ( + 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() + 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..17f56aa2d --- /dev/null +++ b/observation/qos/framework/endpoint_query_result.pb.go @@ -0,0 +1,240 @@ +// 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_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 + + // Address of the endpoint that handled the request + EndpointAddr string `protobuf:"bytes,1,opt,name=endpoint_addr,json=endpointAddr,proto3" json:"endpoint_addr,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 + 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"` +} + +func (x *EndpointQueryResult) Reset() { + *x = EndpointQueryResult{} + 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 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) GetEndpointAddr() string { + if x != nil { + return x.EndpointAddr + } + return "" +} + +func (x *EndpointQueryResult) GetJsonrpcResponse() *JsonRpcResponse { + if x != nil { + return x.JsonrpcResponse + } + return nil +} + +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) GetEndpointError() *EndpointError { + if x != nil { + return x.EndpointError + } + 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, 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 ( + 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 + (*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{ + 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() } +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() + 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{ + 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..c0a2d83ee --- /dev/null +++ b/observation/qos/framework/endpoint_sanction.pb.go @@ -0,0 +1,221 @@ +// 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_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{} + 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 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 + } + 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/framework/jsonrpc.pb.go b/observation/qos/framework/jsonrpc.pb.go new file mode 100644 index 000000000..22fa507ef --- /dev/null +++ b/observation/qos/framework/jsonrpc.pb.go @@ -0,0 +1,285 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc v5.28.3 +// source: path/qos/framework/jsonrpc.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) +) + +// JsonRpcRequest represents essential fields of a JSON-RPC request for observation purposes. +// Reference: https://www.jsonrpc.org/specification#request_object +type JsonRpcRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Client-established identifier. Must be a String, Number, or NULL if present. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Name of the JSON-RPC method being called (e.g., eth_chainId for EVM chains) + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` +} + +func (x *JsonRpcRequest) Reset() { + *x = JsonRpcRequest{} + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JsonRpcRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JsonRpcRequest) ProtoMessage() {} + +func (x *JsonRpcRequest) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_jsonrpc_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 JsonRpcRequest.ProtoReflect.Descriptor instead. +func (*JsonRpcRequest) Descriptor() ([]byte, []int) { + return file_path_qos_framework_jsonrpc_proto_rawDescGZIP(), []int{0} +} + +func (x *JsonRpcRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *JsonRpcRequest) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +// JsonRpcResponse represents essential fields of a JSON-RPC response for observation purposes. +// Reference: https://www.jsonrpc.org/specification#response_object +type JsonRpcResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Must match the id value from the corresponding request + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // JSON-serializable response data + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + // Error details, if the request failed + Err *JsonRpcResponseError `protobuf:"bytes,3,opt,name=err,proto3,oneof" json:"err,omitempty"` +} + +func (x *JsonRpcResponse) Reset() { + *x = JsonRpcResponse{} + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JsonRpcResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JsonRpcResponse) ProtoMessage() {} + +func (x *JsonRpcResponse) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_jsonrpc_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 JsonRpcResponse.ProtoReflect.Descriptor instead. +func (*JsonRpcResponse) Descriptor() ([]byte, []int) { + return file_path_qos_framework_jsonrpc_proto_rawDescGZIP(), []int{1} +} + +func (x *JsonRpcResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *JsonRpcResponse) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +func (x *JsonRpcResponse) GetErr() *JsonRpcResponseError { + if x != nil { + return x.Err + } + return nil +} + +// 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. +type JsonRpcResponseError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Error code indicating the type of failure + Code int64 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + // Human-readable error description + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *JsonRpcResponseError) Reset() { + *x = JsonRpcResponseError{} + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JsonRpcResponseError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JsonRpcResponseError) ProtoMessage() {} + +func (x *JsonRpcResponseError) ProtoReflect() protoreflect.Message { + mi := &file_path_qos_framework_jsonrpc_proto_msgTypes[2] + 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 JsonRpcResponseError.ProtoReflect.Descriptor instead. +func (*JsonRpcResponseError) Descriptor() ([]byte, []int) { + return file_path_qos_framework_jsonrpc_proto_rawDescGZIP(), []int{2} +} + +func (x *JsonRpcResponseError) GetCode() int64 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *JsonRpcResponseError) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +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_framework_jsonrpc_proto_rawDescOnce sync.Once + file_path_qos_framework_jsonrpc_proto_rawDescData = file_path_qos_framework_jsonrpc_proto_rawDesc +) + +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_framework_jsonrpc_proto_rawDescData +} + +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_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 + 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_jsonrpc_proto_init() } +func file_path_qos_framework_jsonrpc_proto_init() { + if File_path_qos_framework_jsonrpc_proto != nil { + return + } + 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_framework_jsonrpc_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + 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_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/request.pb.go b/observation/qos/framework/request.pb.go new file mode 100644 index 000000000..bb246f816 --- /dev/null +++ b/observation/qos/framework/request.pb.go @@ -0,0 +1,240 @@ +// 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.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_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. +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_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_UNMARSHALING_ERROR": 3, + "REQUEST_ERROR_JSONRPC_VALIDATION_ERROR": 4, + } +) + +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 + + // 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"` + // 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() { + *x = RequestError{} + 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 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) GetErrorDetails() string { + if x != nil { + return x.ErrorDetails + } + return "" +} + +func (x *RequestError) GetJsonRpcResponse() *JsonRpcResponse { + if x != nil { + return x.JsonRpcResponse + } + 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, 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, 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, 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, + 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, 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 ( + 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, 1) +var file_path_qos_framework_request_proto_goTypes = []any{ + (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.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 + 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() } +func file_path_qos_framework_request_proto_init() { + if File_path_qos_framework_request_proto != nil { + return + } + file_path_qos_framework_jsonrpc_proto_init() + 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: 1, + 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/framework/request_journal.pb.go b/observation/qos/framework/request_journal.pb.go new file mode 100644 index 000000000..3c31e5775 --- /dev/null +++ b/observation/qos/framework/request_journal.pb.go @@ -0,0 +1,201 @@ +// 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) +) + +// 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 + + // 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) + EndpointQueryResultObservations []*EndpointQueryResult `protobuf:"bytes,4,rep,name=endpoint_query_result_observations,json=endpointQueryResultObservations,proto3" json:"endpoint_query_result_observations,omitempty"` +} + +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 *RequestJournalObservations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestJournalObservations) ProtoMessage() {} + +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)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestJournalObservations.ProtoReflect.Descriptor instead. +func (*RequestJournalObservations) Descriptor() ([]byte, []int) { + return file_path_qos_framework_request_journal_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestJournalObservations) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *RequestJournalObservations) GetJsonrpcRequest() *JsonRpcRequest { + if x != nil { + return x.JsonrpcRequest + } + return nil +} + +func (x *RequestJournalObservations) GetRequestError() *RequestError { + if x != nil { + return x.RequestError + } + return nil +} + +func (x *RequestJournalObservations) GetEndpointQueryResultObservations() []*EndpointQueryResult { + if x != nil { + return x.EndpointQueryResultObservations + } + 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, 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 ( + 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{ + (*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.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 + 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/jsonrpc.pb.go b/observation/qos/jsonrpc.pb.go deleted file mode 100644 index 7cf0ac91a..000000000 --- a/observation/qos/jsonrpc.pb.go +++ /dev/null @@ -1,327 +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/jsonrpc.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) -) - -// JsonRpcRequest represents essential fields of a JSON-RPC request for observation purposes. -// Reference: https://www.jsonrpc.org/specification#request_object -type JsonRpcRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Client-established identifier. Must be a String, Number, or NULL if present. - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // Name of the JSON-RPC method being called (e.g., eth_chainId for EVM chains) - Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` -} - -func (x *JsonRpcRequest) Reset() { - *x = JsonRpcRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_jsonrpc_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *JsonRpcRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JsonRpcRequest) ProtoMessage() {} - -func (x *JsonRpcRequest) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_jsonrpc_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 JsonRpcRequest.ProtoReflect.Descriptor instead. -func (*JsonRpcRequest) Descriptor() ([]byte, []int) { - return file_path_qos_jsonrpc_proto_rawDescGZIP(), []int{0} -} - -func (x *JsonRpcRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *JsonRpcRequest) GetMethod() string { - if x != nil { - return x.Method - } - return "" -} - -// JsonRpcResponse represents essential fields of a JSON-RPC response for observation purposes. -// Reference: https://www.jsonrpc.org/specification#response_object -type JsonRpcResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Must match the id value from the corresponding request - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // JSON-serializable response data - Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` - // Error details, if the request failed - Err *JsonRpcResponseError `protobuf:"bytes,3,opt,name=err,proto3,oneof" json:"err,omitempty"` -} - -func (x *JsonRpcResponse) Reset() { - *x = JsonRpcResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_jsonrpc_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *JsonRpcResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JsonRpcResponse) ProtoMessage() {} - -func (x *JsonRpcResponse) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_jsonrpc_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 JsonRpcResponse.ProtoReflect.Descriptor instead. -func (*JsonRpcResponse) Descriptor() ([]byte, []int) { - return file_path_qos_jsonrpc_proto_rawDescGZIP(), []int{1} -} - -func (x *JsonRpcResponse) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *JsonRpcResponse) GetResult() string { - if x != nil { - return x.Result - } - return "" -} - -func (x *JsonRpcResponse) GetErr() *JsonRpcResponseError { - if x != nil { - return x.Err - } - return nil -} - -// 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. -type JsonRpcResponseError struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Error code indicating the type of failure - Code int64 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` - // Human-readable error description - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` -} - -func (x *JsonRpcResponseError) Reset() { - *x = JsonRpcResponseError{} - if protoimpl.UnsafeEnabled { - mi := &file_path_qos_jsonrpc_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *JsonRpcResponseError) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*JsonRpcResponseError) ProtoMessage() {} - -func (x *JsonRpcResponseError) ProtoReflect() protoreflect.Message { - mi := &file_path_qos_jsonrpc_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 JsonRpcResponseError.ProtoReflect.Descriptor instead. -func (*JsonRpcResponseError) Descriptor() ([]byte, []int) { - return file_path_qos_jsonrpc_proto_rawDescGZIP(), []int{2} -} - -func (x *JsonRpcResponseError) GetCode() int64 { - if x != nil { - return x.Code - } - return 0 -} - -func (x *JsonRpcResponseError) GetMessage() string { - if x != nil { - return x.Message - } - 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_jsonrpc_proto_rawDescOnce sync.Once - file_path_qos_jsonrpc_proto_rawDescData = file_path_qos_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) - }) - return file_path_qos_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_jsonrpc_proto_depIdxs = []int32{ - 2, // 0: path.qos.JsonRpcResponse.err:type_name -> path.qos.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 - 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_jsonrpc_proto_init() } -func file_path_qos_jsonrpc_proto_init() { - if File_path_qos_jsonrpc_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_path_qos_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_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_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_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, - 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, - }.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 -} diff --git a/observation/qos/observations.pb.go b/observation/qos/observations.pb.go index 6013bce88..ebffb12e0 100644 --- a/observation/qos/observations.pb.go +++ b/observation/qos/observations.pb.go @@ -1,12 +1,13 @@ // 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 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,19 +32,15 @@ type Observations struct { // // Types that are assignable to ServiceObservations: // - // *Observations_Solana - // *Observations_Evm - // *Observations_Cometbft + // *Observations_RequestJournalObservations ServiceObservations isObservations_ServiceObservations `protobuf_oneof:"service_observations"` } 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 { @@ -56,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) @@ -78,23 +73,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) GetRequestJournalObservations() *framework.RequestJournalObservations { + if x, ok := x.GetServiceObservations().(*Observations_RequestJournalObservations); ok { + return x.RequestJournalObservations } return nil } @@ -103,50 +84,30 @@ 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_Cometbft struct { - // cometbft contains QoS measurements for a single CometBFT blockchain request - Cometbft *CometBFTRequestObservations `protobuf:"bytes,3,opt,name=cometbft,proto3,oneof"` +type Observations_RequestJournalObservations struct { + // jsonrpc contains QoS measurements for a JSON-RPC based service request + RequestJournalObservations *framework.RequestJournalObservations `protobuf:"bytes,1,opt,name=request_journal_observations,json=requestJournalObservations,proto3,oneof"` } -func (*Observations_Solana) isObservations_ServiceObservations() {} - -func (*Observations_Evm) isObservations_ServiceObservations() {} - -func (*Observations_Cometbft) isObservations_ServiceObservations() {} +func (*Observations_RequestJournalObservations) 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, + 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, 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, @@ -168,20 +129,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.RequestJournalObservations)(nil), // 1: path.qos.framework.RequestJournalObservations } 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.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 + 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,27 +146,8 @@ 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 { - 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_Solana)(nil), - (*Observations_Evm)(nil), - (*Observations_Cometbft)(nil), + (*Observations_RequestJournalObservations)(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/framework/endpoint_error.proto b/proto/path/qos/framework/endpoint_error.proto new file mode 100644 index 000000000..4b7f127d0 --- /dev/null +++ b/proto/path/qos/framework/endpoint_error.proto @@ -0,0 +1,29 @@ +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_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. +message EndpointError { + // 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..e40b5ef3f --- /dev/null +++ b/proto/path/qos/framework/endpoint_query_result.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package path.qos.framework; + +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. +// - Contains error/sanction information on endpoint error. +message EndpointQueryResult { + // Address of the endpoint that handled the request + string endpoint_addr = 1; + + // 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 + map string_values = 3; + map int_values = 4; + + // Captures the queried endpoint's error + // Only set if the query result indicates an endpoint error + 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/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/jsonrpc.proto b/proto/path/qos/framework/jsonrpc.proto similarity index 96% rename from proto/path/qos/jsonrpc.proto rename to proto/path/qos/framework/jsonrpc.proto index 7645060d0..99041b77e 100644 --- a/proto/path/qos/jsonrpc.proto +++ b/proto/path/qos/framework/jsonrpc.proto @@ -1,7 +1,7 @@ syntax = "proto3"; -package path.qos; +package path.qos.framework; -option go_package = "github.com/buildwithgrove/path/observation/qos"; +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 @@ -42,4 +42,4 @@ message JsonRpcResponseError { // Human-readable error description string message = 2; -} \ No newline at end of file +} diff --git a/proto/path/qos/framework/request.proto b/proto/path/qos/framework/request.proto new file mode 100644 index 000000000..9488e066b --- /dev/null +++ b/proto/path/qos/framework/request.proto @@ -0,0 +1,27 @@ +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. + +// 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_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 +message RequestError { + // enumeration of the kind of error the request encountered. + RequestErrorKind error_kind = 1; + + // 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..77f09c06f --- /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 + +// 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 + + // 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_query_result_observations = 4; +} diff --git a/proto/path/qos/observations.proto b/proto/path/qos/observations.proto index f0ad3832d..a4981a4d1 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/request_journal.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.RequestJournalObservations request_journal_observations = 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/example_evm/check.go b/qos/example_evm/check.go new file mode 100644 index 000000000..436a51071 --- /dev/null +++ b/qos/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/example_evm/config.go b/qos/example_evm/config.go new file mode 100644 index 000000000..7ab1513f5 --- /dev/null +++ b/qos/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/example_evm/endpoint_result_blocknumber.go b/qos/example_evm/endpoint_result_blocknumber.go new file mode 100644 index 000000000..942bf81e7 --- /dev/null +++ b/qos/example_evm/endpoint_result_blocknumber.go @@ -0,0 +1,65 @@ +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.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. + 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. + blockNumber, err := ctx.GetResultAsInt() + if err != nil { + 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. + 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/example_evm/endpoint_result_chainid.go b/qos/example_evm/endpoint_result_chainid.go new file mode 100644 index 000000000..43f29f83e --- /dev/null +++ b/qos/example_evm/endpoint_result_chainid.go @@ -0,0 +1,61 @@ +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.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 to an eth_chainId request.") + } + + // 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/example_evm/endpoint_selection.go b/qos/example_evm/endpoint_selection.go new file mode 100644 index 000000000..0e1a80214 --- /dev/null +++ b/qos/example_evm/endpoint_selection.go @@ -0,0 +1,53 @@ +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, + config EVMConfig, +) (protocol.EndpointAddr, error) { + // Fetch latest block number from the service state. + perceivedBlockNumber, _ := ctx.GetIntParam(methodETHBlockNumber) + + // The perceived block number not set yet: return a random endpoint + if perceivedBlockNumber <= 0 { + 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/example_evm/qos.go b/qos/example_evm/qos.go new file mode 100644 index 000000000..35c0e0763 --- /dev/null +++ b/qos/example_evm/qos.go @@ -0,0 +1,68 @@ +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, config EVMQoSServiceConfig) *QoS { + logger = logger.With( + "qos_instance", "evm", + "evm_chain_id", config.GetEVMChainID(), + ) + + // 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(config), + +/* + // 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(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): 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) + } +} diff --git a/qos/example_evm/state_update.go b/qos/example_evm/state_update.go new file mode 100644 index 000000000..bf84caeaf --- /dev/null +++ b/qos/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/example_evm/state_update_archival.go b/qos/example_evm/state_update_archival.go new file mode 100644 index 000000000..3d9aa817b --- /dev/null +++ b/qos/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/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), - } -} diff --git a/qos/jsonrpc/request.go b/qos/jsonrpc/request.go index b26626f2c..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. @@ -12,6 +14,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 +57,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 r.JSONRPC != Version2 { + return fmt.Errorf("%w: invalid version: %s", ErrInvalidRequestInvalidVersion, r.JSONRPC) + } + + // Check method presence + if r.Method == "" { + return ErrInvalidRequestMissingMethod + } + + // Request is valid + return nil +} diff --git a/qos/jsonrpc/response.go b/qos/jsonrpc/response.go index e46418671..fba879a94 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,8 +44,20 @@ func (r Response) GetResultAsBytes() ([]byte, error) { return json.Marshal(r.Result) } +func (r Response) GetResultAsInt() (int, error) { + var intValue int + err := json.Unmarshal(r.Result, &intValue) + return intValue, err +} + +func (r Response) GetResultAsStr() (string, error) { + var strValue string + err := json.Unmarshal(r.Result, &strValue) + 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 { +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 bd2d7cf3f..41afd2766 100644 --- a/qos/jsonrpc/response_error.go +++ b/qos/jsonrpc/response_error.go @@ -5,11 +5,12 @@ 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. // 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"` } 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/judge/client_http_response.go b/qos/judge/client_http_response.go new file mode 100644 index 000000000..dd3415fd0 --- /dev/null +++ b/qos/judge/client_http_response.go @@ -0,0 +1,78 @@ +package judge + +import ( + "encoding/json" + "errors" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// 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(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(jsonrpcResp.ID, err) + } + + 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(id jsonrpc.ID, err error) gateway.HTTPResponse { + errResp := newJSONRPCErrResponseMarshalError(id, err) + errPayload, _ := json.Marshal(errResp) + return &ClientHTTPResponse{ + StatusCode: errResp.GetRecommendedHTTPStatusCode(), + Headers: map[string]string{"Content-Type": "application/json"}, + Payload: errPayload, + } +} diff --git a/qos/judge/context_endpoint_checks.go b/qos/judge/context_endpoint_checks.go new file mode 100644 index 000000000..8f308ba8d --- /dev/null +++ b/qos/judge/context_endpoint_checks.go @@ -0,0 +1,62 @@ +package judge + +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// - Struct name: QualityCheckContext +// - Struct Methods: +// - 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/judge/context_endpoint_result.go b/qos/judge/context_endpoint_result.go new file mode 100644 index 000000000..942360e6b --- /dev/null +++ b/qos/judge/context_endpoint_result.go @@ -0,0 +1,105 @@ +package judge + +import ( + "encoding/json" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// 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. +// 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. + // 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[jsonrpc.Method]EndpointQueryResultBuilder +} + +// 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.updateEndpointQueryResultWithParsedResponse() + + // 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 ctx.EndpointQueryResult + } + + // Use the custom endpoint query result builder, if one is found matching the JSONRPC request's method. + builder, found := ctx.jsonrpcMethodResultBuilders[ctx.getJSONRPCRequestMethod()] + if !found { + // Use default processor for methods not specified by the custom QoS service definition. + builder = defaultResultBuilder + } + + // Process the result using custom service's result processor. + // Pass the context to the builder to provide helper methods. + queryResult := builder(ctx) + + // Return the endpoint query result. + return queryResult +} + +// TODO_IN_THIS_PR: define/allow customization of sanctions for endpoint errors: e.g. malformed response. +// +// 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) updateEndpointQueryResultWithParsedResponse() bool { + logger := ctx.getHydratedLogger() + + // Check for empty response + if len(ctx.EndpointQueryResult.endpointPayload) == 0 { + 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.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(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 + + // Return true to signal that parsing was successful. + // Processing will continue to the next step. + return true +} + +// TODO_IN_THIS_PR: implement. +func (ctx *EndpointQueryResultContext) getHydratedLogger() polylog.Logger { + // hydrate the logger with endpointQuery fields. + return ctx.logger +} diff --git a/qos/judge/context_endpoint_selection.go b/qos/judge/context_endpoint_selection.go new file mode 100644 index 000000000..02670969a --- /dev/null +++ b/qos/judge/context_endpoint_selection.go @@ -0,0 +1,163 @@ +package judge + +import ( + "errors" + "fmt" + "math/rand" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/protocol" +) + +// 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 + + // 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 + + // Endpoints disqualified from current selection context. + disqualifiedEndpoints map[protocol.EndpointAddr]struct{} + + candidateEndpoints map[protocol.EndpointAddr]*Endpoint +} + +func (ctx *EndpointSelectionContext) buildCandidateEndpointSet(availableEndpoints []protocol.Endpoint) { + // 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.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. + ctx.disqualifySanctionedEndpoints() + + // If all endpoints were sanctioned, return early + if len(ctx.candidateEndpoints) == len(ctx.disqualifiedEndpoints) { + // TODO_IN_THIS_PR: should we error out here instead? + ctx.logger.Info().Msg("All endpoints are currently sanctioned: returning a random endpoint.") + return ctx.selectRandomEndpoint() + } + + if ctx.customSelector == nil { + return protocol.EndpointAddr(""), fmt.Errorf("Endpoint selection failed: custom QoS endpoint selection must be provided.") + } + + // Call the custom selector. + // Pass the context to provide helper methods. + return ctx.customSelector(ctx) +} + +type EndpointFilter func(*Endpoint) error + +// 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 + } + + // 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{}{} + } + } + } + + // Disqualified endpoints have been marked. + // return a random qualified endpoint. + return ctx.selectRandomEndpoint() +} + +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 +} + +// 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 ctx.candidateEndpoints { + // Check if the endpoint is sanctioned. + activeSanction, isSanctioned := endpoint.GetActiveSanction() + + // endpoint has no active sanctions: skip further processing. + if !isSanctioned { + continue + } + + ctx.logger.With( + "endpoint_addr", string(endpointAddr), + "sanction", activeSanction, + ).Debug().Msg("Dropping sanctioned endpoint") + + ctx.disqualifiedEndpoints[endpointAddr] = struct{}{} + } +} diff --git a/qos/judge/context_request.go b/qos/judge/context_request.go new file mode 100644 index 000000000..302e67ec7 --- /dev/null +++ b/qos/judge/context_request.go @@ -0,0 +1,191 @@ +package judge + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/gateway" + qosobservations "github.com/buildwithgrove/path/observation/qos" + "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +// 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. +// +// requestQoSContext provides the support required by the gateway +// package for handling service requests. +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 + + // Tracks all data related to the current request context: + // - client's request + // - endpoint query result(s) + journal *requestJournal + + // 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. +func (rc requestQoSContext) GetServicePayload() protocol.Payload { + return rc.journal.getServicePayload() +} + +// 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. + endpointQueryResult := rc.journal.buildEndpointQueryResult(endpointAddr, receivedData) + + // Instantiate a result context using the endpointQuery. + resultCtx := rc.contextBuilder.buildEndpointQueryResultContext(endpointQueryResult) + + // Build an endpoint query result using the context. + processedEndpointQueryResult := resultCtx.buildEndpointQueryResult() + + // Track the result in the request journal. + rc.journal.reportEndpointQueryResult(processedEndpointQueryResult) +} + +// 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 { + // 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. + return rc.journal.getHTTPResponse() +} + +// GetObservations uses the request's journal to build and return all observations. +// Implements gateway.RequestQoSContext interface. +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.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 { + 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 *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 *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, + } + + // 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. +// - 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.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) + 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", string(jsonrpcReq.Method)). + Msg("JSONRPC Request validation failed") + + return &jsonrpcReq, buildRequestErrorJSONRPCValidationError(jsonrpcReq.ID, validationErr) + } + + // Request is valid + logger.Debug(). + Str("method", string(jsonrpcReq.Method)). + Msg("Request validation successful") + + return &jsonrpcReq, nil +} diff --git a/qos/judge/context_state_update.go b/qos/judge/context_state_update.go new file mode 100644 index 000000000..60d99a004 --- /dev/null +++ b/qos/judge/context_state_update.go @@ -0,0 +1,59 @@ +package judge + +// StateUpdateContext provides context and helper methods for updating service state. +type StateUpdateContext struct { + // Current service state (read-only copy) + // Provides direct access to the read-only state + *ServiceState + + // 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 +} + +func (ctx *StateUpdateContext) updateFromEndpoints(updatedEndpoints []*Endpoint) 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) +} + +func (ctx *StateUpdateContext) GetUpdatedEndpoints() []*Endpoint { + return ctx.updatedEndpoints +} + +func (ctx *StateUpdateContext) SetIntParam(paramName string, value int) { + param := &StateParameter{ + intValue: &value, + } + + ctx.paramsToUpdate.Set(paramName, param) +} + +func (ctx *StateUpdateContext) SetStrParam(paramName, value string) { + param := &StateParameter{ + strValue: &value, + } + + 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{ + consensusValues: 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/judge/endpoint.go b/qos/judge/endpoint.go new file mode 100644 index 000000000..5d22eef2e --- /dev/null +++ b/qos/judge/endpoint.go @@ -0,0 +1,97 @@ +package judge + +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. +// +// 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 { + logger polylog.Logger + + // queryResults maps keys to query results for this endpoint. + // 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{ + // StrValues: {"address": "0x8d97..."}, + // IntValues: {"balance": 133456789}, + // } + queryResults map[jsonrpc.Method]*EndpointQueryResult + + // mutex for query results + resultsMu sync.RWMutex +} + +// 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) GetStrResult(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 + } + + strValue, found := result.StrValues[valueKey] + return strValue, found +} + +// 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 0, false + } + + intValue, found := result.IntValues[valueKey] + return intValue, found +} + +// TODO_IN_THIS_PR: implement. +func (e *Endpoint) GetActiveSanction() (Sanction, bool) { + return Sanction{}, false +} + +// 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(endpointQueryResults []*EndpointQueryResult) { + e.resultsMu.Lock() + defer e.resultsMu.Unlock() + + // Initialize the results map if nil. + if e.queryResults == nil { + e.queryResults = make(map[jsonrpc.Method]*EndpointQueryResult) + } + + // Add or update attributes from the query result + for _, endpointQueryResult := range endpointQueryResults { + jsonrpcRequestMethod := endpointQueryResult.getJSONRPCRequestMethod() + + 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.queryResults[jsonrpcRequestMethod] = endpointQueryResult + + e.logger.With("jsonrpc_request_method", jsonrpcRequestMethod).Debug().Msg("Updated endpoint with query result.") + } +} diff --git a/qos/judge/endpoint_errors.go b/qos/judge/endpoint_errors.go new file mode 100644 index 000000000..1dff3417c --- /dev/null +++ b/qos/judge/endpoint_errors.go @@ -0,0 +1,30 @@ +package judge + +type EndpointErrorKind int + +const ( + 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 +) + +// 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 { + // 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/judge/endpoint_query_result.go b/qos/judge/endpoint_query_result.go new file mode 100644 index 000000000..85ce07eea --- /dev/null +++ b/qos/judge/endpoint_query_result.go @@ -0,0 +1,182 @@ +package judge + +import ( + "fmt" + "time" + + "github.com/buildwithgrove/path/protocol" + "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. +// - Stores one or more string/integer values. +// - Contains error/sanction information on endpoint error. +type EndpointQueryResult struct { + // 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 + + // 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 + // - "Epoch": 5 + StrValues map[string]string + IntValues map[string]int + + // 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. +} + +func (eqr *EndpointQueryResult) GetEndpointAddr() protocol.EndpointAddr { + return eqr.endpointAddr +} + +func (eqr *EndpointQueryResult) IsJSONRPCError() bool { + parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() + if err != nil { + return false + } + + return parsedJSONRPCResponse.IsError() +} + +func (eqr *EndpointQueryResult) GetResultAsInt() (int, error) { + parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() + if err != nil { + return 0, err + } + + return parsedJSONRPCResponse.GetResultAsInt() +} + +func (eqr *EndpointQueryResult) GetResultAsStr() (string, error) { + parsedJSONRPCResponse, err := eqr.getParsedJSONRPCResponse() + if err != nil { + return "", err + } + + return parsedJSONRPCResponse.GetResultAsStr() +} + +func (eqr *EndpointQueryResult) getParsedJSONRPCResponse() (*jsonrpc.Response, error) { + parsedJSONRPCResponse := eqr.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) Success( + resultBuilders ...ResultBuilder, +) *EndpointQueryResult { + for _, builder := range resultBuilders { + builder(eqr) + } + + return eqr +} + +// 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{ + ErrorKind: EndpointErrKindInvalidResult, + // Description is set by the custom service implementation + Description: description, + } + + return eqr +} + +// 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, + Description: description, + RecommendedSanction: &Sanction{ + Type: SanctionTypeTemporary, + Reason: reason, + ExpiryTime: time.Now().Add(duration), + }, + } + + return eqr +} + +// PermanentSanction creates an error result with a permanent sanction. +func (eqr *EndpointQueryResult) PermanentSanction(description, reason string) *EndpointQueryResult { + eqr.EndpointError = &EndpointError{ + ErrorKind: EndpointErrKindInvalidResult, + Description: description, + RecommendedSanction: &Sanction{ + Type: SanctionTypePermanent, + Reason: reason, + }, + } + + return eqr +} + +type ResultBuilder func(*EndpointQueryResult) + +func (eqr *EndpointQueryResult) AddIntResult(key string, value int) ResultBuilder { + 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) ResultBuilder { + return func(r *EndpointQueryResult) { + if r.StrValues == nil { + r.StrValues = make(map[string]string) + } + r.StrValues[key] = value + } +} diff --git a/qos/judge/endpoint_query_result_defaults.go b/qos/judge/endpoint_query_result_defaults.go new file mode 100644 index 000000000..67d3bcbed --- /dev/null +++ b/qos/judge/endpoint_query_result_defaults.go @@ -0,0 +1,89 @@ +package judge + +import ( + "fmt" +) + +// 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: +// - Metrics +// - Data Pipeline +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()), + */ + return nil +} + +// 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(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), + } + + // Set a generic response. + endpointQueryResult.parsedJSONRPCResponse = newErrResponseEmptyEndpointResponse(endpointQueryResult.getJSONRPCRequestID()) + // Set the endpoint error + endpointQueryResult.EndpointError = endpointError + + return endpointQueryResult +} + +// buildResultForErrorUnmarshalingEndpointReturnedData handles the case when parsing the endpoint's returned data failed. +func buildResultForErrorUnmarshalingEndpointReturnedData( + endpointQueryResult *EndpointQueryResult, + parseError error, +) *EndpointQueryResult { + endpointError := &EndpointError{ + ErrorKind: EndpointErrKindParseErr, + Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), + RecommendedSanction: getRecommendedSanction(EndpointErrKindParseErr, parseError), + } + + // Set a generic response. + endpointQueryResult.parsedJSONRPCResponse = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) + // Set the endpoint error + endpointQueryResult.EndpointError = endpointError + + return endpointQueryResult +} + +// buildResultForErrorValidatingEndpointResponse handles the case when validating the unmarshaled endpoint's JSONRPC response has failed. +func buildResultForErrorValidatingEndpointResponse( + endpointQueryResult *EndpointQueryResult, + parseError error, +) *EndpointQueryResult { + endpointError := &EndpointError{ + ErrorKind: EndpointErrKindValidationErr, + Description: fmt.Sprintf("endpoint payload failed to unmarshal: %q", parseError.Error()), + RecommendedSanction: getRecommendedSanction(EndpointErrKindValidationErr, parseError), + } + + // Set a generic response. + endpointQueryResult.parsedJSONRPCResponse = newErrResponseParseError(endpointQueryResult.getJSONRPCRequestID(), parseError) + // Set the endpoint error + endpointQueryResult.EndpointError = endpointError + + return endpointQueryResult +} diff --git a/qos/judge/endpoint_sanction.go b/qos/judge/endpoint_sanction.go new file mode 100644 index 000000000..f98bbb702 --- /dev/null +++ b/qos/judge/endpoint_sanction.go @@ -0,0 +1,24 @@ +package judge + +import ( + "time" +) + +// ====================== +// Sanction Types +// ====================== +// SanctionType identifies different types of endpoint sanctions. +type SanctionType int + +const ( + 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. +type Sanction struct { + Type SanctionType + Reason string + ExpiryTime time.Time // Zero time means permanent +} diff --git a/qos/judge/endpoint_sanction_defaults.go b/qos/judge/endpoint_sanction_defaults.go new file mode 100644 index 000000000..d4f56d3e3 --- /dev/null +++ b/qos/judge/endpoint_sanction_defaults.go @@ -0,0 +1,48 @@ +package judge + +import ( + "fmt" + "time" +) + +// 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 + + // Default sanction duration for parse errors + DefaultParseErrorSanctionDuration = 5 * time.Minute + + // Default sanction duration for no responses + DefaultNoResponseSanctionDuration = 5 * time.Minute +) + +func getRecommendedSanction(endpointErrKind EndpointErrorKind, err error) *Sanction { + switch endpointErrKind { + case EndpointErrKindEmptyPayload: + return newSanctionForEmptyResponse() + case EndpointErrKindParseErr: + return newSanctionForUnmarshalingError(err) + default: + return nil + } +} + +// 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), + } +} + +// 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), + } +} diff --git a/qos/judge/endpoint_store.go b/qos/judge/endpoint_store.go new file mode 100644 index 000000000..15d3abe11 --- /dev/null +++ b/qos/judge/endpoint_store.go @@ -0,0 +1,79 @@ +package judge + +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(endpointQueryResults []*EndpointQueryResult) []*Endpoint { + es.endpointsMu.Lock() + defer es.endpointsMu.Unlock() + + 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[endpointAddr] + if !found { + endpoint = &Endpoint{} + } + + endpoint.applyQueryResults(queryResults) + + // Store the updated endpoint + es.endpoints[endpointAddr] = endpoint + + // 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. +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 { + es.endpointsMu.RLock() + defer es.endpointsMu.RUnlock() + + if es.endpoints == nil { + return &Endpoint{} + } + + endpoint := es.endpoints[addr] + return endpoint +} diff --git a/qos/judge/framework.go b/qos/judge/framework.go new file mode 100644 index 000000000..8376c1916 --- /dev/null +++ b/qos/judge/framework.go @@ -0,0 +1,99 @@ +// 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 judge + +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. +// 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. +// 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 + + // ServiceName identifies and describes the service. + // 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[jsonrpc.Method]EndpointQueryResultBuilder + + // StateUpdater defines how endpoint results affect service state + StateUpdater + + // EndpointSelector defines custom endpoint selection logic + 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. + // - Use the supplied request validator in the framework. + // RequestValidator RequestValidator + + // TODO_FUTURE(@adshmh): Add additional configuration options: + // - AllowedMethods: Restrict which JSONRPC methods can be processed + // - RequestTimeout: Custom timeout for requests + // - RetryPolicy: Configuration for request retries + // - StateExpiryPolicy: Rules for expiring state entries +} + +// 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. +// 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(*EndpointQueryResultContext) *EndpointQueryResult + +// StateUpdater updates service state based on endpoint results +type StateUpdater func(*StateUpdateContext) *StateParameterUpdateSet + +// EndpointSelector chooses an endpoint for a request based on service state +type EndpointSelector func(*EndpointSelectionContext) (protocol.EndpointAddr, error) + +// 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/judge/jsonrpc_errors.go b/qos/judge/jsonrpc_errors.go new file mode 100644 index 000000000..facae3afc --- /dev/null +++ b/qos/judge/jsonrpc_errors.go @@ -0,0 +1,236 @@ +package judge + +import ( + "encoding/json" + "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 + 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 { + 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 + 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", + }, + ) + + return &jsonrpcResp +} + +// 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 { + jsonrpcResp := jsonrpc.GetErrorResponse( + requestID, + ErrorCodeParseError, + "Failed to parse endpoint response", + map[string]string{ + "error": parseErr.Error(), + "retryable": "true", + }, + ) + return &jsonrpcResp +} + +// 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 { + 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 + 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", + }, + ) + + return &jsonrpcResp +} + +func newJSONRPCErrResponseInternalProtocolError(requestID jsonrpc.ID) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( + requestID, + ErrorCodeInternalError, + "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", + }, + ) + + return &jsonrpcResp +} + +// 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": readErr.Error(), + "retryable": "true", + }, + ) + + return &jsonrpcResp +} + +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 + 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", + }, + ) + + return &jsonrpcResp +} + +// 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 newJSONRPCErrResponseMarshalError(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", + }, + ) +} + +// 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", + }, + ) +} + +// 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 newJSONRPCErrResponseParseRequestError(parseErr error) *jsonrpc.Response { + jsonrpcResp := jsonrpc.GetErrorResponse( + jsonrpc.ID{}, // No ID for parse errors + ErrorCodeParseError, + "Failed to parse JSON-RPC request", + map[string]string{ + "error": parseErr.Error(), + "retryable": "false", + }, + ) + + return &jsonrpcResp +} + +// 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 := 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"}}`, + response.ID, response.Error.Code, response.Error.Message) + return []byte(fallback), nil + } + return payload, nil +} diff --git a/qos/judge/observations.go b/qos/judge/observations.go new file mode 100644 index 000000000..7096e2beb --- /dev/null +++ b/qos/judge/observations.go @@ -0,0 +1,137 @@ +package judge + +import ( + "errors" + + "github.com/pokt-network/poktroll/pkg/polylog" + + qosobservations "github.com/buildwithgrove/path/observation/qos" + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +// getObservations returns the set of observations for the requestJournal. +// 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 +// +// 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{ + ServiceName: rj.serviceName, + } + + // observation for parsed JSONRPC (if parsed) + if rj.jsonrpcRequest != nil { + journalObservations.JsonrpcRequest = buildJSONRPCRequestObservation(rj.jsonrpcRequest) + } + + // observation for request error (if set) + if rj.requestError != nil { + journalObservations.RequestError = rj.requestError.buildObservation() + } + + // No endpoint query results. + // e.g. for invalid requests. + // Skip adding endpoint observations. + if len(rj.endpointQueryResults) == 0 { + return qosobservations.Observations{ + ServiceObservations: &qosobservations.Observations_RequestJournalObservations{ + RequestJournalObservations: &journalObservations, + }, + } + } + + endpointObservations := make([]*observations.EndpointQueryResult, len(rj.endpointQueryResults)) + for index, endpointQueryResult := range rj.endpointQueryResults { + endpointObservations[index] = endpointQueryResult.buildObservation(rj.logger) + } + + journalObservations.EndpointQueryResultObservations = endpointObservations + return qosobservations.Observations{ + ServiceObservations: &qosobservations.Observations_RequestJournalObservations{ + RequestJournalObservations: &journalObservations, + }, + } +} + +func buildRequestJournalFromObservations( + logger polylog.Logger, + journalObs *observations.RequestJournalObservations, +) (*requestJournal, error) { + // 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) + + // Instantiate the request journal. + requestJournal := &requestJournal{ + logger: logger, + jsonrpcRequest: jsonrpcRequest, + } + + // 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 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) + } + + logger.Debug().Msg("Successfully parsed the request journal from observations.") + return requestJournal, nil + } + + // reconstruct endpoint query results. + endpointsObs := journalObs.GetEndpointQueryResultObservations() + + // No endpoint observation present: skip the processing. + if endpointsObs == nil || len(endpointsObs) == 0 { + 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. + 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 := buildEndpointQueryResultFromObservation(logger, 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 requestJournal, nil +} diff --git a/qos/judge/observations_endpoint_error.go b/qos/judge/observations_endpoint_error.go new file mode 100644 index 000000000..6bb2a7591 --- /dev/null +++ b/qos/judge/observations_endpoint_error.go @@ -0,0 +1,72 @@ +package judge + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +// buildObservation converts an EndpointError to an observations.EndpointError +func (ee *EndpointError) buildObservation() *observations.EndpointError { + endpointErrorObs := &observations.EndpointError{ + ErrorKind: translateToObservationEndpointErrorKind(ee.ErrorKind), + Description: ee.Description, + } + + // Include sanction information if available + if ee.RecommendedSanction != nil { + endpointErrorObs.RecommendedSanction = ee.RecommendedSanction.buildObservation() + } + + return endpointErrorObs +} + +// buildEndpointErrorFromObservation extracts an EndpointError from an observations.EndpointError +func buildEndpointErrorFromObservation(endpointErrorObs *observations.EndpointError) *EndpointError { + endpointErr := &EndpointError{ + ErrorKind: translateFromObservationEndpointErrorKind(endpointErrorObs.GetErrorKind()), + Description: endpointErrorObs.Description, + } + + recommendedSanctionObs := endpointErrorObs.GetRecommendedSanction() + // No sanctions: skip the rest of the processing. + if recommendedSanctionObs == nil { + return endpointErr + } + + endpointErr.RecommendedSanction = buildSanctionFromObservation(recommendedSanctionObs) + + 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 translateToObservationEndpointErrorKind(errKind EndpointErrorKind) observations.EndpointErrorKind { + switch errKind { + 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.EndpointErrorKind_ENDPOINT_ERROR_KIND_UNSPECIFIED + } +} + +// DEV_NOTE: you MUST update this function when changing the set of endpoint error kinds. +func translateFromObservationEndpointErrorKind(errKind observations.EndpointErrorKind) EndpointErrorKind { + switch errKind { + 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 EndpointErrKindUnspecified + } +} diff --git a/qos/judge/observations_endpoint_result.go b/qos/judge/observations_endpoint_result.go new file mode 100644 index 000000000..d67b33c0e --- /dev/null +++ b/qos/judge/observations_endpoint_result.go @@ -0,0 +1,98 @@ +package judge + +import ( + "github.com/pokt-network/poktroll/pkg/polylog" + + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +// buildObservation converts an EndpointQueryResult to observations.EndpointQueryResult +// Used for reporting metrics. +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), + } + + // 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 + 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 { + obs.IntValues[key] = int64(value) + } + + // Convert error information if available + if eqr.EndpointError != nil { + obs.EndpointError = eqr.EndpointError.buildObservation() + } + + // Set expiry time + if !eqr.ExpiryTime.IsZero() { + obs.ExpiryTime = timestampProto(eqr.ExpiryTime) + } + + return obs +} + +// buildEndpointQueryResultFromObservation builds a single EndpointQueryResult from an observation's EndpointQueryResult +func buildEndpointQueryResultFromObservation( + 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 result values to be copied from the observations. + 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.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(); endpointErr != nil { + result.EndpointError = buildEndpointErrorFromObservation(endpointErr) + } + + return result +} diff --git a/qos/judge/observations_endpoint_sanction.go b/qos/judge/observations_endpoint_sanction.go new file mode 100644 index 000000000..2df0b3093 --- /dev/null +++ b/qos/judge/observations_endpoint_sanction.go @@ -0,0 +1,47 @@ +package judge + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +// TODO_IN_THIS_PR: change all `*Kind*` enum names to `*Type*`. + +func (s *Sanction) buildObservation() *observations.Sanction { + return &observations.Sanction{ + Type: translateToObservationSanctionType(s.Type), + Reason: s.Reason, + ExpiryTimestamp: timestampProto(s.ExpiryTime), + } +} + +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/judge/observations_interpreter.go b/qos/judge/observations_interpreter.go new file mode 100644 index 000000000..859710cb5 --- /dev/null +++ b/qos/judge/observations_interpreter.go @@ -0,0 +1,6 @@ +package judge + +// 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/judge/observations_jsonrpc.go b/qos/judge/observations_jsonrpc.go new file mode 100644 index 000000000..6af23ea57 --- /dev/null +++ b/qos/judge/observations_jsonrpc.go @@ -0,0 +1,57 @@ +package judge + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +func buildJSONRPCRequestObservation(jsonrpcReq *jsonrpc.Request) *observations.JsonRpcRequest { + if jsonrpcReq == nil { + return nil + } + + return &observations.JsonRpcRequest{ + Id: jsonrpcReq.ID.String(), + Method: string(jsonrpcReq.Method), + } +} + +// TODO_IN_THIS_PR: implement. +func buildObservationFromJSONRPCResponse(jsonrpcResp *jsonrpc.Response) *observations.JsonRpcResponse { + return nil +} + +func buildJSONRPCRequestFromObservation( + jsonrpcRequestObs *observations.JsonRpcRequest, +) *jsonrpc.Request { + if jsonrpcRequestObs == nil { + return nil + } + + // The only field required in applying the observations is the request's method. + return &jsonrpc.Request{ + Method: jsonrpc.Method(jsonrpcRequestObs.GetMethod()), + } +} + +func buildJSONRPCResponseFromObservation( + 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/judge/observations_request_error.go b/qos/judge/observations_request_error.go new file mode 100644 index 000000000..065eff975 --- /dev/null +++ b/qos/judge/observations_request_error.go @@ -0,0 +1,56 @@ +package judge + +import ( + observations "github.com/buildwithgrove/path/observation/qos/framework" +) + +func (re *requestError) buildObservation() *observations.RequestError { + return &observations.RequestError{ + ErrorKind: translateToObservationRequestErrorKind(re.errorKind), + ErrorDetails: re.errorDetails, + // The JSONRPC response returned to the client. + JsonRpcResponse: buildObservationFromJSONRPCResponse(re.jsonrpcErrorResponse), + } +} + +func buildRequestErrorFromObservation(obs *observations.RequestError) *requestError { + return &requestError{ + errorKind: translateFromObservationRequestErrorKind(obs.GetErrorKind()), + errorDetails: obs.GetErrorDetails(), + jsonrpcErrorResponse: buildJSONRPCResponseFromObservation(obs.GetJsonRpcResponse()), + } +} + +// DEV_NOTE: you MUST update this function when changing the set of request errors. +func translateToObservationRequestErrorKind(errKind requestErrorKind) observations.RequestErrorKind { + switch errKind { + case requestErrKindInternalErrReadyHTTPBody: + return observations.RequestErrorKind_REQUEST_ERROR_INTERNAL_BODY_READ_FAILURE + case requestErrKindInternalProtocolError: + 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.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/judge/observations_time.go b/qos/judge/observations_time.go new file mode 100644 index 000000000..a4cf78be4 --- /dev/null +++ b/qos/judge/observations_time.go @@ -0,0 +1,22 @@ +package judge + +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() +} diff --git a/qos/judge/qos.go b/qos/judge/qos.go new file mode 100644 index 000000000..3c3e29dc4 --- /dev/null +++ b/qos/judge/qos.go @@ -0,0 +1,156 @@ +package judge + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "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. +// 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 + + serviceState *ServiceState + + // The definitoin of QoS behavior, supplied by the custom QoS service. + qosDefinition *QoSDefinition +} + +// ParseHTTPRequest handles parsing an HTTP request and validating its content +// It returns a RequestQoSContext and a boolean indicating if processing should continue +func (s *QoS) ParseHTTPRequest( + _ context.Context, + httpReq *http.Request, +) (*requestQoSContext, bool) { + // Context for processing the HTTP request. + requestCtx := &requestQoSContext{ + logger: s.logger, + } + + // Initialize the request context from the HTTP request. + shouldContinue := requestCtx.initFromHTTP(httpReq) + + return requestCtx, shouldContinue +} + +// TODO_IN_THIS_PR: implement this method +// func (qos *QoS) ParseWebsocketRequest(_ context.Context) (gateway.RequestQoSContext, bool) + +func (q *QoS) ApplyObservations(observations *qosobservations.Observations) error { + // 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 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, requestJournalObservations) + if err != nil { + logger.Error().Err(err).Msg("Error building the request journal from observations: skipping the application of observations.") + return err + } + + // update the stored endpoints + updatedEndpoints := q.serviceState.updateStoredEndpoints(requestJournal.endpointQueryResults) + + // instantiate a state update context. + 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) []gateway.RequestQoSContext { + endpointChecksCtx := q.buildEndpointChecksContext(endpointAddr) + return endpointChecksCtx.buildEndpointQualityCheckContexts() +} + +// 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(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. + 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, + } +} + +// 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, + + 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, + // The endpoint selector logic defined by the custom QoS service defintion. + customSelector: q.qosDefinition.EndpointSelector, + } +} + +// TODO_IN_THIS_PR: implement this method. +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) + + return &EndpointQualityChecksContext{ + logger: q.logger.With("context", "endpoint_quality_check_builder"), + + // 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: q.serviceState, + + // Endpoint loaded from the endpoint store. + endpoint: endpoint, + + // Custom service's Endpoint Checks function + endpointChecksBuilder: q.qosDefinition.EndpointQualityChecksBuilder, + } +} + +// TODO_IN_THIS_PR: implement this method. +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/judge/request_errors.go b/qos/judge/request_errors.go new file mode 100644 index 000000000..74f3f2d63 --- /dev/null +++ b/qos/judge/request_errors.go @@ -0,0 +1,79 @@ +package judge + +import ( + "fmt" + + "github.com/buildwithgrove/path/qos/jsonrpc" +) + +type requestErrorKind int + +const ( + requestErrKindUnspecified requestErrorKind = iota // matches the "UNSPECIFIED" enum value in proto definitions. + requestErrKindInternalErrReadyHTTPBody + 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. + 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), + } +} + +// 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. +// 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 { + return &requestError{ + errorKind: requestErrKindInternalProtocolError, + 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: requestErrKindJSONRPCParsingError, + errorDetails: fmt.Sprintf("error parsing HTTP request into JSONRPC: %v", err), + // Create JSONRPC error response for parse failure + jsonrpcErrorResponse: newJSONRPCErrResponseParseRequestError(err), + } +} + +func buildRequestErrorJSONRPCValidationError(requestID jsonrpc.ID, validationErr error) *requestError { + return &requestError{ + 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/judge/request_journal.go b/qos/judge/request_journal.go new file mode 100644 index 000000000..c2e7da109 --- /dev/null +++ b/qos/judge/request_journal.go @@ -0,0 +1,157 @@ +package judge + +import ( + "encoding/json" + "net/http" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "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: +// - 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. + 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. +type requestJournal struct { + logger polylog.Logger + + // Service identification + serviceName string + + // The client's JSONRPC request + // Only set if the request was successfully parsed. + jsonrpcRequest *jsonrpc.Request + + // Request error, if any. + requestError *requestError + + // All endpoint interactions that occurred during processing. + endpointQueryResults []*EndpointQueryResult +} + +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{ + // request journal reference. + // Used to access the request, e.g. JSONRPC request method. + requestJournal: rj, + + // Address of the queried endpoint. + endpointAddr: endpointAddr, + // Data received from the endpoint. + endpointPayload: receivedData, + + // Initialize attribute maps + IntValues: make(map[string]int), + StrValues: make(map[string]string), + } +} + +func (rj *requestJournal) reportEndpointQueryResult(endpointQueryResult *EndpointQueryResult) { + 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.requestError = requestErr +} + +func (rj *requestJournal) getServicePayload() protocol.Payload { + // Sanity check the request fields. + // A non-nil requestErr indicates the request failed to parse/validate. + 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{} + } + + 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{} + } + + 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, + } +} + +// TODO_FUTURE(@adshmh): A retry mechanism would require support from this struct to determine if the most recent endpoint query has been successful. +// +// 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 requestErr := rj.requestError; requestErr != nil { + return buildHTTPResponse(rj.logger, requestErr.jsonrpcErrorResponse) + } + + // 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] + return buildHTTPResponse(rj.logger, selectedEndpointQueryResult.parsedJSONRPCResponse) +} + +func (rj *requestJournal) getJSONRPCRequestMethod() jsonrpc.Method { + request := rj.jsonrpcRequest + if request == nil { + return 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/judge/state.go b/qos/judge/state.go new file mode 100644 index 000000000..055e3271d --- /dev/null +++ b/qos/judge/state.go @@ -0,0 +1,86 @@ +package judge + +import ( + "sync" + + "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 + + // 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) { + 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(endpointQueryResults []*EndpointQueryResult) []*Endpoint { + s.mu.Lock() + defer s.mu.Unlock() + + return s.endpointStore.updateStoredEndpoints(endpointQueryResults) +} + +// 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() + + for paramName, param := range updates.Updates { + s.parameters[paramName] = param + } + + return nil +} diff --git a/qos/judge/state_parameter.go b/qos/judge/state_parameter.go new file mode 100644 index 000000000..1e8e0426a --- /dev/null +++ b/qos/judge/state_parameter.go @@ -0,0 +1,56 @@ +package judge + +// 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/judge/state_update.go b/qos/judge/state_update.go new file mode 100644 index 000000000..6056df86a --- /dev/null +++ b/qos/judge/state_update.go @@ -0,0 +1,14 @@ +package judge + +// TODO_FUTURE(@adshmh): Support deleting StateParameters by adding a `ToDelete` field +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/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 +}