Skip to content

Commit

Permalink
Throw if the transaction fails when simulated to estimate CUs (#3290)
Browse files Browse the repository at this point in the history
* Return the transaction error when estimating CUs

* Add changeset

* Throw an error when the transaction fails in simulation instead
  • Loading branch information
mcintyre94 authored Oct 2, 2024
1 parent 93b48f7 commit 2368163
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-clocks-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solana/web3.js': patch
---

Throw an error if a transaction fails when being simulated to estimate CUs
2 changes: 2 additions & 0 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export const SOLANA_ERROR__TRANSACTION__ADDRESSES_CANNOT_SIGN_TRANSACTION = 5663
export const SOLANA_ERROR__TRANSACTION__CANNOT_ENCODE_WITH_EMPTY_SIGNATURES = 5663016 as const;
export const SOLANA_ERROR__TRANSACTION__MESSAGE_SIGNATURES_MISMATCH = 5663017 as const;
export const SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT = 5663018 as const;
export const SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT = 5663019 as const;

// Transaction errors.
// Reserve error codes starting with [7050000-7050999] for the Rust enum `TransactionError`.
Expand Down Expand Up @@ -492,6 +493,7 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND
| typeof SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT
| typeof SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT
| typeof SOLANA_ERROR__TRANSACTION__FEE_PAYER_MISSING
| typeof SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING
| typeof SOLANA_ERROR__TRANSACTION__INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_MUST_BE_ADVANCE_NONCE
Expand Down
4 changes: 4 additions & 0 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ import {
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__INVOKED_PROGRAMS_CANNOT_PAY_FEES,
SOLANA_ERROR__TRANSACTION__INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE,
SOLANA_ERROR__TRANSACTION__MESSAGE_SIGNATURES_MISMATCH,
Expand Down Expand Up @@ -570,6 +571,9 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
[SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND]: {
index: number;
};
[SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT]: {
unitsConsumed: number;
};
[SOLANA_ERROR__TRANSACTION__INVOKED_PROGRAMS_CANNOT_PAY_FEES]: {
programAddress: string;
};
Expand Down
6 changes: 6 additions & 0 deletions packages/errors/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ import {
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_FEE_PAYER_MISSING,
SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__FEE_PAYER_MISSING,
SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING,
SOLANA_ERROR__TRANSACTION__INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_MUST_BE_ADVANCE_NONCE,
Expand Down Expand Up @@ -583,6 +584,11 @@ export const SolanaErrorMessages: Readonly<{
'Failed to estimate the compute unit consumption for this transaction message. This is ' +
'likely because simulating the transaction failed. Inspect the `cause` property of this ' +
'error to learn more',
[SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT]:
'Transaction failed when it was simulated in order to estimate the compute unit consumption. ' +
'The compute unit estimate provided is for a transaction that failed when simulated and may not ' +
'be representative of the compute units this transaction would consume if successful. Inspect the ' +
'`cause` property of this error to learn more',
[SOLANA_ERROR__TRANSACTION__FEE_PAYER_MISSING]: 'Transaction is missing a fee payer.',
[SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING]:
"Could not determine this transaction's signature. Make sure that the transaction has " +
Expand Down
20 changes: 18 additions & 2 deletions packages/library/src/__tests__/compute-limit-internal-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Address } from '@solana/addresses';
import {
SOLANA_ERROR__INSTRUCTION_ERROR__INSUFFICIENT_FUNDS,
SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
SolanaError,
} from '@solana/errors';
import { AccountRole } from '@solana/instructions';
import { Rpc, SimulateTransactionApi } from '@solana/rpc';
import { Blockhash } from '@solana/rpc-types';
import { Blockhash, TransactionError } from '@solana/rpc-types';
import { ITransactionMessageWithFeePayer, Nonce, TransactionMessage } from '@solana/transaction-messages';

import { getComputeUnitEstimateForTransactionMessage_INTERNAL_ONLY_DO_NOT_EXPORT } from '../compute-limit-internal';
Expand Down Expand Up @@ -242,7 +243,22 @@ describe('getComputeUnitEstimateForTransactionMessage_INTERNAL_ONLY_DO_NOT_EXPOR
rpc,
transactionMessage: mockTransactionMessage,
});
await expect(estimatePromise).resolves.toBe(1400000 /* MAX_COMPUTE_UNITS */);
await expect(estimatePromise).resolves.toBe(1400000);
});
it('throws with the transaction error as cause when the transaction fails in simulation', async () => {
expect.assertions(1);
const transactionError: TransactionError = 'AccountNotFound';
sendSimulateTransactionRequest.mockResolvedValue({ value: { err: transactionError, unitsConsumed: 42n } });
const estimatePromise = getComputeUnitEstimateForTransactionMessage_INTERNAL_ONLY_DO_NOT_EXPORT({
rpc,
transactionMessage: mockTransactionMessage,
});
await expect(estimatePromise).rejects.toThrow(
new SolanaError(SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT, {
cause: transactionError,
unitsConsumed: 42,
}),
);
});
it('throws with the cause when simulation fails', async () => {
expect.assertions(1);
Expand Down
16 changes: 14 additions & 2 deletions packages/library/src/compute-limit-internal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Address } from '@solana/addresses';
import { getU32Encoder } from '@solana/codecs';
import { SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT, SolanaError } from '@solana/errors';
import {
isSolanaError,
SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT,
SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
SolanaError,
} from '@solana/errors';
import {
IInstruction,
IInstructionWithData,
Expand Down Expand Up @@ -162,7 +167,7 @@ export async function getComputeUnitEstimateForTransactionMessage_INTERNAL_ONLY_
const wireTransactionBytes = getBase64EncodedWireTransaction(compiledTransaction);
try {
const {
value: { unitsConsumed },
value: { err: transactionError, unitsConsumed },
} = await rpc
.simulateTransaction(wireTransactionBytes, {
...simulateConfig,
Expand All @@ -179,8 +184,15 @@ export async function getComputeUnitEstimateForTransactionMessage_INTERNAL_ONLY_
// compute units as a u64, but the `SetComputeLimit` instruction only accepts a u32. Until
// this changes, downcast it.
const downcastUnitsConsumed = unitsConsumed > 4_294_967_295n ? 4_294_967_295 : Number(unitsConsumed);
if (transactionError) {
throw new SolanaError(SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT, {
cause: transactionError,
unitsConsumed: downcastUnitsConsumed,
});
}
return downcastUnitsConsumed;
} catch (e) {
if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT)) throw e;
throw new SolanaError(SOLANA_ERROR__TRANSACTION__FAILED_TO_ESTIMATE_COMPUTE_LIMIT, {
cause: e,
});
Expand Down

0 comments on commit 2368163

Please sign in to comment.