Fix problems with delivery checking

Hide IGP debug errors for PI chains
Update react-query lib
Remove unused wagmi and rainbowkit libs
pull/73/head
J M Rossy 7 months ago
parent 84a9a3420a
commit b6baaa1e5e
  1. 4
      package.json
  2. 28
      src/features/debugger/debugMessage.ts
  3. 7
      src/features/deliveryStatus/fetchDeliveryStatus.ts
  4. 117
      src/features/deliveryStatus/useMessageDeliveryStatus.tsx
  5. 15
      src/features/messages/MessageDetails.tsx
  6. 18
      src/features/messages/cards/TransactionCard.tsx
  7. 17
      src/features/messages/ica.ts
  8. 38
      src/features/messages/pi-queries/usePiChainMessageQuery.ts
  9. 4
      src/features/messages/queries/useMessageQuery.ts
  10. 40
      src/pages/_app.tsx
  11. 20
      src/store.ts
  12. 2135
      yarn.lock

@ -10,8 +10,7 @@
"@hyperlane-xyz/utils": "3.11.1",
"@hyperlane-xyz/widgets": "3.8.0",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
"@rainbow-me/rainbowkit": "0.12.16",
"@tanstack/react-query": "^4.24.10",
"@tanstack/react-query": "^5.35.5",
"bignumber.js": "^9.1.2",
"buffer": "^6.0.3",
"ethers": "^5.7.2",
@ -24,7 +23,6 @@
"react-toastify": "^9.1.1",
"react-tooltip": "^5.26.3",
"urql": "^3.0.3",
"wagmi": "0.12.18",
"zod": "^3.21.2",
"zustand": "4.3.8"
},

