From 6cc1b9293ad11cdfedc0333dc656efd182c2f20a Mon Sep 17 00:00:00 2001 From: pshenmic Date: Wed, 5 Jun 2024 20:04:12 +0700 Subject: [PATCH 1/4] Implement tx status, gas and error in the API --- packages/api/src/dao/IdentitiesDAO.js | 7 ++- packages/api/src/dao/TransactionsDAO.js | 4 +- packages/api/src/models/Transaction.js | 12 +++- .../api/test/integration/identities.spec.js | 20 +++++-- .../api/test/integration/transactions.spec.js | 57 +++++++++++++++++-- 5 files changed, 84 insertions(+), 16 deletions(-) diff --git a/packages/api/src/dao/IdentitiesDAO.js b/packages/api/src/dao/IdentitiesDAO.js index c8aba8c14..a8832d32b 100644 --- a/packages/api/src/dao/IdentitiesDAO.js +++ b/packages/api/src/dao/IdentitiesDAO.js @@ -222,13 +222,16 @@ module.exports = class IdentitiesDAO { const toRank = fromRank + limit - 1 const subquery = this.knex('state_transitions') - .select('state_transitions.id as state_transition_id', 'state_transitions.hash as tx_hash', 'state_transitions.index as index', - 'state_transitions.type as type', 'state_transitions.block_hash as block_hash') + .select('state_transitions.id as state_transition_id', 'state_transitions.hash as tx_hash', + 'state_transitions.index as index', 'state_transitions.type as type', 'state_transitions.block_hash as block_hash', + 'state_transitions.gas_used as gas_used','state_transitions.status as status','state_transitions.error as error', + ) .select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`)) .where('state_transitions.owner', '=', identifier) const rows = await this.knex.with('with_alias', subquery) .select('state_transition_id', 'tx_hash', 'index', 'block_hash', 'type', 'rank', + 'gas_used', 'status', 'gas_used', 'blocks.timestamp as timestamp', 'blocks.height as block_height') .select(this.knex('with_alias').count('*').as('total_count')) .leftJoin('blocks', 'blocks.hash', 'block_hash') diff --git a/packages/api/src/dao/TransactionsDAO.js b/packages/api/src/dao/TransactionsDAO.js index d98cbd68c..c4720827f 100644 --- a/packages/api/src/dao/TransactionsDAO.js +++ b/packages/api/src/dao/TransactionsDAO.js @@ -10,6 +10,7 @@ module.exports = class TransactionsDAO { getTransactionByHash = async (hash) => { const [row] = await this.knex('state_transitions') .select('state_transitions.hash as tx_hash', 'state_transitions.data as data', + 'state_transitions.gas_used as gas_used','state_transitions.status as status','state_transitions.error as error', 'state_transitions.type as type', 'state_transitions.index as index', 'blocks.height as block_height', 'blocks.hash as block_hash', 'blocks.timestamp as timestamp') .where('state_transitions.hash', hash) @@ -29,13 +30,14 @@ module.exports = class TransactionsDAO { const subquery = this.knex('state_transitions') .select(this.knex('state_transitions').count('hash').as('total_count'), 'state_transitions.hash as tx_hash', 'state_transitions.data as data', 'state_transitions.type as type', 'state_transitions.index as index', + 'state_transitions.gas_used as gas_used','state_transitions.status as status','state_transitions.error as error', 'state_transitions.block_hash as block_hash', 'state_transitions.id as id') .select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`)) .as('state_transitions') const rows = await this.knex(subquery) .select('total_count', 'data', 'type', 'index', 'rank', 'block_hash', 'state_transitions.tx_hash as tx_hash', - 'blocks.height as block_height', 'blocks.timestamp as timestamp') + 'gas_used', 'status', 'error', 'blocks.height as block_height', 'blocks.timestamp as timestamp') .leftJoin('blocks', 'blocks.hash', 'block_hash') .whereBetween('rank', [fromRank, toRank]) .orderBy('state_transitions.id', order) diff --git a/packages/api/src/models/Transaction.js b/packages/api/src/models/Transaction.js index 3c2418d23..96ac6e79b 100644 --- a/packages/api/src/models/Transaction.js +++ b/packages/api/src/models/Transaction.js @@ -6,8 +6,11 @@ module.exports = class Transaction { type data timestamp + gasUsed + status + error - constructor (hash, index, blockHash, blockHeight, type, data, timestamp) { + constructor (hash, index, blockHash, blockHeight, type, data, timestamp, gasUsed, status, error) { this.hash = hash ?? null this.index = index ?? null this.blockHash = blockHash ?? null @@ -15,10 +18,13 @@ module.exports = class Transaction { this.type = type ?? null this.data = data ?? null this.timestamp = timestamp ?? null + this.gasUsed = gasUsed ?? null + this.status = status ?? null + this.error = error ?? null } // eslint-disable-next-line camelcase - static fromRow ({ tx_hash, index, block_hash, block_height, type, data, timestamp }) { - return new Transaction(tx_hash, index, block_hash, block_height, type, data, timestamp) + static fromRow ({ tx_hash, index, block_hash, block_height, type, data, timestamp, gas_used, status, error }) { + return new Transaction(tx_hash, index, block_hash, block_height, type, data, timestamp, parseInt(gas_used), status, error) } } diff --git a/packages/api/test/integration/identities.spec.js b/packages/api/test/integration/identities.spec.js index faec7807d..4500c42d4 100644 --- a/packages/api/test/integration/identities.spec.js +++ b/packages/api/test/integration/identities.spec.js @@ -755,7 +755,10 @@ describe('Identities routes', () => { blockHeight: _transaction.block.height, type: i === 0 ? StateTransitionEnum.IDENTITY_CREATE : StateTransitionEnum.DOCUMENTS_BATCH, data: null, - timestamp: _transaction.block.timestamp.toISOString() + timestamp: _transaction.block.timestamp.toISOString(), + gasUsed: _transaction.transaction.gas_used, + status: _transaction.transaction.status, + error: _transaction.transaction.error })) assert.deepEqual(body.resultSet, expectedTransactions) @@ -795,7 +798,10 @@ describe('Identities routes', () => { blockHeight: _transaction.block.height, type: StateTransitionEnum.DOCUMENTS_BATCH, data: null, - timestamp: _transaction.block.timestamp.toISOString() + timestamp: _transaction.block.timestamp.toISOString(), + gasUsed: _transaction.transaction.gas_used, + status: _transaction.transaction.status, + error: _transaction.transaction.error })) assert.deepEqual(body.resultSet, expectedTransactions) @@ -835,7 +841,10 @@ describe('Identities routes', () => { blockHeight: _transaction.block.height, type: StateTransitionEnum.DOCUMENTS_BATCH, data: null, - timestamp: _transaction.block.timestamp.toISOString() + timestamp: _transaction.block.timestamp.toISOString(), + gasUsed: _transaction.transaction.gas_used, + status: _transaction.transaction.status, + error: _transaction.transaction.error })) assert.deepEqual(body.resultSet, expectedTransactions) @@ -875,7 +884,10 @@ describe('Identities routes', () => { blockHeight: _transaction.block.height, type: StateTransitionEnum.DOCUMENTS_BATCH, data: null, - timestamp: _transaction.block.timestamp.toISOString() + timestamp: _transaction.block.timestamp.toISOString(), + gasUsed: _transaction.transaction.gas_used, + status: _transaction.transaction.status, + error: _transaction.transaction.error })) assert.deepEqual(body.resultSet, expectedTransactions) diff --git a/packages/api/test/integration/transactions.spec.js b/packages/api/test/integration/transactions.spec.js index cc58c45d0..2b0d0d140 100644 --- a/packages/api/test/integration/transactions.spec.js +++ b/packages/api/test/integration/transactions.spec.js @@ -31,7 +31,15 @@ describe('Transaction routes', () => { transactions = [{ transaction: identity.transaction, block }] - for (let i = 1; i < 30; i++) { + // error tx + const errorTx = await fixtures.transaction(knex, { + block_hash: block.hash, data: '{}', type: StateTransitionEnum.DOCUMENTS_BATCH, + owner: identity.identifier, + error: 'fake_err', status: 'FAIL' + }) + transactions.push({transaction: errorTx, block}) + + for (let i = 2; i < 30; i++) { const block = await fixtures.block(knex, { height: i + 1, timestamp: new Date(startDate.getTime() + i * 1000 * 60) }) @@ -81,7 +89,32 @@ describe('Transaction routes', () => { hash: transaction.transaction.hash, index: transaction.transaction.index, timestamp: transaction.block.timestamp.toISOString(), - type: transaction.transaction.type + type: transaction.transaction.type, + gasUsed: transaction.transaction.gas_used, + status: transaction.transaction.status, + error: transaction.transaction.error + } + + assert.deepEqual(expectedTransaction, body) + }) + + it('should error transaction', async () => { + const [,transaction] = transactions + const { body } = await client.get(`/transaction/${transaction.transaction.hash}`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + const expectedTransaction = { + blockHash: transaction.block.hash, + blockHeight: transaction.block.height, + data: '{}', + hash: transaction.transaction.hash, + index: transaction.transaction.index, + timestamp: transaction.block.timestamp.toISOString(), + type: transaction.transaction.type, + gasUsed: 0, + status: 'FAIL', + error: 'fake_err' } assert.deepEqual(expectedTransaction, body) @@ -114,7 +147,10 @@ describe('Transaction routes', () => { hash: transaction.transaction.hash, index: transaction.transaction.index, timestamp: transaction.block.timestamp.toISOString(), - type: transaction.transaction.type + type: transaction.transaction.type, + gasUsed: transaction.transaction.gas_used, + status: transaction.transaction.status, + error: transaction.transaction.error })) assert.deepEqual(expectedTransactions, body.resultSet) @@ -140,7 +176,10 @@ describe('Transaction routes', () => { hash: transaction.transaction.hash, index: transaction.transaction.index, timestamp: transaction.block.timestamp.toISOString(), - type: transaction.transaction.type + type: transaction.transaction.type, + gasUsed: transaction.transaction.gas_used, + status: transaction.transaction.status, + error: transaction.transaction.error })) assert.deepEqual(expectedTransactions, body.resultSet) @@ -166,7 +205,10 @@ describe('Transaction routes', () => { hash: transaction.transaction.hash, index: transaction.transaction.index, timestamp: transaction.block.timestamp.toISOString(), - type: transaction.transaction.type + type: transaction.transaction.type, + gasUsed: transaction.transaction.gas_used, + status: transaction.transaction.status, + error: transaction.transaction.error })) assert.deepEqual(expectedTransactions, body.resultSet) @@ -192,7 +234,10 @@ describe('Transaction routes', () => { hash: transaction.transaction.hash, index: transaction.transaction.index, timestamp: transaction.block.timestamp.toISOString(), - type: transaction.transaction.type + type: transaction.transaction.type, + gasUsed: transaction.transaction.gas_used, + status: transaction.transaction.status, + error: transaction.transaction.error })) assert.deepEqual(expectedTransactions, body.resultSet) From f3d700bf7089d883c0e737effe23570a8ae88ee3 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Wed, 5 Jun 2024 20:08:01 +0700 Subject: [PATCH 2/4] Update readme --- packages/api/README.md | 22 ++++++++++++++++++++-- packages/frontend/src/app/api/content.md | 22 ++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/api/README.md b/packages/api/README.md index 623fef4ba..553aa4f39 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -128,6 +128,9 @@ GET /blocks --- ### Transaction by hash Get a transaction (state transition) by hash + +Status can be either `SUCCESS` or `FAIL`. In case of error tx, message will appear in the `error` field as Base64 string + ``` GET /transaction/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF @@ -138,7 +141,10 @@ GET /transaction/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEE hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", index: 0, timestamp: "2024-03-18T10:13:54.150Z", - type: 0 + type: 0, + gasUsed: 1337000, + status: "SUCCESS", + error: null } ``` @@ -151,6 +157,9 @@ Response codes: --- ### Transactions Return transaction set paged + +Status can be either `SUCCESS` or `FAIL`. In case of error tx, message will appear in the `error` field as Base64 string + ``` GET /transactions?=1&limit=10&order=asc @@ -168,7 +177,10 @@ GET /transactions?=1&limit=10&order=asc hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", index: 0, timestamp: "2024-03-18T10:13:54.150Z", - type: 0 + type: 0, + gasUsed: 1337000, + status: "SUCCESS", + error: null }, ... ] } @@ -419,6 +431,9 @@ Response codes: --- ### Transactions by Identity Return all transactions made by the given identity + +Status can be either `SUCCESS` or `FAIL`. In case of error tx, message will appear in the `error` field as Base64 string + ``` GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transactions?page=1&limit=10&order=asc @@ -437,6 +452,9 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transactions?page=1 type: 0, data: null, timestamp: "2024-03-18T10:13:54.150Z", + gasUsed: 1337000, + status: "SUCCESS", + error: null }, ... ] } diff --git a/packages/frontend/src/app/api/content.md b/packages/frontend/src/app/api/content.md index 286fab13d..cffdd14cc 100644 --- a/packages/frontend/src/app/api/content.md +++ b/packages/frontend/src/app/api/content.md @@ -97,6 +97,9 @@ GET /blocks --- ### Transaction by hash Get a transaction (state transition) by hash + +Status can be either `SUCCESS` or `FAIL`. In case of error tx, message will appear in the `error` field as Base64 string + ``` GET /transaction/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF @@ -107,7 +110,10 @@ GET /transaction/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEE hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", index: 0, timestamp: "2024-03-18T10:13:54.150Z", - type: 0 + type: 0, + gasUsed: 1337000, + status: "SUCCESS", + error: null } ``` @@ -120,6 +126,9 @@ Response codes: --- ### Transactions Return transaction set paged + +Status can be either `SUCCESS` or `FAIL`. In case of error tx, message will appear in the `error` field as Base64 string + ``` GET /transactions?=1&limit=10&order=asc @@ -137,7 +146,10 @@ GET /transactions?=1&limit=10&order=asc hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", index: 0, timestamp: "2024-03-18T10:13:54.150Z", - type: 0 + type: 0, + gasUsed: 1337000, + status: "SUCCESS", + error: null }, ... ] } @@ -388,6 +400,9 @@ Response codes: --- ### Transactions by Identity Return all transactions made by the given identity + +Status can be either `SUCCESS` or `FAIL`. In case of error tx, message will appear in the `error` field as Base64 string + ``` GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transactions?page=1&limit=10&order=asc @@ -406,6 +421,9 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transactions?page=1 type: 0, data: null, timestamp: "2024-03-18T10:13:54.150Z", + gasUsed: 1337000, + status: "SUCCESS", + error: null }, ... ] } From f5d635687401ff7edc9cbfa2ea336491343356f7 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Wed, 5 Jun 2024 22:54:33 +0700 Subject: [PATCH 3/4] Lint fix --- packages/api/src/dao/IdentitiesDAO.js | 4 ++-- packages/api/src/dao/TransactionsDAO.js | 4 ++-- packages/api/test/integration/transactions.spec.js | 11 +++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/api/src/dao/IdentitiesDAO.js b/packages/api/src/dao/IdentitiesDAO.js index a8832d32b..0a0cc27ef 100644 --- a/packages/api/src/dao/IdentitiesDAO.js +++ b/packages/api/src/dao/IdentitiesDAO.js @@ -224,8 +224,8 @@ module.exports = class IdentitiesDAO { const subquery = this.knex('state_transitions') .select('state_transitions.id as state_transition_id', 'state_transitions.hash as tx_hash', 'state_transitions.index as index', 'state_transitions.type as type', 'state_transitions.block_hash as block_hash', - 'state_transitions.gas_used as gas_used','state_transitions.status as status','state_transitions.error as error', - ) + 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error' + ) .select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`)) .where('state_transitions.owner', '=', identifier) diff --git a/packages/api/src/dao/TransactionsDAO.js b/packages/api/src/dao/TransactionsDAO.js index c4720827f..c581fe1e1 100644 --- a/packages/api/src/dao/TransactionsDAO.js +++ b/packages/api/src/dao/TransactionsDAO.js @@ -10,7 +10,7 @@ module.exports = class TransactionsDAO { getTransactionByHash = async (hash) => { const [row] = await this.knex('state_transitions') .select('state_transitions.hash as tx_hash', 'state_transitions.data as data', - 'state_transitions.gas_used as gas_used','state_transitions.status as status','state_transitions.error as error', + 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error', 'state_transitions.type as type', 'state_transitions.index as index', 'blocks.height as block_height', 'blocks.hash as block_hash', 'blocks.timestamp as timestamp') .where('state_transitions.hash', hash) @@ -30,7 +30,7 @@ module.exports = class TransactionsDAO { const subquery = this.knex('state_transitions') .select(this.knex('state_transitions').count('hash').as('total_count'), 'state_transitions.hash as tx_hash', 'state_transitions.data as data', 'state_transitions.type as type', 'state_transitions.index as index', - 'state_transitions.gas_used as gas_used','state_transitions.status as status','state_transitions.error as error', + 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error', 'state_transitions.block_hash as block_hash', 'state_transitions.id as id') .select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`)) .as('state_transitions') diff --git a/packages/api/test/integration/transactions.spec.js b/packages/api/test/integration/transactions.spec.js index 2b0d0d140..9dfbe1850 100644 --- a/packages/api/test/integration/transactions.spec.js +++ b/packages/api/test/integration/transactions.spec.js @@ -33,11 +33,14 @@ describe('Transaction routes', () => { // error tx const errorTx = await fixtures.transaction(knex, { - block_hash: block.hash, data: '{}', type: StateTransitionEnum.DOCUMENTS_BATCH, + block_hash: block.hash, + data: '{}', + type: StateTransitionEnum.DOCUMENTS_BATCH, owner: identity.identifier, - error: 'fake_err', status: 'FAIL' + error: 'fake_err', + status: 'FAIL' }) - transactions.push({transaction: errorTx, block}) + transactions.push({ transaction: errorTx, block }) for (let i = 2; i < 30; i++) { const block = await fixtures.block(knex, { @@ -99,7 +102,7 @@ describe('Transaction routes', () => { }) it('should error transaction', async () => { - const [,transaction] = transactions + const [, transaction] = transactions const { body } = await client.get(`/transaction/${transaction.transaction.hash}`) .expect(200) .expect('Content-Type', 'application/json; charset=utf-8') From 4e56c95d193225e1f9dda41bb16a8ff1d9ec7184 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Wed, 5 Jun 2024 23:09:44 +0700 Subject: [PATCH 4/4] Fix main spec --- packages/api/test/integration/main.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/api/test/integration/main.spec.js b/packages/api/test/integration/main.spec.js index fef422be6..31b096241 100644 --- a/packages/api/test/integration/main.spec.js +++ b/packages/api/test/integration/main.spec.js @@ -115,7 +115,10 @@ describe('Other routes', () => { blockHeight: block.height, type: dataContractTransaction.type, data: JSON.stringify(dataContractTransaction.data), - timestamp: block.timestamp.toISOString() + timestamp: block.timestamp.toISOString(), + gasUsed: dataContractTransaction.gas_used, + status: dataContractTransaction.status, + error: dataContractTransaction.error } assert.deepEqual({ transaction: expectedTransaction }, body)