From bc575e0018f9f56c192f25c4093d7ea13b26ca56 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Thu, 5 Sep 2024 11:58:00 +0300 Subject: [PATCH 01/14] feat: add retries, add sleep between attempts --- scripts/startup-checks/rpc.mjs | 91 +++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/scripts/startup-checks/rpc.mjs b/scripts/startup-checks/rpc.mjs index 7bb55cb4..0dba1660 100644 --- a/scripts/startup-checks/rpc.mjs +++ b/scripts/startup-checks/rpc.mjs @@ -1,12 +1,16 @@ import { createClient, http } from 'viem'; -import { getChainId } from 'viem/actions' +import { getChainId } from 'viem/actions'; // Safely initialize a global variable let globalRPCCheckResults = globalThis.__rpcCheckResults || []; globalThis.__rpcCheckResults = globalRPCCheckResults; export const BROKEN_URL = 'BROKEN_URL'; -export const RPC_TIMEOUT_SECONDS = 10_000; +export const RPC_TIMEOUT_MS = 10_000; +export const MAX_RETRY_COUNT = 3; +export const RETRY_WAIT_TIME_MS = 30_000; + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const pushRPCCheckResult = (domain, success) => { globalRPCCheckResults.push({ domain, success }); @@ -19,6 +23,50 @@ const getRpcUrls = (chainId) => { return rpcUrls?.filter((url) => url); }; +const checkRPCWithRetries = async (url, defaultChain) => { + let domain; + try { + domain = new URL(url).hostname; + } catch (err) { + console.error('There is a broken URL.'); + pushRPCCheckResult(BROKEN_URL, false); + return false; + } + + for (let attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++) { + try { + console.info(`[startupCheckRPCs] Attempt ${attempt} for RPC ${domain}.`); + + const client = createClient({ + transport: http(url, { retryCount: 0, timeout: RPC_TIMEOUT_MS }), + }); + + const chainId = await getChainId(client); + + if (defaultChain === chainId) { + pushRPCCheckResult(domain, true); + console.info(`[startupCheckRPCs] RPC ${domain} works!`); + return true; + } else { + throw new Error(`[startupCheckRPCs] RPC ${domain} returned chainId ${chainId}, expected ${defaultChain}.`); + } + } catch (err) { + console.error(`[startupCheckRPCs] Error on attempt ${attempt} with RPC ${domain}:`); + console.error(String(err).replaceAll(url, domain)); + + if (attempt === MAX_RETRY_COUNT) { + console.error(`[startupCheckRPCs] Failed after ${MAX_RETRY_COUNT} attempts.`); + pushRPCCheckResult(domain, false); + } else { + console.info(`[startupCheckRPCs] Waiting for ${RETRY_WAIT_TIME_MS} ms before retrying...`); + await sleep(RETRY_WAIT_TIME_MS); + } + } + } + + return false; +}; + export const startupCheckRPCs = async () => { console.info('[startupCheckRPCs] Starting...'); @@ -30,44 +78,19 @@ export const startupCheckRPCs = async () => { throw new Error('[startupCheckRPCs] No RPC URLs found!'); } - let errorCount = 0; + let brokenRPCCount = 0; for (const url of rpcUrls) { - let domain; - try { - domain = new URL(url).hostname; - } catch (err) { - errorCount += 1; - console.error('There is a broken URL.'); - pushRPCCheckResult(BROKEN_URL, false); - continue; - } + const success = await checkRPCWithRetries(url, defaultChain); - try { - const client = createClient({ - transport: http(url, { retryCount: 0, timeout: RPC_TIMEOUT_SECONDS }) - }); - - const chainId = await getChainId(client); - - if (defaultChain === chainId) { - pushRPCCheckResult(domain, true); - console.info(`[startupCheckRPCs] RPC ${domain} works!`); - } else { - throw(`[startupCheckRPCs] RPC ${domain} does not work!`); - } - } catch (err) { - errorCount += 1; - pushRPCCheckResult(domain, false); - console.error(`[startupCheckRPCs] Error with RPC ${domain}:`); - console.error(String(err).replaceAll(rpcUrls, domain)); - console.error(`[startupCheckRPCs] Timeout: ${RPC_TIMEOUT_SECONDS} seconds`); + if (!success) { + brokenRPCCount += 1; } } - if (errorCount > 0) { - console.info(`[startupCheckRPCs] Number of working RPCs: ${rpcUrls.length - errorCount}`); - console.info(`[startupCheckRPCs] Number of broken RPCs: ${errorCount}`); + if (brokenRPCCount > 0) { + console.info(`[startupCheckRPCs] Number of working RPCs: ${rpcUrls.length - brokenRPCCount}`); + console.info(`[startupCheckRPCs] Number of broken RPCs: ${brokenRPCCount}`); } else { console.info('[startupCheckRPCs] All RPC works!'); } From 299f5cec09e50c5d437b0b0b2c24f3eea9a7fbe9 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Thu, 5 Sep 2024 13:37:06 +0300 Subject: [PATCH 02/14] feat: send startup metrics after all attempts --- config/get-secret-config.ts | 2 + scripts/startup-checks/rpc.mjs | 19 +++++--- utilsApi/metrics/metrics.ts | 2 +- utilsApi/metrics/startup-metrics.ts | 70 +++++++++++++++++++++++++---- 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/config/get-secret-config.ts b/config/get-secret-config.ts index 52746b73..6bad263d 100644 --- a/config/get-secret-config.ts +++ b/config/get-secret-config.ts @@ -11,6 +11,8 @@ export type SecretConfigType = Modify< rpcUrls_1: [string, ...string[]]; rpcUrls_17000: [string, ...string[]]; rpcUrls_11155111: [string, ...string[]]; + // Dynamic keys like rpcUrls_ + [key: `rpcUrls_${number}`]: string[]; cspReportOnly: boolean; diff --git a/scripts/startup-checks/rpc.mjs b/scripts/startup-checks/rpc.mjs index 0dba1660..f293f34e 100644 --- a/scripts/startup-checks/rpc.mjs +++ b/scripts/startup-checks/rpc.mjs @@ -1,22 +1,28 @@ import { createClient, http } from 'viem'; import { getChainId } from 'viem/actions'; +export const STATUS_CHECKING = 'CHECKING'; +export const STATUS_FINISHED = 'STATUS_FINISHED'; + // Safely initialize a global variable -let globalRPCCheckResults = globalThis.__rpcCheckResults || []; -globalThis.__rpcCheckResults = globalRPCCheckResults; +let globalRPCCheck = globalThis.__rpcCheckStatus || { + status: STATUS_CHECKING, + results: [], +}; +globalThis.__rpcCheckStatus = globalRPCCheck; export const BROKEN_URL = 'BROKEN_URL'; export const RPC_TIMEOUT_MS = 10_000; export const MAX_RETRY_COUNT = 3; -export const RETRY_WAIT_TIME_MS = 30_000; +export const RETRY_WAIT_TIME_MS = 10_000; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const pushRPCCheckResult = (domain, success) => { - globalRPCCheckResults.push({ domain, success }); + globalRPCCheck.results.push({ domain, success }); }; -export const getRPCCheckResults = () => globalThis.__rpcCheckResults || []; +export const getRPCChecks = () => globalThis.__rpcCheckStatus || { status: STATUS_CHECKING, results: [] }; const getRpcUrls = (chainId) => { const rpcUrls = process.env[`EL_RPC_URLS_${chainId}`]?.split(','); @@ -69,6 +75,7 @@ const checkRPCWithRetries = async (url, defaultChain) => { export const startupCheckRPCs = async () => { console.info('[startupCheckRPCs] Starting...'); + globalRPCCheck.status = STATUS_CHECKING; try { const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10); @@ -97,5 +104,7 @@ export const startupCheckRPCs = async () => { } catch (err) { console.error('[startupCheckRPCs] Error during startup check:'); console.error(err); + } finally { + globalRPCCheck.status = STATUS_FINISHED; } }; diff --git a/utilsApi/metrics/metrics.ts b/utilsApi/metrics/metrics.ts index d75d75c4..a755ac30 100644 --- a/utilsApi/metrics/metrics.ts +++ b/utilsApi/metrics/metrics.ts @@ -11,7 +11,7 @@ class Metrics { request = new RequestMetrics(this.registry); constructor() { - collectStartupMetrics(this.registry); + void collectStartupMetrics(this.registry); collectDefaultMetrics({ prefix: METRICS_PREFIX, register: this.registry }); } } diff --git a/utilsApi/metrics/startup-metrics.ts b/utilsApi/metrics/startup-metrics.ts index 9fd41785..3d3b0db2 100644 --- a/utilsApi/metrics/startup-metrics.ts +++ b/utilsApi/metrics/startup-metrics.ts @@ -3,23 +3,73 @@ import { collectStartupMetrics as collectBuildInfoMetrics } from '@lidofinance/a import buildInfoJson from 'build-info.json'; import { openKeys } from 'scripts/log-environment-variables.mjs'; -import { getRPCCheckResults } from 'scripts/startup-checks/rpc.mjs'; +import { + getRPCChecks, + MAX_RETRY_COUNT, + RETRY_WAIT_TIME_MS, + STATUS_FINISHED, +} from 'scripts/startup-checks/rpc.mjs'; -import { config } from 'config'; +import { config, secretConfig } from 'config'; import { METRICS_PREFIX } from 'consts/metrics'; import { StartupChecksRPCMetrics } from './startup-checks'; -const collectStartupChecksRPCMetrics = (registry: Registry): void => { +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +const retryGetRPCChecks = async (maxAttempts: number, delay: number) => { + let attempts = 0; + + while (attempts < maxAttempts) { + const rpcCheckStatus = getRPCChecks(); + + if (rpcCheckStatus.status === STATUS_FINISHED) { + return rpcCheckStatus.results; + } + + console.info( + `[collectStartupChecksRPCMetrics] Waiting for RPC checks to finish. Attempt ${attempts + 1}.`, + ); + + await sleep(delay); + attempts += 1; + } + + throw new Error( + `[collectStartupChecksRPCMetrics] RPC check results did not finish after ${maxAttempts} attempts.`, + ); +}; + +const collectStartupChecksRPCMetrics = async ( + registry: Registry, +): Promise => { const rpcMetrics = new StartupChecksRPCMetrics(registry); + // secretConfig.rpcUrls_1.length || secretConfig.rpcUrls_17000.length || other ... + const rpcCount = + secretConfig[`rpcUrls_${secretConfig.defaultChain}`]?.length ?? 0; + // retryCount with count shift + const retryCount = rpcCount * MAX_RETRY_COUNT + 1; - getRPCCheckResults().forEach( - (_check: { domain: string; success: boolean }) => { + try { + // RETRY_WAIT_TIME_MS with time shift 1_000 + const rpcCheckResults = await retryGetRPCChecks( + retryCount, + RETRY_WAIT_TIME_MS + 1_000, + ); + + rpcCheckResults.forEach((_check: { domain: string; success: boolean }) => { rpcMetrics.requestCounter .labels(_check.domain, _check.success.toString()) .inc(); - }, - ); + }); + } catch (error) { + console.error( + `[collectStartupChecksRPCMetrics] Error collecting RPC metrics: ${error}`, + ); + rpcMetrics.requestCounter + .labels('BROKEN_URL', 'false') // false as string + .inc(); + } }; const collectEnvInfoMetrics = (registry: Registry): void => { @@ -37,7 +87,9 @@ const collectEnvInfoMetrics = (registry: Registry): void => { envInfo.labels(...labelPairs.map((pair) => pair.value)).set(1); }; -export const collectStartupMetrics = (registry: Registry): void => { +export const collectStartupMetrics = async ( + registry: Registry, +): Promise => { collectEnvInfoMetrics(registry); collectBuildInfoMetrics({ @@ -50,5 +102,5 @@ export const collectStartupMetrics = (registry: Registry): void => { branch: buildInfoJson.branch, }); - collectStartupChecksRPCMetrics(registry); + await collectStartupChecksRPCMetrics(registry); }; From 0ebd59cd6fdb77a77163402d821bd31ac8347ca5 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Thu, 5 Sep 2024 15:30:13 +0300 Subject: [PATCH 03/14] refactor: counter to Gauge --- consts/metrics.ts | 2 +- utilsApi/metrics/startup-checks.ts | 14 +++++++------- utilsApi/metrics/startup-metrics.ts | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/consts/metrics.ts b/consts/metrics.ts index b1a9ba10..9899a061 100644 --- a/consts/metrics.ts +++ b/consts/metrics.ts @@ -2,7 +2,7 @@ export const METRICS_PREFIX = 'eth_stake_widget_ui_'; export const enum METRIC_NAMES { REQUESTS_TOTAL = 'requests_total', - STARTUP_CHECKS_RPC = 'startup_checks_rpc', + STARTUP_CHECKS_RPC_FAILED = 'startup_checks_rpc_failed', API_RESPONSE = 'api_response', ETH_CALL_ADDRESS_TO = 'eth_call_address_to', SSR_COUNT = 'ssr_count', diff --git a/utilsApi/metrics/startup-checks.ts b/utilsApi/metrics/startup-checks.ts index aa1e4f5d..89966d41 100644 --- a/utilsApi/metrics/startup-checks.ts +++ b/utilsApi/metrics/startup-checks.ts @@ -1,21 +1,21 @@ -import { Counter, Registry } from 'prom-client'; +import { Gauge, Registry } from 'prom-client'; import { METRICS_PREFIX, METRIC_NAMES } from 'consts/metrics'; export class StartupChecksRPCMetrics { - requestCounter: Counter<'rpc_domain' | 'success'>; + requestStatusGauge: Gauge<'rpc_domain' | 'success'>; constructor(public registry: Registry) { - this.requestCounter = this.requestsCounterInit(); + this.requestStatusGauge = this.requestStatusGaugeInit(); } - requestsCounterInit() { + requestStatusGaugeInit() { const requestsCounterName = - METRICS_PREFIX + METRIC_NAMES.STARTUP_CHECKS_RPC; + METRICS_PREFIX + METRIC_NAMES.STARTUP_CHECKS_RPC_FAILED; - return new Counter({ + return new Gauge({ name: requestsCounterName, help: 'The total number of RPC checks after the app started.', - labelNames: ['rpc_domain', 'success'], + labelNames: ['rpc_domain'], registers: [this.registry], }); } diff --git a/utilsApi/metrics/startup-metrics.ts b/utilsApi/metrics/startup-metrics.ts index 3d3b0db2..8c76c2b8 100644 --- a/utilsApi/metrics/startup-metrics.ts +++ b/utilsApi/metrics/startup-metrics.ts @@ -58,17 +58,17 @@ const collectStartupChecksRPCMetrics = async ( ); rpcCheckResults.forEach((_check: { domain: string; success: boolean }) => { - rpcMetrics.requestCounter - .labels(_check.domain, _check.success.toString()) - .inc(); + rpcMetrics.requestStatusGauge + .labels(_check.domain) + .set(Number(+!_check.success)); }); } catch (error) { console.error( `[collectStartupChecksRPCMetrics] Error collecting RPC metrics: ${error}`, ); - rpcMetrics.requestCounter - .labels('BROKEN_URL', 'false') // false as string - .inc(); + rpcMetrics.requestStatusGauge + .labels('BROKEN_URL') // false as string + .inc(1); } }; From ded52790af65dbe493c281e73f29b593cdd205f6 Mon Sep 17 00:00:00 2001 From: Alexander Khramov Date: Tue, 10 Sep 2024 16:25:16 +0300 Subject: [PATCH 04/14] fix: make "you will receive" row always first --- .../unwrap/unwrap-form/unwrap-stats.tsx | 16 ++++++------ features/wsteth/wrap/wrap-form/wrap-stats.tsx | 25 +++++++++---------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx b/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx index 8d26726d..0f9acf23 100644 --- a/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx +++ b/features/wsteth/unwrap/unwrap-form/unwrap-stats.tsx @@ -23,14 +23,6 @@ export const UnwrapStats = () => { return ( - - - - { trimEllipsis /> + + + + ); }; diff --git a/features/wsteth/wrap/wrap-form/wrap-stats.tsx b/features/wsteth/wrap/wrap-form/wrap-stats.tsx index 1d26d5e2..b179f6d1 100644 --- a/features/wsteth/wrap/wrap-form/wrap-stats.tsx +++ b/features/wsteth/wrap/wrap-form/wrap-stats.tsx @@ -47,6 +47,18 @@ export const WrapFormStats = () => { return ( + + + { loading={isApprovalLoading} token={TOKENS.STETH} /> - - - - ); }; From f3e3ed4acf3894bb845b7a628ff84137dd9e5fde Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Mon, 16 Sep 2024 10:40:58 +0300 Subject: [PATCH 05/14] refactor: startup check of RPCs (promises) --- scripts/startup-checks/rpc.mjs | 137 +++++++++++++++------------- utilsApi/metrics/startup-metrics.ts | 51 ++--------- 2 files changed, 83 insertions(+), 105 deletions(-) diff --git a/scripts/startup-checks/rpc.mjs b/scripts/startup-checks/rpc.mjs index f293f34e..98c1c562 100644 --- a/scripts/startup-checks/rpc.mjs +++ b/scripts/startup-checks/rpc.mjs @@ -1,70 +1,90 @@ import { createClient, http } from 'viem'; import { getChainId } from 'viem/actions'; -export const STATUS_CHECKING = 'CHECKING'; -export const STATUS_FINISHED = 'STATUS_FINISHED'; - -// Safely initialize a global variable -let globalRPCCheck = globalThis.__rpcCheckStatus || { - status: STATUS_CHECKING, - results: [], -}; -globalThis.__rpcCheckStatus = globalRPCCheck; - export const BROKEN_URL = 'BROKEN_URL'; export const RPC_TIMEOUT_MS = 10_000; export const MAX_RETRY_COUNT = 3; export const RETRY_WAIT_TIME_MS = 10_000; -const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - -const pushRPCCheckResult = (domain, success) => { - globalRPCCheck.results.push({ domain, success }); +// Safely initialize a global variable +const globalStartupRPCChecks = globalThis.__startupRPCChecks || { + promise: null, + results: [], }; +globalThis.__startupRPCChecks = globalStartupRPCChecks; + +// Utility +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); -export const getRPCChecks = () => globalThis.__rpcCheckStatus || { status: STATUS_CHECKING, results: [] }; +// Utility +const timeoutPromise = (ms, message) => + new Promise((_, reject) => setTimeout(() => reject(new Error(message)), ms)); -const getRpcUrls = (chainId) => { +const getRPCUrls = (chainId) => { const rpcUrls = process.env[`EL_RPC_URLS_${chainId}`]?.split(','); return rpcUrls?.filter((url) => url); }; -const checkRPCWithRetries = async (url, defaultChain) => { +const pushRPCCheckResult = (domain, success) => { + globalStartupRPCChecks.results.push({ domain, success }); +}; + +const checkRPC = async (url, defaultChain) => { let domain; try { domain = new URL(url).hostname; - } catch (err) { - console.error('There is a broken URL.'); + } catch { + console.error(`[checkRPC] Invalid URL: ${url}`); pushRPCCheckResult(BROKEN_URL, false); return false; } + try { + const client = createClient({ + transport: http(url, { retryCount: 0, timeout: RPC_TIMEOUT_MS }), + }); + + const chainId = await getChainId(client); + + if (chainId === defaultChain) { + pushRPCCheckResult(domain, true); + console.info(`[checkRPC] RPC ${domain} is working`); + return true; + } else { + throw new Error(`[checkRPC] Expected chainId ${defaultChain}, but got ${chainId}`); + } + } catch (err) { + console.error(`[checkRPC] Error checking RPC ${domain}: ${err.message}`); + return false; + } +}; + +const checkRPCWithRetries = async (url, defaultChain) => { + const domain = new URL(url).hostname; + for (let attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++) { try { - console.info(`[startupCheckRPCs] Attempt ${attempt} for RPC ${domain}.`); + console.info(`[checkRPCWithRetries] Attempt ${attempt} for RPC ${domain}`); - const client = createClient({ - transport: http(url, { retryCount: 0, timeout: RPC_TIMEOUT_MS }), - }); + const result = await Promise.race([ + checkRPC(url, defaultChain), + timeoutPromise(RPC_TIMEOUT_MS, `[checkRPCWithRetries] RPC ${domain} timed out`), + ]); - const chainId = await getChainId(client); - - if (defaultChain === chainId) { - pushRPCCheckResult(domain, true); - console.info(`[startupCheckRPCs] RPC ${domain} works!`); - return true; - } else { - throw new Error(`[startupCheckRPCs] RPC ${domain} returned chainId ${chainId}, expected ${defaultChain}.`); + if (!result) { + throw new Error('[checkRPCWithRetries] Promise(checkRPC) returned false!'); } + + // Stop checking for ${url} with success (a success is set in the 'checkRPC' function) + return true; } catch (err) { - console.error(`[startupCheckRPCs] Error on attempt ${attempt} with RPC ${domain}:`); - console.error(String(err).replaceAll(url, domain)); + console.error(`[checkRPCWithRetries] Error on attempt ${attempt} for RPC ${domain}: ${err.message}`); if (attempt === MAX_RETRY_COUNT) { - console.error(`[startupCheckRPCs] Failed after ${MAX_RETRY_COUNT} attempts.`); + console.error(`[checkRPCWithRetries] Failed after ${MAX_RETRY_COUNT} attempts for ${domain}`); pushRPCCheckResult(domain, false); } else { - console.info(`[startupCheckRPCs] Waiting for ${RETRY_WAIT_TIME_MS} ms before retrying...`); + console.info(`[checkRPCWithRetries] Retrying in ${RETRY_WAIT_TIME_MS} ms...`); await sleep(RETRY_WAIT_TIME_MS); } } @@ -73,38 +93,31 @@ const checkRPCWithRetries = async (url, defaultChain) => { return false; }; -export const startupCheckRPCs = async () => { - console.info('[startupCheckRPCs] Starting...'); - globalRPCCheck.status = STATUS_CHECKING; - - try { - const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10); - const rpcUrls = getRpcUrls(defaultChain); +export const getRPCChecks = () => globalStartupRPCChecks; - if (!rpcUrls || rpcUrls.length === 0) { - throw new Error('[startupCheckRPCs] No RPC URLs found!'); - } +export const startupCheckRPCs = async () => { + console.info('[startupCheckRPCs] Starting RPC checks...'); - let brokenRPCCount = 0; + if (globalStartupRPCChecks.promise) { + return globalStartupRPCChecks.promise; + } - for (const url of rpcUrls) { - const success = await checkRPCWithRetries(url, defaultChain); + globalStartupRPCChecks.promise = (async () => { + try { + const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10); + const rpcUrls = getRPCUrls(defaultChain); - if (!success) { - brokenRPCCount += 1; + if (!rpcUrls.length) { + throw new Error('[startupCheckRPCs] No RPC URLs found!'); } - } - if (brokenRPCCount > 0) { - console.info(`[startupCheckRPCs] Number of working RPCs: ${rpcUrls.length - brokenRPCCount}`); - console.info(`[startupCheckRPCs] Number of broken RPCs: ${brokenRPCCount}`); - } else { - console.info('[startupCheckRPCs] All RPC works!'); + const checkResults = await Promise.all(rpcUrls.map((url) => checkRPCWithRetries(url, defaultChain))); + const brokenRPCCount = checkResults.filter((success) => !success).length; + + console.info(`[startupCheckRPCs] Working RPCs: ${rpcUrls.length - brokenRPCCount}`); + console.info(`[startupCheckRPCs] Broken RPCs: ${brokenRPCCount}`); + } catch (err) { + console.error('[startupCheckRPCs] Error during RPC checks:', err); } - } catch (err) { - console.error('[startupCheckRPCs] Error during startup check:'); - console.error(err); - } finally { - globalRPCCheck.status = STATUS_FINISHED; - } + })(); }; diff --git a/utilsApi/metrics/startup-metrics.ts b/utilsApi/metrics/startup-metrics.ts index 8c76c2b8..05ef85b1 100644 --- a/utilsApi/metrics/startup-metrics.ts +++ b/utilsApi/metrics/startup-metrics.ts @@ -3,61 +3,26 @@ import { collectStartupMetrics as collectBuildInfoMetrics } from '@lidofinance/a import buildInfoJson from 'build-info.json'; import { openKeys } from 'scripts/log-environment-variables.mjs'; -import { - getRPCChecks, - MAX_RETRY_COUNT, - RETRY_WAIT_TIME_MS, - STATUS_FINISHED, -} from 'scripts/startup-checks/rpc.mjs'; +import { getRPCChecks } from 'scripts/startup-checks/rpc.mjs'; -import { config, secretConfig } from 'config'; +import { config } from 'config'; import { METRICS_PREFIX } from 'consts/metrics'; import { StartupChecksRPCMetrics } from './startup-checks'; -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -const retryGetRPCChecks = async (maxAttempts: number, delay: number) => { - let attempts = 0; - - while (attempts < maxAttempts) { - const rpcCheckStatus = getRPCChecks(); - - if (rpcCheckStatus.status === STATUS_FINISHED) { - return rpcCheckStatus.results; - } - - console.info( - `[collectStartupChecksRPCMetrics] Waiting for RPC checks to finish. Attempt ${attempts + 1}.`, - ); - - await sleep(delay); - attempts += 1; - } - - throw new Error( - `[collectStartupChecksRPCMetrics] RPC check results did not finish after ${maxAttempts} attempts.`, - ); -}; - const collectStartupChecksRPCMetrics = async ( registry: Registry, ): Promise => { const rpcMetrics = new StartupChecksRPCMetrics(registry); - // secretConfig.rpcUrls_1.length || secretConfig.rpcUrls_17000.length || other ... - const rpcCount = - secretConfig[`rpcUrls_${secretConfig.defaultChain}`]?.length ?? 0; - // retryCount with count shift - const retryCount = rpcCount * MAX_RETRY_COUNT + 1; try { - // RETRY_WAIT_TIME_MS with time shift 1_000 - const rpcCheckResults = await retryGetRPCChecks( - retryCount, - RETRY_WAIT_TIME_MS + 1_000, - ); + const { promise: rpcChecksPromise, results: rpcChecksResults } = + getRPCChecks(); + + // Await the promise if it's still in progress + await rpcChecksPromise; - rpcCheckResults.forEach((_check: { domain: string; success: boolean }) => { + rpcChecksResults.forEach((_check: { domain: string; success: boolean }) => { rpcMetrics.requestStatusGauge .labels(_check.domain) .set(Number(+!_check.success)); From c01adecb036f7d5917c522763246d7f357d1f9ec Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Mon, 16 Sep 2024 15:36:07 +0700 Subject: [PATCH 06/14] fix: correct locator abi --- abi/lidoLocator.abi.json | 236 +++++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 85 deletions(-) diff --git a/abi/lidoLocator.abi.json b/abi/lidoLocator.abi.json index 078f17b1..8cfca327 100644 --- a/abi/lidoLocator.abi.json +++ b/abi/lidoLocator.abi.json @@ -2,134 +2,200 @@ { "inputs": [ { - "internalType": "address", - "name": "implementation_", - "type": "address" - }, - { "internalType": "address", "name": "admin_", "type": "address" }, - { "internalType": "bytes", "name": "data_", "type": "bytes" } + "components": [ + { + "internalType": "address", + "name": "accountingOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "depositSecurityModule", + "type": "address" + }, + { + "internalType": "address", + "name": "elRewardsVault", + "type": "address" + }, + { + "internalType": "address", + "name": "legacyOracle", + "type": "address" + }, + { "internalType": "address", "name": "lido", "type": "address" }, + { + "internalType": "address", + "name": "oracleReportSanityChecker", + "type": "address" + }, + { + "internalType": "address", + "name": "postTokenRebaseReceiver", + "type": "address" + }, + { "internalType": "address", "name": "burner", "type": "address" }, + { + "internalType": "address", + "name": "stakingRouter", + "type": "address" + }, + { "internalType": "address", "name": "treasury", "type": "address" }, + { + "internalType": "address", + "name": "validatorsExitBusOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalQueue", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalVault", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleDaemonConfig", + "type": "address" + } + ], + "internalType": "struct LidoLocator.Config", + "name": "_config", + "type": "tuple" + } ], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "NotAdmin", "type": "error" }, - { "inputs": [], "name": "ProxyIsOssified", "type": "error" }, + { "inputs": [], "name": "ZeroAddress", "type": "error" }, { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "previousAdmin", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "AdminChanged", - "type": "event" + "inputs": [], + "name": "accountingOracle", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "beacon", - "type": "address" - } + "inputs": [], + "name": "burner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "coreComponents", + "outputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } ], - "name": "BeaconUpgraded", - "type": "event" + "stateMutability": "view", + "type": "function" }, { - "anonymous": false, "inputs": [], - "name": "ProxyOssified", - "type": "event" + "name": "depositSecurityModule", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" + "inputs": [], + "name": "elRewardsVault", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" }, - { "stateMutability": "payable", "type": "fallback" }, { - "inputs": [ - { "internalType": "address", "name": "newAdmin_", "type": "address" } + "inputs": [], + "name": "legacyOracle", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lido", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleDaemonConfig", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportComponentsForLido", + "outputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } ], - "name": "proxy__changeAdmin", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "proxy__getAdmin", + "name": "oracleReportSanityChecker", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "proxy__getImplementation", + "name": "postTokenRebaseReceiver", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "proxy__getIsOssified", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "name": "stakingRouter", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "proxy__ossify", - "outputs": [], - "stateMutability": "nonpayable", + "name": "treasury", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation_", - "type": "address" - } - ], - "name": "proxy__upgradeTo", - "outputs": [], - "stateMutability": "nonpayable", + "inputs": [], + "name": "validatorsExitBusOracle", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation_", - "type": "address" - }, - { "internalType": "bytes", "name": "setupCalldata_", "type": "bytes" }, - { "internalType": "bool", "name": "forceCall_", "type": "bool" } - ], - "name": "proxy__upgradeToAndCall", - "outputs": [], - "stateMutability": "nonpayable", + "inputs": [], + "name": "withdrawalQueue", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", "type": "function" }, - { "stateMutability": "payable", "type": "receive" } + { + "inputs": [], + "name": "withdrawalVault", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + } ] From b2ef17dd5696c692fddd29b2daab213ed63e6021 Mon Sep 17 00:00:00 2001 From: Anton Shalimov Date: Mon, 16 Sep 2024 11:43:38 +0300 Subject: [PATCH 07/14] refactor: return Promise in getRPCChecks --- scripts/startup-checks/rpc.mjs | 6 +++++- utilsApi/metrics/startup-metrics.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/startup-checks/rpc.mjs b/scripts/startup-checks/rpc.mjs index 98c1c562..a9afe665 100644 --- a/scripts/startup-checks/rpc.mjs +++ b/scripts/startup-checks/rpc.mjs @@ -93,7 +93,7 @@ const checkRPCWithRetries = async (url, defaultChain) => { return false; }; -export const getRPCChecks = () => globalStartupRPCChecks; +export const getRPCChecks = () => globalStartupRPCChecks.promise; export const startupCheckRPCs = async () => { console.info('[startupCheckRPCs] Starting RPC checks...'); @@ -116,8 +116,12 @@ export const startupCheckRPCs = async () => { console.info(`[startupCheckRPCs] Working RPCs: ${rpcUrls.length - brokenRPCCount}`); console.info(`[startupCheckRPCs] Broken RPCs: ${brokenRPCCount}`); + + return globalStartupRPCChecks.results; } catch (err) { console.error('[startupCheckRPCs] Error during RPC checks:', err); + + return null; } })(); }; diff --git a/utilsApi/metrics/startup-metrics.ts b/utilsApi/metrics/startup-metrics.ts index 05ef85b1..baaf99bf 100644 --- a/utilsApi/metrics/startup-metrics.ts +++ b/utilsApi/metrics/startup-metrics.ts @@ -16,11 +16,14 @@ const collectStartupChecksRPCMetrics = async ( const rpcMetrics = new StartupChecksRPCMetrics(registry); try { - const { promise: rpcChecksPromise, results: rpcChecksResults } = - getRPCChecks(); - // Await the promise if it's still in progress - await rpcChecksPromise; + const rpcChecksResults = await getRPCChecks(); + + if (!rpcChecksResults) { + throw new Error( + '[collectStartupChecksRPCMetrics] getRPCChecks resolved as "null"!', + ); + } rpcChecksResults.forEach((_check: { domain: string; success: boolean }) => { rpcMetrics.requestStatusGauge From 1cefd503b00b3fa4d12febe8c3a5b07d1ddae4ee Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 17 Sep 2024 13:03:09 +0700 Subject: [PATCH 08/14] fix: short revalidation --- utilsApi/get-default-static-props.ts | 11 +++++------ utilsApi/rpcFactory.ts | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/utilsApi/get-default-static-props.ts b/utilsApi/get-default-static-props.ts index aefc9807..95a3bde6 100644 --- a/utilsApi/get-default-static-props.ts +++ b/utilsApi/get-default-static-props.ts @@ -3,6 +3,7 @@ import type { ParsedUrlQuery } from 'querystring'; import Metrics from 'utilsApi/metrics'; import { fetchExternalManifest } from './fetch-external-manifest'; +import { config } from 'config'; export const getDefaultStaticProps = < P extends { [key: string]: any } = { [key: string]: any }, @@ -11,15 +12,14 @@ export const getDefaultStaticProps = < >( custom?: GetStaticProps, ): GetStaticProps

