diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index 9effb991c..9565d6268 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -18,13 +18,12 @@ import { } from '@vegaprotocol/web3'; import { clients, - EnvironmentProvider, envTriggerMapping, Networks, - NodeSwitcherDialog, useEnvironment, useEnvironment2, useInitializeEnv, + useNodeHealth, useStatisticsQuery, } from '@vegaprotocol/environment'; import { AppLoader, Web3Provider } from '../components/app-loader'; @@ -41,8 +40,10 @@ import { ViewingBanner } from '../components/viewing-banner'; import { Banner } from '../components/banner'; import classNames from 'classnames'; import { Dialog } from '@vegaprotocol/ui-toolkit'; +import type { InMemoryCacheConfig } from '@apollo/client'; import { ApolloProvider } from '@apollo/client'; -import { useHeaderStore } from '@vegaprotocol/apollo-client'; +import { createClient, useHeaderStore } from '@vegaprotocol/apollo-client'; +import classNames from 'classnames'; const DEFAULT_TITLE = t('Welcome to Vega trading!'); @@ -126,19 +127,34 @@ const DynamicLoader = dynamic( function VegaTradingApp(props: AppProps) { const [open, setOpen] = useState(false); - const status = useEnvironment2((store) => store.status); + const { status, url } = useEnvironment2((store) => ({ + status: store.status, + url: store.url, + })); + useInitializeEnv(); - if (status === 'default' || status === 'pending') { + const client = useMemo(() => { + if (url) { + return createClient({ + url, + cacheConfig, + }); + } + return undefined; + }, [url]); + + if (status === 'default' || status === 'pending' || !client) { return ; } return ( - {/* */} - - - + + + + + ); } @@ -151,7 +167,7 @@ const NodeSwitcher = ({ setOpen: (x: boolean) => void; }) => { const [customUrl, setCustomUrl] = useState(''); - const { status, nodes, setUrl } = useEnvironment2((store) => ({ + const { nodes, setUrl } = useEnvironment2((store) => ({ status: store.status, nodes: store.nodes, setUrl: store.setUrl, @@ -164,7 +180,8 @@ const NodeSwitcher = ({ node response time - block height + core block height + datanode block height @@ -199,7 +216,10 @@ const Row = ({ url }: { url: string }) => { const [time, setTime] = useState(); const { data } = useStatisticsQuery({ pollInterval: 3000, + fetchPolicy: 'no-cache', }); + const headerStore = useHeaderStore(); + const headers = headerStore[url]; useEffect(() => { const requestUrl = new URL(url); @@ -209,11 +229,19 @@ const Row = ({ url }: { url: string }) => { setTime(duration); }, [url]); + const headerBlockHeightClass = classNames('text-right', { + 'text-vega-pink': + headers && + data && + headers.blockHeight < Number(data.statistics.blockHeight) - 3, + }); + return ( <> {url} {time ? time.toFixed(2) + 'ms' : 'n/a'} {data?.statistics.blockHeight || '-'} + {headers?.blockHeight || '-'} ); }; @@ -226,10 +254,20 @@ const Test = () => { status: store.status, })); const headers = useHeaderStore(); + const { + coreBlockHeight, + coreVegaTime, + datanodeBlockHeight, + datanodeVegaTime, + } = useNodeHealth(); return (
{JSON.stringify(env, null, 2)}
{JSON.stringify(headers, null, 2)}
+
Core BH {coreBlockHeight}
+
Core time {coreVegaTime.toISOString()}
+
Datanode BH {datanodeBlockHeight}
+
Datanode time {datanodeVegaTime?.toISOString()}
); }; @@ -248,3 +286,51 @@ const MaybeConnectEagerly = () => { } return null; }; + +const cacheConfig: InMemoryCacheConfig = { + typePolicies: { + Account: { + keyFields: false, + fields: { + balanceFormatted: {}, + }, + }, + Instrument: { + keyFields: false, + }, + TradableInstrument: { + keyFields: ['instrument'], + }, + Product: { + keyFields: ['settlementAsset', ['id']], + }, + MarketData: { + keyFields: ['market', ['id']], + }, + Node: { + keyFields: false, + }, + Withdrawal: { + fields: { + pendingOnForeignChain: { + read: (isPending = false) => isPending, + }, + }, + }, + ERC20: { + keyFields: ['contractAddress'], + }, + PositionUpdate: { + keyFields: false, + }, + AccountUpdate: { + keyFields: false, + }, + Party: { + keyFields: false, + }, + Fees: { + keyFields: false, + }, + }, +}; diff --git a/libs/environment/src/hooks/use-node-health.ts b/libs/environment/src/hooks/use-node-health.ts index e54cddfaa..a4eee921f 100644 --- a/libs/environment/src/hooks/use-node-health.ts +++ b/libs/environment/src/hooks/use-node-health.ts @@ -1,88 +1,36 @@ -import compact from 'lodash/compact'; -import shuffle from 'lodash/shuffle'; -import type { createClient } from '@vegaprotocol/apollo-client'; -import { useEffect, useState } from 'react'; -import type { StatisticsQuery } from '../utils/__generated__/Node'; -import { StatisticsDocument } from '../utils/__generated__/Node'; -import type { ClientCollection } from './use-nodes'; +import { useMemo } from 'react'; +import { useStatisticsQuery } from '../utils/__generated__/Node'; +import { useHeaderStore } from '@vegaprotocol/apollo-client'; +import { useEnvironment } from './use-environment'; +import { fromNanoSeconds } from '@vegaprotocol/react-helpers'; +import { useEnvironment2 } from './use-environment-2'; -// How often to query other nodes -export const INTERVAL_TIME = 30 * 1000; -// Number of nodes to query against -export const NODE_SUBSET_COUNT = 5; +export const useNodeHealth = () => { + const url = useEnvironment2((store) => store.url); + const headerStore = useHeaderStore(); + const headers = url ? headerStore[url] : undefined; + const { data } = useStatisticsQuery({ + pollInterval: 1000, + fetchPolicy: 'no-cache', + }); -// Queries all nodes from the environment provider via an interval -// to calculate and return the difference between the most advanced block -// and the block height of the current node -export const useNodeHealth = (clients: ClientCollection, vegaUrl?: string) => { - const [blockDiff, setBlockDiff] = useState(0); + const blockDiff = useMemo(() => { + if (!data?.statistics.blockHeight) { + return null; + } - useEffect(() => { - if (!clients || !vegaUrl) return; + if (!headers) { + return 0; + } - const fetchBlockHeight = async ( - client?: ReturnType - ) => { - try { - const result = await client?.query({ - query: StatisticsDocument, - fetchPolicy: 'no-cache', // always fetch and never cache - }); + return Number(data.statistics.blockHeight) - headers.blockHeight; + }, [data, headers]); - if (!result) return null; - if (result.error) return null; - return result; - } catch { - return null; - } - }; - - const getBlockHeights = async () => { - const nodes = Object.keys(clients).filter((key) => key !== vegaUrl); - // make sure that your current vega url is always included - // so we can compare later - const testNodes = [vegaUrl, ...randomSubset(nodes, NODE_SUBSET_COUNT)]; - const result = await Promise.all( - testNodes.map((node) => fetchBlockHeight(clients[node])) - ); - const blockHeights: { [node: string]: number | null } = {}; - testNodes.forEach((node, i) => { - const data = result[i]; - const blockHeight = data - ? Number(data?.data.statistics.blockHeight) - : null; - blockHeights[node] = blockHeight; - }); - return blockHeights; - }; - - // Every INTERVAL_TIME get block heights of a random subset - // of nodes and determine if your current node is falling behind - const interval = setInterval(async () => { - const blockHeights = await getBlockHeights(); - const highestBlock = Math.max.apply( - null, - compact(Object.values(blockHeights)) - ); - const currNodeBlock = blockHeights[vegaUrl]; - - if (!currNodeBlock) { - // Block height query failed and null was returned - setBlockDiff(-1); - } else { - setBlockDiff(highestBlock - currNodeBlock); - } - }, INTERVAL_TIME); - - return () => { - clearInterval(interval); - }; - }, [clients, vegaUrl]); - - return blockDiff; -}; - -const randomSubset = (arr: string[], size: number) => { - const shuffled = shuffle(arr); - return shuffled.slice(0, size); + return { + coreBlockHeight: Number(data?.statistics.blockHeight || 0), + coreVegaTime: fromNanoSeconds(data?.statistics.vegaTime), + datanodeBlockHeight: headers?.blockHeight, + datanodeVegaTime: headers?.timestamp, + blockDiff, + }; }; diff --git a/libs/environment/src/utils/Node.graphql b/libs/environment/src/utils/Node.graphql index 6cb58464e..64ab73970 100644 --- a/libs/environment/src/utils/Node.graphql +++ b/libs/environment/src/utils/Node.graphql @@ -2,6 +2,7 @@ query Statistics { statistics { chainId blockHeight + vegaTime } } diff --git a/libs/environment/src/utils/__generated__/Node.ts b/libs/environment/src/utils/__generated__/Node.ts index 4be4f15b6..2f256176c 100644 --- a/libs/environment/src/utils/__generated__/Node.ts +++ b/libs/environment/src/utils/__generated__/Node.ts @@ -6,7 +6,7 @@ const defaultOptions = {} as const; export type StatisticsQueryVariables = Types.Exact<{ [key: string]: never; }>; -export type StatisticsQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string } }; +export type StatisticsQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any } }; export type BlockTimeSubscriptionVariables = Types.Exact<{ [key: string]: never; }>; @@ -19,6 +19,7 @@ export const StatisticsDocument = gql` statistics { chainId blockHeight + vegaTime } } `;