Skip to content

Commit 4511053

Browse files
committed
add getMessageFee to core protocol, add handlers for some Solana simulate errors
1 parent 33195de commit 4511053

File tree

8 files changed

+85
-12
lines changed

8 files changed

+85
-12
lines changed

core/definitions/src/protocols/core.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface WormholeCore<
1010
P extends Platform,
1111
C extends PlatformToChains<P>,
1212
> {
13+
getMessageFee(): Promise<bigint>;
1314
publishMessage(
1415
sender: AccountAddress<C>,
1516
message: string | Uint8Array,

platforms/aptos/protocols/core/src/core.ts

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export class AptosWormholeCore<N extends Network, C extends AptosChains>
3636
throw new Error(`CoreBridge contract Address for chain ${chain} not found`);
3737
this.coreBridge = coreBridgeAddress;
3838
}
39+
getMessageFee(): Promise<bigint> {
40+
throw new Error("Method not implemented.");
41+
}
3942

4043
static async fromRpc<N extends Network>(
4144
connection: AptosClient,

platforms/cosmwasm/protocols/core/src/wormholeCore.ts platforms/cosmwasm/protocols/core/src/core.ts

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export class CosmwasmWormholeCore<N extends Network, C extends CosmwasmChains>
3737
this.coreAddress = coreAddress;
3838
}
3939

40+
getMessageFee(): Promise<bigint> {
41+
throw new Error("Method not implemented.");
42+
}
43+
4044
static async fromRpc<N extends Network>(
4145
rpc: CosmWasmClient,
4246
config: ChainsConfig<N, CosmwasmPlatformType>,

platforms/cosmwasm/protocols/core/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { registerProtocol } from "@wormhole-foundation/connect-sdk";
22
import { _platform } from "@wormhole-foundation/connect-sdk-cosmwasm";
3-
import { CosmwasmWormholeCore } from "./wormholeCore";
3+
import { CosmwasmWormholeCore } from "./core";
44

55
declare global {
66
namespace WormholeNamespace {
@@ -12,4 +12,4 @@ declare global {
1212

1313
registerProtocol(_platform, "WormholeCore", CosmwasmWormholeCore);
1414

15-
export * from "./wormholeCore";
15+
export * from "./core";

platforms/evm/protocols/core/src/wormholeCore.ts platforms/evm/protocols/core/src/core.ts

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export class EvmWormholeCore<
6262
);
6363
}
6464

65+
async getMessageFee(): Promise<bigint> {
66+
return await this.core.messageFee.staticCall();
67+
}
68+
6569
static async fromRpc<N extends Network>(
6670
provider: Provider,
6771
config: ChainsConfig<N, EvmPlatformType>,

platforms/evm/protocols/core/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { registerProtocol } from '@wormhole-foundation/connect-sdk';
22
import { _platform } from '@wormhole-foundation/connect-sdk-evm';
3-
import { EvmWormholeCore } from './wormholeCore';
3+
import { EvmWormholeCore } from './core';
44

55
declare global {
66
namespace WormholeNamespace {
@@ -13,4 +13,4 @@ declare global {
1313
registerProtocol(_platform, 'WormholeCore', EvmWormholeCore);
1414

1515
export * as ethers_contracts from './ethers-contracts';
16-
export * from './wormholeCore';
16+
export * from './core';

platforms/solana/protocols/core/src/core.ts

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
createReadOnlyWormholeProgramInterface,
3636
createVerifySignaturesInstructions,
3737
derivePostedVaaKey,
38+
getWormholeBridgeData,
3839
} from './utils';
3940

4041
const SOLANA_SEQ_LOG = 'Program log: Sequence: ';
@@ -86,6 +87,14 @@ export class SolanaWormholeCore<N extends Network, C extends SolanaChains>
8687
);
8788
}
8889

90+
async getMessageFee(): Promise<bigint> {
91+
const bd = await getWormholeBridgeData(
92+
this.connection,
93+
this.coreBridge.programId,
94+
);
95+
return bd.config.fee;
96+
}
97+
8998
async *publishMessage(
9099
sender: AnySolanaAddress,
91100
message: Uint8Array,

platforms/solana/src/platform.ts

+60-8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
ParsedAccountData,
2323
PublicKey,
2424
SendOptions,
25+
SendTransactionError,
26+
TransactionExpiredBlockheightExceededError,
2527
} from '@solana/web3.js';
2628
import { SolanaAddress, SolanaZeroAddress } from './address';
2729
import {
@@ -160,6 +162,52 @@ export class SolanaPlatform<N extends Network> extends PlatformContext<
160162
return balancesArr.reduce((obj, item) => Object.assign(obj, item), {});
161163
}
162164

165+
// Handles retrying a Transaction if the error is deemed to be
166+
// recoverable. Currently handles:
167+
// - Blockhash not found (blockhash too new for the node we submitted to)
168+
// - Not enough bytes (storage account not seen yet)
169+
170+
private static async sendWithRetry(
171+
rpc: Connection,
172+
stxns: SignedTx,
173+
opts: SendOptions,
174+
retries: number = 3,
175+
): Promise<string> {
176+
// Shouldnt get hit but just in case
177+
if (!retries) throw new Error('Too many retries');
178+
179+
try {
180+
const txid = await rpc.sendRawTransaction(stxns.tx, opts);
181+
return txid;
182+
} catch (e) {
183+
retries -= 1;
184+
if (!retries) throw e;
185+
186+
// Would require re-signing, for now bail
187+
if (e instanceof TransactionExpiredBlockheightExceededError) throw e;
188+
189+
// Only handle SendTransactionError
190+
if (!(e instanceof SendTransactionError)) throw e;
191+
const emsg = e.message;
192+
193+
// Only handle simulation errors
194+
if (!emsg.includes('Transaction simulation failed')) throw e;
195+
196+
// Blockhash not found _yet_
197+
if (emsg.includes('Blockhash not found'))
198+
return this.sendWithRetry(rpc, stxns, opts, retries);
199+
200+
// Find the log message with the error details
201+
const loggedErr = e.logs.find((log) =>
202+
log.startsWith('Program log: Error: '),
203+
);
204+
205+
// Probably caused by storage account not seen yet
206+
if (loggedErr && loggedErr.includes('Not enough bytes'))
207+
return this.sendWithRetry(rpc, stxns, opts, retries);
208+
}
209+
}
210+
163211
static async sendWait(
164212
chain: Chain,
165213
rpc: Connection,
@@ -168,14 +216,16 @@ export class SolanaPlatform<N extends Network> extends PlatformContext<
168216
): Promise<TxHash[]> {
169217
const { blockhash, lastValidBlockHeight } = await this.latestBlock(rpc);
170218

171-
// Set the commitment level to match the rpc commitment level
172-
// otherwise, it defaults to finalized
173-
if (!opts) opts = { preflightCommitment: rpc.commitment };
174-
175219
const txhashes = await Promise.all(
176-
stxns.map((stxn) => {
177-
return rpc.sendRawTransaction(stxn, opts);
178-
}),
220+
stxns.map((stxn) =>
221+
this.sendWithRetry(
222+
rpc,
223+
stxn,
224+
// Set the commitment level to match the rpc commitment level
225+
// otherwise, it defaults to finalized
226+
opts ?? { preflightCommitment: rpc.commitment },
227+
),
228+
),
179229
);
180230

181231
await Promise.all(
@@ -198,11 +248,13 @@ export class SolanaPlatform<N extends Network> extends PlatformContext<
198248
rpc: Connection,
199249
commitment?: Commitment,
200250
): Promise<{ blockhash: string; lastValidBlockHeight: number }> {
251+
// Use finalized to prevent blockhash not found errors
252+
// Note: this may mean we have less time to submit transactions?
201253
return rpc.getLatestBlockhash(commitment ?? 'finalized');
202254
}
203255

204256
static async getLatestBlock(rpc: Connection): Promise<number> {
205-
const { lastValidBlockHeight } = await this.latestBlock(rpc);
257+
const { lastValidBlockHeight } = await this.latestBlock(rpc, 'confirmed');
206258
return lastValidBlockHeight;
207259
}
208260

0 commit comments

Comments
 (0)