Implement multiple message relay

multi-message-relay
Yorke Rhodes 2 weeks ago
parent db5875cc22
commit 4c77fe1319
No known key found for this signature in database
GPG Key ID: 9876451F25A3B38C
  1. 67
      typescript/cli/src/status/message.ts
  2. 10
      typescript/sdk/src/core/HyperlaneCore.ts
  3. 34
      typescript/sdk/src/core/HyperlaneRelayer.ts

@ -2,12 +2,10 @@ import type { TransactionReceipt } from '@ethersproject/providers';
import { input } from '@inquirer/prompts'; import { input } from '@inquirer/prompts';
import { ChainName, HyperlaneCore, HyperlaneRelayer } from '@hyperlane-xyz/sdk'; import { ChainName, HyperlaneCore, HyperlaneRelayer } from '@hyperlane-xyz/sdk';
import { assert, parseWarpRouteMessage } from '@hyperlane-xyz/utils';
import { WriteCommandContext } from '../context/types.js'; 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 { runSingleChainSelectionStep } from '../utils/chains.js';
import { stubMerkleTreeConfig } from '../utils/relay.js';
export async function checkMessageStatus({ export async function checkMessageStatus({
context, context,
@ -31,15 +29,9 @@ export async function checkMessageStatus({
); );
} }
if (!messageId) { const coreAddresses = await context.registry.getAddresses();
messageId = await input({
message: 'Please specify the message id',
});
}
const chainAddresses = await context.registry.getAddresses();
const core = HyperlaneCore.fromAddressesMap( const core = HyperlaneCore.fromAddressesMap(
chainAddresses, coreAddresses,
context.multiProvider, context.multiProvider,
); );
@ -50,6 +42,9 @@ export async function checkMessageStatus({
.getProvider(origin) .getProvider(origin)
.getTransactionReceipt(dispatchTx); .getTransactionReceipt(dispatchTx);
} else { } else {
messageId ??= await input({
message: 'Please specify the message id',
});
try { try {
dispatchedReceipt = await core.getDispatchTx(origin, messageId); dispatchedReceipt = await core.getDispatchTx(origin, messageId);
} catch (e) { } catch (e) {
@ -64,44 +59,24 @@ export async function checkMessageStatus({
} }
} }
const messages = core.getDispatchedMessages(dispatchedReceipt!); 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; let undelivered = [];
for (const message of messages) {
log(`Checking status of message ${messageId} on ${destination}`); log(
const delivered = await core.isDelivered(message); `Checking status of message ${message.id} on ${message.parsed.destinationChain}`,
if (delivered) { );
logGreen(`Message ${messageId} was delivered`); const delivered = await core.isDelivered(message);
deliveredTx = await core.getProcessedReceipt(message); if (delivered) {
} else { logGreen(`Message ${message.id} was delivered`);
logBlue(`Message ${messageId} was not yet delivered`); } else {
logBlue(`Message ${message.id} was not yet delivered`);
if (!selfRelay) { undelivered.push(message);
return;
} }
}
if (selfRelay) {
const relayer = new HyperlaneRelayer({ core }); const relayer = new HyperlaneRelayer({ core });
await relayer.relayAll(dispatchedReceipt, undelivered);
const hookAddress = await core.getSenderHookAddress(message);
const merkleAddress = chainAddresses[origin].merkleTreeHook;
stubMerkleTreeConfig(relayer, origin, hookAddress, merkleAddress);
deliveredTx = await relayer.relayMessage(dispatchedReceipt);
} }
logGreen(
`Message ${messageId} delivered in ${
context.multiProvider.tryGetExplorerTxUrl(message.parsed.destination, {
hash: deliveredTx.transactionHash,
}) ?? deliveredTx.transactionHash
}`,
);
} }

@ -14,6 +14,7 @@ import {
ProtocolType, ProtocolType,
addBufferToGasLimit, addBufferToGasLimit,
addressToBytes32, addressToBytes32,
assert,
bytes32ToAddress, bytes32ToAddress,
isZeroishAddress, isZeroishAddress,
messageId, messageId,
@ -307,7 +308,7 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
message: DispatchedMessage, message: DispatchedMessage,
): Promise<ethers.ContractReceipt> { ): Promise<ethers.ContractReceipt> {
const destinationChain = this.getDestination(message); 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 processedBlock = await mailbox.processedAt(message.id);
const events = await mailbox.queryFilter( const events = await mailbox.queryFilter(
@ -315,6 +316,11 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
processedBlock, processedBlock,
processedBlock, processedBlock,
); );
assert(
events.length === 1,
`Expected exactly one process event, got ${events.length}`,
);
const processedEvent = events[0]; const processedEvent = events[0];
return processedEvent.getTransactionReceipt(); return processedEvent.getTransactionReceipt();
} }
@ -422,6 +428,8 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
if (matching.length === 0) { if (matching.length === 0) {
throw new Error(`No dispatch event found for message ${messageId}`); 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 const event = matching[0]; // only 1 event per message ID
return event.getTransactionReceipt(); return event.getTransactionReceipt();
} }

@ -17,7 +17,7 @@ import { DerivedIsmConfig, EvmIsmReader } from '../ism/EvmIsmReader.js';
import { BaseMetadataBuilder } from '../ism/metadata/builder.js'; import { BaseMetadataBuilder } from '../ism/metadata/builder.js';
import { IsmConfigSchema } from '../ism/schemas.js'; import { IsmConfigSchema } from '../ism/schemas.js';
import { MultiProvider } from '../providers/MultiProvider.js'; import { MultiProvider } from '../providers/MultiProvider.js';
import { ChainName } from '../types.js'; import { ChainMap, ChainName } from '../types.js';
import { HyperlaneCore } from './HyperlaneCore.js'; import { HyperlaneCore } from './HyperlaneCore.js';
import { DispatchedMessage } from './types.js'; import { DispatchedMessage } from './types.js';
@ -147,6 +147,38 @@ export class HyperlaneRelayer {
return this.getIsmConfig(destinationChain, ism, message); return this.getIsmConfig(destinationChain, ism, message);
} }
async relayAll(
dispatchTx: providers.TransactionReceipt,
messages = HyperlaneCore.getDispatchedMessages(dispatchTx),
): Promise<ChainMap<ethers.ContractReceipt[]>> {
let 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) => {
let 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( async relayMessage(
dispatchTx: providers.TransactionReceipt, dispatchTx: providers.TransactionReceipt,
messageIndex = 0, messageIndex = 0,

Loading…
Cancel
Save