From d40eb028aaffb1e416b7ed00b2c9fa6b10f670e9 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 11 May 2023 15:00:19 -0400 Subject: [PATCH] Check mailbox for delivery status if logs are not found --- .../deliveryStatus/fetchDeliveryStatus.ts | 88 ++++++++++++------- src/features/messages/cards/KeyValueRow.tsx | 10 ++- src/features/messages/placeholderMessages.ts | 8 +- src/utils/number.ts | 12 ++- src/utils/time.ts | 2 + 5 files changed, 77 insertions(+), 43 deletions(-) diff --git a/src/features/deliveryStatus/fetchDeliveryStatus.ts b/src/features/deliveryStatus/fetchDeliveryStatus.ts index 7dae284..d9df86c 100644 --- a/src/features/deliveryStatus/fetchDeliveryStatus.ts +++ b/src/features/deliveryStatus/fetchDeliveryStatus.ts @@ -1,5 +1,6 @@ import { constants } from 'ethers'; +import { IMailbox__factory } from '@hyperlane-xyz/core'; import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk'; import { Message, MessageStatus } from '../../types'; @@ -9,7 +10,6 @@ import type { ChainConfig } from '../chains/chainConfig'; import { getContractAddress } from '../chains/utils'; import { debugExplorerMessage } from '../debugger/debugMessage'; import { MessageDebugStatus } from '../debugger/types'; -import { TX_HASH_ZERO } from '../messages/placeholderMessages'; import { MessageDeliveryFailingResult, @@ -17,12 +17,6 @@ import { MessageDeliverySuccessResult, } from './types'; -// The keccak-256 hash of the ProcessId event: ProcessId(bytes32) -// https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/1.0.0-beta0/solidity/contracts/Mailbox.sol#L84 -// https://emn178.github.io/online-tools/keccak_256.html -// Alternatively could get this by creating the Mailbox contract object via SDK -const PROCESS_TOPIC_0 = '0x1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a9'; - export async function fetchDeliveryStatus( multiProvider: MultiProvider, customChainConfigs: ChainMap, @@ -31,35 +25,37 @@ export async function fetchDeliveryStatus( const destName = multiProvider.getChainName(message.destinationChainId); const destMailboxAddr = getContractAddress(customChainConfigs, destName, 'mailbox'); - const logs = await fetchMessageLogs(multiProvider, message, destMailboxAddr); + const { isDelivered, blockNumber, transactionHash } = await checkIsMessageDelivered( + multiProvider, + message, + destMailboxAddr, + ); - if (logs?.length) { - logger.debug(`Found delivery log for tx ${message.origin.hash}`); - const log = logs[0]; // Should only be 1 log per message delivery + if (isDelivered) { const txDetails = await fetchTransactionDetails( multiProvider, message.destinationChainId, - log.transactionHash, + transactionHash, ); // If a delivery (aka process) tx is found, mark as success const result: MessageDeliverySuccessResult = { status: MessageStatus.Delivered, deliveryTransaction: { - timestamp: toDecimalNumber(txDetails.timestamp || 0) * 1000, - hash: log.transactionHash, - from: txDetails.from || constants.AddressZero, - to: txDetails.to || constants.AddressZero, - blockHash: txDetails.blockHash || TX_HASH_ZERO, - blockNumber: toDecimalNumber(log.blockNumber), + timestamp: toDecimalNumber(txDetails?.timestamp || 0) * 1000, + hash: transactionHash || constants.HashZero, + from: txDetails?.from || constants.AddressZero, + to: txDetails?.to || constants.AddressZero, + blockHash: txDetails?.blockHash || constants.HashZero, + blockNumber: toDecimalNumber(blockNumber || 0), mailbox: constants.AddressZero, - nonce: txDetails.nonce || 0, - gasLimit: toDecimalNumber(txDetails.gasLimit || 0), - gasPrice: toDecimalNumber(txDetails.gasPrice || 0), - effectiveGasPrice: toDecimalNumber(txDetails.gasPrice || 0), - gasUsed: toDecimalNumber(txDetails.gasLimit || 0), - cumulativeGasUsed: toDecimalNumber(txDetails.gasLimit || 0), - maxFeePerGas: toDecimalNumber(txDetails.maxFeePerGas || 0), - maxPriorityPerGas: toDecimalNumber(txDetails.maxPriorityFeePerGas || 0), + nonce: txDetails?.nonce || 0, + gasLimit: toDecimalNumber(txDetails?.gasLimit || 0), + gasPrice: toDecimalNumber(txDetails?.gasPrice || 0), + effectiveGasPrice: toDecimalNumber(txDetails?.gasPrice || 0), + gasUsed: toDecimalNumber(txDetails?.gasLimit || 0), + cumulativeGasUsed: toDecimalNumber(txDetails?.gasLimit || 0), + maxFeePerGas: toDecimalNumber(txDetails?.maxFeePerGas || 0), + maxPriorityPerGas: toDecimalNumber(txDetails?.maxPriorityFeePerGas || 0), }, }; return result; @@ -81,17 +77,41 @@ export async function fetchDeliveryStatus( } } -function fetchMessageLogs(multiProvider: MultiProvider, message: Message, mailboxAddr: Address) { - const { msgId, origin, destinationChainId } = message; - logger.debug(`Searching for delivery logs for tx ${origin.hash}`); +async function checkIsMessageDelivered( + multiProvider: MultiProvider, + message: Message, + mailboxAddr: Address, +) { + const { msgId, destinationChainId } = message; const provider = multiProvider.getProvider(destinationChainId); - return provider.getLogs({ - topics: [PROCESS_TOPIC_0, msgId], - address: mailboxAddr, - }); + const mailbox = IMailbox__factory.connect(mailboxAddr, provider); + + // 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)); + if (logs?.length) { + logger.debug(`Found process log for ${msgId}}`); + const log = logs[0]; // Should only be 1 log per message delivery + return { + isDelivered: true, + transactionHash: log.transactionHash, + blockNumber: log.blockNumber, + }; + } + } catch (error) { + logger.warn(`Error querying for process logs for msgId ${msgId}`, error); + } + + // 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}}`); + return { isDelivered }; } -function fetchTransactionDetails(multiProvider: MultiProvider, chainId: ChainId, txHash: string) { +function fetchTransactionDetails(multiProvider: MultiProvider, chainId: ChainId, txHash?: string) { + if (!txHash) return null; logger.debug(`Searching for transaction details for ${txHash}`); const provider = multiProvider.getProvider(chainId); return provider.getTransaction(txHash); diff --git a/src/features/messages/cards/KeyValueRow.tsx b/src/features/messages/cards/KeyValueRow.tsx index b891f16..1c64afe 100644 --- a/src/features/messages/cards/KeyValueRow.tsx +++ b/src/features/messages/cards/KeyValueRow.tsx @@ -1,4 +1,5 @@ import { CopyButton } from '../../../components/buttons/CopyButton'; +import { isZeroish } from '../../../utils/number'; interface Props { label: string; @@ -21,14 +22,17 @@ export function KeyValueRow({ blurValue, classes, }: Props) { + const isValueZeroish = isZeroish(display); return (
- {display} - {subDisplay && {subDisplay}} + {!isValueZeroish ? display : 'Unknown'} + {subDisplay && !isValueZeroish && {subDisplay}}
- {showCopy && } + {showCopy && !isValueZeroish && ( + + )}
); } diff --git a/src/features/messages/placeholderMessages.ts b/src/features/messages/placeholderMessages.ts index f87669a..14e41b5 100644 --- a/src/features/messages/placeholderMessages.ts +++ b/src/features/messages/placeholderMessages.ts @@ -2,14 +2,12 @@ import { constants } from 'ethers'; import { Message, MessageStatus, MessageTx } from '../../types'; -export const TX_HASH_ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; - export const TX_ZERO: MessageTx = { timestamp: Date.now(), - hash: TX_HASH_ZERO, + hash: constants.HashZero, from: constants.AddressZero, to: constants.AddressZero, - blockHash: TX_HASH_ZERO, + blockHash: constants.HashZero, blockNumber: 123456789, mailbox: constants.AddressZero, nonce: 0, @@ -27,7 +25,7 @@ const BODY_ZERO = export const PLACEHOLDER_MESSAGE: Message = { id: '1', - msgId: TX_HASH_ZERO, + msgId: constants.HashZero, nonce: 1, status: MessageStatus.Pending, sender: constants.AddressZero, diff --git a/src/utils/number.ts b/src/utils/number.ts index b52602d..d6245eb 100644 --- a/src/utils/number.ts +++ b/src/utils/number.ts @@ -1,4 +1,4 @@ -import { BigNumber, BigNumberish } from 'ethers'; +import { BigNumber, BigNumberish, constants } from 'ethers'; import { logger } from './logger'; import { isNullish } from './typeof'; @@ -26,3 +26,13 @@ export function isBigNumberish(value: any): value is BigNumberish { return false; } } + +// If a value (e.g. hex string or number) is zeroish (0, 0x0, 0x00, etc.) +export function isZeroish(value: BigNumberish) { + try { + if (!value || value === constants.HashZero || value === constants.AddressZero) return true; + return BigNumber.from(value).isZero(); + } catch (error) { + return false; + } +} diff --git a/src/utils/time.ts b/src/utils/time.ts index 117393a..0611cf1 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,5 +1,7 @@ // Inspired by https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site export function getHumanReadableTimeString(timestamp: number) { + if (timestamp <= 0) return ''; + const seconds = Math.floor((Date.now() - timestamp) / 1000); if (seconds <= 1) {