diff --git a/src/routes/v4/route-utils.js b/src/routes/v4/route-utils.js deleted file mode 100644 index cab689c..0000000 --- a/src/routes/v4/route-utils.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - A private library of utility functions used by several different routes. -*/ - -'use strict' - -const axios = require('axios') -const wlogger = require('../../util/winston-logging') - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -module.exports = { - validateNetwork, // Prevents a common user error - setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars. - decodeError, // Extract and interpret error messages. - validateArraySize, // Ensure the passed array meets rate limiting requirements. - getAxiosOptions -} - -// This function expects the Request Express.js object and an array as input. -// The array is then validated against freemium and pro-tier rate limiting -// requirements. A boolean is returned to indicate if the array size if valid -// or not. -function validateArraySize (req, array) { - const FREEMIUM_INPUT_SIZE = 20 - const PRO_INPUT_SIZE = 20 - - if (req.locals && req.locals.proLimit) { - if (array.length <= PRO_INPUT_SIZE) return true - } else if (array.length <= FREEMIUM_INPUT_SIZE) { - return true - } - - return false -} - -// Returns true if user-provided cash address matches the correct network, -// mainnet or testnet. If NETWORK env var is not defined, it returns false. -// This prevent a common user-error issue that is easy to make: passing a -// testnet address into rest.bitcoin.com or passing a mainnet address into -// trest.bitcoin.com. -function validateNetwork (addr) { - try { - const network = process.env.NETWORK - - // Return false if NETWORK is not defined. - if (!network || network === '') { - console.log('Warning: NETWORK environment variable is not defined!') - return false - } - - // Convert the user-provided address to a cashaddress, for easy detection - // of the intended network. - const cashAddr = bchjs.Address.toCashAddress(addr) - - // Return true if the network and address both match testnet - const addrIsTest = bchjs.Address.isTestnetAddress(cashAddr) - if (network === 'testnet' && addrIsTest) return true - - // Return true if the network and address both match mainnet - const addrIsMain = bchjs.Address.isMainnetAddress(cashAddr) - if (network === 'mainnet' && addrIsMain) return true - - return false - } catch (err) { - wlogger.error('Error in validateNetwork()') - return false - } -} - -// Dynamically set these based on env vars. Allows unit testing. -function setEnvVars () { - const BitboxHTTP = axios.create({ - baseURL: process.env.RPC_BASEURL, - timeout: 15000 - }) - const username = process.env.RPC_USERNAME - const password = process.env.RPC_PASSWORD - - const requestConfig = { - method: 'post', - auth: { - username: username, - password: password - }, - data: { - jsonrpc: '1.0' - } - } - - return { BitboxHTTP, username, password, requestConfig } -} - -// Axios options used when calling axios.post() to talk with a full node. -function getAxiosOptions () { - return { - method: 'post', - baseURL: process.env.RPC_BASEURL, - timeout: 15000, - auth: { - username: process.env.RPC_USERNAME, - password: process.env.RPC_PASSWORD - }, - data: { - jsonrpc: '1.0' - } - } -} - -// Error messages returned by a full node can be burried pretty deep inside the -// error object returned by Axios. This function attempts to extract and interpret -// error messages. -// Returns an object. If successful, obj.msg is a string. -// If there is a failure, obj.msg is false. -function decodeError (err) { - try { - // Attempt to extract the full node error message. - if ( - err.response && - err.response.data && - err.response.data.error && - err.response.data.error.message - ) { - return { msg: err.response.data.error.message, status: 400 } - } - - // Attempt to extract the Insight error message - if (err.response && err.response.data) { - return { msg: err.response.data, status: err.response.status } - } - - // console.log(`err.message: ${err.message}`) - // console.log(`err: `, err) - - // Attempt to detect a network connection error. - if (err.message && err.message.indexOf('ENOTFOUND') > -1) { - return { - msg: - 'Network error: Could not communicate with full node or other external service.', - status: 503 - } - } - - // Different kind of network error - if (err.message && err.message.indexOf('ENETUNREACH') > -1) { - return { - msg: - 'Network error: Could not communicate with full node or other external service.', - status: 503 - } - } - - // Different kind of network error - if (err.message && err.message.indexOf('EAI_AGAIN') > -1) { - return { - msg: - 'Network error: Could not communicate with full node or other external service.', - status: 503 - } - } - - // Axios timeout (aborted) error, or service is down (connection refused). - if ( - err.code && - (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED') - ) { - return { - msg: - 'Network error: Could not communicate with full node or other external service.', - status: 503 - } - } - - // Handle general Error objects. - if (err.message) { - return { - message: err.message, - status: 422 - } - } - - return { msg: false, status: 500 } - } catch (err) { - console.error('unhandled error in route-utils.js/decodeError(): ', err) - wlogger.error('unhandled error in route-utils.js/decodeError(): ', err) - return { msg: false, status: 500 } - } -} diff --git a/src/routes/v4/slp.js b/src/routes/v4/slp.js index 6beec5c..1abf58f 100644 --- a/src/routes/v4/slp.js +++ b/src/routes/v4/slp.js @@ -104,8 +104,13 @@ class Slp { // DRY error handler. errorHandler (err, res) { + // console.error('Entering slp.js/errorHandler(). err: ', err) + // Attempt to decode the error message. const { msg, status } = _this.routeUtils.decodeError(err) + console.log('slp.js/errorHandler msg from decodeError: ', msg) + console.log('slp.js/errorHandler status from decodeError: ', status) + if (msg) { res.status(status) return res.json({ error: msg }) @@ -2051,12 +2056,13 @@ class Slp { return res.json({ slpUtxos: utxos }) } catch (err) { wlogger.error('Error in slp.js/hydrateUtxos().', err) - console.error('Error in slp.js/hydrateUtxos().', err) + // console.error('Error in slp.js/hydrateUtxos().', err) // Decode the error message. const { msg, status } = routeUtils.decodeError(err) - console.log('msg: ', msg) - console.log('status: ', status) + // console.log('msg: ', msg) + // console.log('status: ', status) + if (msg) { res.status(status) return res.json({ error: msg, message: msg, success: false }) diff --git a/src/routes/v4/util.js b/src/routes/v4/util.js index 7862aad..e3d20a7 100644 --- a/src/routes/v4/util.js +++ b/src/routes/v4/util.js @@ -4,7 +4,9 @@ const express = require('express') const router = express.Router() const axios = require('axios') -const routeUtils = require('./route-utils') +const RouteUtils = require('../../util/route-utils') +const routeUtils = new RouteUtils() + const wlogger = require('../../util/winston-logging') const util = require('util') diff --git a/src/routes/v4/xpub.js b/src/routes/v4/xpub.js index fa1d9d4..e3410c9 100644 --- a/src/routes/v4/xpub.js +++ b/src/routes/v4/xpub.js @@ -5,8 +5,10 @@ 'use strict' const express = require('express') -// const axios = require('axios') -const routeUtils = require('./route-utils') + +const RouteUtils = require('../../util/route-utils') +const routeUtils = new RouteUtils() + const wlogger = require('../../util/winston-logging') // const router = express.Router() diff --git a/src/util/route-utils.js b/src/util/route-utils.js index d5a98ff..f297469 100644 --- a/src/util/route-utils.js +++ b/src/util/route-utils.js @@ -154,6 +154,23 @@ class RouteUtils { } } + // Handle 429 errors thrown by nginx + if (err.error) { + // console.log('decodeError: err: ', err) + + if (err.error.includes('429 Too Many Requests')) { + const internalMsg = + '429 error thrown by nginx caught by route-utils.js/decodeError()' + console.error(internalMsg) + wlogger.error(internalMsg) + + return { + msg: '429 Too Many Requests', + status: 429 + } + } + } + // Handle general Error objects. if (err.message) { return { @@ -169,6 +186,29 @@ class RouteUtils { return { msg: false, status: 500 } } } + + // Dynamically set these based on env vars. Allows unit testing. + setEnvVars () { + const BitboxHTTP = axios.create({ + baseURL: process.env.RPC_BASEURL, + timeout: 15000 + }) + const username = process.env.RPC_USERNAME + const password = process.env.RPC_PASSWORD + + const requestConfig = { + method: 'post', + auth: { + username: username, + password: password + }, + data: { + jsonrpc: '1.0' + } + } + + return { BitboxHTTP, username, password, requestConfig } + } } module.exports = RouteUtils