diff --git a/src/routes/v3/blockbook.js b/src/routes/v3/blockbook.js index 747098a..e946718 100644 --- a/src/routes/v3/blockbook.js +++ b/src/routes/v3/blockbook.js @@ -27,6 +27,8 @@ router.get("/balance/:address", balanceSingle) router.post("/balance", balanceBulk) router.get("/utxos/:address", utxosSingle) router.post("/utxos", utxosBulk) +router.get("/tx/:txid", txSingle) +router.post("/tx", txBulk) // Root API endpoint. Simply acknowledges that it exists. function root(req, res, next) { @@ -367,6 +369,135 @@ async function utxosBulk(req, res, next) { } } +// Query the Blockbook Node API for transactions on a single TXID. +// Returns a Promise. +async function transactionsFromBlockbook(txid) { + try { + //console.log(`BLOCKBOOK_URL: ${BLOCKBOOK_URL}`) + + const path = `${process.env.BLOCKBOOK_URL}api/v2/tx/${txid}` + + // Query the Blockbook Node API. + const axiosResponse = await axios.get(path) + const retPromise = axiosResponse.data + //console.log(`retData: ${util.inspect(retData)}`) + + return retPromise + } catch (err) { + // Dev Note: Do not log error messages here. Throw them instead and let the + // parent function handle it. + throw err + } +} + +// GET handler for single transaction details. +async function txSingle(req, res, next) { + try { + const address = 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: "address can not be an array. Use POST for bulk upload." + }) + } + + wlogger.debug(`Executing blockbook/txSingle with this txid: `, txid) + + // TODO: Add regex comparison of txid to ensure it's valid. + + // Query the Blockbook Node API. + const retData = await transactionsFromBlockbook(address) + + // 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. + wlogger.error(`Error in blockbook.js/txSingle().`, err) + + res.status(500) + return res.json({ error: util.inspect(err) }) + } +} + +// POST handler for bulk queries on tx details +async function txBulk(req, res, next) { + try { + let txids = req.body.txids + const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0 + + // 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 address." + }) + } + + // 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.` + }) + } + + wlogger.debug(`Executing blockbook.js/txBulk with these txids: `, txids) + + // Validate each element in the txids array. + for (let i = 0; i < txids.length; i++) { + const thisTxid = txids[i] + + if (!thisTxid || thisTxid === "") { + res.status(400) + return res.json({ error: "txid can not be empty" }) + } + + // TODO: Add regex comparison of txid to ensure it's valid. + } + + // Loops through each address and creates an array of Promises, querying + // Insight API in parallel. + txids = txids.map(async (txid, index) => + //console.log(`address: ${address}`) + transactionsFromBlockbook(txid) + ) + + // Wait for all parallel Insight requests to return. + const result = await axios.all(txids) + + // 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 }) + } + + wlogger.error(`Error in blockbook.js/txBulk().`, err) + + res.status(500) + return res.json({ error: util.inspect(err) }) + } +} + module.exports = { router, testableComponents: { @@ -374,6 +505,8 @@ module.exports = { balanceSingle, balanceBulk, utxosSingle, - utxosBulk + utxosBulk, + txSingle, + txBulk } } diff --git a/test/v3/blockbook.js b/test/v3/blockbook.js index b127daa..557fc35 100644 --- a/test/v3/blockbook.js +++ b/test/v3/blockbook.js @@ -37,6 +37,9 @@ describe("#Blockbook Router", () => { process.env.BLOCKBOOK_URL = "http://fakeurl/api/" mockServerUrl = `http://fakeurl` } + + if (!process.env.NETWORK) process.env.NETWORK = "testnet" + // console.log(`Testing type is: ${process.env.TEST}`) })