Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions core/services/gateway/handlers/confidentialrelay/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@

globalNodeRateLimiter limits.RateLimiter
perNodeRateLimiters map[string]limits.RateLimiter
userRateLimiter limits.RateLimiter
requestTimeout time.Duration

activeRequests map[string]*activeRequest
Expand Down Expand Up @@ -171,6 +172,14 @@
perNodeRateLimiters[member.Address] = rl
}

// userRateLimiter bounds inbound user requests before they are fanned out to the
// DON. Without it, a single unauthenticated request amplifies into per-node
// attestation work; the limiters above only guard node responses.
userRateLimiter, err := limitsFactory.MakeRateLimiter(cresettings.Default.GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests_integration)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests_integration)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_race_tests)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)

Check failure on line 178 in core/services/gateway/handlers/confidentialrelay/handler.go

View workflow job for this annotation

GitHub Actions / Core Tests (go_core_fuzz)

cresettings.Default.GatewayConfidentialRelayUserRate undefined (type cresettings.Schema has no field or method GatewayConfidentialRelayUserRate)
if err != nil {
return nil, fmt.Errorf("failed to create user rate limiter: %w", err)
}

metrics, err := newMetrics()
if err != nil {
return nil, fmt.Errorf("failed to create metrics: %w", err)
Expand All @@ -183,6 +192,7 @@
requestTimeout: time.Duration(cfg.RequestTimeoutSec) * time.Second,
globalNodeRateLimiter: globalNodeRateLimiter,
perNodeRateLimiters: perNodeRateLimiters,
userRateLimiter: userRateLimiter,
activeRequests: make(map[string]*activeRequest),
mu: sync.RWMutex{},
stopCh: make(services.StopChan),
Expand Down Expand Up @@ -224,6 +234,9 @@
for _, rl := range h.perNodeRateLimiters {
err = errors.Join(err, rl.Close())
}
if h.userRateLimiter != nil {
err = errors.Join(err, h.userRateLimiter.Close())
}
return err
})
}
Expand Down Expand Up @@ -272,6 +285,13 @@
}

func (h *handler) HandleJSONRPCUserMessage(ctx context.Context, req jsonrpc.Request[json.RawMessage], callback gwhandlers.Callback) error {
// Shed load before any fan-out. Each forwarded request costs every DON node an
// attestation verification and a capabilities-registry lookup, so throttling here
// caps that amplification for unauthenticated ingress.
if !h.userRateLimiter.Allow(ctx) {
h.lggr.Debugw("user request rate limited", "requestID", req.ID)
return errors.New("request rate limit exceeded")
}
if req.ID == "" {
return errors.New("request ID cannot be empty")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,47 @@ func TestConfidentialRelayHandler_RateLimitedNode(t *testing.T) {
require.Error(t, err) // Should timeout
}

func TestConfidentialRelayHandler_RateLimitedUserIngress(t *testing.T) {
t.Parallel()
handlerConfig := Config{RequestTimeoutSec: 30}
methodConfig, err := json.Marshal(handlerConfig)
require.NoError(t, err)

lggr := logger.Test(t)
don := mocks.NewDON(t)
// F=0 single-node DON so a passing request fans out to exactly one node.
donConfig := &config.DONConfig{
DonId: "test_relay_don",
F: 0,
Members: []config.NodeConfig{nodeOne},
}
clock := clockwork.NewFakeClock()
limitsFactory := limits.Factory{Settings: cresettings.DefaultGetter, Logger: lggr}
h, err := NewHandler(methodConfig, donConfig, don, lggr, clock, limitsFactory)
require.NoError(t, err)
// Burst of 1 with a negligible refill rate: the first ingress request passes,
// the next is rejected before any fan-out to nodes.
h.userRateLimiter = limits.GlobalRateLimiter(rate.Limit(0.001), 1)

don.On("SendToNode", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()

params := json.RawMessage(`{"workflow_id":"wf1"}`)

// First request consumes the burst allowance and fans out.
cb1 := common.NewCallback()
req1 := jsonrpc.Request[json.RawMessage]{ID: "req-1", Method: MethodCapabilityExec, Params: &params}
require.NoError(t, h.HandleJSONRPCUserMessage(t.Context(), req1, cb1))

// Second request is rate limited at ingress and never reaches the DON.
cb2 := common.NewCallback()
req2 := jsonrpc.Request[json.RawMessage]{ID: "req-2", Method: MethodCapabilityExec, Params: &params}
err = h.HandleJSONRPCUserMessage(t.Context(), req2, cb2)
require.Error(t, err)
assert.Contains(t, err.Error(), "rate limit")

don.AssertNumberOfCalls(t, "SendToNode", 1)
}

func TestConfidentialRelayHandler_LateNodeResponse(t *testing.T) {
t.Parallel()
h, cb, _, _ := setupHandler(t, 4)
Expand Down
Loading