From ce55351f51488bf6c163e86d9a32fdef625752b5 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Jul 2019 20:30:51 -0700 Subject: [PATCH] Added transaction library for Insight --- src/app.js | 2 + src/routes/v3/insight/transaction.js | 187 +++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/routes/v3/insight/transaction.js diff --git a/src/app.js b/src/app.js index 8fef0ff..4c56b62 100644 --- a/src/app.js +++ b/src/app.js @@ -60,6 +60,7 @@ const bitcoreV3 = require("./routes/v3/bitcore") const blockbookV3 = require("./routes/v3/blockbook") const insightV3 = require("./routes/v3/insight") const insightBlockV3 = require("./routes/v3/insight/block") +const insightTranactionV3 = require("./routes/v3/insight/transaction") require("dotenv").config() @@ -143,6 +144,7 @@ app.use(`/${v3prefix}/` + `bitcore`, bitcoreV3.router) app.use(`/${v3prefix}/` + `blockbook`, blockbookV3.router) app.use(`/${v3prefix}/` + `insight`, insightV3.router) app.use(`/${v3prefix}/` + `insight/block`, insightBlockV3.router) +app.use(`/${v3prefix}/` + `insight/transaction`, insightTranactionV3.router) // catch 404 and forward to error handler app.use((req, res, next) => { diff --git a/src/routes/v3/insight/transaction.js b/src/routes/v3/insight/transaction.js new file mode 100644 index 0000000..ccb16e9 --- /dev/null +++ b/src/routes/v3/insight/transaction.js @@ -0,0 +1,187 @@ +"use strict" + +const express = require("express") +const router = express.Router() +const axios = require("axios") + +const routeUtils = require("../route-utils") +const wlogger = require("../../../util/winston-logging") + +const BITBOXJS = require("@chris.troutner/bitbox-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.` + }) + } + + wlogger.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." + }) + } + + wlogger.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 + } +}