Skip to content

Commit 2fc5a17

Browse files
authored
[Payload] Refactoring how null (i.e. empty Data) payloads are returned (#382)
- Introduced `EmptyErrorPayload()` to replace `protocol.Payload{}` returns with unique error data value - Add `SHOULD NEVER HAPPEN` to a few log messages - Ensure `Payload` struct is fully hydrated
1 parent 8fd4714 commit 2fc5a17

File tree

10 files changed

+81
-71
lines changed

10 files changed

+81
-71
lines changed

gateway/request_context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ func (rc *requestContext) BroadcastAllObservations() {
333333
}
334334

335335
// The service request context contains all the details the QoS needs to update its internal metrics about endpoint(s), which it should use to build
336-
// the observation.QoSObservations struct.
336+
// the qosobservations.Observations struct.
337337
// This ensures that separate PATH instances can communicate and share their QoS observations.
338338
// The QoS context will be nil if the target service ID is not specified correctly by the request.
339339
var qosObservations qosobservations.Observations

protocol/payload.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@ import (
44
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
55
)
66

7-
// TODO_TECHDEBT(@adshmh): use an interface here that returns the serialized form of the request, with the following requirements:
8-
// 1. Payload should return the serialized form of the request to be delivered to the backend service,
9-
// i.e. the onchain service to which the protocol endpoint proxies relay requests.
10-
// 2. Use an enum to represent the underlying spec/standard, e.g. REST/JSONRPC/gRPC/etc.
11-
//
12-
// Payload currently supports HTTP(s) requests to blockchain services
13-
// TODO_DOCUMENT(@adshmh): add more examples, e.g. for RESTful services, as support for more types of services
14-
// is added.
7+
// Payload represents the HTTP(s) requests proxied between clients and backend services.
8+
// TODO_DOCUMENT(@adshmh): Add more examples (e.g. for RESTful services)
9+
// TODO_IMPROVE(@adshmh): Use an interface here that returns the serialized form of the request.
1510
type Payload struct {
1611
Data string
1712
Method string
@@ -23,4 +18,20 @@ type Payload struct {
2318
// - RPCType_COMET_BFT: CometBFT RPC (typically port 26657)
2419
// - RPCType_JSON_RPC: EVM JSON-RPC (typically port 8545)
2520
RPCType sharedtypes.RPCType
26-
}
21+
}
22+
23+
// EmptyPayload returns an empty payload struct.
24+
// It should only be used when an error is encountered and the actual request cannot be
25+
// proxied, parsed or otherwise processed.
26+
func EmptyErrorPayload() Payload {
27+
return Payload{
28+
// This is an intentional placeholder to distinguish errors in retrieving payloads
29+
// from explicit empty payloads set by PATH.
30+
Data: "PATH_EmptyErrorPayload",
31+
Method: "",
32+
Path: "",
33+
Headers: map[string]string{},
34+
TimeoutMillisec: 0,
35+
RPCType: sharedtypes.RPCType_UNKNOWN_RPC,
36+
}
37+
}

qos/cosmos/error_context.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ func (ec *errorContext) GetObservations() qosobservations.Observations {
8080
// It logs a warning and returns nil.
8181
// Implements the gateway.RequestQoSContext interface.
8282
func (ec *errorContext) GetServicePayload() protocol.Payload {
83-
ec.logger.Warn().Msg("Invalid usage: errorContext.GetServicePayload() should never be called.")
84-
return protocol.Payload{}
83+
ec.logger.Warn().Msg("SHOULD NEVER HAPPEN: errorContext.GetServicePayload() should never be called.")
84+
return protocol.EmptyErrorPayload()
8585
}
8686

8787
// UpdateWithResponse should never be called.
@@ -91,14 +91,14 @@ func (ec *errorContext) UpdateWithResponse(endpointAddr protocol.EndpointAddr, e
9191
ec.logger.With(
9292
"endpoint_addr", endpointAddr,
9393
"endpoint_response_len", len(endpointSerializedResponse),
94-
).Warn().Msg("Invalid usage: errorContext.UpdateWithResponse() should never be called.")
94+
).Warn().Msg("SHOULD NEVER HAPPEN: errorContext.UpdateWithResponse() should never be called.")
9595
}
9696

9797
// UpdateWithResponse should never be called.
9898
// It logs a warning and returns a failing selector that logs a warning on all selection attempts.
9999
// Implements the gateway.RequestQoSContext interface.
100100
func (ec *errorContext) GetEndpointSelector() protocol.EndpointSelector {
101-
ec.logger.Warn().Msg("Invalid usage: errorContext.GetEndpointSelector() should never be called.")
101+
ec.logger.Warn().Msg("SHOULD NEVER HAPPEN: errorContext.GetEndpointSelector() should never be called.")
102102

103103
return errorTrackingSelector{
104104
logger: ec.logger,
@@ -119,7 +119,7 @@ type errorTrackingSelector struct {
119119
func (ets errorTrackingSelector) Select(endpoints protocol.EndpointAddrList) (protocol.EndpointAddr, error) {
120120
ets.logger.With(
121121
"num_endpoints", len(endpoints),
122-
).Warn().Msg("Invalid usage: errorTrackingSelector.Select() should never be called.")
122+
).Warn().Msg("SHOULD NEVER HAPPEN: errorTrackingSelector.Select() should never be called.")
123123

124124
return protocol.EndpointAddr(""), errInvalidSelectorUsage
125125
}
@@ -131,7 +131,7 @@ func (ets errorTrackingSelector) SelectMultiple(endpoints protocol.EndpointAddrL
131131
ets.logger.With(
132132
"num_endpoints", len(endpoints),
133133
"num_endpoints_to_select", numEndpoints,
134-
).Warn().Msg("Invalid usage: errorTrackingSelector.SelectMultiple() should never be called.")
134+
).Warn().Msg("SHOULD NEVER HAPPEN: errorTrackingSelector.SelectMultiple() should never be called.")
135135

136136
return nil, errInvalidSelectorUsage
137137
}

qos/cosmos/request_validator_jsonrpc.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const (
1919
// maximum length of the error message stored in request validation failure observations and logs.
2020
maxErrMessageLen = 1000
2121

22+
// defaultJSONRPCRequestTimeoutMillisec is the default timeout when sending a request to a Cosmos blockchain endpoint.
23+
// TODO_IMPROVE(@adshmh): Support method level specific timeouts and allow the user to configure them.
2224
defaultJSONRPCRequestTimeoutMillisec = 10_000
2325
)
2426

@@ -106,22 +108,23 @@ func (rv *requestValidator) buildJSONRPCRequestContext(
106108
}, true
107109
}
108110

111+
// buildJSONRPCServicePayload builds a protocol payload for a JSONRPC request.
109112
func buildJSONRPCServicePayload(rpcType sharedtypes.RPCType, jsonrpcReq jsonrpc.Request) (protocol.Payload, error) {
110113
// DEV_NOTE: marshaling the request, rather than using the original payload, is necessary.
111114
// Otherwise, a request missing `id` field could fail.
112115
// See the Request struct in `jsonrpc` package for the details.
113116
reqBz, err := json.Marshal(jsonrpcReq)
114117
if err != nil {
115-
return protocol.Payload{}, err
118+
return protocol.EmptyErrorPayload(), err
116119
}
117120

118121
return protocol.Payload{
119-
Data: string(reqBz),
120-
// JSONRPC always uses POST
121-
Method: http.MethodPost,
122+
Data: string(reqBz),
123+
Method: http.MethodPost, // JSONRPC always uses POST
124+
Path: "", // JSONRPC does not use paths
125+
Headers: map[string]string{},
122126
TimeoutMillisec: defaultJSONRPCRequestTimeoutMillisec,
123-
// Add the RPCType hint, so protocol sets correct HTTP headers for the endpoint.
124-
RPCType: rpcType,
127+
RPCType: rpcType, // Add the RPCType hint the so protocol sets correct HTTP headers for the endpoint.
125128
}, nil
126129
}
127130

qos/cosmos/request_validator_rest.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import (
44
"errors"
55
"net/url"
66

7+
"github.com/pokt-network/poktroll/pkg/polylog"
8+
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
9+
710
"github.com/buildwithgrove/path/gateway"
811
qosobservations "github.com/buildwithgrove/path/observation/qos"
912
"github.com/buildwithgrove/path/protocol"
1013
"github.com/buildwithgrove/path/qos"
1114
"github.com/buildwithgrove/path/qos/jsonrpc"
12-
"github.com/pokt-network/poktroll/pkg/polylog"
13-
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
1415
)
1516

1617
// Default timeout for REST requests
18+
// TODO_IMPROVE(@adshmh): Support method level specific timeouts and allow the user to configure them.
1719
const defaultRESTRequestTimeoutMillisec = 30000
1820

1921
// validateRESTRequest validates a REST request by:
@@ -124,11 +126,10 @@ func buildRESTServicePayload(
124126
return protocol.Payload{
125127
Data: string(httpRequestBody),
126128
Method: httpRequestMethod,
129+
Path: path,
130+
Headers: map[string]string{},
127131
TimeoutMillisec: defaultRESTRequestTimeoutMillisec,
128-
// Add the RPCType hint, so protocol sets correct HTTP headers for the endpoint.
129-
RPCType: rpcType,
130-
// Set the request path, including raw query, if used.
131-
Path: path,
132+
RPCType: rpcType, // Add the RPCType hint, so protocol sets correct HTTP headers for the endpoint.
132133
}
133134
}
134135

qos/error_context.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ func (rec *RequestErrorContext) GetObservations() qosobservations.Observations {
6868
// It logs a warning and returns nil.
6969
// Implements the gateway.RequestQoSContext interface.
7070
func (rec *RequestErrorContext) GetServicePayload() protocol.Payload {
71-
rec.Logger.Warn().Msg("Invalid usage: RequestErrorContext.GetServicePayload() should never be called.")
72-
return protocol.Payload{}
71+
rec.Logger.Warn().Msg("SHOULD NEVER HAPPEN: RequestErrorContext.GetServicePayload() should never be called.")
72+
return protocol.EmptyErrorPayload()
7373
}
7474

7575
// UpdateWithResponse should never be called.
@@ -79,14 +79,14 @@ func (rec *RequestErrorContext) UpdateWithResponse(endpointAddr protocol.Endpoin
7979
rec.Logger.With(
8080
"endpoint_addr", endpointAddr,
8181
"endpoint_response_len", len(endpointSerializedResponse),
82-
).Warn().Msg("Invalid usage: RequestErrorContext.UpdateWithResponse() should never be called.")
82+
).Warn().Msg("SHOULD NEVER HAPPEN: RequestErrorContext.UpdateWithResponse() should never be called.")
8383
}
8484

8585
// UpdateWithResponse should never be called.
8686
// It logs a warning and returns a failing selector that logs a warning on all selection attempts.
8787
// Implements the gateway.RequestQoSContext interface.
8888
func (rec *RequestErrorContext) GetEndpointSelector() protocol.EndpointSelector {
89-
rec.Logger.Warn().Msg("Invalid usage: RequestErrorContext.GetEndpointSelector() should never be called.")
89+
rec.Logger.Warn().Msg("SHOULD NEVER HAPPEN: RequestErrorContext.GetEndpointSelector() should never be called.")
9090

9191
return errorTrackingSelector{
9292
logger: rec.Logger,
@@ -107,7 +107,7 @@ type errorTrackingSelector struct {
107107
func (ets errorTrackingSelector) Select(endpoints protocol.EndpointAddrList) (protocol.EndpointAddr, error) {
108108
ets.logger.With(
109109
"num_endpoints", len(endpoints),
110-
).Warn().Msg("Invalid usage: errorTrackingSelector.Select() should never be called.")
110+
).Warn().Msg("SHOULD NEVER HAPPEN: errorTrackingSelector.Select() should never be called.")
111111

112112
return protocol.EndpointAddr(""), errInvalidSelectorUsage
113113
}
@@ -116,7 +116,7 @@ func (ets errorTrackingSelector) Select(endpoints protocol.EndpointAddrList) (pr
116116
// It logs a warning and returns an invalid usage error.
117117
// Implements the protocol.EndpointSelector interface.
118118
func (ets errorTrackingSelector) SelectMultiple(endpoints protocol.EndpointAddrList, numEndpoints uint) (protocol.EndpointAddrList, error) {
119-
ets.logger.Warn().Msg("Invalid usage: errorTrackingSelector.SelectMultiple() should never be called.")
119+
ets.logger.Warn().Msg("SHOULD NEVER HAPPEN: errorTrackingSelector.SelectMultiple() should never be called.")
120120

121121
return nil, errInvalidSelectorUsage
122122
}

qos/evm/context.go

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import (
55
"net/http"
66

77
"github.com/pokt-network/poktroll/pkg/polylog"
8+
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
89

910
"github.com/buildwithgrove/path/gateway"
1011
qosobservations "github.com/buildwithgrove/path/observation/qos"
1112
"github.com/buildwithgrove/path/protocol"
1213
"github.com/buildwithgrove/path/qos/jsonrpc"
1314
)
1415

15-
// TODO_MVP(@adshmh): Support individual configuration of timeout for every service that uses EVM QoS.
16-
// The default timeout when sending a request to an EVM blockchain endpoint.
16+
// defaultServiceRequestTimeoutMillisec is the default timeout when sending a request to an EVM blockchain endpoint.
17+
// TODO_IMPROVE(@adshmh): Support method level specific timeouts and allow the user to configure them.
1718
const defaultServiceRequestTimeoutMillisec = 10_500
1819

1920
// requestContext provides the support required by the gateway
@@ -93,28 +94,22 @@ type requestContext struct {
9394
endpointSelectionMetadata EndpointSelectionMetadata
9495
}
9596

96-
// TODO_MVP(@adshmh): Ensure the JSONRPC request struct
97-
// can handle all valid service requests.
97+
// GetServicePayload implements the gateway.RequestQoSContext interface.
98+
// TODO_MVP(@adshmh): Ensure the JSONRPC request struct can handle all valid service requests.
9899
func (rc requestContext) GetServicePayload() protocol.Payload {
99100
reqBz, err := json.Marshal(rc.jsonrpcReq)
100101
if err != nil {
101-
// TODO_MVP(@adshmh): find a way to guarantee this never happens,
102-
// e.g. by storing the serialized form of the JSONRPC request
103-
// at the time of creating the request context.
104-
return protocol.Payload{}
102+
rc.logger.Error().Err(err).Msg("SHOULD RARELY HAPPEN: requestContext.GetServicePayload() should never fail marshaling the JSONRPC request.")
103+
return protocol.EmptyErrorPayload()
105104
}
106105

107106
return protocol.Payload{
108-
Data: string(reqBz),
109-
// Method is alway POST for EVM-based blockchains.
110-
Method: http.MethodPost,
111-
112-
// Path field is not used for EVM-based blockchains.
113-
114-
// TODO_IMPROVE: adjust the timeout based on the request method:
115-
// An endpoint may need more time to process certain requests,
116-
// as indicated by the request's method and/or parameters.
107+
Data: string(reqBz),
108+
Method: http.MethodPost, // Method is alway POST for EVM-based blockchains.
109+
Path: "", // Path field is not used for EVM-based blockchains.
110+
Headers: map[string]string{},
117111
TimeoutMillisec: defaultServiceRequestTimeoutMillisec,
112+
RPCType: sharedtypes.RPCType_JSON_RPC,
118113
}
119114
}
120115

qos/evm/error_context.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ func (ec *errorContext) GetObservations() qosobservations.Observations {
8080
// It logs a warning and returns nil.
8181
// Implements the gateway.RequestQoSContext interface.
8282
func (ec *errorContext) GetServicePayload() protocol.Payload {
83-
ec.logger.Warn().Msg("Invalid usage: errorContext.GetServicePayload() should never be called.")
84-
return protocol.Payload{}
83+
ec.logger.Warn().Msg("SHOULD NEVER HAPPEN: errorContext.GetServicePayload() should never be called.")
84+
return protocol.EmptyErrorPayload()
8585
}
8686

8787
// UpdateWithResponse should never be called.
@@ -91,14 +91,14 @@ func (ec *errorContext) UpdateWithResponse(endpointAddr protocol.EndpointAddr, e
9191
ec.logger.With(
9292
"endpoint_addr", endpointAddr,
9393
"endpoint_response_len", len(endpointSerializedResponse),
94-
).Warn().Msg("Invalid usage: errorContext.UpdateWithResponse() should never be called.")
94+
).Warn().Msg("SHOULD NEVER HAPPEN: errorContext.UpdateWithResponse() should never be called.")
9595
}
9696

9797
// UpdateWithResponse should never be called.
9898
// It logs a warning and returns a failing selector that logs a warning on all selection attempts.
9999
// Implements the gateway.RequestQoSContext interface.
100100
func (ec *errorContext) GetEndpointSelector() protocol.EndpointSelector {
101-
ec.logger.Warn().Msg("Invalid usage: errorContext.GetEndpointSelector() should never be called.")
101+
ec.logger.Warn().Msg("SHOULD NEVER HAPPEN: errorContext.GetEndpointSelector() should never be called.")
102102

103103
return errorTrackingSelector{
104104
logger: ec.logger,
@@ -119,7 +119,7 @@ type errorTrackingSelector struct {
119119
func (ets errorTrackingSelector) Select(endpoints protocol.EndpointAddrList) (protocol.EndpointAddr, error) {
120120
ets.logger.With(
121121
"num_endpoints", len(endpoints),
122-
).Warn().Msg("Invalid usage: errorTrackingSelector.Select() should never be called.")
122+
).Warn().Msg("SHOULD NEVER HAPPEN: errorTrackingSelector.Select() should never be called.")
123123

124124
return protocol.EndpointAddr(""), errInvalidSelectorUsage
125125
}

qos/noop/context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package noop
33
import (
44
"net/http"
55

6+
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
7+
68
"github.com/buildwithgrove/path/gateway"
79
qosobservations "github.com/buildwithgrove/path/observation/qos"
810
"github.com/buildwithgrove/path/protocol"
@@ -55,7 +57,10 @@ func (rc *requestContext) GetServicePayload() protocol.Payload {
5557
payload := protocol.Payload{
5658
Data: string(rc.httpRequestBody),
5759
Method: rc.httpRequestMethod,
60+
Path: "", // set below
61+
Headers: map[string]string{},
5862
TimeoutMillisec: rc.endpointResponseTimeoutMillisec,
63+
RPCType: sharedtypes.RPCType_UNKNOWN_RPC,
5964
}
6065
if rc.httpRequestPath != "" {
6166
payload.Path = rc.httpRequestPath

qos/solana/context.go

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77

88
"github.com/pokt-network/poktroll/pkg/polylog"
9+
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
910

1011
"github.com/buildwithgrove/path/gateway"
1112
qosobservations "github.com/buildwithgrove/path/observation/qos"
@@ -15,8 +16,8 @@ import (
1516
)
1617

1718
const (
18-
// The default timeout when sending a request to
19-
// a Solana blockchain endpoint.
19+
// defaultServiceRequestTimeoutMillisec is the default timeout when sending a request to a Solana blockchain endpoint.
20+
// TODO_IMPROVE(@adshmh): Support method level specific timeouts and allow the user to configure them.
2021
defaultServiceRequestTimeoutMillisec = 15_000
2122
)
2223

@@ -80,23 +81,17 @@ type requestContext struct {
8081
func (rc requestContext) GetServicePayload() protocol.Payload {
8182
reqBz, err := json.Marshal(rc.JSONRPCReq)
8283
if err != nil {
83-
// TODO_MVP(@adshmh): find a way to guarantee this never happens,
84-
// e.g. by storing the serialized form of the JSONRPC request
85-
// at the time of creating the request context.
86-
return protocol.Payload{}
84+
rc.logger.Error().Err(err).Msg("SHOULD RARELY HAPPEN: requestContext.GetServicePayload() should never fail marshaling the JSONRPC request.")
85+
return protocol.EmptyErrorPayload()
8786
}
8887

8988
return protocol.Payload{
90-
Data: string(reqBz),
91-
// Method is alway POST for Solana.
92-
Method: http.MethodPost,
93-
94-
// Path field is not used for Solana.
95-
96-
// TODO_IMPROVE: adjust the timeout based on the request method:
97-
// An endpoint may need more time to process certain requests,
98-
// as indicated by the request's method and/or parameters.
89+
Data: string(reqBz),
90+
Method: http.MethodPost, // Method is alway POST for Solana.
91+
Path: "", // Path field is not used for Solana.
92+
Headers: map[string]string{},
9993
TimeoutMillisec: defaultServiceRequestTimeoutMillisec,
94+
RPCType: sharedtypes.RPCType_JSON_RPC,
10095
}
10196
}
10297

0 commit comments

Comments
 (0)