diff --git a/relayer/generic_relayer/relayer-engine-v2/src/executionRecord.ts b/relayer/generic_relayer/relayer-engine-v2/src/executionRecord.ts new file mode 100644 index 000000000..54a8149d3 --- /dev/null +++ b/relayer/generic_relayer/relayer-engine-v2/src/executionRecord.ts @@ -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") + : ""; +} diff --git a/relayer/generic_relayer/relayer-engine-v2/src/log.ts b/relayer/generic_relayer/relayer-engine-v2/src/log.ts index 174d1638e..1bc165e0d 100644 --- a/relayer/generic_relayer/relayer-engine-v2/src/log.ts +++ b/relayer/generic_relayer/relayer-engine-v2/src/log.ts @@ -26,3 +26,5 @@ const jsonFormat = winston.format.combine( winston.format.json(), winston.format.errors({ stack: true }) ); + +type ExecutionContext = {}; diff --git a/relayer/generic_relayer/relayer-engine-v2/src/processor.ts b/relayer/generic_relayer/relayer-engine-v2/src/processor.ts index d7ae34be1..bce0139e4 100644 --- a/relayer/generic_relayer/relayer-engine-v2/src/processor.ts +++ b/relayer/generic_relayer/relayer-engine-v2/src/processor.ts @@ -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; } }