mirror of
https://github.com/wharfkit/antelope.git
synced 2026-07-21 17:43:35 +00:00
Standardizing error handling in send_transaction2 (#123)
* Standardizing error handling in send_transaction2 * v1.2.0-rc2 * Update package.json
This commit is contained in:
+3
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wharfkit/antelope",
|
||||
"description": "Library for working with Antelope powered blockchains.",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"homepage": "https://github.com/wharfkit/antelope",
|
||||
"license": "BSD-3-Clause-No-Military-License",
|
||||
"main": "lib/antelope.js",
|
||||
@@ -58,5 +58,6 @@
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typedoc": "^0.23.10",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
+36
-2
@@ -1,4 +1,5 @@
|
||||
import {APIClient} from '../client'
|
||||
import {APIClient, APIError} from '../client'
|
||||
import {APIResponse} from '../provider'
|
||||
|
||||
import {
|
||||
BlockIdType,
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
SendTransaction2Options,
|
||||
SendTransaction2Response,
|
||||
SendTransactionResponse,
|
||||
SendTransactionResponseExceptionStack,
|
||||
TableIndexType,
|
||||
TableIndexTypes,
|
||||
} from './types'
|
||||
@@ -53,6 +55,14 @@ import {
|
||||
import {ABISerializableConstructor, ABISerializableType, Serializer} from '../../serializer'
|
||||
import {isInstanceOf} from '../../utils'
|
||||
|
||||
function resolveStackFormat(entry: SendTransactionResponseExceptionStack): string {
|
||||
if (!entry.format) return ''
|
||||
if (!entry.data) return entry.format
|
||||
return entry.format.replace(/\$\{(\w+)\}/g, (_, key) =>
|
||||
entry.data[key] !== undefined ? String(entry.data[key]) : `\${${key}}`
|
||||
)
|
||||
}
|
||||
|
||||
export class ChainAPI {
|
||||
constructor(private client: APIClient) {}
|
||||
|
||||
@@ -227,7 +237,7 @@ export class ChainAPI {
|
||||
if (!isInstanceOf(tx, PackedTransaction)) {
|
||||
tx = PackedTransaction.fromSigned(SignedTransaction.from(tx))
|
||||
}
|
||||
return this.client.call<SendTransaction2Response>({
|
||||
const response = await this.client.call<SendTransaction2Response>({
|
||||
path: '/v1/chain/send_transaction2',
|
||||
params: {
|
||||
return_failure_trace: true,
|
||||
@@ -237,6 +247,30 @@ export class ChainAPI {
|
||||
...options,
|
||||
},
|
||||
})
|
||||
if (response && response.processed && response.processed.except) {
|
||||
const except = response.processed.except
|
||||
const synthesized: APIResponse = {
|
||||
status: 202,
|
||||
headers: {},
|
||||
text: JSON.stringify(response),
|
||||
json: {
|
||||
...response,
|
||||
error: {
|
||||
code: except.code || 0,
|
||||
name: except.name || 'transaction_exception',
|
||||
what: except.message || 'Transaction failed',
|
||||
details: (except.stack || []).map((s) => ({
|
||||
message: resolveStackFormat(s) || '',
|
||||
file: (s.context && s.context.file) || '',
|
||||
line_number: (s.context && s.context.line) || 0,
|
||||
method: (s.context && s.context.method) || '',
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
throw new APIError('/v1/chain/send_transaction2', synthesized)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async get_table_rows<Index extends TableIndexType = Name>(
|
||||
|
||||
+3
-1
@@ -476,8 +476,10 @@ export interface SendTransaction2Response {
|
||||
id: string
|
||||
block_num: number
|
||||
block_time: string
|
||||
receipt: {status: string; cpu_usage_us: number; net_usage_words: number}
|
||||
receipt: {status: string; cpu_usage_us: number; net_usage_words: number} | null
|
||||
elapsed: number
|
||||
except?: SendTransactionResponseException
|
||||
error_code?: string
|
||||
net_usage: number
|
||||
scheduled: boolean
|
||||
action_traces: any[]
|
||||
|
||||
+35
@@ -618,6 +618,41 @@ suite('api v1', function () {
|
||||
assert.equal(result.transaction_id, transaction.id.hexString)
|
||||
})
|
||||
|
||||
test('chain send_transaction2 (failure detection)', async function () {
|
||||
const provider = jungle4.provider as MockProvider
|
||||
provider.setContext(this.test!.title)
|
||||
const info = await jungle4.v1.chain.get_info()
|
||||
const header = info.getTransactionHeader(90)
|
||||
const transfer = Transfer.from({
|
||||
from: 'corecorecore',
|
||||
to: 'nonexistent1',
|
||||
quantity: '0.0001 EOS',
|
||||
memo: 'this should fail',
|
||||
})
|
||||
const action = Action.from({
|
||||
authorization: [{actor: 'corecorecore', permission: 'active'}],
|
||||
account: 'eosio.token',
|
||||
name: 'transfer',
|
||||
data: transfer,
|
||||
})
|
||||
const transaction = Transaction.from({...header, actions: [action]})
|
||||
const signedTransaction = await signMockTransaction(transaction, info)
|
||||
try {
|
||||
await jungle4.v1.chain.send_transaction2(signedTransaction)
|
||||
assert.fail('should have thrown')
|
||||
} catch (error) {
|
||||
assert.equal(error instanceof APIError, true)
|
||||
const apiError = error as APIError
|
||||
assert.equal(apiError.name, 'eosio_assert_message_exception')
|
||||
assert.equal(apiError.code, 3050003)
|
||||
assert.isAbove(apiError.details.length, 0)
|
||||
assert.isDefined(apiError.response.json.processed)
|
||||
assert.isDefined(apiError.response.json.processed.except)
|
||||
} finally {
|
||||
provider.setContext('')
|
||||
}
|
||||
})
|
||||
|
||||
test('chain get_table_rows (untyped)', async function () {
|
||||
const res = await eos.v1.chain.get_table_rows({
|
||||
code: 'eosio.token',
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"api": "https://jungle4.api.eosnation.io",
|
||||
"headers": {
|
||||
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept",
|
||||
"access-control-allow-methods": "GET, POST, HEAD, OPTIONS",
|
||||
"access-control-allow-origin": "*",
|
||||
"connection": "close",
|
||||
"content-length": "966",
|
||||
"content-type": "application/json",
|
||||
"date": "Fri, 20 Feb 2026 18:42:11 GMT",
|
||||
"server": "nginx/1.28.2"
|
||||
},
|
||||
"status": 200,
|
||||
"json": {
|
||||
"server_version": "42b514a1",
|
||||
"chain_id": "73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d",
|
||||
"head_block_num": 251509646,
|
||||
"last_irreversible_block_num": 251509644,
|
||||
"last_irreversible_block_id": "0efdbb8c537db74dea9eb0ba6bceafc4f0f1879b2d462aeda58619d54e2ce8da",
|
||||
"head_block_id": "0efdbb8efd8f422850484a14cbf4cfb90e37e99a10e4aa3fa7f010721432cdfc",
|
||||
"head_block_time": "2026-02-20T18:42:11.500",
|
||||
"head_block_producer": "gorillapower",
|
||||
"virtual_block_cpu_limit": 200000000,
|
||||
"virtual_block_net_limit": 1048576000,
|
||||
"block_cpu_limit": 200000,
|
||||
"block_net_limit": 1048576,
|
||||
"server_version_string": "v1.2.1",
|
||||
"fork_db_head_block_num": 251509646,
|
||||
"fork_db_head_block_id": "0efdbb8efd8f422850484a14cbf4cfb90e37e99a10e4aa3fa7f010721432cdfc",
|
||||
"server_full_version_string": "v1.2.1-42b514a1566f46a3c652e5025911b295214d3ce4-dirty",
|
||||
"total_cpu_weight": "120571940060499",
|
||||
"total_net_weight": "117540133617123",
|
||||
"earliest_available_block_num": 1,
|
||||
"last_irreversible_block_time": "2026-02-20T18:42:10.500"
|
||||
},
|
||||
"text": "{\"server_version\":\"42b514a1\",\"chain_id\":\"73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d\",\"head_block_num\":251509646,\"last_irreversible_block_num\":251509644,\"last_irreversible_block_id\":\"0efdbb8c537db74dea9eb0ba6bceafc4f0f1879b2d462aeda58619d54e2ce8da\",\"head_block_id\":\"0efdbb8efd8f422850484a14cbf4cfb90e37e99a10e4aa3fa7f010721432cdfc\",\"head_block_time\":\"2026-02-20T18:42:11.500\",\"head_block_producer\":\"gorillapower\",\"virtual_block_cpu_limit\":200000000,\"virtual_block_net_limit\":1048576000,\"block_cpu_limit\":200000,\"block_net_limit\":1048576,\"server_version_string\":\"v1.2.1\",\"fork_db_head_block_num\":251509646,\"fork_db_head_block_id\":\"0efdbb8efd8f422850484a14cbf4cfb90e37e99a10e4aa3fa7f010721432cdfc\",\"server_full_version_string\":\"v1.2.1-42b514a1566f46a3c652e5025911b295214d3ce4-dirty\",\"total_cpu_weight\":\"120571940060499\",\"total_net_weight\":\"117540133617123\",\"earliest_available_block_num\":1,\"last_irreversible_block_time\":\"2026-02-20T18:42:10.500\"}"
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"api": "https://jungle4.api.eosnation.io",
|
||||
"headers": {
|
||||
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept",
|
||||
"access-control-allow-methods": "GET, POST, HEAD, OPTIONS",
|
||||
"access-control-allow-origin": "*",
|
||||
"connection": "close",
|
||||
"content-length": "2586",
|
||||
"content-type": "application/json",
|
||||
"date": "Fri, 20 Feb 2026 18:42:11 GMT",
|
||||
"server": "nginx/1.28.2"
|
||||
},
|
||||
"status": 202,
|
||||
"json": {
|
||||
"transaction_id": "21a37ffb8f996491a77953426e8da621b8b01c06cce5a80bbe8464408bae0185",
|
||||
"processed": {
|
||||
"id": "21a37ffb8f996491a77953426e8da621b8b01c06cce5a80bbe8464408bae0185",
|
||||
"block_num": 251509647,
|
||||
"block_time": "2026-02-20T18:42:12.000",
|
||||
"producer_block_id": null,
|
||||
"receipt": null,
|
||||
"elapsed": 214,
|
||||
"net_usage": 135,
|
||||
"scheduled": false,
|
||||
"action_traces": [
|
||||
{
|
||||
"action_ordinal": 1,
|
||||
"creator_action_ordinal": 0,
|
||||
"closest_unnotified_ancestor_action_ordinal": 0,
|
||||
"receipt": null,
|
||||
"receiver": "eosio.token",
|
||||
"act": {
|
||||
"account": "eosio.token",
|
||||
"name": "transfer",
|
||||
"authorization": [
|
||||
{
|
||||
"actor": "corecorecore",
|
||||
"permission": "active"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"from": "corecorecore",
|
||||
"to": "nonexistent1",
|
||||
"quantity": "0.0001 EOS",
|
||||
"memo": "this should fail"
|
||||
},
|
||||
"hex_data": "a02e45ea52a42e4510f25419bbae269d010000000000000004454f530000000010746869732073686f756c64206661696c"
|
||||
},
|
||||
"context_free": false,
|
||||
"elapsed": 74,
|
||||
"console": "",
|
||||
"trx_id": "21a37ffb8f996491a77953426e8da621b8b01c06cce5a80bbe8464408bae0185",
|
||||
"block_num": 251509647,
|
||||
"block_time": "2026-02-20T18:42:12.000",
|
||||
"producer_block_id": null,
|
||||
"account_ram_deltas": [],
|
||||
"except": {
|
||||
"code": 3050003,
|
||||
"name": "eosio_assert_message_exception",
|
||||
"message": "eosio_assert_message assertion failure",
|
||||
"stack": [
|
||||
{
|
||||
"context": {
|
||||
"level": "error",
|
||||
"file": "cf_system.cpp",
|
||||
"line": 14,
|
||||
"method": "eosio_assert",
|
||||
"hostname": "",
|
||||
"thread_name": "nodeos",
|
||||
"timestamp": "2026-02-20T18:42:11.892"
|
||||
},
|
||||
"format": "assertion failure with message: ${s}",
|
||||
"data": {
|
||||
"s": "to account does not exist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": {
|
||||
"level": "warn",
|
||||
"file": "apply_context.cpp",
|
||||
"line": 134,
|
||||
"method": "exec_one",
|
||||
"hostname": "",
|
||||
"thread_name": "nodeos",
|
||||
"timestamp": "2026-02-20T18:42:11.892"
|
||||
},
|
||||
"format": "${receiver} <= ${account}::${action} pending console output: ${console}",
|
||||
"data": {
|
||||
"console": "",
|
||||
"account": "eosio.token",
|
||||
"action": "transfer",
|
||||
"receiver": "eosio.token"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"error_code": "10000000000000000000",
|
||||
"return_value_hex_data": ""
|
||||
}
|
||||
],
|
||||
"account_ram_delta": null,
|
||||
"except": {
|
||||
"code": 3050003,
|
||||
"name": "eosio_assert_message_exception",
|
||||
"message": "eosio_assert_message assertion failure",
|
||||
"stack": [
|
||||
{
|
||||
"context": {
|
||||
"level": "error",
|
||||
"file": "cf_system.cpp",
|
||||
"line": 14,
|
||||
"method": "eosio_assert",
|
||||
"hostname": "",
|
||||
"thread_name": "nodeos",
|
||||
"timestamp": "2026-02-20T18:42:11.892"
|
||||
},
|
||||
"format": "assertion failure with message: ${s}",
|
||||
"data": {
|
||||
"s": "to account does not exist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": {
|
||||
"level": "warn",
|
||||
"file": "apply_context.cpp",
|
||||
"line": 134,
|
||||
"method": "exec_one",
|
||||
"hostname": "",
|
||||
"thread_name": "nodeos",
|
||||
"timestamp": "2026-02-20T18:42:11.892"
|
||||
},
|
||||
"format": "${receiver} <= ${account}::${action} pending console output: ${console}",
|
||||
"data": {
|
||||
"console": "",
|
||||
"account": "eosio.token",
|
||||
"action": "transfer",
|
||||
"receiver": "eosio.token"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"error_code": "10000000000000000000"
|
||||
}
|
||||
},
|
||||
"text": "{\"transaction_id\":\"21a37ffb8f996491a77953426e8da621b8b01c06cce5a80bbe8464408bae0185\",\"processed\":{\"id\":\"21a37ffb8f996491a77953426e8da621b8b01c06cce5a80bbe8464408bae0185\",\"block_num\":251509647,\"block_time\":\"2026-02-20T18:42:12.000\",\"producer_block_id\":null,\"receipt\":null,\"elapsed\":214,\"net_usage\":135,\"scheduled\":false,\"action_traces\":[{\"action_ordinal\":1,\"creator_action_ordinal\":0,\"closest_unnotified_ancestor_action_ordinal\":0,\"receipt\":null,\"receiver\":\"eosio.token\",\"act\":{\"account\":\"eosio.token\",\"name\":\"transfer\",\"authorization\":[{\"actor\":\"corecorecore\",\"permission\":\"active\"}],\"data\":{\"from\":\"corecorecore\",\"to\":\"nonexistent1\",\"quantity\":\"0.0001 EOS\",\"memo\":\"this should fail\"},\"hex_data\":\"a02e45ea52a42e4510f25419bbae269d010000000000000004454f530000000010746869732073686f756c64206661696c\"},\"context_free\":false,\"elapsed\":74,\"console\":\"\",\"trx_id\":\"21a37ffb8f996491a77953426e8da621b8b01c06cce5a80bbe8464408bae0185\",\"block_num\":251509647,\"block_time\":\"2026-02-20T18:42:12.000\",\"producer_block_id\":null,\"account_ram_deltas\":[],\"except\":{\"code\":3050003,\"name\":\"eosio_assert_message_exception\",\"message\":\"eosio_assert_message assertion failure\",\"stack\":[{\"context\":{\"level\":\"error\",\"file\":\"cf_system.cpp\",\"line\":14,\"method\":\"eosio_assert\",\"hostname\":\"\",\"thread_name\":\"nodeos\",\"timestamp\":\"2026-02-20T18:42:11.892\"},\"format\":\"assertion failure with message: ${s}\",\"data\":{\"s\":\"to account does not exist\"}},{\"context\":{\"level\":\"warn\",\"file\":\"apply_context.cpp\",\"line\":134,\"method\":\"exec_one\",\"hostname\":\"\",\"thread_name\":\"nodeos\",\"timestamp\":\"2026-02-20T18:42:11.892\"},\"format\":\"${receiver} <= ${account}::${action} pending console output: ${console}\",\"data\":{\"console\":\"\",\"account\":\"eosio.token\",\"action\":\"transfer\",\"receiver\":\"eosio.token\"}}]},\"error_code\":\"10000000000000000000\",\"return_value_hex_data\":\"\"}],\"account_ram_delta\":null,\"except\":{\"code\":3050003,\"name\":\"eosio_assert_message_exception\",\"message\":\"eosio_assert_message assertion failure\",\"stack\":[{\"context\":{\"level\":\"error\",\"file\":\"cf_system.cpp\",\"line\":14,\"method\":\"eosio_assert\",\"hostname\":\"\",\"thread_name\":\"nodeos\",\"timestamp\":\"2026-02-20T18:42:11.892\"},\"format\":\"assertion failure with message: ${s}\",\"data\":{\"s\":\"to account does not exist\"}},{\"context\":{\"level\":\"warn\",\"file\":\"apply_context.cpp\",\"line\":134,\"method\":\"exec_one\",\"hostname\":\"\",\"thread_name\":\"nodeos\",\"timestamp\":\"2026-02-20T18:42:11.892\"},\"format\":\"${receiver} <= ${account}::${action} pending console output: ${console}\",\"data\":{\"console\":\"\",\"account\":\"eosio.token\",\"action\":\"transfer\",\"receiver\":\"eosio.token\"}}]},\"error_code\":\"10000000000000000000\"}}"
|
||||
}
|
||||
@@ -10,12 +10,17 @@ import {APIMethods, APIProvider, Bytes, Checksum160, FetchProvider} from '$lib'
|
||||
|
||||
export class MockProvider implements APIProvider {
|
||||
recordProvider = new FetchProvider(this.api, {fetch, headers: this.reqHeaders})
|
||||
private context: string = ''
|
||||
|
||||
constructor(private api: string = 'https://jungle4.greymass.com', private reqHeaders = {}) {}
|
||||
|
||||
setContext(name: string) {
|
||||
this.context = name
|
||||
}
|
||||
|
||||
getFilename(path: string, params?: unknown) {
|
||||
const digest = Checksum160.hash(
|
||||
Bytes.from(this.api + path + (params ? JSON.stringify(params) : ''), 'utf8')
|
||||
Bytes.from(this.api + path + this.context + (params ? JSON.stringify(params) : ''), 'utf8')
|
||||
).hexString
|
||||
return joinPath(__dirname, '../data', digest + '.json')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user