Skip to content

Commit

Permalink
chore: Implement destinationImplementation cache on Wormhole
Browse files Browse the repository at this point in the history
  • Loading branch information
jsanmigimeno committed Jul 19, 2024
1 parent 9eba531 commit d86a98a
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 18 deletions.
25 changes: 16 additions & 9 deletions src/collector/wormhole/wormhole-message-sniffer.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,15 @@ import {
import { Store } from 'src/store/store.lib';
import { workerData, MessagePort } from 'worker_threads';
import { tryErrorToString, wait } from '../../common/utils';
import { decodeWormholeMessage } from './wormhole.utils';
import { decodeWormholeMessage, defaultAbiCoder, getDestinationImplementation } from './wormhole.utils';
import { ParsePayload } from 'src/payload/decode.payload';
import { WormholeMessageSnifferWorkerData } from './wormhole.types';
import { AbiCoder, JsonRpcProvider } from 'ethers6';
import { JsonRpcProvider } from 'ethers6';
import { MonitorInterface, MonitorStatus } from 'src/monitor/monitor.interface';
import { Resolver, loadResolver } from 'src/resolvers/resolver';
import { STATUS_LOG_INTERVAL } from 'src/logger/logger.service';
import { AMBMessage } from 'src/store/store.types';

const defaultAbiCoder = AbiCoder.defaultAbiCoder();

class WormholeMessageSnifferWorker {
private readonly store: Store;
private readonly logger: pino.Logger;
Expand All @@ -35,6 +33,8 @@ class WormholeMessageSnifferWorker {

private readonly resolver: Resolver;

private readonly destinationImplementationCache: Record<string, Record<string, string>> = {}; // Map fromApplication + toChainId => destinationImplementation

private currentStatus: MonitorStatus | null = null;
private monitor: MonitorInterface;

Expand Down Expand Up @@ -289,11 +289,18 @@ class WormholeMessageSnifferWorker {
return;
}

//TODO the following contract call could fail. Set to 'undefined' and continue on that case?
//TODO cache the query
const toIncentivesAddress = await this.messageEscrowContract.implementationAddress(
decodedPayload?.sourceApplicationAddress,
defaultAbiCoder.encode(['uint256'], [destinationChain]),
const channelId = defaultAbiCoder.encode(
['uint256'],
[destinationWormholeChainId],
);

const toIncentivesAddress = await getDestinationImplementation(
decodedPayload.sourceApplicationAddress,
channelId,
this.messageEscrowContract,
this.destinationImplementationCache,
this.logger,
this.config.retryInterval
);

const ambMessage: AMBMessage = {
Expand Down
24 changes: 15 additions & 9 deletions src/collector/wormhole/wormhole-recovery.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ParsedVaaWithBytes,
parseVaaWithBytes,
} from '@wormhole-foundation/relayer-engine';
import { decodeWormholeMessage } from './wormhole.utils';
import { decodeWormholeMessage, getDestinationImplementation } from './wormhole.utils';
import { add0X } from 'src/common/utils';
import { AMBMessage, AMBProof } from 'src/store/store.types';
import { ParsePayload } from 'src/payload/decode.payload';
Expand Down Expand Up @@ -36,6 +36,8 @@ class WormholeRecoveryWorker {

private readonly messageEscrowContract: IncentivizedMessageEscrow;

private readonly destinationImplementationCache: Record<string, Record<string, string>> = {}; // Map fromApplication + toChainId => destinationImplementation

constructor() {
this.config = workerData as WormholeRecoveryWorkerData;

Expand Down Expand Up @@ -162,14 +164,18 @@ class WormholeRecoveryWorker {
throw new Error('Could not decode VAA payload.');
}

//TODO the following contract call could fail. Set to 'undefined' and continue on that case?
//TODO cache the query
const toIncentivesAddress = await this.messageEscrowContract.implementationAddress(
decodedPayload?.sourceApplicationAddress,
defaultAbiCoder.encode(
['uint256'],
[decodedWormholeMessage.destinationWormholeChainId],
),
const channelId = defaultAbiCoder.encode(
['uint256'],
[decodedWormholeMessage.destinationWormholeChainId],
);

const toIncentivesAddress = await getDestinationImplementation(
decodedPayload.sourceApplicationAddress,
channelId,
this.messageEscrowContract,
this.destinationImplementationCache,
this.logger,
this.config.retryInterval
);

// TODO the following query could fail. Add a retry mechanism.
Expand Down
97 changes: 97 additions & 0 deletions src/collector/wormhole/wormhole.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { IncentivizedMessageEscrow } from 'src/contracts';
import { WormholeChainConfig, WormholeChainId } from './wormhole.types';
import { AbiCoder } from 'ethers6';
import pino from 'pino';
import { tryErrorToString, wait } from 'src/common/utils';

export const defaultAbiCoder = AbiCoder.defaultAbiCoder();

export interface DecodedWormholeMessage {
messageIdentifier: string;
Expand Down Expand Up @@ -40,3 +46,94 @@ export function loadWormholeChainIdMap(

return wormholeChainIdMap;
}


export async function getDestinationImplementation(
fromApplication: string,
channelId: string,
escrowContract: IncentivizedMessageEscrow,
cache: Record<string, Record<string, string>> = {}, // Map fromApplication + channelId => destinationImplementation
logger: pino.Logger,
retryInterval: number = 1000,
maxTries: number = 3,
): Promise<string | undefined> {

const cachedImplementation = cache[fromApplication]?.[channelId];

if (cachedImplementation != undefined) {
return cachedImplementation;
}

const destinationImplementation = await queryDestinationImplementation(
fromApplication,
channelId,
escrowContract,
logger,
retryInterval,
maxTries,
);

// Set the destination implementation cache
if (destinationImplementation != undefined) {
if (cache[fromApplication] == undefined) {
cache[fromApplication] = {};
}

cache[fromApplication]![channelId] = destinationImplementation;
}

return destinationImplementation;
}

export async function queryDestinationImplementation(
fromApplication: string,
channelId: string,
escrowContract: IncentivizedMessageEscrow,
logger: pino.Logger,
retryInterval: number = 1000,
maxTries: number = 3,
): Promise<string | undefined> {

for (let tryCount = 0; tryCount < maxTries; tryCount++) {
try {
const destinationImplementation = await escrowContract.implementationAddress(
fromApplication,
channelId,
);

logger.debug(
{
fromApplication,
channelId,
destinationImplementation,
},
`Destination implementation queried.`
);

return '0x' + destinationImplementation.slice(26); // Keep only the last 20-bytes (discard the first '0x' + 12 null bytes)
}
catch (error) {
logger.warn(
{
fromApplication,
channelId,
try: tryCount + 1,
error: tryErrorToString(error),
},
`Error on the destination implementation query. Retrying if possible.`
);
}

await wait(retryInterval);
}

logger.error(
{
fromApplication,
channelId,
},
`Failed to query the destination implementation.`
);

return undefined;
}

0 comments on commit d86a98a

Please sign in to comment.