diff --git a/docker-compose.yml b/docker-compose.yml index 5b4c5ee71c..984f72386d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -138,7 +138,7 @@ services: - SERVICE_BROKER=nats://nats:4222 - GEOIP_JSON=${GEOIP_JSON} - LISK_APP_WS=${LISK_APP_WS} - - USE_HTTP_API=${USE_HTTP_API} + - USE_LISK_HTTP_API=${USE_LISK_HTTP_API} - LISK_APP_HTTP=${LISK_APP_HTTP} - SERVICE_BROKER_TIMEOUT=${SERVICE_BROKER_TIMEOUT} - SERVICE_LOG_CONSOLE=${SERVICE_LOG_CONSOLE} diff --git a/docker/example.env b/docker/example.env index e7bfbf5446..68983fc338 100644 --- a/docker/example.env +++ b/docker/example.env @@ -55,7 +55,7 @@ # Lisk ecosystem configuration LISK_APP_WS=ws://host.docker.internal:7887 -# USE_HTTP_API=true +# USE_LISK_HTTP_API=true # LISK_APP_HTTP=http://host.docker.internal:7887 # Lisk Service geolocation backend (empty = disabled) diff --git a/docs/antora/modules/ROOT/pages/configuration/index.adoc b/docs/antora/modules/ROOT/pages/configuration/index.adoc index 14352f5e17..08f882b511 100644 --- a/docs/antora/modules/ROOT/pages/configuration/index.adoc +++ b/docs/antora/modules/ROOT/pages/configuration/index.adoc @@ -373,7 +373,7 @@ By default, it is set to `ws://127.0.0.1:7887`. By default, it is set to `http://127.0.0.1:7887`. | http://127.0.0.1:7887 -| `USE_HTTP_API` +| `USE_LISK_HTTP_API` | boolean | Boolean flag to enable HTTP-API based connection to the Lisk application node. | true diff --git a/ecosystem.config.js b/ecosystem.config.js index 88a40e8b3a..871a5a30f6 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -114,7 +114,7 @@ module.exports = { SERVICE_BROKER: 'redis://lisk:password@127.0.0.1:6379/0', // USE_LISK_IPC_CLIENT: true, // LISK_APP_DATA_PATH: '~/.lisk/lisk-core', - // USE_HTTP_API: true, + // USE_LISK_HTTP_API: true, // LISK_APP_HTTP: 'http://127.0.0.1:7887', // LISK_APP_WS: 'ws://127.0.0.1:7887', // GEOIP_JSON: 'https://geoip.lisk.com/json', diff --git a/services/blockchain-connector/README.md b/services/blockchain-connector/README.md index 3ced3a9faa..dce73ac917 100644 --- a/services/blockchain-connector/README.md +++ b/services/blockchain-connector/README.md @@ -29,7 +29,7 @@ A list of the most commonly used environment variables is presented below: - `SERVICE_BROKER`: URL of the microservice message broker (NATS or Redis). - `LISK_APP_WS`: URL to connect with the Lisk SDK-based application node over WebSocket. By default, it is set to `ws://127.0.0.1:7887`. - `LISK_APP_HTTP`: URL to connect with the Lisk SDK-based application node over HTTP(s). By default, it is set to `http://127.0.0.1:7887`. -- `USE_HTTP_API`: Boolean flag to enable HTTP-API based connection to the Lisk SDK-based application node. +- `USE_LISK_HTTP_API`: Boolean flag to enable HTTP-API based connection to the Lisk SDK-based application node. - `USE_LISK_IPC_CLIENT`: Boolean flag to enable IPC-based connection to the Lisk SDK-based application node. Not applicable to a docker-based setup. - `LISK_APP_DATA_PATH`: Data path to connect with the Lisk SDK-based application node over IPC. Not applicable to a docker-based setup. - `GENESIS_BLOCK_URL`: URL of the Lisk SDK-based application' genesis block. Only to be used when the genesis block is large enough to be transmitted over API calls within the timeout. diff --git a/services/blockchain-connector/config.js b/services/blockchain-connector/config.js index 45e4b7b59f..f3342dea64 100644 --- a/services/blockchain-connector/config.js +++ b/services/blockchain-connector/config.js @@ -42,7 +42,7 @@ config.endpoints.geoip = process.env.GEOIP_JSON || 'https://geoip.lisk.com/json' /** * API Client related settings */ -config.isUseHttpApi = Boolean(String(process.env.USE_HTTP_API).toLowerCase() === 'true'); // Disabled by default +config.isUseHttpApi = Boolean(String(process.env.USE_LISK_HTTP_API).toLowerCase() === 'true'); // Disabled by default config.isUseLiskIPCClient = Boolean( String(process.env.USE_LISK_IPC_CLIENT).toLowerCase() === 'true', ); diff --git a/services/blockchain-connector/shared/sdk/client.js b/services/blockchain-connector/shared/sdk/client.js index bdf60deaaf..f6e90d05e9 100644 --- a/services/blockchain-connector/shared/sdk/client.js +++ b/services/blockchain-connector/shared/sdk/client.js @@ -27,7 +27,11 @@ const delay = require('../utils/delay'); const logger = Logger(); // Constants -const timeoutMessage = 'Response not received in'; +const HTTP_TIMEOUT_STATUS = 'ETIMEDOUT'; +const RPC_TIMEOUT_MESSAGE = 'Response not received in'; +const TIMEOUT_REGEX_STR = `(?:${HTTP_TIMEOUT_STATUS}|${RPC_TIMEOUT_MESSAGE})`; +const TIMEOUT_REGEX = new RegExp(TIMEOUT_REGEX_STR); + const liskAddressWs = config.endpoints.liskWs; const liskAddressHttp = config.endpoints.liskHttp; const NUM_REQUEST_RETRIES = config.apiClient.request.maxRetries; @@ -46,22 +50,21 @@ const checkIsClientAlive = async clientCache => return resolve(false); } - // Skip heartbeat check for IPC client if (config.isUseLiskIPCClient) { - return resolve(true); + return resolve(clientCache._channel.isAlive); } const heartbeatCheckBeginTime = Date.now(); - const wsInstance = clientCache._channel._ws; + const wsClientInstance = clientCache._channel._ws; // eslint-disable-next-line no-use-before-define const boundPongListener = () => pongListener(resolve); - wsInstance.on('pong', boundPongListener); - wsInstance.ping(() => {}); + wsClientInstance.on('pong', boundPongListener); + wsClientInstance.ping(() => {}); // eslint-disable-next-line consistent-return const timeout = setTimeout(() => { - wsInstance.removeListener('pong', boundPongListener); + wsClientInstance.removeListener('pong', boundPongListener); logger.debug( `Did not receive API client pong after ${Date.now() - heartbeatCheckBeginTime}ms.`, ); @@ -70,7 +73,7 @@ const checkIsClientAlive = async clientCache => const pongListener = res => { clearTimeout(timeout); - wsInstance.removeListener('pong', boundPongListener); + wsClientInstance.removeListener('pong', boundPongListener); logger.debug(`Received API client pong in ${Date.now() - heartbeatCheckBeginTime}ms.`); return res(true); }; @@ -118,40 +121,47 @@ const getApiClient = async () => { return cachedApiClients[0]; }; -const isResponse2XX = response => String(response.status).startsWith('2'); +const is2XXResponse = response => String(response.status).startsWith('2'); +const isSuccessResponse = response => is2XXResponse(response) && response.data.result; + +const buildHTTPResponse = (endpoint, params, response) => { + if (isSuccessResponse(response)) return response.data.result; -let id = -1; + const errorMessage = + response.data && response.data.error + ? response.data.error.message + : `${response.status}: ${response.message}`; + logger.trace( + `Error invoking endpoint '${endpoint}' with params ${JSON.stringify(params)}:\n${errorMessage}`, + ); + throw new Error(errorMessage); +}; + +let id = 0; // eslint-disable-next-line consistent-return const invokeEndpoint = async (endpoint, params = {}, numRetries = NUM_REQUEST_RETRIES) => { let retriesLeft = numRetries; do { - id++; try { if (config.isUseHttpApi) { + // HTTP API-based communication with the Lisk app node const rpcRequest = { jsonrpc: '2.0', - id, + id: id++, method: endpoint, params, }; const response = await HTTP.post(`${liskAddressHttp}/rpc`, rpcRequest); - return isResponse2XX(response) - ? response.data.result - : (() => { - logger.trace( - `Error when invoking endpoint ${endpoint} with params ${JSON.stringify(params)}: ${ - response.data.error.message - }`, - ); - throw new Error(response.data.error); - })(); + return buildHTTPResponse(endpoint, params, response); } + + // WS and IPC client-based communication with the Lisk app node const apiClient = await getApiClient(); const response = await apiClient._channel.invoke(endpoint, params); return response; } catch (err) { - if (err.message.includes(timeoutMessage)) { + if (TIMEOUT_REGEX.test(err.message)) { if (!retriesLeft) { const exceptionMsg = Object.getOwnPropertyNames(params).length ? `Request timed out when calling '${endpoint}' with params:\n${JSON.stringify( @@ -243,12 +253,11 @@ const resetApiClientListener = async () => { }; Signals.get('resetApiClient').add(resetApiClientListener); -// Check periodically for client aliveness and refill cached clients pool -(async () => { - if (config.isUseHttpApi) return; +if (!config.isUseHttpApi) { + refreshClientsCache(); // Initialize the client cache - // eslint-disable-next-line no-constant-condition - while (true) { + // Periodically ensure API client liveliness + setInterval(async () => { const cacheRefreshStartTime = Date.now(); await refreshClientsCache(); logger.debug( @@ -256,12 +265,11 @@ Signals.get('resetApiClient').add(resetApiClientListener); cachedApiClients.length } API client(s) in the pool.`, ); - await delay(CLIENT_ALIVE_ASSUMPTION_TIME); - } -})(); + }, CLIENT_ALIVE_ASSUMPTION_TIME); +} module.exports = { - timeoutMessage, + TIMEOUT_REGEX, getApiClient, invokeEndpoint, diff --git a/services/blockchain-connector/shared/sdk/genesisBlock.js b/services/blockchain-connector/shared/sdk/genesisBlock.js index d06dbee22b..d69865f19a 100644 --- a/services/blockchain-connector/shared/sdk/genesisBlock.js +++ b/services/blockchain-connector/shared/sdk/genesisBlock.js @@ -21,7 +21,7 @@ const { const { getNodeInfo } = require('./endpoints_1'); const { getGenesisBlockFromFS } = require('./blocksUtils'); -const { timeoutMessage, invokeEndpoint } = require('./client'); +const { TIMEOUT_REGEX, invokeEndpoint } = require('./client'); const { formatBlock } = require('./formatter'); const logger = Logger(); @@ -57,7 +57,7 @@ const getGenesisBlock = async (isIncludeAssets = false) => { const block = await invokeEndpoint('chain_getBlockByHeight', { height }); return block; } catch (err) { - if (err.message.includes(timeoutMessage)) { + if (TIMEOUT_REGEX.test(err.message)) { throw new TimeoutException("Request timed out when calling 'getGenesisBlock'."); } throw err; @@ -85,7 +85,7 @@ const getGenesisConfig = async () => { } return genesisConfig; } catch (err) { - if (err.message.includes(timeoutMessage)) { + if (TIMEOUT_REGEX.test(err.message)) { throw new TimeoutException("Request timed out when calling 'getGenesisConfig'."); } throw err;