fix(block): Removing block route because it used Insight API

This commit is contained in:
Chris Troutner
2019-06-14 19:56:36 -07:00
parent d23cd1f46f
commit 41c8f73013
2 changed files with 0 additions and 890 deletions
-280
View File
@@ -1,280 +0,0 @@
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const bitbox = require("./services/bitbox")
const logger = require("./logging.js")
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.`
})
}
logger.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
}
}
-610
View File
@@ -1,610 +0,0 @@
"use strict"
const blockRoute = require("../../src/routes/v3/block")
const chai = require("chai")
const assert = chai.assert
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/block-mock")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Block", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
// Activate nock if it's inactive.
if (!nock.isActive()) nock.activate()
})
afterEach(() => {
// Clean up HTTP mocks.
nock.cleanAll() // clear interceptor list.
nock.restore()
})
after(() => {
// Restore any pre-existing environment variables.
process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL
process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL
process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME
process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD
})
describe("#root", () => {
// root route handler.
const root = blockRoute.testableComponents.root
it("should respond to GET for base route", async () => {
const result = root(req, res)
assert.equal(result.status, "block", "Returns static string")
})
})
describe("#detailsByHashSingle", () => {
const detailsByHash = blockRoute.testableComponents.detailsByHashSingle
it("should throw an error for an empty hash", async () => {
req.params.hash = ""
const result = await detailsByHash(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hash must not be empty",
"Proper error message"
)
})
it("should throw 50X when network issues", async () => {
// Save the existing RPC URL.
const savedUrl = process.env.BITCOINCOM_BASEURL
// Manipulate the URL to cause a 500 network error.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
req.params.hash = "abc123"
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.isAbove(res.statusCode, 499, "HTTP status code 50X expected.")
//assert.include(result.error, "ENOTFOUND", "Error message expected")
})
it("should throw an error for invalid hash", async () => {
req.params.hash = "abc123"
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.params.hash}`)
.reply(404, "Not found")
}
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.include(result.error, "Not found", "Proper error message")
})
it("should GET /detailsByHash/:hash", async () => {
req.params.hash =
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.params.hash}`)
.reply(200, mockData.mockBlockDetails)
}
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"hash",
"size",
"height",
"version",
"merkleroot",
"tx",
"time",
"nonce",
"bits",
"difficulty",
"chainwork",
"confirmations",
"previousblockhash",
"nextblockhash",
"reward",
"isMainChain",
"poolInfo"
])
assert.isArray(result.tx)
})
})
describe("#detailsByHashBulk", () => {
// details route handler.
const detailsByHashBulk = blockRoute.testableComponents.detailsByHashBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsByHashBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hashes needs to be an array",
"Proper error message"
)
})
it("should error on non-array single address", async () => {
req.body = {
hashes:
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
}
const result = await detailsByHashBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hashes needs to be an array",
"Proper error message"
)
})
it("should throw 400 error if addresses array is too large", async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push("")
req.body.hashes = testArray
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Array too large")
})
it("should throw an error for an invalid hash", async () => {
req.body = {
hashes: [`abc123`]
}
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(result.error, "Invalid hash", "Proper error message")
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.body = {
hashes: [
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
]
}
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsByHashBulk(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single hash", async () => {
req.body = {
hashes: [
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.equal(result.length, 1, "Array with one entry")
assert.hasAllKeys(result[0], [
"bits",
"chainwork",
"confirmations",
"difficulty",
"hash",
"height",
"isMainChain",
"merkleroot",
"nextblockhash",
"nonce",
"poolInfo",
"previousblockhash",
"reward",
"size",
"time",
"tx",
"version"
])
})
it("should get details for multiple hashes", async () => {
req.body = {
hashes: [
`00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79`,
`00000000c2b2c19cf499f57d5b0f724c6df753330d7acc7d4a8ebe412d427bd0`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
.reply(200, mockData.mockBlockDetails)
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[1]}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
it("should throw an error if hash not found", async () => {
req.body = {
hashes: [
`00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44abcdef`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
//.reply(404, { error: { message: "Not Found" } })
.reply(404, "Not found")
}
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 404, "HTTP status code 404 expected.")
assert.include(result.error, "Not found", "Proper error message")
})
})
describe("Block Details By Height", () => {
// block route handler.
const detailsByHeight = blockRoute.testableComponents.detailsByHeightSingle
it("should throw an error for an empty height", async () => {
req.params.height = ""
const result = await detailsByHeight(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"height must not be empty",
"Proper error message"
)
})
it("should throw 500 when network issues", async () => {
// Save the existing RPC URL.
const savedUrl = process.env.BITCOINCOM_BASEURL
const savedUrl2 = process.env.RPC_BASEURL
// Manipulate the URL to cause a 500 network error.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api/"
req.params.height = "abc123"
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
process.env.RPC_BASEURL = savedUrl2
assert.isAbove(
res.statusCode,
499,
"HTTP status code 500 or great expected."
)
//assert.include(result.error, "ENOTFOUND", "Error message expected")
})
it("should throw an error for invalid height", async () => {
req.params.height = "abc123"
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: {
code: -1,
message: "JSON value is not an integer as expected"
}
})
}
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"JSON value is not an integer as expected",
"Proper error message"
)
})
it("should GET /detailsByHeight/:height", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockBlockHash })
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(
`/block/00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79`
)
.reply(200, mockData.mockBlockDetails)
}
req.params.height = 500000
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"hash",
"size",
"height",
"version",
"merkleroot",
"tx",
"time",
"nonce",
"bits",
"difficulty",
"chainwork",
"confirmations",
"previousblockhash",
"nextblockhash",
"reward",
"isMainChain",
"poolInfo"
])
assert.isArray(result.tx)
})
})
describe("#detailsByHeightBulk", () => {
// details route handler.
const detailsByHeightBulk =
blockRoute.testableComponents.detailsByHeightBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsByHeightBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"heights needs to be an array",
"Proper error message"
)
})
it("should error on non-array single height", async () => {
req.body = {
heights: 500000
}
const result = await detailsByHeightBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"heights needs to be an array",
"Proper error message"
)
})
it("should throw 400 error if addresses array is too large", async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push("")
req.body.heights = testArray
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Array too large")
})
it("should throw error for an invalid height", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: {
code: -1,
message: "JSON value is not an integer as expected"
}
})
}
req.body.heights = [`abc123`]
const result = await detailsByHeightBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.body.heights = [`500000`]
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsByHeightBulk(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single height", async () => {
req.body.heights = [`500000`]
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockBlockHash })
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${mockData.mockBlockHash}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.equal(result.length, 1, "Array with one entry")
assert.hasAllKeys(result[0], [
"bits",
"chainwork",
"confirmations",
"difficulty",
"hash",
"height",
"isMainChain",
"merkleroot",
"nextblockhash",
"nonce",
"poolInfo",
"previousblockhash",
"reward",
"size",
"time",
"tx",
"version"
])
})
it("should get details for multiple block heights", async () => {
req.body = {
heights: [`500000`, `500001`]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.times(2)
.reply(200, { result: mockData.mockBlockHash })
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${mockData.mockBlockHash}`)
.times(2)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
})
})