From 2567952dc5be0f4ae06651198022713e33d12716 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Mon, 3 Jun 2024 17:37:43 -0700 Subject: [PATCH 1/6] initial commit --- packages/sdk/src/errors/errors.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/sdk/src/errors/errors.ts diff --git a/packages/sdk/src/errors/errors.ts b/packages/sdk/src/errors/errors.ts new file mode 100644 index 000000000..6894228c1 --- /dev/null +++ b/packages/sdk/src/errors/errors.ts @@ -0,0 +1,30 @@ +import { TransactionStatus } from "@onflow/typedefs"; + +export class TransactionError extends Error { + constructor( + public message: string, + public code?: T, + public name: string = "TransactionError" + ) { + super(message); + } + + static fromErrorMessage(message: string) { + return new TransactionError(message); + } + + static fromStatus(status: TransactionStatus): TransactionError | null { + if (!status.errorMessage) return null; + return TransactionError.fromErrorMessage(status.errorMessage); + } +} + +export class TransactionRejectedError extends TransactionError<403> { + constructor(message: string) { + super(message, 403, "TransactionRejectedError"); + } + + static fromErrorMessage(message: string) { + return new TransactionRejectedError(message); + } +} \ No newline at end of file From 404f5b4c68672a85cc7b6e9acde0851a6a4c1707 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Wed, 5 Jun 2024 23:51:29 -0700 Subject: [PATCH 2/6] PKG -- [fcl] Use TransactionError type for FVM errors encountered when polling TX status --- .changeset/pink-students-divide.md | 5 ++ .changeset/soft-tomatoes-brake.md | 5 ++ .changeset/witty-pants-argue.md | 5 ++ packages/fcl-core/src/transaction/index.js | 17 +++- .../src/transaction/transaction-error.test.ts | 61 +++++++++++++++ .../src/transaction/transaction-error.ts | 21 +++++ packages/typedefs/src/fvm-errors.ts | 77 +++++++++++++++++++ packages/typedefs/src/index.ts | 20 ++++- 8 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 .changeset/pink-students-divide.md create mode 100644 .changeset/soft-tomatoes-brake.md create mode 100644 .changeset/witty-pants-argue.md create mode 100644 packages/fcl-core/src/transaction/transaction-error.test.ts create mode 100644 packages/fcl-core/src/transaction/transaction-error.ts create mode 100644 packages/typedefs/src/fvm-errors.ts diff --git a/.changeset/pink-students-divide.md b/.changeset/pink-students-divide.md new file mode 100644 index 000000000..4f0849df4 --- /dev/null +++ b/.changeset/pink-students-divide.md @@ -0,0 +1,5 @@ +--- +"@onflow/typedefs": minor +--- + +Add missing field to TransactionStatus type diff --git a/.changeset/soft-tomatoes-brake.md b/.changeset/soft-tomatoes-brake.md new file mode 100644 index 000000000..719088952 --- /dev/null +++ b/.changeset/soft-tomatoes-brake.md @@ -0,0 +1,5 @@ +--- +"@onflow/typedefs": minor +--- + +Add FvmErrorCode enum for categorizing transaction/script execution errors diff --git a/.changeset/witty-pants-argue.md b/.changeset/witty-pants-argue.md new file mode 100644 index 000000000..2e52dd184 --- /dev/null +++ b/.changeset/witty-pants-argue.md @@ -0,0 +1,5 @@ +--- +"@onflow/fcl-core": minor +--- + +Add custom error `TransactionError` type for failing transaction results diff --git a/packages/fcl-core/src/transaction/index.js b/packages/fcl-core/src/transaction/index.js index 3f985269a..df78e1292 100644 --- a/packages/fcl-core/src/transaction/index.js +++ b/packages/fcl-core/src/transaction/index.js @@ -12,6 +12,7 @@ import { import {send as fclSend, decode, getTransactionStatus} from "@onflow/sdk" import {HTTPRequestError} from "@onflow/transport-http" import {grpc} from "@improbable-eng/grpc-web" +import {TransactionError} from "./transaction-error" const TXID_REGEXP = /^[0-9a-fA-F]{64}$/ @@ -149,12 +150,20 @@ export function transaction( const suppress = opts.suppress || false return new Promise((resolve, reject) => { const unsub = subscribe((txStatus, error) => { - if ((error || txStatus.statusCode) && !suppress) { - reject(error || txStatus.errorMessage) - unsub() + if (!suppress) { + const transactionError = new TransactionError.from(txStatus) + if (error != null) { + reject(transactionError) + unsub() + } else if (transactionError != null) { + reject(transactionError) + unsub() + } } else if (predicate(txStatus)) { resolve(txStatus) unsub() + } else { + return } }) }) @@ -176,3 +185,5 @@ transaction.isFinalized = isFinalized transaction.isExecuted = isExecuted transaction.isSealed = isSealed transaction.isExpired = isExpired + +export {TransactionError} diff --git a/packages/fcl-core/src/transaction/transaction-error.test.ts b/packages/fcl-core/src/transaction/transaction-error.test.ts new file mode 100644 index 000000000..0f2c7d8a2 --- /dev/null +++ b/packages/fcl-core/src/transaction/transaction-error.test.ts @@ -0,0 +1,61 @@ +import {FvmErrorCode, TransactionStatus} from "@onflow/typedefs" +import {TransactionError} from "./transaction-error" + +describe("TransactionError", () => { + test("parses transaction error from status", () => { + const status: TransactionStatus = { + blockId: "123", + status: 1, + statusString: "PENDING", + statusCode: 1, + errorMessage: "Transaction rejected by the network", + events: [], + } + const error = TransactionError.from(status) + expect(error).toBeInstanceOf(TransactionError) + expect(error!.message).toEqual("Transaction rejected by the network") + expect(error!.code).toBeUndefined() + }) + + test("parses transaction error with code from status", () => { + const status: TransactionStatus = { + blockId: "123", + status: 1, + statusString: "PENDING", + statusCode: 1, + errorMessage: "[Error Code: 1101] Some Cadence Error", + events: [], + } + const error = TransactionError.from(status) + expect(error).toBeInstanceOf(TransactionError) + expect(error!.message).toEqual("[Error Code: 1101] Some Cadence Error") + expect(error!.code).toEqual(FvmErrorCode.CadenceRunTimeError) + }) + + test("returns null for successful transaction", () => { + const status: TransactionStatus = { + blockId: "123", + status: 1, + statusString: "PENDING", + statusCode: 0, + errorMessage: "", + events: [], + } + const error = TransactionError.from(status) + expect(error).toBeNull() + }) + + test("returns unknown error for missing error message", () => { + const status: TransactionStatus = { + blockId: "123", + status: 1, + statusString: "PENDING", + statusCode: 1, + errorMessage: "", + events: [], + } + const error = TransactionError.from(status) + expect(error).toBeInstanceOf(TransactionError) + expect(error!.message).toEqual("Unknown error") + }) +}) diff --git a/packages/fcl-core/src/transaction/transaction-error.ts b/packages/fcl-core/src/transaction/transaction-error.ts new file mode 100644 index 000000000..2ecf8d221 --- /dev/null +++ b/packages/fcl-core/src/transaction/transaction-error.ts @@ -0,0 +1,21 @@ +import {FvmErrorCode, TransactionStatus} from "@onflow/typedefs" + +const ERROR_CODE_REGEX = /\[Error Code: (\d+)\]/ + +export class TransactionError extends Error { + public code?: FvmErrorCode + + private constructor(message: string, code?: FvmErrorCode) { + super(message) + this.code = code + } + + static from(status: TransactionStatus): TransactionError | null { + if (status.statusCode === 0) return null + + const match = status.errorMessage.match(ERROR_CODE_REGEX) + const code = match ? parseInt(match[1]) : undefined + + return new TransactionError(status.errorMessage || "Unknown error", code) + } +} diff --git a/packages/typedefs/src/fvm-errors.ts b/packages/typedefs/src/fvm-errors.ts new file mode 100644 index 000000000..1134d006b --- /dev/null +++ b/packages/typedefs/src/fvm-errors.ts @@ -0,0 +1,77 @@ +export enum FvmErrorCode { + // tx validation errors 1000 - 1049 + // Deprecated: no longer in use + TxValidationError = 1000, + // Deprecated: No longer used. + InvalidTxByteSizeError = 1001, + // Deprecated: No longer used. + InvalidReferenceBlockError = 1002, + // Deprecated: No longer used. + ExpiredTransactionError = 1003, + // Deprecated: No longer used. + InvalidScriptError = 1004, + // Deprecated: No longer used. + InvalidGasLimitError = 1005, + InvalidProposalSignatureError = 1006, + InvalidProposalSeqNumberError = 1007, + InvalidPayloadSignatureError = 1008, + InvalidEnvelopeSignatureError = 1009, + + // base errors 1050 - 1100 + // Deprecated: No longer used. + FVMInternalError = 1050, + ValueError = 1051, + InvalidArgumentError = 1052, + InvalidAddressError = 1053, + InvalidLocationError = 1054, + AccountAuthorizationError = 1055, + OperationAuthorizationError = 1056, + OperationNotSupportedError = 1057, + BlockHeightOutOfRangeError = 1058, + + // execution errors 1100 - 1200 + // Deprecated: No longer used. + ExecutionError = 1100, + CadenceRunTimeError = 1101, + // Deprecated: No longer used. + EncodingUnsupportedValue = 1102, + StorageCapacityExceeded = 1103, + // Deprecated: No longer used. + GasLimitExceededError = 1104, + EventLimitExceededError = 1105, + LedgerInteractionLimitExceededError = 1106, + StateKeySizeLimitError = 1107, + StateValueSizeLimitError = 1108, + TransactionFeeDeductionFailedError = 1109, + ComputationLimitExceededError = 1110, + MemoryLimitExceededError = 1111, + CouldNotDecodeExecutionParameterFromState = 1112, + ScriptExecutionTimedOutError = 1113, + ScriptExecutionCancelledError = 1114, + EventEncodingError = 1115, + InvalidInternalStateAccessError = 1116, + // 1117 was never deployed and is free to use + InsufficientPayerBalance = 1118, + + // accounts errors 1200 - 1250 + // Deprecated: No longer used. + AccountError = 1200, + AccountNotFoundError = 1201, + AccountPublicKeyNotFoundError = 1202, + AccountAlreadyExistsError = 1203, + // Deprecated: No longer used. + FrozenAccountError = 1204, + // Deprecated: No longer used. + AccountStorageNotInitializedError = 1205, + AccountPublicKeyLimitError = 1206, + + // contract errors 1250 - 1300 + // Deprecated: No longer used. + ContractError = 1250, + ContractNotFoundError = 1251, + // Deprecated: No longer used. + ContractNamesNotFoundError = 1252, + + // fvm std lib errors 1300-1400 + EVMExecutionError = 1300, +} diff --git a/packages/typedefs/src/index.ts b/packages/typedefs/src/index.ts index 097c3e477..c6d0250ba 100644 --- a/packages/typedefs/src/index.ts +++ b/packages/typedefs/src/index.ts @@ -330,13 +330,17 @@ export type TransactionStatus = { */ blockId: string /** - * - The status code of the transaction. + * - The execution status of the transaction */ - status: number + status: TransactionExecutionStatus /** * - The status as as descriptive text (e.g. "FINALIZED"). */ statusString: string + /** + * - The result of the transaction, if executed (i.e. 0 for success, 1 for failure) + */ + statusCode: 0 | 1 /** * - The error message of the transaction. */ @@ -346,6 +350,17 @@ export type TransactionStatus = { */ events: Array } +/** + * The execution status of the transaction. + */ +export enum TransactionExecutionStatus { + UNKNOWN = 0, + PENDING = 1, + FINALIZED = 2, + EXECUTED = 3, + SEALED = 4, + EXPIRED = 5, +} export type Provider = { /** * - Provider name. @@ -412,3 +427,4 @@ export type EventStream = StreamConnection<{ }> export * from "./interaction" +export * from "./fvm-errors" From 226cc4e39ccc26b5321aa9ed0fa07633a87cd652 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Thu, 6 Jun 2024 00:24:09 -0700 Subject: [PATCH 3/6] fix casing --- .../src/transaction/transaction-error.test.ts | 2 +- packages/typedefs/src/fvm-errors.ts | 96 +++++++++---------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/packages/fcl-core/src/transaction/transaction-error.test.ts b/packages/fcl-core/src/transaction/transaction-error.test.ts index 0f2c7d8a2..59a436b5c 100644 --- a/packages/fcl-core/src/transaction/transaction-error.test.ts +++ b/packages/fcl-core/src/transaction/transaction-error.test.ts @@ -29,7 +29,7 @@ describe("TransactionError", () => { const error = TransactionError.from(status) expect(error).toBeInstanceOf(TransactionError) expect(error!.message).toEqual("[Error Code: 1101] Some Cadence Error") - expect(error!.code).toEqual(FvmErrorCode.CadenceRunTimeError) + expect(error!.code).toEqual(FvmErrorCode.CADENCE_RUNTIME_ERROR) }) test("returns null for successful transaction", () => { diff --git a/packages/typedefs/src/fvm-errors.ts b/packages/typedefs/src/fvm-errors.ts index 1134d006b..1b063911c 100644 --- a/packages/typedefs/src/fvm-errors.ts +++ b/packages/typedefs/src/fvm-errors.ts @@ -1,77 +1,77 @@ export enum FvmErrorCode { // tx validation errors 1000 - 1049 // Deprecated: no longer in use - TxValidationError = 1000, + TX_VALIDATION_ERROR = 1000, // Deprecated: No longer used. - InvalidTxByteSizeError = 1001, + INVALID_TX_BYTE_SIZE_ERROR = 1001, // Deprecated: No longer used. - InvalidReferenceBlockError = 1002, + INVALID_REFERENCE_BLOCK_ERROR = 1002, // Deprecated: No longer used. - ExpiredTransactionError = 1003, + EXPIRED_TRANSACTION_ERROR = 1003, // Deprecated: No longer used. - InvalidScriptError = 1004, + INVALID_SCRIPT_ERROR = 1004, // Deprecated: No longer used. - InvalidGasLimitError = 1005, - InvalidProposalSignatureError = 1006, - InvalidProposalSeqNumberError = 1007, - InvalidPayloadSignatureError = 1008, - InvalidEnvelopeSignatureError = 1009, + INVALID_GAS_LIMIT_ERROR = 1005, + INVALID_PROPOSAL_SIGNATURE_ERROR = 1006, + INVALID_PROPOSAL_SEQ_NUMBER_ERROR = 1007, + INVALID_PAYLOAD_SIGNATURE_ERROR = 1008, + INVALID_ENVELOPE_SIGNATURE_ERROR = 1009, // base errors 1050 - 1100 // Deprecated: No longer used. - FVMInternalError = 1050, - ValueError = 1051, - InvalidArgumentError = 1052, - InvalidAddressError = 1053, - InvalidLocationError = 1054, - AccountAuthorizationError = 1055, - OperationAuthorizationError = 1056, - OperationNotSupportedError = 1057, - BlockHeightOutOfRangeError = 1058, + FVM_INTERNAL_ERROR = 1050, + VALUE_ERROR = 1051, + INVALID_ARGUMENT_ERROR = 1052, + INVALID_ADDRESS_ERROR = 1053, + INVALID_LOCATION_ERROR = 1054, + ACCOUNT_AUTHORIZATION_ERROR = 1055, + OPERATION_AUTHORIZATION_ERROR = 1056, + OPERATION_NOT_SUPPORTED_ERROR = 1057, + BLOCK_HEIGHT_OUT_OF_RANGE_ERROR = 1058, // execution errors 1100 - 1200 // Deprecated: No longer used. - ExecutionError = 1100, - CadenceRunTimeError = 1101, + EXECUTION_ERROR = 1100, + CADENCE_RUNTIME_ERROR = 1101, // Deprecated: No longer used. - EncodingUnsupportedValue = 1102, - StorageCapacityExceeded = 1103, + ENCODING_UNSUPPORTED_VALUE = 1102, + STORAGE_CAPACITY_EXCEEDED = 1103, // Deprecated: No longer used. - GasLimitExceededError = 1104, - EventLimitExceededError = 1105, - LedgerInteractionLimitExceededError = 1106, - StateKeySizeLimitError = 1107, - StateValueSizeLimitError = 1108, - TransactionFeeDeductionFailedError = 1109, - ComputationLimitExceededError = 1110, - MemoryLimitExceededError = 1111, - CouldNotDecodeExecutionParameterFromState = 1112, - ScriptExecutionTimedOutError = 1113, - ScriptExecutionCancelledError = 1114, - EventEncodingError = 1115, - InvalidInternalStateAccessError = 1116, + GAS_LIMIT_EXCEEDED_ERROR = 1104, + EVENT_LIMIT_EXCEEDED_ERROR = 1105, + LEDGER_INTERACTION_LIMIT_EXCEEDED_ERROR = 1106, + STATE_KEY_SIZE_LIMIT_ERROR = 1107, + STATE_VALUE_SIZE_LIMIT_ERROR = 1108, + TRANSACTION_FEE_DEDUCTION_FAILED_ERROR = 1109, + COMPUTATION_LIMIT_EXCEEDED_ERROR = 1110, + MEMORY_LIMIT_EXCEEDED_ERROR = 1111, + COULD_NOT_DECODE_EXECUTION_PARAMETER_FROM_STATE = 1112, + SCRIPT_EXECUTION_TIMED_OUT_ERROR = 1113, + SCRIPT_EXECUTION_CANCELLED_ERROR = 1114, + EVENT_ENCODING_ERROR = 1115, + INVALID_INTERNAL_STATE_ACCESS_ERROR = 1116, // 1117 was never deployed and is free to use - InsufficientPayerBalance = 1118, + INSUFFICIENT_PAYER_BALANCE = 1118, // accounts errors 1200 - 1250 // Deprecated: No longer used. - AccountError = 1200, - AccountNotFoundError = 1201, - AccountPublicKeyNotFoundError = 1202, - AccountAlreadyExistsError = 1203, + ACCOUNT_ERROR = 1200, + ACCOUNT_NOT_FOUND_ERROR = 1201, + ACCOUNT_PUBLIC_KEY_NOT_FOUND_ERROR = 1202, + ACCOUNT_ALREADY_EXISTS_ERROR = 1203, // Deprecated: No longer used. - FrozenAccountError = 1204, + FROZEN_ACCOUNT_ERROR = 1204, // Deprecated: No longer used. - AccountStorageNotInitializedError = 1205, - AccountPublicKeyLimitError = 1206, + ACCOUNT_STORAGE_NOT_INITIALIZED_ERROR = 1205, + ACCOUNT_PUBLIC_KEY_LIMIT_ERROR = 1206, // contract errors 1250 - 1300 // Deprecated: No longer used. - ContractError = 1250, - ContractNotFoundError = 1251, + CONTRACT_ERROR = 1250, + CONTRACT_NOT_FOUND_ERROR = 1251, // Deprecated: No longer used. - ContractNamesNotFoundError = 1252, + CONTRACT_NAMES_NOT_FOUND_ERROR = 1252, // fvm std lib errors 1300-1400 - EVMExecutionError = 1300, + EVM_EXECUTION_ERROR = 1300, } From d098479866ae44d519c0c0e37a66ff4e3825c73b Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Thu, 6 Jun 2024 00:33:18 -0700 Subject: [PATCH 4/6] prettier --- packages/sdk/src/errors/errors.ts | 46 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/sdk/src/errors/errors.ts b/packages/sdk/src/errors/errors.ts index 6894228c1..776095ce7 100644 --- a/packages/sdk/src/errors/errors.ts +++ b/packages/sdk/src/errors/errors.ts @@ -1,30 +1,32 @@ -import { TransactionStatus } from "@onflow/typedefs"; +import {TransactionStatus} from "@onflow/typedefs" export class TransactionError extends Error { - constructor( - public message: string, - public code?: T, - public name: string = "TransactionError" - ) { - super(message); - } + constructor( + public message: string, + public code?: T, + public name: string = "TransactionError" + ) { + super(message) + } - static fromErrorMessage(message: string) { - return new TransactionError(message); - } + static fromErrorMessage(message: string) { + return new TransactionError(message) + } - static fromStatus(status: TransactionStatus): TransactionError | null { - if (!status.errorMessage) return null; - return TransactionError.fromErrorMessage(status.errorMessage); - } + static fromStatus( + status: TransactionStatus + ): TransactionError | null { + if (!status.errorMessage) return null + return TransactionError.fromErrorMessage(status.errorMessage) + } } export class TransactionRejectedError extends TransactionError<403> { - constructor(message: string) { - super(message, 403, "TransactionRejectedError"); - } + constructor(message: string) { + super(message, 403, "TransactionRejectedError") + } - static fromErrorMessage(message: string) { - return new TransactionRejectedError(message); - } -} \ No newline at end of file + static fromErrorMessage(message: string) { + return new TransactionRejectedError(message) + } +} From c9e9af514182641c729e8de1b3e0f9e1200ef48b Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Thu, 6 Jun 2024 13:42:56 -0700 Subject: [PATCH 5/6] fix conditional --- packages/fcl-core/src/transaction/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fcl-core/src/transaction/index.js b/packages/fcl-core/src/transaction/index.js index df78e1292..ee837632c 100644 --- a/packages/fcl-core/src/transaction/index.js +++ b/packages/fcl-core/src/transaction/index.js @@ -155,15 +155,15 @@ export function transaction( if (error != null) { reject(transactionError) unsub() + return } else if (transactionError != null) { reject(transactionError) unsub() + return } } else if (predicate(txStatus)) { resolve(txStatus) unsub() - } else { - return } }) }) From 06ddbbad493790afba8c55c7fb5fc144394b4480 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 14 Jun 2024 11:05:24 -0700 Subject: [PATCH 6/6] Update packages/fcl-core/src/transaction/transaction-error.ts Co-authored-by: Alex <12097569+nialexsan@users.noreply.github.com> --- packages/fcl-core/src/transaction/transaction-error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fcl-core/src/transaction/transaction-error.ts b/packages/fcl-core/src/transaction/transaction-error.ts index 2ecf8d221..231fb4fe8 100644 --- a/packages/fcl-core/src/transaction/transaction-error.ts +++ b/packages/fcl-core/src/transaction/transaction-error.ts @@ -14,7 +14,7 @@ export class TransactionError extends Error { if (status.statusCode === 0) return null const match = status.errorMessage.match(ERROR_CODE_REGEX) - const code = match ? parseInt(match[1]) : undefined + const code = match ? parseInt(match[1], 10) : undefined return new TransactionError(status.errorMessage || "Unknown error", code) }