From b26b2fffe5fce067a27c310d171ab626dc5817c1 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Jul 2019 20:26:36 -0700 Subject: [PATCH] added Insight and block routes back to v3 routes --- src/app.js | 8 + src/routes/v3/insight/block.js | 279 +++++++++++++++++++++++++++++++++ src/routes/v3/insight/index.js | 38 +++++ 3 files changed, 325 insertions(+) create mode 100644 src/routes/v3/insight/block.js create mode 100644 src/routes/v3/insight/index.js diff --git a/src/app.js b/src/app.js index 751ed53..8fef0ff 100644 --- a/src/app.js +++ b/src/app.js @@ -56,6 +56,10 @@ const rawtransactionsV3 = require("./routes/v3/rawtransactions") const utilV3 = require("./routes/v3/util") const slpV3 = require("./routes/v3/slp") const xpubV3 = require("./routes/v3/xpub") +const bitcoreV3 = require("./routes/v3/bitcore") +const blockbookV3 = require("./routes/v3/blockbook") +const insightV3 = require("./routes/v3/insight") +const insightBlockV3 = require("./routes/v3/insight/block") require("dotenv").config() @@ -135,6 +139,10 @@ app.use(`/${v3prefix}/` + `rawtransactions`, rawtransactionsV3.router) app.use(`/${v3prefix}/` + `util`, utilV3.router) app.use(`/${v3prefix}/` + `slp`, slpV3.router) app.use(`/${v3prefix}/` + `xpub`, xpubV3.router) +app.use(`/${v3prefix}/` + `bitcore`, bitcoreV3.router) +app.use(`/${v3prefix}/` + `blockbook`, blockbookV3.router) +app.use(`/${v3prefix}/` + `insight`, insightV3.router) +app.use(`/${v3prefix}/` + `insight/block`, insightBlockV3.router) // catch 404 and forward to error handler app.use((req, res, next) => { diff --git a/src/routes/v3/insight/block.js b/src/routes/v3/insight/block.js new file mode 100644 index 0000000..ce8a964 --- /dev/null +++ b/src/routes/v3/insight/block.js @@ -0,0 +1,279 @@ +"use strict" + +const express = require("express") +const requestUtils = require("../services/requestUtils") +const axios = require("axios") +const bitbox = require("../services/bitbox") +const wlogger = require("../../../util/winston-logging") + +const routeUtils = require("../route-utils") + +// Used for processing error messages before sending them to the user. +const util = require("util") +util.inspect.defaultOptions = { depth: 3 } + +const router = express.Router() +//const BitboxHTTP = bitbox.getInstance() + +router.get("/", root) +router.get("/detailsByHash/:hash", detailsByHashSingle) +router.post("/detailsByHash", detailsByHashBulk) +router.get("/detailsByHeight/:height", detailsByHeightSingle) +router.post("/detailsByHeight", detailsByHeightBulk) + +function root(req, res, next) { + return res.json({ status: "block" }) +} + +// Call the insight server to get block details based on the hash. +async function detailsByHashSingle(req, res, next) { + try { + const hash = req.params.hash + + // Reject if hash is empty + if (!hash || hash === "") { + res.status(400) + return res.json({ error: "hash must not be empty" }) + } + + const response = await axios.get( + `${process.env.BITCOINCOM_BASEURL}block/${hash}` + ) + //console.log(`response.data: ${JSON.stringify(response.data,null,2)}`) + + const parsed = response.data + return res.json(parsed) + } catch (error) { + //console.log(`error object: ${util.inspect(error)}`) + + // Attempt to decode the error message. + const { msg, status } = routeUtils.decodeError(error) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } + + if (error.response && error.response.status === 404) { + res.status(404) + return res.json({ error: "Not Found" }) + } + + // Write out error to error log. + //logger.error(`Error in block/detailsByHash: `, error) + wlogger.error(`Error in block.ts/detailsByHashSingle().`, error) + + res.status(500) + return res.json({ error: util.inspect(error) }) + } +} + +async function detailsByHashBulk(req, res, next) { + try { + const hashes = req.body.hashes + + // Reject if hashes is not an array. + if (!Array.isArray(hashes)) { + res.status(400) + return res.json({ + error: "hashes needs to be an array. Use GET for single address." + }) + } + + // Enforce array size rate limits + if (!routeUtils.validateArraySize(req, hashes)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: `Array too large.` + }) + } + + // Validate each hash in the array. + for (let i = 0; i < hashes.length; i++) { + const thisHash = hashes[i] + + if (thisHash.length !== 64) { + res.status(400) + return res.json({ + error: `Invalid hash. Double check your hash is valid: ${thisHash}` + }) + } + } + + // Loop through each hash and creates an array of promises + const axiosPromises = hashes.map(async hash => + axios.get(`${process.env.BITCOINCOM_BASEURL}block/${hash}`) + ) + + // Wait for all parallel promises to return. + const axiosResult = await axios.all(axiosPromises) + + // Extract the data component from the axios response. + const result = axiosResult.map(x => x.data) + //console.log(`result: ${util.inspect(result)}`) + + res.status(200) + return res.json(result) + } catch (error) { + // Attempt to decode the error message. + const { msg, status } = routeUtils.decodeError(error) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } + + if (error.response && error.response.status === 404) { + res.status(404) + return res.json({ error: "Not Found" }) + } + + // Write out error to error log. + //logger.error(`Error in block/detailsByHash: `, error) + wlogger.error(`Error in block.ts/detailsByHashBulk().`, error) + + res.status(500) + return res.json({ error: util.inspect(error) }) + } +} + +// Call the Full Node to get block hash based on height, then call the Insight +// server to get details from that hash. +async function detailsByHeightSingle(req, res, next) { + try { + const height = req.params.height + + // Reject if id is empty + if (!height || height === "") { + res.status(400) + return res.json({ error: "height must not be empty" }) + } + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + + requestConfig.data.id = "getblockhash" + requestConfig.data.method = "getblockhash" + requestConfig.data.params = [parseInt(height)] + + const response = await BitboxHTTP(requestConfig) + + const hash = response.data.result + //console.log(`response.data: ${util.inspect(response.data)}`) + + // Call detailsByHashSingle now that the hash has been retrieved. + req.params.hash = hash + return detailsByHashSingle(req, res, next) + } catch (err) { + // Attempt to decode the error message. + const { msg, status } = routeUtils.decodeError(err) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } + + // Write out error to error log. + //logger.error(`Error in control/getInfo: `, error) + wlogger.error(`Error in block.ts/detailsByHeightSingle().`, err) + + res.status(500) + return res.json({ error: util.inspect(err) }) + } +} + +async function detailsByHeightBulk(req, res, next) { + try { + const heights = req.body.heights + + // Reject if heights is not an array. + if (!Array.isArray(heights)) { + res.status(400) + return res.json({ + error: "heights needs to be an array. Use GET for single height." + }) + } + + // Enforce array size rate limits + if (!routeUtils.validateArraySize(req, heights)) { + res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 + return res.json({ + error: `Array too large.` + }) + } + + wlogger.debug(`Executing detailsByHeight with these heights: `, heights) + + // Validate each element in the address array. + for (let i = 0; i < heights.length; i++) { + const thisHeight = heights[i] + + // Reject if id is empty + if (!thisHeight || thisHeight === "") { + res.status(400) + return res.json({ error: "height must not be empty" }) + } + } + + const { + BitboxHTTP, + username, + password, + requestConfig + } = routeUtils.setEnvVars() + + // Loop through each height and creates an array of requests to call in parallel + const promises = heights.map(async height => { + requestConfig.data.id = "getblockhash" + requestConfig.data.method = "getblockhash" + requestConfig.data.params = [parseInt(height)] + + const response = await BitboxHTTP(requestConfig) + + const hash = response.data.result + + const axiosResult = await axios.get( + `${process.env.BITCOINCOM_BASEURL}block/${hash}` + ) + + return axiosResult.data + }) + + // Wait for all parallel Insight requests to return. + const result = await axios.all(promises) + + res.status(200) + return res.json(result) + } catch (error) { + // Attempt to decode the error message. + const { msg, status } = routeUtils.decodeError(error) + if (msg) { + res.status(status) + return res.json({ error: msg }) + } + + if (error.response && error.response.status === 404) { + res.status(404) + return res.json({ error: "Not Found" }) + } + + // Write out error to error log. + //logger.error(`Error in block/detailsByHash: `, error) + wlogger.error(`Error in block.ts/detailsByHeightBulk().`, error) + + res.status(500) + return res.json({ error: util.inspect(error) }) + } +} + +module.exports = { + router, + testableComponents: { + root, + detailsByHashSingle, + detailsByHashBulk, + detailsByHeightSingle, + detailsByHeightBulk + } +} diff --git a/src/routes/v3/insight/index.js b/src/routes/v3/insight/index.js new file mode 100644 index 0000000..ad5037d --- /dev/null +++ b/src/routes/v3/insight/index.js @@ -0,0 +1,38 @@ +/* + Insight API route + A parent route that encompases the other libraries. +*/ + +"use strict" + +const express = require("express") +const requestUtils = require("../services/requestUtils") +const axios = require("axios") +const routeUtils = require("../route-utils") +const wlogger = require("../../../util/winston-logging") + +const router = express.Router() + +// Used for processing error messages before sending them to the user. +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +const BITBOXJS = require("@chris.troutner/bitbox-js") +const BITBOX = new BITBOXJS() + +//const BITCORE_URL = process.env.BITCORE_URL + +// Connect the route endpoints to their handler functions. +router.get("/", root) + +// Root API endpoint. Simply acknowledges that it exists. +function root(req, res, next) { + return res.json({ status: "address" }) +} + +module.exports = { + router, + testableComponents: { + root + } +}