mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-22 09:12:05 -07:00
feat(no v2): Removing v2 routes
This commit is contained in:
+2
-43
@@ -20,21 +20,6 @@ const cors = require("cors")
|
||||
const AuthMW = require("./middleware/auth")
|
||||
const jwtAuth = require("./middleware/jwt-auth")
|
||||
|
||||
// v2
|
||||
const indexV2 = require("./routes/v2/index")
|
||||
const healthCheckV2 = require("./routes/v2/health-check")
|
||||
const addressV2 = require("./routes/v2/address")
|
||||
const blockV2 = require("./routes/v2/block")
|
||||
const blockchainV2 = require("./routes/v2/blockchain")
|
||||
const controlV2 = require("./routes/v2/control")
|
||||
const generatingV2 = require("./routes/v2/generating")
|
||||
const miningV2 = require("./routes/v2/mining")
|
||||
const networkV2 = require("./routes/v2/network")
|
||||
const rawtransactionsV2 = require("./routes/v2/rawtransactions")
|
||||
const transactionV2 = require("./routes/v2/transaction")
|
||||
const utilV2 = require("./routes/v2/util")
|
||||
const slpV2 = require("./routes/v2/slp")
|
||||
|
||||
// v3
|
||||
const healthCheckV3 = require("./routes/v3/health-check")
|
||||
const blockchainV3 = require("./routes/v3/blockchain")
|
||||
@@ -78,17 +63,7 @@ app.use(bodyParser.urlencoded({ extended: false }))
|
||||
app.use(cookieParser())
|
||||
app.use(express.static(path.join(__dirname, "public")))
|
||||
|
||||
//
|
||||
// let username = process.env.USERNAME;
|
||||
// let password = process.env.PASSWORD;
|
||||
//
|
||||
// app.use(basicAuth(
|
||||
// {
|
||||
// users: { username: password }
|
||||
// }
|
||||
// ));
|
||||
|
||||
const v2prefix = "v2"
|
||||
// const v2prefix = "v2"
|
||||
const v3prefix = "v3"
|
||||
|
||||
// Inspect the header for a JWT token.
|
||||
@@ -96,25 +71,9 @@ app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders)
|
||||
|
||||
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
|
||||
const auth = new AuthMW()
|
||||
app.use(`/${v2prefix}/`, auth.mw())
|
||||
// app.use(`/${v2prefix}/`, auth.mw())
|
||||
app.use(`/${v3prefix}/`, auth.mw())
|
||||
|
||||
// Rate limit on all v2 routes
|
||||
app.use(`/${v2prefix}/`, routeRateLimit)
|
||||
app.use("/", indexV2)
|
||||
app.use(`/${v2prefix}/` + `health-check`, healthCheckV2)
|
||||
app.use(`/${v2prefix}/` + `address`, addressV2.router)
|
||||
app.use(`/${v2prefix}/` + `blockchain`, blockchainV2.router)
|
||||
app.use(`/${v2prefix}/` + `block`, blockV2.router)
|
||||
app.use(`/${v2prefix}/` + `control`, controlV2.router)
|
||||
app.use(`/${v2prefix}/` + `generating`, generatingV2)
|
||||
app.use(`/${v2prefix}/` + `mining`, miningV2.router)
|
||||
app.use(`/${v2prefix}/` + `network`, networkV2)
|
||||
app.use(`/${v2prefix}/` + `rawtransactions`, rawtransactionsV2.router)
|
||||
app.use(`/${v2prefix}/` + `transaction`, transactionV2.router)
|
||||
app.use(`/${v2prefix}/` + `util`, utilV2.router)
|
||||
app.use(`/${v2prefix}/` + `slp`, slpV2.router)
|
||||
|
||||
// Rate limit on all v3 routes
|
||||
app.use(`/${v3prefix}/`, routeRateLimit)
|
||||
app.use(`/${v3prefix}/` + `health-check`, healthCheckV3)
|
||||
|
||||
@@ -1,805 +0,0 @@
|
||||
/*
|
||||
Address route
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const requestUtils = require("./services/requestUtils")
|
||||
const axios = require("axios")
|
||||
const logger = require("./logging.js")
|
||||
const routeUtils = require("./route-utils")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
//const router = express.Router()
|
||||
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/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
// Use the default (and max) page size of 1000
|
||||
// https://github.com/bitpay/insight-api#notes-on-upgrading-from-v03
|
||||
const PAGE_SIZE = 1000
|
||||
|
||||
// Connect the route endpoints to their handler functions.
|
||||
router.get("/", root)
|
||||
router.get("/details/:address", detailsSingle)
|
||||
router.post("/details", detailsBulk)
|
||||
router.post("/utxo", utxoBulk)
|
||||
router.get("/utxo/:address", utxoSingle)
|
||||
router.post("/unconfirmed", unconfirmedBulk)
|
||||
router.get("/unconfirmed/:address", unconfirmedSingle)
|
||||
router.get("/transactions/:address", transactionsSingle)
|
||||
router.post("/transactions", transactionsBulk)
|
||||
router.get("/fromXPub/:xpub", fromXPubSingle)
|
||||
|
||||
// Root API endpoint. Simply acknowledges that it exists.
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "address" })
|
||||
}
|
||||
|
||||
// Query the Insight API for details on a single BCH address.
|
||||
// Returns a Promise.
|
||||
async function detailsFromInsight(thisAddress, currentPage = 0) {
|
||||
try {
|
||||
let addr
|
||||
if (
|
||||
process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/"
|
||||
)
|
||||
addr = BITBOX.Address.toCashAddress(thisAddress)
|
||||
else addr = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
|
||||
let path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}`
|
||||
|
||||
// Set from and to params based on currentPage and pageSize
|
||||
// https://github.com/bitpay/insight-api/blob/master/README.md#notes-on-upgrading-from-v02
|
||||
const from = currentPage * PAGE_SIZE
|
||||
const to = from + PAGE_SIZE
|
||||
path = `${path}?from=${from}&to=${to}`
|
||||
|
||||
// Query the Insight server.
|
||||
const axiosResponse = await axios.get(path)
|
||||
const retData = axiosResponse.data
|
||||
//console.log(`retData: ${util.inspect(retData)}`)
|
||||
|
||||
// Calculate pagesTotal from response
|
||||
const pagesTotal = Math.ceil(retData.txApperances / PAGE_SIZE)
|
||||
|
||||
// Append different address formats to the return data.
|
||||
retData.legacyAddress = BITBOX.Address.toLegacyAddress(retData.addrStr)
|
||||
retData.cashAddress = BITBOX.Address.toCashAddress(retData.addrStr)
|
||||
delete retData.addrStr
|
||||
|
||||
// Append pagination information to the return data.
|
||||
retData.currentPage = currentPage
|
||||
retData.pagesTotal = pagesTotal
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// POST handler for bulk queries on address details
|
||||
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details
|
||||
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details
|
||||
async function detailsBulk(req, res, next) {
|
||||
try {
|
||||
let addresses = req.body.addresses
|
||||
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
|
||||
|
||||
// Reject if addresses is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "addresses needs to be an array. Use GET for single address."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/details with these addresses: `, addresses)
|
||||
wlogger.debug(`Executing address/details with these addresses: `, addresses)
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loops through each address and creates an array of Promises, querying
|
||||
// Insight API in parallel.
|
||||
addresses = addresses.map(async (address, index) =>
|
||||
detailsFromInsight(address, currentPage)
|
||||
)
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(addresses)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
//logger.error(`Error in detailsBulk(): `, err)
|
||||
wlogger.error(`Error in address.ts/detailsBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler for single address details
|
||||
async function detailsSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
|
||||
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/detailsSingle with this address: `, address)
|
||||
wlogger.debug(
|
||||
`Executing address/detailsSingle with this address: `,
|
||||
address
|
||||
)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await detailsFromInsight(address, currentPage)
|
||||
|
||||
// Return the retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} 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 address.ts/detailsSingle: `, err)
|
||||
wlogger.error(`Error in address.ts/detailsSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve UTXO data from the Insight API
|
||||
async function utxoFromInsight(thisAddress) {
|
||||
try {
|
||||
let addr
|
||||
if (
|
||||
process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/"
|
||||
)
|
||||
addr = BITBOX.Address.toCashAddress(thisAddress)
|
||||
else addr = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
|
||||
const path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}/utxo`
|
||||
|
||||
// Query the Insight server.
|
||||
const response = await axios.get(path)
|
||||
|
||||
// Append different address formats to the return data.
|
||||
const retData = {
|
||||
utxos: Array,
|
||||
legacyAddress: String,
|
||||
cashAddress: String,
|
||||
scriptPubKey: String
|
||||
}
|
||||
if (response.data.length && response.data[0].scriptPubKey) {
|
||||
const spk = response.data[0].scriptPubKey
|
||||
retData.scriptPubKey = spk
|
||||
}
|
||||
retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress)
|
||||
retData.utxos = response.data.map(utxo => {
|
||||
delete utxo.address
|
||||
delete utxo.scriptPubKey
|
||||
return utxo
|
||||
})
|
||||
//console.log(`utxoFromInsight retData: ${util.inspect(retData)}`)
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve UTXO information for an address.
|
||||
async function utxoBulk(req, res, next) {
|
||||
try {
|
||||
let addresses = req.body.addresses
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "addresses needs to be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (er) {
|
||||
//if (er.message.includes("Unsupported address format"))
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxoBulk with these addresses: `, addresses)
|
||||
wlogger.debug(
|
||||
`Executing address/utxoBulk with these addresses: `,
|
||||
addresses
|
||||
)
|
||||
|
||||
// Loops through each address and creates an array of Promises, querying
|
||||
// Insight API in parallel.
|
||||
addresses = addresses.map(async (address, index) =>
|
||||
utxoFromInsight(address)
|
||||
)
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(addresses)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/utxoBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler for single address details
|
||||
async function utxoSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await utxoFromInsight(address)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/utxoSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve any unconfirmed TX information for a given address.
|
||||
async function unconfirmedBulk(req, res, next) {
|
||||
try {
|
||||
const addresses = req.body.addresses
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "addresses needs to be an array" })
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Collect an array of promises.
|
||||
const promises = addresses.map(address => utxoFromInsight(address))
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(promises)
|
||||
|
||||
// Loop through each result
|
||||
const finalResult = result.map(elem => {
|
||||
//console.log(`elem: ${util.inspect(elem)}`)
|
||||
|
||||
// Filter out confirmed transactions.
|
||||
const unconfirmedUtxos = elem.utxos.filter(
|
||||
utxo => utxo.confirmations === 0
|
||||
)
|
||||
|
||||
elem.utxos = unconfirmedUtxos
|
||||
|
||||
return elem
|
||||
})
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(finalResult)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/unconfirmedBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler. Retrieve any unconfirmed TX information for a given address.
|
||||
async function unconfirmedSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await utxoFromInsight(address)
|
||||
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
|
||||
|
||||
// Loop through each returned UTXO.
|
||||
const unconfirmedUTXOs = []
|
||||
for (let j = 0; j < retData.utxos.length; j++) {
|
||||
const thisUtxo = retData.utxos[j]
|
||||
|
||||
// Only interested in UTXOs with no confirmations.
|
||||
if (thisUtxo.confirmations === 0) unconfirmedUTXOs.push(thisUtxo)
|
||||
}
|
||||
|
||||
retData.utxos = unconfirmedUTXOs
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/unconfirmedSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve transaction data from the Insight API
|
||||
async function transactionsFromInsight(thisAddress, currentPage = 0) {
|
||||
try {
|
||||
const path = `${process.env.BITCOINCOM_BASEURL}txs/?address=${thisAddress}&pageNum=${currentPage}`
|
||||
|
||||
// Query the Insight server.
|
||||
const response = await axios.get(path)
|
||||
|
||||
// Append different address formats to the return data.
|
||||
const retData = response.data
|
||||
retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress)
|
||||
retData.currentPage = currentPage
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get an array of TX information for a given address.
|
||||
async function transactionsBulk(req, res, next) {
|
||||
try {
|
||||
let addresses = req.body.addresses
|
||||
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "addresses needs to be an array" })
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through each address and collect an array of promises.
|
||||
addresses = addresses.map(async (address, index) =>
|
||||
transactionsFromInsight(address, currentPage)
|
||||
)
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(addresses)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/transactionsBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler. Retrieve any unconfirmed TX information for a given address.
|
||||
async function transactionsSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
|
||||
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing address/transactionsSingle with this address: `,
|
||||
address
|
||||
)
|
||||
wlogger.debug(
|
||||
`Executing address/transactionsSingle with this address: `,
|
||||
address
|
||||
)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await transactionsFromInsight(address, currentPage)
|
||||
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/transactionsSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function fromXPubSingle(req, res, next) {
|
||||
try {
|
||||
const xpub = req.params.xpub
|
||||
const hdPath = req.query.hdPath ? req.query.hdPath : "0"
|
||||
|
||||
if (!xpub || xpub === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "xpub can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if xpub is an array.
|
||||
if (Array.isArray(xpub)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "xpub can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/fromXPub with this xpub: `, xpub)
|
||||
wlogger.debug(`Executing address/fromXPub with this xpub: `, xpub)
|
||||
|
||||
const cashAddr = BITBOX.Address.fromXPub(xpub, hdPath)
|
||||
const legacyAddr = BITBOX.Address.toLegacyAddress(cashAddr)
|
||||
res.status(200)
|
||||
return res.json({
|
||||
cashAddress: cashAddr,
|
||||
legacyAddress: legacyAddr
|
||||
})
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/fromXPubSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
detailsBulk,
|
||||
detailsSingle,
|
||||
utxoBulk,
|
||||
utxoSingle,
|
||||
unconfirmedBulk,
|
||||
unconfirmedSingle,
|
||||
transactionsBulk,
|
||||
transactionsSingle,
|
||||
fromXPubSingle
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,890 +0,0 @@
|
||||
/*
|
||||
TODO
|
||||
- Add blockhash functionality back into getTxOutProof
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
// Define routes.
|
||||
router.get("/", root)
|
||||
router.get("/getBestBlockHash", getBestBlockHash)
|
||||
// Dev Note: getBlock/:hash ommited because its the same as block/detailsByHash
|
||||
//router.get("/getBlock/:hash", getBlock)
|
||||
router.get("/getBlockchainInfo", getBlockchainInfo)
|
||||
router.get("/getBlockCount", getBlockCount)
|
||||
router.get("/getBlockHeader/:hash", getBlockHeaderSingle)
|
||||
router.post("/getBlockHeader", getBlockHeaderBulk)
|
||||
|
||||
router.get("/getChainTips", getChainTips)
|
||||
router.get("/getDifficulty", getDifficulty)
|
||||
router.get("/getMempoolEntry/:txid", getMempoolEntrySingle)
|
||||
router.post("/getMempoolEntry", getMempoolEntryBulk)
|
||||
router.get("/getMempoolInfo", getMempoolInfo)
|
||||
router.get("/getRawMempool", getRawMempool)
|
||||
router.get("/getTxOut/:txid/:n", getTxOut)
|
||||
router.get("/getTxOutProof/:txid", getTxOutProofSingle)
|
||||
router.post("/getTxOutProof", getTxOutProofBulk)
|
||||
router.get("/verifyTxOutProof/:proof", verifyTxOutProofSingle)
|
||||
router.post("/verifyTxOutProof", verifyTxOutProofBulk)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "blockchain" })
|
||||
}
|
||||
|
||||
// Returns the hash of the best (tip) block in the longest block chain.
|
||||
async function getBestBlockHash(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getbestblockhash"
|
||||
requestConfig.data.method = "getbestblockhash"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockchainInfo(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockchaininfo"
|
||||
requestConfig.data.method = "getblockchaininfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockCount(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockcount"
|
||||
requestConfig.data.method = "getblockcount"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockCount().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockHeaderSingle(req, res, next) {
|
||||
try {
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose.toString() === "true")
|
||||
verbose = true
|
||||
|
||||
const hash = req.params.hash
|
||||
if (!hash || hash === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hash can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockheader"
|
||||
requestConfig.data.method = "getblockheader"
|
||||
requestConfig.data.params = [hash, verbose]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockHeaderSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockHeaderBulk(req, res, next) {
|
||||
try {
|
||||
const hashes = req.body.hashes
|
||||
const verbose = req.body.verbose ? req.body.verbose : false
|
||||
|
||||
if (!Array.isArray(hashes)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "hashes needs to be an array. Use GET for single hash."
|
||||
})
|
||||
}
|
||||
|
||||
// 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.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing blockchain/getBlockHeaderBulk with these hashes: `,
|
||||
hashes
|
||||
)
|
||||
|
||||
// Validate each hash in the array.
|
||||
for (let i = 0; i < hashes.length; i++) {
|
||||
const hash = hashes[i]
|
||||
|
||||
if (hash.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({ error: `This is not a hash: ${hash}` })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each hash and creates an array of requests to call in parallel
|
||||
const promises = hashes.map(async hash => {
|
||||
requestConfig.data.id = "getblockheader"
|
||||
requestConfig.data.method = "getblockheader"
|
||||
requestConfig.data.params = [hash, verbose]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockHeaderBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getChainTips(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getchaintips"
|
||||
requestConfig.data.method = "getchaintips"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getChainTips().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current difficulty value, used to regulate mining power on the network.
|
||||
async function getDifficulty(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getdifficulty"
|
||||
requestConfig.data.method = "getdifficulty"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getDifficulty().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns mempool data for given transaction. TXID must be in mempool (unconfirmed)
|
||||
async function getMempoolEntrySingle(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getmempoolentry"
|
||||
requestConfig.data.method = "getmempoolentry"
|
||||
requestConfig.data.params = [txid]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getMempoolEntrySingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getMempoolEntryBulk(req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "txids needs to be an array. Use GET for single txid."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing blockchain/getMempoolEntry with these txids: `,
|
||||
txids
|
||||
)
|
||||
|
||||
// Validate each element in the array
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const txid = txids[i]
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({ error: "This is not a txid" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async txid => {
|
||||
requestConfig.data.id = "getmempoolentry"
|
||||
requestConfig.data.method = "getmempoolentry"
|
||||
requestConfig.data.params = [txid]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getMempoolEntryBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getMempoolInfo(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getmempoolinfo"
|
||||
requestConfig.data.method = "getmempoolinfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getMempoolInfo().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getRawMempool(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose === "true") verbose = true
|
||||
|
||||
requestConfig.data.id = "getrawmempool"
|
||||
requestConfig.data.method = "getrawmempool"
|
||||
requestConfig.data.params = [verbose]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getRawMempool().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns details about an unspent transaction output.
|
||||
async function getTxOut(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
let n = req.params.n
|
||||
if (n === undefined || n === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "n can not be empty" })
|
||||
}
|
||||
n = parseInt(n)
|
||||
|
||||
let include_mempool = false
|
||||
if (req.query.include_mempool && req.query.include_mempool === "true")
|
||||
include_mempool = true
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "gettxout"
|
||||
requestConfig.data.method = "gettxout"
|
||||
requestConfig.data.params = [txid, n, include_mempool]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getTxOut().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a hex-encoded proof that 'txid' was included in a block.
|
||||
async function getTxOutProofSingle(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "gettxoutproof"
|
||||
requestConfig.data.method = "gettxoutproof"
|
||||
requestConfig.data.params = [[txid]]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getTxOutProofSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a hex-encoded proof that 'txid' was included in a block.
|
||||
async function getTxOutProofBulk(req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
// Reject if txids is not an array.
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "txids needs to be an array. Use GET for single txid."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Validate each element in the array.
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const txid = txids[i]
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid txid. Double check your txid is valid: ${txid}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Executing blockchain/getTxOutProof with these txids: `, txids)
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async txid => {
|
||||
requestConfig.data.id = "gettxoutproof"
|
||||
requestConfig.data.method = "gettxoutproof"
|
||||
requestConfig.data.params = [[txid]]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel promisses to resolve.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getTxOutProofBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//
|
||||
// router.get('/preciousBlock/:hash', async (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"preciousblock",
|
||||
// method: "preciousblock",
|
||||
// params: [
|
||||
// req.params.hash
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(JSON.stringify(response.data.result));
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/pruneBlockchain/:height', async (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"pruneblockchain",
|
||||
// method: "pruneblockchain",
|
||||
// params: [
|
||||
// req.params.height
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/verifyChain', async (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"verifychain",
|
||||
// method: "verifychain"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
*/
|
||||
|
||||
async function verifyTxOutProofSingle(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const proof = req.params.proof
|
||||
if (!proof || proof === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "proof can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "verifytxoutproof"
|
||||
requestConfig.data.method = "verifytxoutproof"
|
||||
requestConfig.data.params = [req.params.proof]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/verifyTxOutProofSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyTxOutProofBulk(req, res, next) {
|
||||
try {
|
||||
const proofs = req.body.proofs
|
||||
|
||||
// Reject if proofs is not an array.
|
||||
if (!Array.isArray(proofs)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "proofs needs to be an array. Use GET for single proof."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, proofs)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Validate each element in the array.
|
||||
for (let i = 0; i < proofs.length; i++) {
|
||||
const proof = proofs[i]
|
||||
|
||||
if (!proof || proof === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: `proof can not be empty: ${proof}` })
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing blockchain/verifyTxOutProof with these proofs: `,
|
||||
proofs
|
||||
)
|
||||
|
||||
// Loop through each proof and creates an array of requests to call in parallel
|
||||
const promises = proofs.map(async proof => {
|
||||
requestConfig.data.id = "verifytxoutproof"
|
||||
requestConfig.data.method = "verifytxoutproof"
|
||||
requestConfig.data.params = [proof]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel promisses to resolve.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result[0])
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/verifyTxOutProofBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
getBestBlockHash,
|
||||
//getBlock,
|
||||
getBlockchainInfo,
|
||||
getBlockCount,
|
||||
getBlockHeaderSingle,
|
||||
getBlockHeaderBulk,
|
||||
getChainTips,
|
||||
getDifficulty,
|
||||
getMempoolInfo,
|
||||
getRawMempool,
|
||||
getMempoolEntrySingle,
|
||||
getMempoolEntryBulk,
|
||||
getTxOut,
|
||||
getTxOutProofSingle,
|
||||
getTxOutProofBulk,
|
||||
verifyTxOutProofSingle,
|
||||
verifyTxOutProofBulk
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const logger = require("./logging.js")
|
||||
const routeUtils = require("./route-utils")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used for processing error messages before sending them to the user.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/getInfo", getInfo)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "control" })
|
||||
}
|
||||
|
||||
// Execute the RPC getinfo call.
|
||||
async function getInfo(req, res, next) {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getinfo"
|
||||
requestConfig.data.method = "getinfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
try {
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (error) {
|
||||
wlogger.error(`Error in control.ts/getInfo().`, error)
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in control/getInfo: `, error)
|
||||
|
||||
res.status(500)
|
||||
if (error.response && error.response.data && error.response.data.error)
|
||||
return res.json({ error: error.response.data.error })
|
||||
return res.json({ error: util.inspect(error) })
|
||||
}
|
||||
}
|
||||
|
||||
// router.get('/getMemoryInfo', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getmemoryinfo",
|
||||
// method: "getmemoryinfo"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/help', (req, res, next) => {
|
||||
// BITBOX.Control.help()
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/stop', (req, res, next) => {
|
||||
// BITBOX.Control.stop()
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
getInfo
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
//const axios = require("axios");
|
||||
|
||||
//const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default;
|
||||
//const BITBOX = new BITBOXCli();
|
||||
|
||||
//const BitboxHTTP = axios.create({
|
||||
// baseURL: process.env.RPC_BASEURL,
|
||||
//});
|
||||
//const username = process.env.RPC_USERNAME;
|
||||
//const password = process.env.RPC_PASSWORD;
|
||||
|
||||
router.get("/", (req, res, next) => {
|
||||
res.json({ status: "generating" })
|
||||
})
|
||||
//
|
||||
// router.post('/generateToAddress/:nblocks/:address', (req, res, next) => {
|
||||
// let maxtries = 1000000;
|
||||
// if(req.query.maxtries) {
|
||||
// maxtries = parseInt(req.query.maxtries);
|
||||
// }
|
||||
//
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"generatetoaddress",
|
||||
// method: "generatetoaddress",
|
||||
// params: [
|
||||
// req.params.nblocks,
|
||||
// req.params.address,
|
||||
// maxtries
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = router
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
|
||||
/* GET home page. */
|
||||
router.get("/", (req, res, next) => {
|
||||
res.json({ status: "winning v2" })
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -1,15 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
|
||||
/* GET home page. */
|
||||
router.get("/", (req, res, next) => {
|
||||
res.render("swagger-v2")
|
||||
})
|
||||
|
||||
router.get("/v2", (req, res, next) => {
|
||||
res.render("swagger-v2")
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
A utility library for setting up greylog2 logging.
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
// This will be uncommented and correct once we have our logging server functioning.
|
||||
/*
|
||||
var graylog2 = require("graylog2");
|
||||
var logger = new graylog2.graylog({
|
||||
servers: [
|
||||
{ 'host': '127.0.0.1', port: 12201 },
|
||||
{ 'host': '127.0.0.2', port: 12201 }
|
||||
],
|
||||
hostname: 'server.name', // the name of this host
|
||||
// (optional, default: os.hostname())
|
||||
facility: 'Node.js', // the facility for these log messages
|
||||
// (optional, default: "Node.js")
|
||||
bufferSize: 1350 // max UDP packet size, should never exceed the
|
||||
// MTU of your system (optional, default: 1400)
|
||||
});
|
||||
|
||||
logger.on('error', function (error) {
|
||||
console.error('Error while trying to write to graylog2:', error);
|
||||
});
|
||||
*/
|
||||
|
||||
// This is just a placeholder function that will be replaced once we get the
|
||||
// greylog server working.
|
||||
function log(msg, obj) {
|
||||
//console.log(msg, obj)
|
||||
}
|
||||
|
||||
// This is just a placeholder function that will be replaced once we get the
|
||||
// greylog server working.
|
||||
function error(msg, obj) {
|
||||
if (!obj) console.error(msg)
|
||||
else console.error(msg, obj)
|
||||
}
|
||||
|
||||
// This is just a placeholder function that will be replaced once we get the
|
||||
// greylog server working.
|
||||
function debug(msg, obj) {
|
||||
//console.log(msg, obj)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
error,
|
||||
debug
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/getMiningInfo", getMiningInfo)
|
||||
router.get("/getNetworkHashps", getNetworkHashPS)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "mining" })
|
||||
}
|
||||
|
||||
//
|
||||
// router.get('/getBlockTemplate/:templateRequest', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getblocktemplate",
|
||||
// method: "getblocktemplate",
|
||||
// params: [
|
||||
// req.params.templateRequest
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
|
||||
async function getMiningInfo(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getmininginfo"
|
||||
requestConfig.data.method = "getmininginfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in mining.ts/getMiningInfo().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getNetworkHashPS(req, res, next) {
|
||||
try {
|
||||
let nblocks = 120 // Default
|
||||
let height = -1 // Default
|
||||
if (req.query.nblocks) nblocks = parseInt(req.query.nblocks)
|
||||
if (req.query.height) height = parseInt(req.query.height)
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getnetworkhashps"
|
||||
requestConfig.data.method = "getnetworkhashps"
|
||||
requestConfig.data.params = [nblocks, height]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in mining.ts/getNetworkHashPS().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// router.post('/submitBlock/:hex', (req, res, next) => {
|
||||
// let parameters = '';
|
||||
// if(req.query.parameters && req.query.parameters !== '') {
|
||||
// parameters = true;
|
||||
// }
|
||||
//
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"submitblock",
|
||||
// method: "submitblock",
|
||||
// params: [
|
||||
// req.params.hex,
|
||||
// parameters
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
getMiningInfo,
|
||||
getNetworkHashPS
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
|
||||
router.get("/", async (req, res, next) => {
|
||||
res.json({ status: "network" })
|
||||
})
|
||||
|
||||
// router.post('/addNode/:node/:command', (req, res, next) => {
|
||||
// BITBOX.Network.addNode(req.params.node, req.params.command)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/clearBanned', (req, res, next) => {
|
||||
// BITBOX.Network.clearBanned()
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/disconnectNode/:address/:nodeid', (req, res, next) => {
|
||||
// BITBOX.Network.disconnectNode(req.params.address, req.params.nodeid)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getAddedNodeInfo/:node', (req, res, next) => {
|
||||
// BITBOX.Network.getAddedNodeInfo(req.params.node)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getConnectionCount', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getconnectioncount",
|
||||
// method: "getconnectioncount"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getNetTotals', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getnettotals",
|
||||
// method: "getnettotals"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getNetworkInfo', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getnetworkinfo",
|
||||
// method: "getnetworkinfo"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/getPeerInfo', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getpeerinfo",
|
||||
// method: "getpeerinfo"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/ping', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"ping",
|
||||
// method: "ping"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(JSON.stringify(response.data.result));
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/setBan/:subnet/:command', (req, res, next) => {
|
||||
// // TODO finish this
|
||||
// BITBOX.Network.getConnectionCount(req.params.subnet, req.params.command)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/setNetworkActive/:state', (req, res, next) => {
|
||||
// let state = true;
|
||||
// if(req.params.state && req.params.state === 'false') {
|
||||
// state = false;
|
||||
// }
|
||||
// BITBOX.Network.getConnectionCount(state)
|
||||
// .then((result) => {
|
||||
// res.json(result);
|
||||
// }, (err) => { console.log(err);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = router
|
||||
@@ -1,609 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/decodeRawTransaction/:hex", decodeRawTransactionSingle)
|
||||
router.post("/decodeRawTransaction", decodeRawTransactionBulk)
|
||||
router.get("/decodeScript/:hex", decodeScriptSingle)
|
||||
router.post("/decodeScript", decodeScriptBulk)
|
||||
router.post("/getRawTransaction", getRawTransactionBulk)
|
||||
router.get("/getRawTransaction/:txid", getRawTransactionSingle)
|
||||
router.post("/sendRawTransaction", sendRawTransactionBulk)
|
||||
router.get("/sendRawTransaction/:hex", sendRawTransactionSingle)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "rawtransactions" })
|
||||
}
|
||||
|
||||
// Decode transaction hex into a JSON object.
|
||||
// GET
|
||||
async function decodeRawTransactionSingle(req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "decoderawtransaction"
|
||||
requestConfig.data.method = "decoderawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
`Error in rawtransactions.ts/decodeRawTransactionSingle().`,
|
||||
err
|
||||
)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function decodeRawTransactionBulk(req, res, next) {
|
||||
try {
|
||||
const hexes = req.body.hexes
|
||||
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "hexes must be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
const results = []
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const thisHex = hexes[i]
|
||||
|
||||
// Reject if id is empty
|
||||
if (!thisHex || thisHex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "Encountered empty hex" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each height and creates an array of requests to call in parallel
|
||||
const promises = hexes.map(async hex => {
|
||||
requestConfig.data.id = "decoderawtransaction"
|
||||
requestConfig.data.method = "decoderawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
|
||||
/*
|
||||
// Loop through each hex and creates an array of requests to call in parallel
|
||||
hexes = hexes.map(async (hex: any) => {
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "Encountered empty hex" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "decoderawtransaction"
|
||||
requestConfig.data.method = "decoderawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
const result: Array<any> = []
|
||||
return axios.all(hexes).then(
|
||||
axios.spread((...args) => {
|
||||
args.forEach((arg: any) => {
|
||||
if (arg) {
|
||||
result.push(arg.data.result)
|
||||
}
|
||||
})
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
})
|
||||
)
|
||||
*/
|
||||
} 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 rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
`Error in rawtransactions.ts/decodeRawTransactionBulk().`,
|
||||
err
|
||||
)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw transaction from hex to assembly.
|
||||
// GET single
|
||||
async function decodeScriptSingle(req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "decodescript"
|
||||
requestConfig.data.method = "decodescript"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} 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 rawtransactions/decodeScript: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/decodeScriptSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw transaction from hex to assembly.
|
||||
// POST bulk
|
||||
async function decodeScriptBulk(req, res, next) {
|
||||
try {
|
||||
const hexes = req.body.hexes
|
||||
|
||||
// Validation
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "hexes must be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each hex in the array
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const hex = hexes[i]
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "Encountered empty hex" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each hex and create an array of promises
|
||||
const promises = hexes.map(async hex => {
|
||||
requestConfig.data.id = "decodescript"
|
||||
requestConfig.data.method = "decodescript"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return response
|
||||
})
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const resolved = await Promise.all(promises)
|
||||
|
||||
// Retrieve the data from each resolved promise.
|
||||
const result = resolved.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} 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 rawtransactions/decodeScript: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/decodeScriptBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve raw transactions details from the full node.
|
||||
async function getRawTransactionsFromNode(txid, verbose) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getrawtransaction"
|
||||
requestConfig.data.method = "getrawtransaction"
|
||||
requestConfig.data.params = [txid, verbose]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return response.data.result
|
||||
} catch (err) {
|
||||
wlogger.error(`Error in rawtransactions.ts/getRawTransactionsFromNode().`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a JSON object breakdown of transaction details.
|
||||
// POST
|
||||
async function getRawTransactionBulk(req, res, next) {
|
||||
try {
|
||||
let verbose = 0
|
||||
if (req.body.verbose) verbose = 1
|
||||
|
||||
const txids = req.body.txids
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "txids must be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// stub response object
|
||||
const returnResponse = {
|
||||
status: 100,
|
||||
json: {
|
||||
error: ""
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each txid in the array.
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const txid = txids[i]
|
||||
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: `Encountered empty TXID` })
|
||||
}
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `parameter 1 must be of length 64 (not ${txid.length})`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through each txid and create an array of promises
|
||||
const promises = txids.map(async txid =>
|
||||
getRawTransactionsFromNode(txid, verbose)
|
||||
)
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
res.status(200)
|
||||
return res.json(axiosResult)
|
||||
} 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 rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/getRawTransactionBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Get a JSON object breakdown of transaction details.
|
||||
// GET
|
||||
async function getRawTransactionSingle(req, res, next) {
|
||||
try {
|
||||
let verbose = 0
|
||||
if (req.query.verbose === "true") verbose = 1
|
||||
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
const data = await getRawTransactionsFromNode(txid, verbose)
|
||||
|
||||
return res.json(data)
|
||||
} 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 rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/getRawTransactionSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit a raw transaction to the BCH network.
|
||||
async function sendRawTransactionBulk(req, res, next) {
|
||||
try {
|
||||
// Validation
|
||||
const hexes = req.body.hexes
|
||||
|
||||
// Reject if input is not an array
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex must be an array" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const hex = hexes[i]
|
||||
|
||||
if (hex === "") {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Encountered empty hex`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Dev Note CT 1/31/2019:
|
||||
// Sending the 'sendrawtrnasaction' RPC call to a full node in parallel will
|
||||
// not work. Testing showed that the full node will return the same TXID for
|
||||
// different TX hexes. I believe this is by design, to prevent double spends.
|
||||
// In parallel, we are essentially asking the node to broadcast a new TX before
|
||||
// it's finished broadcast the previous one. Serial execution is required.
|
||||
|
||||
// How to send TX hexes in parallel the WRONG WAY:
|
||||
/*
|
||||
// Collect an array of promises.
|
||||
const promises = hexes.map(async (hex: any) => {
|
||||
requestConfig.data.id = "sendrawtransaction"
|
||||
requestConfig.data.method = "sendrawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult: Array<any> = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
*/
|
||||
|
||||
// Sending them serially.
|
||||
const result = []
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const hex = hexes[i]
|
||||
|
||||
requestConfig.data.id = "sendrawtransaction"
|
||||
requestConfig.data.method = "sendrawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const rpcResult = await BitboxHTTP(requestConfig)
|
||||
|
||||
result.push(rpcResult.data.result)
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in rawtransactions.ts/sendRawTransactionBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit a raw transaction to the BCH network.
|
||||
async function sendRawTransactionSingle(req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex // URL parameter
|
||||
|
||||
// Reject if input is not an array or a string
|
||||
if (typeof hex !== "string") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex must be a string" })
|
||||
}
|
||||
|
||||
// Validation
|
||||
if (hex === "") {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Encountered empty hex`
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// RPC call
|
||||
requestConfig.data.id = "sendrawtransaction"
|
||||
requestConfig.data.method = "sendrawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const rpcResult = await BitboxHTTP(requestConfig)
|
||||
|
||||
const result = rpcResult.data.result
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(
|
||||
`Error in rawtransactions.ts/sendRawTransactionSingle().`,
|
||||
err
|
||||
)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
getRawTransactionsFromNode,
|
||||
testableComponents: {
|
||||
root,
|
||||
decodeRawTransactionSingle,
|
||||
decodeRawTransactionBulk,
|
||||
decodeScriptSingle,
|
||||
decodeScriptBulk,
|
||||
getRawTransactionBulk,
|
||||
getRawTransactionSingle,
|
||||
sendRawTransactionBulk,
|
||||
sendRawTransactionSingle
|
||||
}
|
||||
}
|
||||
@@ -1,139 +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 BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
// 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 = 100
|
||||
|
||||
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 = BITBOX.Address.toCashAddress(addr)
|
||||
|
||||
// Return true if the network and address both match testnet
|
||||
const addrIsTest = BITBOX.Address.isTestnetAddress(cashAddr)
|
||||
if (network === "testnet" && addrIsTest) return true
|
||||
|
||||
// Return true if the network and address both match mainnet
|
||||
const addrIsMain = BITBOX.Address.isMainnetAddress(cashAddr)
|
||||
if (network === "mainnet" && addrIsMain) return true
|
||||
|
||||
return false
|
||||
} catch (err) {
|
||||
logger.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
|
||||
})
|
||||
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 }
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
return { msg: false, status: 500 }
|
||||
} catch (err) {
|
||||
wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err)
|
||||
return { msg: false, status: 500 }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const axios = require("axios")
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
|
||||
const getInstance = () => BitboxHTTP
|
||||
|
||||
module.exports = getInstance
|
||||
@@ -1,20 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const getRequestConfig = (method, params) => ({
|
||||
method: "post",
|
||||
auth: {
|
||||
username,
|
||||
password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0",
|
||||
id: method,
|
||||
method,
|
||||
params
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = getRequestConfig
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,188 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
const BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 3 }
|
||||
|
||||
// Manipulates and formats the raw data comming from Insight API.
|
||||
const processInputs = tx => {
|
||||
// Add legacy and cashaddr to tx vin
|
||||
if (tx.vin) {
|
||||
tx.vin.forEach(vin => {
|
||||
if (!vin.coinbase) {
|
||||
vin.value = vin.valueSat
|
||||
const address = vin.addr
|
||||
if (address) {
|
||||
vin.legacyAddress = BITBOX.Address.toLegacyAddress(address)
|
||||
vin.cashAddress = BITBOX.Address.toCashAddress(address)
|
||||
delete vin.addr
|
||||
}
|
||||
delete vin.valueSat
|
||||
delete vin.doubleSpentTxID
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add legacy and cashaddr to tx vout
|
||||
if (tx.vout) {
|
||||
tx.vout.forEach(vout => {
|
||||
// Overwrite value string with value in satoshis
|
||||
//vout.value = parseFloat(vout.value) * 100000000
|
||||
|
||||
if (vout.scriptPubKey) {
|
||||
if (vout.scriptPubKey.addresses) {
|
||||
const cashAddrs = []
|
||||
vout.scriptPubKey.addresses.forEach(addr => {
|
||||
const cashAddr = BITBOX.Address.toCashAddress(addr)
|
||||
cashAddrs.push(cashAddr)
|
||||
})
|
||||
vout.scriptPubKey.cashAddrs = cashAddrs
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.post("/details", detailsBulk)
|
||||
router.get("/details/:txid", detailsSingle)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "transaction" })
|
||||
}
|
||||
|
||||
// Retrieve transaction data from the Insight API
|
||||
// This function is also used by the SLP route library.
|
||||
async function transactionsFromInsight(txid) {
|
||||
try {
|
||||
const path = `${process.env.BITCOINCOM_BASEURL}tx/${txid}`
|
||||
|
||||
// Query the Insight server.
|
||||
const response = await axios.get(path)
|
||||
//console.log(`Insight output: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
// Parse the data.
|
||||
const parsed = response.data
|
||||
if (parsed) processInputs(parsed)
|
||||
|
||||
return parsed
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function detailsBulk(req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "txids needs to be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing transaction/details with these txids: `, txids)
|
||||
|
||||
// Collect an array of promises
|
||||
const promises = txids.map(
|
||||
async txid => await transactionsFromInsight(txid)
|
||||
)
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const result = await Promise.all(promises)
|
||||
|
||||
// Return the array of retrieved transaction information.
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in transactions.ts/detailsBulk().`, err)
|
||||
|
||||
//console.log(`Error in transaction details: `, err)
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler. Retrieve any unconfirmed TX information for a given address.
|
||||
async function detailsSingle(req, res, next) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(txid)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "txid can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing transaction.ts/detailsSingle with this txid: `,
|
||||
txid
|
||||
)
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await transactionsFromInsight(txid)
|
||||
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} 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 rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in transactions.ts/detailsSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
transactionsFromInsight,
|
||||
testableComponents: {
|
||||
root,
|
||||
detailsBulk,
|
||||
detailsSingle
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
const BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/validateAddress/:address", validateAddressSingle)
|
||||
router.post("/validateAddress", validateAddressBulk)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "util" })
|
||||
}
|
||||
|
||||
async function validateAddressSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "validateaddress"
|
||||
requestConfig.data.method = "validateaddress"
|
||||
requestConfig.data.params = [address]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in util.ts/validateAddressSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function validateAddressBulk(req, res, next) {
|
||||
try {
|
||||
const addresses = req.body.addresses
|
||||
|
||||
// Reject if addresses is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "addresses needs to be an array. Use GET for single address."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const address = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Executing util/validate with these addresses: `, addresses)
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each address and creates an array of requests to call in parallel
|
||||
const promises = addresses.map(async address => {
|
||||
requestConfig.data.id = "validateaddress"
|
||||
requestConfig.data.method = "validateaddress"
|
||||
requestConfig.data.params = [address]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in util.ts/validateAddressSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
validateAddressSingle,
|
||||
validateAddressBulk
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user