chore: update use node health and query to include vega tme
This commit is contained in:
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ query Statistics {
|
||||
statistics {
|
||||
chainId
|
||||
blockHeight
|
||||
vegaTime
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user