@ -31,6 +31,7 @@ import { GasPayment, IsmModuleTypes, MessageDebugResult, MessageDebugStatus } fr
type Provider = providers.Provider;
const HANDLE_FUNCTION_SIG = 'handle(uint32,bytes32,bytes)';
const IGP_PAYMENT_CHECK_DELAY = 30_000; // 30 seconds
export async function debugMessage(
multiProvider: MultiProvider,
@ -41,10 +42,12 @@ export async function debugMessage(
nonce,
sender,
recipient,
origin,
originDomainId: originDomain,
destinationDomainId: destDomain,
body,
totalGasAmount,
isPiMsg,
}: Message,
): Promise<MessageDebugResult> {
logger.debug(`Debugging message id: ${msgId}`);
@ -95,15 +98,21 @@ export async function debugMessage(
if (ismCheckResult.status && ismCheckResult.description) return { ...ismCheckResult, ...details };
else details.ismDetails = ismCheckResult.ismDetails;
const gasCheckResult = await tryCheckIgpGasFunded(
msgId,
originProvider,
deliveryResult.gasEstimate,
totalGasAmount,
);
if (gasCheckResult?.status && gasCheckResult?.description)
return { ...gasCheckResult, ...details };
else details.gasDetails = gasCheckResult?.gasDetails;
// TODO support for non-default IGP gas checks here
// Disabling for now for https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3668
// Also skipping if the message is still very new otherwise this raises premature
// underfunded errors when in fact payment was made
if (!isPiMsg && Date.now() - origin.timestamp > IGP_PAYMENT_CHECK_DELAY) {
const gasCheckResult = await tryCheckIgpGasFunded(
msgId,
originProvider,
deliveryResult.gasEstimate,
totalGasAmount,
);
if (gasCheckResult?.status && gasCheckResult?.description)
return { ...gasCheckResult, ...details };
else details.gasDetails = gasCheckResult?.gasDetails;
}
logger.debug(`No errors found debugging message id: ${msgId}`);
return {
@ -243,6 +252,7 @@ async function tryCheckIgpGasFunded(
logger.warn('No gas estimate provided, skipping IGP check');
return null;
}
try {
let gasAlreadyFunded = BigNumber.from(0);
let gasDetails: MessageDebugResult['gasDetails'] = {

@ -19,6 +19,8 @@ import {
MessageDeliverySuccessResult,
} from './types';
const DELIVERY_LOG_CHECK_BLOCK_RANGE = 1000;
export async function fetchDeliveryStatus(
multiProvider: MultiProvider,
registry: IRegistry,
@ -92,7 +94,8 @@ async function checkIsMessageDelivered(
// Try finding logs first as they have more info
try {
logger.debug(`Searching for process logs for msgId ${msgId}`);
const logs = await mailbox.queryFilter(mailbox.filters.ProcessId(msgId));
const fromBlock = (await provider.getBlockNumber()) - DELIVERY_LOG_CHECK_BLOCK_RANGE;
const logs = await mailbox.queryFilter(mailbox.filters.ProcessId(msgId), fromBlock, 'latest');
if (logs?.length) {
logger.debug(`Found process log for ${msgId}}`);
const log = logs[0]; // Should only be 1 log per message delivery
@ -109,7 +112,7 @@ async function checkIsMessageDelivered(
// Logs are unreliable so check the mailbox itself as a fallback
logger.debug(`Querying mailbox about msgId ${msgId}`);
const isDelivered = await mailbox.delivered(msgId);
logger.debug(`Mailbox delivery status for ${msgId}: ${isDelivered}}`);
logger.debug(`Mailbox delivery status for ${msgId}: ${isDelivered}`);
return { isDelivered };
}

@ -1,10 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import { useEffect, useMemo } from 'react';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { MultiProvider } from '@hyperlane-xyz/sdk';
import { errorToString } from '@hyperlane-xyz/utils';
import { useMultiProvider, useRegistry } from '../../store';
import { useReadyMultiProvider, useRegistry } from '../../store';
import { Message, MessageStatus } from '../../types';
import { logger } from '../../utils/logger';
import { MissingChainConfigToast } from '../chains/MissingChainConfigToast';
@ -12,46 +13,70 @@ import { useChainConfigs } from '../chains/useChainConfigs';
import { fetchDeliveryStatus } from './fetchDeliveryStatus';
export function useMessageDeliveryStatus({ message, pause }: { message: Message; pause: boolean }) {
export function useMessageDeliveryStatus({
message,
enabled = true,
}: {
message: Message;
enabled: boolean;
}) {
const chainConfigs = useChainConfigs();
const multiProvider = useMultiProvider();
const multiProvider = useReadyMultiProvider();
const registry = useRegistry();
const serializedMessage = JSON.stringify(message);
const { data, error, isFetching } = useQuery(
['messageDeliveryStatus', serializedMessage, pause],
async () => {
if (pause || !message || message.status === MessageStatus.Delivered) return null;
const { data, error, isFetching } = useQuery({
queryKey: ['messageDeliveryStatus', message, !!multiProvider],
queryFn: async () => {
if (!multiProvider || message.status == MessageStatus.Delivered) {
return { message };
}
const { id, originChainId, originDomainId, destinationChainId, destinationDomainId } =
message;
if (!multiProvider.tryGetChainMetadata(message.originChainId)) {
toast.error(
<MissingChainConfigToast
chainId={message.originChainId}
domainId={message.originDomainId}
/>,
);
return null;
} else if (!multiProvider.tryGetChainMetadata(message.destinationChainId)) {
toast.error(
<MissingChainConfigToast
chainId={message.destinationChainId}
domainId={message.destinationDomainId}
/>,
);
return null;
if (
!checkHasChain(multiProvider, originChainId, originDomainId) ||
!checkHasChain(multiProvider, destinationChainId, destinationDomainId)
) {
return { message };
}
logger.debug('Fetching message delivery status for:', message.id);
logger.debug('Fetching message delivery status for:', id);
const deliverStatus = await fetchDeliveryStatus(
multiProvider,
registry,
chainConfigs,
message,
);
return deliverStatus;
if (deliverStatus.status === MessageStatus.Delivered) {
return {
message: {
...message,
status: MessageStatus.Delivered,
destination: deliverStatus.deliveryTransaction,
},
};
} else if (
deliverStatus.status === MessageStatus.Failing ||
deliverStatus.status === MessageStatus.Pending
) {
return {
message: {
...message,
status: deliverStatus.status,
},
debugResult: deliverStatus.debugResult,
};
} else {
return { message };
}
},
{ retry: false },
);
retry: false,
refetchInterval: (query) =>
query.state.data?.message.status === MessageStatus.Delivered ? false : 10_000,
enabled,
});
// Show toast on error
useEffect(() => {
@ -61,27 +86,17 @@ export function useMessageDeliveryStatus({ message, pause }: { message: Message;
}
}, [error]);
const [messageWithDeliveryStatus, debugResult] = useMemo(() => {
if (data?.status === MessageStatus.Delivered) {
return [
{
...message,
status: MessageStatus.Delivered,
destination: data.deliveryTransaction,
},
];
} else if (data?.status === MessageStatus.Failing || data?.status === MessageStatus.Pending) {
return [
{
...message,
status: data.status,
},
data.debugResult,
];
} else {
return [message];
}
}, [message, data]);
return {
messageWithDeliveryStatus: data?.message || message,
debugResult: data?.debugResult,
isDeliveryStatusFetching: isFetching,
};
}
return { messageWithDeliveryStatus, debugResult, isDeliveryStatusFetching: isFetching };
function checkHasChain(multiProvider: MultiProvider, chainId: ChainId, domainId: number) {
if (!multiProvider.hasChain(chainId)) {
toast.error(<MissingChainConfigToast chainId={chainId} domainId={domainId} />);
return false;
}
return true;
}

@ -1,5 +1,5 @@
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { toTitleCase, trimToLength } from '@hyperlane-xyz/utils';
@ -32,10 +32,6 @@ interface Props {
export function MessageDetails({ messageId, message: messageFromUrlParams }: Props) {
const multiProvider = useMultiProvider();
// Needed to force pause of message query if the useMessageDeliveryStatus
// Hook finds a delivery record on it's own
const [deliveryFound, setDeliveryFound] = useState(false);
// GraphQL query and results
const {
isFetching: isGraphQlFetching,
@ -43,7 +39,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
hasRun: hasGraphQlRun,
isMessageFound: isGraphQlMessageFound,
message: messageFromGraphQl,
} = useMessageQuery({ messageId, pause: !!messageFromUrlParams || deliveryFound });
} = useMessageQuery({ messageId, pause: !!messageFromUrlParams });
// Run permissionless interop chains query if needed
const {
@ -73,7 +69,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
isDeliveryStatusFetching,
} = useMessageDeliveryStatus({
message: _message,
pause: !isMessageFound,
enabled: isMessageFound,
});
const {
@ -87,11 +83,6 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
destination,
} = message;
// Mark delivery found to prevent pause queries
useEffect(() => {
if (status === MessageStatus.Delivered) setDeliveryFound(true);
}, [status]);
// Banner color setter
useDynamicBannerColor(isFetching, status, isMessageFound, isError || isPiError);

@ -212,14 +212,16 @@ function TransactionDetails({
showCopy={true}
blurValue={blur}
/>
<KeyValueRow
label="Time:"
labelWidth="w-16"
display={getHumanReadableTimeString(timestamp)}
subDisplay={`(${getDateTimeString(timestamp)})`}
displayWidth="w-60 sm:w-64"
blurValue={blur}
/>
{!!timestamp && (
<KeyValueRow
label="Time:"
labelWidth="w-16"
display={getHumanReadableTimeString(timestamp)}
subDisplay={`(${getDateTimeString(timestamp)})`}
displayWidth="w-60 sm:w-64"
blurValue={blur}
/>
)}
<KeyValueRow
label="Block:"
labelWidth="w-16"

@ -5,7 +5,7 @@ import { useMemo } from 'react';
import { InterchainAccountRouter__factory } from '@hyperlane-xyz/core';
import { eqAddress, isValidAddress } from '@hyperlane-xyz/utils';
import { useMultiProvider } from '../../store';
import { useReadyMultiProvider } from '../../store';
import { logger } from '../../utils/logger';
// This assumes all chains have the same ICA address
@ -98,15 +98,16 @@ export async function tryFetchIcaAddress(
}
export function useIcaAddress(originDomainId: DomainId, sender?: Address | null) {
const multiProvider = useMultiProvider();
return useQuery(
['useIcaAddress', originDomainId, sender],
() => {
if (!originDomainId || !sender || BigNumber.from(sender).isZero()) return null;
const multiProvider = useReadyMultiProvider();
return useQuery({
queryKey: ['useIcaAddress', originDomainId, sender, !!multiProvider],
queryFn: () => {
if (!originDomainId || !multiProvider || !sender || BigNumber.from(sender).isZero())
return null;
const provider = multiProvider.tryGetProvider(originDomainId);
if (!provider) return null;
return tryFetchIcaAddress(originDomainId, sender, provider);
},
{ retry: false },
);
retry: false,
});
}

@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { MultiProvider } from '@hyperlane-xyz/sdk';
import { ensure0x } from '@hyperlane-xyz/utils';
import { useMultiProvider } from '../../../store';
import { useReadyMultiProvider } from '../../../store';
import { Message } from '../../../types';
import { logger } from '../../../utils/logger';
import { ChainConfig } from '../../chains/chainConfig';
@ -26,20 +26,28 @@ export function usePiChainMessageSearchQuery({
pause: boolean;
}) {
const chainConfigs = useChainConfigs();
const multiProvider = useMultiProvider();
const { isLoading, isError, data } = useQuery(
[
const multiProvider = useReadyMultiProvider();
const { isLoading, isError, data } = useQuery({
queryKey: [
'usePiChainMessageSearchQuery',
chainConfigs,
sanitizedInput,
startTimeFilter,
endTimeFilter,
!!multiProvider,
pause,
],
async () => {
queryFn: async () => {
const hasInput = !!sanitizedInput;
const isValidInput = isValidSearchQuery(sanitizedInput, true);
if (pause || !hasInput || !isValidInput || !Object.keys(chainConfigs).length) return [];
if (
pause ||
!multiProvider ||
!hasInput ||
!isValidInput ||
!Object.keys(chainConfigs).length
)
return [];
logger.debug('Starting PI Chain message search for:', sanitizedInput);
// TODO convert timestamps to from/to blocks here
const query = { input: ensure0x(sanitizedInput) };
@ -53,8 +61,8 @@ export function usePiChainMessageSearchQuery({
return [];
}
},
{ retry: false },
);
retry: false,
});
return {
isFetching: isLoading,
@ -73,11 +81,11 @@ export function usePiChainMessageQuery({
pause: boolean;
}) {
const chainConfigs = useChainConfigs();
const multiProvider = useMultiProvider();
const { isLoading, isError, data } = useQuery(
['usePiChainMessageQuery', chainConfigs, messageId, pause],
async () => {
if (pause || !messageId || !Object.keys(chainConfigs).length) return [];
const multiProvider = useReadyMultiProvider();
const { isLoading, isError, data } = useQuery({
queryKey: ['usePiChainMessageQuery', chainConfigs, messageId, !!multiProvider, pause],
queryFn: async () => {
if (pause || !multiProvider || !messageId || !Object.keys(chainConfigs).length) return [];
logger.debug('Starting PI Chain message query for:', messageId);
const query = { input: ensure0x(messageId) };
try {
@ -92,8 +100,8 @@ export function usePiChainMessageQuery({
return [];
}
},
{ retry: false },
);
retry: false,
});
const message = data?.length ? data[0] : null;
const isMessageFound = !!message;

@ -14,8 +14,8 @@ import {
import { MessagesQueryResult, MessagesStubQueryResult } from '../queries/fragments';
import { parseMessageQueryResult, parseMessageStubResult } from '../queries/parse';
const SEARCH_AUTO_REFRESH_DELAY = 15000;
const MSG_AUTO_REFRESH_DELAY = 10000;
const SEARCH_AUTO_REFRESH_DELAY = 15_000; // 15s
const MSG_AUTO_REFRESH_DELAY = 10_000; // 10s
const LATEST_QUERY_LIMIT = 20;
const SEARCH_QUERY_LIMIT = 50;

@ -1,10 +1,3 @@
// import {
// RainbowKitProvider,
// connectorsForWallets,
// lightTheme,
// wallet,
// } from '@rainbow-me/rainbowkit';
// import '@rainbow-me/rainbowkit/styles.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { AppProps } from 'next/app';
import { ToastContainer, Zoom, toast } from 'react-toastify';
@ -14,8 +7,6 @@ import { Provider as UrqlProvider, createClient as createUrqlClient } from 'urql
import '@hyperlane-xyz/widgets/styles.css';
// import { WagmiConfig, configureChains, createClient as createWagmiClient } from 'wagmi';
// import { publicProvider } from 'wagmi/providers/public';
import { ErrorBoundary } from '../components/errors/ErrorBoundary';
import { AppLayout } from '../components/layout/AppLayout';
import { config } from '../consts/config';
@ -24,26 +15,6 @@ import '../styles/fonts.css';
import '../styles/global.css';
import { useIsSsr } from '../utils/ssr';
// const { chains, provider } = configureChains(prodAndTestChains, [publicProvider()]);
// const connectors = connectorsForWallets([
// {
// groupName: 'Recommended',
// wallets: [
// wallet.metaMask({ chains }),
// wallet.walletConnect({ chains }),
// wallet.rainbow({ chains }),
// wallet.steak({ chains }),
// ],
// },
// ]);
// const wagmiClient = createWagmiClient({
// autoConnect: false, // TODO
// provider,
// connectors,
// });
const urqlClient = createUrqlClient({
url: config.apiUrl,
});
@ -66,15 +37,6 @@ export default function App({ Component, router, pageProps }: AppProps) {
return (
<ErrorBoundary>
{/* <WagmiConfig client={wagmiClient}> */}
{/* <RainbowKitProvider
chains={chains}
theme={lightTheme({
accentColor: Color.primaryRed,
borderRadius: 'small',
fontStack: 'system',
})}
> */}
<QueryClientProvider client={reactQueryClient}>
<UrqlProvider value={urqlClient}>
<ChainConfigSyncer>
@ -86,8 +48,6 @@ export default function App({ Component, router, pageProps }: AppProps) {
</QueryClientProvider>
<ToastContainer transition={Zoom} position={toast.POSITION.BOTTOM_RIGHT} limit={2} />
<Tooltip id="root-tooltip" className="z-50" />
{/* </RainbowKitProvider> */}
{/* </WagmiConfig> */}
</ErrorBoundary>
);
}

@ -29,14 +29,17 @@ export const useStore = create<AppState>()(
chainConfigs: {},
setChainConfigs: async (configs: ChainMap<ChainConfig>) => {
const multiProvider = await buildMultiProvider(get().registry, configs);
console.log('setChainConfigs');
set({ chainConfigs: configs, multiProvider });
},
multiProvider: new MultiProvider({}),
setMultiProvider: (multiProvider: MultiProvider) => {
console.log('setMultiProvider');
set({ multiProvider });
},
registry: new GithubRegistry(),
setRegistry: (registry: IRegistry) => {
console.log('setRegistry');
set({ registry });
},
bannerClassName: '',
@ -54,7 +57,10 @@ export const useStore = create<AppState>()(
return;
}
buildMultiProvider(state.registry, state.chainConfigs)
.then((mp) => state.setMultiProvider(mp))
.then((mp) => {
state.setMultiProvider(mp);
logger.debug('Rehydration complete');
})
.catch((e) => logger.error('Error building MultiProvider', e));
};
},
@ -62,12 +68,20 @@ export const useStore = create<AppState>()(
),
);
export function useRegistry() {
return useStore((s) => s.registry);
}
export function useMultiProvider() {
return useStore((s) => s.multiProvider);
}
export function useRegistry() {
return useStore((s) => s.registry);
// Ensures that the multiProvider has been populated during the onRehydrateStorage hook above,
// otherwise returns undefined
export function useReadyMultiProvider() {
const multiProvider = useMultiProvider();
if (multiProvider.getKnownChainNames().length === 0) return undefined;
return multiProvider;
}
async function buildMultiProvider(registry: IRegistry, customChainConfigs: ChainMap<ChainConfig>) {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save