chore: update use node health and query to include vega tme

This commit is contained in:
Matthew Russell
2023-02-15 11:47:55 -08:00
parent 0dcb026dea
commit 609e7bdc4d
4 changed files with 130 additions and 94 deletions
+97 -11
View File
@@ -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 <DynamicLoader />;
}
return (
<HashRouter>
{/* <AppBody {...props} /> */}
<Test />
<button onClick={() => setOpen(true)}>Status</button>
<NodeSwitcher open={open} setOpen={setOpen} />
<ApolloProvider client={client}>
<Test />
<button onClick={() => setOpen(true)}>Status</button>
<NodeSwitcher open={open} setOpen={setOpen} />
</ApolloProvider>
</HashRouter>
);
}
@@ -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 = ({
<tr>
<th className="text-left">node</th>
<th className="text-right">response time</th>
<th className="text-right">block height</th>
<th className="text-right">core block height</th>
<th className="text-right">datanode block height</th>
</tr>
</thead>
<tbody>
@@ -199,7 +216,10 @@ const Row = ({ url }: { url: string }) => {
const [time, setTime] = useState<number>();
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 (
<>
<td>{url}</td>
<td className="text-right">{time ? time.toFixed(2) + 'ms' : 'n/a'}</td>
<td className="text-right">{data?.statistics.blockHeight || '-'}</td>
<td className={headerBlockHeightClass}>{headers?.blockHeight || '-'}</td>
</>
);
};
@@ -226,10 +254,20 @@ const Test = () => {
status: store.status,
}));
const headers = useHeaderStore();
const {
coreBlockHeight,
coreVegaTime,
datanodeBlockHeight,
datanodeVegaTime,
} = useNodeHealth();
return (
<div>
<pre>{JSON.stringify(env, null, 2)}</pre>
<pre>{JSON.stringify(headers, null, 2)}</pre>
<div>Core BH {coreBlockHeight}</div>
<div>Core time {coreVegaTime.toISOString()}</div>
<div>Datanode BH {datanodeBlockHeight}</div>
<div>Datanode time {datanodeVegaTime?.toISOString()}</div>
</div>
);
};
@@ -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,
},
},
};
+30 -82
View File
@@ -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<typeof createClient>
) => {
try {
const result = await client?.query<StatisticsQuery>({
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,
};
};
+1
View File
@@ -2,6 +2,7 @@ query Statistics {
statistics {
chainId
blockHeight
vegaTime
}
}
+2 -1
View File
@@ -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
}
}
`;