Skip to content

Commit

Permalink
Replace RpcError with a coded exception
Browse files Browse the repository at this point in the history
  • Loading branch information
steveluscher committed Mar 9, 2024
1 parent f886f55 commit 588e110
Show file tree
Hide file tree
Showing 16 changed files with 598 additions and 297 deletions.
104 changes: 104 additions & 0 deletions packages/errors/src/__tests__/json-rpc-error-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR,
SOLANA_ERROR__JSON_RPC__INVALID_PARAMS,
SOLANA_ERROR__JSON_RPC__INVALID_REQUEST,
SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND,
SOLANA_ERROR__JSON_RPC__PARSE_ERROR,
SOLANA_ERROR__JSON_RPC__SCAN_ERROR,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NO_SNAPSHOT,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NODE_UNHEALTHY,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE,
SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION,
SolanaErrorCode,
} from '../codes';
import { SolanaErrorContext } from '../context';
import { SolanaError } from '../error';
import { getSolanaErrorFromJsonRpcError } from '../json-rpc-error';
import { getSolanaErrorFromTransactionError } from '../transaction-error';

jest.mock('../transaction-error.ts');

describe('getSolanaErrorFromJsonRpcError', () => {
it.each([
[SOLANA_ERROR__JSON_RPC__PARSE_ERROR, -32700],
[SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR, -32603],
[SOLANA_ERROR__JSON_RPC__INVALID_PARAMS, -32602],
[SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND, -32601],
[SOLANA_ERROR__JSON_RPC__INVALID_REQUEST, -32600],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, -32016],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, -32015],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET, -32014],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH, -32013],
[SOLANA_ERROR__JSON_RPC__SCAN_ERROR, -32012],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE, -32011],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX, -32010],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED, -32009],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NO_SNAPSHOT, -32008],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED, -32007],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE, -32006],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NODE_UNHEALTHY, -32005],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE, -32004],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE, -32003],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, -32002],
[SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP, -32001],
])(
'produces the `SolanaError` code `%s` given a %s JSON-RPC 2.0 error code',
(expectedSolanaErrorCode, jsonRpcErrorCode) => {
const error = getSolanaErrorFromJsonRpcError({ code: jsonRpcErrorCode, message: 'o no' });
expect(error).toHaveProperty('context.__code', expectedSolanaErrorCode);
},
);
it('produces a `SolanaError` given a JSON-RPC error with data', () => {
const expectedData = Symbol() as unknown as SolanaErrorContext[SolanaErrorCode];
const error = getSolanaErrorFromJsonRpcError({ code: 0, data: expectedData, message: 'o no' });
expect(error).toEqual(new SolanaError(7766768 as SolanaErrorCode, expectedData));
});
it('produces a `SolanaError` with the message as the data given a JSON-RPC error with no data', () => {
const error = getSolanaErrorFromJsonRpcError({ code: 0, data: null, message: 'o no' });
expect(error).toEqual(new SolanaError(7766768 as SolanaErrorCode, { message: 'o no' }));
});
describe('when passed a preflight failure', () => {
it('produces a `SolanaError` with the transaction error as the `cause`', () => {
const mockErrorResult = Symbol() as unknown as SolanaError;
jest.mocked(getSolanaErrorFromTransactionError).mockReturnValue(mockErrorResult);
const error = getSolanaErrorFromJsonRpcError({
code: -32002 /* Corresponds to SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE */,
data: { err: Symbol() },
message: 'o no',
});
expect(error.cause).toBe(mockErrorResult);
});
it('produces a `SolanaError` with the preflight failure data (minus the `err` property) as the context', () => {
const preflightErrorData = { bar: 2, baz: 3, foo: 1 };
const error = getSolanaErrorFromJsonRpcError({
code: -32002 /* Corresponds to SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE */,
data: { ...preflightErrorData, err: Symbol() },
message: 'o no',
});
expect(error.context).toEqual({
__code: SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
...preflightErrorData,
});
});
it('delegates `err` to the transaction error getter', () => {
const transactionError = Symbol();
getSolanaErrorFromJsonRpcError({
code: -32002 /* Corresponds to SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE */,
data: { err: transactionError },
message: 'o no',
});
expect(getSolanaErrorFromTransactionError).toHaveBeenCalledWith(transactionError);
});
});
});
47 changes: 47 additions & 0 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ export const SOLANA_ERROR__MALFORMED_BIGINT_STRING = 7 as const;
export const SOLANA_ERROR__MALFORMED_NUMBER_STRING = 8 as const;
export const SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE = 9 as const;

// JSON-RPC-related errors.
// Reserve error codes in the range [7734000, 7734999]
// Resolve JSON-RPC error codes ([-32768, -32000]) to codes in this range by adding 7766768 to them.
// https://github.com/anza-xyz/agave/blob/master/rpc-client-api/src/custom_error.rs
export const SOLANA_ERROR__JSON_RPC__PARSE_ERROR = 7734068 as const;
export const SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR = 7734165 as const;
export const SOLANA_ERROR__JSON_RPC__INVALID_PARAMS = 7734166 as const;
export const SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND = 7734167 as const;
export const SOLANA_ERROR__JSON_RPC__INVALID_REQUEST = 7734168 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED = 7734752 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION = 7734753 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET = 7734754 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH = 7734755 as const;
export const SOLANA_ERROR__JSON_RPC__SCAN_ERROR = 7734756 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE = 7734757 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX = 7734758 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED = 7734759 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NO_SNAPSHOT = 7734760 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED = 7734761 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE = 7734762 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NODE_UNHEALTHY = 7734763 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE = 7734764 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE = 7734765 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE = 7734766 as const;
export const SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP = 7734767 as const;

// Addresses-related errors.
// Reserve error codes in the range [2800000-2800999].
export const SOLANA_ERROR__ADDRESSES__INVALID_BYTE_LENGTH = 2800000 as const;
Expand Down Expand Up @@ -363,6 +389,27 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__INVARIANT_VIOLATION__SWITCH_MUST_BE_EXHAUSTIVE
| typeof SOLANA_ERROR__INVARIANT_VIOLATION__WEBSOCKET_MESSAGE_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE
| typeof SOLANA_ERROR__INVARIANT_VIOLATION__WEBSOCKET_MESSAGE_ITERATOR_STATE_MISSING
| typeof SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR
| typeof SOLANA_ERROR__JSON_RPC__INVALID_PARAMS
| typeof SOLANA_ERROR__JSON_RPC__INVALID_REQUEST
| typeof SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND
| typeof SOLANA_ERROR__JSON_RPC__PARSE_ERROR
| typeof SOLANA_ERROR__JSON_RPC__SCAN_ERROR
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NO_SNAPSHOT
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_NODE_UNHEALTHY
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE
| typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION
| typeof SOLANA_ERROR__KEYS__INVALID_KEY_PAIR_BYTE_LENGTH
| typeof SOLANA_ERROR__KEYS__INVALID_PRIVATE_KEY_BYTE_LENGTH
| typeof SOLANA_ERROR__KEYS__INVALID_SIGNATURE_BYTE_LENGTH
Expand Down
Loading

0 comments on commit 588e110

Please sign in to comment.