=> { - let shouldZeroRevalidate = true; return async (context) => { /// common props - const { ___prefetch_manifest___, revalidate } = - await fetchExternalManifest(); + const { ___prefetch_manifest___ } = await fetchExternalManifest(); const props = ___prefetch_manifest___ ? { ___prefetch_manifest___ } : {}; const base = { props, - revalidate: shouldZeroRevalidate ? 1 : revalidate, + // because next only remembers first value, default to short revalidation period + revalidate: config.ERROR_REVALIDATION_SECONDS, }; /// custom getStaticProps @@ -35,11 +35,10 @@ export const getDefaultStaticProps = < /// metrics console.debug( - `[getDefaultStaticProps] running revalidation, next revalidation in ${base.revalidate}`, + `[getDefaultStaticProps] running revalidation, next revalidation in ${result.revalidate}`, ); Metrics.request.ssrCounter.labels({ revalidate: base.revalidate }).inc(1); - shouldZeroRevalidate = false; return result; }; }; diff --git a/utilsApi/rpcFactory.ts b/utilsApi/rpcFactory.ts index fd1fe239..81089660 100644 --- a/utilsApi/rpcFactory.ts +++ b/utilsApi/rpcFactory.ts @@ -222,9 +222,9 @@ export const rpcFactory = ({ .pipe(sizeLimit) .on('error', (error) => { if (error instanceof SizeTooLargeError) { - console.warn('[rpcFactory] RPC response too large', { - request: JSON.stringify(requests), - }); + console.warn( + `[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`, + ); res.statusCode = 413; // Payload Too Large res.end(error.message); } else { From 91dbaea71297f46892179b93269033d85d569148 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 17 Sep 2024 13:15:23 +0700 Subject: [PATCH 09/14] fix: default revalidation --- config/groups/revalidation.ts | 3 +-- utilsApi/fetch-external-manifest.ts | 6 +----- utilsApi/get-default-static-props.ts | 6 ++++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/config/groups/revalidation.ts b/config/groups/revalidation.ts index 523d09e7..37c99677 100644 --- a/config/groups/revalidation.ts +++ b/config/groups/revalidation.ts @@ -1,2 +1 @@ -export const DEFAULT_REVALIDATION = 60 * 15; // 15 minutes -export const ERROR_REVALIDATION_SECONDS = 60; // 1 minute +export const DEFAULT_REVALIDATION = 60; // 1 minute diff --git a/utilsApi/fetch-external-manifest.ts b/utilsApi/fetch-external-manifest.ts index a388c1ef..02f9765d 100644 --- a/utilsApi/fetch-external-manifest.ts +++ b/utilsApi/fetch-external-manifest.ts @@ -8,7 +8,6 @@ import FallbackLocalManifest from 'IPFS.json' assert { type: 'json' }; export type ExternalConfigResult = { ___prefetch_manifest___: object | null; - revalidate: number; }; const cache = new Cache< @@ -25,7 +24,6 @@ export const fetchExternalManifest = async () => { if (config.ipfsMode) { return { ___prefetch_manifest___: FallbackLocalManifest, - revalidate: config.DEFAULT_REVALIDATION, }; } @@ -44,7 +42,6 @@ export const fetchExternalManifest = async () => { const result = { ___prefetch_manifest___: data, - revalidate: config.DEFAULT_REVALIDATION, }; cache.put( @@ -68,10 +65,9 @@ export const fetchExternalManifest = async () => { } } console.error( - `[fetchExternalManifest] failed to fetch external manifest after retries, revalidation is set to ${config.ERROR_REVALIDATION_SECONDS}`, + `[fetchExternalManifest] failed to fetch external manifest after retries`, ); return { - revalidate: config.ERROR_REVALIDATION_SECONDS, ___prefetch_manifest___: FallbackLocalManifest, }; }; diff --git a/utilsApi/get-default-static-props.ts b/utilsApi/get-default-static-props.ts index 95a3bde6..c21715de 100644 --- a/utilsApi/get-default-static-props.ts +++ b/utilsApi/get-default-static-props.ts @@ -19,7 +19,7 @@ export const getDefaultStaticProps = < const base = { props, // because next only remembers first value, default to short revalidation period - revalidate: config.ERROR_REVALIDATION_SECONDS, + revalidate: config.DEFAULT_REVALIDATION, }; /// custom getStaticProps @@ -37,7 +37,9 @@ export const getDefaultStaticProps = < console.debug( `[getDefaultStaticProps] running revalidation, next revalidation in ${result.revalidate}`, ); - Metrics.request.ssrCounter.labels({ revalidate: base.revalidate }).inc(1); + Metrics.request.ssrCounter + .labels({ revalidate: String(result.revalidate) }) + .inc(1); return result; }; From a61e6fc848e988a0092d8267582854e0b5faaa80 Mon Sep 17 00:00:00 2001 From: Alexander Khramov Date: Tue, 17 Sep 2024 19:24:44 +0300 Subject: [PATCH 10/14] feat: reef-knot 5.6.0 --- package.json | 2 +- yarn.lock | 78 ++++++++++++++++++---------------------------------- 2 files changed, 27 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 79c3ddb3..86aa0192 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "react-hook-form": "^7.45.2", "react-is": "^18.2.0", "react-transition-group": "^4.4.2", - "reef-knot": "5.5.4", + "reef-knot": "5.6.0", "remark": "^13.0.0", "remark-external-links": "^8.0.0", "remark-html": "^13.0.1", diff --git a/yarn.lock b/yarn.lock index cf3a9207..5b5b706d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2904,16 +2904,16 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ== -"@reef-knot/connect-wallet-modal@5.3.3": - version "5.3.3" - resolved "https://registry.yarnpkg.com/@reef-knot/connect-wallet-modal/-/connect-wallet-modal-5.3.3.tgz#a6e4402a93885296f90cd79661282c1b3e9eecdf" - integrity sha512-qiNkPMSygapnIQPLnsGWTomam8dqzLzPCYw0kNCLth1YHqev1eXp68NNuGPHn2TyvA/wBijL2OlxD9MCt0Is7A== +"@reef-knot/connect-wallet-modal@5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@reef-knot/connect-wallet-modal/-/connect-wallet-modal-5.4.0.tgz#292e763fb5d3a132c554d9bd64bcb02f55732cc8" + integrity sha512-z9cUTg+P180Bi75v8z69yQhkgHinDuewwC+v0NZJ86FR+O+CZYFLLUJGGfA8IYuz/iLFDkzq7/1ZlfMic5sy3A== dependencies: "@ledgerhq/hw-app-eth" "^6.37.1" "@ledgerhq/hw-transport" "^6.31.0" "@ledgerhq/hw-transport-webhid" "^6.29.0" "@lidofinance/lido-ui" "^3.18.0" - "@reef-knot/wallets-list" "^2.2.2" + "@reef-knot/wallets-list" "^2.3.0" "@types/react" "18.2.45" "@types/react-dom" "18.2.17" @@ -2962,14 +2962,6 @@ resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-ambire/-/wallet-adapter-ambire-2.0.1.tgz#91e137cffa4bc06fd91856edc8eeebe5377ae356" integrity sha512-3Td22/Jf0BLW1Ap+MlOODTZ9iE19Ss3BUCxXlh0+kFyAT9nqoRFCmGHU/RRs/JyVPhZHDpza/OxiCZRnanY+fg== -"@reef-knot/wallet-adapter-binance-wallet@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-binance-wallet/-/wallet-adapter-binance-wallet-1.0.2.tgz#2efafe8dc6d0cb0b020da49591bfef8293ff2847" - integrity sha512-LbzqyHsU7/cQjF4XlzTvd6vvBrGqHb/wMupw1UF4/ZfqlxAXfQcDzMv8pdGfnSNbDdTgyUkwKSmCQvhxfDoFmg== - dependencies: - "@binance/w3w-utils" "^1.1.6" - "@binance/w3w-wagmi-connector-v2" "^1.2.3" - "@reef-knot/wallet-adapter-binance-wallet@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-binance-wallet/-/wallet-adapter-binance-wallet-1.0.3.tgz#a8f93273261792db03a47c21f6b3bce2c893ddf6" @@ -2998,10 +2990,15 @@ resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-coin98/-/wallet-adapter-coin98-2.1.0.tgz#d145ac52e3e9dfbb607aa7d3316254bdb5838e3c" integrity sha512-etN3IcWh4Dlox7H4bwff+nRn7vTSBS8WPmqaGQfKhOW7NVAnlMQDjBD5ZO11CGPbwYtJ7dAkRi8A8H5JEeuvGg== -"@reef-knot/wallet-adapter-coinbase@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-coinbase/-/wallet-adapter-coinbase-2.1.0.tgz#9edb16889aa777fe90d6bb5582af2a577020f12e" - integrity sha512-AGI21eh6j7NOtTfiYNZk1u9qYIeOedv0iOCOga9p8DDLzEEzXGsr+dHZq39JX4TGk4BjEPAZUifbYf1ZS+ySDg== +"@reef-knot/wallet-adapter-coinbase-smart-wallet@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-coinbase-smart-wallet/-/wallet-adapter-coinbase-smart-wallet-1.0.0.tgz#f3a2cea182059e280845ac9137dfd5cc8fe78302" + integrity sha512-txZteS7ZOBPPr9pqbcEt0uLIOf5CB9imjTD6bu+uFEfpW4aLCdLnwqnhL+Vr1httKhSqGJPKKG8rP6onnp5Egw== + +"@reef-knot/wallet-adapter-coinbase@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@reef-knot/wallet-adapter-coinbase/-/wallet-adapter-coinbase-2.2.0.tgz#b74e77211dc3da5ef279428c119be5c79348e078" + integrity sha512-nJCUHzkPKKtHU6xK4FzzebjYwWdlxhOrHgOjCZMTSOxrOeexImtvS+ray8PtRH/PePlB1aAx/3osE5/JlPAdTA== "@reef-knot/wallet-adapter-dapp-browser-injected@2.0.1": version "2.0.1" @@ -3066,10 +3063,10 @@ "@types/ua-parser-js" "0.7.39" ua-parser-js "1.0.37" -"@reef-knot/wallets-list@2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@reef-knot/wallets-list/-/wallets-list-2.2.3.tgz#8f89183622a32e97031222aeba6dd0178e5fbc2a" - integrity sha512-4L9A1AharYM4FxzzN8VFjaR5zRkHbqa80Mp56aw4dR/DJj7VOvbM4MieSvh179FEjs2lFyPz2oanQ+nqiKpu8A== +"@reef-knot/wallets-list@2.3.0", "@reef-knot/wallets-list@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@reef-knot/wallets-list/-/wallets-list-2.3.0.tgz#066be557d5b26a45954509347ebcf36d2808dacf" + integrity sha512-yZ6EzrvULM7ZSY8iDK6CA+//UbkyID2tLFj69ZHD5JRYn8Pt7ecy2FCxMPfk8Z7KXDhKiT35r4eK33q9VzxfqQ== dependencies: "@reef-knot/wallet-adapter-ambire" "2.0.1" "@reef-knot/wallet-adapter-binance-wallet" "1.0.3" @@ -3077,31 +3074,8 @@ "@reef-knot/wallet-adapter-brave" "2.1.0" "@reef-knot/wallet-adapter-browser-extension" "2.0.1" "@reef-knot/wallet-adapter-coin98" "2.1.0" - "@reef-knot/wallet-adapter-coinbase" "2.1.0" - "@reef-knot/wallet-adapter-dapp-browser-injected" "2.0.1" - "@reef-knot/wallet-adapter-exodus" "2.1.0" - "@reef-knot/wallet-adapter-imtoken" "2.0.1" - "@reef-knot/wallet-adapter-ledger-hid" "3.0.1" - "@reef-knot/wallet-adapter-ledger-live" "3.0.1" - "@reef-knot/wallet-adapter-metamask" "2.1.0" - "@reef-knot/wallet-adapter-okx" "2.1.0" - "@reef-knot/wallet-adapter-safe" "2.0.1" - "@reef-knot/wallet-adapter-trust" "2.1.0" - "@reef-knot/wallet-adapter-walletconnect" "2.0.1" - "@reef-knot/wallet-adapter-xdefi" "2.1.0" - -"@reef-knot/wallets-list@^2.2.2": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@reef-knot/wallets-list/-/wallets-list-2.2.2.tgz#249e99f092bccac6bb8d71d389396b0bb282116f" - integrity sha512-GZvgQOlqH5rReIgRL+qnGbRc26EpmaHxv8gGm8mv7eOcMaXS5f85XDcOb5qaNTp7iHyuD+SPrhe+NLJO1EECwQ== - dependencies: - "@reef-knot/wallet-adapter-ambire" "2.0.1" - "@reef-knot/wallet-adapter-binance-wallet" "1.0.2" - "@reef-knot/wallet-adapter-bitkeep" "2.1.0" - "@reef-knot/wallet-adapter-brave" "2.1.0" - "@reef-knot/wallet-adapter-browser-extension" "2.0.1" - "@reef-knot/wallet-adapter-coin98" "2.1.0" - "@reef-knot/wallet-adapter-coinbase" "2.1.0" + "@reef-knot/wallet-adapter-coinbase" "2.2.0" + "@reef-knot/wallet-adapter-coinbase-smart-wallet" "1.0.0" "@reef-knot/wallet-adapter-dapp-browser-injected" "2.0.1" "@reef-knot/wallet-adapter-exodus" "2.1.0" "@reef-knot/wallet-adapter-imtoken" "2.0.1" @@ -9818,18 +9792,18 @@ redis-parser@^3.0.0: dependencies: redis-errors "^1.0.0" -reef-knot@5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/reef-knot/-/reef-knot-5.5.4.tgz#dd77cb1f6a0ede01909e6303b8a7751fc67963f0" - integrity sha512-zHPnuTuKOU3ic4W14khX5M3CRcX0NYEItZBTDSFxQ2w9W316h/TlV/HMgM1IeBaWMvPIKXfY/NQRrsupW63vkw== +reef-knot@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/reef-knot/-/reef-knot-5.6.0.tgz#da0076d811e83fc7191399ccec7788f1ba5fe16a" + integrity sha512-5JflqvV9nnqUQ+YfgTwW5mZ5ble13Keb3eFSvfgH4VLBoCAUfezrD+9WtVtSCXx/ozEoNPpvE5vNGjrM9GZZSg== dependencies: - "@reef-knot/connect-wallet-modal" "5.3.3" + "@reef-knot/connect-wallet-modal" "5.4.0" "@reef-knot/core-react" "4.2.1" "@reef-knot/ledger-connector" "4.1.0" "@reef-knot/types" "2.1.0" "@reef-knot/ui-react" "2.1.3" "@reef-knot/wallets-helpers" "2.1.0" - "@reef-knot/wallets-list" "2.2.3" + "@reef-knot/wallets-list" "2.3.0" "@reef-knot/web3-react" "4.0.1" reflect.getprototypeof@^1.0.4: From 3d4181ba6e21e9790e53ebd59ac76694d52c3c00 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 18 Sep 2024 12:03:53 +0700 Subject: [PATCH 11/14] fix: batch withdrawal status requests --- .../hooks/contract/useWithdrawalsData.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/features/withdrawals/hooks/contract/useWithdrawalsData.ts b/features/withdrawals/hooks/contract/useWithdrawalsData.ts index 138078de..2676f3bb 100644 --- a/features/withdrawals/hooks/contract/useWithdrawalsData.ts +++ b/features/withdrawals/hooks/contract/useWithdrawalsData.ts @@ -14,6 +14,7 @@ import { standardFetcher } from 'utils/standardFetcher'; import { default as dynamics } from 'config/dynamics'; import { encodeURLQuery } from 'utils/encodeURLQuery'; +import type { WithdrawalQueueAbi } from '@lido-sdk/contracts'; export type WithdrawalRequests = NonNullable< ReturnType['data'] @@ -99,7 +100,17 @@ export const useWithdrawalRequests = () => { }), contractRpc.getLastCheckpointIndex(), ]); - const requestStatuses = await contractRpc.getWithdrawalStatus(requestIds); + + const STATUS_BATCH_SIZE = 500; + const requestStatuses: Awaited< + ReturnType + > = []; + + for (let i = 0; i < requestIds.length; i += STATUS_BATCH_SIZE) { + const batch = requestIds.slice(i, i + STATUS_BATCH_SIZE); + const batchStatuses = await contractRpc.getWithdrawalStatus(batch); + requestStatuses.push(...batchStatuses); + } const claimableRequests: RequestStatus[] = []; const pendingRequests: RequestStatusPending[] = []; From 75bd625b5e5b3a50b721f2853cefbbc06fa4a67e Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 18 Sep 2024 13:53:18 +0700 Subject: [PATCH 12/14] fix: increase custom server security --- next.config.mjs | 6 ++++-- server.mjs | 10 ++++++++-- utilsApi/rpcFactory.ts | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index ce2c62c2..13f10cf1 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -7,11 +7,13 @@ import { startupCheckRPCs } from './scripts/startup-checks/rpc.mjs'; logEnvironmentVariables(); buildDynamics(); -if (process.env.RUN_STARTUP_CHECKS === 'true' && typeof window === 'undefined') { +if ( + process.env.RUN_STARTUP_CHECKS === 'true' && + typeof window === 'undefined' +) { void startupCheckRPCs(); } - // https://nextjs.org/docs/pages/api-reference/next-config-js/basePath const basePath = process.env.BASE_PATH; diff --git a/server.mjs b/server.mjs index 0c91cc43..8734385a 100644 --- a/server.mjs +++ b/server.mjs @@ -31,7 +31,7 @@ const overrideSetHeader = (res) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises app.prepare().then(() => { - createServer(async (req, res) => { + const server = createServer(async (req, res) => { // Be sure to pass `true` as the second argument to `url.parse`. // This tells it to parse the query portion of the URL. const parsedUrl = parse(req.url, true); @@ -46,5 +46,11 @@ app.prepare().then(() => { }) .listen(port, () => { console.debug(`> Ready on http://${hostname}:${port}`); - }); + }) + // hanging socket timeout + .setTimeout(10_000); + // prevents malicious client from slowly sending headers and rest of request + server.headersTimeout = 10_000; + server.requestTimeout = 30_000; + server.maxHeadersCount = 50; }); diff --git a/utilsApi/rpcFactory.ts b/utilsApi/rpcFactory.ts index 81089660..2142ac7c 100644 --- a/utilsApi/rpcFactory.ts +++ b/utilsApi/rpcFactory.ts @@ -225,8 +225,8 @@ export const rpcFactory = ({ console.warn( `[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`, ); - res.statusCode = 413; // Payload Too Large - res.end(error.message); + // Payload Too Large + res.status(413).end(); } else { res.statusCode = 500; res.end(DEFAULT_API_ERROR_MESSAGE); From ffed48517c386237f170bde3ff5f1ff9c39c4d97 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 18 Sep 2024 15:14:43 +0700 Subject: [PATCH 13/14] fix: catch for eth_call metric --- utilsApi/nextApiWrappers.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/utilsApi/nextApiWrappers.ts b/utilsApi/nextApiWrappers.ts index cf92ce50..9fa6efc5 100644 --- a/utilsApi/nextApiWrappers.ts +++ b/utilsApi/nextApiWrappers.ts @@ -142,10 +142,20 @@ const collectRequestAddressMetric = async ({ const address = utils.getAddress(to) as `0x${string}`; const contractName = METRIC_CONTRACT_ADDRESSES[chainId]?.[address]; const methodEncoded = data?.slice(0, 10); // `0x` and 8 next symbols - const methodDecoded = contractName - ? getMetricContractInterface(contractName)?.getFunction(methodEncoded) - ?.name - : null; + + let methodDecoded = 'N/A'; + try { + if (contractName) { + methodDecoded = + getMetricContractInterface(contractName).getFunction( + methodEncoded, + ).name; + } + } catch (error) { + console.warn( + `[collectRequestAddressMetric] failed to decode ${methodEncoded} method for ${contractName}: ${error} `, + ); + } metrics .labels({ From 13d1c48f715d4d81216cbefc8e8b2eb239c5a9d3 Mon Sep 17 00:00:00 2001 From: Alexander Khramov Date: Wed, 18 Sep 2024 20:38:01 +0300 Subject: [PATCH 14/14] feat: add matomo events for coinbase smart wallet --- consts/matomo-wallets-events.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/consts/matomo-wallets-events.ts b/consts/matomo-wallets-events.ts index 2028ac3b..5c385c43 100644 --- a/consts/matomo-wallets-events.ts +++ b/consts/matomo-wallets-events.ts @@ -10,7 +10,9 @@ export const enum MATOMO_WALLETS_EVENTS_TYPES { onClickCoin98 = 'onClickCoin98', onConnectCoin98 = 'onConnectCoin98', onClickCoinbase = 'onClickCoinbase', + onClickCoinbaseSmartWallet = 'onClickCoinbaseSmartWallet', onConnectCoinbase = 'onConnectCoinbase', + onConnectCoinbaseSmartWallet = 'onConnectCoinbaseSmartWallet', onClickExodus = 'onClickExodus', onConnectExodus = 'onConnectExodus', onClickImToken = 'onClickImToken', @@ -79,6 +81,16 @@ export const MATOMO_WALLETS_EVENTS: Record< 'Connect Coinbase Wallet wallet', 'eth_widget_connect_coinbase_wallet', ], + [MATOMO_WALLETS_EVENTS_TYPES.onClickCoinbaseSmartWallet]: [ + 'Ethereum_Staking_Widget', + 'Click Coinbase Smart Wallet wallet', + 'eth_widget_click_coinbase_smart_wallet', + ], + [MATOMO_WALLETS_EVENTS_TYPES.onConnectCoinbaseSmartWallet]: [ + 'Ethereum_Staking_Widget', + 'Connect Coinbase Smart Wallet wallet', + 'eth_widget_connect_coinbase_smart_wallet', + ], [MATOMO_WALLETS_EVENTS_TYPES.onClickExodus]: [ 'Ethereum_Staking_Widget', 'Click Exodus wallet', @@ -202,6 +214,9 @@ export const walletsMetrics: Metrics = { brave: getMetricHandler(MATOMO_WALLETS_EVENTS.onClickBrave), coin98: getMetricHandler(MATOMO_WALLETS_EVENTS.onClickCoin98), coinbase: getMetricHandler(MATOMO_WALLETS_EVENTS.onClickCoinbase), + coinbaseSmartWallet: getMetricHandler( + MATOMO_WALLETS_EVENTS.onClickCoinbaseSmartWallet, + ), exodus: getMetricHandler(MATOMO_WALLETS_EVENTS.onClickExodus), imToken: getMetricHandler(MATOMO_WALLETS_EVENTS.onClickImToken), ledgerHID: getMetricHandler(MATOMO_WALLETS_EVENTS.onClickLedger), @@ -223,6 +238,9 @@ export const walletsMetrics: Metrics = { brave: getMetricHandler(MATOMO_WALLETS_EVENTS.onConnectBrave), coin98: getMetricHandler(MATOMO_WALLETS_EVENTS.onConnectCoin98), coinbase: getMetricHandler(MATOMO_WALLETS_EVENTS.onConnectCoinbase), + coinbaseSmartWallet: getMetricHandler( + MATOMO_WALLETS_EVENTS.onConnectCoinbaseSmartWallet, + ), exodus: getMetricHandler(MATOMO_WALLETS_EVENTS.onConnectExodus), imToken: getMetricHandler(MATOMO_WALLETS_EVENTS.onConnectImToken), ledgerHID: getMetricHandler(MATOMO_WALLETS_EVENTS.onConnectLedger),