Skip to content

Commit

Permalink
Retrying known and unknown simulation errors (#3046)
Browse files Browse the repository at this point in the history
* Retrying known and unknown simulation errors

Signed-off-by: Emre Bogazliyanlioglu <[email protected]>

* Log all found errors instead of the first one encountered

Signed-off-by: Emre Bogazliyanlioglu <[email protected]>

* Addressing the first round of review feedback

Signed-off-by: Emre Bogazliyanlioglu <[email protected]>

---------

Signed-off-by: Emre Bogazliyanlioglu <[email protected]>
  • Loading branch information
emreboga authored Dec 6, 2024
1 parent 0a9e65c commit e3d07c3
Showing 1 changed file with 62 additions and 24 deletions.
86 changes: 62 additions & 24 deletions wormhole-connect/src/utils/wallet/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Connection,
RpcResponseAndContext,
SignatureResult,
SimulatedTransactionResponse,
Transaction,
} from '@solana/web3.js';

Expand All @@ -28,7 +29,7 @@ import {
} from '@xlabs-libs/wallet-aggregator-solana';

import config from 'config';
import { sleep } from 'utils';
import { isEmptyObject, sleep } from 'utils';

import {
isVersionedTransaction,
Expand All @@ -44,6 +45,48 @@ import { VersionedTransaction } from '@solana/web3.js';
const getWalletName = (wallet: Wallet) =>
wallet.getName().toLowerCase().replaceAll('wallet', '').trim();

// Checks response logs for known errors.
// Returns when the first error is encountered.
function checkKnownSimulationError(
response: SimulatedTransactionResponse,
): boolean {
const errors = {};

// This error occur when the blockhash included in a transaction is not deemed to be valid
// when a validator processes a transaction. We can retry the simulation to get a valid blockhash.
if (response.err === 'BlockhashNotFound') {
errors['BlockhashNotFound'] =
'Blockhash not found during simulation. Trying again.';
}

// Check the response logs for any known errors
if (response.logs) {
for (const line of response.logs) {
// In some cases which aren't deterministic, like a slippage error, we can retry the
// simulation a few times to get a successful response.
if (line.includes('SlippageToleranceExceeded')) {
errors['SlippageToleranceExceeded'] =
'Slippage failure during simulation. Trying again.';
}

// In this case a require_gte expression was violated during a Swap instruction.
// We can retry the simulation to get a successful response.
if (line.includes('RequireGteViolated')) {
errors['RequireGteViolated'] =
'Swap instruction failure during simulation. Trying again.';
}
}
}

// No known simulation errors found
if (isEmptyObject(errors)) {
return false;
}

console.table(errors);
return true;
}

export function fetchOptions() {
const tag = config.isMainnet ? 'mainnet-beta' : 'devnet';
const connection = new Connection(config.rpcs.Solana || clusterApiUrl(tag));
Expand Down Expand Up @@ -196,7 +239,7 @@ export async function signAndSendTransaction(
return signature;
}

// this will throw if the simulation fails
// This will throw if the simulation fails
async function createPriorityFeeInstructions(
connection: Connection,
transaction: Transaction | VersionedTransaction,
Expand All @@ -205,15 +248,13 @@ async function createPriorityFeeInstructions(
let unitsUsed = 200_000;
let simulationAttempts = 0;

simulationLoop: while (simulationAttempts < 5) {
simulationAttempts++;

simulationLoop: while (true) {
if (
isVersionedTransaction(transaction) &&
!transaction.message.recentBlockhash
) {
// This is required for versioned transactions - simulateTransaction throws
// if recentBlockhash is an empty string.
// This is required for versioned transactions
// SimulateTransaction throws if recentBlockhash is an empty string
const { blockhash } = await connection.getLatestBlockhash(commitment);
transaction.message.recentBlockhash = blockhash;
}
Expand All @@ -226,32 +267,29 @@ async function createPriorityFeeInstructions(
: connection.simulateTransaction(transaction));

if (response.value.err) {
if (response.value.err === 'BlockhashNotFound') {
console.info('Blockhash not found during simulation. Trying again.');
sleep(1000);
continue simulationLoop;
}

// In some cases which aren't deterministic, like a slippage error, we can retry the
// simulation a few times to get a successful response.
if (response.value.logs) {
for (const line of response.value.logs) {
if (line.includes('SlippageToleranceExceeded')) {
console.info('Slippage failure during simulation. Trying again.');
sleep(1000);
continue simulationLoop;
}
if (checkKnownSimulationError(response.value)) {
// Number of attempts will be at most 5 for known errors
if (simulationAttempts < 5) {
simulationAttempts++;
await sleep(1000);
continue simulationLoop;
}
} else if (simulationAttempts < 3) {
// Number of attempts will be at most 3 for unknown errors
simulationAttempts++;
await sleep(1000);
continue simulationLoop;
}

// Logs didn't match an error case we would retry; throw
// Still failing after multiple attempts for both known and unknown errors
// We should throw in that case
throw new Error(
`Simulation failed: ${JSON.stringify(response.value.err)}\nLogs:\n${(
response.value.logs || []
).join('\n ')}`,
);
} else {
// Success case
// Simulation was successful
if (response.value.unitsConsumed) {
unitsUsed = response.value.unitsConsumed;
}
Expand Down

0 comments on commit e3d07c3

Please sign in to comment.