diff --git a/src/utils/ProviderUtils.ts b/src/utils/ProviderUtils.ts index e3b412bf1e..bb6c6a1dc0 100644 --- a/src/utils/ProviderUtils.ts +++ b/src/utils/ProviderUtils.ts @@ -72,8 +72,14 @@ class RateLimitedProvider extends ethers.providers.StaticJsonRpcProvider { const defaultTimeout = 60 * 1000; +// @dev To avoid accidentally leaking RPC keys in log messages, resolve the RPC provider protocol and +// hostname centrally. There should be no instances of `provider.connection.url` in log messages or errors. +function getProviderOrigin(provider: ethers.providers.StaticJsonRpcProvider): string { + return getOriginFromURL(provider.connection.url); +} + function formatProviderError(provider: ethers.providers.StaticJsonRpcProvider, rawErrorText: string) { - return `Provider ${getOriginFromURL(provider.connection.url)} failed with error: ${rawErrorText}`; + return `Provider ${getProviderOrigin(provider)} failed with error: ${rawErrorText}`; } function createSendErrorWithMessage(message: string, sendError: any) { @@ -127,6 +133,8 @@ class CacheProvider extends RateLimitedProvider { this.maxReorgDistance = CHAIN_CACHE_FOLLOW_DISTANCE[chainId]; // Pre-compute as much of the redis key as possible. + // The full provider URL is deliberately used here, since the redis cache is considered sensitive, + // but additional caution is needed to ensure the cache prefix is not logged anywhere. const cachePrefix = `${providerCacheNamespace},${new URL(this.connection.url).hostname},${chainId}`; this.getBlockByNumberPrefix = `${cachePrefix}:getBlockByNumber,`; this.getLogsCachePrefix = `${cachePrefix}:eth_getLogs,`; @@ -306,7 +314,9 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider { if (!results.every(isPromiseFulfilled)) { // Format the error so that it's very clear which providers failed and succeeded. const errorTexts = errors.map(([provider, errorText]) => formatProviderError(provider, errorText)); - const successfulProviderUrls = results.filter(isPromiseFulfilled).map((result) => result.value[0].connection.url); + const successfulProviderUrls = results + .filter(isPromiseFulfilled) + .map((result) => resolveProviderOrigin(result.value[0])); throw createSendErrorWithMessage( `Not enough providers succeeded. Errors:\n${errorTexts.join("\n")}\n` + `Successful Providers:\n${successfulProviderUrls.join("\n")}`, @@ -324,13 +334,13 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider { const throwQuorumError = () => { const errorTexts = errors.map(([provider, errorText]) => formatProviderError(provider, errorText)); - const successfulProviderUrls = values.map(([provider]) => provider.connection.url); + const successfulProviderUrls = values.map(([provider]) => resolveProviderOrigin(provider)); throw new Error( "Not enough providers agreed to meet quorum.\n" + "Providers that errored:\n" + `${errorTexts.join("\n")}\n` + "Providers that succeeded, but some failed to match:\n" + - successfulProviderUrls.join("\n") + successfulProviderUrls.map((url) => getOriginFromURL(url)).join("\n") ); }; @@ -390,11 +400,11 @@ export class RetryProvider extends ethers.providers.StaticJsonRpcProvider { const mismatchedProviders = Object.fromEntries( [...values, ...fallbackValues] .filter(([, result]) => !compareRpcResults(method, result, quorumResult)) - .map(([provider, result]) => [provider.connection.url, result]) + .map(([provider, result]) => [getProviderOrigin(provider), result]) ); const quorumProviders = [...values, ...fallbackValues] .filter(([, result]) => compareRpcResults(method, result, quorumResult)) - .map(([provider]) => provider.connection.url); + .map(([provider]) => getProviderOrigin(provider)); if (Object.keys(mismatchedProviders).length > 0 || errors.length > 0) { logger.warn({ at: "ProviderUtils",