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" + } ] 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/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/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), 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/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[] = []; 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} /> - - - - ); }; 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/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/scripts/startup-checks/rpc.mjs b/scripts/startup-checks/rpc.mjs index 7bb55cb4..a9afe665 100644 --- a/scripts/startup-checks/rpc.mjs +++ b/scripts/startup-checks/rpc.mjs @@ -1,78 +1,127 @@ import { createClient, http } from 'viem'; -import { getChainId } from 'viem/actions' - -// Safely initialize a global variable -let globalRPCCheckResults = globalThis.__rpcCheckResults || []; -globalThis.__rpcCheckResults = globalRPCCheckResults; +import { getChainId } from 'viem/actions'; 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 = 10_000; -const pushRPCCheckResult = (domain, success) => { - globalRPCCheckResults.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 getRPCCheckResults = () => globalThis.__rpcCheckResults || []; +// 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); }; -export const startupCheckRPCs = async () => { - console.info('[startupCheckRPCs] Starting...'); +const pushRPCCheckResult = (domain, success) => { + globalStartupRPCChecks.results.push({ domain, success }); +}; +const checkRPC = async (url, defaultChain) => { + let domain; try { - const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10); - const rpcUrls = getRpcUrls(defaultChain); + domain = new URL(url).hostname; + } catch { + console.error(`[checkRPC] Invalid URL: ${url}`); + pushRPCCheckResult(BROKEN_URL, false); + return false; + } - if (!rpcUrls || rpcUrls.length === 0) { - throw new Error('[startupCheckRPCs] No RPC URLs found!'); + 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(`[checkRPCWithRetries] Attempt ${attempt} for RPC ${domain}`); + + const result = await Promise.race([ + checkRPC(url, defaultChain), + timeoutPromise(RPC_TIMEOUT_MS, `[checkRPCWithRetries] RPC ${domain} timed out`), + ]); - let errorCount = 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; + if (!result) { + throw new Error('[checkRPCWithRetries] Promise(checkRPC) returned false!'); } - 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; + // Stop checking for ${url} with success (a success is set in the 'checkRPC' function) + return true; + } catch (err) { + console.error(`[checkRPCWithRetries] Error on attempt ${attempt} for RPC ${domain}: ${err.message}`); + + if (attempt === MAX_RETRY_COUNT) { + console.error(`[checkRPCWithRetries] Failed after ${MAX_RETRY_COUNT} attempts for ${domain}`); 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`); + } else { + console.info(`[checkRPCWithRetries] Retrying in ${RETRY_WAIT_TIME_MS} ms...`); + await sleep(RETRY_WAIT_TIME_MS); } } + } - if (errorCount > 0) { - console.info(`[startupCheckRPCs] Number of working RPCs: ${rpcUrls.length - errorCount}`); - console.info(`[startupCheckRPCs] Number of broken RPCs: ${errorCount}`); - } else { - console.info('[startupCheckRPCs] All RPC works!'); - } - } catch (err) { - console.error('[startupCheckRPCs] Error during startup check:'); - console.error(err); + return false; +}; + +export const getRPCChecks = () => globalStartupRPCChecks.promise; + +export const startupCheckRPCs = async () => { + console.info('[startupCheckRPCs] Starting RPC checks...'); + + if (globalStartupRPCChecks.promise) { + return globalStartupRPCChecks.promise; } + + globalStartupRPCChecks.promise = (async () => { + try { + const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10); + const rpcUrls = getRPCUrls(defaultChain); + + if (!rpcUrls.length) { + throw new Error('[startupCheckRPCs] No RPC URLs found!'); + } + + 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}`); + + return globalStartupRPCChecks.results; + } catch (err) { + console.error('[startupCheckRPCs] Error during RPC checks:', err); + + return null; + } + })(); }; 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/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 aefc9807..c21715de 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.DEFAULT_REVALIDATION, }; /// custom getStaticProps @@ -35,11 +35,12 @@ 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); + Metrics.request.ssrCounter + .labels({ revalidate: String(result.revalidate) }) + .inc(1); - shouldZeroRevalidate = false; return result; }; }; 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-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 9fd41785..baaf99bf 100644 --- a/utilsApi/metrics/startup-metrics.ts +++ b/utilsApi/metrics/startup-metrics.ts @@ -3,23 +3,41 @@ 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 } from 'scripts/startup-checks/rpc.mjs'; import { config } from 'config'; import { METRICS_PREFIX } from 'consts/metrics'; import { StartupChecksRPCMetrics } from './startup-checks'; -const collectStartupChecksRPCMetrics = (registry: Registry): void => { +const collectStartupChecksRPCMetrics = async ( + registry: Registry, +): Promise => { const rpcMetrics = new StartupChecksRPCMetrics(registry); - getRPCCheckResults().forEach( - (_check: { domain: string; success: boolean }) => { - rpcMetrics.requestCounter - .labels(_check.domain, _check.success.toString()) - .inc(); - }, - ); + try { + // Await the promise if it's still in progress + const rpcChecksResults = await getRPCChecks(); + + if (!rpcChecksResults) { + throw new Error( + '[collectStartupChecksRPCMetrics] getRPCChecks resolved as "null"!', + ); + } + + rpcChecksResults.forEach((_check: { domain: string; success: boolean }) => { + rpcMetrics.requestStatusGauge + .labels(_check.domain) + .set(Number(+!_check.success)); + }); + } catch (error) { + console.error( + `[collectStartupChecksRPCMetrics] Error collecting RPC metrics: ${error}`, + ); + rpcMetrics.requestStatusGauge + .labels('BROKEN_URL') // false as string + .inc(1); + } }; const collectEnvInfoMetrics = (registry: Registry): void => { @@ -37,7 +55,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 +70,5 @@ export const collectStartupMetrics = (registry: Registry): void => { branch: buildInfoJson.branch, }); - collectStartupChecksRPCMetrics(registry); + await collectStartupChecksRPCMetrics(registry); }; 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({ diff --git a/utilsApi/rpcFactory.ts b/utilsApi/rpcFactory.ts index fd1fe239..2142ac7c 100644 --- a/utilsApi/rpcFactory.ts +++ b/utilsApi/rpcFactory.ts @@ -222,11 +222,11 @@ export const rpcFactory = ({ .pipe(sizeLimit) .on('error', (error) => { if (error instanceof SizeTooLargeError) { - console.warn('[rpcFactory] RPC response too large', { - request: JSON.stringify(requests), - }); - res.statusCode = 413; // Payload Too Large - res.end(error.message); + console.warn( + `[rpcFactory] RPC response too large: ${JSON.stringify(requests)}`, + ); + // Payload Too Large + res.status(413).end(); } else { res.statusCode = 500; res.end(DEFAULT_API_ERROR_MESSAGE); 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: