Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PKG -- [fcl-core] Add TransactionError type to better expose execution errors #1893

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pink-students-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/typedefs": minor
---

Add missing field to TransactionStatus type
5 changes: 5 additions & 0 deletions .changeset/soft-tomatoes-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/typedefs": minor
---

Add FvmErrorCode enum for categorizing transaction/script execution errors
5 changes: 5 additions & 0 deletions .changeset/witty-pants-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/fcl-core": minor
---

Add custom error `TransactionError` type for failing transaction results
17 changes: 14 additions & 3 deletions packages/fcl-core/src/transaction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}$/

Expand Down Expand Up @@ -149,9 +150,17 @@ 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()
return
} else if (transactionError != null) {
reject(transactionError)
unsub()
return
}
} else if (predicate(txStatus)) {
resolve(txStatus)
unsub()
Expand All @@ -176,3 +185,5 @@ transaction.isFinalized = isFinalized
transaction.isExecuted = isExecuted
transaction.isSealed = isSealed
transaction.isExpired = isExpired

export {TransactionError}
61 changes: 61 additions & 0 deletions packages/fcl-core/src/transaction/transaction-error.test.ts
Original file line number Diff line number Diff line change
@@ -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.CADENCE_RUNTIME_ERROR)
})

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")
})
})
21 changes: 21 additions & 0 deletions packages/fcl-core/src/transaction/transaction-error.ts
Original file line number Diff line number Diff line change
@@ -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], 10) : undefined

return new TransactionError(status.errorMessage || "Unknown error", code)
}
}
32 changes: 32 additions & 0 deletions packages/sdk/src/errors/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {TransactionStatus} from "@onflow/typedefs"

export class TransactionError<T extends number> 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<number> | 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)
}
}
77 changes: 77 additions & 0 deletions packages/typedefs/src/fvm-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export enum FvmErrorCode {
// tx validation errors 1000 - 1049
// Deprecated: no longer in use
TX_VALIDATION_ERROR = 1000,
// Deprecated: No longer used.
INVALID_TX_BYTE_SIZE_ERROR = 1001,
// Deprecated: No longer used.
INVALID_REFERENCE_BLOCK_ERROR = 1002,
// Deprecated: No longer used.
EXPIRED_TRANSACTION_ERROR = 1003,
// Deprecated: No longer used.
INVALID_SCRIPT_ERROR = 1004,
// Deprecated: No longer used.
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.
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.
EXECUTION_ERROR = 1100,
CADENCE_RUNTIME_ERROR = 1101,
// Deprecated: No longer used.
ENCODING_UNSUPPORTED_VALUE = 1102,
STORAGE_CAPACITY_EXCEEDED = 1103,
// Deprecated: No longer used.
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
INSUFFICIENT_PAYER_BALANCE = 1118,

// accounts errors 1200 - 1250
// Deprecated: No longer used.
ACCOUNT_ERROR = 1200,
ACCOUNT_NOT_FOUND_ERROR = 1201,
ACCOUNT_PUBLIC_KEY_NOT_FOUND_ERROR = 1202,
ACCOUNT_ALREADY_EXISTS_ERROR = 1203,
// Deprecated: No longer used.
FROZEN_ACCOUNT_ERROR = 1204,
// Deprecated: No longer used.
ACCOUNT_STORAGE_NOT_INITIALIZED_ERROR = 1205,
ACCOUNT_PUBLIC_KEY_LIMIT_ERROR = 1206,

// contract errors 1250 - 1300
// Deprecated: No longer used.
CONTRACT_ERROR = 1250,
CONTRACT_NOT_FOUND_ERROR = 1251,
// Deprecated: No longer used.
CONTRACT_NAMES_NOT_FOUND_ERROR = 1252,

// fvm std lib errors 1300-1400
EVM_EXECUTION_ERROR = 1300,
}
20 changes: 18 additions & 2 deletions packages/typedefs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -346,6 +350,17 @@ export type TransactionStatus = {
*/
events: Array<Event>
}
/**
* 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.
Expand Down Expand Up @@ -412,3 +427,4 @@ export type EventStream = StreamConnection<{
}>

export * from "./interaction"
export * from "./fvm-errors"
Loading