relayer: improve delivery process debugging, add utility functions (#3028)

* starting calcs for relayer inbound / outbound

* monitoring additions & adding price monitor relay process

* adding basic infra for pricing & monitoring process

* added a large amount of debugging info to the relayer delivery process

* redelivery relayer processing fixes & debug improvements

* fixing delivery process breaks from renaming

* fixing naming errors inside the pricing process

* removed pricing process, rename delivery folders

* removed test error & refactored util directory

* moving out utilities from relayer delivery process

* moved lambda logic inline

* cleaning up comments

* Restore Tiltfile change

* Restore sdk package.json change

---------

Co-authored-by: derpy-duck <115193320+derpy-duck@users.noreply.github.com>
This commit is contained in:
chase-45 2023-06-15 04:33:26 -04:00 committed by GitHub
parent 12b18d4ccc
commit 515ccceedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 376 additions and 134 deletions

View File

@ -0,0 +1,80 @@
export type DeliveryExecutionRecord = {
didError?: boolean; // if true, the error will be logged in fatalStackTrace
errorName?: string; // If a detectable error occurred, this is the name of that failure.
didSubmitTransaction?: boolean; // if true, the process submitted at least one transaction, which will be logged in transactionHashes
executionStartTime?: number; // unix timestamp in milliseconds
executionEndTime?: number; // unix timestamp in milliseconds
rawVaaHex?: string; // hex string of the raw VAA
rawVaaPayloadHex?: string; // hex string of the raw VAA payload
payloadType?: string; // the payload type of the VAA
didParse?: boolean; // if true, the VAA was successfully parsed
specifiedDeliveryProvider?: string; // the relay provider specified in the VAA
didMatchDeliveryProvider?: boolean; // if true, the relay provider specified in the VAA matched the relay provider for the chain
redeliveryRecord?: RedeliveryRecord; // if the VAA is a redelivery, the redeliveryRecord
deliveryRecord?: DeliveryRecord; // information about the delivery process of the VAA
fatalStackTrace?: string; // if the top level unexpected exception try-catch caught, this was the stack trace
};
export type RedeliveryRecord = {
validVaaKeyFormat?: boolean; // if true, the VAA key format interpretable
vaaKeyPrintable?: string; // the VAA key in printable format of the original VAA
originalVaaFetchTimeStart?: number; // unix timestamp in milliseconds
originalVaaFetchTimeEnd?: number; // unix timestamp in milliseconds
originalVaaDidFetch?: boolean; // if true, the original VAA was successfully fetched
originalVaaHex?: string; // hex string of the original VAA
originalVaaDidParse?: boolean; // if true, the original VAA was successfully parsed
isValidRedelivery?: boolean; // if true, the redelivery VAA is valid
invalidRedeliveryReason?: string; // if the redelivery VAA is invalid, the reason why
};
export type DeliveryRecord = {
deliveryInstructionsPrintable?: string; // the delivery instructions in printable format
hasAdditionalVaas?: boolean; // if true, the delivery instructions contain additional VAAs
additionalVaaKeysFormatValid?: boolean; // if true, the additional VAA key format interpretable
additionalVaaKeysPrintable?: string; // the additional VAA key in printable format
fetchAdditionalVaasTimeStart?: number; // unix timestamp in milliseconds
fetchAdditionalVaasTimeEnd?: number; // unix timestamp in milliseconds
additionalVaasDidFetch?: boolean; // if true, the additional VAAs were successfully fetched
additionalVaasHex?: string[]; // hex string of the additional VAAs
chainId?: number; // the chain ID of the chain the VAA is being sent to
receiverValue?: string; // the receiver value of the VAA;
maxRefund?: string; // the max refund of the VAA;
budget?: string; // the budget of the VAA;
walletAcquisitionStartTime?: number; // unix timestamp in milliseconds
walletAcquisitionEndTime?: number; // unix timestamp in milliseconds
walletAcquisitionDidSucceed?: boolean; // if true, the wallet acquisition was successful
walletAddress?: string; // the wallet address of the wallet used to send the VAA
walletBalance?: string; // the balance of the wallet used to send the VAA
walletNonce?: number; // the nonce of the wallet used to send the VAA
gasUnitsEstimate?: number; // the gas units estimate for the transaction being submitted
gasPriceEstimate?: string; // the gas price estimate for the transaction being submitted
estimatedTransactionFee?: string; // the estimated transaction fee for the transaction being submitted
estimatedTransactionFeeEther?: string; // the estimated transaction fee for the transaction being submitted in the base units of the chain
transactionSubmitTimeStart?: number; // unix timestamp in milliseconds
transactionSubmitTimeEnd?: number; // unix timestamp in milliseconds
transactionDidSubmit?: boolean; // if true, the transaction was successfully submitted
transactionHashes?: string[]; // the transaction hashes of the transactions submitted
resultLogDidParse?: boolean; // if true, the result log was successfully parsed
resultLog?: string; // the result log of the transaction
};
export function deliveryExecutionRecordPrintable(
executionRecord: DeliveryExecutionRecord
): string {
return JSON.stringify(executionRecord, null, 2); //TODO deal with line breaks and such better
}
export function addFatalError(
executionRecord: DeliveryExecutionRecord,
e: any
) {
executionRecord.didError = true;
executionRecord.errorName = e.name;
executionRecord.fatalStackTrace = e.stack
? e.stack.replace(/\n/g, "\\n")
: "";
}

View File

@ -26,3 +26,5 @@ const jsonFormat = winston.format.combine(
winston.format.json(),
winston.format.errors({ stack: true })
);
type ExecutionContext = {};

View File

@ -12,36 +12,67 @@ import {
DeliveryInstruction,
packOverrides,
DeliveryOverrideArgs,
parseEVMExecutionInfoV1
parseEVMExecutionInfoV1,
} from "@certusone/wormhole-sdk/lib/cjs/relayer";
import { EVMChainId } from "@certusone/wormhole-sdk";
import { GRContext } from "./app";
import { BigNumber, ethers } from "ethers";
import { IWormholeRelayerDelivery__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts";
import { WormholeRelayer__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts";
import {
DeliveryExecutionRecord,
addFatalError,
deliveryExecutionRecordPrintable,
} from "./executionRecord";
export async function processGenericRelayerVaa(ctx: GRContext, next: Next) {
ctx.logger.info(`Processing generic relayer vaa`);
const payloadId = parseWormholeRelayerPayloadType(ctx.vaa!.payload);
// route payload types
if (payloadId == RelayerPayloadId.Delivery) {
ctx.logger.info(`Detected delivery VAA, processing delivery payload...`);
await processDelivery(ctx);
} else if (payloadId == RelayerPayloadId.Redelivery) {
ctx.logger.info(
`Detected redelivery VAA, processing redelivery payload...`
);
await processRedelivery(ctx);
} else {
ctx.logger.error(`Expected GR Delivery payload type, found ${payloadId}`);
throw new Error("Expected GR Delivery payload type");
const executionRecord: DeliveryExecutionRecord = {};
executionRecord.executionStartTime = Date.now();
try {
ctx.logger.info(`Processing generic relayer vaa`);
executionRecord.rawVaaHex = ctx.vaaBytes!.toString("hex");
executionRecord.rawVaaPayloadHex = ctx.vaa!.payload.toString("hex");
const payloadId = parseWormholeRelayerPayloadType(ctx.vaa!.payload);
executionRecord.payloadType = RelayerPayloadId[payloadId];
// route payload types
if (payloadId == RelayerPayloadId.Delivery) {
ctx.logger.info(`Detected delivery VAA, processing delivery payload...`);
await processDelivery(ctx, executionRecord);
} else if (payloadId == RelayerPayloadId.Redelivery) {
ctx.logger.info(
`Detected redelivery VAA, processing redelivery payload...`
);
await processRedelivery(ctx, executionRecord);
} else {
ctx.logger.error(`Expected GR Delivery payload type, found ${payloadId}`);
throw new Error("Expected GR Delivery payload type");
}
executionRecord.didError = false;
} catch (e: any) {
ctx.logger.error(`Fatal error in processGenericRelayerVaa: ${e}`);
addFatalError(executionRecord, e);
ctx.logger.error("Dumping execution context for fatal error");
ctx.logger.error(deliveryExecutionRecordPrintable(executionRecord));
}
executionRecord.executionEndTime = Date.now();
await next();
}
async function processDelivery(ctx: GRContext) {
async function processDelivery(
ctx: GRContext,
executionRecord: DeliveryExecutionRecord
) {
const deliveryVaa = parseWormholeRelayerSend(ctx.vaa!.payload);
const sourceDeliveryProvider = ethers.utils.getAddress(wh.tryUint8ArrayToNative(deliveryVaa.sourceDeliveryProvider, "ethereum"));
executionRecord.didParse = true;
const sourceDeliveryProvider = ethers.utils.getAddress(
wh.tryUint8ArrayToNative(deliveryVaa.sourceDeliveryProvider, "ethereum")
);
if (
sourceDeliveryProvider !==
ctx.deliveryProviders[ctx.vaa!.emitterChain as EVMChainId]
@ -49,14 +80,25 @@ async function processDelivery(ctx: GRContext) {
ctx.logger.info("Delivery vaa specifies different relay provider", {
sourceDeliveryProvider: deliveryVaa.sourceDeliveryProvider,
});
executionRecord.didMatchDeliveryProvider = false;
executionRecord.specifiedDeliveryProvider = sourceDeliveryProvider;
return;
}
processDeliveryInstruction(ctx, deliveryVaa, ctx.vaaBytes!);
processDeliveryInstruction(ctx, deliveryVaa, ctx.vaaBytes!, executionRecord);
}
async function processRedelivery(ctx: GRContext) {
async function processRedelivery(
ctx: GRContext,
executionRecord: DeliveryExecutionRecord
) {
executionRecord.redeliveryRecord = {};
const redeliveryVaa = parseWormholeRelayerResend(ctx.vaa!.payload);
const sourceDeliveryProvider = ethers.utils.getAddress(wh.tryUint8ArrayToNative(redeliveryVaa.newSourceDeliveryProvider, "ethereum"));
const sourceDeliveryProvider = ethers.utils.getAddress(
wh.tryUint8ArrayToNative(
redeliveryVaa.newSourceDeliveryProvider,
"ethereum"
)
);
if (
sourceDeliveryProvider !==
ctx.deliveryProviders[ctx.vaa!.emitterChain as EVMChainId]
@ -64,32 +106,76 @@ async function processRedelivery(ctx: GRContext) {
ctx.logger.info("Delivery vaa specifies different relay provider", {
sourceDeliveryProvider: redeliveryVaa.newSourceDeliveryProvider,
});
executionRecord.didMatchDeliveryProvider = false;
executionRecord.specifiedDeliveryProvider = sourceDeliveryProvider;
return;
}
if (
!redeliveryVaa.deliveryVaaKey.emitterAddress ||
!redeliveryVaa.deliveryVaaKey.sequence ||
!redeliveryVaa.deliveryVaaKey.chainId
) {
executionRecord.redeliveryRecord.validVaaKeyFormat = false;
throw new Error(`Received an invalid redelivery VAA key`);
}
ctx.logger.info(
`Redelivery requested for the following VAA: `,
vaaKeyPrintable(redeliveryVaa.deliveryVaaKey)
);
executionRecord.redeliveryRecord.vaaKeyPrintable = vaaKeyPrintable(
redeliveryVaa.deliveryVaaKey
).toString();
let originalVaa = await ctx.fetchVaa(
redeliveryVaa.deliveryVaaKey.chainId as wh.ChainId,
Buffer.from(redeliveryVaa.deliveryVaaKey.emitterAddress!),
redeliveryVaa.deliveryVaaKey.sequence!.toBigInt()
);
executionRecord.redeliveryRecord.originalVaaFetchTimeStart = Date.now();
let originalVaa: ParsedVaaWithBytes;
try {
originalVaa = await ctx.fetchVaa(
redeliveryVaa.deliveryVaaKey.chainId as wh.ChainId,
Buffer.from(redeliveryVaa.deliveryVaaKey.emitterAddress!),
redeliveryVaa.deliveryVaaKey.sequence!.toBigInt()
);
executionRecord.redeliveryRecord.originalVaaDidFetch = true;
executionRecord.redeliveryRecord.originalVaaHex =
originalVaa.bytes.toString("hex");
} catch (e: any) {
//TODO this failure mode is encountered both if the VAA does not exist, I.E, the redelivery is invalid,
// but also if there's just a network or RPC error in fetching the VAA. We should distinguish between these
// two cases, because the first case does not need to be retried, but the second case does.
ctx.logger.error(
`Failed while attempting to pull original delivery VAA: ${e}`
);
addFatalError(executionRecord, e);
return;
}
executionRecord.redeliveryRecord.originalVaaFetchTimeEnd = Date.now();
ctx.logger.info("Retrieved original VAA!");
const delivery = parseWormholeRelayerSend(originalVaa.payload);
if (!isValidRedelivery(ctx, delivery, redeliveryVaa)) {
const validityCheck = isValidRedelivery(ctx, delivery, redeliveryVaa); //TODO better name?
if (!validityCheck.isValid) {
ctx.logger.info("Exiting redelivery process");
executionRecord.redeliveryRecord.isValidRedelivery = false;
executionRecord.redeliveryRecord.invalidRedeliveryReason =
validityCheck.reason;
return;
} else {
executionRecord.redeliveryRecord.isValidRedelivery = true;
ctx.logger.info("Redelivery is valid, proceeding with redelivery");
processDeliveryInstruction(ctx, delivery, originalVaa.bytes, {
newReceiverValue: redeliveryVaa.newRequestedReceiverValue,
newExecutionInfo: redeliveryVaa.newEncodedExecutionInfo,
redeliveryHash: ctx.vaa!.hash,
});
processDeliveryInstruction(
ctx,
delivery,
originalVaa.bytes,
executionRecord,
{
newReceiverValue: redeliveryVaa.newRequestedReceiverValue,
newExecutionInfo: redeliveryVaa.newEncodedExecutionInfo,
redeliveryHash: ctx.vaa!.hash,
}
);
}
}
@ -97,79 +183,68 @@ function isValidRedelivery(
ctx: GRContext,
delivery: DeliveryInstruction,
redelivery: RedeliveryInstruction
): boolean {
//TODO check that the delivery & redelivery chains agree!
): { isValid: boolean; reason?: string } {
const output: any = { isValid: true };
if (delivery.targetChainId != redelivery.targetChainId) {
ctx.logger.info(
"Redelivery targetChain does not match original delivery targetChain"
);
ctx.logger.info(
output.isValid = false;
output.reason =
"Redelivery targetChain does not match original delivery targetChain, " +
"Original targetChain: " +
delivery.targetChainId +
" Redelivery targetChain: " +
redelivery.targetChainId
);
return false;
delivery.targetChainId +
" Redelivery targetChain: " +
redelivery.targetChainId;
ctx.logger.info(output.reason);
return output;
}
//TODO check that the sourceRelayerAddress is one of this relayer's addresses
if (!redelivery.newSourceDeliveryProvider) {
}
const [deliveryExecutionInfo,] = parseEVMExecutionInfoV1(delivery.encodedExecutionInfo, 0);
const [redeliveryExecutionInfo,] = parseEVMExecutionInfoV1(redelivery.newEncodedExecutionInfo, 0);
if (deliveryExecutionInfo.targetChainRefundPerGasUnused.gt(redeliveryExecutionInfo.targetChainRefundPerGasUnused)) {
ctx.logger.info(
"Redelivery target chain refund per gas unused is less than original delivery target chain refund per gas unused"
);
ctx.logger.info(
"Original refund: " +
deliveryExecutionInfo.targetChainRefundPerGasUnused.toBigInt().toLocaleString() +
" Redelivery: " +
redeliveryExecutionInfo.targetChainRefundPerGasUnused.toBigInt().toLocaleString()
);
return false;
}
if (delivery.requestedReceiverValue.gt(redelivery.newRequestedReceiverValue)) {
ctx.logger.info(
"Redelivery requested receiverValue is less than original delivery requested receiverValue"
);
ctx.logger.info(
"Original refund: " +
delivery.requestedReceiverValue.toBigInt().toLocaleString(),
+" Redelivery: " +
redelivery.newRequestedReceiverValue.toBigInt().toLocaleString()
);
return false;
}
if (
deliveryExecutionInfo.gasLimit >
redeliveryExecutionInfo.gasLimit
delivery.requestedReceiverValue.gt(redelivery.newRequestedReceiverValue)
) {
ctx.logger.info(
"Redelivery gasLimit is less than original delivery gasLimit"
);
ctx.logger.info(
"Original refund: " + deliveryExecutionInfo.gasLimit,
" Redelivery: " + redeliveryExecutionInfo.gasLimit
);
return false;
output.isValid = false;
(output.reason =
"Redelivery receiverValueTarget is less than original delivery receiverValueTarget, " +
"Original receiverValue: " +
delivery.requestedReceiverValue.toBigInt().toLocaleString()),
+" Redelivery: " +
redelivery.newRequestedReceiverValue.toBigInt().toLocaleString();
ctx.logger.info(output.reason);
return output;
}
return true;
//TODO check that information inside the execution params is valid
return output;
}
async function processDeliveryInstruction(
ctx: GRContext,
delivery: DeliveryInstruction,
deliveryVaa: Buffer | Uint8Array,
executionRecord: DeliveryExecutionRecord,
overrides?: DeliveryOverrideArgs
) {
executionRecord.deliveryRecord = {};
executionRecord.deliveryRecord.deliveryInstructionsPrintable =
deliveryInstructionsPrintable(delivery).toString();
executionRecord.deliveryRecord.hasAdditionalVaas =
delivery.vaaKeys.length > 0;
//TODO this check is not quite correct
if (
delivery.vaaKeys.findIndex(
(m) => !m.emitterAddress || !m.sequence || !m.chainId
) != -1
) {
executionRecord.deliveryRecord.additionalVaaKeysFormatValid = false;
throw new Error(`Received an invalid additional VAA key`);
}
const vaaKeysString = delivery.vaaKeys.map((m) => vaaKeyPrintable(m));
ctx.logger.info(`Fetching vaas from parsed delivery vaa manifest...`, {
vaaKeys: delivery.vaaKeys.map(vaaKeyPrintable),
vaaKeys: vaaKeysString,
});
executionRecord.deliveryRecord.additionalVaaKeysPrintable =
vaaKeysString.toString();
const vaaIds = delivery.vaaKeys.map((m) => ({
emitterAddress: m.emitterAddress!,
@ -177,10 +252,25 @@ async function processDeliveryInstruction(
sequence: m.sequence!.toBigInt(),
}));
let results = await ctx.fetchVaas({
ids: vaaIds,
// txHash: ctx.sourceTxHash,
});
let results: ParsedVaaWithBytes[];
executionRecord.deliveryRecord.fetchAdditionalVaasTimeStart = Date.now();
try {
results = await ctx.fetchVaas({
ids: vaaIds,
// txHash: ctx.sourceTxHash,
});
executionRecord.deliveryRecord.additionalVaasDidFetch = true;
} catch (e: any) {
ctx.logger.error(`Failed while attempting to pull additional VAAs: ${e}`);
executionRecord.deliveryRecord.additionalVaasDidFetch = false;
addFatalError(executionRecord, e);
return;
}
executionRecord.deliveryRecord.fetchAdditionalVaasTimeEnd = Date.now();
executionRecord.deliveryRecord.additionalVaasHex = results.map((v) =>
v.bytes.toString("hex")
);
ctx.logger.debug(`Processing delivery`, {
deliveryVaa: deliveryInstructionsPrintable(delivery),
@ -189,64 +279,130 @@ async function processDeliveryInstruction(
const chainId = delivery.targetChainId as EVMChainId;
const receiverValue = overrides?.newReceiverValue
? overrides.newReceiverValue
: (delivery.requestedReceiverValue.add(delivery.extraReceiverValue));
: delivery.requestedReceiverValue.add(delivery.extraReceiverValue);
const getMaxRefund = (encodedDeliveryInfo: Buffer) => {
const [deliveryInfo,] = parseEVMExecutionInfoV1(encodedDeliveryInfo, 0);
return deliveryInfo.targetChainRefundPerGasUnused.mul(deliveryInfo.gasLimit);
}
const maxRefund = getMaxRefund(overrides?.newExecutionInfo
? overrides.newExecutionInfo
: delivery.encodedExecutionInfo);
const [deliveryInfo] = parseEVMExecutionInfoV1(encodedDeliveryInfo, 0);
return deliveryInfo.targetChainRefundPerGasUnused.mul(
deliveryInfo.gasLimit
);
};
const maxRefund = getMaxRefund(
overrides?.newExecutionInfo
? overrides.newExecutionInfo
: delivery.encodedExecutionInfo
);
const budget = receiverValue.add(maxRefund);
await ctx.wallets.onEVM(chainId, async ({ wallet }) => {
const wormholeRelayer = IWormholeRelayerDelivery__factory.connect(
ctx.wormholeRelayers[chainId],
wallet
);
executionRecord.deliveryRecord.chainId = chainId;
executionRecord.deliveryRecord.receiverValue = receiverValue.toString();
executionRecord.deliveryRecord.maxRefund = maxRefund.toString();
executionRecord.deliveryRecord.budget = budget.toString();
const encodedVMs = results.map((v) => v.bytes);
const packedOverrides = overrides ? packOverrides(overrides) : [];
const gasUnitsEstimate = await wormholeRelayer.estimateGas.deliver(encodedVMs, deliveryVaa, wallet.address, packedOverrides, {
value: budget,
gasLimit: 3000000,
executionRecord.deliveryRecord.walletAcquisitionStartTime = Date.now();
try {
await ctx.wallets.onEVM(chainId, async ({ wallet }) => {
executionRecord.deliveryRecord!.walletAcquisitionEndTime = Date.now();
executionRecord.deliveryRecord!.walletAcquisitionDidSucceed = true;
executionRecord.deliveryRecord!.walletAddress = wallet.address;
executionRecord.deliveryRecord!.walletBalance = (
await wallet.getBalance()
).toString();
executionRecord.deliveryRecord!.walletNonce =
await wallet.getTransactionCount();
const wormholeRelayer = WormholeRelayer__factory.connect(
ctx.wormholeRelayers[chainId],
wallet
);
//TODO properly import this type from the SDK for safety
const input: any = {
encodedVMs: results.map((v) => v.bytes),
encodedDeliveryVAA: deliveryVaa,
relayerRefundAddress: wallet.address,
overrides: overrides ? packOverrides(overrides) : [],
};
const gasUnitsEstimate = await wormholeRelayer.estimateGas.deliver(
results.map((v) => v.bytes),
deliveryVaa,
wallet.address,
overrides ? packOverrides(overrides) : [],
{
value: budget,
gasLimit: 3000000,
}
);
const gasPrice = await wormholeRelayer.provider.getGasPrice();
const estimatedTransactionFee = gasPrice.mul(gasUnitsEstimate);
const estimatedTransactionFeeEther = ethers.utils.formatEther(
estimatedTransactionFee
);
executionRecord.deliveryRecord!.gasUnitsEstimate =
gasUnitsEstimate.toNumber();
executionRecord.deliveryRecord!.gasPriceEstimate = gasPrice.toString();
executionRecord.deliveryRecord!.estimatedTransactionFee =
estimatedTransactionFee.toString();
executionRecord.deliveryRecord!.estimatedTransactionFeeEther =
estimatedTransactionFeeEther;
ctx.logger.info(
`Estimated transaction cost (ether): ${estimatedTransactionFeeEther}`,
{
gasUnitsEstimate: gasUnitsEstimate.toString(),
gasPrice: gasPrice.toString(),
estimatedTransactionFee: estimatedTransactionFee.toString(),
estimatedTransactionFeeEther,
valueEther: ethers.utils.formatEther(budget),
}
);
process.stdout.write("");
await sleep(200);
ctx.logger.debug("Sending 'deliver' tx...");
executionRecord.deliveryRecord!.transactionSubmitTimeStart = Date.now();
const receipt = await wormholeRelayer
.deliver(
results.map((v) => v.bytes),
deliveryVaa,
wallet.address,
overrides ? packOverrides(overrides) : [],
{
value: budget,
gasLimit: 3000000,
}
) //TODO more intelligent gas limit
.then((x: any) => x.wait());
executionRecord.deliveryRecord!.transactionSubmitTimeEnd = Date.now();
executionRecord.deliveryRecord!.transactionDidSubmit = true;
executionRecord.deliveryRecord!.transactionHashes = [
receipt.transactionHash,
];
logResults(ctx, receipt, chainId, executionRecord);
});
const gasPrice = await wormholeRelayer.provider.getGasPrice();
const estimatedTransactionFee = gasPrice.mul(gasUnitsEstimate);
const estimatedTransactionFeeEther = ethers.utils.formatEther(
estimatedTransactionFee
);
ctx.logger.info(
`Estimated transaction cost (ether): ${estimatedTransactionFeeEther}`,
{
gasUnitsEstimate: gasUnitsEstimate.toString(),
gasPrice: gasPrice.toString(),
estimatedTransactionFee: estimatedTransactionFee.toString(),
estimatedTransactionFeeEther,
valueEther: ethers.utils.formatEther(budget),
}
);
process.stdout.write("");
await sleep(200);
ctx.logger.debug("Sending 'deliver' tx...");
const receipt = await wormholeRelayer
.deliver(encodedVMs, deliveryVaa, wallet.address, packedOverrides, { value: budget, gasLimit: 3000000 })
.then((x: any) => x.wait());
logResults(ctx, receipt, chainId);
});
} catch (e: any) {
ctx.logger.error(`Fatal error in processGenericRelayerVaa: ${e}`);
addFatalError(executionRecord, e);
ctx.logger.error("Dumping execution context for fatal error");
ctx.logger.error(deliveryExecutionRecordPrintable(executionRecord));
}
}
function logResults(
ctx: GRContext,
receipt: ethers.ContractReceipt,
chainId: EVMChainId
chainId: EVMChainId,
executionRecord: DeliveryExecutionRecord
) {
const relayerContractLog = receipt.logs?.find((x: any) => {
return x.address === ctx.wormholeRelayers[chainId];
});
if (relayerContractLog) {
const parsedLog = IWormholeRelayerDelivery__factory.createInterface().parseLog(
const parsedLog = WormholeRelayer__factory.createInterface().parseLog(
relayerContractLog!
);
const logArgs = {
@ -257,15 +413,19 @@ function logResults(
status: parsedLog.args[4],
};
ctx.logger.info("Parsed Delivery event", logArgs);
executionRecord.deliveryRecord!.resultLogDidParse = true;
switch (logArgs.status) {
case 0:
ctx.logger.info("Delivery Success");
executionRecord.deliveryRecord!.resultLog = "Delivery Success";
break;
case 1:
ctx.logger.info("Receiver Failure");
executionRecord.deliveryRecord!.resultLog = "Receiver Failure";
break;
case 2:
ctx.logger.info("Forwarding Failure");
executionRecord.deliveryRecord!.resultLog = "Forwarding Failure";
break;
}
}