From 0d23db5149a55ac9235abc5da78526d78bfe5db1 Mon Sep 17 00:00:00 2001 From: Justin Kaseman Date: Wed, 10 Jun 2026 05:26:20 -0700 Subject: [PATCH 1/3] (feat): Use per owner max ciphertext size (#22750) --- core/capabilities/vault/capability.go | 2 +- core/services/gateway/handlers/vault/handler.go | 2 +- core/services/ocr2/plugins/vault/plugin.go | 2 +- core/services/ocr2/plugins/vault/plugin_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/capabilities/vault/capability.go b/core/capabilities/vault/capability.go index 87a7c85441a..4a4045b0cca 100644 --- a/core/capabilities/vault/capability.go +++ b/core/capabilities/vault/capability.go @@ -342,7 +342,7 @@ func NewCapability( if err != nil { return nil, fmt.Errorf("could not create request batch size limiter: %w", err) } - ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.VaultCiphertextSizeLimit) + ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.PerOwner.VaultCiphertextSizeLimit) if err != nil { return nil, fmt.Errorf("could not create ciphertext size limiter: %w", err) } diff --git a/core/services/gateway/handlers/vault/handler.go b/core/services/gateway/handlers/vault/handler.go index 94e64ac1605..51460c8b02b 100644 --- a/core/services/gateway/handlers/vault/handler.go +++ b/core/services/gateway/handlers/vault/handler.go @@ -231,7 +231,7 @@ func newHandlerWithAuthorizer(methodConfig json.RawMessage, donConfig *config.DO if err != nil { return nil, fmt.Errorf("could not create request batch size limiter: %w", err) } - ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.VaultCiphertextSizeLimit) + ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.PerOwner.VaultCiphertextSizeLimit) if err != nil { return nil, fmt.Errorf("could not create ciphertext size limiter: %w", err) } diff --git a/core/services/ocr2/plugins/vault/plugin.go b/core/services/ocr2/plugins/vault/plugin.go index d6eb8614f29..25371012c76 100644 --- a/core/services/ocr2/plugins/vault/plugin.go +++ b/core/services/ocr2/plugins/vault/plugin.go @@ -175,7 +175,7 @@ func (r *ReportingPluginFactory) pollForKeyMaterial(ctx context.Context, instanc } func newReportingPluginConfigLimiters(factory limits.Factory) (*ReportingPluginConfig, error) { - maxCiphertextLengthBytesLimiter, err := limits.MakeUpperBoundLimiter(factory, cresettings.Default.VaultCiphertextSizeLimit) + maxCiphertextLengthBytesLimiter, err := limits.MakeUpperBoundLimiter(factory, cresettings.Default.PerOwner.VaultCiphertextSizeLimit) if err != nil { return nil, fmt.Errorf("VaultCiphertextSizeLimit: %w", err) } diff --git a/core/services/ocr2/plugins/vault/plugin_test.go b/core/services/ocr2/plugins/vault/plugin_test.go index 6485367fc2f..cc5754d9344 100644 --- a/core/services/ocr2/plugins/vault/plugin_test.go +++ b/core/services/ocr2/plugins/vault/plugin_test.go @@ -7924,7 +7924,7 @@ func TestPlugin_broadcastBlobPayloads(t *testing.T) { func TestProperty_broadcastBlobPayloads_MaxSizePayloadsWithinBlobLimit(t *testing.T) { maxRequestBatchSize := cresettings.Default.VaultRequestBatchSizeLimit.DefaultValue - maxCiphertextBytes := cresettings.Default.VaultCiphertextSizeLimit.DefaultValue + maxCiphertextBytes := cresettings.Default.PerOwner.VaultCiphertextSizeLimit.DefaultValue maxIDKeySize := cresettings.Default.VaultIdentifierKeySizeLimit.DefaultValue maxIDOwnerSize := cresettings.Default.VaultIdentifierOwnerSizeLimit.DefaultValue maxIDNamespaceSize := cresettings.Default.VaultIdentifierNamespaceSizeLimit.DefaultValue From c0c25c455c622fd124436ed384f95d352ce8a79b Mon Sep 17 00:00:00 2001 From: Cedric Date: Thu, 11 Jun 2026 11:29:31 +0100 Subject: [PATCH 2/3] CRE-4857: Small mtls improvements (#22787) - Add a concurrency limiter for mtls requests - Fail requests with invalid credentials before acquiring a slot --- .../handlers/capabilities/v2/http_handler.go | 33 ++++- .../capabilities/v2/http_handler_test.go | 75 ++++++++++ core/services/gateway/network/httpclient.go | 49 +++++- .../gateway/network/httpclient_mtls_test.go | 140 ++++++++++++++++-- 4 files changed, 277 insertions(+), 20 deletions(-) diff --git a/core/services/gateway/handlers/capabilities/v2/http_handler.go b/core/services/gateway/handlers/capabilities/v2/http_handler.go index d8465aeadca..f2fc9dd87ba 100644 --- a/core/services/gateway/handlers/capabilities/v2/http_handler.go +++ b/core/services/gateway/handlers/capabilities/v2/http_handler.go @@ -50,6 +50,7 @@ type gatewayHandler struct { globalNodeRateLimiter limits.RateLimiter // Global rate limiter shared across all incoming node requests from workflow DON perNodeRateLimiters map[string]limits.RateLimiter // Per-node rate limiters keyed by node address, one independent bucket per DON member mtlsRequestRateLimiter limits.RateLimiter + mtlsConcurrencyLimiter limits.ResourcePoolLimiter[int] // Bounds the number of in-flight outbound mTLS requests wg sync.WaitGroup stopCh services.StopChan responseCache ResponseCache // Caches HTTP responses to avoid redundant requests for outbound HTTP actions @@ -141,6 +142,11 @@ func NewGatewayHandler(handlerConfig json.RawMessage, donConfig *config.DONConfi return nil, fmt.Errorf("failed to create mtls rate limiter: %w", err) } + mtlsConcurrencyLimiter, err := limits.MakeResourcePoolLimiter(lf, cresettings.Default.GatewayHTTPActionMtlsConcurrencyLimit) + if err != nil { + return nil, fmt.Errorf("failed to create mtls concurrency limiter: %w", err) + } + metrics, err := metrics.NewMetrics(donConfig) if err != nil { return nil, fmt.Errorf("failed to initialize metrics: %w", err) @@ -156,6 +162,7 @@ func NewGatewayHandler(handlerConfig json.RawMessage, donConfig *config.DONConfi globalNodeRateLimiter: globalNodeRateLimiter, perNodeRateLimiters: perNodeRateLimiters, mtlsRequestRateLimiter: mtlsRequestRateLimiter, + mtlsConcurrencyLimiter: mtlsConcurrencyLimiter, stopCh: make(services.StopChan), responseCache: newResponseCache(lggr, cfg.OutboundRequestCacheTTLMs, metrics), triggerHandler: triggerHandler, @@ -272,13 +279,6 @@ func (h *gatewayHandler) send(ctx context.Context, httpReq network.HTTPRequest, return h.httpClient.Send(ctx, httpReq) } - // We don't have access to the org here, so this will fall back to the environment default (=false). - // That's appropriate because all fields set on the request come from untrusted nodes. - // The capability separately applies an org-specific check. - if !h.mtlsRequestRateLimiter.Allow(ctx) { - return nil, fmt.Errorf("global mtls request rate limit exceeded: %w", network.ErrBlockedRequest) - } - if h.httpClientFactory == nil { return nil, errors.New("nil http client factory, cannot make mtls request") } @@ -290,16 +290,29 @@ func (h *gatewayHandler) send(ctx context.Context, httpReq network.HTTPRequest, // b) we apply rate limits limiting the ability of sending nodes to spam requests // c) we apply per-owner rate limits in the action capability in the // workflow node limiting the ability of users to abuse this flow by spamming Mtls requests. + // The client enforces the mtls concurrency limit internally (on the request's + // capped-timeout context) before delegating to the underlying transport. client, err := h.httpClientFactory(network.HTTPClientConfig{ Mtls: &gateway_common.MtlsAuth{ PrivateKey: req.Mtls.PrivateKey, Certificate: req.Mtls.Certificate, }, + ConcurrencyLimiter: h.mtlsConcurrencyLimiter, }) if err != nil { return nil, fmt.Errorf("failed to instantiate http client for mtls request: %w", err) } + // We don't have access to the org here, so this will fall back to the environment default (=false). + // That's appropriate because all fields set on the request come from untrusted nodes. + // The capability separately applies an org-specific check. + + // Note: we intentionally consume the rate-limit after instantiating the client so that a malicious user + // can't send requests with invalid mtls credentials and thus cheaply consume global tokens. + if !h.mtlsRequestRateLimiter.Allow(ctx) { + return nil, fmt.Errorf("global mtls request rate limit exceeded: %w", network.ErrBlockedRequest) + } + return client.Send(ctx, httpReq) } @@ -478,6 +491,12 @@ func (h *gatewayHandler) Close() error { h.lggr.Errorw("failed to close per-node rate limiter", "nodeAddr", nodeAddr, "err", err) } } + if err = h.mtlsRequestRateLimiter.Close(); err != nil { + h.lggr.Errorw("failed to close mtls request rate limiter", "err", err) + } + if err = h.mtlsConcurrencyLimiter.Close(); err != nil { + h.lggr.Errorw("failed to close mtls concurrency limiter", "err", err) + } close(h.stopCh) h.wg.Wait() return nil diff --git a/core/services/gateway/handlers/capabilities/v2/http_handler_test.go b/core/services/gateway/handlers/capabilities/v2/http_handler_test.go index 0c72c604612..9ff433558ed 100644 --- a/core/services/gateway/handlers/capabilities/v2/http_handler_test.go +++ b/core/services/gateway/handlers/capabilities/v2/http_handler_test.go @@ -947,6 +947,9 @@ func TestGatewayHandler_Send_NoMtls_UsesDefaultClient(t *testing.T) { func TestGatewayHandler_Send_MtlsBlockedByRateLimit(t *testing.T) { handler := createTestHandler(t) handler.mtlsRequestRateLimiter = limits.GlobalRateLimiter(0, 0) + handler.httpClientFactory = func(config network.HTTPClientConfig) (network.HTTPClient, error) { + return httpmocks.NewHTTPClient(t), nil + } httpReq := network.HTTPRequest{Method: "GET", URL: "https://example.com/api"} outboundReq := gateway_common.OutboundHTTPRequest{ @@ -965,6 +968,41 @@ func TestGatewayHandler_Send_MtlsBlockedByRateLimit(t *testing.T) { require.Contains(t, err.Error(), "global mtls request rate limit exceeded") } +// TestGatewayHandler_Send_MtlsPassesConcurrencyLimiterToFactory verifies the +// handler hands its shared mtls concurrency limiter to the client factory. The +// limiter is enforced inside the HTTP client (on the request's capped-timeout +// context); that enforcement is covered by the network package tests. +func TestGatewayHandler_Send_MtlsPassesConcurrencyLimiterToFactory(t *testing.T) { + handler := createTestHandler(t) + handler.mtlsRequestRateLimiter = limits.GlobalRateLimiter(100, 100) + + httpReq := network.HTTPRequest{Method: "GET", URL: "https://example.com/api"} + outboundReq := gateway_common.OutboundHTTPRequest{ + Method: "GET", + URL: "https://example.com/api", + Mtls: &gateway_common.MtlsAuth{ + PrivateKey: []byte("private-key"), + Certificate: []byte("certificate"), + }, + } + + expectedResp := &network.HTTPResponse{StatusCode: 200, Body: []byte("ok")} + mtlsClient := httpmocks.NewHTTPClient(t) + mtlsClient.EXPECT().Send(mock.Anything, httpReq).Return(expectedResp, nil).Once() + + var capturedConfig network.HTTPClientConfig + handler.httpClientFactory = func(config network.HTTPClientConfig) (network.HTTPClient, error) { + capturedConfig = config + return mtlsClient, nil + } + + resp, err := handler.send(testutils.Context(t), httpReq, outboundReq) + require.NoError(t, err) + require.Equal(t, expectedResp, resp) + require.NotNil(t, capturedConfig.ConcurrencyLimiter, "handler must pass its mtls concurrency limiter to the client factory") + require.Equal(t, handler.mtlsConcurrencyLimiter, capturedConfig.ConcurrencyLimiter) +} + func TestGatewayHandler_Send_MtlsUsesFactory(t *testing.T) { handler := createTestHandler(t) handler.mtlsRequestRateLimiter = limits.GlobalRateLimiter(100, 100) @@ -1026,6 +1064,37 @@ func TestGatewayHandler_Send_MtlsFactoryError(t *testing.T) { require.ErrorIs(t, err, factoryErr, "factory error should be wrapped and discoverable via errors.Is") } +// TestGatewayHandler_Send_InvalidMtlsCertDoesNotConsumeGlobalTokens verifies that a +// request carrying invalid mTLS credentials does not consume a global rate-limit token. +// Otherwise a malicious user could cheaply drain the shared mtls token bucket by spamming +// requests with bogus certificates. It uses the real HTTP client factory so that the +// production code path is what rejects the certificate as invalid. +func TestGatewayHandler_Send_InvalidMtlsCertDoesNotConsumeGlobalTokens(t *testing.T) { + handler := createTestHandler(t) + // Burst of exactly 1: only a single mtls request may pass the rate limiter. + handler.mtlsRequestRateLimiter = limits.GlobalRateLimiter(1, 1) + handler.httpClientFactory = network.NewHTTPClientFactory(network.HTTPClientConfig{}, logger.Test(t)) + + httpReq := network.HTTPRequest{Method: "GET", URL: "https://example.com/api"} + outboundReq := gateway_common.OutboundHTTPRequest{ + Method: "GET", + URL: "https://example.com/api", + Mtls: &gateway_common.MtlsAuth{PrivateKey: []byte("not-a-key"), Certificate: []byte("not-a-cert")}, + } + + ctx := testutils.Context(t) + resp, err := handler.send(ctx, httpReq, outboundReq) + require.Error(t, err) + require.Nil(t, resp) + require.Contains(t, err.Error(), "failed to parse MtlsAuth into KeyPair", + "the real client factory should reject the invalid certificate material") + + // The single available token must still be present: the failed request above must not + // have consumed it. + require.True(t, handler.mtlsRequestRateLimiter.Allow(ctx), + "global mtls rate-limit token must not be consumed by a request with an invalid certificate") +} + // TestGatewayHandler_Send_MtlsRoutesThroughCallbackOnly_DefaultClientUntouched // verifies that an mTLS request flowing through the full callback path does not // touch the default (shared) http client even when the factory returns a working @@ -1062,6 +1131,9 @@ func TestGatewayHandler_Send_MtlsRoutesThroughCallbackOnly_DefaultClientUntouche func TestGatewayHandler_Send_MtlsBlockedRequestIsValidationError(t *testing.T) { handler := createTestHandler(t) handler.mtlsRequestRateLimiter = limits.GlobalRateLimiter(0, 0) + handler.httpClientFactory = func(config network.HTTPClientConfig) (network.HTTPClient, error) { + return httpmocks.NewHTTPClient(t), nil + } httpReq := network.HTTPRequest{Method: "GET", URL: "https://example.com/api", Timeout: 5 * time.Second} outboundReq := gateway_common.OutboundHTTPRequest{ @@ -1085,6 +1157,9 @@ func TestGatewayHandler_Send_MtlsBlockedRequestIsValidationError(t *testing.T) { // meaning mtls is blocked out of the box. func TestGatewayHandler_Send_MtlsRateLimitEnabledByDefault(t *testing.T) { handler := createTestHandler(t) + handler.httpClientFactory = func(config network.HTTPClientConfig) (network.HTTPClient, error) { + return httpmocks.NewHTTPClient(t), nil + } httpReq := network.HTTPRequest{Method: "GET", URL: "https://example.com/api"} outboundReq := gateway_common.OutboundHTTPRequest{ diff --git a/core/services/gateway/network/httpclient.go b/core/services/gateway/network/httpclient.go index 1d74c5709a3..ea9e6295fa9 100644 --- a/core/services/gateway/network/httpclient.go +++ b/core/services/gateway/network/httpclient.go @@ -20,6 +20,7 @@ import ( "github.com/doyensec/safeurl" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/types/gateway" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -51,6 +52,11 @@ type HTTPClientConfig struct { // Mtls, when set, configures the client to present the supplied client // certificate for mutual TLS. Mtls *gateway.MtlsAuth + + // ConcurrencyLimiter, when set together with Mtls, bounds the number of + // in-flight mTLS requests. The limiter is acquired on the request's + // (capped) context so waiters self-evict at the request timeout. + ConcurrencyLimiter limits.ResourcePoolLimiter[int] } // merge returns a copy of c with any set fields from override applied on top. @@ -98,6 +104,9 @@ func (c HTTPClientConfig) merge(override HTTPClientConfig) HTTPClientConfig { if override.Mtls != nil { merged.Mtls = override.Mtls } + if override.ConcurrencyLimiter != nil { + merged.ConcurrencyLimiter = override.ConcurrencyLimiter + } return merged } @@ -223,8 +232,32 @@ func responseHeadersFromNetHeader(h http.Header) (map[string]string, map[string] return headers, multiHeaders } +// httpDoer is the subset of the HTTP client used by httpClient. It is satisfied +// by *safeurl.WrappedClient and by concurrencyLimitedClient, which decorates it. +type httpDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// concurrencyLimitedClient bounds the number of in-flight requests delegated to +// the underlying client. The slot is acquired on the request's context, so a +// waiter self-evicts when that context (carrying the capped request timeout) +// expires rather than blocking indefinitely. +type concurrencyLimitedClient struct { + client httpDoer + limiter limits.ResourcePoolLimiter[int] +} + +func (c *concurrencyLimitedClient) Do(req *http.Request) (*http.Response, error) { + free, err := c.limiter.Wait(req.Context(), 1) + if err != nil { + return nil, fmt.Errorf("mtls concurrency limit exceeded: %w", ErrBlockedRequest) + } + defer free() + return c.client.Do(req) +} + type httpClient struct { - client *safeurl.WrappedClient + client httpDoer config HTTPClientConfig lggr logger.Logger metrics *httpClientMetrics @@ -259,6 +292,8 @@ func NewHTTPClient(config HTTPClientConfig, lggr logger.Logger) (HTTPClient, err SetCheckRedirect(disableRedirects). SetTransport(defaultTransport) + var client httpDoer + if config.Mtls != nil { // Defence-in-depth protection against accidental reuse // of the HTTP client leading to auth'd connections leaking across @@ -276,6 +311,16 @@ func NewHTTPClient(config HTTPClientConfig, lggr logger.Logger) (HTTPClient, err MinVersion: tls.VersionTLS12, } safeConfigBuilder.SetTransport(defaultTransport) + + if config.ConcurrencyLimiter == nil { + return nil, errors.New("mtls requires a ConcurrencyLimiter") + } + client = &concurrencyLimitedClient{ + client: safeurl.Client(safeConfigBuilder.Build()), + limiter: config.ConcurrencyLimiter, + } + } else { + client = safeurl.Client(safeConfigBuilder.Build()) } metrics, err := newHTTPClientMetrics() @@ -285,7 +330,7 @@ func NewHTTPClient(config HTTPClientConfig, lggr logger.Logger) (HTTPClient, err return &httpClient{ config: config, - client: safeurl.Client(safeConfigBuilder.Build()), + client: client, lggr: lggr, metrics: metrics, }, nil diff --git a/core/services/gateway/network/httpclient_mtls_test.go b/core/services/gateway/network/httpclient_mtls_test.go index 46b57b26e96..1d8f44ed8ab 100644 --- a/core/services/gateway/network/httpclient_mtls_test.go +++ b/core/services/gateway/network/httpclient_mtls_test.go @@ -1,6 +1,7 @@ package network import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -17,12 +18,43 @@ import ( "testing" "time" + "github.com/doyensec/safeurl" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/types/gateway" ) +// testMtlsLimiter returns a generously-sized concurrency limiter for tests that +// only need mTLS construction to succeed (the limit itself is not exercised). +func testMtlsLimiter() limits.ResourcePoolLimiter[int] { + return limits.GlobalResourcePoolLimiter[int](50) +} + +// doerFunc adapts a function to the httpDoer interface. +type doerFunc func(*http.Request) (*http.Response, error) + +func (f doerFunc) Do(r *http.Request) (*http.Response, error) { return f(r) } + +// underlyingSafeURLClient unwraps the *httpClient's doer down to the safeurl +// client, transparently stepping through the concurrencyLimitedClient decorator +// that the mTLS path installs. +func underlyingSafeURLClient(t *testing.T, hc *httpClient) *safeurl.WrappedClient { + t.Helper() + switch c := hc.client.(type) { + case *safeurl.WrappedClient: + return c + case *concurrencyLimitedClient: + wc, ok := c.client.(*safeurl.WrappedClient) + require.True(t, ok, "expected *safeurl.WrappedClient under the limiter, got %T", c.client) + return wc + default: + t.Fatalf("unexpected client type %T", hc.client) + return nil + } +} + // pemKeyPair generates a fresh ECDSA P-256 key and a self-signed certificate for // the given common name. Returns PEM-encoded certificate and private key bytes // in the same form `tls.X509KeyPair` accepts. @@ -102,7 +134,8 @@ func TestNewHTTPClientWithOptions_MtlsValidKeyPair(t *testing.T) { certPEM, keyPEM, _ := pemKeyPair(t, "client") client, err := NewHTTPClient(HTTPClientConfig{ - Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + ConcurrencyLimiter: testMtlsLimiter(), }, lggr) require.NoError(t, err) require.NotNil(t, client) @@ -123,7 +156,8 @@ func TestNewHTTPClientFactory_MtlsFlow(t *testing.T) { t.Run("valid mtls config returns a working client", func(t *testing.T) { certPEM, keyPEM, _ := pemKeyPair(t, "client") client, err := factory(HTTPClientConfig{ - Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + ConcurrencyLimiter: testMtlsLimiter(), }) require.NoError(t, err) require.NotNil(t, client) @@ -189,9 +223,10 @@ func TestHTTPClient_MtlsPresentsCertificateToServer(t *testing.T) { client, err := NewHTTPClient( HTTPClientConfig{ - AllowedIPs: []string{host}, - AllowedPorts: []int{port}, - Mtls: &gateway.MtlsAuth{Certificate: clientCertPEM, PrivateKey: clientKeyPEM}, + AllowedIPs: []string{host}, + AllowedPorts: []int{port}, + Mtls: &gateway.MtlsAuth{Certificate: clientCertPEM, PrivateKey: clientKeyPEM}, + ConcurrencyLimiter: testMtlsLimiter(), }, lggr, ) @@ -199,7 +234,7 @@ func TestHTTPClient_MtlsPresentsCertificateToServer(t *testing.T) { hc, ok := client.(*httpClient) require.True(t, ok) - transport, ok := hc.client.Client.Transport.(*http.Transport) + transport, ok := underlyingSafeURLClient(t, hc).Client.Transport.(*http.Transport) require.True(t, ok) require.NotNil(t, transport.TLSClientConfig) transport.TLSClientConfig.RootCAs = serverPool @@ -259,7 +294,7 @@ func TestHTTPClient_NoMtls_RejectedByMtlsServer(t *testing.T) { // Trust the server cert so we get past server-cert verification — the TLS // failure should come from the missing *client* cert. hc := client.(*httpClient) - transport := hc.client.Client.Transport.(*http.Transport) + transport := underlyingSafeURLClient(t, hc).Client.Transport.(*http.Transport) if transport.TLSClientConfig == nil { transport.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12} } @@ -285,19 +320,102 @@ func TestHTTPClient_MtlsDisablesKeepAlives(t *testing.T) { certPEM, keyPEM, _ := pemKeyPair(t, "client") client, err := NewHTTPClient(HTTPClientConfig{ - Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + ConcurrencyLimiter: testMtlsLimiter(), }, lggr) require.NoError(t, err) hc, ok := client.(*httpClient) require.True(t, ok) - require.NotNil(t, hc.client.Client) - transport, ok := hc.client.Client.Transport.(*http.Transport) - require.True(t, ok, "expected *http.Transport, got %T", hc.client.Client.Transport) + sc := underlyingSafeURLClient(t, hc) + require.NotNil(t, sc.Client) + transport, ok := sc.Client.Transport.(*http.Transport) + require.True(t, ok, "expected *http.Transport, got %T", sc.Client.Transport) require.True(t, transport.DisableKeepAlives, "keep-alives must be disabled to prevent auth'd connection reuse across users") require.Equal(t, 10*time.Second, transport.TLSHandshakeTimeout, "TLS handshake timeout should be set (safeurl defaults to 0 == no timeout)") require.NotNil(t, transport.TLSClientConfig) require.Len(t, transport.TLSClientConfig.Certificates, 1, "client certificate must be installed on the transport") } + +func TestNewHTTPClient_MtlsRequiresConcurrencyLimiter(t *testing.T) { + t.Parallel() + certPEM, keyPEM, _ := pemKeyPair(t, "client") + + _, err := NewHTTPClient(HTTPClientConfig{ + Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + }, logger.Test(t)) + require.Error(t, err) + require.Contains(t, err.Error(), "mtls requires a ConcurrencyLimiter") +} + +// TestConcurrencyLimitedClient_BlocksWhenSaturated verifies a held slot blocks a +// second request until its context expires, and that completing the first frees +// the slot. +func TestConcurrencyLimitedClient_BlocksWhenSaturated(t *testing.T) { + t.Parallel() + + entered := make(chan struct{}) + release := make(chan struct{}) + wrapped := &concurrencyLimitedClient{ + client: doerFunc(func(*http.Request) (*http.Response, error) { + entered <- struct{}{} + <-release + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil + }), + limiter: limits.GlobalResourcePoolLimiter[int](1), + } + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://example.com", nil) + require.NoError(t, err) + + // First request takes the only slot and blocks inside Do. + done := make(chan struct{}) + go func() { defer close(done); _, _ = wrapped.Do(req) }() + <-entered + + avail, err := wrapped.limiter.Available(context.Background()) + require.NoError(t, err) + require.Equal(t, 0, avail, "the single slot should be held") + + // Second request is denied once its (short) context expires. + ctx2, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + _, err = wrapped.Do(req.Clone(ctx2)) + require.ErrorIs(t, err, ErrBlockedRequest) + require.Contains(t, err.Error(), "mtls concurrency limit exceeded") + + // Releasing the first request frees the slot. + close(release) + <-done + avail, err = wrapped.limiter.Available(context.Background()) + require.NoError(t, err) + require.Equal(t, 1, avail, "slot must be freed after the request completes") +} + +// TestHTTPClient_MtlsConcurrencyLimit_RespectsRequestTimeout proves the wait is +// bounded by the (capped) request timeout rather than blocking indefinitely: +// with a zero-capacity pool no slot can ever be acquired, so Send must return +// the concurrency error around the request timeout. +func TestHTTPClient_MtlsConcurrencyLimit_RespectsRequestTimeout(t *testing.T) { + t.Parallel() + lggr := logger.Test(t) + certPEM, keyPEM, _ := pemKeyPair(t, "client") + + client, err := NewHTTPClient(HTTPClientConfig{ + Mtls: &gateway.MtlsAuth{Certificate: certPEM, PrivateKey: keyPEM}, + ConcurrencyLimiter: limits.GlobalResourcePoolLimiter[int](0), + }, lggr) + require.NoError(t, err) + + start := time.Now() + _, err = client.Send(t.Context(), HTTPRequest{ + Method: http.MethodGet, + URL: "https://example.com", + Timeout: 100 * time.Millisecond, + }) + require.ErrorIs(t, err, ErrBlockedRequest) + require.Contains(t, err.Error(), "mtls concurrency limit exceeded") + require.Less(t, time.Since(start), time.Second, "wait must be bounded by the request timeout") +} From 878c2fc7066ea832c63a68f798b4ed07e52d7f03 Mon Sep 17 00:00:00 2001 From: Cedric Cordenier Date: Thu, 11 Jun 2026 12:41:09 +0100 Subject: [PATCH 3/3] Bump chainlink-common --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- deployment/go.mod | 2 +- deployment/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- system-tests/lib/go.mod | 2 +- system-tests/lib/go.sum | 4 ++-- system-tests/tests/go.mod | 2 +- system-tests/tests/go.sum | 4 ++-- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index b5befe40ce8..12ead1c8f9a 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -43,7 +43,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.101 github.com/smartcontractkit/chainlink-automation v0.8.1 github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260506144252-c100eabfda74 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index bc6f1cf8e17..397b95c27ae 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1573,8 +1573,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h1:IMopuENFVS63AerRELdfWo6o60UNUidcldJOxJLmk24= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= diff --git a/deployment/go.mod b/deployment/go.mod index 06721ed5445..a8d13589956 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -42,7 +42,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260504204047-af9826978b72 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 diff --git a/deployment/go.sum b/deployment/go.sum index 02520060e83..fbbe48ee0da 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1382,8 +1382,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260428205321-9ce8f4c44d23 h1:1Rt4HLpwbRN1YtBFcbsxSJYIiUP2wJ11qizevOEeCrs= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260428205321-9ce8f4c44d23/go.mod h1:V+wrhuNve+JiFwoBr25d6y0lL1rYSCSJhTFyloL3ueo= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= diff --git a/go.mod b/go.mod index ca845c99e38..a5e6a2f7c06 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a diff --git a/go.sum b/go.sum index 11b4441ae4a..6ddc5c5d023 100644 --- a/go.sum +++ b/go.sum @@ -1181,8 +1181,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h1:IMopuENFVS63AerRELdfWo6o60UNUidcldJOxJLmk24= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index c3a87e3a8a4..6e17c4a6c28 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -29,7 +29,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260506144252-c100eabfda74 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260506144252-c100eabfda74 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260609161557-8ceae53b8ab1 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 9ecf440f9f0..3386d3ceecb 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1367,8 +1367,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260428205321-9ce8f4c44d23 h1:1Rt4HLpwbRN1YtBFcbsxSJYIiUP2wJ11qizevOEeCrs= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260428205321-9ce8f4c44d23/go.mod h1:V+wrhuNve+JiFwoBr25d6y0lL1rYSCSJhTFyloL3ueo= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 72e6c0a24dd..a2aa35f1851 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -20,7 +20,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260506144252-c100eabfda74 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260506144252-c100eabfda74 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260609161557-8ceae53b8ab1 github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.1 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 363b3cf031f..a4976f92959 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1633,8 +1633,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260428205321-9ce8f4c44d23 h1:1Rt4HLpwbRN1YtBFcbsxSJYIiUP2wJ11qizevOEeCrs= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260428205321-9ce8f4c44d23/go.mod h1:V+wrhuNve+JiFwoBr25d6y0lL1rYSCSJhTFyloL3ueo= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 3f60ad58dde..5ea9d50f7c9 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -33,7 +33,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.101 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260507123701-77fc93b573bb github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260609161557-8ceae53b8ab1 diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 8089215f605..8250a66d4da 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1540,8 +1540,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h1:IMopuENFVS63AerRELdfWo6o60UNUidcldJOxJLmk24= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index bec43b85f86..01e6ff86367 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -60,7 +60,7 @@ require ( github.com/rs/zerolog v1.34.0 github.com/smartcontractkit/chain-selectors v1.0.101 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-deployments-framework v0.105.0 github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260403151002-2c91155b5501 diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index 68a35283f97..722246e433f 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1554,8 +1554,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd h1:IMopuENFVS63AerRELdfWo6o60UNUidcldJOxJLmk24= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260428133800-3b1484e8b1fd/go.mod h1:SBN8Urnh5sQvrQRbSo1Nr8coWatHg8LZoPw3R/42sho= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e h1:uTuCrOqBZeYfIl2QG70KuCG7xpubUETFJpHiip5+7FU= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260609183712-678afb1edd2e/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b h1:UMQ+MwHI341h+yARqeKmY/cagkB/dH0J34aMoJG00io= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260610184803-96d1e031407b/go.mod h1:GlEVw7ziizXoMfzl1onNSwansrVBLHhj5gUJlGQpb4I= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto=