Skip to content

Commit

Permalink
so close
Browse files Browse the repository at this point in the history
  • Loading branch information
barnjamin committed Oct 13, 2023
1 parent 609c009 commit e093a2a
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 146 deletions.
94 changes: 57 additions & 37 deletions connect/src/protocols/gatewayTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
GatewayTransferDetails,
GatewayTransferMsg,
GatewayTransferWithPayloadMsg,
IbcBridge,
IbcTransferInfo,
NativeAddress,
Signer,
TokenId,
TransactionId,
Expand All @@ -20,15 +20,14 @@ import {
UnsignedTransaction,
VAA,
WormholeMessageId,
toGatewayMsg,
deserialize,
gatewayTransferMsg,
isGatewayTransferDetails,
isTransactionIdentifier,
isWormholeMessageId,
toNative,
IbcBridge,
nativeChainAddress,
toGatewayMsg,
toNative,
} from "@wormhole-foundation/sdk-definitions";
import { Wormhole } from "../wormhole";
import {
Expand Down Expand Up @@ -115,14 +114,17 @@ export class GatewayTransfer implements WormholeTransfer {
static async from(
wh: Wormhole,
from: WormholeMessageId,
timeout?: number,
): Promise<GatewayTransfer>;
static async from(
wh: Wormhole,
from: TransactionId,
timeout?: number,
): Promise<GatewayTransfer>;
static async from(
wh: Wormhole,
from: GatewayTransferDetails | WormholeMessageId | TransactionId,
timeout?: number,
): Promise<GatewayTransfer> {
// we need this regardless of the type of `from`
const wc = wh.getChain(GatewayTransfer.chain);
Expand Down Expand Up @@ -154,7 +156,7 @@ export class GatewayTransfer implements WormholeTransfer {
gt.state = TransferState.Initiated;

// Wait for what _can_ complete to complete
await gt.fetchAttestation();
await gt.fetchAttestation(timeout);

return gt;
}
Expand All @@ -165,13 +167,7 @@ export class GatewayTransfer implements WormholeTransfer {
from: WormholeMessageId,
): Promise<GatewayTransferDetails> {
// Starting with the VAA
const { chain: emitterChain, emitter, sequence } = from;
const vaa = await GatewayTransfer.getTransferVaa(
wh,
emitterChain,
emitter,
sequence,
);
const vaa = await GatewayTransfer.getTransferVaa(wh, from);

// The VAA may have a payload which may have a nested GatewayTransferMessage
let payload: Uint8Array | undefined =
Expand Down Expand Up @@ -235,7 +231,9 @@ export class GatewayTransfer implements WormholeTransfer {

const originChain = wh.getChain(chain);

// If its origin chain is Cosmos, itll be an IBC message
// If its origin chain is Cosmos, it should be an IBC message
// but its not all the time so do this differently?
// check if the chain supports gateway?
if (chainToPlatform(chain) === "Cosmwasm") {
// Get the ibc tx info from the origin
const ibcBridge = await originChain.getIbcBridge();
Expand Down Expand Up @@ -401,8 +399,10 @@ export class GatewayTransfer implements WormholeTransfer {
});
}

// TODO: track the time elapsed and subtract it from the timeout passed with
// successive updates
// wait for the Attestations to be ready
async fetchAttestation(): Promise<AttestationId[]> {
async fetchAttestation(timeout?: number): Promise<AttestationId[]> {
// Note: this method probably does too much

if (
Expand Down Expand Up @@ -446,18 +446,18 @@ export class GatewayTransfer implements WormholeTransfer {
const retryInterval = 5000;
const task = () =>
this.gatewayIbcBridge.lookupMessageFromIbcMsgId(xfer.id);
const whm = await retry<WormholeMessageId>(task, retryInterval);
const whm = await retry<WormholeMessageId>(
task,
retryInterval,
timeout,
"Gateway:IbcBridge:LookupWormholeMessageFromIncomingIbcMessage",
);
if (!whm)
throw new Error(
"Matching wormhole message not found after retries exhausted",
);

const vaa = await GatewayTransfer.getTransferVaa(
this.wh,
whm.chain,
whm.emitter,
whm.sequence,
);
const vaa = await GatewayTransfer.getTransferVaa(this.wh, whm);
this.vaas = [{ id: whm, vaa }];

attestations.push(whm);
Expand All @@ -469,18 +469,14 @@ export class GatewayTransfer implements WormholeTransfer {
// GatewayTransferMsg
const { chain, txid } = this.transactions[0];
const [whm] = await this.wh.parseMessageFromTx(chain, txid);
const vaa = await GatewayTransfer.getTransferVaa(
this.wh,
whm.chain,
whm.emitter,
whm.sequence,
);
const vaa = await GatewayTransfer.getTransferVaa(this.wh, whm);
this.vaas = [{ id: whm, vaa }];

attestations.push(whm);

// TODO: conf for these settings? how do we choose them?
const retryInterval = 2000;
const vaaRedeemedRetryInterval = 2000;
const transferCompleteInterval = 5000;

// Wait until the vaa is redeemed before trying to look up the
// transfer message
Expand All @@ -491,24 +487,49 @@ export class GatewayTransfer implements WormholeTransfer {
const redeemed = await isVaaRedeemed(wcTb, [vaa]);
return redeemed ? redeemed : null;
};
const redeemed = await retry<boolean>(isRedeemedTask, retryInterval);
const redeemed = await retry<boolean>(
isRedeemedTask,
vaaRedeemedRetryInterval,
timeout,
"Gateway:TokenBridge:IsVaaRedeemed",
);
if (!redeemed)
throw new Error("VAA not redeemed after retries exhausted");

// Finally, get the IBC transactions from wormchain
// Next, get the IBC transactions from wormchain
// Note: Because we search by GatewayTransferMsg payload
// there is a possibility of dupe messages being returned
// using a nonce should help
const wcTransferTask = () =>
fetchIbcXfer(this.gatewayIbcBridge, this.msg);
const wcTransfer = await retry<IbcTransferInfo>(
wcTransferTask,
retryInterval,
vaaRedeemedRetryInterval,
timeout,
"Gateway:IbcBridge:WormchainTransferInitiated",
);
if (!wcTransfer)
throw new Error("Wormchain transfer not found after retries exhausted");

this.ibcTransfers.push(wcTransfer);

// Finally, get the IBC transfer to the destination chain
const destChain = this.wh.getChain(this.transfer.to.chain);
const destIbcBridge = await destChain.getIbcBridge();
const destTransferTask = () => fetchIbcXfer(destIbcBridge, wcTransfer.id);
const destTransfer = await retry<IbcTransferInfo>(
destTransferTask,
transferCompleteInterval,
timeout,
"Destination:IbcBridge:WormchainTransferCompleted",
);
if (!destTransfer)
throw new Error(
"IBC Transfer into destination not found after retries exhausted" +
JSON.stringify(wcTransfer.id),
);

this.ibcTransfers.push(destTransfer);
}

// Add transfers to attestations we return
Expand Down Expand Up @@ -583,13 +604,12 @@ export class GatewayTransfer implements WormholeTransfer {

static async getTransferVaa(
wh: Wormhole,
chain: ChainName,
emitter: UniversalAddress | NativeAddress<PlatformName>,
sequence: bigint,
retries: number = 5,
whm: WormholeMessageId,
): Promise<VAA<"TransferWithPayload"> | VAA<"Transfer">> {
const vaaBytes = await wh.getVAABytes(chain, emitter, sequence, retries);
if (!vaaBytes) throw new Error(`No VAA available after ${retries} retries`);
const { chain, emitter, sequence } = whm;
const vaaBytes = await wh.getVAABytes(chain, emitter, sequence);
if (!vaaBytes)
throw new Error(`No VAA Available: ${chain}/${emitter}/${sequence}`);

const partial = deserialize("Uint8Array", vaaBytes);
switch (partial.payload[0]) {
Expand Down
4 changes: 4 additions & 0 deletions connect/src/protocols/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export async function retry<T>(
task: Task<T>,
interval: number,
timeout: number = DEFAULT_TIMEOUT,
title?: string,
): Promise<T | null> {
const maxRetries = Math.floor(timeout / interval);

Expand All @@ -24,6 +25,9 @@ export async function retry<T>(
resolve(result);
}

if (title)
console.log(`Retrying ${title}, attempt ${retries}/${maxRetries} `);

retries++;
}, interval);
});
Expand Down
12 changes: 6 additions & 6 deletions connect/src/protocols/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export async function fetchIbcXfer(
if (isTransactionIdentifier(msg)) {
try {
return await wcIbc.lookupTransferFromTx(msg.txid);
} catch (e) {
console.error("Failed to lookup transfer from tx: ", e);
} catch {
//console.error("Failed to lookup transfer from tx: ", e);
}
//
} else if (
Expand All @@ -50,14 +50,14 @@ export async function fetchIbcXfer(
) {
try {
return await wcIbc.lookupTransferFromMsg(msg);
} catch (e) {
console.error("Failed to lookup transfer from message: ", e);
} catch {
//console.error("Failed to lookup transfer from message: ", e);
}
} else if (isIbcMessageId(msg)) {
try {
return await wcIbc.lookupTransferFromIbcMsgId(msg);
} catch (e) {
console.error("Failed to lookup transfer from sequence: ", e);
} catch {
//console.error("Failed to lookup transfer from sequence: ", e);
}
}

Expand Down
44 changes: 25 additions & 19 deletions connect/src/wormhole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { CCTPTransfer } from "./protocols/cctpTransfer";
import { GatewayTransfer } from "./protocols/gatewayTransfer";
import { TransactionStatus } from "./api";
import { getCircleAttestation } from "./circle-api";
import { retry } from "./protocols/retry";

export class Wormhole {
protected _platforms: Map<PlatformName, Platform<PlatformName>>;
Expand Down Expand Up @@ -362,41 +363,46 @@ export class Wormhole {
chain: ChainName,
emitter: UniversalAddress | NativeAddress<PlatformName>,
sequence: bigint,
retries: number = 5,
opts?: {
retryDelay?: number;
requestTimeout?: number;
},
timeout: number = 6000,
): Promise<Uint8Array | undefined> {
const chainId = toChainId(chain);
const universalAddress = emitter.toUniversalAddress().toString();
const emitterAddress = universalAddress.startsWith("0x")
? universalAddress.slice(2)
: universalAddress;

let response: AxiosResponse<any, any> | undefined;
const url = `${this.conf.api}/v1/signed_vaa/${chainId}/${emitterAddress}/${sequence}`;
const axiosOptions: AxiosRequestConfig = {};
if (opts?.requestTimeout) {
axiosOptions.timeout = opts.requestTimeout;
axiosOptions.signal = AbortSignal.timeout(opts.requestTimeout);
}

for (let i = retries; i > 0 && !response; i--) {
if (i != retries)
await new Promise((f) => setTimeout(f, opts?.retryDelay ?? 2000));
// TODO: hardcoded timeouts
const reqTimeout = 3000;
const retryInterval = 2000;

const axiosOptions: AxiosRequestConfig = {
//timeout,
//signal: AbortSignal.timeout(reqTimeout),
};

const task = async () => {
try {
response = await axios.get(url);
const response = await axios.get(url, axiosOptions);
if (!response || !response.data) return null;
const { data } = response;
return new Uint8Array(Buffer.from(data.vaaBytes, "base64"));
} catch (e) {
console.error(`Caught an error waiting for VAA: ${e}\n${url}\n`);
}
}
if (!response || !response.data) return;
return null;
};

const { data } = response;
const vaa = await retry<Uint8Array>(
task,
retryInterval,
timeout,
"Wormholescan:signed_vaa",
);

return new Uint8Array(Buffer.from(data.vaaBytes, "base64"));
if (vaa) return vaa;
return undefined;
}

/**
Expand Down
6 changes: 5 additions & 1 deletion core/definitions/src/protocols/ibc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,11 @@ export interface IbcBridge<P extends PlatformName> {
// fetched from contract
fetchTransferChannel(chain: ChainName): Promise<string | null>;

lookupMessageFromIbcMsgId(msg: IbcMessageId): Promise<WormholeMessageId>;
// Find the wormhole emitted message id for a given IBC transfer
// if it does not exist, this will return null
lookupMessageFromIbcMsgId(
msg: IbcMessageId,
): Promise<WormholeMessageId | null>;

// Get IbcTransferInfo
// TODO: overload
Expand Down
Loading

0 comments on commit e093a2a

Please sign in to comment.