Check mailbox for delivery status if logs are not found

pull/40/head
J M Rossy 2 years ago
parent 5f1246c727
commit d40eb028aa
  1. 88
      src/features/deliveryStatus/fetchDeliveryStatus.ts
  2. 10
      src/features/messages/cards/KeyValueRow.tsx
  3. 8
      src/features/messages/placeholderMessages.ts
  4. 12
      src/utils/number.ts
  5. 2
      src/utils/time.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<ChainConfig>,
@ -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);

@ -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 (
<div className={`flex items-center pl-px ${classes}`}>
<label className={`text-sm text-gray-500 ${labelWidth}`}>{label}</label>
<div className={`text-sm ml-1 truncate ${displayWidth || ''} ${blurValue && 'blur-xs'}`}>
<span>{display}</span>
{subDisplay && <span className="text-xs ml-2">{subDisplay}</span>}
<span>{!isValueZeroish ? display : 'Unknown'}</span>
{subDisplay && !isValueZeroish && <span className="text-xs ml-2">{subDisplay}</span>}
</div>
{showCopy && <CopyButton copyValue={display} width={13} height={13} classes="ml-1" />}
{showCopy && !isValueZeroish && (
<CopyButton copyValue={display} width={13} height={13} classes="ml-1" />
)}
</div>
);
}

@ -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,

@ -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;
}
}

@ -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) {

Loading…
Cancel
Save