Skip to content

Commit

Permalink
PKG -- [fcl-core] Add TransactionError type to better expose execut…
Browse files Browse the repository at this point in the history
…ion errors (#1893)
  • Loading branch information
jribbink authored Oct 24, 2024
1 parent b84b34e commit b9f4949
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 9 deletions.
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: 1 addition & 4 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,5 @@
"@onflow/util-template": "1.2.2",
"@onflow/util-uid": "1.2.2"
},
"changesets": [
"slow-lies-fix",
"tasty-ducks-mix"
]
"changesets": ["slow-lies-fix", "tasty-ducks-mix"]
}
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
19 changes: 16 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, parseTransactionErrorCode} from "./transaction-error"

const TXID_REGEXP = /^[0-9a-fA-F]{64}$/

Expand Down Expand Up @@ -149,9 +150,19 @@ 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) {
if (error != null) {
reject(error)
unsub()
return
} else if (txStatus.statusCode === 1) {
const transactionError = TransactionError.fromErrorMessage(
txStatus.errorMessage
)
reject(transactionError)
unsub()
return
}
} else if (predicate(txStatus)) {
resolve(txStatus)
unsub()
Expand All @@ -176,3 +187,5 @@ transaction.isFinalized = isFinalized
transaction.isExecuted = isExecuted
transaction.isSealed = isSealed
transaction.isExpired = isExpired

export {TransactionError}
48 changes: 48 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,48 @@
import {FvmErrorCode} from "@onflow/typedefs"
import {TransactionError} from "./transaction-error"

describe("TransactionError", () => {
describe("fromErrorMessage", () => {
test("returns unknown error if no code exists", () => {
const errorMessage = "Transaction rejected by the network"
const error = TransactionError.fromErrorMessage(errorMessage)
expect(error).toBeInstanceOf(TransactionError)
expect(error.code).toEqual(FvmErrorCode.UNKNOWN_ERROR)
expect(error.type).toEqual("UNKNOWN_ERROR")
})

test("parses transaction error with code from status", () => {
const errorMessage = "[Error Code: 1101] Some Cadence Error"
const error = TransactionError.fromErrorMessage(errorMessage)
expect(error).toBeInstanceOf(TransactionError)
expect(error.code).toEqual(FvmErrorCode.CADENCE_RUNTIME_ERROR)
expect(error.type).toEqual("CADENCE_RUNTIME_ERROR")
})

test("uses first instance of error code in message", () => {
const errorMessage =
"[Error Code: 1102] Unsupported value... [Error Code: 1105] Something else to say"
const error = TransactionError.fromErrorMessage(errorMessage)
expect(error).toBeInstanceOf(TransactionError)
expect(error.code).toEqual(FvmErrorCode.ENCODING_UNSUPPORTED_VALUE)
expect(error.type).toEqual("ENCODING_UNSUPPORTED_VALUE")
})

test("allows leading text before error code", () => {
const errorMessage =
"This is a message [Error Code: 1102] Unsupported value"
const error = TransactionError.fromErrorMessage(errorMessage)
expect(error).toBeInstanceOf(TransactionError)
expect(error.code).toEqual(FvmErrorCode.ENCODING_UNSUPPORTED_VALUE)
expect(error.type).toEqual("ENCODING_UNSUPPORTED_VALUE")
})

test("returns unknown error for missing error message", () => {
const errorMessage = ""
const error = TransactionError.fromErrorMessage(errorMessage)
expect(error).toBeInstanceOf(TransactionError)
expect(error.code).toEqual(FvmErrorCode.UNKNOWN_ERROR)
expect(error.type).toEqual("UNKNOWN_ERROR")
})
})
})
24 changes: 24 additions & 0 deletions packages/fcl-core/src/transaction/transaction-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {FvmErrorCode} from "@onflow/typedefs"

const ERROR_CODE_REGEX = /\[Error Code: (\d+)\]/

export class TransactionError extends Error {
public code: FvmErrorCode
public type: string

private constructor(message: string, code: FvmErrorCode) {
super(message)
this.code = code
this.type = FvmErrorCode[code]
}

static fromErrorMessage(errorMessage: string): TransactionError {
const match = errorMessage.match(ERROR_CODE_REGEX)
const code = match ? parseInt(match[1], 10) : undefined

return new TransactionError(
errorMessage,
code || FvmErrorCode.UNKNOWN_ERROR
)
}
}
80 changes: 80 additions & 0 deletions packages/typedefs/src/fvm-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export enum FvmErrorCode {
// We use -1 for unknown error in FCL because FVM defines error codes as uint16
// This means we have no risk of collision with FVM error codes
UNKNOWN_ERROR = -1,
// 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 @@ -347,6 +351,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,
}
/*
* The Provider type describes a Wallet Provider associated with a specific Service.
*/
export type Provider = {
Expand Down Expand Up @@ -443,3 +458,4 @@ export type EventStream = StreamConnection<{
}>

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

0 comments on commit b9f4949

Please sign in to comment.