diff --git a/src/routes/v3/full-node/blockchain2.js b/src/routes/v3/full-node/blockchain2.js index 9b345ee..a7c103f 100644 --- a/src/routes/v3/full-node/blockchain2.js +++ b/src/routes/v3/full-node/blockchain2.js @@ -7,14 +7,16 @@ const express = require("express") const router = express.Router() +const axios = require("axios") +const wlogger = require("../../../util/winston-logging") + +const RouteUtils = require("../route-utils2") +const routeUtils = new RouteUtils() + // Used to convert error messages to strings, to safely pass to users. const util = require("util") util.inspect.defaultOptions = { depth: 1 } -const axios = require("axios") -const routeUtils = require("../route-utils") -const wlogger = require("../../../util/winston-logging") - const BCHJS = require("@chris.troutner/bch-js") const bchjs = new BCHJS() @@ -26,6 +28,7 @@ class Blockchain { this.bchjs = bchjs this.axios = axios + this.routeUtils = routeUtils this.router = router this.router.get("/", this.root) @@ -40,7 +43,7 @@ class Blockchain { // DRY error handler. errorHandler(err, res) { // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) + const { msg, status } = this.routeUtils.decodeError(err) if (msg) { res.status(status) return res.json({ error: msg }) @@ -65,7 +68,7 @@ class Blockchain { async getBestBlockHash(req, res, next) { try { // Axios options - const options = routeUtils.getAxiosOptions() + const options = this.routeUtils.getAxiosOptions() options.data.id = "getbestblockhash" options.data.method = "getbestblockhash" options.data.params = [] @@ -110,7 +113,7 @@ class Blockchain { async getBlockchainInfo(req, res, next) { try { // Axios options - const options = routeUtils.getAxiosOptions() + const options = this.routeUtils.getAxiosOptions() options.data.id = "getblockchaininfo" options.data.method = "getblockchaininfo" options.data.params = [] diff --git a/src/routes/v3/route-utils2.js b/src/routes/v3/route-utils2.js new file mode 100644 index 0000000..f7315d3 --- /dev/null +++ b/src/routes/v3/route-utils2.js @@ -0,0 +1,163 @@ +/* + 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("@chris.troutner/bch-js") +const bchjs = new BCHJS() + +let _this + +class RouteUtils { + constructor() { + _this = this + + this.bchjs = bchjs + this.axios = axios + } + + // 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. + 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 + } + + // Axios options used when calling axios.post() to talk with a full node. + 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" + } + } + } + + // 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. + 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 = this.bchjs.Address.toCashAddress(addr) + + // Return true if the network and address both match testnet + const addrIsTest = this.bchjs.Address.isTestnetAddress(cashAddr) + if (network === "testnet" && addrIsTest) return true + + // Return true if the network and address both match mainnet + const addrIsMain = this.bchjs.Address.isMainnetAddress(cashAddr) + if (network === "mainnet" && addrIsMain) return true + + return false + } catch (err) { + logger.error(`Error in validateNetwork()`) + return false + } + } + + // 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. + 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 + } + } + + return { msg: false, status: 500 } + } catch (err) { + wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err) + return { msg: false, status: 500 } + } + } +} + +module.exports = RouteUtils diff --git a/test/v3/blockchain2.js b/test/v3/blockchain2.js index 3cb0a0b..debaa2d 100644 --- a/test/v3/blockchain2.js +++ b/test/v3/blockchain2.js @@ -28,7 +28,7 @@ const mockData = require("./mocks/blockchain-mock") let originalEnvVars // Used during transition from integration to unit tests. -describe("#BlockchainRouter", () => { +describe("#BlockchainRouter2", () => { let req, res let sandbox