Documentation
¶
Overview ¶
Package gateway implements components for operating a gateway service.
Protocol (Shannon): - Provide available endpoints for a service - Send relays to specific endpoints
Gateways: - Select endpoints for relay transmission
QoS Services: - Interpret user requests into endpoint payloads - Select optimal endpoints for request handling
TODO_MVP(@adshmh): add a README with a diagram of all the above. TODO_MVP(@adshmh): add a section for the following packages once they are added: Metrics, Message.
TODO_MVP(@adshmh): Add a mermaid diagram of the different structural (i.e. packages, types) components to help clarify the role of each.
Index ¶
Constants ¶
const ( // APIVersionPrefix is the prefix for the API version. // It is used and removed by the routing middleware. // // Example: // // /v1/{rest_path_1} -> /{rest_path_1} // /v1/{rest_path_1}/{rest_path_2} -> /{rest_path_1}/{rest_path_2} // /v1/{rest_path_1}/{rest_path_2}/... -> /{rest_path_1}/{rest_path_2}/... APIVersionPrefix = "/v1" // HttpHeaderPortalAppID is the header key for the portal app ID. // // It is also used by the routing middleware in `router.go` to ensure // the portal app ID is not present in the request path that is forwarded to // the service endpoint. // // Example: // // /1a2b3c4d/path/segment -> /path/segment // /1a2b3c4d/path -> /path HttpHeaderPortalAppID = "Portal-Application-ID" // Portal API Key HTTP header. HttpHeaderAuthorization = "Authorization" )
const DefaultWebsocketMessageBufferSize = 100
DefaultWebsocketMessageBufferSize is the default buffer size for websocket message observations. This should match the default in config/router.go. At 100: 100 × ~3KB × 100 connections = ~30MB.
const ( // RelayRequestTimeout is the timeout for relay requests // TODO_TECHDEBT: Look into whether we can remove this variable altogether and consolidate // it with HTTP level timeouts. RelayRequestTimeout = 60 * time.Second )
Variables ¶
var ( // no service ID was provided by the user. ErrGatewayNoServiceIDProvided = errors.New("no service ID provided") )
Publicly exposed errors
Functions ¶
This section is empty.
Types ¶
type EndpointHydrator ¶
type EndpointHydrator struct {
Logger polylog.Logger
// Protocol instance to be used by the hydrator when listing endpoints and sending relays.
Protocol
// ActiveQoSServices provides the hydrator with the QoS instances
// it needs to invoke for generating synthetic service requests.
// IMPORTANT: ActiveQoSServices should not be modified after the hydrator is started.
ActiveQoSServices map[protocol.ServiceID]QoSService
// MetricsReporter is used to export metrics based on observations made in handling service requests.
MetricsReporter RequestResponseReporter
// DataReporter is used to export, to the data pipeline, observations made in handling service requests.
// It is declared separately from the `MetricsReporter` to be consistent with the gateway package's role
// of explicitly defining PATH gateway's components and their interactions.
DataReporter RequestResponseReporter
// RunInterval is the interval at which the Endpoint Hydrator will run HTTP checks in milliseconds.
RunInterval time.Duration
// MaxEndpointCheckWorkers is the maximum number of workers that will be used to concurrently check endpoints.
MaxEndpointCheckWorkers int
// contains filtered or unexported fields
}
Please see the following link for details on the use of `Hydrator` word in the name. https://stackoverflow.com/questions/6991135/what-does-it-mean-to-hydrate-an-object
EndpointHydrator augments the available dataset on quality of endpoints. For example, it can be used to process raw data into QoS data. This ensures that each service on each instance has the information needed to make real-time decisions to handle user requests.
An example QoS transformation workflow can be: 1. Consulting each service's QoS instance on the checks required to validate an endpoint. 2. Performing the required checks on the endpoint, in the form of a (synthetic) service request. 3. Reporting the results back to the service's QoS instance.
func (*EndpointHydrator) IsAlive ¶
func (eph *EndpointHydrator) IsAlive() bool
IsAlive returns true if the hydrator has completed 1 iteration. It is used to check the status/health of the hydrator
func (*EndpointHydrator) Name ¶
func (eph *EndpointHydrator) Name() string
Name is used when checking the status/health of the hydrator.
func (*EndpointHydrator) Start ¶
func (eph *EndpointHydrator) Start(ctx context.Context) error
Start should be called to signal this instance of the hydrator to start generating and sending endpoint check requests. It starts two separate goroutines: one for HTTP checks and one for Websocket checks. The ctx parameter allows for graceful shutdown - when ctx is canceled, both goroutines will exit.
type Gateway ¶
type Gateway struct {
Logger polylog.Logger
// HTTPRequestParser is used by the gateway instance to
// interpret an HTTP request as a pair of service ID and
// its corresponding QoS instance.
HTTPRequestParser
// The Protocol instance is used to fulfill the
// service requests received by the gateway through
// sending the service payload to an endpoint.
Protocol
// MetricsReporter is used to export metrics based on observations made in handling service requests.
MetricsReporter RequestResponseReporter
// DataReporter is used to export, to the data pipeline, observations made in handling service requests.
// It is declared separately from the `MetricsReporter` to be consistent with the gateway package's role
// of explicitly defining PATH gateway's components and their interactions.
DataReporter RequestResponseReporter
// WebsocketMessageBufferSize is the buffer size for websocket message observation channels.
// Configurable to balance memory usage vs throughput for websocket connections.
// Default: DefaultWebsocketMessageBufferSize (100)
WebsocketMessageBufferSize int
}
Gateway handles end-to-end service requests via HandleHTTPServiceRequest: - Receives user request - Processes request - Returns response
TODO_FUTURE: Current HTTP-only format supports JSONRPC, REST, Websockets and gRPC. May expand to other formats in future.
func (Gateway) HandleServiceRequest ¶
func (g Gateway) HandleServiceRequest( ctx context.Context, httpReq *http.Request, responseWriter http.ResponseWriter, )
HandleServiceRequest implements PATH gateway's service request processing.
This method acts as a request router that: 1. Determines the type of incoming request (e.g. HTTP or Websocket upgrade) 2. Delegates to the appropriate handler:
- Websocket: Long-lived bidirectional connection with message-based observations
- HTTP: Request-response cycle with single observation broadcast
This separation allows for different processing flows while maintaining a unified entry point.
TODO_FUTURE: Refactor when adding other protocols (e.g. gRPC):
- Extract generic processing into common method
- Keep protocol-specific details separate
type HTTPRequestParser ¶
type HTTPRequestParser interface {
// GetQoSService returns the qos for the service matching an HTTP request.
GetQoSService(context.Context, *http.Request) (protocol.ServiceID, QoSService, error)
// GetHTTPErrorResponse returns an HTTP response using the supplied error.
// It will only be called if the GetQoSService method above returns an error.
GetHTTPErrorResponse(context.Context, error) pathhttp.HTTPResponse
}
HTTPRequestParser is used, in handling an HTTP service request, to extract the service ID and corresponding QoS service from an HTTP request.
type Protocol ¶
type Protocol interface {
// AvailableHTTPEndpoints returns the list of available HTTP endpoints matching both the service ID
//
// If the Pocket Network Gateway is in delegated mode, the staked application is passed via
// the `App-Address` header. In all other modes, *http.Request will be nil.
//
// Context may contain a deadline that protocol should respect on best-effort basis.
// Return observation if endpoint lookup fails.
// Used as protocol observation for the request when no protocol context exists.
AvailableHTTPEndpoints(
context.Context,
protocol.ServiceID,
*http.Request,
) (protocol.EndpointAddrList, protocolobservations.Observations, error)
// AvailableWebsocketEndpoints returns the list of available websocket endpoints matching both the service ID
//
// If the Pocket Network Gateway is in delegated mode, the staked application is passed via
// the `App-Address` header. In all other modes, *http.Request will be nil.
//
// Context may contain a deadline that protocol should respect on best-effort basis.
// Return observation if endpoint lookup fails.
// Used as protocol observation for the request when no protocol context exists.
AvailableWebsocketEndpoints(
context.Context,
protocol.ServiceID,
*http.Request,
) (protocol.EndpointAddrList, protocolobservations.Observations, error)
// BuildRequestContextForEndpoint builds and returns a ProtocolRequestContext containing a single selected endpoint.
// One `ProtocolRequestContext` correspond to a single request, which is sent to a single endpoint.
//
// If the Pocket Network Gateway is in delegated mode, the staked application is passed via
// the `App-Address` header. In all other modes, *http.Request will be nil.
//
// Context may contain a deadline that protocol should respect on best-effort basis.
//
// Return observation if the context setup fails.
// Used as protocol observation for the request when no protocol context exists.
BuildHTTPRequestContextForEndpoint(
context.Context,
protocol.ServiceID,
protocol.EndpointAddr,
*http.Request,
) (ProtocolRequestContext, protocolobservations.Observations, error)
// BuildWebsocketRequestContextForEndpoint builds and returns a ProtocolRequestContextWebsocket containing a single selected endpoint.
// One `ProtocolRequestContextWebsocket` corresponds to a single long-lived websocket connection to a single endpoint.
// This method immediately establishes the Websocket connection and starts the bridge.
//
// If the Pocket Network Gateway is in delegated mode, the staked application is passed via
// the `App-Address` header. In all other modes, *http.Request will be nil.
//
// Return observation channel for connection-level observations (establishment, closure, errors).
// Message observations are sent through the provided messageObservationsChan.
// Return error if the context setup or connection establishment fails.
BuildWebsocketRequestContextForEndpoint(
context.Context,
protocol.ServiceID,
protocol.EndpointAddr,
websockets.WebsocketMessageProcessor,
*http.Request,
http.ResponseWriter,
chan *observation.RequestResponseObservations,
) (ProtocolRequestContextWebsocket, <-chan *protocolobservations.Observations, error)
// SupportedGatewayModes returns the Gateway modes supported by the protocol instance.
// See protocol/gateway_mode.go for more details.
SupportedGatewayModes() []protocol.GatewayMode
// ApplyHTTPObservations applies the supplied observations to the protocol instance's internal state.
// Hypothetical example (for illustrative purposes only):
// - protocol: Shannon
// - observation: "endpoint maxed-out or over-serviced (i.e. onchain rate limiting)"
// - result: skip the endpoint for a set time period until a new session begins.
ApplyHTTPObservations(*protocolobservations.Observations) error
// ApplyWebSocketObservations applies the supplied observations to the protocol instance's internal state.
// Hypothetical example (for illustrative purposes only):
// - protocol: Shannon
// - observation: "endpoint maxed-out or over-serviced (i.e. onchain rate limiting)"
// - result: skip the endpoint for a set time period until a new session begins.
ApplyWebSocketObservations(*protocolobservations.Observations) error
// TODO_FUTURE(@adshmh): support specifying the app(s) used for sending/signing synthetic relay requests by the hydrator.
// TODO_TECHDEBT: Enable the hydrator for gateway modes beyond Centralized only.
//
// ConfiguredServiceIDs returns the list of service IDs that the protocol instance is configured to serve.
ConfiguredServiceIDs() map[protocol.ServiceID]struct{}
// GetTotalServiceEndpointsCount returns the count of all unique endpoints for a service ID
// without filtering sanctioned endpoints.
GetTotalServiceEndpointsCount(protocol.ServiceID, *http.Request) (int, error)
// HydrateDisqualifiedEndpointsResponse hydrates the disqualified endpoint response with the protocol-specific data.
HydrateDisqualifiedEndpointsResponse(protocol.ServiceID, *devtools.DisqualifiedEndpointResponse)
// CheckWebsocketConnection checks if the websocket connection to the endpoint is established.
CheckWebsocketConnection(context.Context, protocol.ServiceID, protocol.EndpointAddr) *protocolobservations.Observations
// health.Check interface is used to verify protocol instance's health status.
health.Check
}
Protocol defines the core functionality of a protocol from the perspective of a gateway. The gateway expects a protocol to build and return a request context for a particular service ID.
type ProtocolRequestContext ¶
type ProtocolRequestContext interface {
// HandleServiceRequest sends the supplied payload to the endpoint selected using the above SelectEndpoint method,
// and receives and verifies the response.
HandleServiceRequest([]protocol.Payload) ([]protocol.Response, error)
// GetObservations builds and returns the set of protocol-specific observations using the current context.
//
// Hypothetical illustrative example.
//
// If the context is:
// - Protocol: Shannon
// - SelectedEndpoint: `endpoint_101`
// - Event: HandleServiceRequest returned a "maxed-out endpoint" error
//
// Then the observation can be:
// - `maxed-out endpoint` on `endpoint_101`.
GetObservations() protocolobservations.Observations
}
ProtocolRequestContext defines the functionality expected by the gateway from the protocol, for a particular service ID.
These include but not limited to:
- Listing the endpoints available for sending relays for a specific service.
- Send a relay to a specific endpoint and return its response.
The implementation of this interface is in the relayer/shannon package.
type ProtocolRequestContextWebsocket ¶
type ProtocolRequestContextWebsocket interface {
// ProcessProtocolClientWebsocketMessage processes a message from the client.
ProcessProtocolClientWebsocketMessage([]byte) ([]byte, error)
// ProcessProtocolEndpointWebsocketMessage processes a message from the endpoint.
ProcessProtocolEndpointWebsocketMessage([]byte) ([]byte, protocolobservations.Observations, error)
}
ProtocolRequestContextWebsocket defines the functionality expected by the gateway from the protocol, specifically for websocket requests
type QoSContextBuilder ¶
type QoSContextBuilder interface {
// ParseHTTPRequest:
// - Ensures the HTTP request is valid for the target service.
ParseHTTPRequest(context.Context, *http.Request) (RequestQoSContext, bool)
// ParseWebsocketRequest:
// - Ensures a Websocket request is valid for the target service.
// - Websocket connection requests have no body, so no parsing needed.
// - If service supports Websocket, returns a valid RequestQoSContext.
//
// TODO_TECHDEBT(@adshmh,@commoddity): Remove ParseWebsocketRequest and update ParseHTTPRequest to be the single entry point to QoS.
// It should perform basic validation of the HTTP handshake request in the case that it is a Websocket request.
// eg. check that the request is a websocket request, check headers, etc.
ParseWebsocketRequest(context.Context) (RequestQoSContext, bool)
}
QoSContextBuilder
Builds the QoS context required for all steps of a service request. Example: Generate a user-facing HTTP response from an endpoint's response.
type QoSEndpointCheckGenerator ¶
type QoSEndpointCheckGenerator interface {
// TODO_FUTURE:
// - Add GetOptionalQualityChecks() to collect additional QoS data (e.g., endpoint latency).
//
// GetRequiredQualityChecks:
// - Returns required quality checks for a QoS instance to assess endpoint validity.
// - Example: EVM QoS may skip block height check if chain ID check already failed.
GetRequiredQualityChecks(protocol.EndpointAddr) []RequestQoSContext
// TODO_TECHDEBT(@commoddity): Currently websocket QoS only performs a protocol-level check
// which determines if an endpoint connection request is successful or not.
//
// This only require a simple bool that tells the hydrator if the endpoint should be checked
// for Websocket connection and applies protocol-level sanctions if it fails.
//
// In the future, we may want to add QoS-level checks that take into account specific endpoint
// responses to apply websocket-related filtering at the QoS level.
//
// CheckWebsocketConnection
// - Checks if the endpoint supports Websocket connections.
// - Returns a boolean indicating whether the endpoint should be checked for Websocket connection.
CheckWebsocketConnection() bool
}
QoSEndpointCheckGenerator
Returns one or more service request contexts that: - Provide data on endpoint quality by sending payloads and parsing responses. - Checks are service-specific; the QoS instance decides what checks to run.
type QoSService ¶
type QoSService interface {
QoSContextBuilder
QoSEndpointCheckGenerator
// ApplyObservations:
// - Applies QoS-related observations to the local QoS instance.
// - TODO_FUTURE: Observations can be:
// - "local": from requests sent to an endpoint by THIS PATH instance.
// - "shared": from QoS observations shared by OTHER PATH instances.
ApplyObservations(*qos.Observations) error
// HydrateDisqualifiedEndpointsResponse:
// - Fills the disqualified endpoint response with QoS-specific data.
HydrateDisqualifiedEndpointsResponse(protocol.ServiceID, *devtools.DisqualifiedEndpointResponse)
}
TODO_IMPLEMENT: - Add a QoS instance per service supported by the gateway (e.g., Ethereum, Solana, RESTful).
QoSService: - Represents the embedded definition of a service (e.g., JSONRPC blockchain). - Responsibilities:
- QoSRequestParser: Translates service requests (currently only HTTP) into service request contexts.
- EndpointSelector: Chooses the best endpoint for a specific service request.
type RequestQoSContext ¶
type RequestQoSContext interface {
// TODO_TECHDEBT: Should eventually return []Payload
// - Allows mapping a single RelayRequest into multiple ServiceRequests.
// - Example: A batch relay request on JSONRPC should decompose into multiple independent requests.
GetServicePayloads() []protocol.Payload
// TODO_FUTURE:
// - Add retry-related return values to UpdateWithResponse,
// or add retry-related methods (e.g., Failed(), ShouldRetry()).
//
// UpdateWithResponse:
// - Informs the request QoS context of the payload returned by a specific endpoint.
// - Response is for the service payload produced by GetServicePayload.
// - httpStatusCode is the original HTTP status code from the backend endpoint.
UpdateWithResponse(endpointAddr protocol.EndpointAddr, endpointSerializedResponse []byte, httpStatusCode int)
// GetHTTPResponse:
// - Returns the user-facing HTTP response.
// - Response depends on the current state of the service request context.
// - State is set at context creation and updated via UpdateWithResponse.
// - If never updated, may return 404 HTTP status.
GetHTTPResponse() pathhttp.HTTPResponse
// GetObservations:
// - Returns QoS-level observations in the context.
//
// Example:
// Context:
// - Service: Solana
// - SelectedEndpoint: `endpoint_101`
// - Request: `getHealth`
// - Endpoint response: error
// Observation:
// - `endpoint_101` is unhealthy.
GetObservations() qos.Observations
// GetEndpointSelector:
// - Enables specialized endpoint selection (e.g., method-based selection for EVM requests).
GetEndpointSelector() protocol.EndpointSelector
}
RequestQoSContext
Represents interactions between the gateway and the QoS instance for a given service request.
Construction methods: - Parse an organic request from an end-user. - Rebuild from a shared context deserialized from another PATH instance.
type RequestResponseReporter ¶
type RequestResponseReporter interface {
// Publish exports observations made on a service request and response(s), to the external component used by the corresponding implementation.
Publish(*observation.RequestResponseObservations)
}
RequestResponseReporter defines the interface for reporting observations with respect to a request, its corresponding response, and the set of events to any interested entity. Examples of reporters include:
- MetricsReporter: exports metrics based on the observations
- DataReporter: exports observations to external components (e.g.Messaging system or Database)