Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement multi-message relay #4812

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/lovely-planes-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---

Allow self-relaying of all messages if there are multiple in a given dispatch transaction.
3 changes: 1 addition & 2 deletions typescript/cli/src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ export const statusCommand: CommandModuleWithWriteContext<
description: 'Dispatch transaction hash',
},
},
handler: async ({ context, origin, destination, id, relay, dispatchTx }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that the destination var is unused maybe we should remove it from the command options too so that users won't try to set it and then see unexpected results because the value is not used

handler: async ({ context, origin, id, relay, dispatchTx }) => {
await checkMessageStatus({
context,
dispatchTx,
messageId: id,
destination,
origin,
selfRelay: relay,
});
Expand Down
77 changes: 26 additions & 51 deletions typescript/cli/src/status/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@ import type { TransactionReceipt } from '@ethersproject/providers';
import { input } from '@inquirer/prompts';

import { ChainName, HyperlaneCore, HyperlaneRelayer } from '@hyperlane-xyz/sdk';
import { assert, parseWarpRouteMessage } from '@hyperlane-xyz/utils';

import { WriteCommandContext } from '../context/types.js';
import { log, logBlue, logGray, logGreen, logRed } from '../logger.js';
import { log, logBlue, logGreen, logRed } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js';
import { stubMerkleTreeConfig } from '../utils/relay.js';

export async function checkMessageStatus({
context,
messageId,
destination,
origin,
selfRelay,
dispatchTx,
}: {
context: WriteCommandContext;
dispatchTx?: string;
messageId?: string;
destination?: ChainName;
origin?: ChainName;
selfRelay?: boolean;
}) {
Expand All @@ -31,15 +28,9 @@ export async function checkMessageStatus({
);
}

if (!messageId) {
messageId = await input({
message: 'Please specify the message id',
});
}

const chainAddresses = await context.registry.getAddresses();
const coreAddresses = await context.registry.getAddresses();
const core = HyperlaneCore.fromAddressesMap(
chainAddresses,
coreAddresses,
context.multiProvider,
);

Expand All @@ -50,6 +41,9 @@ export async function checkMessageStatus({
.getProvider(origin)
.getTransactionReceipt(dispatchTx);
} else {
messageId ??= await input({
message: 'Please specify the message id',
});
try {
dispatchedReceipt = await core.getDispatchTx(origin, messageId);
} catch {
Expand All @@ -64,48 +58,29 @@ export async function checkMessageStatus({
}
}

const messages = core.getDispatchedMessages(dispatchedReceipt!);
const match = messages.find((m) => m.id === messageId);
assert(match, `Message ${messageId} not found in dispatch tx ${dispatchTx}`);
const message = match;
try {
const { amount, recipient } = parseWarpRouteMessage(message.parsed.body);
logGray(`Warping ${amount} to ${recipient}`);
// eslint-disable-next-line no-empty
} catch {}

let deliveredTx: TransactionReceipt;

log(`Checking status of message ${messageId} on ${destination}`);
const delivered = await core.isDelivered(message);
if (delivered) {
logGreen(`Message ${messageId} was delivered`);
deliveredTx = await core.getProcessedReceipt(message);
} else {
logBlue(`Message ${messageId} was not yet delivered`);
const messages = core.getDispatchedMessages(dispatchedReceipt);

if (!selfRelay) {
return;
const undelivered = [];
for (const message of messages) {
log(
`Checking status of message ${message.id} on ${message.parsed.destinationChain}`,
);
const delivered = await core.isDelivered(message);
if (delivered) {
logGreen(`Message ${message.id} was delivered`);
} else {
logBlue(`Message ${message.id} was not yet delivered`);
undelivered.push(message);
}
}

if (selfRelay) {
const relayer = new HyperlaneRelayer({ core });

const hookAddress = await core.getSenderHookAddress(message);
const merkleAddress = chainAddresses[origin].merkleTreeHook;
stubMerkleTreeConfig(relayer, origin, hookAddress, merkleAddress);

deliveredTx = await relayer.relayMessage(
dispatchedReceipt,
undefined,
message,
);
for (const message of undelivered) {
const hookAddress = await core.getSenderHookAddress(message);
const merkleAddress = coreAddresses[origin].merkleTreeHook;
stubMerkleTreeConfig(relayer, origin, hookAddress, merkleAddress);
}
await relayer.relayAll(dispatchedReceipt, undelivered);
}

logGreen(
`Message ${messageId} delivered in ${
context.multiProvider.tryGetExplorerTxUrl(message.parsed.destination, {
hash: deliveredTx.transactionHash,
}) ?? deliveredTx.transactionHash
}`,
);
}
10 changes: 9 additions & 1 deletion typescript/sdk/src/core/HyperlaneCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ProtocolType,
addBufferToGasLimit,
addressToBytes32,
assert,
bytes32ToAddress,
isZeroishAddress,
messageId,
Expand Down Expand Up @@ -313,14 +314,19 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
message: DispatchedMessage,
): Promise<ethers.ContractReceipt> {
const destinationChain = this.getDestination(message);
const mailbox = this.contractsMap[destinationChain].mailbox;
const mailbox = this.getContracts(destinationChain).mailbox;

const processedBlock = await mailbox.processedAt(message.id);
const events = await mailbox.queryFilter(
mailbox.filters.ProcessId(message.id),
processedBlock,
processedBlock,
);

assert(
events.length === 1,
`Expected exactly one process event, got ${events.length}`,
);
const processedEvent = events[0];
return processedEvent.getTransactionReceipt();
}
Expand Down Expand Up @@ -428,6 +434,8 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
if (matching.length === 0) {
throw new Error(`No dispatch event found for message ${messageId}`);
}

assert(matching.length === 1, 'Multiple dispatch events found');
const event = matching[0]; // only 1 event per message ID
return event.getTransactionReceipt();
}
Expand Down
32 changes: 32 additions & 0 deletions typescript/sdk/src/core/HyperlaneRelayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,38 @@ export class HyperlaneRelayer {
return this.getIsmConfig(destinationChain, ism, message);
}

async relayAll(
dispatchTx: providers.TransactionReceipt,
messages = HyperlaneCore.getDispatchedMessages(dispatchTx),
): Promise<ChainMap<ethers.ContractReceipt[]>> {
const destinationMap: ChainMap<DispatchedMessage[]> = {};
messages.forEach((message) => {
destinationMap[message.parsed.destination] ??= [];
destinationMap[message.parsed.destination].push(message);
});

// parallelize relaying to different destinations
return promiseObjAll(
objMap(destinationMap, async (_destination, messages) => {
const receipts: ethers.ContractReceipt[] = [];
// serially relay messages to the same destination
for (const message of messages) {
try {
const receipt = await this.relayMessage(
dispatchTx,
undefined,
message,
);
receipts.push(receipt);
} catch (e) {
this.logger.error(`Failed to relay message ${message.id}, ${e}`);
}
}
return receipts;
}),
);
}

async relayMessage(
dispatchTx: providers.TransactionReceipt,
messageIndex = 0,
Expand Down
Loading