From b25938df418403fffce08e80626cdc974c03f151 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 10 Sep 2024 21:17:53 -0400 Subject: [PATCH 01/20] Reducing cache-control for scheduled transactions. Signed-off-by: mgoelswirlds --- .../scheduled-transactions/responseHeaders.json | 3 +++ hedera-mirror-rest/middleware/responseHandler.js | 6 ++++-- hedera-mirror-rest/transactions.js | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json new file mode 100644 index 00000000000..9e34e9f0ca2 --- /dev/null +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json @@ -0,0 +1,3 @@ +{ + "cache-control": "public, max-age=5" +} diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index 9fab2a88205..fcb2ea9bc52 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -37,8 +37,10 @@ const responseHandler = async (req, res, next) => { throw new NotFoundError(); } else { const path = res.locals[requestPathLabel] ?? req.route.path; - res.set(headers.default); - res.set(headers.path[path]); + if (!res.get('cache-control')) { + res.set(headers.default); + res.set(headers.path[path]); + } const code = res.locals.statusCode; const contentType = res.locals[responseContentType] || APPLICATION_JSON; diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index a0d5f83dc61..908e5d7d443 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -48,6 +48,9 @@ const { const cache = new Cache(); +const scheduleCreate = TransactionType.getName(42); +const CACHE_CONTROL_HEADER_NAME = 'cache-control'; + const transactionFields = [ Transaction.CHARGED_TX_FEE, Transaction.CONSENSUS_TIMESTAMP, @@ -847,6 +850,18 @@ const getTransactionsByIdOrHash = async (req, res) => { const transactions = await formatTransactionRows(rows); + if (transactions.length > 1) { + for (const transaction of transactions) { + const cacheControlMaxAge = 5; + if ( + transaction.name === scheduleCreate && + !filters.some((f) => f.key === constants.filterKeys.SCHEDULED && f.value === true) + ) { + res.set(CACHE_CONTROL_HEADER_NAME, `public, max-age=${cacheControlMaxAge}`); + } + } + } + logger.debug(`getTransactionsByIdOrHash returning ${transactions.length} entries`); res.locals[constants.responseDataLabel] = { transactions, From 8700740ec894eae7912ec4d15622d608871c6f2a Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 10 Sep 2024 21:38:27 -0400 Subject: [PATCH 02/20] Missed files. Signed-off-by: mgoelswirlds --- .../specs/transactions/{id}/no-params.json | 112 ------------------ .../scheduled-false.json | 0 2 files changed, 112 deletions(-) rename hedera-mirror-rest/__tests__/specs/transactions/{id}/{ => scheduled-transactions}/scheduled-false.json (100%) diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json index 0ecc7f8748f..b7d010ec22d 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json @@ -177,118 +177,6 @@ ] }, "tests": [ - { - "url": "/api/v1/transactions/0.0.10-1234567890-000000001", - "responseStatus": 200, - "responseJson": { - "transactions": [ - { - "bytes": null, - "consensus_timestamp": "1234567890.000000002", - "charged_tx_fee": 7, - "entity_id": "0.0.100", - "max_fee": "33", - "memo_base64": null, - "name": "SCHEDULECREATE", - "nft_transfers": [], - "node": "0.0.3", - "nonce": 0, - "parent_consensus_timestamp": null, - "result": "SUCCESS", - "scheduled": false, - "staking_reward_transfers": [], - "token_transfers": [], - "transaction_hash": "rovr8cn6DzCTVuSAV/YEevfN5jA30FCdFt3Dsg4IUVi/3xTRU0XBsYsZm3L+1Kxv", - "transaction_id": "0.0.10-1234567890-000000001", - "transfers": [ - { - "account": "0.0.9", - "amount": 10, - "is_approval": false - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": false - } - ], - "valid_duration_seconds": "11", - "valid_start_timestamp": "1234567890.000000001" - }, - { - "bytes": "Ynl0ZXM=", - "charged_tx_fee": 7, - "consensus_timestamp": "1234567890.000000003", - "entity_id": null, - "max_fee": "33", - "memo_base64": null, - "name": "CRYPTOTRANSFER", - "nft_transfers": [], - "node": "0.0.3", - "nonce": 0, - "parent_consensus_timestamp": null, - "result": "SUCCESS", - "scheduled": true, - "staking_reward_transfers": [], - "token_transfers": [], - "transaction_hash": "lCDEQjAnWTLkkpv1i/mipNt5niRIU7xzY3h0ZHSituUyLqFrI6UF2mOUvZmywjsG", - "transaction_id": "0.0.10-1234567890-000000001", - "transfers": [ - { - "account": "0.0.9", - "amount": 10, - "is_approval": false - }, - { - "account": "0.0.10", - "amount": -11, - "is_approval": false - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": false - } - ], - "valid_duration_seconds": "11", - "valid_start_timestamp": "1234567890.000000001" - }, - { - "bytes": "Ynl0ZXM=", - "charged_tx_fee": 7, - "consensus_timestamp": "1234567890.000000040", - "entity_id": null, - "max_fee": "33", - "memo_base64": null, - "name": "CRYPTOTRANSFER", - "nft_transfers": [], - "node": "0.0.4", - "nonce": 0, - "parent_consensus_timestamp": null, - "result": "DUPLICATE_TRANSACTION", - "scheduled": false, - "staking_reward_transfers": [], - "token_transfers": [], - "transaction_hash": "OxfaENC/745n9QsBD59QDfpPjkVVRPEoWwQZHy7KSrG3ScJJNovCMCP7g/hh7R+0", - "transaction_id": "0.0.10-1234567890-000000001", - "transfers": [ - { - "account": "0.0.9", - "amount": 100, - "is_approval": false - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": false - } - ], - "valid_duration_seconds": "11", - "valid_start_timestamp": "1234567890.000000001" - } - ] - } - }, { "urls": [ "/api/v1/transactions/rovr8cn6DzCTVuSAV_YEevfN5jA30FCdFt3Dsg4IUVi_3xTRU0XBsYsZm3L-1Kxv", diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/scheduled-false.json similarity index 100% rename from hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json rename to hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/scheduled-false.json From 81d263ed9aa816ea9aba8320a0e2266f8b7f948c Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Wed, 11 Sep 2024 10:48:08 -0400 Subject: [PATCH 03/20] Fixing middleware test. Signed-off-by: mgoelswirlds --- hedera-mirror-rest/__tests__/middleware/responseHandler.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index 4cf0d8aedb0..eead1c83c6e 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -40,6 +40,7 @@ describe('Response middleware', () => { }, }; mockResponse = { + get: jest.fn(), locals: { responseData: responseData, statusCode: 200, From 7c388a9b40efe34f27d8d5dbb4d49cf34cd8cff4 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Wed, 11 Sep 2024 23:34:37 -0400 Subject: [PATCH 04/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- .../__tests__/integration/template.js | 2 +- .../middleware/responseHandler.test.js | 16 ++++------ .../specs/transactions/{id}/no-params.json | 5 ++- .../repeated-valid-params-schedule-false.json | 3 ++ .../scheduled-false.json | 3 ++ .../responseHeaders.json | 3 -- hedera-mirror-rest/constants.js | 3 ++ .../middleware/responseHandler.js | 18 ++++++++--- hedera-mirror-rest/transactions.js | 32 +++++++++++-------- 9 files changed, 52 insertions(+), 33 deletions(-) rename hedera-mirror-rest/__tests__/specs/transactions/{id}/{scheduled-transactions => }/scheduled-false.json (98%) delete mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json diff --git a/hedera-mirror-rest/__tests__/integration/template.js b/hedera-mirror-rest/__tests__/integration/template.js index dd1102fe7e4..0ad8fd3fcce 100644 --- a/hedera-mirror-rest/__tests__/integration/template.js +++ b/hedera-mirror-rest/__tests__/integration/template.js @@ -91,8 +91,8 @@ const getResponseHeadersFromFileOrDefault = (specPath) => { const getResponseHeaders = (spec, specPath) => { spec.responseHeaders = { - ...(spec.responseHeaders ?? {}), ...getResponseHeadersFromFileOrDefault(specPath), + ...(spec.responseHeaders ?? {}), }; }; diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index eead1c83c6e..6324e3b04de 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -40,7 +40,6 @@ describe('Response middleware', () => { }, }; mockResponse = { - get: jest.fn(), locals: { responseData: responseData, statusCode: 200, @@ -60,8 +59,7 @@ describe('Response middleware', () => { await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.default); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, headers.path[mockRequest.route.path]); - expect(mockResponse.set).toHaveBeenNthCalledWith(3, 'Content-Type', 'application/json; charset=utf-8'); + expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', 'application/json; charset=utf-8'); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); @@ -69,9 +67,8 @@ describe('Response middleware', () => { mockRequest.route.path = '/api/v1/accounts'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); - expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.default); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, headers.path[mockRequest.route.path]); - expect(mockResponse.set).toHaveBeenNthCalledWith(3, 'Content-Type', 'application/json; charset=utf-8'); + expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.path[mockRequest.route.path]); + expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', 'application/json; charset=utf-8'); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); @@ -81,8 +78,7 @@ describe('Response middleware', () => { await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(mockResponse.locals.responseData); expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.default); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, headers.path[mockRequest.route.path]); - expect(mockResponse.set).toHaveBeenNthCalledWith(3, 'Content-Type', mockResponse.locals.responseContentType); + expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', mockResponse.locals.responseContentType); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); @@ -91,11 +87,11 @@ describe('Response middleware', () => { mockResponse.locals.responseData.links.next = MOCK_URL; const assertNextValue = `<${MOCK_URL}>; rel=\"next\"`; await responseHandler(mockRequest, mockResponse, null); - expect(mockResponse.set).toHaveBeenNthCalledWith(4, 'Link', assertNextValue); + expect(mockResponse.set).toHaveBeenNthCalledWith(3, 'Link', assertNextValue); }); test('should NOT set the Link next header and confirm it exists', async () => { await responseHandler(mockRequest, mockResponse, null); - expect(mockResponse.set).toHaveBeenCalledTimes(3); + expect(mockResponse.set).toHaveBeenCalledTimes(2); }); }); diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json index b7d010ec22d..ba5e3eb2eb6 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json @@ -223,5 +223,8 @@ ] } } - ] + ], + "responseHeaders": { + "cache-control": "public, max-age=5" + } } diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json index 30032720d35..d6f8b65f2c6 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json @@ -73,6 +73,9 @@ }, "url": "/api/v1/transactions/0.0.10-1234567890-000000001?scheduled=true&scheduled=true&scheduled=false", "responseStatus": 200, + "responseHeaders": { + "cache-control": "public, max-age=5" + }, "responseJson": { "transactions": [ { diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/scheduled-false.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json similarity index 98% rename from hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/scheduled-false.json rename to hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json index 879ce96c8af..66a4af27e73 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/scheduled-false.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json @@ -74,6 +74,9 @@ }, "url": "/api/v1/transactions/0.0.10-1234567890-000000001?scheduled=false", "responseStatus": 200, + "responseHeaders": { + "cache-control": "public, max-age=5" + }, "responseJson": { "transactions": [ { diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json deleted file mode 100644 index 9e34e9f0ca2..00000000000 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-transactions/responseHeaders.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache-control": "public, max-age=5" -} diff --git a/hedera-mirror-rest/constants.js b/hedera-mirror-rest/constants.js index e7be0bb7028..2bd9e71798a 100644 --- a/hedera-mirror-rest/constants.js +++ b/hedera-mirror-rest/constants.js @@ -105,6 +105,8 @@ const requestStartTime = 'requestStartTime'; const responseContentType = 'responseContentType'; const responseDataLabel = 'responseData'; +const responseHeadersLabel = 'responseHeaders'; + const orderFilterValues = { ASC: 'asc', DESC: 'desc', @@ -252,6 +254,7 @@ export { requestStartTime, responseContentType, responseDataLabel, + responseHeadersLabel, tokenTypeFilter, transactionResultFilter, zeroRandomPageCostQueryHint, diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index fcb2ea9bc52..9874bc6a0f7 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -15,7 +15,13 @@ */ import config from '../config'; -import {requestPathLabel, requestStartTime, responseContentType, responseDataLabel} from '../constants'; +import { + requestPathLabel, + requestStartTime, + responseContentType, + responseDataLabel, + responseHeadersLabel, +} from '../constants'; import {NotFoundError} from '../errors'; import {JSONStringify} from '../utils'; @@ -37,10 +43,12 @@ const responseHandler = async (req, res, next) => { throw new NotFoundError(); } else { const path = res.locals[requestPathLabel] ?? req.route.path; - if (!res.get('cache-control')) { - res.set(headers.default); - res.set(headers.path[path]); - } + const mergedHeaders = { + ...headers.default, + ...(headers.path[path] ?? {}), + ...(res.locals[responseHeadersLabel] ?? {}), + }; + res.set(mergedHeaders); const code = res.locals.statusCode; const contentType = res.locals[responseContentType] || APPLICATION_JSON; diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 908e5d7d443..24e249d39ad 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -48,8 +48,8 @@ const { const cache = new Cache(); -const scheduleCreate = TransactionType.getName(42); -const CACHE_CONTROL_HEADER_NAME = 'cache-control'; +const scheduleCreate = 'SCHEDULECREATE'; +const SHORTER_CACHE_CONTROL_HEADER = {'cache-control': `public, max-age=5`}; const transactionFields = [ Transaction.CHARGED_TX_FEE, @@ -833,6 +833,22 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, return {query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params}; }; +function getTransactionsByIdOrHashCacheControlHeader(transactions) { + let hasScheduleCreate = false; + let scheduleExecuted = false; + for (const transaction of transactions) { + if (transaction.name === scheduleCreate) { + hasScheduleCreate = true; + } else if (transaction.scheduled === true) { + scheduleExecuted = true; + } + } + if (hasScheduleCreate && !scheduleExecuted) { + return SHORTER_CACHE_CONTROL_HEADER; + } + return {}; // no override +} + /** * Handler function for /transactions/:transactionIdOrHash API. * @param {Request} req HTTP request object @@ -850,17 +866,7 @@ const getTransactionsByIdOrHash = async (req, res) => { const transactions = await formatTransactionRows(rows); - if (transactions.length > 1) { - for (const transaction of transactions) { - const cacheControlMaxAge = 5; - if ( - transaction.name === scheduleCreate && - !filters.some((f) => f.key === constants.filterKeys.SCHEDULED && f.value === true) - ) { - res.set(CACHE_CONTROL_HEADER_NAME, `public, max-age=${cacheControlMaxAge}`); - } - } - } + res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader(transactions); logger.debug(`getTransactionsByIdOrHash returning ${transactions.length} entries`); res.locals[constants.responseDataLabel] = { From 2e0840ef3d9775a76688cd75bb3b216a5133061b Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Fri, 13 Sep 2024 22:10:09 -0400 Subject: [PATCH 05/20] Adding corner cases and tests. Signed-off-by: mgoelswirlds --- .../{id}/duplicate-schedule-create.json | 188 ++++++++++++++++++ .../specs/transactions/{id}/no-params.json | 117 ++++++++++- .../repeated-valid-params-schedule-false.json | 3 - ...uled-create-non-scheduled-transaction.json | 135 +++++++++++++ .../transactions/{id}/scheduled-false.json | 3 - .../{id}/scheduled-no-filter.json | 132 ++++++++++++ hedera-mirror-rest/transactions.js | 38 +++- 7 files changed, 604 insertions(+), 12 deletions(-) create mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json create mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json create mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-no-filter.json diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json new file mode 100644 index 00000000000..21311a995ba --- /dev/null +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json @@ -0,0 +1,188 @@ +{ + "description": "Transaction api calls for a specific transaction using transaction id with scheduled transaction but scheduled flag is not provided", + "setup": { + "accounts": [ + { + "num": 3 + }, + { + "num": 9 + }, + { + "num": 10 + }, + { + "num": 98 + } + ], + "balances": [], + "transactions": [ + { + "charged_tx_fee": 7, + "payerAccountId": "0.0.10", + "nodeAccountId": "0.0.3", + "consensus_timestamp": "1234567890000000002", + "valid_start_timestamp": "1234567890000000001", + "name": "SCHEDULECREATE", + "result": 11, + "type": 42, + "transfers": [ + { + "account": "0.0.9", + "amount": 10 + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "entity_id": "0.0.1000" + }, + { + "charged_tx_fee": 7, + "payerAccountId": "0.0.10", + "nodeAccountId": "0.0.3", + "consensus_timestamp": "1234567890000000004", + "valid_start_timestamp": "1234567890000000001", + "name": "SCHEDULECREATE", + "result": 11, + "type": 42, + "transfers": [ + { + "account": "0.0.9", + "amount": 10 + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "entity_id": "0.0.1000" + } + ], + "cryptotransfers": [ + { + "consensus_timestamp": "1234567890000000003", + "valid_start_timestamp": "1234567890000000001", + "payerAccountId": "0.0.10", + "recipientAccountId": "0.0.9", + "amount": 10, + "nodeAccountId": "0.0.3", + "treasuryAccountId": "0.0.98", + "scheduled": false + } + ] + }, + "url": "/api/v1/transactions/0.0.10-1234567890-000000001", + "responseStatus": 200, + "responseJson": { + "transactions": [ + { + "bytes": "Ynl0ZXM=", + "consensus_timestamp": "1234567890.000000002", + "entity_id": "0.0.1000", + "charged_tx_fee": 7, + "max_fee": "33", + "memo_base64": null, + "name": "SCHEDULECREATE", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "DUPLICATE_TRANSACTION", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + }, + { + "bytes": "Ynl0ZXM=", + "charged_tx_fee": 7, + "consensus_timestamp": "1234567890.000000003", + "entity_id": null, + "max_fee": "33", + "memo_base64": null, + "name": "CRYPTOTRANSFER", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.10", + "amount": -11, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": false + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + }, + { + "bytes": "Ynl0ZXM=", + "consensus_timestamp": "1234567890.000000004", + "entity_id": "0.0.1000", + "charged_tx_fee": 7, + "max_fee": "33", + "memo_base64": null, + "name": "SCHEDULECREATE", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "DUPLICATE_TRANSACTION", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + } + ] + } +} diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json index ba5e3eb2eb6..0ecc7f8748f 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/no-params.json @@ -177,6 +177,118 @@ ] }, "tests": [ + { + "url": "/api/v1/transactions/0.0.10-1234567890-000000001", + "responseStatus": 200, + "responseJson": { + "transactions": [ + { + "bytes": null, + "consensus_timestamp": "1234567890.000000002", + "charged_tx_fee": 7, + "entity_id": "0.0.100", + "max_fee": "33", + "memo_base64": null, + "name": "SCHEDULECREATE", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "rovr8cn6DzCTVuSAV/YEevfN5jA30FCdFt3Dsg4IUVi/3xTRU0XBsYsZm3L+1Kxv", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": false + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + }, + { + "bytes": "Ynl0ZXM=", + "charged_tx_fee": 7, + "consensus_timestamp": "1234567890.000000003", + "entity_id": null, + "max_fee": "33", + "memo_base64": null, + "name": "CRYPTOTRANSFER", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": true, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "lCDEQjAnWTLkkpv1i/mipNt5niRIU7xzY3h0ZHSituUyLqFrI6UF2mOUvZmywjsG", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.10", + "amount": -11, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": false + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + }, + { + "bytes": "Ynl0ZXM=", + "charged_tx_fee": 7, + "consensus_timestamp": "1234567890.000000040", + "entity_id": null, + "max_fee": "33", + "memo_base64": null, + "name": "CRYPTOTRANSFER", + "nft_transfers": [], + "node": "0.0.4", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "DUPLICATE_TRANSACTION", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "OxfaENC/745n9QsBD59QDfpPjkVVRPEoWwQZHy7KSrG3ScJJNovCMCP7g/hh7R+0", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 100, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": false + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + } + ] + } + }, { "urls": [ "/api/v1/transactions/rovr8cn6DzCTVuSAV_YEevfN5jA30FCdFt3Dsg4IUVi_3xTRU0XBsYsZm3L-1Kxv", @@ -223,8 +335,5 @@ ] } } - ], - "responseHeaders": { - "cache-control": "public, max-age=5" - } + ] } diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json index d6f8b65f2c6..30032720d35 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/repeated-valid-params-schedule-false.json @@ -73,9 +73,6 @@ }, "url": "/api/v1/transactions/0.0.10-1234567890-000000001?scheduled=true&scheduled=true&scheduled=false", "responseStatus": 200, - "responseHeaders": { - "cache-control": "public, max-age=5" - }, "responseJson": { "transactions": [ { diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json new file mode 100644 index 00000000000..f05135ab450 --- /dev/null +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json @@ -0,0 +1,135 @@ +{ + "description": "Transaction api calls for a specific transaction using transaction id but scheduled flag is not provided", + "setup": { + "accounts": [ + { + "num": 3 + }, + { + "num": 9 + }, + { + "num": 10 + }, + { + "num": 98 + } + ], + "balances": [], + "transactions": [ + { + "charged_tx_fee": 7, + "payerAccountId": "0.0.10", + "nodeAccountId": "0.0.3", + "consensus_timestamp": "1896279200000000002", + "valid_start_timestamp": "1896279200000000001", + "name": "SCHEDULECREATE", + "type": 42, + "transfers": [ + { + "account": "0.0.9", + "amount": 10 + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "entity_id": "0.0.1000" + } + ], + "cryptotransfers": [ + { + "consensus_timestamp": "1896279200000000003", + "valid_start_timestamp": "1896279200000000001", + "payerAccountId": "0.0.10", + "recipientAccountId": "0.0.9", + "amount": 10, + "nodeAccountId": "0.0.3", + "treasuryAccountId": "0.0.98", + "scheduled": false + } + ] + }, + "url": "/api/v1/transactions/0.0.10-1896279200-000000001", + "responseStatus": 200, + "responseHeaders": { + "cache-control": "public, max-age=5" + }, + "responseJson": { + "transactions": [ + { + "bytes": "Ynl0ZXM=", + "consensus_timestamp": "1896279200.000000002", + "entity_id": "0.0.1000", + "charged_tx_fee": 7, + "max_fee": "33", + "memo_base64": null, + "name": "SCHEDULECREATE", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1896279200-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1896279200.000000001" + }, + { + "bytes": "Ynl0ZXM=", + "charged_tx_fee": 7, + "consensus_timestamp": "1896279200.000000003", + "entity_id": null, + "max_fee": "33", + "memo_base64": null, + "name": "CRYPTOTRANSFER", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1896279200-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.10", + "amount": -11, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": false + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1896279200.000000001" + } + ] + } +} diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json index 66a4af27e73..879ce96c8af 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-false.json @@ -74,9 +74,6 @@ }, "url": "/api/v1/transactions/0.0.10-1234567890-000000001?scheduled=false", "responseStatus": 200, - "responseHeaders": { - "cache-control": "public, max-age=5" - }, "responseJson": { "transactions": [ { diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-no-filter.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-no-filter.json new file mode 100644 index 00000000000..a6ef6b70bde --- /dev/null +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-no-filter.json @@ -0,0 +1,132 @@ +{ + "description": "Transaction api calls for a specific transaction using transaction id with scheduled transaction but scheduled flag is not provided", + "setup": { + "accounts": [ + { + "num": 3 + }, + { + "num": 9 + }, + { + "num": 10 + }, + { + "num": 98 + } + ], + "balances": [], + "transactions": [ + { + "charged_tx_fee": 7, + "payerAccountId": "0.0.10", + "nodeAccountId": "0.0.3", + "consensus_timestamp": "1234567890000000002", + "valid_start_timestamp": "1234567890000000001", + "name": "SCHEDULECREATE", + "type": 42, + "transfers": [ + { + "account": "0.0.9", + "amount": 10 + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "entity_id": "0.0.1000" + } + ], + "cryptotransfers": [ + { + "consensus_timestamp": "1234567890000000003", + "valid_start_timestamp": "1234567890000000001", + "payerAccountId": "0.0.10", + "recipientAccountId": "0.0.9", + "amount": 10, + "nodeAccountId": "0.0.3", + "treasuryAccountId": "0.0.98", + "scheduled": true + } + ] + }, + "url": "/api/v1/transactions/0.0.10-1234567890-000000001", + "responseStatus": 200, + "responseJson": { + "transactions": [ + { + "bytes": "Ynl0ZXM=", + "consensus_timestamp": "1234567890.000000002", + "entity_id": "0.0.1000", + "charged_tx_fee": 7, + "max_fee": "33", + "memo_base64": null, + "name": "SCHEDULECREATE", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + }, + { + "bytes": "Ynl0ZXM=", + "charged_tx_fee": 7, + "consensus_timestamp": "1234567890.000000003", + "entity_id": null, + "max_fee": "33", + "memo_base64": null, + "name": "CRYPTOTRANSFER", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": true, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.10", + "amount": -11, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": false + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + } + ] + } +} diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 24e249d39ad..d608b02e02a 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -21,6 +21,7 @@ import {Cache} from './cache'; import config from './config'; import * as constants from './constants'; import EntityId from './entityId'; +import * as math from 'mathjs'; import {NotFoundError} from './errors'; import {getTransactionHash, isValidTransactionHash} from './transactionHash'; import TransactionId from './transactionId'; @@ -50,6 +51,9 @@ const cache = new Cache(); const scheduleCreate = 'SCHEDULECREATE'; const SHORTER_CACHE_CONTROL_HEADER = {'cache-control': `public, max-age=5`}; +const successTransactionResult = TransactionResult.getProtoId('SUCCESS'); +const successMissingOperationTransactionResult = TransactionResult.getProtoId('SUCCESS_BUT_MISSING_EXPECTED_OPERATION'); +const successFeeSchedulePartUploadedTransactionResult = TransactionResult.getProtoId('FEE_SCHEDULE_FILE_PART_UPLOADED'); const transactionFields = [ Transaction.CHARGED_TX_FEE, @@ -833,14 +837,40 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, return {query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params}; }; -function getTransactionsByIdOrHashCacheControlHeader(transactions) { +function isTransactionSuccessful(result) { + return ( + result === successTransactionResult || + result === successFeeSchedulePartUploadedTransactionResult || + result === successMissingOperationTransactionResult + ); +} + +function getTransactionsByIdOrHashCacheControlHeader(transactions, transactionIdOrHash, filters) { let hasScheduleCreate = false; let scheduleExecuted = false; + const currSecs = Math.floor(new Date().getTime() / 1000); + let timestamp = 0; + if (filters.some((f) => f.key === constants.filterKeys.SCHEDULED) || isValidTransactionHash(transactionIdOrHash)) { + return {}; // no override + } + for (const transaction of transactions) { if (transaction.name === scheduleCreate) { + if (!isTransactionSuccessful(TransactionResult.getProtoId(transaction.result))) { + // If all duplicate SCHEDULECREATE transactions_t fail, the cache max-age will not be shortened. + continue; + } + + timestamp = math.subtract(currSecs, math.bignumber(transaction.consensus_timestamp).toNumber()); hasScheduleCreate = true; } else if (transaction.scheduled === true) { scheduleExecuted = true; + } else if ( + timestamp > Number(config.query.maxTransactionConsensusTimestampRangeNs / 1_000_000_000n) && + !transaction.scheduled + ) { + //If the scheduled transaction never executes in the given timeframe , the cache max-age will not be shortened + return {}; } } if (hasScheduleCreate && !scheduleExecuted) { @@ -866,7 +896,11 @@ const getTransactionsByIdOrHash = async (req, res) => { const transactions = await formatTransactionRows(rows); - res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader(transactions); + res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader( + transactions, + req.params.transactionIdOrHash, + filters + ); logger.debug(`getTransactionsByIdOrHash returning ${transactions.length} entries`); res.locals[constants.responseDataLabel] = { From 4e737c43573225735ab1bd5d5a09e23db6b32d6f Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Mon, 16 Sep 2024 14:38:22 -0400 Subject: [PATCH 06/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- ...uled-create-non-scheduled-transaction.json | 4 +- hedera-mirror-rest/constants.js | 2 + .../controllers/networkController.js | 4 +- .../middleware/responseHandler.js | 2 +- hedera-mirror-rest/model/transactionResult.js | 5 ++ hedera-mirror-rest/transactions.js | 66 ++++++++----------- 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json index f05135ab450..d1141db769d 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json @@ -48,7 +48,7 @@ "amount": 10, "nodeAccountId": "0.0.3", "treasuryAccountId": "0.0.98", - "scheduled": false + "scheduled": true } ] }, @@ -105,7 +105,7 @@ "nonce": 0, "parent_consensus_timestamp": null, "result": "SUCCESS", - "scheduled": false, + "scheduled": true, "staking_reward_transfers": [], "token_transfers": [], "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", diff --git a/hedera-mirror-rest/constants.js b/hedera-mirror-rest/constants.js index 2bd9e71798a..f90a08d541a 100644 --- a/hedera-mirror-rest/constants.js +++ b/hedera-mirror-rest/constants.js @@ -15,6 +15,7 @@ */ const NANOSECONDS_PER_MILLISECOND = 10n ** 6n; +const NANOSECONDS_PER_SECOND = 10n ** 9n; const MAX_INT32 = 2147483647; const MAX_LONG = 2n ** 63n - 1n; const ONE_DAY_IN_NS = 86_400_000_000_000n; @@ -228,6 +229,7 @@ export { EVM_ADDRESS_LENGTH, ETH_HASH_LENGTH, NANOSECONDS_PER_MILLISECOND, + NANOSECONDS_PER_SECOND, MAX_INT32, MAX_LONG, ONE_DAY_IN_NS, diff --git a/hedera-mirror-rest/controllers/networkController.js b/hedera-mirror-rest/controllers/networkController.js index 9ac7b7d5262..79f63c23d82 100644 --- a/hedera-mirror-rest/controllers/networkController.js +++ b/hedera-mirror-rest/controllers/networkController.js @@ -23,8 +23,8 @@ import { networkSupplyCurrencyFormatType, networkSupplyQuery, orderFilterValues, - responseContentType, responseDataLabel, + responseHeadersLabel, } from '../constants'; import {InvalidArgumentError, NotFoundError} from '../errors'; import {AddressBookEntry, FileData} from '../model'; @@ -341,7 +341,7 @@ class NetworkController extends BaseController { const valueInTinyCoins = q === networkSupplyQuery.TOTALCOINS ? viewModel.total_supply : viewModel.released_supply; const valueInCurrencyFormat = this.convertToCurrencyFormat(valueInTinyCoins, config.network.currencyFormat); res.locals[responseDataLabel] = valueInCurrencyFormat; - res.locals[responseContentType] = NetworkController.contentTypeTextPlain; + res.locals[responseHeadersLabel] = NetworkController.contentTypeTextPlain; } else { res.locals[responseDataLabel] = viewModel; } diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index 9874bc6a0f7..c5b7abcdb06 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -51,7 +51,7 @@ const responseHandler = async (req, res, next) => { res.set(mergedHeaders); const code = res.locals.statusCode; - const contentType = res.locals[responseContentType] || APPLICATION_JSON; + const contentType = res.locals[responseHeadersLabel] || APPLICATION_JSON; const linksNext = res.locals.responseData.links?.next; res.status(code); res.set(CONTENT_TYPE_HEADER, contentType); diff --git a/hedera-mirror-rest/model/transactionResult.js b/hedera-mirror-rest/model/transactionResult.js index 8fbe49e7294..5d1c5fef2ba 100644 --- a/hedera-mirror-rest/model/transactionResult.js +++ b/hedera-mirror-rest/model/transactionResult.js @@ -363,8 +363,13 @@ const getSuccessProtoIds = () => { ]; }; +const isSuccessful = (result) => { + return getSuccessProtoIds().includes(Number.parseInt(getProtoId(result))); +}; + export default { getName, getProtoId, getSuccessProtoIds, + isSuccessful, }; diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 2ab8b1f25ec..f066d9ae430 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -51,9 +51,6 @@ const cache = new Cache(); const scheduleCreate = 'SCHEDULECREATE'; const SHORTER_CACHE_CONTROL_HEADER = {'cache-control': `public, max-age=5`}; -const successTransactionResult = TransactionResult.getProtoId('SUCCESS'); -const successMissingOperationTransactionResult = TransactionResult.getProtoId('SUCCESS_BUT_MISSING_EXPECTED_OPERATION'); -const successFeeSchedulePartUploadedTransactionResult = TransactionResult.getProtoId('FEE_SCHEDULE_FILE_PART_UPLOADED'); const transactionFields = [ Transaction.CHARGED_TX_FEE, @@ -705,8 +702,10 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, const mainConditions = []; const commonConditions = []; const params = []; + const isTransactionHash = isValidTransactionHash(transactionIdOrHash); + let scheduled; - if (isValidTransactionHash(transactionIdOrHash)) { + if (isTransactionHash) { const encoding = transactionIdOrHash.length === Transaction.BASE64_HASH_SIZE ? 'base64url' : 'hex'; if (transactionIdOrHash.length === Transaction.HEX_HASH_WITH_PREFIX_SIZE) { transactionIdOrHash = transactionIdOrHash.substring(2); @@ -748,7 +747,6 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, // only parse nonce and scheduled query filters if the path parameter is transaction id let nonce; - let scheduled; for (const filter of filters) { // honor the last for both nonce and scheduled switch (filter.key) { @@ -773,46 +771,35 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, } mainConditions.unshift(...commonConditions); - return {query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params}; + return { + query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), + params, + scheduled: scheduled, + isTransactionHash: isTransactionHash, + }; }; -function isTransactionSuccessful(result) { - return ( - result === successTransactionResult || - result === successFeeSchedulePartUploadedTransactionResult || - result === successMissingOperationTransactionResult - ); -} - -function getTransactionsByIdOrHashCacheControlHeader(transactions, transactionIdOrHash, filters) { - let hasScheduleCreate = false; - let scheduleExecuted = false; - const currSecs = Math.floor(new Date().getTime() / 1000); - let timestamp = 0; - if (filters.some((f) => f.key === constants.filterKeys.SCHEDULED) || isValidTransactionHash(transactionIdOrHash)) { +function getTransactionsByIdOrHashCacheControlHeader(transactions, isTransactionHash, scheduled) { + if (scheduled || isTransactionHash) { return {}; // no override } + let successScheduleCreateTimestamp; + for (const transaction of transactions) { if (transaction.name === scheduleCreate) { - if (!isTransactionSuccessful(TransactionResult.getProtoId(transaction.result))) { - // If all duplicate SCHEDULECREATE transactions_t fail, the cache max-age will not be shortened. - continue; + if (TransactionResult.isSuccessful(transaction.result)) { + successScheduleCreateTimestamp = successScheduleCreateTimestamp = + BigInt(math.bignumber(transaction.consensus_timestamp).toNumber()) * constants.NANOSECONDS_PER_SECOND; + } else if (transaction.scheduled) { + return SHORTER_CACHE_CONTROL_HEADER; } - - timestamp = math.subtract(currSecs, math.bignumber(transaction.consensus_timestamp).toNumber()); - hasScheduleCreate = true; - } else if (transaction.scheduled === true) { - scheduleExecuted = true; - } else if ( - timestamp > Number(config.query.maxTransactionConsensusTimestampRangeNs / 1_000_000_000n) && - !transaction.scheduled - ) { - //If the scheduled transaction never executes in the given timeframe , the cache max-age will not be shortened - return {}; } } - if (hasScheduleCreate && !scheduleExecuted) { + if ( + successScheduleCreateTimestamp !== undefined && + utils.nowInNs() - successScheduleCreateTimestamp < maxTransactionConsensusTimestampRangeNs + ) { return SHORTER_CACHE_CONTROL_HEADER; } return {}; // no override @@ -825,7 +812,10 @@ function getTransactionsByIdOrHashCacheControlHeader(transactions, transactionId */ const getTransactionsByIdOrHash = async (req, res) => { const filters = utils.buildAndValidateFilters(req.query, acceptedSingleTransactionParameters); - const {query, params} = await extractSqlFromTransactionsByIdOrHashRequest(req.params.transactionIdOrHash, filters); + const {query, params, scheduled, isTransactionHash} = await extractSqlFromTransactionsByIdOrHashRequest( + req.params.transactionIdOrHash, + filters + ); // Execute query const {rows} = await pool.queryQuietly(query, params); @@ -837,8 +827,8 @@ const getTransactionsByIdOrHash = async (req, res) => { res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader( transactions, - req.params.transactionIdOrHash, - filters + isTransactionHash, + scheduled ); logger.debug(`getTransactionsByIdOrHash returning ${transactions.length} entries`); From 26d48ecaeeaa0f381cfe575b5295daab387b2ac6 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Mon, 16 Sep 2024 21:59:07 -0400 Subject: [PATCH 07/20] Addressing PR comments. Removing the use of responseHeadersLabel. Signed-off-by: mgoelswirlds --- .../{id}/scheduled-create-non-scheduled-transaction.json | 7 ++++--- hedera-mirror-rest/controllers/networkController.js | 4 ++-- hedera-mirror-rest/middleware/responseHandler.js | 2 +- hedera-mirror-rest/transactions.js | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json index d1141db769d..acf33cb93f7 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json @@ -24,6 +24,7 @@ "consensus_timestamp": "1896279200000000002", "valid_start_timestamp": "1896279200000000001", "name": "SCHEDULECREATE", + "scheduled": true, "type": 42, "transfers": [ { @@ -48,7 +49,7 @@ "amount": 10, "nodeAccountId": "0.0.3", "treasuryAccountId": "0.0.98", - "scheduled": true + "scheduled": false } ] }, @@ -72,7 +73,7 @@ "nonce": 0, "parent_consensus_timestamp": null, "result": "SUCCESS", - "scheduled": false, + "scheduled": true, "staking_reward_transfers": [], "token_transfers": [], "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", @@ -105,7 +106,7 @@ "nonce": 0, "parent_consensus_timestamp": null, "result": "SUCCESS", - "scheduled": true, + "scheduled": false, "staking_reward_transfers": [], "token_transfers": [], "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", diff --git a/hedera-mirror-rest/controllers/networkController.js b/hedera-mirror-rest/controllers/networkController.js index 79f63c23d82..9ac7b7d5262 100644 --- a/hedera-mirror-rest/controllers/networkController.js +++ b/hedera-mirror-rest/controllers/networkController.js @@ -23,8 +23,8 @@ import { networkSupplyCurrencyFormatType, networkSupplyQuery, orderFilterValues, + responseContentType, responseDataLabel, - responseHeadersLabel, } from '../constants'; import {InvalidArgumentError, NotFoundError} from '../errors'; import {AddressBookEntry, FileData} from '../model'; @@ -341,7 +341,7 @@ class NetworkController extends BaseController { const valueInTinyCoins = q === networkSupplyQuery.TOTALCOINS ? viewModel.total_supply : viewModel.released_supply; const valueInCurrencyFormat = this.convertToCurrencyFormat(valueInTinyCoins, config.network.currencyFormat); res.locals[responseDataLabel] = valueInCurrencyFormat; - res.locals[responseHeadersLabel] = NetworkController.contentTypeTextPlain; + res.locals[responseContentType] = NetworkController.contentTypeTextPlain; } else { res.locals[responseDataLabel] = viewModel; } diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index c5b7abcdb06..9874bc6a0f7 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -51,7 +51,7 @@ const responseHandler = async (req, res, next) => { res.set(mergedHeaders); const code = res.locals.statusCode; - const contentType = res.locals[responseHeadersLabel] || APPLICATION_JSON; + const contentType = res.locals[responseContentType] || APPLICATION_JSON; const linksNext = res.locals.responseData.links?.next; res.status(code); res.set(CONTENT_TYPE_HEADER, contentType); diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index f066d9ae430..27efc4583e7 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -789,11 +789,11 @@ function getTransactionsByIdOrHashCacheControlHeader(transactions, isTransaction for (const transaction of transactions) { if (transaction.name === scheduleCreate) { if (TransactionResult.isSuccessful(transaction.result)) { - successScheduleCreateTimestamp = successScheduleCreateTimestamp = + successScheduleCreateTimestamp = BigInt(math.bignumber(transaction.consensus_timestamp).toNumber()) * constants.NANOSECONDS_PER_SECOND; - } else if (transaction.scheduled) { - return SHORTER_CACHE_CONTROL_HEADER; } + } else if (transaction.scheduled) { + return {}; } } if ( From b97bbe7b9fe18b8bc3608a0caa5a2ca226c658e0 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 17 Sep 2024 13:26:47 -0400 Subject: [PATCH 08/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- hedera-mirror-rest/constants.js | 2 -- hedera-mirror-rest/model/transactionResult.js | 2 +- hedera-mirror-rest/transactions.js | 19 ++++++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/hedera-mirror-rest/constants.js b/hedera-mirror-rest/constants.js index f90a08d541a..2bd9e71798a 100644 --- a/hedera-mirror-rest/constants.js +++ b/hedera-mirror-rest/constants.js @@ -15,7 +15,6 @@ */ const NANOSECONDS_PER_MILLISECOND = 10n ** 6n; -const NANOSECONDS_PER_SECOND = 10n ** 9n; const MAX_INT32 = 2147483647; const MAX_LONG = 2n ** 63n - 1n; const ONE_DAY_IN_NS = 86_400_000_000_000n; @@ -229,7 +228,6 @@ export { EVM_ADDRESS_LENGTH, ETH_HASH_LENGTH, NANOSECONDS_PER_MILLISECOND, - NANOSECONDS_PER_SECOND, MAX_INT32, MAX_LONG, ONE_DAY_IN_NS, diff --git a/hedera-mirror-rest/model/transactionResult.js b/hedera-mirror-rest/model/transactionResult.js index 5d1c5fef2ba..8179613f9ad 100644 --- a/hedera-mirror-rest/model/transactionResult.js +++ b/hedera-mirror-rest/model/transactionResult.js @@ -364,7 +364,7 @@ const getSuccessProtoIds = () => { }; const isSuccessful = (result) => { - return getSuccessProtoIds().includes(Number.parseInt(getProtoId(result))); + return getSuccessProtoIds().includes(result); }; export default { diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 27efc4583e7..f89753b575f 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -20,7 +20,6 @@ import {Cache} from './cache'; import config from './config'; import * as constants from './constants'; import EntityId from './entityId'; -import * as math from 'mathjs'; import {NotFoundError} from './errors'; import {bindTimestampRange} from './timestampRange'; import {getTransactionHash, isValidTransactionHash} from './transactionHash'; @@ -703,7 +702,7 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, const commonConditions = []; const params = []; const isTransactionHash = isValidTransactionHash(transactionIdOrHash); - let scheduled; + let scheduledParamExists = false; if (isTransactionHash) { const encoding = transactionIdOrHash.length === Transaction.BASE64_HASH_SIZE ? 'base64url' : 'hex'; @@ -747,6 +746,8 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, // only parse nonce and scheduled query filters if the path parameter is transaction id let nonce; + let scheduled; + for (const filter of filters) { // honor the last for both nonce and scheduled switch (filter.key) { @@ -755,6 +756,7 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, break; case constants.filterKeys.SCHEDULED: scheduled = filter.value; + scheduledParamExists = true; break; } } @@ -774,23 +776,22 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, return { query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params, - scheduled: scheduled, + scheduledParamExists: scheduledParamExists, isTransactionHash: isTransactionHash, }; }; -function getTransactionsByIdOrHashCacheControlHeader(transactions, isTransactionHash, scheduled) { +function getTransactionsByIdOrHashCacheControlHeader(transactionsRows, isTransactionHash, scheduled) { if (scheduled || isTransactionHash) { return {}; // no override } let successScheduleCreateTimestamp; - for (const transaction of transactions) { - if (transaction.name === scheduleCreate) { + for (const transaction of transactionsRows) { + if (TransactionType.getName(transaction.type) === scheduleCreate) { if (TransactionResult.isSuccessful(transaction.result)) { - successScheduleCreateTimestamp = - BigInt(math.bignumber(transaction.consensus_timestamp).toNumber()) * constants.NANOSECONDS_PER_SECOND; + successScheduleCreateTimestamp = transaction.consensus_timestamp; } } else if (transaction.scheduled) { return {}; @@ -826,7 +827,7 @@ const getTransactionsByIdOrHash = async (req, res) => { const transactions = await formatTransactionRows(rows); res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader( - transactions, + rows, isTransactionHash, scheduled ); From 03e720aa90a31e95b1917b963634b03b521ff22d Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 17 Sep 2024 14:06:44 -0400 Subject: [PATCH 09/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- ...uled-create-non-scheduled-transaction.json | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json deleted file mode 100644 index acf33cb93f7..00000000000 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-non-scheduled-transaction.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "description": "Transaction api calls for a specific transaction using transaction id but scheduled flag is not provided", - "setup": { - "accounts": [ - { - "num": 3 - }, - { - "num": 9 - }, - { - "num": 10 - }, - { - "num": 98 - } - ], - "balances": [], - "transactions": [ - { - "charged_tx_fee": 7, - "payerAccountId": "0.0.10", - "nodeAccountId": "0.0.3", - "consensus_timestamp": "1896279200000000002", - "valid_start_timestamp": "1896279200000000001", - "name": "SCHEDULECREATE", - "scheduled": true, - "type": 42, - "transfers": [ - { - "account": "0.0.9", - "amount": 10 - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": true - } - ], - "entity_id": "0.0.1000" - } - ], - "cryptotransfers": [ - { - "consensus_timestamp": "1896279200000000003", - "valid_start_timestamp": "1896279200000000001", - "payerAccountId": "0.0.10", - "recipientAccountId": "0.0.9", - "amount": 10, - "nodeAccountId": "0.0.3", - "treasuryAccountId": "0.0.98", - "scheduled": false - } - ] - }, - "url": "/api/v1/transactions/0.0.10-1896279200-000000001", - "responseStatus": 200, - "responseHeaders": { - "cache-control": "public, max-age=5" - }, - "responseJson": { - "transactions": [ - { - "bytes": "Ynl0ZXM=", - "consensus_timestamp": "1896279200.000000002", - "entity_id": "0.0.1000", - "charged_tx_fee": 7, - "max_fee": "33", - "memo_base64": null, - "name": "SCHEDULECREATE", - "nft_transfers": [], - "node": "0.0.3", - "nonce": 0, - "parent_consensus_timestamp": null, - "result": "SUCCESS", - "scheduled": true, - "staking_reward_transfers": [], - "token_transfers": [], - "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", - "transaction_id": "0.0.10-1896279200-000000001", - "transfers": [ - { - "account": "0.0.9", - "amount": 10, - "is_approval": false - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": true - } - ], - "valid_duration_seconds": "11", - "valid_start_timestamp": "1896279200.000000001" - }, - { - "bytes": "Ynl0ZXM=", - "charged_tx_fee": 7, - "consensus_timestamp": "1896279200.000000003", - "entity_id": null, - "max_fee": "33", - "memo_base64": null, - "name": "CRYPTOTRANSFER", - "nft_transfers": [], - "node": "0.0.3", - "nonce": 0, - "parent_consensus_timestamp": null, - "result": "SUCCESS", - "scheduled": false, - "staking_reward_transfers": [], - "token_transfers": [], - "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", - "transaction_id": "0.0.10-1896279200-000000001", - "transfers": [ - { - "account": "0.0.9", - "amount": 10, - "is_approval": false - }, - { - "account": "0.0.10", - "amount": -11, - "is_approval": false - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": false - } - ], - "valid_duration_seconds": "11", - "valid_start_timestamp": "1896279200.000000001" - } - ] - } -} From d2d727a89f170ab2f7f80396c8482a52d3e0af7e Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 17 Sep 2024 14:53:58 -0400 Subject: [PATCH 10/20] Changing network supply to use response headers. Signed-off-by: mgoelswirlds --- hedera-mirror-rest/controllers/networkController.js | 5 ++--- hedera-mirror-rest/middleware/responseHandler.js | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/hedera-mirror-rest/controllers/networkController.js b/hedera-mirror-rest/controllers/networkController.js index 9ac7b7d5262..5ad3310a7c8 100644 --- a/hedera-mirror-rest/controllers/networkController.js +++ b/hedera-mirror-rest/controllers/networkController.js @@ -23,8 +23,7 @@ import { networkSupplyCurrencyFormatType, networkSupplyQuery, orderFilterValues, - responseContentType, - responseDataLabel, + responseDataLabel, responseHeadersLabel, } from '../constants'; import {InvalidArgumentError, NotFoundError} from '../errors'; import {AddressBookEntry, FileData} from '../model'; @@ -341,7 +340,7 @@ class NetworkController extends BaseController { const valueInTinyCoins = q === networkSupplyQuery.TOTALCOINS ? viewModel.total_supply : viewModel.released_supply; const valueInCurrencyFormat = this.convertToCurrencyFormat(valueInTinyCoins, config.network.currencyFormat); res.locals[responseDataLabel] = valueInCurrencyFormat; - res.locals[responseContentType] = NetworkController.contentTypeTextPlain; + res.locals[responseHeadersLabel] = NetworkController.contentTypeTextPlain; } else { res.locals[responseDataLabel] = viewModel; } diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index 9874bc6a0f7..c7eae74b389 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -51,7 +51,9 @@ const responseHandler = async (req, res, next) => { res.set(mergedHeaders); const code = res.locals.statusCode; - const contentType = res.locals[responseContentType] || APPLICATION_JSON; + const headerLabel = res.locals[responseHeadersLabel] + const contentType = (headerLabel === undefined || typeof headerLabel === 'object') ? APPLICATION_JSON : headerLabel ; + const linksNext = res.locals.responseData.links?.next; res.status(code); res.set(CONTENT_TYPE_HEADER, contentType); From 1a60a794ea88334ba2f5556388e835ef4c804f6b Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 17 Sep 2024 15:25:13 -0400 Subject: [PATCH 11/20] fix test Signed-off-by: mgoelswirlds --- .../__tests__/middleware/responseHandler.test.js | 4 ++-- hedera-mirror-rest/middleware/responseHandler.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index 6324e3b04de..78427659266 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -73,12 +73,12 @@ describe('Response middleware', () => { }); test('Custom Content-Type', async () => { - mockResponse.locals.responseContentType = 'text/plain'; + mockResponse.locals.responseHeadersLabel = 'text/plain'; mockResponse.locals.responseData = '123'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(mockResponse.locals.responseData); expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.default); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', mockResponse.locals.responseContentType); + expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', mockResponse.locals.responseHeadersLabel); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index c7eae74b389..2b5d5e1452a 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -51,8 +51,8 @@ const responseHandler = async (req, res, next) => { res.set(mergedHeaders); const code = res.locals.statusCode; - const headerLabel = res.locals[responseHeadersLabel] - const contentType = (headerLabel === undefined || typeof headerLabel === 'object') ? APPLICATION_JSON : headerLabel ; + const responseHeader = res.locals.responseHeadersLabel; + const contentType = (responseHeader === undefined || typeof responseHeader === 'object') ? APPLICATION_JSON : responseHeader ; const linksNext = res.locals.responseData.links?.next; res.status(code); From 6736a2c4caf82034c049a953a84a32e0059be320 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 17 Sep 2024 15:54:02 -0400 Subject: [PATCH 12/20] fix test Signed-off-by: mgoelswirlds --- hedera-mirror-rest/controllers/networkController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-mirror-rest/controllers/networkController.js b/hedera-mirror-rest/controllers/networkController.js index 5ad3310a7c8..0f833e810e8 100644 --- a/hedera-mirror-rest/controllers/networkController.js +++ b/hedera-mirror-rest/controllers/networkController.js @@ -340,7 +340,7 @@ class NetworkController extends BaseController { const valueInTinyCoins = q === networkSupplyQuery.TOTALCOINS ? viewModel.total_supply : viewModel.released_supply; const valueInCurrencyFormat = this.convertToCurrencyFormat(valueInTinyCoins, config.network.currencyFormat); res.locals[responseDataLabel] = valueInCurrencyFormat; - res.locals[responseHeadersLabel] = NetworkController.contentTypeTextPlain; + res.locals.responseHeadersLabel = NetworkController.contentTypeTextPlain; } else { res.locals[responseDataLabel] = viewModel; } From 72470f20c31fffc2e32f73af5fa8c8b02d5f4932 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Tue, 17 Sep 2024 19:47:58 -0400 Subject: [PATCH 13/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- .../__tests__/middleware/responseHandler.test.js | 10 +++++----- hedera-mirror-rest/config/application.yml | 3 ++- hedera-mirror-rest/controllers/networkController.js | 4 ++-- hedera-mirror-rest/middleware/responseHandler.js | 3 +-- hedera-mirror-rest/transactions.js | 13 +++++++------ 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index 78427659266..b5945b7707d 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -20,6 +20,7 @@ import config from '../../config'; import {NotFoundError} from '../../errors'; import {responseHandler} from '../../middleware'; import {JSONStringify} from '../../utils'; +import {responseHeadersLabel} from "../../constants.js"; const { response: {headers}, @@ -67,18 +68,17 @@ describe('Response middleware', () => { mockRequest.route.path = '/api/v1/accounts'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); - expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.path[mockRequest.route.path]); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', 'application/json; charset=utf-8'); + expect(mockResponse.set).toHaveBeenCalledWith({'Content-type': 'application/json; charset=utf-8'},headers.path[mockRequest.route.path]); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); test('Custom Content-Type', async () => { - mockResponse.locals.responseHeadersLabel = 'text/plain'; + mockResponse.locals[responseHeadersLabel] = {'Content-type' : 'text/plain; charset=utf-8'}; mockResponse.locals.responseData = '123'; + const cacheControl = 'cache-control'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(mockResponse.locals.responseData); - expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.default); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', mockResponse.locals.responseHeadersLabel); + expect(mockResponse.set).toHaveBeenNthCalledWith(1,mockResponse.locals[responseHeadersLabel],{'cache-control' : headers.default[cacheControl]}); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); diff --git a/hedera-mirror-rest/config/application.yml b/hedera-mirror-rest/config/application.yml index 9dbf7cd0153..f6a4f89b66f 100644 --- a/hedera-mirror-rest/config/application.yml +++ b/hedera-mirror-rest/config/application.yml @@ -87,7 +87,8 @@ hedera: response: compression: true headers: - default: { "cache-control": "public, max-age=1" } + default: { "cache-control": "public, max-age=1", + "Content-type": "application/json; charset=utf-8"} path: /api/v1/accounts: { "cache-control": "public, max-age=10" } /api/v1/accounts/:idOrAliasOrEvmAddress: diff --git a/hedera-mirror-rest/controllers/networkController.js b/hedera-mirror-rest/controllers/networkController.js index 0f833e810e8..3fdf0e4367c 100644 --- a/hedera-mirror-rest/controllers/networkController.js +++ b/hedera-mirror-rest/controllers/networkController.js @@ -41,7 +41,7 @@ const networkNodesDefaultSize = 10; const networkNodesMaxSize = 25; class NetworkController extends BaseController { - static contentTypeTextPlain = 'text/plain; charset=utf-8'; + static contentTypeTextPlain = {'Content-type' : 'text/plain; charset=utf-8'}; acceptedExchangeRateParameters = new Set([filterKeys.TIMESTAMP]); acceptedFeesParameters = new Set([filterKeys.ORDER, filterKeys.TIMESTAMP]); acceptedNodeParameters = new Set([filterKeys.FILE_ID, filterKeys.LIMIT, filterKeys.NODE_ID, filterKeys.ORDER]); @@ -340,7 +340,7 @@ class NetworkController extends BaseController { const valueInTinyCoins = q === networkSupplyQuery.TOTALCOINS ? viewModel.total_supply : viewModel.released_supply; const valueInCurrencyFormat = this.convertToCurrencyFormat(valueInTinyCoins, config.network.currencyFormat); res.locals[responseDataLabel] = valueInCurrencyFormat; - res.locals.responseHeadersLabel = NetworkController.contentTypeTextPlain; + res.locals[responseHeadersLabel] = NetworkController.contentTypeTextPlain; } else { res.locals[responseDataLabel] = viewModel; } diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index 2b5d5e1452a..81925b5d1fb 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -51,8 +51,7 @@ const responseHandler = async (req, res, next) => { res.set(mergedHeaders); const code = res.locals.statusCode; - const responseHeader = res.locals.responseHeadersLabel; - const contentType = (responseHeader === undefined || typeof responseHeader === 'object') ? APPLICATION_JSON : responseHeader ; + const contentType = mergedHeaders['Content-type']; const linksNext = res.locals.responseData.links?.next; res.status(code); diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index f89753b575f..bdc214f8215 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -48,7 +48,7 @@ const { const cache = new Cache(); -const scheduleCreate = 'SCHEDULECREATE'; +const scheduleCreateProtoId = 42; const SHORTER_CACHE_CONTROL_HEADER = {'cache-control': `public, max-age=5`}; const transactionFields = [ @@ -789,7 +789,8 @@ function getTransactionsByIdOrHashCacheControlHeader(transactionsRows, isTransac let successScheduleCreateTimestamp; for (const transaction of transactionsRows) { - if (TransactionType.getName(transaction.type) === scheduleCreate) { + + if (transaction.type === scheduleCreateProtoId) { if (TransactionResult.isSuccessful(transaction.result)) { successScheduleCreateTimestamp = transaction.consensus_timestamp; } @@ -797,12 +798,12 @@ function getTransactionsByIdOrHashCacheControlHeader(transactionsRows, isTransac return {}; } } - if ( - successScheduleCreateTimestamp !== undefined && - utils.nowInNs() - successScheduleCreateTimestamp < maxTransactionConsensusTimestampRangeNs - ) { + + if (successScheduleCreateTimestamp !== undefined + && (utils.nowInNs() - successScheduleCreateTimestamp) < maxTransactionConsensusTimestampRangeNs ) { return SHORTER_CACHE_CONTROL_HEADER; } + return {}; // no override } From e7b1761d0075d6dd5e5db5a51a39c78ab86466e8 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Wed, 18 Sep 2024 18:01:10 -0400 Subject: [PATCH 14/20] Modifying tests to match the object format. Signed-off-by: mgoelswirlds --- .../__tests__/middleware/responseHandler.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index b5945b7707d..81cdc5de0ed 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -28,7 +28,7 @@ const { describe('Response middleware', () => { let mockRequest, mockResponse, responseData; - + const cacheControl = 'cache-control'; beforeEach(() => { responseData = {transactions: [], links: {next: null}}; mockRequest = { @@ -68,17 +68,16 @@ describe('Response middleware', () => { mockRequest.route.path = '/api/v1/accounts'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); - expect(mockResponse.set).toHaveBeenCalledWith({'Content-type': 'application/json; charset=utf-8'},headers.path[mockRequest.route.path]); + expect(mockResponse.set).toBeCalledWith({'Content-type': 'application/json; charset=utf-8','cache-control' : headers.path[mockRequest.route.path][cacheControl] }); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); test('Custom Content-Type', async () => { mockResponse.locals[responseHeadersLabel] = {'Content-type' : 'text/plain; charset=utf-8'}; mockResponse.locals.responseData = '123'; - const cacheControl = 'cache-control'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(mockResponse.locals.responseData); - expect(mockResponse.set).toHaveBeenNthCalledWith(1,mockResponse.locals[responseHeadersLabel],{'cache-control' : headers.default[cacheControl]}); + expect(mockResponse.set).toHaveBeenNthCalledWith(1,{'Content-type' : mockResponse.locals[responseHeadersLabel]['Content-type'],'cache-control' : headers.default[cacheControl]}); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); From faa145e419f854077fc019a5d7d69d391f71e141 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Thu, 19 Sep 2024 14:41:14 -0400 Subject: [PATCH 15/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- .../middleware/responseHandler.test.js | 15 +++++++----- hedera-mirror-rest/config/application.yml | 2 +- .../middleware/responseHandler.js | 9 +++---- hedera-mirror-rest/model/transactionResult.js | 5 ---- hedera-mirror-rest/transactions.js | 24 ++++++++++--------- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index 81cdc5de0ed..bac27280630 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -29,6 +29,7 @@ const { describe('Response middleware', () => { let mockRequest, mockResponse, responseData; const cacheControl = 'cache-control'; + const contentType = 'content-type'; beforeEach(() => { responseData = {transactions: [], links: {next: null}}; mockRequest = { @@ -45,6 +46,7 @@ describe('Response middleware', () => { responseData: responseData, statusCode: 200, }, + get: jest.fn(), send: jest.fn(), set: jest.fn(), status: jest.fn(), @@ -57,27 +59,28 @@ describe('Response middleware', () => { }); test('Custom headers', async () => { + mockResponse.get.mockReturnValue('application/json; charset=utf-8'); await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); expect(mockResponse.set).toHaveBeenNthCalledWith(1, headers.default); - expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Content-Type', 'application/json; charset=utf-8'); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); test('Default headers', async () => { mockRequest.route.path = '/api/v1/accounts'; + mockResponse.get.mockReturnValue('application/json; charset=utf-8'); await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); - expect(mockResponse.set).toBeCalledWith({'Content-type': 'application/json; charset=utf-8','cache-control' : headers.path[mockRequest.route.path][cacheControl] }); + expect(mockResponse.set).toHaveBeenCalledWith({'cache-control' : headers.path[mockRequest.route.path][cacheControl],'content-type': 'application/json; charset=utf-8'}); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); test('Custom Content-Type', async () => { - mockResponse.locals[responseHeadersLabel] = {'Content-type' : 'text/plain; charset=utf-8'}; + mockResponse.locals[responseHeadersLabel] = {'content-type' : 'text/plain; charset=utf-8'}; mockResponse.locals.responseData = '123'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(mockResponse.locals.responseData); - expect(mockResponse.set).toHaveBeenNthCalledWith(1,{'Content-type' : mockResponse.locals[responseHeadersLabel]['Content-type'],'cache-control' : headers.default[cacheControl]}); + expect(mockResponse.set).toHaveBeenNthCalledWith(1,{'cache-control' : headers.default[cacheControl],'content-type' : mockResponse.locals[responseHeadersLabel][contentType]}); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); @@ -86,11 +89,11 @@ describe('Response middleware', () => { mockResponse.locals.responseData.links.next = MOCK_URL; const assertNextValue = `<${MOCK_URL}>; rel=\"next\"`; await responseHandler(mockRequest, mockResponse, null); - expect(mockResponse.set).toHaveBeenNthCalledWith(3, 'Link', assertNextValue); + expect(mockResponse.set).toHaveBeenNthCalledWith(2, 'Link', assertNextValue); }); test('should NOT set the Link next header and confirm it exists', async () => { await responseHandler(mockRequest, mockResponse, null); - expect(mockResponse.set).toHaveBeenCalledTimes(2); + expect(mockResponse.set).toHaveBeenCalledTimes(1); }); }); diff --git a/hedera-mirror-rest/config/application.yml b/hedera-mirror-rest/config/application.yml index f6a4f89b66f..102d49a40fe 100644 --- a/hedera-mirror-rest/config/application.yml +++ b/hedera-mirror-rest/config/application.yml @@ -88,7 +88,7 @@ hedera: compression: true headers: default: { "cache-control": "public, max-age=1", - "Content-type": "application/json; charset=utf-8"} + "content-type": "application/json; charset=utf-8"} path: /api/v1/accounts: { "cache-control": "public, max-age=10" } /api/v1/accounts/:idOrAliasOrEvmAddress: diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index 81925b5d1fb..64875ab2d15 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -18,7 +18,6 @@ import config from '../config'; import { requestPathLabel, requestStartTime, - responseContentType, responseDataLabel, responseHeadersLabel, } from '../constants'; @@ -29,7 +28,7 @@ const { response: {headers}, } = config; -const CONTENT_TYPE_HEADER = 'Content-Type'; +const CONTENT_TYPE_HEADER = 'content-type'; const APPLICATION_JSON = 'application/json; charset=utf-8'; const LINK_NEXT_HEADER = 'Link'; const linkNextHeaderValue = (linksNext) => `<${linksNext}>; rel="next"`; @@ -51,15 +50,13 @@ const responseHandler = async (req, res, next) => { res.set(mergedHeaders); const code = res.locals.statusCode; - const contentType = mergedHeaders['Content-type']; - const linksNext = res.locals.responseData.links?.next; - res.status(code); - res.set(CONTENT_TYPE_HEADER, contentType); + res.status(code) if (linksNext) { res.set(LINK_NEXT_HEADER, linkNextHeaderValue(linksNext)); } + const contentType = res.get(CONTENT_TYPE_HEADER); if (contentType === APPLICATION_JSON) { res.send(JSONStringify(responseData)); diff --git a/hedera-mirror-rest/model/transactionResult.js b/hedera-mirror-rest/model/transactionResult.js index 8179613f9ad..8fbe49e7294 100644 --- a/hedera-mirror-rest/model/transactionResult.js +++ b/hedera-mirror-rest/model/transactionResult.js @@ -363,13 +363,8 @@ const getSuccessProtoIds = () => { ]; }; -const isSuccessful = (result) => { - return getSuccessProtoIds().includes(result); -}; - export default { getName, getProtoId, getSuccessProtoIds, - isSuccessful, }; diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 387d8636f4e..5ab96cc1eed 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -39,6 +39,8 @@ import { import {AssessedCustomFeeViewModel, NftTransferViewModel} from './viewmodel'; +const _SUCCESS_PROTO_IDS = TransactionResult.getSuccessProtoIds(); + const { query: {maxTransactionConsensusTimestampRangeNs}, response: { @@ -788,31 +790,31 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params, scheduledParamExists: scheduledParamExists, - isTransactionHash: isTransactionHash, }; }; -function getTransactionsByIdOrHashCacheControlHeader(transactionsRows, isTransactionHash, scheduled) { - if (scheduled || isTransactionHash) { +function getTransactionsByIdOrHashCacheControlHeader(transactionsRows, scheduledParamExists) { + if (scheduledParamExists || transactionsRows.length < 2) { + // Checking for transactionsRows.length indicates it is a transactionsByHash call return {}; // no override } let successScheduleCreateTimestamp; for (const transaction of transactionsRows) { - - if (transaction.type === scheduleCreateProtoId) { - if (TransactionResult.isSuccessful(transaction.result)) { - successScheduleCreateTimestamp = transaction.consensus_timestamp; - } + if (transaction.type === scheduleCreateProtoId && _SUCCESS_PROTO_IDS.includes(transaction.result)) { + // SCHEDULECREATE transaction cannot be scheduled + successScheduleCreateTimestamp = transaction.consensus_timestamp; } else if (transaction.scheduled) { return {}; } } - if (successScheduleCreateTimestamp !== undefined - && (utils.nowInNs() - successScheduleCreateTimestamp) < maxTransactionConsensusTimestampRangeNs ) { - return SHORTER_CACHE_CONTROL_HEADER; + if (successScheduleCreateTimestamp) { + const elapsed = utils.nowInNs() - successScheduleCreateTimestamp; + if (elapsed < maxTransactionConsensusTimestampRangeNs) { + return SHORTER_CACHE_CONTROL_HEADER; + } } return {}; // no override From 840f1fb442d554bff743c50ce46b8e0c0a0f53a0 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Thu, 19 Sep 2024 14:47:58 -0400 Subject: [PATCH 16/20] Addressing PR comments. Signed-off-by: mgoelswirlds --- hedera-mirror-rest/transactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 5ab96cc1eed..d70519f2321 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -793,7 +793,7 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, }; }; -function getTransactionsByIdOrHashCacheControlHeader(transactionsRows, scheduledParamExists) { +const getTransactionsByIdOrHashCacheControlHeader = (transactionsRows, scheduledParamExists) => { if (scheduledParamExists || transactionsRows.length < 2) { // Checking for transactionsRows.length indicates it is a transactionsByHash call return {}; // no override From e0661c67ff9f33fcc0c221b590f3389feb3b0c4f Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Thu, 19 Sep 2024 15:25:44 -0400 Subject: [PATCH 17/20] Adding missing test coverage. Signed-off-by: mgoelswirlds --- hedera-mirror-rest/__tests__/transactions.test.js | 8 ++++++++ hedera-mirror-rest/transactions.js | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/hedera-mirror-rest/__tests__/transactions.test.js b/hedera-mirror-rest/__tests__/transactions.test.js index 26a897a1d18..2d2dedd80e5 100644 --- a/hedera-mirror-rest/__tests__/transactions.test.js +++ b/hedera-mirror-rest/__tests__/transactions.test.js @@ -457,6 +457,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery(), params: defaultParams, + scheduledParamExists: false, }, }, { @@ -468,6 +469,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery('nonce = $4'), params: [...defaultParams, 1], + scheduledParamExists: false, }, }, { @@ -482,6 +484,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery('nonce = $4'), params: [...defaultParams, 2], + scheduledParamExists: false, }, }, { @@ -493,6 +496,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery('scheduled = $4'), params: [...defaultParams, true], + scheduledParamExists: true, }, }, { @@ -507,6 +511,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery('scheduled = $4'), params: [...defaultParams, false], + scheduledParamExists: true, }, }, { @@ -521,6 +526,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery('nonce = $4 and scheduled = $5'), params: [...defaultParams, 1, true], + scheduledParamExists: true, }, }, { @@ -539,6 +545,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { expected: { query: getTransactionIdQuery('nonce = $4 and scheduled = $5'), params: [...defaultParams, 3, false], + scheduledParamExists: true, }, }, ]; @@ -552,6 +559,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { testutils.assertSqlQueryEqual(actual.query, testSpec.expected.query); expect(actual.params).toStrictEqual(testSpec.expected.params); + expect(actual.scheduledParamExists).toEqual(testSpec.expected.scheduledParamExists); }); } }); diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index d70519f2321..baa82975386 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -827,7 +827,7 @@ const getTransactionsByIdOrHashCacheControlHeader = (transactionsRows, scheduled */ const getTransactionsByIdOrHash = async (req, res) => { const filters = utils.buildAndValidateFilters(req.query, acceptedSingleTransactionParameters); - const {query, params, scheduled, isTransactionHash} = await extractSqlFromTransactionsByIdOrHashRequest( + const {query, params, scheduled} = await extractSqlFromTransactionsByIdOrHashRequest( req.params.transactionIdOrHash, filters ); @@ -842,7 +842,6 @@ const getTransactionsByIdOrHash = async (req, res) => { res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader( rows, - isTransactionHash, scheduled ); From 1e11fc03e5423f2049684c0c6c20b89fa9835e16 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Fri, 20 Sep 2024 11:05:09 -0400 Subject: [PATCH 18/20] Adding missing test coverage. Signed-off-by: mgoelswirlds --- .../__tests__/transactions.test.js | 153 ++++++++++++++++++ hedera-mirror-rest/transactions.js | 15 +- 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/hedera-mirror-rest/__tests__/transactions.test.js b/hedera-mirror-rest/__tests__/transactions.test.js index 2d2dedd80e5..3dc40761e72 100644 --- a/hedera-mirror-rest/__tests__/transactions.test.js +++ b/hedera-mirror-rest/__tests__/transactions.test.js @@ -29,6 +29,7 @@ const { extractSqlFromTransactionsRequest, formatTransactionRows, getStakingRewardTimestamps, + getTransactionsByIdOrHashCacheControlHeader, isValidTransactionHash, } = subject; @@ -458,6 +459,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery(), params: defaultParams, scheduledParamExists: false, + isTransactionHash: false, }, }, { @@ -470,6 +472,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery('nonce = $4'), params: [...defaultParams, 1], scheduledParamExists: false, + isTransactionHash: false, }, }, { @@ -485,6 +488,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery('nonce = $4'), params: [...defaultParams, 2], scheduledParamExists: false, + isTransactionHash: false, }, }, { @@ -497,6 +501,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery('scheduled = $4'), params: [...defaultParams, true], scheduledParamExists: true, + isTransactionHash: false, }, }, { @@ -512,6 +517,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery('scheduled = $4'), params: [...defaultParams, false], scheduledParamExists: true, + isTransactionHash: false, }, }, { @@ -527,6 +533,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery('nonce = $4 and scheduled = $5'), params: [...defaultParams, 1, true], scheduledParamExists: true, + isTransactionHash: false, }, }, { @@ -546,6 +553,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { query: getTransactionIdQuery('nonce = $4 and scheduled = $5'), params: [...defaultParams, 3, false], scheduledParamExists: true, + isTransactionHash: false, }, }, ]; @@ -560,6 +568,7 @@ describe('extractSqlFromTransactionsByIdOrHashRequest', () => { testutils.assertSqlQueryEqual(actual.query, testSpec.expected.query); expect(actual.params).toStrictEqual(testSpec.expected.params); expect(actual.scheduledParamExists).toEqual(testSpec.expected.scheduledParamExists); + expect(actual.isTransactionHash).toEqual(testSpec.expected.isTransactionHash); }); } }); @@ -935,3 +944,147 @@ describe('getStakingRewardTimestamps', () => { expect(getStakingRewardTimestamps(transactions)).toEqual(expected); }); }); + +describe('getTransactionsByIdOrHashCacheControlHeader', () => { + test(' longer max-age for SCHEDULECREATE', async () => { + + const scheduledTransactionsExecuted = [ + { + consensus_timestamp: 1, + entity_id: 98, + memo: null, + charged_tx_fee: 5, + max_fee: 33, + nonce: 0, + parent_consensus_timestamp: null, + token_transfers: [], + transfers: [], + result: 22, + scheduled: false, + transaction_hash: 'hash', + type: 42, + valid_start_ns: 1623787159737799966n, + transaction_bytes: 'bytes', + node_account_id: 2, + payer_account_id: 3, + }, + { + consensus_timestamp: 2, + entity_id: 100, + memo: null, + charged_tx_fee: 5, + max_fee: 33, + nonce: 1, + parent_consensus_timestamp: 1, + token_transfers: [], + transfers: [], + result: 22, + scheduled: true, + transaction_hash: 'hash', + type: 14, + valid_start_ns: 1623787159737799966n, + transaction_bytes: 'bytes', + node_account_id: 2, + payer_account_id: 3, + crypto_transfer_list: [{amount: 100, entity_id: 100, is_approval: true}], + }, + ]; + + expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsExecuted, false)).toEqual({}); + expect(await getTransactionsByIdOrHashCacheControlHeader(true, scheduledTransactionsExecuted, false)).toEqual({}); + expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsExecuted, true)).toEqual({}); + }); + test(' shorter max-age for SCHEDULECREATE', async () => { + + const expected = {'cache-control': `public, max-age=5`}; + const scheduledTransactionsNotYetExecuted = [ + { + consensus_timestamp: 1823787159637799966n, + entity_id: 98, + memo: null, + charged_tx_fee: 5, + max_fee: 33, + nonce: 0, + parent_consensus_timestamp: null, + token_transfers: [], + transfers: [], + result: 22, + scheduled: false, + transaction_hash: 'hash', + type: 42, + valid_start_ns: 1623787159737799966n, + transaction_bytes: 'bytes', + node_account_id: 2, + payer_account_id: 3, + }, + { + consensus_timestamp: 2, + entity_id: 100, + memo: null, + charged_tx_fee: 5, + max_fee: 33, + nonce: 1, + parent_consensus_timestamp: 1, + token_transfers: [], + transfers: [], + result: 22, + scheduled: false, + transaction_hash: 'hash', + type: 14, + valid_start_ns: 1623787159737799966n, + transaction_bytes: 'bytes', + node_account_id: 2, + payer_account_id: 3, + crypto_transfer_list: [{amount: 100, entity_id: 100, is_approval: true}], + }, + ]; + + expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsNotYetExecuted, false)).toEqual(expected); + }); + test(' longer max-age for failed SCHEDULECREATE', async () => { + + const scheduledTransactionsExecuted = [ + { + consensus_timestamp: 1, + entity_id: 98, + memo: null, + charged_tx_fee: 5, + max_fee: 33, + nonce: 0, + parent_consensus_timestamp: null, + token_transfers: [], + transfers: [], + result: 15, + scheduled: false, + transaction_hash: 'hash', + type: 42, + valid_start_ns: 1623787159737799966n, + transaction_bytes: 'bytes', + node_account_id: 2, + payer_account_id: 3, + }, + { + consensus_timestamp: 2, + entity_id: 100, + memo: null, + charged_tx_fee: 5, + max_fee: 33, + nonce: 1, + parent_consensus_timestamp: 1, + token_transfers: [], + transfers: [], + result: 22, + scheduled: true, + transaction_hash: 'hash', + type: 14, + valid_start_ns: 1623787159737799966n, + transaction_bytes: 'bytes', + node_account_id: 2, + payer_account_id: 3, + crypto_transfer_list: [{amount: 100, entity_id: 100, is_approval: true}], + }, + ]; + + expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsExecuted, false)).toEqual({}); + }); +}); diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index baa82975386..874b3b2189a 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -39,7 +39,7 @@ import { import {AssessedCustomFeeViewModel, NftTransferViewModel} from './viewmodel'; -const _SUCCESS_PROTO_IDS = TransactionResult.getSuccessProtoIds(); +const SUCCESS_PROTO_IDS = TransactionResult.getSuccessProtoIds(); const { query: {maxTransactionConsensusTimestampRangeNs}, @@ -790,19 +790,20 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params, scheduledParamExists: scheduledParamExists, + isTransactionHash: isTransactionHash }; }; -const getTransactionsByIdOrHashCacheControlHeader = (transactionsRows, scheduledParamExists) => { - if (scheduledParamExists || transactionsRows.length < 2) { - // Checking for transactionsRows.length indicates it is a transactionsByHash call +const getTransactionsByIdOrHashCacheControlHeader = (isTransactionHash, transactionsRows, scheduledParamExists) => { + if (scheduledParamExists || isTransactionHash) { + // If a schedule filter exists or the query uses a transaction hash, we return the longer max_age return {}; // no override } let successScheduleCreateTimestamp; for (const transaction of transactionsRows) { - if (transaction.type === scheduleCreateProtoId && _SUCCESS_PROTO_IDS.includes(transaction.result)) { + if (transaction.type === scheduleCreateProtoId && SUCCESS_PROTO_IDS.includes(transaction.result)) { // SCHEDULECREATE transaction cannot be scheduled successScheduleCreateTimestamp = transaction.consensus_timestamp; } else if (transaction.scheduled) { @@ -827,7 +828,7 @@ const getTransactionsByIdOrHashCacheControlHeader = (transactionsRows, scheduled */ const getTransactionsByIdOrHash = async (req, res) => { const filters = utils.buildAndValidateFilters(req.query, acceptedSingleTransactionParameters); - const {query, params, scheduled} = await extractSqlFromTransactionsByIdOrHashRequest( + const {query, params, scheduled, isTransactionHash} = await extractSqlFromTransactionsByIdOrHashRequest( req.params.transactionIdOrHash, filters ); @@ -841,6 +842,7 @@ const getTransactionsByIdOrHash = async (req, res) => { const transactions = await formatTransactionRows(rows); res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader( + isTransactionHash, rows, scheduled ); @@ -882,6 +884,7 @@ if (utils.isTestEnv()) { extractSqlFromTransactionsRequest, formatTransactionRows, getStakingRewardTimestamps, + getTransactionsByIdOrHashCacheControlHeader, isValidTransactionHash, }); } From 4507742bfbea7862554647b3482fddb6cc471e0e Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Fri, 20 Sep 2024 12:03:13 -0400 Subject: [PATCH 19/20] Adding spec tests for shorter max age. Signed-off-by: mgoelswirlds --- .../{id}/duplicate-schedule-create.json | 60 ++----------- .../scheduled-create-shorter-max-age.json | 87 +++++++++++++++++++ 2 files changed, 95 insertions(+), 52 deletions(-) create mode 100644 hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-shorter-max-age.json diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json index 21311a995ba..83fab6b4ef1 100644 --- a/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/duplicate-schedule-create.json @@ -1,6 +1,9 @@ { "description": "Transaction api calls for a specific transaction using transaction id with scheduled transaction but scheduled flag is not provided", "setup": { + "features": { + "fakeTime": "2009-02-13T10:50:00Z" + }, "accounts": [ { "num": 3 @@ -24,7 +27,7 @@ "consensus_timestamp": "1234567890000000002", "valid_start_timestamp": "1234567890000000001", "name": "SCHEDULECREATE", - "result": 11, + "result": 22, "type": 42, "transfers": [ { @@ -61,22 +64,13 @@ ], "entity_id": "0.0.1000" } - ], - "cryptotransfers": [ - { - "consensus_timestamp": "1234567890000000003", - "valid_start_timestamp": "1234567890000000001", - "payerAccountId": "0.0.10", - "recipientAccountId": "0.0.9", - "amount": 10, - "nodeAccountId": "0.0.3", - "treasuryAccountId": "0.0.98", - "scheduled": false - } ] }, "url": "/api/v1/transactions/0.0.10-1234567890-000000001", "responseStatus": 200, + "responseHeaders": { + "cache-control": "public, max-age=5" + }, "responseJson": { "transactions": [ { @@ -91,39 +85,6 @@ "node": "0.0.3", "nonce": 0, "parent_consensus_timestamp": null, - "result": "DUPLICATE_TRANSACTION", - "scheduled": false, - "staking_reward_transfers": [], - "token_transfers": [], - "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", - "transaction_id": "0.0.10-1234567890-000000001", - "transfers": [ - { - "account": "0.0.9", - "amount": 10, - "is_approval": false - }, - { - "account": "0.0.98", - "amount": 1, - "is_approval": true - } - ], - "valid_duration_seconds": "11", - "valid_start_timestamp": "1234567890.000000001" - }, - { - "bytes": "Ynl0ZXM=", - "charged_tx_fee": 7, - "consensus_timestamp": "1234567890.000000003", - "entity_id": null, - "max_fee": "33", - "memo_base64": null, - "name": "CRYPTOTRANSFER", - "nft_transfers": [], - "node": "0.0.3", - "nonce": 0, - "parent_consensus_timestamp": null, "result": "SUCCESS", "scheduled": false, "staking_reward_transfers": [], @@ -136,15 +97,10 @@ "amount": 10, "is_approval": false }, - { - "account": "0.0.10", - "amount": -11, - "is_approval": false - }, { "account": "0.0.98", "amount": 1, - "is_approval": false + "is_approval": true } ], "valid_duration_seconds": "11", diff --git a/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-shorter-max-age.json b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-shorter-max-age.json new file mode 100644 index 00000000000..267eeded8e1 --- /dev/null +++ b/hedera-mirror-rest/__tests__/specs/transactions/{id}/scheduled-create-shorter-max-age.json @@ -0,0 +1,87 @@ +{ + "description": "Transaction api calls for a specific transaction using transaction id and scheduled is false", + "setup": { + "features": { + "fakeTime": "2009-02-13T10:50:00Z" + }, + "accounts": [ + { + "num": 3 + }, + { + "num": 9 + }, + { + "num": 10 + }, + { + "num": 98 + } + ], + "balances": [], + "transactions": [ + { + "charged_tx_fee": 7, + "payerAccountId": "0.0.10", + "nodeAccountId": "0.0.3", + "consensus_timestamp": "1234567890000000002", + "name": "SCHEDULECREATE", + "type": 42, + "transfers": [ + { + "account": "0.0.9", + "amount": 10 + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "entity_id": "0.0.1000" + } + ] + }, + "url": "/api/v1/transactions/0.0.10-1234567890-000000001", + "responseStatus": 200, + "responseHeaders": { + "cache-control": "public, max-age=5" + }, + "responseJson": { + "transactions": [ + { + "bytes": "Ynl0ZXM=", + "consensus_timestamp": "1234567890.000000002", + "entity_id": "0.0.1000", + "charged_tx_fee": 7, + "max_fee": "33", + "memo_base64": null, + "name": "SCHEDULECREATE", + "nft_transfers": [], + "node": "0.0.3", + "nonce": 0, + "parent_consensus_timestamp": null, + "result": "SUCCESS", + "scheduled": false, + "staking_reward_transfers": [], + "token_transfers": [], + "transaction_hash": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8w", + "transaction_id": "0.0.10-1234567890-000000001", + "transfers": [ + { + "account": "0.0.9", + "amount": 10, + "is_approval": false + }, + { + "account": "0.0.98", + "amount": 1, + "is_approval": true + } + ], + "valid_duration_seconds": "11", + "valid_start_timestamp": "1234567890.000000001" + } + ] + } +} From 45ab8c38130f1d8353367868233ba3330cd61014 Mon Sep 17 00:00:00 2001 From: mgoelswirlds Date: Fri, 20 Sep 2024 21:32:16 -0400 Subject: [PATCH 20/20] Moving content type to constants. Signed-off-by: mgoelswirlds --- .../__tests__/middleware/responseHandler.test.js | 16 +++++++++++----- .../__tests__/transactions.test.js | 9 ++++----- hedera-mirror-rest/config/application.yml | 7 +++++-- hedera-mirror-rest/constants.js | 4 ++-- .../controllers/networkController.js | 8 +++++--- hedera-mirror-rest/middleware/responseHandler.js | 9 ++------- hedera-mirror-rest/transactions.js | 6 +++--- 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js index bac27280630..52350b2bdb6 100644 --- a/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js +++ b/hedera-mirror-rest/__tests__/middleware/responseHandler.test.js @@ -20,7 +20,7 @@ import config from '../../config'; import {NotFoundError} from '../../errors'; import {responseHandler} from '../../middleware'; import {JSONStringify} from '../../utils'; -import {responseHeadersLabel} from "../../constants.js"; +import {contentTypeHeader, responseHeadersLabel} from '../../constants.js'; const { response: {headers}, @@ -29,7 +29,7 @@ const { describe('Response middleware', () => { let mockRequest, mockResponse, responseData; const cacheControl = 'cache-control'; - const contentType = 'content-type'; + beforeEach(() => { responseData = {transactions: [], links: {next: null}}; mockRequest = { @@ -71,16 +71,22 @@ describe('Response middleware', () => { mockResponse.get.mockReturnValue('application/json; charset=utf-8'); await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(JSONStringify(responseData)); - expect(mockResponse.set).toHaveBeenCalledWith({'cache-control' : headers.path[mockRequest.route.path][cacheControl],'content-type': 'application/json; charset=utf-8'}); + expect(mockResponse.set).toHaveBeenCalledWith({ + [cacheControl]: headers.path[mockRequest.route.path][cacheControl], + [contentTypeHeader]: 'application/json; charset=utf-8', + }); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); test('Custom Content-Type', async () => { - mockResponse.locals[responseHeadersLabel] = {'content-type' : 'text/plain; charset=utf-8'}; + mockResponse.locals[responseHeadersLabel] = {[contentTypeHeader]: 'text/plain; charset=utf-8'}; mockResponse.locals.responseData = '123'; await responseHandler(mockRequest, mockResponse, null); expect(mockResponse.send).toBeCalledWith(mockResponse.locals.responseData); - expect(mockResponse.set).toHaveBeenNthCalledWith(1,{'cache-control' : headers.default[cacheControl],'content-type' : mockResponse.locals[responseHeadersLabel][contentType]}); + expect(mockResponse.set).toHaveBeenNthCalledWith(1, { + [cacheControl]: headers.default[cacheControl], + [contentTypeHeader]: mockResponse.locals[responseHeadersLabel][contentTypeHeader], + }); expect(mockResponse.status).toBeCalledWith(mockResponse.locals.statusCode); }); diff --git a/hedera-mirror-rest/__tests__/transactions.test.js b/hedera-mirror-rest/__tests__/transactions.test.js index 3dc40761e72..30f7b7139bd 100644 --- a/hedera-mirror-rest/__tests__/transactions.test.js +++ b/hedera-mirror-rest/__tests__/transactions.test.js @@ -947,7 +947,6 @@ describe('getStakingRewardTimestamps', () => { describe('getTransactionsByIdOrHashCacheControlHeader', () => { test(' longer max-age for SCHEDULECREATE', async () => { - const scheduledTransactionsExecuted = [ { consensus_timestamp: 1, @@ -995,7 +994,6 @@ describe('getTransactionsByIdOrHashCacheControlHeader', () => { expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsExecuted, true)).toEqual({}); }); test(' shorter max-age for SCHEDULECREATE', async () => { - const expected = {'cache-control': `public, max-age=5`}; const scheduledTransactionsNotYetExecuted = [ { @@ -1039,10 +1037,11 @@ describe('getTransactionsByIdOrHashCacheControlHeader', () => { }, ]; - expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsNotYetExecuted, false)).toEqual(expected); + expect( + await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsNotYetExecuted, false) + ).toEqual(expected); }); test(' longer max-age for failed SCHEDULECREATE', async () => { - const scheduledTransactionsExecuted = [ { consensus_timestamp: 1, @@ -1086,5 +1085,5 @@ describe('getTransactionsByIdOrHashCacheControlHeader', () => { ]; expect(await getTransactionsByIdOrHashCacheControlHeader(false, scheduledTransactionsExecuted, false)).toEqual({}); - }); + }); }); diff --git a/hedera-mirror-rest/config/application.yml b/hedera-mirror-rest/config/application.yml index 102d49a40fe..dfcf7c4aa34 100644 --- a/hedera-mirror-rest/config/application.yml +++ b/hedera-mirror-rest/config/application.yml @@ -87,8 +87,11 @@ hedera: response: compression: true headers: - default: { "cache-control": "public, max-age=1", - "content-type": "application/json; charset=utf-8"} + default: + { + "cache-control": "public, max-age=1", + "content-type": "application/json; charset=utf-8", + } path: /api/v1/accounts: { "cache-control": "public, max-age=10" } /api/v1/accounts/:idOrAliasOrEvmAddress: diff --git a/hedera-mirror-rest/constants.js b/hedera-mirror-rest/constants.js index 2bd9e71798a..59337b93f5a 100644 --- a/hedera-mirror-rest/constants.js +++ b/hedera-mirror-rest/constants.js @@ -99,10 +99,10 @@ const keyTypes = { PROTOBUF: 'ProtobufEncoded', }; +const contentTypeHeader = 'content-type'; const requestIdLabel = 'requestId'; const requestPathLabel = 'requestPath'; const requestStartTime = 'requestStartTime'; -const responseContentType = 'responseContentType'; const responseDataLabel = 'responseData'; const responseHeadersLabel = 'responseHeaders'; @@ -235,6 +235,7 @@ export { apiPrefix, characterEncoding, cloudProviders, + contentTypeHeader, cryptoTransferType, defaultBucketNames, defaultCloudProviderEndpoints, @@ -252,7 +253,6 @@ export { requestIdLabel, requestPathLabel, requestStartTime, - responseContentType, responseDataLabel, responseHeadersLabel, tokenTypeFilter, diff --git a/hedera-mirror-rest/controllers/networkController.js b/hedera-mirror-rest/controllers/networkController.js index 3fdf0e4367c..3a839996031 100644 --- a/hedera-mirror-rest/controllers/networkController.js +++ b/hedera-mirror-rest/controllers/networkController.js @@ -19,11 +19,13 @@ import BaseController from './baseController'; import config from '../config'; import { DECIMALS_IN_HBARS, + contentTypeHeader, filterKeys, networkSupplyCurrencyFormatType, networkSupplyQuery, orderFilterValues, - responseDataLabel, responseHeadersLabel, + responseDataLabel, + responseHeadersLabel, } from '../constants'; import {InvalidArgumentError, NotFoundError} from '../errors'; import {AddressBookEntry, FileData} from '../model'; @@ -41,7 +43,7 @@ const networkNodesDefaultSize = 10; const networkNodesMaxSize = 25; class NetworkController extends BaseController { - static contentTypeTextPlain = {'Content-type' : 'text/plain; charset=utf-8'}; + static contentTypeTextPlain = 'text/plain; charset=utf-8'; acceptedExchangeRateParameters = new Set([filterKeys.TIMESTAMP]); acceptedFeesParameters = new Set([filterKeys.ORDER, filterKeys.TIMESTAMP]); acceptedNodeParameters = new Set([filterKeys.FILE_ID, filterKeys.LIMIT, filterKeys.NODE_ID, filterKeys.ORDER]); @@ -340,7 +342,7 @@ class NetworkController extends BaseController { const valueInTinyCoins = q === networkSupplyQuery.TOTALCOINS ? viewModel.total_supply : viewModel.released_supply; const valueInCurrencyFormat = this.convertToCurrencyFormat(valueInTinyCoins, config.network.currencyFormat); res.locals[responseDataLabel] = valueInCurrencyFormat; - res.locals[responseHeadersLabel] = NetworkController.contentTypeTextPlain; + res.locals[responseHeadersLabel] = {[contentTypeHeader]: NetworkController.contentTypeTextPlain}; } else { res.locals[responseDataLabel] = viewModel; } diff --git a/hedera-mirror-rest/middleware/responseHandler.js b/hedera-mirror-rest/middleware/responseHandler.js index 64875ab2d15..28b271c1830 100644 --- a/hedera-mirror-rest/middleware/responseHandler.js +++ b/hedera-mirror-rest/middleware/responseHandler.js @@ -15,12 +15,7 @@ */ import config from '../config'; -import { - requestPathLabel, - requestStartTime, - responseDataLabel, - responseHeadersLabel, -} from '../constants'; +import {requestPathLabel, requestStartTime, responseDataLabel, responseHeadersLabel} from '../constants'; import {NotFoundError} from '../errors'; import {JSONStringify} from '../utils'; @@ -51,7 +46,7 @@ const responseHandler = async (req, res, next) => { const code = res.locals.statusCode; const linksNext = res.locals.responseData.links?.next; - res.status(code) + res.status(code); if (linksNext) { res.set(LINK_NEXT_HEADER, linkNextHeaderValue(linksNext)); diff --git a/hedera-mirror-rest/transactions.js b/hedera-mirror-rest/transactions.js index 874b3b2189a..904d6dbb2c8 100644 --- a/hedera-mirror-rest/transactions.js +++ b/hedera-mirror-rest/transactions.js @@ -790,7 +790,7 @@ const extractSqlFromTransactionsByIdOrHashRequest = async (transactionIdOrHash, query: getTransactionQuery(mainConditions.join(' and '), commonConditions.join(' and ')), params, scheduledParamExists: scheduledParamExists, - isTransactionHash: isTransactionHash + isTransactionHash: isTransactionHash, }; }; @@ -819,7 +819,7 @@ const getTransactionsByIdOrHashCacheControlHeader = (isTransactionHash, transact } return {}; // no override -} +}; /** * Handler function for /transactions/:transactionIdOrHash API. @@ -842,7 +842,7 @@ const getTransactionsByIdOrHash = async (req, res) => { const transactions = await formatTransactionRows(rows); res.locals[constants.responseHeadersLabel] = getTransactionsByIdOrHashCacheControlHeader( - isTransactionHash, + isTransactionHash, rows, scheduled );