From aae0a939a5677eb2314a64863b17d56816faa732 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 22 Mar 2023 11:01:18 -0400 Subject: [PATCH 1/8] retry api call --- packages/airnode-node/package.json | 2 +- packages/airnode-node/src/api/index.ts | 6 +++++- .../src/coordinator/calls/coordinated-execution.test.ts | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/airnode-node/package.json b/packages/airnode-node/package.json index 2cd46be6cd..0cd2472618 100644 --- a/packages/airnode-node/package.json +++ b/packages/airnode-node/package.json @@ -30,7 +30,7 @@ "@api3/airnode-utilities": "^0.10.0", "@api3/airnode-validator": "^0.10.0", "@api3/ois": "2.0.0", - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "@aws-sdk/client-lambda": "^3.295.0", "date-fns": "^2.29.3", "dotenv": "^16.0.3", diff --git a/packages/airnode-node/src/api/index.ts b/packages/airnode-node/src/api/index.ts index 8ae423db30..755b7b28c5 100644 --- a/packages/airnode-node/src/api/index.ts +++ b/packages/airnode-node/src/api/index.ts @@ -203,7 +203,11 @@ export async function performApiCall( const options = buildOptions(payload); const timeout = API_CALL_TIMEOUT; // We also pass the timeout to adapter to gracefully abort the request after the timeout - const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout }), { + // timeout passed to adapter will cause axios socket to hang until the timeout is reached + // even if the attemptTimeoutMs is reached and the 2nd attempt is made + const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: (timeout * 2) / 3 }), { + retries: 1, + attemptTimeoutMs: [timeout / 3, (timeout * 2) / 3], totalTimeoutMs: timeout, }); if (!goRes.success) { diff --git a/packages/airnode-node/src/coordinator/calls/coordinated-execution.test.ts b/packages/airnode-node/src/coordinator/calls/coordinated-execution.test.ts index d4d4c75e4a..691350dc16 100644 --- a/packages/airnode-node/src/coordinator/calls/coordinated-execution.test.ts +++ b/packages/airnode-node/src/coordinator/calls/coordinated-execution.test.ts @@ -96,6 +96,7 @@ describe('callApis', () => { jest.spyOn(validator, 'unsafeParseConfigWithSecrets').mockReturnValue(config); const spy = jest.spyOn(adapter, 'buildAndExecuteRequest') as jest.SpyInstance; spy.mockRejectedValueOnce(new Error('Unexpected error')); + spy.mockRejectedValueOnce(new Error('Unexpected error')); const parameters = { _type: 'int256', _path: 'prices.1' }; const aggregatedApiCall = fixtures.buildAggregatedRegularApiCall({ parameters }); const workerOpts = fixtures.buildWorkerOptions(); @@ -116,8 +117,8 @@ describe('callApis', () => { errorMessage: `${RequestErrorMessage.ApiCallFailed}`, }, ]); - expect(spy).toHaveBeenCalledTimes(1); - }); + expect(spy).toHaveBeenCalledTimes(2); + }, 35000); it('returns an error if the worker crashes', async () => { const spy = jest.spyOn(workers, 'spawn'); From 8c1516d3eaacc85c3701dbbd498c9ced1ad4c3b4 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 22 Mar 2023 11:03:52 -0400 Subject: [PATCH 2/8] update promise-utils to v0.4.0 --- .changeset/cuddly-roses-yawn.md | 10 ++++++++++ package.json | 2 +- packages/airnode-adapter/package.json | 2 +- packages/airnode-admin/package.json | 2 +- packages/airnode-deployer/package.json | 2 +- packages/airnode-utilities/package.json | 2 +- packages/airnode-validator/package.json | 2 +- yarn.lock | 8 ++++---- 8 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 .changeset/cuddly-roses-yawn.md diff --git a/.changeset/cuddly-roses-yawn.md b/.changeset/cuddly-roses-yawn.md new file mode 100644 index 0000000000..5460c449f3 --- /dev/null +++ b/.changeset/cuddly-roses-yawn.md @@ -0,0 +1,10 @@ +--- +'@api3/airnode-utilities': minor +'@api3/airnode-validator': minor +'@api3/airnode-deployer': minor +'@api3/airnode-adapter': minor +'@api3/airnode-admin': minor +'@api3/airnode-node': minor +--- + +update promise-utils to v0.4.0 diff --git a/package.json b/package.json index 675edae86f..f208889849 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ }, "dependencies": {}, "devDependencies": { - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "@changesets/changelog-github": "^0.4.8", "@changesets/cli": "^2.26.0", "@octokit/core": "^4.2.0", diff --git a/packages/airnode-adapter/package.json b/packages/airnode-adapter/package.json index db97a1b0a6..8b3a9cf9b8 100644 --- a/packages/airnode-adapter/package.json +++ b/packages/airnode-adapter/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@api3/ois": "2.0.0", - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "axios": "^1.3.4", "bignumber.js": "^9.1.1", "ethers": "^5.7.2", diff --git a/packages/airnode-admin/package.json b/packages/airnode-admin/package.json index 8f069397ff..c2f165fbcd 100644 --- a/packages/airnode-admin/package.json +++ b/packages/airnode-admin/package.json @@ -28,7 +28,7 @@ "@api3/airnode-protocol": "^0.10.0", "@api3/airnode-utilities": "^0.10.0", "@api3/airnode-validator": "^0.10.0", - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "ethers": "^5.7.2", "lodash": "^4.17.21", "yargs": "^17.7.1" diff --git a/packages/airnode-deployer/package.json b/packages/airnode-deployer/package.json index c634b5c3dc..09f2897632 100644 --- a/packages/airnode-deployer/package.json +++ b/packages/airnode-deployer/package.json @@ -28,7 +28,7 @@ "@api3/airnode-protocol": "^0.10.0", "@api3/airnode-utilities": "^0.10.0", "@api3/airnode-validator": "^0.10.0", - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "@aws-sdk/client-s3": "^3.295.0", "@aws-sdk/signature-v4-crt": "^3.295.0", "@google-cloud/storage": "^6.9.4", diff --git a/packages/airnode-utilities/package.json b/packages/airnode-utilities/package.json index 7d63edd8e0..874a76a1a1 100644 --- a/packages/airnode-utilities/package.json +++ b/packages/airnode-utilities/package.json @@ -19,7 +19,7 @@ "main": "dist/index.js", "dependencies": { "@api3/airnode-validator": "^0.10.0", - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "date-fns": "^2.29.3", "ethers": "^5.7.2" }, diff --git a/packages/airnode-validator/package.json b/packages/airnode-validator/package.json index 131ca0c190..7887c0ef0a 100644 --- a/packages/airnode-validator/package.json +++ b/packages/airnode-validator/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@api3/ois": "2.0.0", - "@api3/promise-utils": "^0.3.0", + "@api3/promise-utils": "^0.4.0", "dotenv": "^16.0.3", "ethers": "^5.7.2", "lodash": "^4.17.21", diff --git a/yarn.lock b/yarn.lock index 8ab8c997c1..b06f91dc0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,10 +18,10 @@ lodash "^4.17.21" zod "^3.20.6" -"@api3/promise-utils@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@api3/promise-utils/-/promise-utils-0.3.0.tgz#e7ebf92bfd8c1d39983321fc5445070c51fce176" - integrity sha512-fH3CzEcsCQjoX6BZ5M+3yRIXZ2zz4/nFdzKUB4wvn3KjvvzvroHFZrzhbKa4mB9E4AS0xnou1AXhlrnN5Fcy+A== +"@api3/promise-utils@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@api3/promise-utils/-/promise-utils-0.4.0.tgz#d1dcd77d74377b4fdb3071d2cc76d98c9151309c" + integrity sha512-+8fcNjjQeQAuuSXFwu8PMZcYzjwjDiGYcMUfAQ0lpREb1zHonwWZ2N0B9h/g1cvWzg9YhElbeb/SyhCrNm+b/A== "@aws-crypto/crc32@3.0.0": version "3.0.0" From a7c4b6877cb078580d3e13dedf396c58982ac1b0 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 22 Mar 2023 14:46:07 -0400 Subject: [PATCH 3/8] update api call with non-blocking call --- packages/airnode-node/src/api/index.test.ts | 17 ++++++++------- packages/airnode-node/src/api/index.ts | 24 ++++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/airnode-node/src/api/index.test.ts b/packages/airnode-node/src/api/index.test.ts index b0e8ca82d4..5bc59f0dfb 100644 --- a/packages/airnode-node/src/api/index.test.ts +++ b/packages/airnode-node/src/api/index.test.ts @@ -63,7 +63,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); @@ -108,7 +108,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); @@ -146,7 +146,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); @@ -197,7 +197,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); @@ -216,7 +216,7 @@ describe('callApi', () => { expect.objectContaining({ parameters: { from: 'ETH', amount: '1' }, }), - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); @@ -224,7 +224,7 @@ describe('callApi', () => { const spy = jest.spyOn(adapter, 'buildAndExecuteRequest') as any; const nonAxiosError = new Error('A non-axios error'); spy.mockRejectedValueOnce(nonAxiosError); - + spy.mockRejectedValueOnce(nonAxiosError); const parameters = { _type: 'int256', _path: 'unknown', from: 'ETH' }; const aggregatedApiCall = fixtures.buildAggregatedRegularApiCall({ parameters }); const [logs, res] = await callApi({ type: 'regular', config: fixtures.buildConfig(), aggregatedApiCall }); @@ -256,6 +256,7 @@ describe('callApi', () => { const spy = jest.spyOn(adapter, 'buildAndExecuteRequest') as any; const axiosError = e; spy.mockRejectedValueOnce(axiosError); + spy.mockRejectedValueOnce(axiosError); const parameters = { _type: 'int256', _path: 'unknown', from: 'ETH' }; const aggregatedApiCall = fixtures.buildAggregatedRegularApiCall({ parameters }); @@ -388,7 +389,7 @@ describe('callApi', () => { expect.objectContaining({ parameters: { from: 'BTC', source: 'airnode', amount: '1' }, }), - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); @@ -432,7 +433,7 @@ describe('callApi', () => { expect.objectContaining({ parameters: { from: 'ETH', amount: '1' }, }), - { timeout: API_CALL_TIMEOUT } + { timeout: API_CALL_TIMEOUT / 3 } ); }); }); diff --git a/packages/airnode-node/src/api/index.ts b/packages/airnode-node/src/api/index.ts index 755b7b28c5..289dbe4cf6 100644 --- a/packages/airnode-node/src/api/index.ts +++ b/packages/airnode-node/src/api/index.ts @@ -205,18 +205,22 @@ export async function performApiCall( // We also pass the timeout to adapter to gracefully abort the request after the timeout // timeout passed to adapter will cause axios socket to hang until the timeout is reached // even if the attemptTimeoutMs is reached and the 2nd attempt is made - const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: (timeout * 2) / 3 }), { - retries: 1, - attemptTimeoutMs: [timeout / 3, (timeout * 2) / 3], - totalTimeoutMs: timeout, + const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: timeout / 3 }), { + totalTimeoutMs: timeout / 3, }); if (!goRes.success) { - const { aggregatedApiCall } = payload; - const log = logger.pend('ERROR', `Failed to call Endpoint:${aggregatedApiCall.endpointName}`, goRes.error); - // eslint-disable-next-line import/no-named-as-default-member - const axiosErrorMsg = axios.isAxiosError(goRes.error) ? errorMsgFromAxiosError(goRes.error) : ''; - const errorMessage = compact([RequestErrorMessage.ApiCallFailed, axiosErrorMsg]).join(' '); - return [[log], { success: false, errorMessage: errorMessage }]; + const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: (timeout * 2) / 3 }), { + totalTimeoutMs: (timeout * 2) / 3, + }); + if (!goRes.success) { + const { aggregatedApiCall } = payload; + const log = logger.pend('ERROR', `Failed to call Endpoint:${aggregatedApiCall.endpointName}`, goRes.error); + // eslint-disable-next-line import/no-named-as-default-member + const axiosErrorMsg = axios.isAxiosError(goRes.error) ? errorMsgFromAxiosError(goRes.error) : ''; + const errorMessage = compact([RequestErrorMessage.ApiCallFailed, axiosErrorMsg]).join(' '); + return [[log], { success: false, errorMessage: errorMessage }]; + } + return [[], { ...goRes.data }]; } return [[], { ...goRes.data }]; From 775d4944c00260462bd5861bd5b673ac03698427 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 22 Mar 2023 15:35:37 -0400 Subject: [PATCH 4/8] update e2e test --- packages/airnode-node/test/e2e/signed-data.feature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/airnode-node/test/e2e/signed-data.feature.ts b/packages/airnode-node/test/e2e/signed-data.feature.ts index 54cd2b42d2..b920bc4d68 100644 --- a/packages/airnode-node/test/e2e/signed-data.feature.ts +++ b/packages/airnode-node/test/e2e/signed-data.feature.ts @@ -35,6 +35,6 @@ it('makes a call for signed API data', async () => { // Verify that all internal parameters have been removed from the parameters forwarded to the API expect(adapter.buildAndExecuteRequest).toHaveBeenCalledWith( expect.objectContaining({ parameters: { from: 'ETH', amount: '1' } }), - { timeout: 30000 } + { timeout: 10000 } ); }); From 08023c4db0decc79f158f8264294b1ac687ff186 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Tue, 28 Mar 2023 10:07:49 -0400 Subject: [PATCH 5/8] add retry unit test --- packages/airnode-node/src/api/index.test.ts | 58 +++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/airnode-node/src/api/index.test.ts b/packages/airnode-node/src/api/index.test.ts index 5bc59f0dfb..10ba6308b9 100644 --- a/packages/airnode-node/src/api/index.test.ts +++ b/packages/airnode-node/src/api/index.test.ts @@ -220,6 +220,64 @@ describe('callApi', () => { ); }); + it('retries the API call if the first attempt fails', async () => { + const spy = jest.spyOn(adapter, 'buildAndExecuteRequest') as any; + spy.mockRejectedValueOnce(new Error('First attempt failed')); + spy.mockResolvedValueOnce({ data: { price: 1000 } }); + const requestedGasPrice = '100000000'; + const requestedMinConfirmations = '0'; + const parameters = { + _type: 'int256', + _path: 'price', + from: 'ETH', + _gasPrice: requestedGasPrice, + _minConfirmations: requestedMinConfirmations, + }; + + const [logs, res] = await callApi({ + type: 'regular', + config: fixtures.buildConfig(), + aggregatedApiCall: fixtures.buildAggregatedRegularApiCall({ parameters }), + }); + + expect(logs).toEqual([]); + expect(res).toEqual({ + success: true, + data: { + encodedValue: '0x0000000000000000000000000000000000000000000000000000000005f5e100', + signature: + '0xe92f5ee40ddb5aa42cab65fcdc025008b2bc026af80a7c93a9aac4e474f8a88f4f2bd861b9cf9a2b050bf0fd13e9714c4575cebbea658d7501e98c0963a5a38b1c', + }, + // _minConfirmations is processed before making API calls + reservedParameterOverrides: { + gasPrice: requestedGasPrice, + }, + }); + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith( + { + endpointName: 'convertToUSD', + ois: fixtures.buildOIS(), + parameters: { from: 'ETH', amount: '1' }, + metadata: { + chainId: '31337', + chainType: 'evm', + requestId: '0xf40127616f09d41b20891bcfd326957a0e3d5a5ecf659cff4d8106c04b024374', + requesterAddress: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512', + sponsorAddress: '0x2479808b1216E998309A727df8A0A98A1130A162', + sponsorWalletAddress: '0x1C1CEEF1a887eDeAB20219889971e1fd4645b55D', + }, + apiCredentials: [ + { + securitySchemeName: 'myApiSecurityScheme', + securitySchemeValue: 'supersecret', + }, + ], + }, + { timeout: (API_CALL_TIMEOUT * 2) / 3 } + ); + }); + it('returns an error if the API call fails to execute', async () => { const spy = jest.spyOn(adapter, 'buildAndExecuteRequest') as any; const nonAxiosError = new Error('A non-axios error'); From 15d937ba915d262832b4bc7c2f8bf9de9c769ea1 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 29 Mar 2023 08:16:56 -0400 Subject: [PATCH 6/8] refactor and add constant --- packages/airnode-node/src/api/index.test.ts | 18 +++++----- packages/airnode-node/src/api/index.ts | 40 ++++++++++----------- packages/airnode-node/src/constants.ts | 6 +++- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/airnode-node/src/api/index.test.ts b/packages/airnode-node/src/api/index.test.ts index 10ba6308b9..6d5061f335 100644 --- a/packages/airnode-node/src/api/index.test.ts +++ b/packages/airnode-node/src/api/index.test.ts @@ -4,7 +4,7 @@ import { AxiosError, AxiosHeaders } from 'axios'; import * as fixtures from '../../test/fixtures'; import { getExpectedTemplateIdV0 } from '../evm/templates'; import { ApiCallErrorResponse, RequestErrorMessage } from '../types'; -import { API_CALL_TIMEOUT } from '../constants'; +import { FIRST_API_CALL_TIMEOUT, SECOND_API_CALL_TIMEOUT } from '../constants'; import { callApi, verifyTemplateId } from '.'; describe('callApi', () => { @@ -63,7 +63,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); @@ -108,7 +108,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); @@ -146,7 +146,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); @@ -197,7 +197,7 @@ describe('callApi', () => { }, ], }, - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); @@ -216,7 +216,7 @@ describe('callApi', () => { expect.objectContaining({ parameters: { from: 'ETH', amount: '1' }, }), - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); @@ -274,7 +274,7 @@ describe('callApi', () => { }, ], }, - { timeout: (API_CALL_TIMEOUT * 2) / 3 } + { timeout: SECOND_API_CALL_TIMEOUT } ); }); @@ -447,7 +447,7 @@ describe('callApi', () => { expect.objectContaining({ parameters: { from: 'BTC', source: 'airnode', amount: '1' }, }), - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); @@ -491,7 +491,7 @@ describe('callApi', () => { expect.objectContaining({ parameters: { from: 'ETH', amount: '1' }, }), - { timeout: API_CALL_TIMEOUT / 3 } + { timeout: FIRST_API_CALL_TIMEOUT } ); }); }); diff --git a/packages/airnode-node/src/api/index.ts b/packages/airnode-node/src/api/index.ts index 289dbe4cf6..3d014307c5 100644 --- a/packages/airnode-node/src/api/index.ts +++ b/packages/airnode-node/src/api/index.ts @@ -9,7 +9,7 @@ import compact from 'lodash/compact'; import { postProcessApiSpecifications, preProcessApiSpecifications } from './processing'; import { getAirnodeWalletFromPrivateKey, deriveSponsorWalletFromMnemonic } from '../evm'; import { getReservedParameters } from '../adapters/http/parameters'; -import { API_CALL_TIMEOUT } from '../constants'; +import { FIRST_API_CALL_TIMEOUT, SECOND_API_CALL_TIMEOUT } from '../constants'; import { isValidRequestId } from '../evm/verification'; import { getExpectedTemplateIdV0, getExpectedTemplateIdV1 } from '../evm/templates'; import { @@ -201,29 +201,29 @@ export async function performApiCall( payload: ApiCallPayload ): Promise> { const options = buildOptions(payload); - const timeout = API_CALL_TIMEOUT; - // We also pass the timeout to adapter to gracefully abort the request after the timeout + const firstTimeout = FIRST_API_CALL_TIMEOUT; + const secondTimeout = SECOND_API_CALL_TIMEOUT; + // We also pass the timeout to adapter to gracefully abort the request after the timeout. // timeout passed to adapter will cause axios socket to hang until the timeout is reached - // even if the attemptTimeoutMs is reached and the 2nd attempt is made - const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: timeout / 3 }), { - totalTimeoutMs: timeout / 3, + // even if the totalTimeoutMs is reached and the 2nd attempt is made + const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: firstTimeout }), { + totalTimeoutMs: firstTimeout, }); - if (!goRes.success) { - const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: (timeout * 2) / 3 }), { - totalTimeoutMs: (timeout * 2) / 3, - }); - if (!goRes.success) { - const { aggregatedApiCall } = payload; - const log = logger.pend('ERROR', `Failed to call Endpoint:${aggregatedApiCall.endpointName}`, goRes.error); - // eslint-disable-next-line import/no-named-as-default-member - const axiosErrorMsg = axios.isAxiosError(goRes.error) ? errorMsgFromAxiosError(goRes.error) : ''; - const errorMessage = compact([RequestErrorMessage.ApiCallFailed, axiosErrorMsg]).join(' '); - return [[log], { success: false, errorMessage: errorMessage }]; - } + if (goRes.success) { return [[], { ...goRes.data }]; } - - return [[], { ...goRes.data }]; + const goRes2 = await go(() => adapter.buildAndExecuteRequest(options, { timeout: secondTimeout }), { + totalTimeoutMs: secondTimeout, + }); + if (goRes2.success) { + return [[], { ...goRes2.data }]; + } + const { aggregatedApiCall } = payload; + const log = logger.pend('ERROR', `Failed to call Endpoint:${aggregatedApiCall.endpointName}`, goRes2.error); + // eslint-disable-next-line import/no-named-as-default-member + const axiosErrorMsg = axios.isAxiosError(goRes2.error) ? errorMsgFromAxiosError(goRes2.error) : ''; + const errorMessage = compact([RequestErrorMessage.ApiCallFailed, axiosErrorMsg]).join(' '); + return [[log], { success: false, errorMessage: errorMessage }]; } export async function processSuccessfulApiCall( diff --git a/packages/airnode-node/src/constants.ts b/packages/airnode-node/src/constants.ts index a173c2ccee..9357f73cf3 100644 --- a/packages/airnode-node/src/constants.ts +++ b/packages/airnode-node/src/constants.ts @@ -1,5 +1,9 @@ // The maximum time a single API call has before it is timed out -export const API_CALL_TIMEOUT = 30_000; +export const FIRST_API_CALL_TIMEOUT = 10_000; + +// The maximum time a single API call has before it is timed out +// This is used for the second API call in the case of a retry +export const SECOND_API_CALL_TIMEOUT = 20_000; // The number of past blocks to lookup when fetching Airnode RRP events. export const BLOCK_COUNT_HISTORY_LIMIT = 300; From 3f67378008ebef00dfabbae6baec7839b59ef5d7 Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 29 Mar 2023 08:20:17 -0400 Subject: [PATCH 7/8] update comment --- packages/airnode-node/src/constants.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/airnode-node/src/constants.ts b/packages/airnode-node/src/constants.ts index 9357f73cf3..77e0dea4f0 100644 --- a/packages/airnode-node/src/constants.ts +++ b/packages/airnode-node/src/constants.ts @@ -1,8 +1,7 @@ // The maximum time a single API call has before it is timed out export const FIRST_API_CALL_TIMEOUT = 10_000; -// The maximum time a single API call has before it is timed out -// This is used for the second API call in the case of a retry +// The maximum time a single API call has before it is timed out in the second attempt export const SECOND_API_CALL_TIMEOUT = 20_000; // The number of past blocks to lookup when fetching Airnode RRP events. From c9b67b9a939e7d96c8486a28d015b1c77c680c0d Mon Sep 17 00:00:00 2001 From: Ashar2shahid Date: Wed, 29 Mar 2023 09:47:33 -0400 Subject: [PATCH 8/8] PR suggestions --- packages/airnode-node/src/api/index.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/airnode-node/src/api/index.ts b/packages/airnode-node/src/api/index.ts index 3d014307c5..e71e5c85b2 100644 --- a/packages/airnode-node/src/api/index.ts +++ b/packages/airnode-node/src/api/index.ts @@ -201,27 +201,25 @@ export async function performApiCall( payload: ApiCallPayload ): Promise> { const options = buildOptions(payload); - const firstTimeout = FIRST_API_CALL_TIMEOUT; - const secondTimeout = SECOND_API_CALL_TIMEOUT; // We also pass the timeout to adapter to gracefully abort the request after the timeout. // timeout passed to adapter will cause axios socket to hang until the timeout is reached // even if the totalTimeoutMs is reached and the 2nd attempt is made - const goRes = await go(() => adapter.buildAndExecuteRequest(options, { timeout: firstTimeout }), { - totalTimeoutMs: firstTimeout, + const goAttempt1 = await go(() => adapter.buildAndExecuteRequest(options, { timeout: FIRST_API_CALL_TIMEOUT }), { + totalTimeoutMs: FIRST_API_CALL_TIMEOUT, }); - if (goRes.success) { - return [[], { ...goRes.data }]; + if (goAttempt1.success) { + return [[], { ...goAttempt1.data }]; } - const goRes2 = await go(() => adapter.buildAndExecuteRequest(options, { timeout: secondTimeout }), { - totalTimeoutMs: secondTimeout, + const goAttempt2 = await go(() => adapter.buildAndExecuteRequest(options, { timeout: SECOND_API_CALL_TIMEOUT }), { + totalTimeoutMs: SECOND_API_CALL_TIMEOUT, }); - if (goRes2.success) { - return [[], { ...goRes2.data }]; + if (goAttempt2.success) { + return [[], { ...goAttempt2.data }]; } const { aggregatedApiCall } = payload; - const log = logger.pend('ERROR', `Failed to call Endpoint:${aggregatedApiCall.endpointName}`, goRes2.error); + const log = logger.pend('ERROR', `Failed to call Endpoint:${aggregatedApiCall.endpointName}`, goAttempt2.error); // eslint-disable-next-line import/no-named-as-default-member - const axiosErrorMsg = axios.isAxiosError(goRes2.error) ? errorMsgFromAxiosError(goRes2.error) : ''; + const axiosErrorMsg = axios.isAxiosError(goAttempt2.error) ? errorMsgFromAxiosError(goAttempt2.error) : ''; const errorMessage = compact([RequestErrorMessage.ApiCallFailed, axiosErrorMsg]).join(' '); return [[log], { success: false, errorMessage: errorMessage }]; }