Skip to content

Gateway Node proxies user requests to Vault Node - #18420

Merged
secure-michelemin merged 23 commits into
developfrom
PRIV-96-e-2-e-gateway-vault-flow-with-mocks
Jul 11, 2025
Merged

secure-michelemin merged 23 commits into
developfrom
PRIV-96-e-2-e-gateway-vault-flow-with-mocks

Conversation

@DeividasK

@DeividasK DeividasK commented Jul 1, 2025 •

Copy link
Copy Markdown
Contributor

This PR introduces a new VaultHandler implementation and updates to error handling.

TODOs

  • Propagate new handler error
  • Cleanup expired pending requests
  • Switch to using beholder over prometheus

@DeividasK DeividasK changed the title Gateway Node => Vault Node implementation Gateway Node proxy user requests to Vault Node Jul 8, 2025
@DeividasK DeividasK changed the title Gateway Node proxy user requests to Vault Node Gateway Node proxies user requests to Vault Node Jul 8, 2025
cfg.RequestTimeoutSec = 30
}

userRateLimiter, _ := ratelimit.NewRateLimiter(cfg.UserRateLimiterConfig)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the second value that is being omitted here? (Not an error I hope 😬 ?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is error. we should handle it

ErrNotAllowlisted = errors.New("sender not allowlisted")
ErrRateLimited = errors.New("rate-limited")

promRequestInternalError = promauto.NewCounterVec(prometheus.CounterOpts{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use beholder rather than prometheus :)


userRateLimiter *ratelimit.RateLimiter
nodeRateLimiter *ratelimit.RateLimiter
requestTimeoutSec int

@cedric-cordenier cedric-cordenier Jul 9, 2025 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use a time.Duration here directly?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the timeout being used or enforced in our logic.
The gateway has a separate timeout for user request anyway.
If we want to use this timeout to just keep our pendingRequests queue bounded, we will need to implement a cleanup on timeout.
Currently the timeout is just used on a context when calling the don.SendToNode() method, but that doesn't affect our queue here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the timeout to affect the queue instead of just the don.SendToNode. This also effectively prunes the queue.


func NewHandler(methodConfig json.RawMessage, donConfig *config.DONConfig, don gw_handlers.DON, lggr logger.Logger) *handler {
var cfg Config
if err := json.Unmarshal(methodConfig, &cfg); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DeividasK A JSON unmarshal error should be a critical failure IMO; that way we catch it as soon as we deploy the job to the node.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just fail and propagate the error back.

requestTimeoutSec: cfg.RequestTimeoutSec,
userRateLimiter: userRateLimiter,
nodeRateLimiter: nodeRateLimiter,
pendingRequests: make(map[string]*pendingRequest),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we doing anything to ensure that this won't grow in an unbounded way? eg. expiring requests that haven't received a response in a long time

ConnectorMethod = "vault"
HandlerName = "VaultHandler"

promRequestInternalError = promauto.NewCounterVec(prometheus.CounterOpts{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beholder :)


func NewHandler(methodConfig json.RawMessage, donConfig *config.DONConfig, don gw_handlers.DON, lggr logger.Logger) *handler {
var cfg Config
if err := json.Unmarshal(methodConfig, &cfg); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just fail and propagate the error back.

cfg.RequestTimeoutSec = 30
}

userRateLimiter, _ := ratelimit.NewRateLimiter(cfg.UserRateLimiterConfig)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is error. we should handle it

return errors.New("vault service does not support legacy messages")
}

func (h *handler) HandleJSONRPCUserMessage(ctx context.Context, jsonRequest jsonrpc.Request[json.RawMessage], callbackCh chan<- gw_handlers.UserCallbackPayload) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit optional:
you can define a type for this generic
type JsonRawMessageRequest = jsonrpc.Request[json.RawMessage]

And then use this type everywhere for readability.

Just helps readability in case you use it in a lot of places.


rawResponse, err := jsonrpc.EncodeResponse(resp)
if err != nil {
return h.sendResponse(ctx, h.errorResponse(pendingRequest.req, api.NodeReponseEncodingError, fmt.Errorf("failed to marshal response: %w", err)), pendingRequest.callbackCh)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of fmt.Errorf, use:
errors.New("failed to marshal response: " + err.Error()))

I think linter would already be complaining about it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linter seems to be fine with it 🤷‍♂️

}
defer delete(h.pendingRequests, resp.ID)

rawResponse, err := jsonrpc.EncodeResponse(resp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resp object could still contain an error inside right?
We have to propagate that error in that case, instead of sending api.NoError.

h.lggr.Infof("Processed response for request %s from node %s", resp.ID, nodeAddr)
return nil
case <-ctx.Done():
promRequestInternalError.WithLabelValues(h.donConfig.DonId, api.RequestTimeoutError.String()).Inc()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failure necessarily may not be a timeout error.


userRateLimiter *ratelimit.RateLimiter
nodeRateLimiter *ratelimit.RateLimiter
requestTimeoutSec int

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the timeout being used or enforced in our logic.
The gateway has a separate timeout for user request anyway.
If we want to use this timeout to just keep our pendingRequests queue bounded, we will need to implement a cleanup on timeout.
Currently the timeout is just used on a context when calling the don.SendToNode() method, but that doesn't affect our queue here.

@github-actions

Copy link
Copy Markdown
Contributor

Flakeguard Summary

Ran new or updated tests between develop and 31cdbf2 (PRIV-96-e-2-e-gateway-vault-flow-with-mocks).

View Flaky Detector Details | Compare Changes

Found Flaky Tests ❌

4 Results
Name Pass Ratio Panicked? Timed Out? Race? Runs Successes Failures Skips Package Package Panicked? Avg Duration Code Owners
TestVaultHandler_HandleJSONRPCUserMessage/empty_params_error 0% false false false 3 0 3 0 github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/vault false 0s Unknown
TestVaultHandler_HandleJSONRPCUserMessage/happy_path 0% false false false 3 0 3 0 github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/vault false 0s Unknown
TestVaultHandler_HandleJSONRPCUserMessage/invalid_params_error 0% false false false 3 0 3 0 github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/vault false 0s Unknown
TestVaultHandler_HandleJSONRPCUserMessage/unsupported_method 0% false false false 3 0 3 0 github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/vault false 0s Unknown

Artifacts

For detailed logs of the failed tests, please refer to the artifact failed-test-results-with-logs.json.

@github-actions

Copy link
Copy Markdown
Contributor

Flakeguard Summary

Ran new or updated tests between develop and 50b9b60 (PRIV-96-e-2-e-gateway-vault-flow-with-mocks).

View Flaky Detector Details | Compare Changes

Found Flaky Tests ❌

2 Results
Name Pass Ratio Panicked? Timed Out? Race? Runs Successes Failures Skips Package Package Panicked? Avg Duration Code Owners
TestVaultHandler_HandleJSONRPCUserMessage 0% true false false 3 0 3 0 github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/vault true 0s Unknown
TestVaultHandler_HandleJSONRPCUserMessage/happy_path 0% true false false 3 0 3 0 github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/vault true 0s Unknown

Artifacts

For detailed logs of the failed tests, please refer to the artifact failed-test-results-with-logs.json.

@cl-sonarqube-production

Copy link
Copy Markdown

func (h *handler) Start(ctx context.Context) error {
return h.StartOnce("VaultHandler", func() error {
h.lggr.Info("starting vault handler")
ctx, _ := h.stopCh.NewCtx()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should call the cancel func by deferring it inside the go func on line 145

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And maybe calling <-ctx.Done() on line 151 for consistency

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address this in a follow up PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants