From 1f1e3c781fbeb4d66a6000d47a6240ce49ff381d Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Mar 2021 08:28:51 -0800 Subject: [PATCH 1/6] Updating to bch-js v4.15.19 --- package.json | 2 +- test/v4/rate-limits.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index b791c6a..048a4cc 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "node": ">=10.15.1" }, "dependencies": { - "@psf/bch-js": "^4.15.18", + "@psf/bch-js": "^4.15.19", "apidoc": "^0.26.0", "axios": "^0.21.1", "bitcore-lib-cash": "^8.23.1", diff --git a/test/v4/rate-limits.js b/test/v4/rate-limits.js index f2f95bd..b88809b 100644 --- a/test/v4/rate-limits.js +++ b/test/v4/rate-limits.js @@ -2,7 +2,6 @@ const chai = require('chai') const assert = chai.assert -// const nock = require('nock') // HTTP mocking const sinon = require('sinon') // Used for debugging. From 47ddc6f1866c6b967b43a23efbe80c3e8ade1648 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Mar 2021 08:41:09 -0800 Subject: [PATCH 2/6] feat(v3 & blockbook): Deprecating v3 route and blockbook indexer --- src/app.js | 57 +- src/routes/v3/blockbook.js | 521 ------ src/routes/v3/electrumx.js | 1373 -------------- src/routes/v3/encryption.js | 180 -- src/routes/v3/full-node/blockchain.js | 942 ---------- src/routes/v3/full-node/control.js | 116 -- src/routes/v3/full-node/mining.js | 169 -- src/routes/v3/full-node/network.js | 168 -- src/routes/v3/full-node/rawtransactions.js | 570 ------ src/routes/v3/health-check.js | 16 - src/routes/v3/ninsight.js | 32 - src/routes/v3/price.js | 155 -- src/routes/v3/route-utils.js | 183 -- src/routes/v3/services/slpdb.js | 351 ---- src/routes/v3/slp.js | 1691 ----------------- src/routes/v3/util.js | 604 ------- src/routes/v3/xpub.js | 80 - test/v3/a01-electrumx.js | 1894 -------------------- test/v3/blockbook.js | 1122 ------------ test/v3/blockchain.js | 1826 ------------------- test/v3/control.js | 164 -- test/v3/e2e/address.js | 60 - test/v3/encryption.js | 146 -- test/v3/helpers/panda.js | 29 - test/v3/integration/README.md | 7 - test/v3/integration/price.js | 45 - test/v3/integration/rate-limits.js | 164 -- test/v3/integration/raw-transactions.js | 191 -- test/v3/integration/slp.js | 261 --- test/v3/integration/slpdb.js | 31 - test/v3/mining.js | 224 --- test/v3/mocks/address-mock.js | 160 -- test/v3/mocks/bitcore-mock.js | 35 - test/v3/mocks/block-mock.js | 42 - test/v3/mocks/blockbook-mock.js | 75 - test/v3/mocks/blockchain-mock.js | 300 ---- test/v3/mocks/control-mock.js | 45 - test/v3/mocks/electrumx-mock.js | 122 -- test/v3/mocks/encryption-mocks.js | 182 -- test/v3/mocks/express-mocks.js | 89 - test/v3/mocks/mining-mocks.js | 21 - test/v3/mocks/price-mock.js | 241 --- test/v3/mocks/raw-transactions-mocks.js | 157 -- test/v3/mocks/slp-mocks.js | 545 ------ test/v3/mocks/slpjs-mocks.js | 71 - test/v3/mocks/transaction-mocks.js | 74 - test/v3/mocks/util-mocks.js | 130 -- test/v3/price.js | 280 --- test/v3/rate-limits.js | 460 ----- test/v3/raw-transactions.js | 1177 ------------ test/v3/slp.js | 1655 ----------------- test/v3/util.js | 519 ------ test/v3/xpub.js | 143 -- test/v4/blockbook.js | 1122 ------------ 54 files changed, 1 insertion(+), 21016 deletions(-) delete mode 100644 src/routes/v3/blockbook.js delete mode 100644 src/routes/v3/electrumx.js delete mode 100644 src/routes/v3/encryption.js delete mode 100644 src/routes/v3/full-node/blockchain.js delete mode 100644 src/routes/v3/full-node/control.js delete mode 100644 src/routes/v3/full-node/mining.js delete mode 100644 src/routes/v3/full-node/network.js delete mode 100644 src/routes/v3/full-node/rawtransactions.js delete mode 100644 src/routes/v3/health-check.js delete mode 100644 src/routes/v3/ninsight.js delete mode 100644 src/routes/v3/price.js delete mode 100644 src/routes/v3/route-utils.js delete mode 100644 src/routes/v3/services/slpdb.js delete mode 100644 src/routes/v3/slp.js delete mode 100644 src/routes/v3/util.js delete mode 100644 src/routes/v3/xpub.js delete mode 100644 test/v3/a01-electrumx.js delete mode 100644 test/v3/blockbook.js delete mode 100644 test/v3/blockchain.js delete mode 100644 test/v3/control.js delete mode 100644 test/v3/e2e/address.js delete mode 100644 test/v3/encryption.js delete mode 100644 test/v3/helpers/panda.js delete mode 100644 test/v3/integration/README.md delete mode 100644 test/v3/integration/price.js delete mode 100644 test/v3/integration/rate-limits.js delete mode 100644 test/v3/integration/raw-transactions.js delete mode 100644 test/v3/integration/slp.js delete mode 100644 test/v3/integration/slpdb.js delete mode 100644 test/v3/mining.js delete mode 100644 test/v3/mocks/address-mock.js delete mode 100644 test/v3/mocks/bitcore-mock.js delete mode 100644 test/v3/mocks/block-mock.js delete mode 100644 test/v3/mocks/blockbook-mock.js delete mode 100644 test/v3/mocks/blockchain-mock.js delete mode 100644 test/v3/mocks/control-mock.js delete mode 100644 test/v3/mocks/electrumx-mock.js delete mode 100644 test/v3/mocks/encryption-mocks.js delete mode 100644 test/v3/mocks/express-mocks.js delete mode 100644 test/v3/mocks/mining-mocks.js delete mode 100644 test/v3/mocks/price-mock.js delete mode 100644 test/v3/mocks/raw-transactions-mocks.js delete mode 100644 test/v3/mocks/slp-mocks.js delete mode 100644 test/v3/mocks/slpjs-mocks.js delete mode 100644 test/v3/mocks/transaction-mocks.js delete mode 100644 test/v3/mocks/util-mocks.js delete mode 100644 test/v3/price.js delete mode 100644 test/v3/rate-limits.js delete mode 100644 test/v3/raw-transactions.js delete mode 100644 test/v3/slp.js delete mode 100644 test/v3/util.js delete mode 100644 test/v3/xpub.js delete mode 100644 test/v4/blockbook.js diff --git a/src/app.js b/src/app.js index 845e7cb..dc1318d 100644 --- a/src/app.js +++ b/src/app.js @@ -25,22 +25,6 @@ const jwtAuth = require('./middleware/jwt-auth') // Logging for API requests. const logReqInfo = require('./middleware/req-logging') -// v3 -const healthCheckV3 = require('./routes/v3/health-check') -const BlockchainV3 = require('./routes/v3/full-node/blockchain') -const ControlV3 = require('./routes/v3/full-node/control') -const MiningV3 = require('./routes/v3/full-node/mining') -const networkV3 = require('./routes/v3/full-node/network') -const RawtransactionsV3 = require('./routes/v3/full-node/rawtransactions') -const utilV3 = require('./routes/v3/util') -const SlpV3 = require('./routes/v3/slp') -const xpubV3 = require('./routes/v3/xpub') -const BlockbookV3 = require('./routes/v3/blockbook') -const Ninsight = require('./routes/v3/ninsight') -const ElectrumXV3 = require('./routes/v3/electrumx') -const EncryptionV3 = require('./routes/v3/encryption') -const PriceV3 = require('./routes/v3/price') - // v4 const healthCheckV4 = require('./routes/v4/health-check') const BlockchainV4 = require('./routes/v4/full-node/blockchain') @@ -51,32 +35,19 @@ const RawtransactionsV4 = require('./routes/v4/full-node/rawtransactions') const utilV4 = require('./routes/v4/util') const SlpV4 = require('./routes/v4/slp') const xpubV4 = require('./routes/v4/xpub') -const BlockbookV4 = require('./routes/v4/blockbook') const ElectrumXV4 = require('./routes/v4/electrumx') const EncryptionV4 = require('./routes/v4/encryption') const PriceV4 = require('./routes/v4/price') +const Ninsight = require('./routes/v4/ninsight') require('dotenv').config() -// Instantiate v3 route libraries. -const blockchainV3 = new BlockchainV3() -const controlV3 = new ControlV3() -const miningV3 = new MiningV3() -const rawtransactionsV3 = new RawtransactionsV3() -const slpV3 = new SlpV3() -const blockbookV3 = new BlockbookV3() -const electrumxv3 = new ElectrumXV3() -electrumxv3.connect() -const encryptionv3 = new EncryptionV3() -const pricev3 = new PriceV3() - // Instantiate v4 route libraries. const blockchainV4 = new BlockchainV4() const controlV4 = new ControlV4() const miningV4 = new MiningV4() const rawtransactionsV4 = new RawtransactionsV4() const slpV4 = new SlpV4() -const blockbookV4 = new BlockbookV4() const electrumxv4 = new ElectrumXV4() electrumxv4.connect() const encryptionv4 = new EncryptionV4() @@ -122,44 +93,20 @@ app.use(express.static(path.join(__dirname, 'public'))) // Log requests for later analysis. app.use('/', logReqInfo) -// const v2prefix = "v2" -const v3prefix = 'v3' const v4prefix = 'v4' // Inspect the header for a JWT token. -app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders) app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders) // Instantiate the authorization middleware, used to implement pro-tier rate limiting. // Handles Anonymous and Basic Authorization schemes used by passport.js const auth = new AuthMW() -app.use(`/${v3prefix}/`, auth.mw()) app.use(`/${v4prefix}/`, auth.mw()) -// Rate limit on all v3 routes -// Establish and enforce rate limits. -// app.use(`/${v3prefix}/`, rateLimits.routeRateLimit) -app.use(`/${v3prefix}/`, rateLimits.rateLimitByResource) - // Rate limit on all v4 routes // Establish and enforce rate limits. app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource) -// Connect v3 routes -app.use(`/${v3prefix}/` + 'health-check', healthCheckV3) -app.use(`/${v3prefix}/` + 'blockchain', blockchainV3.router) -app.use(`/${v3prefix}/` + 'control', controlV3.router) -app.use(`/${v3prefix}/` + 'mining', miningV3.router) -app.use(`/${v3prefix}/` + 'network', networkV3) -app.use(`/${v3prefix}/` + 'rawtransactions', rawtransactionsV3.router) -app.use(`/${v3prefix}/` + 'util', utilV3.router) -app.use(`/${v3prefix}/` + 'slp', slpV3.router) -app.use(`/${v3prefix}/` + 'xpub', xpubV3.router) -app.use(`/${v3prefix}/` + 'blockbook', blockbookV3.router) -app.use(`/${v3prefix}/` + 'electrumx', electrumxv3.router) -app.use(`/${v3prefix}/` + 'encryption', encryptionv3.router) -app.use(`/${v3prefix}/` + 'price', pricev3.router) - // Connect v4 routes app.use(`/${v4prefix}/` + 'health-check', healthCheckV4) app.use(`/${v4prefix}/` + 'blockchain', blockchainV4.router) @@ -170,13 +117,11 @@ app.use(`/${v4prefix}/` + 'rawtransactions', rawtransactionsV4.router) app.use(`/${v4prefix}/` + 'util', utilV4.router) app.use(`/${v4prefix}/` + 'slp', slpV4.router) app.use(`/${v4prefix}/` + 'xpub', xpubV4.router) -app.use(`/${v4prefix}/` + 'blockbook', blockbookV4.router) app.use(`/${v4prefix}/` + 'electrumx', electrumxv4.router) app.use(`/${v4prefix}/` + 'encryption', encryptionv4.router) app.use(`/${v4prefix}/` + 'price', pricev4.router) const ninsight = new Ninsight() -app.use(`/${v3prefix}/` + 'ninsight', ninsight.router) app.use(`/${v4prefix}/` + 'ninsight', ninsight.router) // catch 404 and forward to error handler diff --git a/src/routes/v3/blockbook.js b/src/routes/v3/blockbook.js deleted file mode 100644 index 1ef0983..0000000 --- a/src/routes/v3/blockbook.js +++ /dev/null @@ -1,521 +0,0 @@ -/* - Blockbook API route -*/ - -'use strict' - -const express = require('express') -const axios = require('axios') -const wlogger = require('../../util/winston-logging') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -// Library for easily switching the API paths to use different instances of -// Blockbook. -const BlockbookPath = require('../../util/blockbook-path') -const BLOCKBOOKPATH = new BlockbookPath() -// BLOCKBOOKPATH.toOpenBazaar() - -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 BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -let _this - -class Blockbook { - constructor () { - _this = this - - _this.axios = axios - _this.routeUtils = routeUtils - _this.bchjs = bchjs - _this.BLOCKBOOKPATH = BLOCKBOOKPATH - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/balance/:address', _this.balanceSingle) - _this.router.post('/balance', _this.balanceBulk) - _this.router.get('/utxos/:address', _this.utxosSingle) - _this.router.post('/utxos', _this.utxosBulk) - _this.router.get('/tx/:txid', _this.txSingle) - _this.router.post('/tx', _this.txBulk) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // Root API endpoint. Simply acknowledges that it exists. - root (req, res, next) { - return res.json({ status: 'address' }) - } - - // Query the Blockbook Node API for a balance on a single BCH address. - // Returns a Promise. - async balanceFromBlockbook (thisAddress) { - try { - // console.log(`BLOCKBOOK_URL: ${BLOCKBOOK_URL}`) - - // Convert the address to a cashaddr without a prefix. - const addr = _this.bchjs.Address.toCashAddress(thisAddress) - - const path = `${_this.BLOCKBOOKPATH.addrPath}${addr}` - // console.log(`path: ${path}`) - - // Query the Blockbook Node API. - const options = { - method: 'get', - baseURL: path - } - - const axiosResponse = await _this.axios.request(options) - const retData = axiosResponse.data - // console.log(`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. - wlogger.debug('Error in blockbook.js/balanceFromBlockbook()') - throw err - } - } - - async balanceSingle (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.' - }) - } - - wlogger.debug( - 'Executing blockbook/balanceSingle with this address: ', - address - ) - - // Ensure the input is a valid BCH address. - try { - // const legacyAddr = bchjs.Address.toLegacyAddress(address) - _this.bchjs.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 = _this.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 Blockbook Node API. - const retData = await _this.balanceFromBlockbook(address) - - // Return the retrieved address information. - res.status(200) - return res.json(retData) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockbook.js/balanceSingle().', err) - - return _this.errorHandler(err, res) - } - } - - // POST handler for bulk queries on address details - async balanceBulk (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 (!_this.routeUtils.validateArraySize(req, addresses)) { - 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/balanceBulk 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 { - _this.bchjs.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 = _this.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) => - // console.log(`address: ${address}`) - _this.balanceFromBlockbook(address) - ) - - // Wait for all parallel Insight requests to return. - const result = await _this.axios.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in blockbook.js/balanceBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Query the Blockbook API for utxos associated with a BCH address. - // Returns a Promise. - async utxosFromBlockbook (thisAddress) { - try { - // console.log(`BLOCKBOOK_URL: ${BLOCKBOOK_URL}`) - - // Convert the address to a cashaddr without a prefix. - const addr = _this.bchjs.Address.toCashAddress(thisAddress) - - const path = `${_this.BLOCKBOOKPATH.utxoPath}${addr}` - // console.log(`path: ${path}`) - - // Query the Blockbook API. - // Query the Blockbook Node API. - const options = { - method: 'get', - baseURL: path - } - const axiosResponse = await _this.axios.request(options) - const retData = axiosResponse.data - // console.log(`retData: ${util.inspect(retData)}`) - - // Add the satoshis property to each UTXO. - for (let i = 0; i < retData.length; i++) { - retData[i].satoshis = Number(retData[i].value) - } - - return retData - } catch (err) { - // Dev Note: Do not log error messages here. Throw them instead and let the - // parent function handle it. - wlogger.debug('Error in blockbook.js/utxosFromBlockbook()') - throw err - } - } - - // GET handler for single balance - async utxosSingle (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.' - }) - } - - wlogger.debug( - 'Executing blockbook/utxosSingle with this address: ', - address - ) - - // Ensure the input is a valid BCH address. - try { - // const legacyAddr = bchjs.Address.toLegacyAddress(address) - _this.bchjs.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 = _this.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 Blockbook API. - const retData = await _this.utxosFromBlockbook(address) - - // Return the retrieved address information. - res.status(200) - return res.json(retData) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockbook.js/utxosSingle().', err) - return _this.errorHandler(err, res) - } - } - - // POST handler for bulk queries on address utxos - async utxosBulk (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 (!_this.routeUtils.validateArraySize(req, addresses)) { - 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/utxosBulk 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 { - _this.bchjs.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 = _this.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) => - // console.log(`address: ${address}`) - _this.utxosFromBlockbook(address) - ) - - // Wait for all parallel Insight requests to return. - const result = await _this.axios.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in blockbook.js/utxosBulk().', err) - return _this.errorHandler(err, res) - } - } - - // Query the Blockbook Node API for transactions on a single TXID. - // Returns a Promise. - async transactionsFromBlockbook (txid) { - try { - // console.log(`BLOCKBOOK_URL: ${BLOCKBOOK_URL}`) - - const path = `${_this.BLOCKBOOKPATH.txPath}${txid}` - // console.log(`path: ${path}`) - - // Query the Blockbook Node API. - const options = { - method: 'get', - baseURL: path - } - const axiosResponse = await _this.axios.request(options) - 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. - wlogger.debug('Error in blockbook.js/transactionsFromBlockbook()') - throw err - } - } - - // GET handler for single transaction details. - async txSingle (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.' - }) - } - - // TODO: Add regex comparison of txid to ensure it's valid. - if (txid.length !== 64) { - res.status(400) - return res.json({ - error: `txid must be of length 64 (not ${txid.length})` - }) - } - - wlogger.debug('Executing blockbook/txSingle with this txid: ', txid) - - // Query the Blockbook Node API. - const retData = await _this.transactionsFromBlockbook(txid) - - // Return the retrieved address information. - res.status(200) - return res.json(retData) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockbook.js/txSingle().', err) - - return _this.errorHandler(err, res) - } - } - - // POST handler for bulk queries on tx details - async 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 need to be an array. Use GET for single address.' - }) - } - - // Enforce array size rate limits - if (!_this.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. - if (thisTxid.length !== 64) { - res.status(400) - return res.json({ - error: `txid must be of length 64 (not ${thisTxid.length})` - }) - } - } - - // 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}`) - _this.transactionsFromBlockbook(txid) - ) - - // Wait for all parallel Insight requests to return. - const result = await _this.axios.all(txids) - - // Return the array of retrieved address information. - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in blockbook.js/txBulk().', err) - return _this.errorHandler(err, res) - } - } -} - -module.exports = Blockbook diff --git a/src/routes/v3/electrumx.js b/src/routes/v3/electrumx.js deleted file mode 100644 index 8e5cc67..0000000 --- a/src/routes/v3/electrumx.js +++ /dev/null @@ -1,1373 +0,0 @@ -/* - Electrum API route -*/ - -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') -const util = require('util') -const bitcore = require('bitcore-lib-cash') - -const ElectrumCash = require('electrum-cash').ElectrumClient -// const ElectrumCash = require('/home/trout/work/personal/electrum-cash/electrum.js').Client // eslint-disable-line - -const wlogger = require('../../util/winston-logging') -const config = require('../../../config') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -let _this - -class Electrum { - constructor () { - _this = this - - _this.config = config - _this.axios = axios - _this.routeUtils = routeUtils - _this.bchjs = bchjs - _this.bitcore = bitcore - - _this.electrumx = new ElectrumCash( - 'bch-api', - '1.4.1', - process.env.FULCRUM_URL, - process.env.FULCRUM_PORT - // '192.168.0.6', - // '50002' - ) - - _this.isReady = false - // _this.connectToServers() - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/utxos/:address', _this.getUtxos) - _this.router.post('/utxos', _this.utxosBulk) - _this.router.get('/tx/data/:txid', _this.getTransactionDetails) - _this.router.post('/tx/data', _this.transactionDetailsBulk) - _this.router.post('/tx/broadcast', _this.broadcastTransaction) - _this.router.get('/block/headers/:height', _this.getBlockHeaders) - _this.router.post('/block/headers', _this.blockHeadersBulk) - _this.router.get('/balance/:address', _this.getBalance) - _this.router.post('/balance', _this.balanceBulk) - _this.router.get('/transactions/:address', _this.getTransactions) - _this.router.post('/transactions', _this.transactionsBulk) - _this.router.get('/unconfirmed/:address', _this.getMempool) - _this.router.post('/unconfirmed', _this.mempoolBulk) - } - - // Initializes a connection to electrum servers. - async connect () { - try { - console.log('Attempting to connect to ElectrumX server...') - - // console.log('_this.electrumx: ', _this.electrumx) - - // Return immediately if a connection has already been established. - if (_this.isReady) return true - - // Connect to the server. - await _this.electrumx.connect() - - // Set the connection flag. - _this.isReady = true - - // Periodically check the connection. If it's not connected, attempt to - // reconnect. - _this.reconnectIntervalHandle = setInterval(async function () { - const status = _this.electrumx.connection.status - // console.log(`Electrumx status: ${status}`) - - // 1 = connected. If we're not connected, attemp to reconnect. - if (status !== 1) { - wlogger.info(`Electrumx not connectes. Status: ${status}`) - wlogger.info('Attempting to reconnect...') - await _this.electrumx.connect() - wlogger.info('...reconnected.') - } - }, 30000) - - console.log('...Successfully connected to ElectrumX server.') - - // console.log(`_this.isReady: ${_this.isReady}`) - return _this.isReady - } catch (err) { - console.log('err: ', err) - wlogger.error('Error in electrumx.js/connect(): ', err) - // throw err - } - } - - // Disconnect from the ElectrumX server. - async disconnect () { - try { - // Return immediately if the isReady flag is false. - if (!_this.isReady) return true - - // Disable the reconnect timer. - clearInterval(_this.reconnectIntervalHandle) - - // Disconnect from the server. - await _this.electrumx.disconnect() - - // Clear the isReady flag. - _this.isReady = false - - // Return true to signal that the disconnection happened successfully. - return true - } catch (err) { - // console.log(`err: `, err) - wlogger.error('Error in electrumx.js/disconnect()') - throw err - } - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Handle error patterns specific to this route. - if (err.message) { - res.status(400) - return res.json({ success: false, error: err.message }) - } - - // If error can be handled, return the stack trace - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // Root API endpoint. Simply acknowledges that it exists. - root (req, res, next) { - return res.json({ status: 'electrumx' }) - } - - // Returns a promise that resolves to UTXO data for an address. Expects input - // to be a cash address, and input validation to have already been done by - // parent, calling function. - async _utxosFromElectrumx (address) { - try { - // Convert the address to a scripthash. - const scripthash = _this.addressToScripthash(address) - - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Query the utxos from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.scripthash.listunspent', - scripthash - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/_utxosFromElectrumx(): ', err) - throw err - } - } - - /** - * @api {get} /electrumx/utxos/{addr} Get utxos for a single address. - * @apiName UTXOs for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with UTXOs associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/electrumx/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getUtxos (req, res, next) { - try { - const address = req.params.address - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - success: false, - error: 'address can not be an array. Use POST for bulk upload.' - }) - } - - const cashAddr = _this.bchjs.Address.toCashAddress(address) - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - wlogger.debug( - 'Executing electrumx/getUtxos with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._utxosFromElectrumx(cashAddr) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - utxos: electrumResponse - }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getUtxos().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/utxo Get utxos for an array of addresses. - * @apiName UTXOs for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with UTXOs associated with an address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on address details - async utxosBulk (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 (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/utxoBulk 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 { - _this.bchjs.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 = _this.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) => { - // console.log(`address: ${address}`) - const utxos = await _this._utxosFromElectrumx(address) - - return { - utxos, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - utxos: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/utxoBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to transaction details data for a txid. - // Expects input to be a txid string, and input validation to have already - // been done by parent, calling function. - async _transactionDetailsFromElectrum (txid, verbose = true) { - try { - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Query the utxos from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.transaction.get', - txid, - verbose - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err: ', err) - - // Write out error to error log. - wlogger.error( - 'Error in elecrumx.js/_transactionDetailsFromElectrum(): ', - err - ) - throw err - } - } - - /** - * @api {get} /electrumx/tx/data/{txid} Get transaction details for a TXID - * @apiName transaction details for a TXID - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with transaction details of the TXID - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json" - * - */ - // GET handler for single transaction - async getTransactionDetails (req, res, next) { - try { - const txid = req.params.txid - const verbose = req.query.verbose - - // Reject if txid is anything other than a string - if (typeof txid !== 'string') { - res.status(400) - return res.json({ - success: false, - error: 'txid must be a string' - }) - } - - wlogger.debug( - 'Executing electrumx/getTransactionDetails with this txid: ', - txid - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._transactionDetailsFromElectrum( - txid, - verbose - ) - // console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (electrumResponse instanceof Error) { - res.status(400) - return res.json({ - success: false, - error: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - details: electrumResponse - }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getTransactionDetails().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/tx/data Get transaction details for an array of TXIDs - * @apiName Transaction details for an array of TXIDs - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with transaction details of an array of TXIDs. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}' - * - * - */ - // POST handler for bulk queries on transaction details - async transactionDetailsBulk (req, res, next) { - try { - const txids = req.body.txids - const verbose = req.body.verbose || true - - // Reject if txids is not an array. - if (!Array.isArray(txids)) { - res.status(400) - return res.json({ - success: false, - error: 'txids needs to be an array. Use GET for single txid.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, txids)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/transactionDetailsBulk with these txids: ', - txids - ) - - // Loops through each address and creates an array of Promises, querying - // the Electrum server in parallel. - const transactions = txids.map(async (txid, index) => { - // console.log(`address: ${address}`) - const details = await _this._transactionDetailsFromElectrum( - txid, - verbose - ) - - return { details, txid } - }) - - // Wait for all parallel Electrum requests to return. - const result = await Promise.all(transactions) - - // Return the array of retrieved transaction details. - res.status(200) - return res.json({ - success: true, - transactions: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/transactionDetailsBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to transaction ID of the broadcasted transaction or an error. - // Expects input to be a txHex string, and input validation to have already - // been done by parent, calling function. - async _broadcastTransactionWithElectrum (txHex) { - try { - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Broadcast the transaction hex to the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.transaction.broadcast', - txHex - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err: ', err) - - // Write out error to error log. - wlogger.error( - 'Error in elecrumx.js/_transactionDetailsFromElectrum(): ', - err - ) - throw err - } - } - - /** - * @api {post} /electrumx/tx/broadcast Broadcast a raw transaction - * @apiName Broadcast a raw transaction - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}' - * - */ - // POST handler for broadcasting a single transaction - async broadcastTransaction (req, res, next) { - try { - const txHex = req.body.txHex - - if (typeof txHex !== 'string') { - res.status(400) - return res.json({ - success: false, - error: 'request body must be a string.' - }) - } - - wlogger.debug( - 'Executing electrumx/broadcastTransaction with this tx hex: ', - txHex - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._broadcastTransactionWithElectrum(txHex) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (electrumResponse instanceof Error) { - res.status(400) - return res.json({ - success: false, - error: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - txid: electrumResponse - }) - } catch (err) { - wlogger.error('Error in electrumx.js/broadcastTransaction().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to block header data for a block height. - // Expects input to be a height number, and input validation to have already - // been done by parent, calling function. - async _blockHeadersFromElectrum (height, count = 1) { - try { - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Query the block header from the ElectrumX server. - const electrumResponse = await _this.electrumx.request('blockchain.block.headers', height, count) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - const HEADER_SIZE = 80 * 2 - - if (!(electrumResponse instanceof Error)) { - const headers = electrumResponse.hex.match(new RegExp(`.{1,${HEADER_SIZE}}`, 'g')) - return headers - } - - return electrumResponse - } catch (err) { - // console.log('err: ', err) - - // Write out error to error log. - wlogger.error( - 'Error in elecrumx.js/_blockHeaderFromElectrum(): ', - err - ) - throw err - } - } - - /** - * @api {get} /electrumx/block/headers/{height} Get `count` block headers starting at a height - * @apiName Block header data for a `count` blocks starting at a block height - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array with block headers starting at the block height - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/electrumx/block/header/42?count=2" -H "accept: application/json" - * - */ - // GET handler for single block headers - async getBlockHeaders (req, res, next) { - try { - const height = Number(req.params.height) - const count = req.query.count === undefined ? 1 : Number(req.query.count) - - // Reject if height is not a number - if (Number.isNaN(height) || height < 0) { - res.status(400) - return res.json({ - success: false, - error: 'height must be a positive number' - }) - } - - // Reject if height is not a number - if (Number.isNaN(count) || count < 0) { - res.status(400) - return res.json({ - success: false, - error: 'count must be a positive number' - }) - } - - wlogger.debug( - 'Executing electrumx/getBlockHeaders with this height: ', - height - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._blockHeadersFromElectrum(height, count) - // console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (electrumResponse instanceof Error) { - res.status(400) - return res.json({ - success: false, - error: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - headers: electrumResponse - }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getBlockHeader().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/block/headers Get block headers for an array of height + count pairs - * @apiName Block headers for an array of height + count pairs - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with blockheaders of an array of TXIDs. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}' - * - */ - // POST handler for bulk queries on block headers - async blockHeadersBulk (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({ - success: false, - error: 'heights needs to be an array. Use GET for single height.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, heights)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - success: false, - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/blockHeadersBulk with these txids: ', - heights - ) - - // Loops through each address and creates an array of Promises, querying - // the Electrum server in parallel. - const transactions = heights.map(async (obj) => { - const headers = await _this._blockHeadersFromElectrum( - obj.height, - obj.count - ) - - return { headers } - }) - - // Wait for all parallel Electrum requests to return. - const result = await Promise.all(transactions) - - // Return the array of retrieved transaction details. - res.status(200) - return res.json({ - success: true, - headers: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/blockHeadersBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to a balance for an address. Expects input - // to be a cash address, and input validation to have already been done by - // parent, calling function. - async _balanceFromElectrumx (address) { - try { - // Convert the address to a scripthash. - const scripthash = _this.addressToScripthash(address) - - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Query the address balance from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.scripthash.get_balance', - scripthash - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err1: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/_utxosFromElectrumx(): ', err) - throw err - } - } - - /** - * @api {get} /electrumx/balance/{addr} Get balance for a single address. - * @apiName Balance for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with confirmed and unconfirmed balance associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getBalance (req, res, next) { - try { - const address = req.params.address - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - success: false, - error: 'address can not be an array. Use POST for bulk upload.' - }) - } - - // Ensure the address is in cash address format. - const cashAddr = _this.bchjs.Address.toCashAddress(address) - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - wlogger.debug( - 'Executing electrumx/getBalance with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._balanceFromElectrumx(cashAddr) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - balance: electrumResponse - }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getBalance().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/balance Get balances for an array of addresses. - * @apiName Balances for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of balanes associated with an array of address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on address balance - async balanceBulk (req, res, next) { - try { - let 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 (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/balanceBulk 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 { - _this.bchjs.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 = _this.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 - // ElectrumX API in parallel. - addresses = addresses.map(async (address, index) => { - // console.log(`address: ${address}`) - const balance = await _this._balanceFromElectrumx(address) - - return { - balance, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - balances: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/balanceBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves an array of transaction history for an - // address. Expects input to be a cash address, and input validation to have - // already been done by parent, calling function. - async _transactionsFromElectrumx (address) { - try { - // Convert the address to a scripthash. - const scripthash = _this.addressToScripthash(address) - - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Query the address transaction history from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.scripthash.get_history', - scripthash - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err1: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/_transactionsFromElectrumx(): ', err) - throw err - } - } - - /** - * @api {get} /electrumx/transactions/{addr} Get transaction history for a single address. - * @apiName Transaction history for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of historical transactions associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getTransactions (req, res, next) { - try { - const address = req.params.address - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - success: false, - error: 'address can not be an array. Use POST for bulk upload.' - }) - } - - // Ensure the address is in cash address format. - const cashAddr = _this.bchjs.Address.toCashAddress(address) - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - wlogger.debug( - 'Executing electrumx/getTransactions with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._transactionsFromElectrumx(cashAddr) - // console.log(`_utxosFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - transactions: electrumResponse - }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getTransactions().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/transactions Get the transaction history for an array of addresses. - * @apiName Transactions for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of transactions associated with an array of address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on transaction histories for addresses. - async transactionsBulk (req, res, next) { - try { - let 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 (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/transactionsBulk 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 { - _this.bchjs.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 = _this.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 - // ElectrumX API in parallel. - addresses = addresses.map(async (address, index) => { - // console.log(`address: ${address}`) - const transactions = await _this._transactionsFromElectrumx(address) - - return { - transactions, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - transactions: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/transactionsBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a promise that resolves to unconfirmed UTXO data (mempool) for an address. - // Expects input to be a cash address, and input validation to have - // already been done by parent, calling function. - async _mempoolFromElectrumx (address) { - try { - // Convert the address to a scripthash. - const scripthash = _this.addressToScripthash(address) - - if (!_this.isReady) { - throw new Error( - 'ElectrumX server connection is not ready. Call await connectToServer() first.' - ) - } - - // Query the unconfirmed utxos from the ElectrumX server. - const electrumResponse = await _this.electrumx.request( - 'blockchain.scripthash.get_mempool', - scripthash - ) - // console.log( - // `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}` - // ) - - return electrumResponse - } catch (err) { - // console.log('err: ', err) - - // Write out error to error log. - wlogger.error('Error in elecrumx.js/_mempoolFromElectrumx(): ', err) - throw err - } - } - - /** - * @api {get} /electrumx/unconfirmed/{addr} Get unconfirmed utxos for a single address. - * @apiName Unconfirmed UTXOs for a single address - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an object with unconfirmed UTXOs associated with an address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json" - * - */ - // GET handler for single balance - async getMempool (req, res, next) { - try { - const address = req.params.address - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - success: false, - error: 'address can not be an array. Use POST for bulk upload.' - }) - } - - // Ensure the address is in cash address format. - const cashAddr = _this.bchjs.Address.toCashAddress(address) - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - wlogger.debug( - 'Executing electrumx/getMempool with this address: ', - cashAddr - ) - - // Get data from ElectrumX server. - const electrumResponse = await _this._mempoolFromElectrumx(cashAddr) - // console.log(`_mempoolFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`) - - // Pass the error message if ElectrumX reports an error. - if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) { - res.status(400) - return res.json({ - success: false, - message: electrumResponse.message - }) - } - - res.status(200) - return res.json({ - success: true, - utxos: electrumResponse - }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in elecrumx.js/getMempool().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /electrumx/unconfirmed Get unconfirmed utxos for an array of addresses. - * @apiName Unconfirmed UTXOs for an array of addresses - * @apiGroup ElectrumX / Fulcrum - * @apiDescription Returns an array of objects with unconfirmed UTXOs associated with an address. - * Limited to 20 items per request. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}' - * - * - */ - // POST handler for bulk queries on address details - async mempoolBulk (req, res, next) { - try { - let 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 (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing electrumx.js/mempoolBulk 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 { - _this.bchjs.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 = _this.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) => { - // console.log(`address: ${address}`) - const utxos = await _this._mempoolFromElectrumx(address) - - return { - utxos, - address - } - }) - - // Wait for all parallel Insight requests to return. - const result = await Promise.all(addresses) - - // Return the array of retrieved address information. - res.status(200) - return res.json({ - success: true, - utxos: result - }) - } catch (err) { - wlogger.error('Error in electrumx.js/mempoolBulk().', err) - - return _this.errorHandler(err, res) - } - } - - // Convert a 'bitcoincash:...' address to a script hash used by ElectrumX. - addressToScripthash (addrStr) { - try { - // console.log(`addrStr: ${addrStr}`) - - const address = _this.bitcore.Address.fromString(addrStr) - // console.log(`address: ${address}`) - - const script = address.isPayToPublicKeyHash() - ? _this.bitcore.Script.buildPublicKeyHashOut(address) - : _this.bitcore.Script.buildScriptHashOut(address) - // console.log(`script: ${script}`) - - const scripthash = _this.bitcore.crypto.Hash.sha256(script.toBuffer()) - .reverse() - .toString('hex') - // console.log(`scripthash: ${scripthash}`) - - return scripthash - } catch (err) { - wlogger.error('Error in electrumx.js/addressToScripthash()') - throw err - } - } -} - -module.exports = Electrum diff --git a/src/routes/v3/encryption.js b/src/routes/v3/encryption.js deleted file mode 100644 index cb65bcf..0000000 --- a/src/routes/v3/encryption.js +++ /dev/null @@ -1,180 +0,0 @@ -/* - Encryption API route -*/ - -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') -const util = require('util') - -const wlogger = require('../../util/winston-logging') -const config = require('../../../config') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const BCHJS = require('@psf/bch-js') -const restURL = process.env.LOCAL_RESTURL - ? process.env.LOCAL_RESTURL - : 'https://api.fullstack.cash/v3/' -const bchjs = new BCHJS({ restURL }) - -let _this - -class Encryption { - constructor () { - _this = this - - _this.config = config - _this.axios = axios - _this.routeUtils = routeUtils - _this.bchjs = bchjs - // _this.blockbook = blockbook - // _this.rawTransactions = rawTransactions - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/publickey/:address', _this.getPublicKey) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Handle error patterns specific to this route. - if (err.message) { - res.status(400) - return res.json({ success: false, error: err.message }) - } - - wlogger.error('Unhandled error in Encryption library: ', err) - - // If error can be handled, return the stack trace - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // Root API endpoint. Simply acknowledges that it exists. - root (req, res, next) { - return res.json({ status: 'encryption' }) - } - - /** - * @api {get} /encryption/publickey/{addr} Get public key for a BCH address. - * @apiName Get encryption key for bch address - * @apiGroup Encryption - * @apiDescription Searches the blockchain for a public key associated with a - * BCH address. Returns an object. If successful, the publicKey property will - * contain a hexidecimal representation of the public key. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json" - * - */ - async getPublicKey (req, res, next) { - try { - const address = req.params.address - - // Reject if address is an array. - if (Array.isArray(address)) { - res.status(400) - return res.json({ - success: false, - error: 'address can not be an array.' - }) - } - - const cashAddr = _this.bchjs.Address.toCashAddress(address) - - // Prevent a common user error. Ensure they are using the correct network address. - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - success: false, - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - wlogger.debug( - 'Executing encryption/getPublicKey with this address: ', - cashAddr - ) - - const rawTxData = await _this.bchjs.Electrumx.transactions(cashAddr) - // console.log(`rawTxData: ${JSON.stringify(rawTxData, null, 2)}`) - - // Extract just the TXIDs - const txids = rawTxData.transactions.map((elem) => elem.tx_hash) - // console.log(`txids: ${JSON.stringify(txids, null, 2)}`) - - // throw error if there is no transaction history. - if (!txids || txids.length === 0) { - throw new Error('No transaction history.') - } - - // Loop through the transaction history and search for the public key. - for (let i = 0; i < txids.length; i++) { - const thisTx = txids[i] - - const txDetails = await _this.bchjs.RawTransactions.getRawTransaction( - thisTx, - true - ) - // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) - - const vin = txDetails.vin - - // Loop through each input. - for (let j = 0; j < vin.length; j++) { - const thisVin = vin[j] - // console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`) - - // Extract the script signature. - const scriptSig = thisVin.scriptSig.asm.split(' ') - // console.log(`scriptSig: ${JSON.stringify(scriptSig, null, 2)}`) - - // Extract the public key from the script signature. - const pubKey = scriptSig[scriptSig.length - 1] - // console.log(`pubKey: ${pubKey}`) - - // Generate cash address from public key. - const keyBuf = Buffer.from(pubKey, 'hex') - const ec = _this.bchjs.ECPair.fromPublicKey(keyBuf) - const cashAddr2 = _this.bchjs.ECPair.toCashAddress(ec) - // console.log(`cashAddr2: ${cashAddr2}`) - - // If public keys match, this is the correct public key. - if (cashAddr === cashAddr2) { - res.status(200) - return res.json({ - success: true, - publicKey: pubKey - }) - } - } - } - - res.status(200) - return res.json({ - success: false, - publicKey: 'not found' - }) - } catch (err) { - wlogger.error('Error in encryption.js/getPublicKey().', err) - - return _this.errorHandler(err, res) - } - } -} - -module.exports = Encryption diff --git a/src/routes/v3/full-node/blockchain.js b/src/routes/v3/full-node/blockchain.js deleted file mode 100644 index b021a48..0000000 --- a/src/routes/v3/full-node/blockchain.js +++ /dev/null @@ -1,942 +0,0 @@ -/* - A library for interacting with the Full Node -*/ - -'use strict' - -const express = require('express') -const router = express.Router() - -const axios = require('axios') -const wlogger = require('../../../util/winston-logging') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -// Used to convert error messages to strings, to safely pass to users. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -let _this - -class Blockchain { - constructor () { - _this = this - - this.bchjs = bchjs - this.axios = axios - this.routeUtils = routeUtils - - this.router = router - this.router.get('/', this.root) - this.router.get('/getBestBlockHash', this.getBestBlockHash) - this.router.get('/getBlockchainInfo', this.getBlockchainInfo) - this.router.get('/getBlockCount', this.getBlockCount) - this.router.get('/getBlockHeader/:hash', this.getBlockHeaderSingle) - this.router.post('/getBlockHeader', this.getBlockHeaderBulk) - this.router.get('/getChainTips', this.getChainTips) - this.router.get('/getDifficulty', this.getDifficulty) - this.router.get('/getMempoolEntry/:txid', this.getMempoolEntrySingle) - this.router.post('/getMempoolEntry', this.getMempoolEntryBulk) - this.router.get( - '/getMempoolAncestors/:txid', - this.getMempoolAncestorsSingle - ) - this.router.get('/getMempoolInfo', this.getMempoolInfo) - this.router.get('/getRawMempool', this.getRawMempool) - this.router.get('/getTxOut/:txid/:n', this.getTxOut) - this.router.post('/getTxOut', this.getTxOutPost) - this.router.get('/getTxOutProof/:txid', this.getTxOutProofSingle) - this.router.post('/getTxOutProof', this.getTxOutProofBulk) - this.router.get('/verifyTxOutProof/:proof', this.verifyTxOutProofSingle) - this.router.post('/verifyTxOutProof', this.verifyTxOutProofBulk) - } - - root (req, res, next) { - return res.json({ status: 'blockchain' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - /** - * @api {get} /blockchain/getBestBlockHash Get best block hash - * @apiName GetBestBlockHash - * @apiGroup Blockchain - * @apiDescription Returns the hash of the best (tip) block in the longest - * block chain. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json" - * - * @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1 - */ - async getBestBlockHash (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getbestblockhash' - options.data.method = 'getbestblockhash' - options.data.params = [] - - const response = await _this.axios.request(options) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockchain.ts/getBestBlockHash().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockchainInfo Get blockchain info - * @apiName GetBlockchainInfo - * @apiGroup Blockchain - * @apiDescription Returns an object containing various state info regarding blockchain processing. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json" - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.chain "main" - * @apiSuccess {Number} object.blocks 561838 - * @apiSuccess {Number} object.headers 561838 - * @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc" - * @apiSuccess {Number} object.difficulty 246585566638.1496 - * @apiSuccess {String} object.mediantime 1545402693 - * @apiSuccess {Number} object.verificationprogress 0.999998831622689 - * @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e" - * @apiSuccess {Number} object.pruned false - * @apiSuccess {Array} object.softforks Array of objects - * @apiSuccess {String} object.softforks.id "bip34" - * @apiSuccess {String} object.softforks.version 2 - * @apiSuccess {Object} object.softforks.reject - * @apiSuccess {String} object.softforks.reject.status true - */ - async getBlockchainInfo (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getblockchaininfo' - options.data.method = 'getblockchaininfo' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in blockchain.ts/getBlockchainInfo().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockCount Get Block Count - * @apiName GetBlockCount - * @apiGroup Blockchain - * @apiDescription Returns the number of blocks in the longest blockchain. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockCount" -H "accept: application/json" - * - * @apiSuccess {Number} bestBlockCount 587665 - */ - async getBlockCount (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getblockcount' - options.data.method = 'getblockcount' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getBlockCount().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getBlockHeader/:hash Get single block header - * @apiName GetSingleBlockHeader - * @apiGroup Blockchain - * @apiDescription If verbose is false (default), returns a string that is - * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, - * returns an Object with information about blockheader hash. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json" - * - * @apiParam {String} hash block hash - * @apiParam {Boolean} verbose Return verbose data - * - * @apiSuccess {Object} object Object containing data - * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" - * @apiSuccess {Number} object.confirmations 61839 - * @apiSuccess {Number} object.height 500000 - * @apiSuccess {Number} object.version 536870912 - * @apiSuccess {String} object.versionHex "20000000" - * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" - * @apiSuccess {Number} object.time 1509343584 - * @apiSuccess {Number} object.mediantime 1509336533 - * @apiSuccess {Number} object.nonce 3604508752 - * @apiSuccess {String} object.bits "1809b91a" - * @apiSuccess {Number} object.difficulty 113081236211.4533 - * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" - * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" - * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" - */ - async 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' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - options.data.id = 'getblockheader' - options.data.method = 'getblockheader' - options.data.params = [hash, verbose] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getBlockHeaderSingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getBlockHeader Get multiple block headers - * @apiName GetBulkBlockHeader - * @apiGroup Blockchain - * @apiDescription If verbose is false (default), returns a string that is - * serialized, hex-encoded data for blockheader 'hash'. If verbose is true, - * returns an Object with information about blockheader hash. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}" - * - * @apiParam {String} hash block hash - * @apiParam {Boolean} verbose Return verbose data - * - * @apiSuccess {Array} array array containing objects - * @apiSuccess {String} object.hash "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" - * @apiSuccess {Number} object.confirmations 61839 - * @apiSuccess {Number} object.height 500000 - * @apiSuccess {Number} object.version 536870912 - * @apiSuccess {String} object.versionHex "20000000" - * @apiSuccess {String} object.merkleroot "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091" - * @apiSuccess {Number} object.time 1509343584 - * @apiSuccess {Number} object.mediantime 1509336533 - * @apiSuccess {Number} object.nonce 3604508752 - * @apiSuccess {String} object.bits "1809b91a" - * @apiSuccess {Number} object.difficulty 113081236211.4533 - * @apiSuccess {String} object.chainwork "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714" - * @apiSuccess {String} object.previousblockhash "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523" - * @apiSuccess {String} object.nextblockhash "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" - */ - async 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/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.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}` }) - } - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // Loop through each hash and creates an array of requests to call in parallel - const promises = hashes.map(async (hash) => { - options.data.id = 'getblockheader' - options.data.method = 'getblockheader' - options.data.params = [hash, verbose] - - return _this.axios.request(options) - }) - - const axiosResult = await _this.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) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getBlockHeaderBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getChainTips Get Chain Tips - * @apiName getChainTips - * @apiGroup Blockchain - * @apiDescription Return information about all known tips in the block tree, - * including the main chain as well as orphaned branches. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getChainTips" -H "accept: application/json" - * - */ - async getChainTips (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getchaintips' - options.data.method = 'getchaintips' - options.data.params = [] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getChainTips().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getDifficulty Get difficulty - * @apiName getDifficulty - * @apiGroup Blockchain - * @apiDescription Get the current difficulty value, used to regulate mining - * power on the network. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getDifficulty" -H "accept: application/json" - * - */ - async getDifficulty (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getdifficulty' - options.data.method = 'getdifficulty' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getDifficulty().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getMempoolEntry/:txid Get single mempool entry - * @apiName getMempoolEntry - * @apiGroup Blockchain - * @apiDescription Returns mempool data for given transaction. TXID must be in - * mempool (unconfirmed) - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - */ - async 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' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmempoolentry' - options.data.method = 'getmempoolentry' - options.data.params = [txid] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolEntrySingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getMempoolEntry Get bulk mempool entry - * @apiName getMempoolEntryBulk - * @apiGroup Blockchain - * @apiDescription Returns mempool data for multiple transactions - * - * @apiExample Example usage: - * curl -X POST https://api.fullstack.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}" - */ - async 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/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.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' }) - } - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // Loop through each txid and creates an array of requests to call in parallel - const promises = txids.map(async (txid) => { - options.data.id = 'getmempoolentry' - options.data.method = 'getmempoolentry' - options.data.params = [txid] - - return _this.axios.request(options) - }) - - const axiosResult = await _this.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) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolEntryBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getMempoolAncestors/:txid Get Mempool Ancestors - * @apiName getMempoolAncestors - * @apiGroup Blockchain - * @apiDescription Returns mempool ancestors data for given TXID. It must be in - * mempool (unconfirmed). This call is handy to tell if a UTXO is bumping up - * against the 25 ancestor chain-limit. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - */ - async getMempoolAncestorsSingle (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 verbose = req.params.verbose - if (verbose === undefined) verbose = false - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmempoolancestors' - options.data.method = 'getmempoolancestors' - options.data.params = [txid, verbose] - - const response = await _this.axios.request(options) - // console.log(`response: ${util.inspect(response)}`) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolAncestorsSingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getMempoolInfo Get mempool info - * @apiName getMempoolInfo - * @apiGroup Blockchain - * @apiDescription Returns details on the active state of the TX memory pool. - * - * @apiExample Example usage: - * curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json" - * - */ - async getMempoolInfo (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmempoolinfo' - options.data.method = 'getmempoolinfo' - options.data.params = [] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getMempoolInfo().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getRawMempool Get mempool info - * @apiName getMempoolInfo - * @apiGroup Blockchain - * @apiDescription Returns details on the active state of the TX memory pool. - * - * @apiExample Example usage: - * curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json" - * - */ - - /** - * @api {get} /blockchain/getRawMempool/?verbose= Get raw mempool - * @apiName getRawMempool - * @apiGroup Blockchain - * @apiDescription Returns all transaction ids in memory pool as a json array - * of string transaction ids. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json" - * - * @apiParam {Boolean} verbose Return verbose data - * - */ - async getRawMempool (req, res, next) { - try { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - let verbose = false - if (req.query.verbose && req.query.verbose === 'true') verbose = true - - options.data.id = 'getrawmempool' - options.data.method = 'getrawmempool' - options.data.params = [verbose] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getRawMempool().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getTxOut/:txid/:n?mempool= Get Tx Out - * @apiName getTxOut - * @apiGroup Blockchain - * @apiDescription Returns details about an unspent transaction output. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json" - * - * @apiParam {String} txid Transaction id (required) - * @apiParam {Number} n Output number (required) - * @apiParam {Boolean} mempool Check mempool or not (optional) - * - */ - // Returns details about an unspent transaction output. - async 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 includeMempool = false - if (req.query.includeMempool && req.query.includeMempool === 'true') { - includeMempool = true - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'gettxout' - options.data.method = 'gettxout' - options.data.params = [txid, n, includeMempool] - - // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOut().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /blockchain/getTxOut Validate a UTXO - * @apiName getTxOut - * @apiGroup Blockchain - * @apiDescription Returns details about an unspent transaction output (UTXO). - * - * @apiExample Example usage: - * curl "https://api.fullstack.cash/v3/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}' - * - * @apiParam {String} txid Transaction id (required) - * @apiParam {Number} vout of transaction (required) - * @apiParam {Boolean} mempool Check mempool or not (optional) - * - */ - // Returns details about an unspent transaction output. - async getTxOutPost (req, res, next) { - try { - const txid = req.body.txid - let n = req.body.vout - const mempool = req.body.mempool ? req.body.mempool : true - - // Validate input parameter - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - if (n === undefined || n === '') { - res.status(400) - return res.json({ error: 'vout can not be empty' }) - } - n = parseInt(n) - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'gettxout' - options.data.method = 'gettxout' - options.data.params = [txid, n, mempool] - - // console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`) - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOutPost().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /blockchain/getTxOutProofSingle/:txid Get Tx Out Proof - * @apiName getTxOutProofSingle - * @apiGroup Blockchain - * @apiDescription Returns a hex-encoded proof that 'txid' was included in a block. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json" - * - * @apiParam {String} txid Transaction id (required) - * - */ - async 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' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'gettxoutproof' - options.data.method = 'gettxoutproof' - options.data.params = [[txid]] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOutProofSingle().', err) - - return _this.errorHandler(err, res) - } - } - - // Returns a hex-encoded proof that 'txid' was included in a block. - async 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/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // 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}` - }) - } - } - - wlogger.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) => { - options.data.id = 'gettxoutproof' - options.data.method = 'gettxoutproof' - options.data.params = [[txid]] - - return _this.axios.request(options) - }) - - // Wait for all parallel promisses to resolve. - const axiosResult = await _this.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) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/getTxOutProofBulk().', err) - - return _this.errorHandler(err, res) - } - } - - async 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' }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'verifytxoutproof' - options.data.method = 'verifytxoutproof' - options.data.params = [req.params.proof] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/verifyTxOutProofSingle().', err) - - return _this.errorHandler(err, res) - } - } - - async 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/api.fullstack.cash/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - // 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}` }) - } - } - - wlogger.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) => { - options.data.id = 'verifytxoutproof' - options.data.method = 'verifytxoutproof' - options.data.params = [proof] - - return _this.axios.request(options) - }) - - // Wait for all parallel promisses to resolve. - const axiosResult = await _this.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) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error('Error in blockchain.ts/verifyTxOutProofBulk().', err) - - return _this.errorHandler(err, res) - } - } -} - -module.exports = Blockchain diff --git a/src/routes/v3/full-node/control.js b/src/routes/v3/full-node/control.js deleted file mode 100644 index 5b577bf..0000000 --- a/src/routes/v3/full-node/control.js +++ /dev/null @@ -1,116 +0,0 @@ -'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 RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -// Used for processing error messages before sending them to the user. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -let _this - -class Control { - constructor () { - _this = this - - _this.axios = axios - _this.routeUtils = routeUtils - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/getNetworkInfo', _this.getNetworkInfo) - } - - root (req, res, next) { - return res.json({ status: 'control' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - /** - * @api {get} /control/getnetworkinfo Get Network Info - * @apiName GetNetworkInfo - * @apiGroup Control - * @apiDescription RPC call which gets basic full node information. - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/control/getnetworkinfo" -H "accept: application/json" - * - */ - async getNetworkInfo (req, res, next) { - // Axios options - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getnetworkinfo' - options.data.method = 'getnetworkinfo' - options.data.params = [] - - try { - // const response = await BitboxHTTP(requestConfig) - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (error) { - wlogger.error('Error in control.ts/getNetworkInfo().', error) - - return _this.errorHandler(error, res) - } - } - // 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 = Control diff --git a/src/routes/v3/full-node/mining.js b/src/routes/v3/full-node/mining.js deleted file mode 100644 index 5ff1154..0000000 --- a/src/routes/v3/full-node/mining.js +++ /dev/null @@ -1,169 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -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 } - -let _this - -class Mining { - constructor () { - _this = this - - _this.axios = axios - _this.routeUtils = routeUtils - - _this.router = router - _this.router.get('/', _this.root) - _this.router.get('/getMiningInfo', _this.getMiningInfo) - _this.router.get('/getNetworkHashPS', _this.getNetworkHashPS) - } - - root (req, res, next) { - return res.json({ status: 'mining' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // 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); - // }); - // }); - - /** - * @api {get} /mining/getMiningInfo Get Mining Info. - * @apiName Mining info. - * @apiGroup Mining - * @apiDescription Returns a json object containing mining-related information. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/mining/getMiningInfo" -H "accept: application/json" - * - * - */ - async getMiningInfo (req, res, next) { - try { - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getmininginfo' - options.data.method = 'getmininginfo' - options.data.params = [] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - wlogger.error('Error in mining.ts/getMiningInfo().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /mining/getNetworkHashps?nblocks=&height= Get Estimated network hashes per second. - * @apiName Estimated network hashes per second. - * @apiGroup Mining - * @apiDescription Returns the estimated network hashes per second based on the last n blocks. Pass in [blocks] to override # of blocks, -1 specifies since last difficulty change. Pass in [height] to estimate the network speed at the time when a certain block was found. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json" - * - * - */ - - async 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 options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getnetworkhashps' - options.data.method = 'getnetworkhashps' - options.data.params = [nblocks, height] - - const response = await _this.axios.request(options) - - return res.json(response.data.result) - } catch (err) { - wlogger.error('Error in mining.ts/getNetworkHashPS().', err) - - return _this.errorHandler(err, res) - } - } - - // 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 = Mining diff --git a/src/routes/v3/full-node/network.js b/src/routes/v3/full-node/network.js deleted file mode 100644 index 3e0c2ed..0000000 --- a/src/routes/v3/full-node/network.js +++ /dev/null @@ -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 diff --git a/src/routes/v3/full-node/rawtransactions.js b/src/routes/v3/full-node/rawtransactions.js deleted file mode 100644 index 2f4c86e..0000000 --- a/src/routes/v3/full-node/rawtransactions.js +++ /dev/null @@ -1,570 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') - -const RouteUtils = require('../../../util/route-utils') -const routeUtils = new RouteUtils() - -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 } - -let _this -class RawTransactions { - constructor () { - _this = this - - // Encapsulate external dependencies. - this.axios = axios - this.routeUtils = routeUtils - - // Define Express routes. - this.router = router - this.router.get('/', this.root) - this.router.get( - '/decodeRawTransaction/:hex', - this.decodeRawTransactionSingle - ) - this.router.post('/decodeRawTransaction', this.decodeRawTransactionBulk) - this.router.get('/decodeScript/:hex', this.decodeScriptSingle) - this.router.post('/decodeScript', this.decodeScriptBulk) - this.router.post('/getRawTransaction', this.getRawTransactionBulk) - this.router.get('/getRawTransaction/:txid', this.getRawTransactionSingle) - this.router.post('/sendRawTransaction', this.sendRawTransactionBulk) - this.router.get('/sendRawTransaction/:hex', this.sendRawTransactionSingle) - } - - root (req, res, next) { - return res.json({ status: 'rawtransactions' }) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // Decode transaction hex into a JSON object. - // GET - /** - * @api {get} /rawtransactions/decodeRawTransaction/{hex} Decode Single Raw Transaction. - * @apiName Decode Single Raw Transaction - * @apiGroup Raw Transaction - * @apiDescription Return a JSON object representing the serialized, hex-encoded transaction. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json" - */ - async 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 options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'decoderawtransaction' - options.data.method = 'decoderawtransaction' - options.data.params = [hex] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/decodeRawTransactionSingle().', - err - ) - return _this.errorHandler(err, res) - } - } - - /** - * @api {post} /rawtransactions/decodeRawTransaction Decode Bulk Raw Transactions. - * @apiName Decode Bulk Raw Transactions - * @apiGroup Raw Transaction - * @apiDescription Return bulk hex encoded transaction. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' - * - * - */ - async 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 (!_this.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 options = _this.routeUtils.getAxiosOptions() - - // Loop through each height and creates an array of requests to call in parallel - const promises = hexes.map(async hex => { - options.data.id = 'decoderawtransaction' - options.data.method = 'decoderawtransaction' - options.data.params = [hex] - - return _this.axios.request(options) - }) - - // Wait for all parallel Insight requests to return. - const axiosResult = await _this.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) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/decodeRawTransactionBulk().', - err - ) - return _this.errorHandler(err, res) - } - } - - // Decode a raw transaction from hex to assembly. - // GET single - /** - * @api {get} /rawtransactions/decodeScript/{hex} Decode Single Script. - * @apiName Decode Single Script - * @apiGroup Raw Transaction - * @apiDescription Decode a hex-encoded script. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json" - * - * - */ - async 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 options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'decodescript' - options.data.method = 'decodescript' - options.data.params = [hex] - - const response = await _this.axios.request(options) - return res.json(response.data.result) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error('Error in rawtransactions.ts/decodeScriptSingle().', err) - - return _this.errorHandler(err, res) - } - } - - // Decode a raw transaction from hex to assembly. - // POST bulk - /** - * @api {post} /rawtransactions/decodeScript Bulk Decode Script. - * @apiName Bulk Decode Script - * @apiGroup Raw Transaction - * @apiDescription Decode multiple hex-encoded scripts. - * - * - * @apiExample Example usage: - *curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' - * - * - */ - async 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 (!_this.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 options = _this.routeUtils.getAxiosOptions() - - // Loop through each hex and create an array of promises - const promises = hexes.map(async hex => { - options.data.id = 'decodescript' - options.data.method = 'decodescript' - options.data.params = [hex] - - const response = await _this.axios.request(options) - 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) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/decodeScript: `, err) - wlogger.error('Error in rawtransactions.ts/decodeScriptBulk().', err) - return _this.errorHandler(err, res) - } - } - - // Retrieve raw transactions details from the full node. - - async getRawTransactionsFromNode (txid, verbose) { - try { - const options = _this.routeUtils.getAxiosOptions() - - options.data.id = 'getrawtransaction' - options.data.method = 'getrawtransaction' - options.data.params = [txid, verbose] - - const response = await _this.axios.request(options) - - return response.data.result - } catch (err) { - wlogger.error('Error in rawtransactions.ts/getRawTransactionsFromNode().') - throw err - } - } - - // Get a JSON object breakdown of transaction details. - // POST - /** - * @api {post} /rawtransactions/getRawTransaction Get Bulk Raw Transactions. - * @apiName Get Bulk Raw Transactions. - * @apiGroup Raw Transaction - * @apiDescription Return the raw transaction data for multiple transactions. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}' - * - */ - async 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 (!_this.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 => - _this.getRawTransactionsFromNode(txid, verbose) - ) - - // Wait for all parallel promises to return. - const axiosResult = await _this.axios.all(promises) - - res.status(200) - return res.json(axiosResult) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error('Error in rawtransactions.ts/getRawTransactionBulk().', err) - return _this.errorHandler(err, res) - } - } - - // Get a JSON object breakdown of transaction details. - // GET - /** - * @api {get} /rawtransactions/getRawTransaction/{txid} Return the raw transaction data. - * @apiName Get Raw Transaction - * @apiGroup Raw Transaction - * @apiDescription return the raw transaction data. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json" - * - * - */ - async 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' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ - error: `parameter 1 must be of length 64 (not ${txid.length})` - }) - } - - const data = await _this.getRawTransactionsFromNode(txid, verbose) - - return res.json(data) - } catch (err) { - // Write out error to error log. - // logger.error(`Error in rawtransactions/getRawTransaction: `, err) - wlogger.error( - 'Error in rawtransactions.ts/getRawTransactionSingle().', - err - ) - - return _this.errorHandler(err, res) - } - } - - // Transmit a raw transaction to the BCH network. - /** - * @api {post} /rawtransactions/sendRawTransaction Send Bulk Raw Transactions. - * @apiName Send Bulk Raw Transactions - * @apiGroup Raw Transaction - * @apiDescription Submits multiple raw transaction (serialized, hex-encoded) to local node and network. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}' - * - * - */ - async 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 options = _this.routeUtils.getAxiosOptions() - - // Enforce array size rate limits - if (!_this.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 = 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] - - options.data.id = 'sendrawtransaction' - options.data.method = 'sendrawtransaction' - options.data.params = [hex] - - const rpcResult = await _this.axios.request(options) - - result.push(rpcResult.data.result) - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error( - 'Error in rawtransactions.ts/sendRawTransactionBulk().', - err - ) - return _this.errorHandler(err, res) - } - } - - // Transmit a raw transaction to the BCH network. - /** - * @api {get} /rawtransactions/sendRawTransaction/{hex} Send Single Raw Transaction. - * @apiName Send Single Raw Transaction - * @apiGroup Raw Transaction - * @apiDescription Submits single raw transaction (serialized, hex-encoded) to local node and network. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: " - * - * - */ - async 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 options = _this.routeUtils.getAxiosOptions() - - // RPC call - options.data.id = 'sendrawtransaction' - options.data.method = 'sendrawtransaction' - options.data.params = [hex] - - const rpcResult = await _this.axios.request(options) - - const result = rpcResult.data.result - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error( - 'Error in rawtransactions.ts/sendRawTransactionSingle().', - err - ) - return _this.errorHandler(err, res) - } - } -} - -module.exports = RawTransactions diff --git a/src/routes/v3/health-check.js b/src/routes/v3/health-check.js deleted file mode 100644 index 28262fd..0000000 --- a/src/routes/v3/health-check.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - This health-check API can be used to test the server for aliveness and - readiness. -*/ - -'use strict' - -const express = require('express') -const router = express.Router() - -/* GET home page. */ -router.get('/', (req, res, next) => { - res.json({ status: true }) -}) - -module.exports = router diff --git a/src/routes/v3/ninsight.js b/src/routes/v3/ninsight.js deleted file mode 100644 index 614be72..0000000 --- a/src/routes/v3/ninsight.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - A library for interacting with the Bitcoin.com ninsight (not Insight) indexer. -*/ - -'use strict' - -const express = require('express') -// const axios = require('axios') -// const routeUtils = require('./route-utils') -// const wlogger = require('../../util/winston-logging') - -const router = express.Router() - -// const BCHJS = require('@psf/bch-js') -// const bchjs = new BCHJS() - -// let _this - -class Ninsight { - constructor () { - // _this = this - - this.router = router - this.router.get('/', this.root) - } - - root (req, res, next) { - return res.json({ status: 'ninsight' }) - } -} - -module.exports = Ninsight diff --git a/src/routes/v3/price.js b/src/routes/v3/price.js deleted file mode 100644 index 3530493..0000000 --- a/src/routes/v3/price.js +++ /dev/null @@ -1,155 +0,0 @@ -/* - This price route is really just a wrapper for another API price endpoint. - The main reason for hosting this price wrapper is so that the price can - be accessible over Tor. Tor is typically blocked by other REST API servers. -*/ - -'use strict' - -const express = require('express') -const axios = require('axios') -const wlogger = require('../../util/winston-logging') -const util = require('util') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -let _this // Global context for 'this' instance of the Class. - -class Price { - constructor () { - _this = this - - this.axios = axios - this.routeUtils = routeUtils - - this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH' - this.coinexPriceUrl = - 'https://api.coinex.com/v1/market/ticker?market=bchausdt' - - this.router = express.Router() - this.router.get('/', _this.root) - this.router.get('/usd', _this.getUSD) - this.router.get('/rates', _this.getBCHRate) - this.router.get('/bchausd', _this.getBCHAUSD) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - // Root API endpoint. Simply acknowledges that it exists. - root (req, res, next) { - return res.json({ status: 'price' }) - } - - /** - * @api {get} /price/usd Get the USD price of BCH - * @apiName Get the USD price of BCH - * @apiGroup Price - * @apiDescription Get the USD price of BCH from Coinbase. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/price/usd" -H "accept: application/json" - * - */ - async getUSD (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.priceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json({ usd: Number(response.data.data.rates.USD) }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getUSD().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /price/usd Get rates for several different currencies - * @apiName Get rates for several different currencies - * @apiGroup Price - * @apiDescription Get rates for several different currencies from Coinbase. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/price/rates" -H "accept: application/json" - * - */ - // Get rates for several different currencies - async getBCHRate (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.priceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - return res.json(response.data.data.rates) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getUSD().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /price/bchausd Get the USD price of BCHA - * @apiName Get the USD price of BCHA - * @apiGroup Price - * @apiDescription Get the USD price of BCHA from Coinex. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/price/bchausd" -H "accept: application/json" - * - */ - async getBCHAUSD (req, res, next) { - try { - // Request options - const opt = { - method: 'get', - baseURL: _this.coinexPriceUrl, - timeout: 15000 - } - - const response = await axios.request(opt) - // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) - - const price = Number(response.data.data.ticker.last) - - return res.json({ usd: price }) - } catch (err) { - // Write out error to error log. - wlogger.error('Error in price.js/getBCHAUSD().', err) - - return _this.errorHandler(err, res) - } - } -} - -module.exports = Price diff --git a/src/routes/v3/route-utils.js b/src/routes/v3/route-utils.js deleted file mode 100644 index fe86c44..0000000 --- a/src/routes/v3/route-utils.js +++ /dev/null @@ -1,183 +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 BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -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. - getAxiosOptions -} - -// 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 = 20 - - 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 = bchjs.Address.toCashAddress(addr) - - // Return true if the network and address both match testnet - const addrIsTest = bchjs.Address.isTestnetAddress(cashAddr) - if (network === 'testnet' && addrIsTest) return true - - // Return true if the network and address both match mainnet - const addrIsMain = bchjs.Address.isMainnetAddress(cashAddr) - if (network === 'mainnet' && addrIsMain) return true - - return false - } catch (err) { - wlogger.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, - timeout: 15000 - }) - 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 } -} - -// Axios options used when calling axios.post() to talk with a full node. -function getAxiosOptions () { - return { - method: 'post', - baseURL: process.env.RPC_BASEURL, - timeout: 15000, - auth: { - username: process.env.RPC_USERNAME, - password: process.env.RPC_PASSWORD - }, - data: { - jsonrpc: '1.0' - } - } -} - -// 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 } - } - - // console.log(`err.message: ${err.message}`) - // console.log(`err: `, err) - - // 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 - } - } - - // Different kind of network error - if (err.message && err.message.indexOf('EAI_AGAIN') > -1) { - return { - msg: - 'Network error: Could not communicate with full node or other external service.', - status: 503 - } - } - - // Axios timeout (aborted) error, or service is down (connection refused). - if ( - err.code && - (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED') - ) { - 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 } - } -} diff --git a/src/routes/v3/services/slpdb.js b/src/routes/v3/services/slpdb.js deleted file mode 100644 index c19c52e..0000000 --- a/src/routes/v3/services/slpdb.js +++ /dev/null @@ -1,351 +0,0 @@ -const axios = require('axios') - -const SLPSDK = require('@psf/bch-js') -const SLP = new SLPSDK() - -class Slpdb { - // Gets transaction history for all tokens for an address. Can also specify - // block height, but defaults to 0. - async getHistoricalSlpTransactions (addressList, fromBlock = 0) { - // Build SLPDB or query from addressList - const orQueryArray = [] - for (const address of addressList) { - const cashAddress = SLP.SLP.Address.toCashAddress(address) - const slpAddress = SLP.SLP.Address.toSLPAddress(address) - - const cashQuery = { - 'in.e.a': cashAddress.slice(12) - } - const slpQuery = { - 'slp.detail.outputs.address': slpAddress - } - - orQueryArray.push(cashQuery) - orQueryArray.push(slpQuery) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: orQueryArray, - 'slp.valid': true, - 'blk.i': { - $not: { - $lte: fromBlock - } - } - }, - $orderby: { - 'blk.i': -1 - } - }, - project: { - _id: 0, - 'tx.h': 1, - 'in.i': 1, - 'in.e': 1, - 'out.e': 1, - 'out.a': 1, - 'slp.detail': 1, - blk: 1 - }, - limit: 500 - } - } - - const result = await this.runQuery(query) - // console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`) - - let transactions = [] - - // Add confirmed transactions - if (result.data && result.data.c) { - transactions = transactions.concat(result.data.c) - } - - // Add unconfirmed transactions - if (result.data && result.data.u) { - transactions = transactions.concat(result.data.u) - } - - return transactions - } - - async getTokenStats (tokenId) { - const [ - totalMinted, - totalBurned, - tokenDetails, - circulatingSupply - ] = await Promise.all([ - this.getTotalMinted(tokenId), - this.getTotalBurned(tokenId), - this.getTokenDetails(tokenId), - this.getTotalCirculating(tokenId) - ]) - - tokenDetails.totalMinted = tokenDetails.initialTokenQty + totalMinted - tokenDetails.totalBurned = totalBurned - - // tokenDetails.circulatingSupply = - // tokenDetails.totalMinted - tokenDetails.totalBurned - tokenDetails.circulatingSupply = circulatingSupply - - return tokenDetails - } - - generateCredentials () { - // Generate the Basic Authentication header for a private instance of SLPDB. - const SLPDB_PASS = process.env.SLPDB_PASS - ? process.env.SLPDB_PASS - : 'BITBOX' - const username = 'BITBOX' - const password = SLPDB_PASS - const combined = `${username}:${password}` - var base64Credential = Buffer.from(combined).toString('base64') - var readyCredential = `Basic ${base64Credential}` - - const options = { - headers: { - authorization: readyCredential, - timeout: 30000 - } - } - - return options - } - - async runQuery (query) { - const queryString = JSON.stringify(query) - const queryBase64 = Buffer.from(queryString).toString('base64') - const url = `${process.env.SLPDB_URL}q/${queryBase64}` - - const options = this.generateCredentials() - - const response = await axios.get(url, options) - return response - } - - async getTotalMinted (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs.status': { - $in: [ - 'BATON_SPENT_IN_MINT', - 'BATON_UNSPENT', - 'BATON_SPENT_NOT_IN_MINT' - ] - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $group: { - _id: null, - count: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].count) - } - - async getTotalCirculating (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { $unwind: '$graphTxn.outputs' }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $group: { - _id: null, - circulating_supply: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 100000 - } - } - - const result = await this.runQuery(query) - // console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].circulating_supply) - } - - async getTotalBurned (tokenId) { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'tokenDetails.tokenIdHex': tokenId, - 'graphTxn.outputs.status': { - $in: [ - 'SPENT_NON_SLP', - 'BATON_SPENT_INVALID_SLP', - 'SPENT_INVALID_SLP', - 'BATON_SPENT_NON_SLP', - 'MISSING_BCH_VOUT', - 'BATON_MISSING_BCH_VOUT', - 'BATON_SPENT_NOT_IN_MINT', - 'EXCESS_INPUT_BURNED' - ] - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': { - $in: [ - 'SPENT_NON_SLP', - 'BATON_SPENT_INVALID_SLP', - 'SPENT_INVALID_SLP', - 'BATON_SPENT_NON_SLP', - 'MISSING_BCH_VOUT', - 'BATON_MISSING_BCH_VOUT', - 'BATON_SPENT_NOT_IN_MINT', - 'EXCESS_INPUT_BURNED' - ] - } - } - }, - { - $group: { - _id: null, - count: { - $sum: '$graphTxn.outputs.slpAmount' - } - } - } - ], - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.g.length) { - return 0 - } - - return parseFloat(result.data.g[0].count) - } - - async getTokenDetails (tokenId) { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - limit: 1 - } - } - - const result = await this.runQuery(query) - - if (!result.data.t.length) { - throw new Error('Token could not be found') - } - - const token = this.formatTokenOutput(result.data.t[0]) - - return token - } - - formatTokenOutput (token) { - // console.log(`token: ${JSON.stringify(token, null, 2)}`) - - token.tokenDetails.id = token.tokenDetails.tokenIdHex - delete token.tokenDetails.tokenIdHex - token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex - delete token.tokenDetails.documentSha256Hex - token.tokenDetails.initialTokenQty = parseFloat( - token.tokenDetails.genesisOrMintQuantity - ) - delete token.tokenDetails.genesisOrMintQuantity - delete token.tokenDetails.transactionType - delete token.tokenDetails.batonVout - delete token.tokenDetails.sendOutputs - - token.tokenDetails.blockCreated = token.tokenStats.block_created - token.tokenDetails.blockLastActiveSend = - token.tokenStats.block_last_active_send - token.tokenDetails.blockLastActiveMint = - token.tokenStats.block_last_active_mint - token.tokenDetails.txnsSinceGenesis = - token.tokenStats.qty_valid_txns_since_genesis - token.tokenDetails.validAddresses = - token.tokenStats.qty_valid_token_addresses - token.tokenDetails.mintingBatonStatus = - token.tokenStats.minting_baton_status - - delete token.tokenStats.block_last_active_send - delete token.tokenStats.block_last_active_mint - delete token.tokenStats.qty_valid_txns_since_genesis - delete token.tokenStats.qty_valid_token_addresses - - token.tokenDetails.timestampUnix = token.tokenDetails.timestamp_unix - delete token.tokenDetails.timestamp_unix - - return token.tokenDetails - } -} - -module.exports = Slpdb diff --git a/src/routes/v3/slp.js b/src/routes/v3/slp.js deleted file mode 100644 index 5510c58..0000000 --- a/src/routes/v3/slp.js +++ /dev/null @@ -1,1691 +0,0 @@ -'use strict' - -const express = require('express') -const router = express.Router() -const axios = require('axios') -const BigNumber = require('bignumber.js') - -const RouteUtils = require('../../util/route-utils') -const routeUtils = new RouteUtils() - -const Slpdb = require('./services/slpdb') - -// const strftime = require('strftime') -const wlogger = require('../../util/winston-logging') - -// Instantiate a local copy of bch-js using the local REST API server. -const LOCAL_RESTURL = process.env.LOCAL_RESTURL - ? process.env.LOCAL_RESTURL - : 'https://api.fullstack.cash/v3/' -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS({ restURL: LOCAL_RESTURL }) - -// Used to convert error messages to strings, to safely pass to users. -const util = require('util') -util.inspect.defaultOptions = { depth: 5 } - -// Setup JSON RPC -// const BitboxHTTP = axios.create({ -// baseURL: process.env.RPC_BASEURL -// }) -// const username = process.env.RPC_USERNAME -// const password = process.env.RPC_PASSWORD - -// Determine the Access password for a private instance of SLPDB. -// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2 -const SLPDB_PASS = process.env.SLPDB_PASS ? process.env.SLPDB_PASS : 'BITBOX' - -// const rawtransactions = require('./full-node/rawtransactions') -const RawTransactions = require('./full-node/rawtransactions') -const rawTransactions = new RawTransactions() - -// Setup REST and TREST URLs used by slpjs -// Dev note: this allows for unit tests to mock the URL. -if (!process.env.REST_URL) process.env.REST_URL = 'https://rest.bitcoin.com/v2/' -if (!process.env.TREST_URL) { - process.env.TREST_URL = 'https://trest.bitcoin.com/v2/' -} - -let _this - -class Slp { - constructor () { - _this = this - - // Encapsulate external libraries. - _this.axios = axios - _this.routeUtils = routeUtils - _this.BigNumber = BigNumber - _this.bchjs = bchjs - _this.rawTransactions = rawTransactions - _this.slpdb = new Slpdb() - - _this.router = router - - _this.router.get('/', _this.root) - // _this.router.get('/list', _this.list) - _this.router.get('/list/:tokenId', _this.listSingleToken) - _this.router.post('/list', _this.listBulkToken) - _this.router.get('/balancesForAddress/:address', _this.balancesForAddress) - _this.router.post('/balancesForAddress', _this.balancesForAddressBulk) - _this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle) - _this.router.get('/convert/:address', _this.convertAddressSingle) - _this.router.post('/convert', _this.convertAddressBulk) - _this.router.post('/validateTxid', _this.validateBulk) - _this.router.get('/validateTxid/:txid', _this.validateSingle) - _this.router.get('/validateTxid2/:txid', _this.validate2Single) - _this.router.get('/txDetails/:txid', _this.txDetails) - _this.router.get('/tokenStats/:tokenId', _this.tokenStats) - _this.router.get( - '/transactions/:tokenId/:address', - _this.txsTokenIdAddressSingle - ) - _this.router.get( - '/transactionHistoryAllTokens/:address', - _this.txsByAddressSingle - ) - _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) - // _this.router.post('/hydrateUtxos', _this.hydrateUtxos) - } - - // DRY error handler. - errorHandler (err, res) { - // Attempt to decode the error message. - const { msg, status } = _this.routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ error: util.inspect(err) }) - } - - formatTokenOutput (token) { - token.tokenDetails.id = token.tokenDetails.tokenIdHex - delete token.tokenDetails.tokenIdHex - token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex - delete token.tokenDetails.documentSha256Hex - token.tokenDetails.initialTokenQty = parseFloat( - token.tokenDetails.genesisOrMintQuantity - ) - delete token.tokenDetails.genesisOrMintQuantity - delete token.tokenDetails.transactionType - delete token.tokenDetails.batonVout - delete token.tokenDetails.sendOutputs - - token.tokenDetails.blockCreated = token.tokenStats.block_created - token.tokenDetails.blockLastActiveSend = - token.tokenStats.block_last_active_send - token.tokenDetails.blockLastActiveMint = - token.tokenStats.block_last_active_mint - token.tokenDetails.txnsSinceGenesis = - token.tokenStats.qty_valid_txns_since_genesis - token.tokenDetails.validAddresses = - token.tokenStats.qty_valid_token_addresses - token.tokenDetails.totalMinted = parseFloat( - token.tokenStats.qty_token_minted - ) - token.tokenDetails.totalBurned = parseFloat( - token.tokenStats.qty_token_burned - ) - token.tokenDetails.circulatingSupply = parseFloat( - token.tokenStats.qty_token_circulating_supply - ) - token.tokenDetails.mintingBatonStatus = - token.tokenStats.minting_baton_status - - delete token.tokenStats.block_last_active_send - delete token.tokenStats.block_last_active_mint - delete token.tokenStats.qty_valid_txns_since_genesis - delete token.tokenStats.qty_valid_token_addresses - return token - } - - root (req, res, next) { - return res.json({ status: 'slp' }) - } - - /** - * @api {get} /slp/list/{tokenId} List single SLP token by id. - * @apiName List single SLP token by id. - * @apiGroup SLP - * @apiDescription Returns the list single SLP token by id. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json" - * - * - */ - async listSingleToken (req, res, next) { - try { - const tokenId = req.params.tokenId - - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const t = await _this.lookupToken(tokenId) - - res.status(200) - return res.json(t) - } catch (err) { - wlogger.error('Error in slp.ts/listSingleToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - /** - * @api {post} /slp/list/ List Bulk SLP token . - * @apiName List Bulk SLP token. - * @apiGroup SLP - * @apiDescription Returns the list bulk SLP token by id. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}' - * - * - */ - async listBulkToken (req, res, next) { - try { - const tokenIds = req.body.tokenIds - - // Reject if tokenIds is not an array. - if (!Array.isArray(tokenIds)) { - res.status(400) - return res.json({ - error: 'tokenIds needs to be an array. Use GET for single tokenId.' - }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, tokenIds)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - const query = { - v: 3, - q: { - db: ['t'], - find: { - 'tokenDetails.tokenIdHex': { - $in: tokenIds - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - sort: { 'tokenStats.block_created': -1 }, - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - - const formattedTokens = [] - const txids = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - txids.push(token.tokenDetails.tokenIdHex) - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - tokenIds.forEach((tokenId) => { - if (!txids.includes(tokenId)) { - formattedTokens.push({ - id: tokenId, - valid: false - }) - } - }) - - res.status(200) - return res.json(formattedTokens) - } catch (err) { - wlogger.error('Error in slp.ts/listBulkToken().', err) - return _this.errorHandler(err, res) - - // return res.json({ error: `Error in /list/:tokenId: ${err.message}` }) - } - } - - async lookupToken (tokenId) { - try { - const query = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { tokenDetails: 1, tokenStats: 1, _id: 0 }, - limit: 1000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - // console.log(`url: ${url}`) - // Request options - const opt = { - method: 'get', - baseURL: url - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${util.inspect(tokenRes.data,null,2)}`) - // console.log( - // `tokenRes.data.t[0]: ${util.inspect(tokenRes.data.t[0], null, 2)}` - // ) - - const formattedTokens = [] - - if (tokenRes.data.t.length) { - tokenRes.data.t.forEach((token) => { - token = _this.formatTokenOutput(token) - formattedTokens.push(token.tokenDetails) - }) - } - - let t - formattedTokens.forEach((token) => { - if (token.id === tokenId) t = token - }) - - // If token could not be found. - if (t === undefined) { - t = { - id: 'not found' - } - } - - return t - } catch (err) { - wlogger.error('Error in slp.ts/lookupToken().', err) - // console.log(`Error in slp.ts/lookupToken()`) - throw err - } - } - - /** - * @api {get} /slp/balancesForAddress/{address} List SLP balance for address. - * @apiName List SLP balance for address. - * @apiGroup SLP - * @apiDescription Returns List SLP balance for address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all tokens for a single address. - async balancesForAddress (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(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 cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': _this.bchjs.SLP.Address.toSLPAddress( - address - ), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentials() - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data.g: ${JSON.stringify(tokenRes.data.g, null, 2)}`) - - const tokenIds = [] - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - - delete token._id - - return token - }) - - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - // Request options - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - const details = await _this.axios.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - return token - }) - - return res.json(tokenRes.data.g) - } - - return res.json('No balance for this address') - } catch (err) { - wlogger.error('Error in slp.ts/balancesForAddress().', err) - - return _this.errorHandler(err, res) - - // return res.json({ - // error: `Error in /address/:address: ${err.message}` - // }) - } - } - - /** - * @api {post} /slp/balancesForAddress List SLP balances for an array of addresses. - * @apiName List SLP balances for an array of addresses. - * @apiGroup SLP - * @apiDescription Returns SLP balances for an array of addresses. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json" - * - * - */ - async balancesForAddressBulk (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' }) - } - - // Enforce array size rate limits - if (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - wlogger.debug( - 'Executing slp/balancesForAddresss with these addresses: ', - addresses - ) - - // Loop through each address and do error checking. - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Validate the input data. - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(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 cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = _this.routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - } - - const options = _this.generateCredentials() - - // Collect an array of promises, one for each request to slpserve. - // This is a nested array of promises. - const balancesPromises = addresses.map(async (address) => { - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - address: _this.bchjs.SLP.Address.toSLPAddress(address), - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - } - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.address': _this.bchjs.SLP.Address.toSLPAddress( - address - ), - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 } - } - }, - { - $project: { - amount: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$tokenId', - balanceString: { - $sum: '$amount' - }, - slpAddress: { - $first: '$address' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - // Request options - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const tokenIds = [] - - if (tokenRes.data.g.length > 0) { - tokenRes.data.g = tokenRes.data.g.map((token) => { - token.tokenId = token._id - tokenIds.push(token.tokenId) - token.balance = parseFloat(token.balanceString) - delete token._id - return token - }) - } - - // Collect another array of promises. - const promises = tokenIds.map(async (tokenId) => { - const query2 = { - v: 3, - q: { - db: ['t'], - find: { - $query: { - 'tokenDetails.tokenIdHex': tokenId - } - }, - project: { - 'tokenDetails.decimals': 1, - 'tokenDetails.tokenIdHex': 1, - _id: 0 - }, - limit: 1000 - } - } - - const s2 = JSON.stringify(query2) - const b642 = Buffer.from(s2).toString('base64') - const url2 = `${process.env.SLPDB_URL}q/${b642}` - const opt = { - method: 'get', - baseURL: url2, - headers: options.headers, - timeout: options.timeout - } - const tokenRes2 = await _this.axios.request(opt) - // console.log(`tokenRes2.data: ${JSON.stringify(tokenRes2.data, null, 2)}`) - - return tokenRes2.data - }) - - // Wait for all the promises to resolve. - const details = await Promise.all(promises) - - tokenRes.data.g = tokenRes.data.g.map((token) => { - details.forEach((detail) => { - if (detail.t[0].tokenDetails.tokenIdHex === token.tokenId) { - token.decimalCount = detail.t[0].tokenDetails.decimals - } - }) - - return token - }) - - return tokenRes.data.g - }) - - // Wait for all the promises to resolve. - const axiosResult = await _this.axios.all(balancesPromises) - - return res.json(axiosResult) - } catch (err) { - wlogger.error('Error in slp.js/balancesForAddressBulk().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in POST balancesForAddress: ${err.message}` - // }) - } - } - - /** - * @api {get} /slp/balancesForToken/{TokenId} List SLP addresses and balances for tokenId. - * @apiName List SLP addresses and balances for tokenId. - * @apiGroup SLP - * @apiDescription Returns List SLP addresses and balances for tokenId. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - // Retrieve token balances for all addresses by single tokenId. - async balancesForTokenSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const query = { - v: 3, - q: { - db: ['g'], - aggregate: [ - { - $match: { - 'graphTxn.outputs': { - $elemMatch: { - status: 'UNSPENT', - slpAmount: { $gte: 0 } - } - }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $unwind: '$graphTxn.outputs' - }, - { - $match: { - 'graphTxn.outputs.status': 'UNSPENT', - 'graphTxn.outputs.slpAmount': { $gte: 0 }, - 'tokenDetails.tokenIdHex': tokenId - } - }, - { - $project: { - token_balance: '$graphTxn.outputs.slpAmount', - address: '$graphTxn.outputs.address', - txid: '$graphTxn.txid', - vout: '$graphTxn.outputs.vout', - tokenId: '$tokenDetails.tokenIdHex' - } - }, - { - $group: { - _id: '$address', - token_balance: { - $sum: '$token_balance' - } - } - } - ], - limit: 10000 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentials() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - const resBalances = tokenRes.data.g.map((addy, index) => { - delete addy.satoshis_balance - addy.tokenBalanceString = addy.token_balance - addy.slpAddress = addy._id - addy.tokenId = tokenId - delete addy._id - delete addy.token_balance - - return addy - }) - - return res.json(resBalances) - } catch (err) { - wlogger.error('Error in slp.ts/balancesForTokenSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /balancesForToken/:tokenId: ${err.message}` - // }) - } - } - - /** - * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. - * @apiName Convert address to slpAddr, cashAddr and legacy. - * @apiGroup SLP - * @apiDescription Convert address to slpAddr, cashAddr and legacy. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json" - * - * - */ - async convertAddressSingle (req, res, next) { - try { - const address = req.params.address - - // Validate input - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const slpAddr = _this.bchjs.SLP.Address.toSLPAddress(address) - - const obj = { - slpAddress: '', - cashAddress: '', - legacyAddress: '' - } - obj.slpAddress = slpAddr - obj.cashAddress = _this.bchjs.SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress( - obj.cashAddress - ) - - res.status(200) - return res.json(obj) - } catch (err) { - wlogger.error('Error in slp.ts/convertAddressSingle().', err) - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /address/convert/:address: ${err.message}` - // }) - } - } - - /** - * @api {post} /slp/convert/ Convert multiple addresses to cash, legacy and simpleledger format. - * @apiName Convert multiple addresses to cash, legacy and simpleledger format. - * @apiGroup SLP - * @apiDescription Convert multiple addresses to cash, legacy and simpleledger format. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}' - * - * - */ - async convertAddressBulk (req, res, next) { - const addresses = req.body.addresses - - // Reject if hashes 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 (!_this.routeUtils.validateArraySize(req, addresses)) { - res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330 - return res.json({ - error: 'Array too large.' - }) - } - - // Convert each address in the array. - const convertedAddresses = [] - for (let i = 0; i < addresses.length; i++) { - const address = addresses[i] - - // Validate input - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const slpAddr = _this.bchjs.SLP.Address.toSLPAddress(address) - - const obj = { - slpAddress: '', - cashAddress: '', - legacyAddress: '' - } - obj.slpAddress = slpAddr - obj.cashAddress = _this.bchjs.SLP.Address.toCashAddress(slpAddr) - obj.legacyAddress = _this.bchjs.SLP.Address.toLegacyAddress( - obj.cashAddress - ) - - convertedAddresses.push(obj) - } - - res.status(200) - return res.json(convertedAddresses) - } - - /** - * @api {post} /slp/validateTxid/ Validate multiple SLP transactions by txid. - * @apiName Validate multiple SLP transactions by txid. - * @apiGroup SLP - * @apiDescription Validate multiple SLP transactions by txid. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}' - * - * - */ - async validateBulk (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' }) - } - - // Enforce array size rate limits - if (!_this.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 slp/validate with these txids: ', txids) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': { $in: txids } - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentials() - - // Get data from SLPDB. - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - let formattedTokens = [] - - // Combine the arrays. Why? Generally there is nothing in the u array. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - - const tokenIds = [] - if (concatArray.length > 0) { - concatArray.forEach((token) => { - tokenIds.push(token.tx.h) // txid - - const validationResult = { - txid: token.tx.h, - valid: token.slp.valid - } - - // If the txid is invalid, add the reason it's invalid. - if (!validationResult.valid) { - validationResult.invalidReason = token.slp.invalidReason - } - - formattedTokens.push(validationResult) - }) - - // If a user-provided txid doesn't exist in the data, add it with - // valid:false property. - txids.forEach((txid) => { - if (!tokenIds.includes(txid)) { - formattedTokens.push({ - txid: txid, - valid: false - }) - } - }) - } - - // Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs, - // which will cause the output array to be smaller than the input array. - if (txids.length > formattedTokens.length) { - const newOutput = [] - for (let i = 0; i < txids.length; i++) { - const thisTxid = txids[i] - - // Find the element that matches the current txid. - const elem = formattedTokens.filter((x) => x.txid === thisTxid) - - newOutput.push(elem[0]) - } - - // Replace the original output object with the new output object. - formattedTokens = newOutput - } - - res.status(200) - return res.json(formattedTokens) - } catch (err) { - wlogger.error('Error in slp.ts/validateBulk().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid/{txid} Validate single SLP transaction by txid. - * @apiName Validate single SLP transaction by txid. - * @apiGroup SLP - * @apiDescription Validate single SLP transaction by txid, using SLPDB. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validateSingle (req, res, next) { - try { - const txid = req.params.txid - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug('Executing slp/validate/:txid with this txid: ', txid) - - const query = { - v: 3, - q: { - db: ['c', 'u'], - find: { - 'tx.h': txid - }, - limit: 300, - project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 } - } - } - - const options = _this.generateCredentials() - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - - // Default return value. - let result = { - txid: txid, - valid: false - } - - // Build result. - const concatArray = tokenRes.data.c.concat(tokenRes.data.u) - if (concatArray.length > 0) { - result = { - txid: concatArray[0].tx.h, - valid: concatArray[0].slp.valid - } - if (!result.valid) { - result.invalidReason = concatArray[0].slp.invalidReason - } - } - - res.status(200) - return res.json(result) - } catch (err) { - wlogger.error('Error in slp.ts/validateSingle().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/validateTxid2/{txid} Validate single SLP transaction by txid. - * @apiName Validate single SLP transaction by txid. - * @apiGroup SLP - * @apiDescription Validate single SLP transaction by txid, using slp-validate. - * Slower, less efficient method of validating an SLP TXID using the slp-validate - * npm library. This method is independent of SLPDB and can be used as a fall-back - * when SLPDB returns 'null' values. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json" - * - * - */ - async validate2Single (req, res, next) { - try { - const txid = req.params.txid - - // Validate input - if (!txid || txid === '') { - res.status(400) - return res.json({ error: 'txid can not be empty' }) - } - - wlogger.debug( - 'Executing slp/validate2Single/:txid with this txid: ', - txid - ) - - // null by default. - // Default return value. - const result = { - txid: txid, - isValid: null, - msg: '' - } - - // Request options - const opt = { - method: 'get', - baseURL: `${process.env.SLP_API_URL}slp/validate/${txid}`, - timeout: 10000 // Exit after 10 seconds. - } - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - // console.log(`tokenRes: `, tokenRes) - - // Overwrite the default value with the result from slp-api. - result.isValid = tokenRes.data.isValid - - res.status(200) - return res.json(result) - } catch (err) { - // console.log('validate2Single error: ', err) - wlogger.error('Error in slp.ts/validate2Single().', err) - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/txDetails/{txid} SLP transaction details. - * @apiName SLP transaction details. - * @apiGroup SLP - * @apiDescription Transaction details on a token transfer. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json" - * - * - */ - async txDetails (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' }) - } - - if (txid.length !== 64) { - res.status(400) - return res.json({ error: 'This is not a txid' }) - } - - const query = { - v: 3, - db: ['g'], - q: { - find: { - 'tx.h': txid - }, - limit: 300 - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - - const options = _this.generateCredentials() - const opt = { - method: 'get', - baseURL: url, - headers: options.headers, - timeout: options.timeout - } - // Get token data from SLPDB - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes: ${util.inspect(tokenRes)}`) - - // Return 'not found' error if both the confirmed and unconfirmed - // collections are empty. - if (tokenRes.data.c.length === 0 && tokenRes.data.u.length === 0) { - res.status(404) - return res.json({ error: 'TXID not found' }) - } - - // Format the returned data to an object. - const formatted = await _this.formatToRestObject(tokenRes) - // console.log(`formatted: ${JSON.stringify(formatted,null,2)}`) - - // Get information on the transaction from Insight API. - // const retData = await transactions.transactionsFromInsight(txid) - const retData = await _this.rawTransactions.getRawTransactionsFromNode( - txid, - true - ) - // console.log(`retData: ${JSON.stringify(retData, null, 2)}`) - - // Return both the tx data from Insight and the formatted token information. - const response = { - retData, - ...formatted - } - - res.status(200) - return res.json(response) - } catch (err) { - wlogger.error('Error in slp.ts/txDetails().', err) - - // Handle corner case of mis-typted txid - // if (err.error && err.error.indexOf('Not found') > -1) { - // res.status(400) - // return res.json({ error: 'TXID not found' }) - // } - - return _this.errorHandler(err, res) - } - } - - /** - * @api {get} /slp/tokenStats/{tokenId} List stats for a single slp token. - * @apiName List stats for a single slp token. - * @apiGroup SLP - * @apiDescription Return list stats for a single slp token. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json" - * - * - */ - async tokenStats (req, res, next) { - try { - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const tokenStats = await _this.slpdb.getTokenStats(tokenId) - - res.status(200) - return res.json(tokenStats) - } catch (err) { - wlogger.error('Error in slp.ts/tokenStats().', err) - return _this.errorHandler(err, res) - // return res.json({ error: `Error in /tokenStats: ${err.message}` }) - } - } - - /** - * @api {get} /slp/transactions/{tokenId}/{address} SLP transactions by tokenId and address. - * @apiName SLP transactions by tokenId and address. - * @apiGroup SLP - * @apiDescription Transactions by tokenId and address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json" - * - * - */ - // Retrieve transactions by tokenId and address. - async txsTokenIdAddressSingle (req, res, next) { - try { - // Validate the input data. - const tokenId = req.params.tokenId - if (!tokenId || tokenId === '') { - res.status(400) - return res.json({ error: 'tokenId can not be empty' }) - } - - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - const query = { - v: 3, - q: { - find: { - db: ['c', 'u'], - $query: { - $or: [ - { - 'in.e.a': address - }, - { - 'out.e.a': address - } - ], - 'slp.detail.tokenIdHex': tokenId - }, - $orderby: { - 'blk.i': -1 - } - }, - limit: 100 - }, - r: { - f: '[.[] | { txid: .tx.h, tokenDetails: .slp } ]' - } - } - - const s = JSON.stringify(query) - const b64 = Buffer.from(s).toString('base64') - const url = `${process.env.SLPDB_URL}q/${b64}` - const opt = { - method: 'get', - baseURL: url - } - // Get data from SLPDB. - const tokenRes = await _this.axios.request(opt) - // console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`) - - return res.json(tokenRes.data.c) - } catch (err) { - wlogger.error('Error in slp.ts/txsTokenIdAddressSingle().', err) - - return _this.errorHandler(err, res) - // return res.json({ - // error: `Error in /transactions/:tokenId/:address: ${err.message}` - // }) - } - } - - // Generates a Basic Authorization header for slpserve. - generateCredentials () { - // Generate the Basic Authentication header for a private instance of SLPDB. - const username = 'BITBOX' - const password = SLPDB_PASS - const combined = `${username}:${password}` - var base64Credential = Buffer.from(combined).toString('base64') - var readyCredential = `Basic ${base64Credential}` - - const options = { - headers: { - authorization: readyCredential - }, - timeout: 15000 - } - - return options - } - - // Format the response from SLPDB into an object. - async formatToRestObject (slpDBFormat) { - _this.BigNumber.set({ DECIMAL_PLACES: 8 }) - - // console.log(`slpDBFormat.data: ${JSON.stringify(slpDBFormat.data, null, 2)}`) - - const transaction = slpDBFormat.data.u.length - ? slpDBFormat.data.u[0] - : slpDBFormat.data.c[0] - - // const inputs = transaction.in - - // const outputs = transaction.out - const tokenOutputs = transaction.slp.detail.outputs - - const sendOutputs = ['0'] - tokenOutputs.map((x) => { - const string = parseFloat(x.amount) * 100000000 - sendOutputs.push(string.toString()) - }) - - // Because you are not using Insight API indexer, you do not get the - // sending addresses from an indexer or from the node. - // However, they are available from the SLPDB output. - const tokenInputs = transaction.in - // Collect the input addresses - const sendInputs = [] - for (let i = 0; i < tokenInputs.length; i += 1) { - const tokenInput = tokenInputs[i] - const sendInput = {} - sendInput.address = tokenInput.e.a - sendInputs.push(sendInput) - } - - const obj = { - tokenInfo: { - versionType: transaction.slp.detail.versionType, - tokenName: transaction.slp.detail.name, - tokenTicker: transaction.slp.detail.symbol, - transactionType: transaction.slp.detail.transactionType, - tokenIdHex: transaction.slp.detail.tokenIdHex, - sendOutputs: sendOutputs, - sendInputsFull: sendInputs, - sendOutputsFull: transaction.slp.detail.outputs - }, - tokenIsValid: transaction.slp.valid - } - - return obj - } - - // Retrieve transactions by address. - async txsByAddressSingle (req, res, next) { - try { - // Validate the input data. - const address = req.params.address - if (!address || address === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - - // Ensure the input is a valid BCH address. - try { - _this.bchjs.SLP.Address.toCashAddress(address) - } catch (err) { - res.status(400) - return res.json({ - error: `Invalid BCH address. Double check your address is valid: ${address}` - }) - } - - // Ensure it is using the correct network. - const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address) - const networkIsValid = routeUtils.validateNetwork(cashAddr) - if (!networkIsValid) { - res.status(400) - return res.json({ - error: - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.' - }) - } - - const transactions = await _this.slpdb.getHistoricalSlpTransactions([ - address - ]) - // console.log(`transactions: ${JSON.stringify(transactions, null, 2)}`) - - res.status(200) - return res.json(transactions) - } catch (err) { - wlogger.error('Error in slp.ts/txsByAddressSingle().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: `Error in /transactionHistoryAllTokens/:address: ${err.message}` - }) - } - } - - /** - * @api {post} /slp/generateSendOpReturn/ generateSendOpReturn - * @apiName SLP generateSendOpReturn - * @apiGroup SLP - * @apiDescription Generate the hex required for a SLP Send OP_RETURN. - * - * This will return a hexidecimal representation of the OP_RETURN code that - * can be used to generate an SLP Send transaction. The number of outputs - * (1 or 2) will also be returned. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/generateSendOpReturn" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenUtxos":[{"tokenId": "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1","decimals": 0, "tokenQty": 2}], "sendQty": 1.5}' - * - * - */ - // Get OP_RETURN script and outputs - async generateSendOpReturn (req, res, next) { - try { - const tokenUtxos = req.body.tokenUtxos - // console.log(`tokenUtxos: `, tokenUtxos) - - const _sendQty = req.body.sendQty - const sendQty = Number(_sendQty) - - // console.log(`sendQty: `, sendQty) - - // Reject if tokenUtxos is not an array. - if (!Array.isArray(tokenUtxos)) { - res.status(400) - return res.json({ - error: 'tokenUtxos needs to be an array.' - }) - } - - // Reject if tokenUtxos array is empty. - if (!tokenUtxos.length) { - res.status(400) - return res.json({ - error: 'tokenUtxos array can not be empty.' - }) - } - - // Reject if sendQty is not an number. - if (!sendQty) { - res.status(400) - return res.json({ - error: 'sendQty must be a number.' - }) - } - - const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn( - tokenUtxos, - sendQty - ) - - const script = opReturn.script.toString('hex') - // console.log(`script: ${script}`) - - res.status(200) - return res.json({ script, outputs: opReturn.outputs }) - } catch (err) { - wlogger.error('Error in slp.js/generateSendOpReturn().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: 'Error in /generateSendOpReturn()' - }) - } - } - - /** - * @api {post} /slp/hydrateUtxos/ hydrateUtxos - * @apiName SLP hydrateUtxos - * @apiGroup SLP - * @apiDescription Hydrate UTXO data with SLP information. - * - * Expects an array of UTXO objects as input. Returns an array of equal size. - * Returns UTXO data hydrated with token information. If the UTXO does not - * belong to a SLP transaction, it will return an isValid property set to - * false. If the UTXO is part of an SLP transaction, it will return the UTXO - * object with additional SLP information attached. An isValid property will - * be included. If its value is true, the UTXO is a valid SLP UTXO. If the - * value is null, then SLPDB has not yet processed that txid and validity has - * not been confirmed. - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}' - * - * - */ - - async hydrateUtxos (req, res, next) { - try { - const utxos = req.body.utxos - - // Validate inputs - if (!Array.isArray(utxos)) { - res.status(422) - return res.json({ - error: 'Input must be an array.' - }) - } - - if (!utxos.length) { - res.status(422) - return res.json({ - error: 'Array should not be empty' - }) - } - - if (utxos.length > 20) { - res.status(422) - return res.json({ - error: 'Array too long, max length is 20' - }) - } - - if (!utxos[0].utxos) { - res.status(422) - return res.json({ - error: 'Each element in array should have a utxos property' - }) - } - - // Loop through each address and query the UTXOs for that element. - for (let i = 0; i < utxos.length; i++) { - const theseUtxos = utxos[i].utxos - - // Get SLP token details. - const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos) - // console.log('details : ', details) - - // Replace the original UTXO data with the hydrated data. - utxos[i].utxos = details - } - - res.status(200) - return res.json({ slpUtxos: utxos }) - } catch (err) { - wlogger.error('Error in slp.js/hydrateUtxos().', err) - console.error('Error in slp.js/hydrateUtxos().', err) - - // Decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - res.status(500) - return res.json({ - error: 'Error in hydrateUtxos()' - }) - } - } -} - -module.exports = Slp diff --git a/src/routes/v3/util.js b/src/routes/v3/util.js deleted file mode 100644 index 13c663a..0000000 --- a/src/routes/v3/util.js +++ /dev/null @@ -1,604 +0,0 @@ -'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 Blockbook = require('./blockbook') -const blockbook = new Blockbook() - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() -// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/' - -// const bchjsHTTP = 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' -// } -// } - -let _this - -class UtilRoute { - constructor () { - this.bchjs = bchjs - this.blockbook = blockbook - - _this = this - } - - root (req, res, next) { - return res.json({ status: 'util' }) - } - - /** - * @api {get} /util/validateAddress/{address} Get information about single bitcoin cash address. - * @apiName Information about single bitcoin cash address - * @apiGroup Util - * @apiDescription Returns information about single bitcoin cash address. - * - * - * @apiExample Example usage: - * curl -X GET "https://api.fullstack.cash/v3/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json" - * - * - */ - async 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) }) - } - } - - /** - * @api {post} /util/validateAddress Get information about bulk bitcoin cash addresses.. - * @apiName Information about bulk bitcoin cash addresses. - * @apiGroup Util - * @apiDescription Returns information about bulk bitcoin cash addresses.. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}' - * curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}' - * - * - */ - async 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 { - bchjs.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.' - }) - } - } - - wlogger.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 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) }) - } - } - - /** - * @api {post} /util/sweep Sweep BCH and tokens - * @apiName Sweep BCH and tokens from a paper wallet - * @apiGroup Util - * @apiDescription This function can be used to check the BCH balance of a - * paper wallet. It can also be used to sweep BCH and tokens from a paper - * wallet and send them to a destination address. - * - * Note: It does not yet support multiple token classes on the same paper wallet. - * - * - * @apiExample Example usage: - * curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}' - * curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}' - * - * - */ - - async sweepWif (req, res, next) { - try { - // Validate input - const wif = req.body.wif - const toAddr = req.body.toAddr - const balanceOnly = req.body.balanceOnly - - if (typeof wif !== 'string' || wif.length !== 52) { - res.status(400) - return res.json({ - error: 'WIF needs to a proper compressed WIF starting with K or L' - }) - } - - if (!balanceOnly) { - // Only throw error if balanceOnly is false or undefined. - if (!toAddr || toAddr === '') { - res.status(400) - return res.json({ error: 'address can not be empty' }) - } - } - - wlogger.debug('Executing util/sweepWif with this address: ', toAddr) - - // Generate a private and public key pair from the WIF. - const ecPair = bchjs.ECPair.fromWIF(wif) - const fromAddr = bchjs.ECPair.toCashAddress(ecPair) - - // Get a balance on the public address - const balances = await _this.blockbook.balanceFromBlockbook(fromAddr) - // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) - - // Total balance is the sum of the confirmed and unconfirmed balance. - const totalBalance = - Number(balances.balance) + Number(balances.unconfirmedBalance) - - // Exit if balance is zero. - if (isNaN(totalBalance) || totalBalance === 0) { - res.status(422) - return res.json({ error: 'No balance found at BCH address.' }) - } - - // Exit if this is a balance-only call. - if (balanceOnly) { - res.status(200) - return res.json(totalBalance) - } - - // Get all UTXOs help by the address. - const utxos = await _this.blockbook.utxosFromBlockbook(fromAddr) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - const tokenUtxos = [] - const bchUtxos = [] - - // Exit if there are no UTXOs. - if (utxos.length === 0) { - res.status(422) - return res.json({ error: 'No utxos found.' }) - } - - // Figure out which UTXOs are associated with SLP tokens. - const isTokenUtxo = await _this.bchjs.SLP.Utils.tokenUtxoDetails(utxos) - // console.log(`isTokenUtxo: ${JSON.stringify(isTokenUtxo, null, 2)}`) - - // Separate the bch and token UTXOs. - for (let i = 0; i < utxos.length; i++) { - // Filter based on isTokenUtxo. - if (!isTokenUtxo[i]) bchUtxos.push(utxos[i]) - else tokenUtxos.push(isTokenUtxo[i]) - } - // console.log( - // `bchUtxos.length: ${bchUtxos.length}, tokenUtxos.length: ${tokenUtxos.length}` - // ) - - // Throw error if no BCH to move tokens. - if (bchUtxos.length === 0 && tokenUtxos.length > 0) { - res.status(422) - return res.json({ - error: - 'Tokens found, but no BCH UTXOs found. Add BCH to wallet to move tokens.' - }) - } - - // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`) - - const options = { - ecPair, - utxos, - fromAddr, - toAddr, - bchUtxos, - tokenUtxos - } - - let hex - - // Choose the sweeping algorithm, based on if there are tokens or not. - if (tokenUtxos.length === 0) hex = await _this._sweepBCH(options) - else hex = await _this._sweepTokens(options, bchUtxos, tokenUtxos) - // console.log(`hex: ${hex}`) - - // Throw error if there is more than one token class. - - // Generate a transaction to move tokens and BCH. - - // Broadcast the transaction. - const txid = _this.bchjs.RawTransactions.sendRawTransaction([hex]) - - res.status(200) - return res.json(txid) - } catch (err) { - // Attempt to decode the error message. - const { msg, status } = routeUtils.decodeError(err) - if (msg) { - res.status(status) - return res.json({ error: msg }) - } - - // Catch the specific case of multiple tokens. - if ( - err.message && - err.message.indexOf('Multiple token classes detected') > -1 - ) { - res.status(422) - return res.json({ error: err.message }) - } - - wlogger.error('Error in util.js/sweepWif().', err) - console.error('Error in util.js/sweepWif().', err) - - res.status(500) - return res.json({ error: err.message }) - } - } - - // Sweep BCH only from a private WIF. - async _sweepBCH (options) { - try { - // const wif = flags.wif - // const toAddr = flags.address - - const ecPair = options.ecPair - const toAddr = options.toAddr - - // const fromAddr = this.BITBOX.ECPair.toCashAddress(ecPair) - // - // // Get the UTXOs for that address. - // let utxos = await this.BITBOX.Blockbook.utxo(fromAddr) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - let utxos = options.utxos - - // Ensure all utxos have the satoshis property. - utxos = utxos.map(x => { - x.satoshis = Number(x.value) - return x - }) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - // instance of transaction builder - let transactionBuilder - if (options.testnet) { - transactionBuilder = new _this.bchjs.TransactionBuilder('testnet') - } else transactionBuilder = new _this.bchjs.TransactionBuilder() - - let originalAmount = 0 - - // Loop through all UTXOs. - for (let i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - originalAmount = originalAmount + utxo.satoshis - - transactionBuilder.addInput(utxo.txid, utxo.vout) - } - - if (originalAmount < 546) { - throw new Error( - 'Original amount less than the dust limit. Not enough BCH to send.' - ) - } - - // get byte count to calculate fee. paying 1 sat/byte - const byteCount = _this.bchjs.BitcoinCash.getByteCount( - { P2PKH: utxos.length }, - { P2PKH: 1 } - ) - const fee = Math.ceil(1.1 * byteCount) - - // amount to send to receiver. It's the original amount - 1 sat/byte for tx size - const sendAmount = originalAmount - fee - - // add output w/ address and amount to send - transactionBuilder.addOutput( - _this.bchjs.Address.toLegacyAddress(toAddr), - sendAmount - ) - - // Loop through each input and sign - let redeemScript - for (var i = 0; i < utxos.length; i++) { - const utxo = utxos[i] - - transactionBuilder.sign( - i, - ecPair, - redeemScript, - transactionBuilder.hashTypes.SIGHASH_ALL, - utxo.satoshis - ) - } - - // build tx - const tx = transactionBuilder.build() - - // output rawhex - const hex = tx.toHex() - return hex - } catch (err) { - wlogger.error('Error in util.js/sweepBCH().') - throw err - } - } - - // Sweep BCH and tokens from a WIF. - async _sweepTokens (options) { - try { - // const { ecPair, utxos, fromAddr, toAddr, bchUtxos, tokenUtxos } = options - const { ecPair, utxos, toAddr, bchUtxos, tokenUtxos } = options - - // Input validation - if (!Array.isArray(bchUtxos) || bchUtxos.length === 0) { - throw new Error('bchUtxos need to be an array with one UTXO.') - } - if (!Array.isArray(tokenUtxos) || tokenUtxos.length === 0) { - throw new Error('tokenUtxos need to be an array with one UTXO.') - } - - // if (flags.testnet) - // this.BITBOX = new config.BCHLIB({ restURL: config.TESTNET_REST }) - - // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`) - - // Ensure there is only one class of token in the wallet. Throw an error if - // there is more than one. - const tokenId = tokenUtxos[0].tokenId - const otherTokens = tokenUtxos.filter(x => x.tokenId !== tokenId) - if (otherTokens.length > 0) { - throw new Error( - 'Multiple token classes detected. This function only supports a single class of token.' - ) - } - - // instance of transaction builder - let transactionBuilder - if (options.testnet) { - transactionBuilder = new _this.bchjs.TransactionBuilder('testnet') - } else transactionBuilder = new _this.bchjs.TransactionBuilder() - - // Combine all the UTXOs into a single array. - const allUtxos = utxos - // console.log(`allUtxos: ${JSON.stringify(allUtxos, null, 2)}`) - - // Loop through all UTXOs. - let originalAmount = 0 - for (let i = 0; i < allUtxos.length; i++) { - const utxo = allUtxos[i] - - originalAmount = originalAmount + utxo.satoshis - - transactionBuilder.addInput(utxo.txid, utxo.vout) - } - - if (originalAmount < 300) { - throw new Error( - 'Not enough BCH to send. Send more BCH to the wallet to pay miner fees.' - ) - } - - // get byte count to calculate fee. paying 1 sat - // Note: This may not be totally accurate. Just guessing on the byteCount size. - // const byteCount = this.BITBOX.BitcoinCash.getByteCount( - // { P2PKH: 3 }, - // { P2PKH: 5 } - // ) - // //console.log(`byteCount: ${byteCount}`) - // const satoshisPerByte = 1.1 - // const txFee = Math.floor(satoshisPerByte * byteCount) - // console.log(`txFee: ${txFee} satoshis\n`) - const txFee = 500 - - // amount to send back to the sending address. It's the original amount - 1 sat/byte for tx size - const remainder = originalAmount - txFee - 546 - if (remainder < 1) { - throw new Error('Selected UTXO does not have enough satoshis') - } - // console.log(`remainder: ${remainder}`) - - // Tally up the quantity of tokens - let tokenQty = 0 - for (let i = 0; i < tokenUtxos.length; i++) { - tokenQty += tokenUtxos[i].tokenQty - } - // console.log(`tokenQty: ${tokenQty}`) - - // Generate the OP_RETURN entry for an SLP SEND transaction. - // console.log(`Generating op-return.`) - const { - script, - outputs - } = _this.bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, tokenQty) - // console.log(`token outputs: ${outputs}`) - - // Since we are sweeping all tokens from the WIF, there generateOpReturn() - // function should only compute 1 token output. If it returns 2, then there - // is something unexpected happening. - if (outputs > 1) { - throw new Error( - 'More than one class of token detected. Sweep feature not supported.' - ) - } - - // Add OP_RETURN as first output. - const data = _this.bchjs.Script.encode(script) - transactionBuilder.addOutput(data, 0) - - // Send dust transaction representing tokens being sent. - transactionBuilder.addOutput( - _this.bchjs.Address.toLegacyAddress(toAddr), - 546 - ) - - // Last output: send remaining BCH - transactionBuilder.addOutput( - _this.bchjs.Address.toLegacyAddress(toAddr), - remainder - ) - // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) - - // Sign each UTXO being consumed. - let redeemScript - for (let i = 0; i < allUtxos.length; i++) { - const thisUtxo = allUtxos[i] - // console.log(`thisUtxo: ${JSON.stringify(thisUtxo, null, 2)}`) - - transactionBuilder.sign( - i, - ecPair, - redeemScript, - transactionBuilder.hashTypes.SIGHASH_ALL, - thisUtxo.satoshis - ) - } - - // build tx - const tx = transactionBuilder.build() - - // output rawhex - const hex = tx.toHex() - // console.log(`Transaction raw hex: `) - // console.log(hex) - - return hex - } catch (err) { - wlogger.error('Error in util.js/sweepBCH().') - throw err - } - } -} - -const utilRoute = new UtilRoute() - -router.get('/', utilRoute.root) -router.get('/validateAddress/:address', utilRoute.validateAddressSingle) -router.post('/validateAddress', utilRoute.validateAddressBulk) -router.post('/sweep', utilRoute.sweepWif) - -module.exports = { - router, - // testableComponents: { - // root, - // validateAddressSingle, - // validateAddressBulk, - // sweepWif - // } - UtilRoute -} diff --git a/src/routes/v3/xpub.js b/src/routes/v3/xpub.js deleted file mode 100644 index fa1d9d4..0000000 --- a/src/routes/v3/xpub.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - xpub route -*/ - -'use strict' - -const express = require('express') -// const axios = require('axios') -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 BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -// Connect the route endpoints to their handler functions. -router.get('/', root) -router.get('/fromXPub/:xpub', fromXPubSingle) - -// Root API endpoint. Simply acknowledges that it exists. -function root (req, res, next) { - return res.json({ status: 'address' }) -} - -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.' - }) - } - - wlogger.debug('Executing address/fromXPub with this xpub: ', xpub) - - const cashAddr = bchjs.Address.fromXPub(xpub, hdPath) - const legacyAddr = bchjs.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. - wlogger.error('Error in address.ts/fromXPubSingle().', err) - - res.status(500) - return res.json({ error: util.inspect(err) }) - } -} - -module.exports = { - router, - testableComponents: { - root, - fromXPubSingle - } -} diff --git a/test/v3/a01-electrumx.js b/test/v3/a01-electrumx.js deleted file mode 100644 index c1f003a..0000000 --- a/test/v3/a01-electrumx.js +++ /dev/null @@ -1,1894 +0,0 @@ -/* - TESTS FOR THE ELECTRUMX.JS LIBRARY - - Named with a01 prefix so that these tests are run first. Something about running - the Blcokbook and Blockchain tests screws up these tests. Spent a couple hours - debugging and couldn't isolate the source of the issue, but renaming the file - was an easy fix. - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - To-Do: -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert - -const sinon = require('sinon') - -const ElecrumxRoute = require('../../src/routes/v3/electrumx') - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/electrumx-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// A wrapper for asserting that the correct response is returned when an error -// is expected. -function expectRouteError (res, result, expectedError, code = 400) { - assert.equal(res.statusCode, code, `HTTP status code ${code} expected.`) - - assert.property(result, 'error') - assert.include(result.error, expectedError) - - assert.property(result, 'success') - assert.equal(result.success, false) -} - -describe('#Electrumx', () => { - let req, res - let sandbox - const electrumxRoute = new ElecrumxRoute() - - before(async () => { - if (!process.env.TEST) process.env.TEST = 'unit' - console.log(`Testing type is: ${process.env.TEST}`) - - if (!process.env.NETWORK) process.env.NETWORK = 'testnet' - - // Connect to electrumx servers if this is an integration test. - if (process.env.TEST === 'integration') { - await electrumxRoute.connect() - console.log('Connected to ElectrumX server') - } - }) - - after(async () => { - // console.log(`electrumxRoute.electrumx: `, electrumxRoute.electrumx) - - // Disconnect from the electrumx server if this is an integration test. - if (process.env.TEST === 'integration') { - await electrumxRoute.disconnect() - console.log('Disconnected from ElectrumX server') - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - - // electrumxRoute = new ElecrumxRoute() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - // - }) - - // A wrapper for stubbing with the Sinon sandbox. - function stubMethodForUnitTests (obj, method, value) { - if (process.env.TEST !== 'unit') return false - - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(obj, method).resolves(value) - - return true - } - - describe('#root', () => { - // root route handler. - const root = electrumxRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'electrumx', 'Returns static string') - }) - }) - - describe('#addressToScripthash', () => { - it('should accurately return a scripthash for a P2PKH address', () => { - const addr = 'bitcoincash:qpr270a5sxphltdmggtj07v4nskn9gmg9yx4m5h7s4' - - const scripthash = electrumxRoute.addressToScripthash(addr) - - const expectedOutput = - 'bce4d5f2803bd1ed7c1ba00dcb3edffcbba50524af7c879d6bb918d04f138965' - - assert.equal(scripthash, expectedOutput) - }) - - it('should accurately return a scripthash for a P2SH address', () => { - const addr = 'bitcoincash:pz0z7u9p96h2p6hfychxdrmwgdlzpk5luc5yks2wxq' - - const scripthash = electrumxRoute.addressToScripthash(addr) - - const expectedOutput = - '8bc2235c8e7d5634d9ec429fc0171f2c58e728d4f1e2fb7e440e313133cfa4f0' - - assert.equal(scripthash, expectedOutput) - }) - }) - - describe('#_utxosFromElectrumx', () => { - it('should throw error for invalid address', async () => { - try { - // Address has invalid checksum. - const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - await electrumxRoute._utxosFromElectrumx(address) - - assert.equal(true, false, 'Unexpected code path') - } catch (err) { - assert.include(err.message, 'Invalid checksum') - } - }) - - it('should return empty array for address with no utxos', async () => { - // Address has invalid checksum. - const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) - } - - // Call the details API. - const result = await electrumxRoute._utxosFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 0) - }) - - it('should get balance for a single address', async () => { - const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.electrumx, 'request') - .resolves(mockData.utxos) - } - - // Call the details API. - const result = await electrumxRoute._utxosFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'height') - assert.property(result[0], 'tx_hash') - assert.property(result[0], 'tx_pos') - assert.property(result[0], 'value') - }) - }) - - describe('#getUtxos', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from ElectrumX to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.electrumx, 'request') - .resolves(mockData.utxos) - } - - // Call the details API. - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get balance for a single address', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_utxosFromElectrumx') - .resolves(mockData.utxos) - } - - // Call the details API. - const result = await electrumxRoute.getUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'tx_pos') - assert.property(result.utxos[0], 'value') - }) - }) - - describe('#utxosBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await electrumxRoute.utxosBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await electrumxRoute.utxosBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_utxosFromElectrumx') - .resolves(mockData.utxos) - } - - // Call the details API. - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'address') - assert.property(result.utxos[0], 'utxos') - - assert.isArray(result.utxos[0].utxos) - assert.property(result.utxos[0].utxos[0], 'height') - assert.property(result.utxos[0].utxos[0], 'tx_hash') - assert.property(result.utxos[0].utxos[0], 'tx_pos') - assert.property(result.utxos[0].utxos[0], 'value') - }) - - it('should get utxos for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_utxosFromElectrumx') - .resolves(mockData.utxos) - } - - // Call the details API. - const result = await electrumxRoute.utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.isArray(result.utxos) - assert.isArray(result.utxos[0].utxos) - assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#_transactionDetailsFromElectrum', () => { - it('should return error object for invalid txid', async () => { - const txid = - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - new Error('Invalid tx hash') - ) - - const result = await electrumxRoute._transactionDetailsFromElectrum(txid) - - assert.instanceOf(result, Error) - assert.include(result.message, 'Invalid tx hash') - }) - - it('should get details for a single txid', async () => { - const txid = - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockData.txDetails - ) - - const result = await electrumxRoute._transactionDetailsFromElectrum(txid) - - assert.isObject(result) - assert.property(result, 'blockhash') - assert.property(result, 'hash') - assert.property(result, 'hex') - assert.property(result, 'vin') - assert.property(result, 'vout') - assert.equal(result.hash, txid) - }) - }) - - describe('#getTransactionDetails', () => { - it('should throw 400 if txid is not a string', async () => { - req.params.txid = 5 - - const result = await electrumxRoute.getTransactionDetails(req, res) - - expectRouteError(res, result, 'txid must be a string') - }) - - it('should throw 400 on array input', async () => { - req.params.txid = [ - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - ] - - const result = await electrumxRoute.getTransactionDetails(req, res) - - expectRouteError(res, result, 'txid must be a string') - }) - - it('should return error object for invalid txid', async () => { - req.params.txid = - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - new Error('Invalid tx hash') - ) - - // Call the details API. - const result = await electrumxRoute.getTransactionDetails(req, res) - - expectRouteError(res, result, 'Invalid tx hash') - }) - - it('should get details for a single txid', async () => { - req.params.txid = - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockData.txDetails - ) - - // Call the details API. - const result = await electrumxRoute.getTransactionDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'details') - assert.isObject(result.details) - - assert.property(result.details, 'blockhash') - assert.property(result.details, 'hash') - assert.property(result.details, 'hex') - assert.property(result.details, 'vin') - assert.property(result.details, 'vout') - assert.equal(result.details.hash, req.params.txid) - }) - }) - - describe('#transactionDetailsBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await electrumxRoute.transactionDetailsBulk(req, res) - - expectRouteError(res, result, 'txids needs to be an array') - }) - - it('should error on non-array single txid', async () => { - req.body = { - txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - } - - const result = await electrumxRoute.transactionDetailsBulk(req, res) - - expectRouteError(res, result, 'txids needs to be an array') - }) - - it('should NOT throw 400 error for an invalid txid', async () => { - req.body = { - txids: [ - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25' - ] - } - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockData.txDetails - ) - - const result = await electrumxRoute.transactionDetailsBulk(req, res) - - // This should probably throw a 400 error, but to be consistent with the other - // bulk endpoints it doesn't throw. This will change in the future - // expectRouteError(res, result, 'Invalid tx hash') - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - }) - - it('should throw 429 error if txid array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - expectRouteError(res, result, 'Array too large', 429) - }) - - it('should get details for a single txid', async () => { - req.body = { - txids: [ - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - ] - } - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockData.txDetails - ) - - // Call the details API. - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'txid') - assert.property(result.transactions[0], 'details') - - assert.property(result.transactions[0].details, 'blockhash') - assert.property(result.transactions[0].details, 'hash') - assert.property(result.transactions[0].details, 'hex') - assert.property(result.transactions[0].details, 'vin') - assert.property(result.transactions[0].details, 'vout') - }) - - it('should get details for multiple txids', async () => { - req.body = { - txids: [ - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - ] - } - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockData.txDetails - ) - - // Call the details API. - const result = await electrumxRoute.transactionDetailsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`)' - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.isArray(result.transactions) - assert.isObject(result.transactions[0].details) - assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#_blockHeadersFromElectrum', () => { - it('should return error object for invalid block height', async () => { - const height = -10 - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - new Error('Invalid height') - ) - - const result = await electrumxRoute._blockHeadersFromElectrum(height, 2) - - assert.instanceOf(result, Error) - assert.include(result.message, 'Invalid height') - }) - - it('should return error object for invalid count', async () => { - const height = 42 - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - new Error('Invalid count') - ) - - const result = await electrumxRoute._blockHeadersFromElectrum(height, -1) - - assert.instanceOf(result, Error) - assert.include(result.message, 'Invalid count') - }) - - it('should get block header for a single block height', async () => { - const height = 42 - - const mockedResponse = { count: 2, hex: mockData.blockHeaders.join(''), max: 2016 } - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockedResponse - ) - - const result = await electrumxRoute._blockHeadersFromElectrum(height, 2) - - assert.isArray(result) - assert.deepEqual(result, mockData.blockHeaders) - }) - }) - - describe('#getBlockheaders', () => { - it('should throw 400 if height is not a number', async () => { - req.params.height = 'Hello' - - const result = await electrumxRoute.getBlockHeaders(req, res) - - expectRouteError(res, result, 'height must be a positive number') - }) - - it('should throw 400 if height is negative', async () => { - req.params.height = -42 - - const result = await electrumxRoute.getBlockHeaders(req, res) - - expectRouteError(res, result, 'height must be a positive number') - }) - - it('should throw 400 if count is not a number', async () => { - req.params.height = 42 - req.query.count = 'Hello' - - const result = await electrumxRoute.getBlockHeaders(req, res) - - expectRouteError(res, result, 'count must be a positive number') - }) - - it('should throw 400 if count is negative', async () => { - req.params.height = 42 - req.query.count = -10 - - const result = await electrumxRoute.getBlockHeaders(req, res) - - expectRouteError(res, result, 'count must be a positive number') - }) - - it('should throw 400 on array input', async () => { - req.params.height = [42, 42] - - const result = await electrumxRoute.getBlockHeaders(req, res) - - expectRouteError(res, result, 'height must be a positive number') - }) - - it('should return error object for invalid height', async () => { - req.params.height = 1000000000 - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - new Error('Invalid height') - ) - - // Call the details API. - const result = await electrumxRoute.getBlockHeaders(req, res) - - expectRouteError(res, result, 'Invalid height') - }) - - it('should get headers for a single block height with count 2', async () => { - req.params.height = 42 - req.query.count = 2 - - stubMethodForUnitTests( - electrumxRoute, - '_blockHeadersFromElectrum', - mockData.blockHeaders - ) - - // Call the details API. - const result = await electrumxRoute.getBlockHeaders(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - assert.deepEqual(result.headers, mockData.blockHeaders) - }) - }) - - describe('#blockHeadersBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await electrumxRoute.blockHeadersBulk(req, res) - - expectRouteError(res, result, 'heights needs to be an array') - }) - - it('should error on non-array single height', async () => { - req.body = { - heights: 42 - } - - const result = await electrumxRoute.blockHeadersBulk(req, res) - - expectRouteError(res, result, 'heights needs to be an array') - }) - - it('should NOT throw 400 error for an invalid height', async () => { - req.body = { - heights: [ - { height: -10, count: 2 } - ] - } - - stubMethodForUnitTests( - electrumxRoute, - '_blockHeadersFromElectrum', - mockData.blockHeaders - ) - - const result = await electrumxRoute.blockHeadersBulk(req, res) - - // This should probably throw a 400 error, but to be consistent with the other - // bulk endpoints it doesn't throw. This will change in the future - // expectRouteError(res, result, 'Invalid tx hash') - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - }) - - it('should throw 429 error if heights array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.heights = testArray - - const result = await electrumxRoute.blockHeadersBulk(req, res) - - expectRouteError(res, result, 'Array too large', 429) - }) - - it('should get details for a single height', async () => { - req.body = { - heights: [ - { height: 42, count: 2 } - ] - } - - stubMethodForUnitTests( - electrumxRoute, - '_blockHeadersFromElectrum', - mockData.blockHeaders - ) - - // Call the details API. - const result = await electrumxRoute.blockHeadersBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - - assert.property(result.headers[0], 'headers') - assert.isArray(result.headers[0].headers) - }) - - it('should get details for multiple txids', async () => { - req.body = { - heights: [ - { height: 42, count: 2 }, - { height: 42, count: 2 } - ] - } - - stubMethodForUnitTests( - electrumxRoute, - '_blockHeadersFromElectrum', - mockData.blockHeaders - ) - - // Call the details API. - const result = await electrumxRoute.blockHeadersBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`)' - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.isArray(result.headers) - assert.isArray(result.headers[0].headers) - assert.equal(result.headers.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#_broadcastTransactionWithElectrum', () => { - it('should return error object for invalid formatted transaction', async () => { - const invalidHex = mockData.txDetails.hex.substring(10) - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n') - ) - - const result = await electrumxRoute._broadcastTransactionWithElectrum(invalidHex) - - assert.instanceOf(result, Error) - assert.include(result.message, 'TX decode failed') - }) - - it('should return txid for valid transaction', async function () { - // We cannot send an actual broadcast transaction to mainnet - if (process.env.TEST !== 'unit') return this.skip() - - const validHex = mockData.txDetails.hex - - stubMethodForUnitTests( - electrumxRoute.electrumx, - 'request', - mockData.txDetails.hash - ) - - const result = await electrumxRoute._broadcastTransactionWithElectrum(validHex) - - assert.typeOf(result, 'string') - assert.equal(result, mockData.txDetails.hash) - }) - }) - - describe('#broadcastTransaction', () => { - it('should throw an error for a non-string', async () => { - req.body = 456 - - stubMethodForUnitTests( - electrumxRoute, - '_broadcastTransactionWithElectrum', - new Error('request body must be a string.') - ) - - const result = await electrumxRoute.broadcastTransaction(req, res) - - expectRouteError(res, result, 'request body must be a string.') - }) - - it('should throw an error object for invalid formatted transaction', async () => { - req.body.txHex = mockData.txDetails.hex.substring(10) - - stubMethodForUnitTests( - electrumxRoute, - '_broadcastTransactionWithElectrum', - new Error('Error: the transaction was rejected by network rules.\n\nTX decode failed\n') - ) - - const result = await electrumxRoute.broadcastTransaction(req, res) - - expectRouteError(res, result, 'TX decode failed') - }) - - it('should return txid for valid transaction', async function () { - // We cannot send an actual broadcast transaction to mainnet - if (process.env.TEST !== 'unit') return this.skip() - - req.body.txHex = mockData.txDetails.hex - - stubMethodForUnitTests( - electrumxRoute, - '_broadcastTransactionWithElectrum', - mockData.txDetails.hash - ) - - const result = await electrumxRoute.broadcastTransaction(req, res) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'txid') - assert.equal(result.txid, mockData.txDetails.hash) - }) - }) - - describe('#_balanceFromElectrumx', () => { - it('should throw error for invalid address', async () => { - try { - // Address has invalid checksum. - const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Mock unit tests to prevent live network calls. - // if (process.env.TEST === 'unit') { - // electrumxRoute.isReady = true // Force flag. - // - // sandbox - // .stub(electrumxRoute.electrumx, 'request') - // .throws('Invalid Argument: Invalid checksum:') - // } - - // Call the details API. - await electrumxRoute._balanceFromElectrumx(address) - - assert.equal(true, false, 'Unexpected code path') - } catch (err) { - // console.log('err2: ', err) - assert.include(err.message, 'Invalid checksum') - } - }) - - it('should get balance for a single address', async () => { - const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.electrumx, 'request') - .resolves(mockData.balance) - } - - // Call the details API. - const result = await electrumxRoute._balanceFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'confirmed') - assert.property(result, 'unconfirmed') - }) - - it('should get balance for an address with no transaction history', async () => { - const address = 'bitcoincash:qp2ew6pvrs22jtsvtjyumjgas6jkvgn2hy3ad4wpw8' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.electrumx, 'request') - .resolves(mockData.balance) - } - - // Call the details API. - const result = await electrumxRoute._balanceFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'confirmed') - assert.property(result, 'unconfirmed') - }) - }) - - describe('#getBalance', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get balance for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_balanceFromElectrumx') - .resolves(mockData.balance) - } - - // Call the details API. - const result = await electrumxRoute.getBalance(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balance') - assert.property(result.balance, 'confirmed') - assert.property(result.balance, 'unconfirmed') - }) - }) - - describe('#balanceBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await electrumxRoute.balanceBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await electrumxRoute.balanceBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_balanceFromElectrumx') - .resolves(mockData.balance) - } - - // Call the details API. - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balances') - assert.isArray(result.balances) - - assert.property(result.balances[0], 'address') - assert.property(result.balances[0], 'balance') - - assert.property(result.balances[0].balance, 'confirmed') - assert.property(result.balances[0].balance, 'unconfirmed') - }) - - it('should get utxos for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_balanceFromElectrumx') - .resolves(mockData.balance) - } - - // Call the details API. - const result = await electrumxRoute.balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.isArray(result.balances) - assert.equal(result.balances.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#_transactionsFromElectrumx', () => { - it('should throw error for invalid address', async () => { - try { - // Address has invalid checksum. - const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - await electrumxRoute._transactionsFromElectrumx(address) - - assert.equal(true, false, 'Unexpected code path') - } catch (err) { - // console.log('err2: ', err) - assert.include(err.message, 'Invalid checksum') - } - }) - - it('should get transaction history for a single address', async () => { - const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.electrumx, 'request') - .resolves(mockData.txHistory) - } - - // Call the details API. - const result = await electrumxRoute._transactionsFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'height') - assert.property(result[0], 'tx_hash') - }) - - it('should get history for an address with no transaction history', async () => { - const address = 'bitcoincash:qp2ew6pvrs22jtsvtjyumjgas6jkvgn2hy3ad4wpw8' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) - } - - // Call the details API. - const result = await electrumxRoute._transactionsFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 0) - }) - }) - - describe('#getTransactions', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from ElectrumX to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get transactions for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_transactionsFromElectrumx') - .resolves(mockData.txHistory) - } - - // Call the details API. - const result = await electrumxRoute.getTransactions(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - assert.property(result.transactions[0], 'height') - assert.property(result.transactions[0], 'tx_hash') - }) - }) - - describe('#balanceBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await electrumxRoute.transactionsBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await electrumxRoute.transactionsBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_transactionsFromElectrumx') - .resolves(mockData.txHistory) - } - - // Call the details API. - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'address') - assert.property(result.transactions[0], 'transactions') - - assert.isArray(result.transactions[0].transactions) - assert.property(result.transactions[0].transactions[0], 'height') - assert.property(result.transactions[0].transactions[0], 'tx_hash') - }) - - it('should get utxos for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_transactionsFromElectrumx') - .resolves(mockData.txHistory) - } - - // Call the details API. - const result = await electrumxRoute.transactionsBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.isArray(result.transactions) - assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#_mempoolFromElectrumx', () => { - it('should throw error for invalid address', async () => { - try { - // Address has invalid checksum. - const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - await electrumxRoute._mempoolFromElectrumx(address) - - assert.equal(true, false, 'Unexpected code path') - } catch (err) { - assert.include(err.message, 'Invalid checksum') - } - }) - - it('should return empty array for address with no unconfirmed utxos', async () => { - // Address has invalid checksum. - const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox.stub(electrumxRoute.electrumx, 'request').resolves([]) - } - - // Call the details API. - const result = await electrumxRoute._mempoolFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 0) - }) - - it('should get mempool for a single address', async () => { - const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute.electrumx, 'request') - .resolves(mockData.mempool) - } else { - // Skip this test for integrations. Unconfirmed UTXOs are transient and - // not easy to test in real-time. - assert.equal(true, true) - return - } - - // Call the details API. - const result = await electrumxRoute._mempoolFromElectrumx(address) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'height') - assert.property(result[0], 'tx_hash') - assert.property(result[0], 'fee') - }) - }) - - describe('#getMempool', () => { - it('should throw 400 if address is empty', async () => { - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw 400 on array input', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'address can not be an array') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'Expect 400 status code') - - assert.property(result, 'error') - assert.include(result.error, 'Invalid network', 'Proper error message') - - assert.property(result, 'success') - assert.equal(result.success, false) - }) - - it('should pass errors from electrum-cash to user', async () => { - // Address has invalid checksum. - req.params.address = - 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2' - - // Call the details API. - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'Unsupported address format') - }) - - it('should get mempool for a single address', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // Mock unit tests to prevent live network calls. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_mempoolFromElectrumx') - .resolves(mockData.mempool) - } else { - // Skip this test for integrations. Unconfirmed UTXOs are transient and - // not easy to test in real-time. - assert.equal(true, true) - return - } - - // Call the details API. - const result = await electrumxRoute.getMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'fee') - }) - }) - - describe('#mempoolBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await electrumxRoute.mempoolBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await electrumxRoute.mempoolBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should get mempool details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_mempoolFromElectrumx') - .resolves(mockData.mempool) - } else { - // Skip this test for integrations. Unconfirmed UTXOs are transient and - // not easy to test in real-time. - assert.equal(true, true) - return - } - - // Call the details API. - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'address') - assert.property(result.utxos[0], 'utxos') - - assert.isArray(result.utxos[0].utxos) - assert.property(result.utxos[0].utxos[0], 'height') - assert.property(result.utxos[0].utxos[0], 'tx_hash') - assert.property(result.utxos[0].utxos[0], 'fee') - }) - - it('should get mempool for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - electrumxRoute.isReady = true // Force flag. - - sandbox - .stub(electrumxRoute, '_mempoolFromElectrumx') - .resolves(mockData.mempool) - } else { - // Skip this test for integrations. Unconfirmed UTXOs are transient and - // not easy to test in real-time. - assert.equal(true, true) - return - } - - // Call the details API. - const result = await electrumxRoute.mempoolBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.isArray(result.utxos) - assert.isArray(result.utxos[0].utxos) - assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs') - }) - }) -}) diff --git a/test/v3/blockbook.js b/test/v3/blockbook.js deleted file mode 100644 index af7d338..0000000 --- a/test/v3/blockbook.js +++ /dev/null @@ -1,1122 +0,0 @@ -/* - TESTS FOR THE BLOCKBOOK.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - To-Do: -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert - -const sinon = require('sinon') - -// Used during transition from integration to unit tests. -// let originalUrl -const originalUrl = process.env.BLOCKBOOK_URL - -// Set default environment variables for unit tests. -if (!process.env.TEST) process.env.TEST = 'unit' -if (process.env.TEST === 'unit') { - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' -} - -// Only load blockbook library after setting BLOCKBOOK_URL env var. -const BlockbookRoute = require('../../src/routes/v3/blockbook') -const blockbookRoute = new BlockbookRoute() - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/blockbook-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#Blockbook Router', () => { - let req, res - let sandbox - before(() => { - // console.log(`Testing type is: ${process.env.TEST}`) - - if (!process.env.NETWORK) process.env.NETWORK = 'testnet' - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - process.env.BLOCKBOOK_URL = originalUrl - }) - - describe('#root', () => { - // root route handler. - const root = blockbookRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'address', 'Returns static string') - }) - }) - - describe('#Balance Single', () => { - // details route handler. - // const balanceSingle = blockbookRoute.balanceSingle - - it('should throw 400 if address is empty', async () => { - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should error on an array', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await blockbookRoute.balanceSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await blockbookRoute.balanceSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await blockbookRoute.balanceSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.params.address = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await blockbookRoute.balanceSingle(req, res) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - - it('returns proper error when downstream service stalls', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get balance for a single address', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // console.log(`process.env.BLOCKBOOK_URL: ${process.env.BLOCKBOOK_URL}`) - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockBalance - }) - } - - // Call the details API. - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'page', - 'totalPages', - 'itemsOnPage', - 'address', - 'balance', - 'totalReceived', - 'totalSent', - 'unconfirmedBalance', - 'unconfirmedTxs', - 'txs', - 'txids' - ]) - assert.isArray(result.txids) - }) - }) - - describe('#Balance Bulk', () => { - // details route handler. - const balanceBulk = blockbookRoute.balanceBulk - - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await balanceBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await balanceBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.body = { - addresses: ['bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3'] - } - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await balanceBulk(req, res) - // console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.isAbove(res.statusCode, 499, 'HTTP status code 500 expected.') - // assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockBalance - }) - } - - // Call the details API. - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'page', - 'totalPages', - 'itemsOnPage', - 'address', - 'balance', - 'totalReceived', - 'totalSent', - 'unconfirmedBalance', - 'unconfirmedTxs', - 'txs', - 'txids' - ]) - assert.isArray(result[0].txids) - }) - - it('should get details for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockBalance - }) - } - - // Call the details API. - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#UTXOs Single', () => { - // details route handler. - const utxosSingle = blockbookRoute.utxosSingle - - it('should throw 400 if address is empty', async () => { - const result = await utxosSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should error on an array', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await utxosSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await utxosSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await utxosSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.params.address = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await utxosSingle(req, res) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await utxosSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await utxosSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get utxos for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockUtxos - }) - } - - // Call the details API. - const result = await utxosSingle(req, res) - // console.log(`result utxosSingle: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'vout', - 'value', - 'height', - 'confirmations' - ]) - }) - }) - - describe('#UTXO Bulk', () => { - // details route handler. - const utxosBulk = blockbookRoute.utxosBulk - - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await utxosBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await utxosBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await utxosBulk(req, res) - // console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.isAbove(res.statusCode, 499, 'HTTP status code 500 expected.') - // assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - - it('returns proper error when downstream service stalls', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockUtxos - }) - } - - // Call the details API. - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.isArray(result[0]) - assert.hasAnyKeys(result[0][0], [ - 'txid', - 'vout', - 'value', - 'height', - 'confirmations' - ]) - }) - - it('should get details for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockUtxos - }) - } - - // Call the details API. - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.isArray(result[0]) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#txSingle', () => { - // route handler - const txSingle = blockbookRoute.txSingle - - it('should throw 400 if txid is empty', async () => { - const result = await txSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should error on an array', async () => { - req.params.txid = [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - - const result = await txSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txid can not be an array', - 'Proper error message' - ) - }) - - it('should throw 400 if txid is not a valid txid', async () => { - req.params.txid = 'abc' - - const result = await txSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid must be of length 64') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await txSingle(req, res) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await txSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await txSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get tx details for a single txid', async () => { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockTx - }) - } - - // process.env.BLOCKBOOK_URL = `https://157.230.178.198:19131/` - // process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0 - - // Call the details API. - const result = await txSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result, [ - 'txid', - 'version', - 'vin', - 'vout', - 'blockHash', - 'blockHeight', - 'confirmations', - 'blockTime', - 'value', - 'valueIn', - 'fees', - 'hex' - ]) - - // Vin - assert.isArray(result.vin) - assert.hasAnyKeys(result.vin[0], [ - 'txid', - 'sequence', - 'n', - 'addresses', - 'value', - 'hex' - ]) - - // Vout - assert.isArray(result.vout) - assert.hasAnyKeys(result.vout[0], [ - 'value', - 'n', - 'spent', - 'hex', - 'addresses' - ]) - }) - }) - - describe('#txBulk', () => { - // route handler - const txBulk = blockbookRoute.txBulk - - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids need to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body.txids = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - const result = await txBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids need to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - txids: [ - '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - 'abc' - ] - } - - const result = await txBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txid must be of length 64', - 'Proper error message' - ) - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.body = { - txids: [ - '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392' - ] - } - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await txBulk(req, res) - // console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.isAbove(res.statusCode, 499, 'HTTP status code 500 expected.') - // assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await txBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await txBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get details for a single txid', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockTx - }) - } - - // Call the details API. - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'version', - 'vin', - 'vout', - 'blockHash', - 'blockHeight', - 'confirmations', - 'blockTime', - 'value', - 'valueIn', - 'fees', - 'hex' - ]) - - // Vin - assert.isArray(result[0].vin) - assert.hasAnyKeys(result[0].vin[0], [ - 'txid', - 'sequence', - 'n', - 'addresses', - 'value', - 'hex' - ]) - - // Vout - assert.isArray(result[0].vout) - assert.hasAnyKeys(result[0].vout[0], [ - 'value', - 'n', - 'spent', - 'hex', - 'addresses' - ]) - }) - - it('should get details for multiple txid', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d', - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockTx - }) - } - - // Call the details API. - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) -}) diff --git a/test/v3/blockchain.js b/test/v3/blockchain.js deleted file mode 100644 index 548381e..0000000 --- a/test/v3/blockchain.js +++ /dev/null @@ -1,1826 +0,0 @@ -/* - TODO: - -getRawMempool - --Add tests for 'verbose' input values - -getMempoolEntry & getMempoolEntryBulk - --Needs e2e test to create unconfirmed tx, for real-world test. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const sinon = require('sinon') - -const Blockchain = require('../../src/routes/v3/full-node/blockchain') -const uut = new Blockchain() - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -if (!process.env.TEST) process.env.TEST = 'unit' - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/blockchain-mock') - -let originalEnvVars // Used during transition from integration to unit tests. - -describe('#BlockchainRouter', () => { - let req, res - let sandbox - - // local node will be started in regtest mode on the port 48332 - // before(panda.runLocalNode) - - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // otherwise the panda will run forever - // process.exit() - - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', () => { - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - - assert.equal(result.status, 'blockchain', 'Returns static string') - }) - }) - - describe('getBestBlockHash()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getBestBlockHash', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHash } }) - } - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isString(result) - assert.equal(result.length, 64, 'Hash string is fixed length') - }) - }) - - describe('getBlockchainInfo()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBlockchainInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBestBlockHash(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getBlockchainInfo', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockchainInfo } }) - } - - const result = await uut.getBlockchainInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'chain', - 'blocks', - 'headers', - 'bestblockhash', - 'difficulty', - 'mediantime', - 'verificationprogress', - 'chainwork', - 'pruned', - 'softforks', - 'bip9_softforks' - ]) - }) - }) - - describe('getBlockCount()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getBlockCount', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: 126769 } }) - } - - const result = await uut.getBlockCount(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result) - }) - }) - - describe('getBlockHeaderSingle()', async () => { - it('should throw 400 error if hash is missing', async () => { - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hash can not be empty') - }) - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hash = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Network error: Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.hash = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.hash = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should GET block header', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { - result: - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - } - }) - } - - req.params.hash = - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isString(result) - assert.equal( - result, - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - ) - }) - - it('should GET verbose block header', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeader } }) - } - - req.query.verbose = true - req.params.hash = - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - - const result = await uut.getBlockHeaderSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, [ - 'hash', - 'confirmations', - 'height', - 'version', - 'versionHex', - 'merkleroot', - 'time', - 'mediantime', - 'nonce', - 'bits', - 'difficulty', - 'chainwork', - 'previousblockhash', - 'nextblockhash', - 'nTx' - ]) - }) - }) - - // - - describe('#getBlockHeaderBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.getBlockHeaderBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hashes needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single hash', async () => { - req.body.hashes = - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - - const result = await uut.getBlockHeaderBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hashes needs to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.hashes = testArray - - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw a 400 error for an invalid hash', async () => { - req.body.hashes = ['badHash'] - - await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.body.hashes = [ - '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900' - ] - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getBlockHeaderBulk(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get concise block header for a single hash', async () => { - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeaderConcise } }) - } - - // Call the details API. - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.isArray(result) - assert.equal( - result[0], - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - ) - }) - - it('should get verbose block header for a single hash', async () => { - req.body = { - hashes: [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ], - verbose: true - } - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeader } }) - } - - // Call the details API. - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.isArray(result) - assert.hasAllKeys(result[0], [ - 'hash', - 'confirmations', - 'height', - 'version', - 'versionHex', - 'merkleroot', - 'time', - 'mediantime', - 'nonce', - 'bits', - 'difficulty', - 'chainwork', - 'previousblockhash', - 'nextblockhash', - 'nTx' - ]) - }) - - it('should get details for multiple block heights', async () => { - req.body.hashes = [ - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0', - '000000000000000002fa9d7851b284c53a5831651b04211c266badf2ad2d8ef0' - ] - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockBlockHeaderConcise } }) - } - - // Call the details API. - const result = await uut.getBlockHeaderBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) - describe('getChainTips()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getChainTips', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockChainTips } }) - } - - const result = await uut.getChainTips(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], ['height', 'hash', 'branchlen', 'status']) - }) - }) - describe('getDifficulty()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getDifficulty', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: 4049809.205246544 } }) - } - - const result = await uut.getDifficulty(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result) - }) - }) - describe('getMempoolInfo()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getMempoolInfo', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockMempoolInfo } }) - } - - const result = await uut.getMempoolInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'result', - 'bytes', - 'usage', - 'maxmempool', - 'mempoolminfree' - ]) - }) - }) - - describe('getRawMempool()', () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getRawMempool', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawMempool } }) - } - - const result = await uut.getRawMempool(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - // Not sure what other assertions should be made here. - }) - }) - - describe('getMempoolEntrySingle()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getMempoolEntry', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { result: { error: 'Transaction not in mempool' } } - }) - } - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntrySingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.isString(result.error) - assert.equal(result.error, 'Transaction not in mempool') - }) - }) - describe('#getMempoolEntryBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.getMempoolEntryBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single txid', async () => { - req.body.txids = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolEntryBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - // Only execute on integration tests. - if (process.env.TEST !== 'unit') { - // Dev-note: This test passes because it expects an error. TXIDs do not - // stay in the mempool for long, so it does not work well for a unit or - // integration test. - it('should retrieve single mempool entry', async () => { - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.isString(result.error) - assert.equal(result.error, 'Transaction not in mempool') - }) - - // Dev-note: This test passes because it expects an error. TXIDs do not - // stay in the mempool for long, so it does not work well for a unit or - // integration test. - it('should retrieve multiple mempool entries', async () => { - req.body.txids = [ - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde', - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - ] - - const result = await uut.getMempoolEntryBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.isString(result.error) - assert.equal(result.error, 'Transaction not in mempool') - }) - } - }) - describe('getMempoolAncestorsSingle()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should GET /getMempoolAncestorsSingle', async () => { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockAncestors } }) - - req.params.txid = - 'bb0d349892d351da2767f8c45f6f7949713ff09bd12838d53e76158ddee3ce93' - - const result = await uut.getMempoolAncestorsSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - }) - }) - - describe('getTxOut()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getTxOut(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 if n is empty', async () => { - req.params.txid = 'sometxid' - const result = await uut.getTxOut(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'n can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - req.params.n = 0 - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - // This test can only run for unit tests. See TODO at the top of this file. - it('should GET /getTxOut', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOut } }) - } - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOut(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, [ - 'bestblock', - 'confirmations', - 'value', - 'scriptPubKey', - 'coinbase' - ]) - assert.hasAllKeys(result.scriptPubKey, [ - 'asm', - 'hex', - 'reqSigs', - 'type', - 'addresses' - ]) - assert.isArray(result.scriptPubKey.addresses) - }) - }) - - describe('getTxOutPost()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 if n is empty', async () => { - req.body.txid = 'sometxid' - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'vout can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - req.body.vout = 0 - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.body.vout = 0 - req.body.mempool = true - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.body.vout = 0 - req.body.mempool = true - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should POST /getTxOut', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOut } }) - } - - req.body.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.body.vout = 0 - req.body.mempool = true - - const result = await uut.getTxOutPost(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAllKeys(result, [ - 'bestblock', - 'confirmations', - 'value', - 'scriptPubKey', - 'coinbase' - ]) - assert.hasAllKeys(result.scriptPubKey, [ - 'asm', - 'hex', - 'reqSigs', - 'type', - 'addresses' - ]) - assert.isArray(result.scriptPubKey.addresses) - }) - }) - - describe('getTxOutProofSingle()', () => { - it('should throw 400 if txid is empty', async () => { - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d' - req.params.n = 0 - req.query.include_mempool = 'true' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET /getTxOutProof', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOutProof } }) - } - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - const result = await uut.getTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isString(result) - }) - }) - - describe('#getTxOutProofBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.getTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single txid', async () => { - req.body.txids = - 'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde' - - const result = await uut.getTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids needs to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET proof for single txid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOutProof } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - }) - - it('should GET proof for multiple txids', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockTxOutProof } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266', - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.equal(result.length, 2, 'Correct length of returned array') - }) - }) - describe('verifyTxOutProofSingle()', () => { - it('should throw 400 if proof is empty', async () => { - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'proof can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.proof = mockData.mockTxOutProof - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.proof = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.proof = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should GET /verifyTxOutProof', async () => { - const expected = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: [expected] } }) - } - - req.params.proof = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - - const result = await uut.verifyTxOutProofSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - assert.equal(result[0], expected) - }) - }) - describe('#verifyTxOutProofBulk', () => { - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await uut.verifyTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'proofs needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single txid', async () => { - req.body.proofs = mockData.mockTxOutProof - - const result = await uut.verifyTxOutProofBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'proofs needs to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.proofs = testArray - - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get single proof', async () => { - const expected = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: [expected] } }) - } - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - assert.equal(result[0], expected) - }) - - it('should get multiple proofs', async () => { - const expected = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: [expected] } }) - } - - req.body.proofs = [ - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700', - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c1e000000067139f230701a2819a76795564bd2f67ded7eeae68596f368eddb3dd5bc54e59320e896f71d61cfc3ae3d4a90fca08b1aa35ba91256d8939d2cad11e638c0081f66724abdef55cf7b8b9fed064bce0369171434f8b289c1330ccef765f8e97a2c0d794d81aafb535855f7daa6bb51e40f77c6b59d7af7f62d0eb726a4fc4df82353d56fcbda7c7ea6bd935d61af8fb3b295637e6f323b10231135b3f10a034cfb238f635830c0595e52c6c31247cf677b555f7a287076e20cd0e1d3cc9af7260f02b700' - ] - - const result = await uut.verifyTxOutProofBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isString(result[0]) - assert.equal(result[0], expected) - assert.equal(result.length, 2) - }) - }) -}) diff --git a/test/v3/control.js b/test/v3/control.js deleted file mode 100644 index e37fcbc..0000000 --- a/test/v3/control.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - TESTS FOR THE CONTROL.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const ControlRoute = require('../../src/routes/v3/full-node/control') -const uut = new ControlRoute() -const sinon = require('sinon') - -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/control-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#ControlRouter', () => { - let req, res - let sandbox - - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - // root route handler. - // const root = controlRoute.testableComponents.root - - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'control', 'Returns static string') - }) - }) - - describe('#GetNetworkInfo', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - await uut.getNetworkInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error, "ENOTFOUND", "Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getNetworkInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getNetworkInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get info on the full node', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockGetNetworkInfo } }) - } - - const result = await uut.getNetworkInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'version', - 'subversion', - 'protocolversion', - 'localservices', - 'localrelay', - 'timeoffset', - 'networkactive', - 'connections', - 'networks', - 'relayfee', - 'excessutxocharge', - 'localaddresses', - 'warnings' - ]) - }) - }) -}) diff --git a/test/v3/e2e/address.js b/test/v3/e2e/address.js deleted file mode 100644 index 8656761..0000000 --- a/test/v3/e2e/address.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - End-to-end tests for the Address endpoint. - These tests assume that the repo is running locally and pointed at TESTNET -*/ - -'use strict' - -const rp = require('request-promise') - -// const rawtransactions = require('../../../dist/routes/v2/address') - -/* - This unconfirmed utxo test needs to be expanded in the future to automatically - create a transaction. For now, that is done manually and the address provided - as a constant. - - 1. Start rest running locally, pointed at TESTNET. - 2. Fund each address with a small amount of tBCH - 3. Run this program with `node address.js` - 4. If successful, it will return the unconfirmed UTXOs. -*/ -const addr1 = 'bchtest:qpdcp5pv7qphu5tsjfgwezld9n9aq9ue6swgqpf45c' -const addr2 = 'bchtest:qzpvx999fagau0xqmvu3xvll9evapge7u5hcgeknzv' - -async function testSingleUnconfirmed () { - try { - const options = { - method: 'GET', - uri: `http://localhost:3000/v2/address/unconfirmed/${addr1}`, - resolveWithFullResponse: true, - json: true - } - - await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 2)}`) - } catch (err) { - // console.log(`Error: `, err) - } -} -testSingleUnconfirmed() - -async function testDoubleUnconfirmed () { - try { - const options = { - method: 'POST', - uri: 'http://localhost:3000/v2/address/unconfirmed', - resolveWithFullResponse: true, - json: true, - body: { - addresses: [addr1, addr2] - } - } - - await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 2)}`) - } catch (err) { - // console.log(`Error: `, err) - } -} -testDoubleUnconfirmed() diff --git a/test/v3/encryption.js b/test/v3/encryption.js deleted file mode 100644 index 66b8270..0000000 --- a/test/v3/encryption.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - TESTS FOR THE ENCRYPTION.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - To-Do: -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert - -const sinon = require('sinon') - -// Set default environment variables for unit tests. -if (!process.env.TEST) process.env.TEST = 'unit' - -// Only load blockbook library after setting BLOCKBOOK_URL env var. -const EncryptionRoute = require('../../src/routes/v3/encryption') -const encryptionRoute = new EncryptionRoute() - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/encryption-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#Encryption Router', () => { - let req, res - let sandbox - before(() => { - // console.log(`Testing type is: ${process.env.TEST}`) - - if (!process.env.NETWORK) process.env.NETWORK = 'testnet' - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => {}) - - describe('#root', () => { - // root route handler. - const root = encryptionRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'encryption', 'Returns static string') - }) - }) - - describe('#getPublicKey', () => { - it('should get public key from blockchain', async () => { - req.params.address = - 'bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(encryptionRoute.bchjs.Electrumx, 'transactions') - .resolves(mockData.mockFulcrumTxHistory) - sandbox - .stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction') - .resolves(mockData.mockTxDetails2) - } - - const result = await encryptionRoute.getPublicKey(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'publicKey') - assert.equal( - result.publicKey, - '044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c' - ) - }) - - it('should return false for address with no tx history', async () => { - req.params.address = - 'bitcoincash:qqwfpk04ecf69wuprj9yjys9rla5mk7rj5j8uthqel' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(encryptionRoute.bchjs.Electrumx, 'transactions') - .resolves(mockData.mockFulcrumNoTxHistory) - } - - const result = await encryptionRoute.getPublicKey(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'error') - assert.include(result.error, 'No transaction history') - }) - - it('should return false for address with no send history', async () => { - req.params.address = - 'bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(encryptionRoute.bchjs.Electrumx, 'transactions') - .resolves(mockData.mockFulcrumNoSendBalance) - sandbox - .stub(encryptionRoute.bchjs.RawTransactions, 'getRawTransaction') - .resolves(mockData.mockNoSendTx) - } - - const result = await encryptionRoute.getPublicKey(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, false) - - assert.property(result, 'publicKey') - assert.include(result.publicKey, 'not found') - }) - }) -}) diff --git a/test/v3/helpers/panda.js b/test/v3/helpers/panda.js deleted file mode 100644 index 40c584f..0000000 --- a/test/v3/helpers/panda.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -/** - * Read more about panda here: https://panda-suite.github.io/ - */ -const panda = require('pandacash-core') - -const runLocalNode = done => { - const server = panda.server({ - // always the same mnemonic - // mnemonic: "cigar magnet ocean purchase travel damp snack alone theme budget wagon wrong", - seedAccounts: true, - enableLogs: false, - debug: false - }) - - server.listen({ - port: 48332, - walletPort: 48333 - }, (err, pandaCashCore) => { - if (err) return console.error(err) - - done() - }) -} - -module.exports = { - runLocalNode -} diff --git a/test/v3/integration/README.md b/test/v3/integration/README.md deleted file mode 100644 index 3bd22f8..0000000 --- a/test/v3/integration/README.md +++ /dev/null @@ -1,7 +0,0 @@ -This directory contains integration tests. To run them requires that -a local copy of rest.bitcoin.com is running. The tests in this directory are -exactly the same calls that end users would use for their app to interact -directly with rest.bitcoin.com. - -At present, this directory focuses on raw-transations. But more integration tests -will be added. diff --git a/test/v3/integration/price.js b/test/v3/integration/price.js deleted file mode 100644 index f15cd36..0000000 --- a/test/v3/integration/price.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - Integration tests for the price library. - */ - -'use strict' - -const assert = require('chai').assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -const Price = require('../../../src/routes/v3/price') -const price = new Price() - -const { mockReq, mockRes } = require('../mocks/express-mocks') - -describe('#price', () => { - let req, res - - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - }) - - describe('#getUSD', () => { - it('should get the USD price', async () => { - const result = await price.getUSD(req, res) - console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) - - describe('#getBCHAUSD', () => { - it('should get the USD price of BCHA', async () => { - const result = await price.getBCHAUSD(req, res) - console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) -}) diff --git a/test/v3/integration/rate-limits.js b/test/v3/integration/rate-limits.js deleted file mode 100644 index f1cfbd8..0000000 --- a/test/v3/integration/rate-limits.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - These tests have been deprecated. To test bch-api rate limits, run the e2e - tests in the bch-js repository. - */ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const SERVER = `http://192.168.0.36:12400/v3/` -const SERVER = 'http://localhost:3000/v3/' -// const SERVER = 'https://api.fullstack.cash/v3/' -// -// const TEST_JWT = -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMDYyODk1MSwiZXhwIjoxNjAzMjIwOTUxfQ.JPXDJQsxJFtCGZjHOd-hRfJuY41Ef_FQ4ET06CtYdNk' - -describe('#JWT rate limits', () => { - it('should get control/getNetworkInfo() with no auth', async () => { - const options = { - method: 'GET', - url: `${SERVER}control/getNetworkInfo` - } - - const result = await axios(options) - // console.log(`result.status: ${result.status}`) - // console.log(`result.data: ${util.inspect(result.data)}`) - - assert.equal(result.status, 200) - assert.hasAnyKeys(result.data, ['version']) - }) - - it('should trigger rate-limit handler if rate limits exceeds 20 request per minute', async () => { - try { - const options = { - method: 'GET', - url: `${SERVER}control/getNetworkInfo` - } - - const promises = [] - for (let i = 0; i < 30; i++) { - const promise = axios(options) - promises.push(promise) - } - - await Promise.all(promises) - - assert.fail('Unexpected result!') - } catch (err) { - console.log('err: ', err) - - assert.equal(err.response.status, 429) - assert.include(err.response.data.error, 'Too many requests') - } - }) - - // it('should not trigger rate-limit handler if correct pro-tier password is used', async () => { - // try { - // const username = 'BITBOX' - // - // // Pro-tier is accessed by using the right password. - // const password = 'BITBOX' - // // const password = "something" - // - // const combined = `${username}:${password}` - // const base64Credential = Buffer.from(combined).toString('base64') - // const readyCredential = `Basic ${base64Credential}` - // - // const options = { - // method: 'GET', - // url: `${SERVER}control/getNetworkInfo`, - // headers: { Authorization: readyCredential } - // } - // - // const promises = [] - // for (let i = 0; i < 30; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, true, 'Not throwing an error is a pass!') - // } catch (err) { - // // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal( - // true, - // false, - // 'This error handler should not have been triggered. Is the password correct?' - // ) - // } - // }) - - // it('should trigger rate-limit handler if rate limits exceeds pro-tier limit', async () => { - // try { - // const username = 'BITBOX' - // - // // Pro-tier is accessed by using the right password. - // const password = 'BITBOX' - // // const password = "something" - // - // const combined = `${username}:${password}` - // const base64Credential = Buffer.from(combined).toString('base64') - // const readyCredential = `Basic ${base64Credential}` - // - // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - // const options = { - // method: 'GET', - // url: `${SERVER}control/getNetworkInfo`, - // headers: { Authorization: readyCredential } - // } - // - // const promises = [] - // for (let i = 0; i < 80; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // assert.equal(true, false, 'Unexpected result!') - // } catch (err) { - // // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal(err.response.status, 429) - // assert.include(err.response.data.error, 'Too many requests') - // } - // }) - - // it('should unlock pro-tier for a valid JWT token', async () => { - // try { - // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - // const options = { - // method: 'GET', - // url: `${SERVER}control/`, - // headers: { - // Authorization: `Token ${TEST_JWT}` - // } - // } - // - // const promises = [] - // for (let i = 0; i < 60; i++) { - // const promise = axios(options) - // promises.push(promise) - // } - // - // await Promise.all(promises) - // - // // assert.equal(true, false, "Unexpected result!") - // assert.equal(true, true, 'Not throwing an error is a pass!') - // } catch (err) { - // console.log(`err.response: ${util.inspect(err.response)}`) - // - // assert.equal(true, false, 'Unexpected result!') - // } - // // Override default timeout for this test. - // }).timeout(20000) -}) diff --git a/test/v3/integration/raw-transactions.js b/test/v3/integration/raw-transactions.js deleted file mode 100644 index 6668957..0000000 --- a/test/v3/integration/raw-transactions.js +++ /dev/null @@ -1,191 +0,0 @@ -/* - These integration tests are intended to be run against a live local copy of - rest.bitcoin.com. They exercise the endpoints in the same way the SDK or - end-user application would. These tests were created to replace the parts - removed from the swagger UI, that otherwise would have excersiced these endpoints. - - TODO: - -/rawtransactions/sendRawTransaction is more appropropriate for an e2e test, - so it is omitted here. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -// const rawtransactions = require('../../../dist/routes/v2/rawtransactions') -const rp = require('request-promise') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const mockData = require('../mocks/raw-transactions-mocks') - -describe('#Raw-Transactions', () => { - describe('#root', () => { - it('should return root', async () => { - const options = { - method: 'GET', - uri: 'http://localhost:3000/v2/rawtransactions/', - resolveWithFullResponse: true, - json: true - } - - const result = await rp(options) - // console.log(`result: ${JSON.stringify(result, null, 0)}`) - - assert.equal(result.body.status, 'rawtransactions') - }) - }) - - describe('#whCreateTx', () => { - it('should return tx hex', async () => { - const minIn = { - txid: - 'f7ed9cf23dee85910f6269c9a101a75fcfd2f3c6fc81f17fad824ff7aaf99ab2', - vout: 1 - } - - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/create', - resolveWithFullResponse: true, - json: true, - body: { - // inputs: [mockData.mockWHCreateInput], - inputs: [minIn], - outputs: {} - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.equal( - result.body, - '0200000001b29af9aaf74f82ad7ff181fcc6f3d2cf5fa701a1c969620f9185ee3df29cedf70100000000ffffffff0000000000' - ) - }) - }) - - describe('#whOpReturn', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/opreturn', - resolveWithFullResponse: true, - json: true, - body: { - rawtx: '01000000000000000000', - payload: '00000000000000020000000006dac2c0' - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.equal( - result.body, - '0100000000010000000000000000166a140877686300000000000000020000000006dac2c000000000' - ) - }) - }) - - describe('#whReference', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/reference', - resolveWithFullResponse: true, - json: true, - body: { - rawtx: - '0100000001a7a9402ecd77f3c9f745793c9ec805bfa2e14b89877581c734c774864247e6f50400000000ffffffff03aa0a0000000000001976a9146d18edfe073d53f84dd491dae1379f8fb0dfe5d488ac5c0d0000000000004751210252ce4bdd3ce38b4ebbc5a6e1343608230da508ff12d23d85b58c964204c4cef3210294cc195fc096f87d0f813a337ae7e5f961b1c8a18f1f8604a909b3a5121f065b52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000', - destination: 'bitcoincash:qrn60nerx5zug4u4hal06atep3lzhtecvy4pxk75lf', - amount: 0.005 - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.isString(result.body) - }) - }) - - describe('#whChangeOutput', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/change', - resolveWithFullResponse: true, - json: true, - body: { - rawtx: - '0100000001b15ee60431ef57ec682790dec5a3c0d83a0c360633ea8308fbf6d5fc10a779670400000000ffffffff025c0d00000000000047512102f3e471222bb57a7d416c82bf81c627bfcd2bdc47f36e763ae69935bba4601ece21021580b888ff56feb27f17f08802ebed26258c23697d6a462d43fc13b565fda2dd52aeaa0a0000000000001976a914946cb2e08075bcbaf157e47bcb67eb2b2339d24288ac00000000', - destination: 'bitcoincash:qrn60nerx5zug4u4hal06atep3lzhtecvy4pxk75lf', - prevtxs: [ - { - txid: - '6779a710fcd5f6fb0883ea3306360c3ad8c0a3c5de902768ec57ef3104e65eb1', - vout: 4, - scriptPubKey: - '76a9147b25205fd98d462880a3e5b0541235831ae959e588ac', - value: 0.00068257 - } - ], - fee: 0.000035 - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.isString(result.body) - }) - }) - - describe('#whInput', () => { - it('should return tx hex', async () => { - const options = { - method: 'PUT', - uri: 'http://localhost:3000/v2/rawtransactions/input', - resolveWithFullResponse: true, - json: true, - body: { - txid: - 'b006729017df05eda586df9ad3f8ccfee5be340aadf88155b784d1fc0e8342ee', - n: 0 - } - } - - const result = await rp(options) - // console.log(`result.body: ${JSON.stringify(result.body, null, 0)}`) - - assert.isString(result.body) - }) - }) - - describe('#getRawTransaction', () => { - it('should return tx hex', async () => { - const options = { - method: 'POST', - uri: 'http://localhost:3000/v2/rawtransactions/getRawTransaction', - resolveWithFullResponse: true, - json: true, - body: { - txids: [ - '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098' - ], - verbose: true - } - } - - const result = await rp(options) - // console.log(`result.body: ${util.inspect(result.body)}`) - - assert.isArray(result.body) - }) - }) -}) diff --git a/test/v3/integration/slp.js b/test/v3/integration/slp.js deleted file mode 100644 index cf8b9c5..0000000 --- a/test/v3/integration/slp.js +++ /dev/null @@ -1,261 +0,0 @@ -/* - These integration tests need to be run against a live SLPDB. They query - against a live SLPDB and test the results against known token stats. - */ - -'use strict' - -const assert = require('chai').assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// Exit if SLPDB URL is not defined. -if (!process.env.SLPDB_URL) { - throw new Error( - 'SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.' - ) -} - -const SLP = require('../../../src/routes/v3/slp') -const slp = new SLP() - -const BCHJS = require('@psf/bch-js') -const bchjs = new BCHJS() - -const { mockReq, mockRes } = require('../mocks/express-mocks') - -describe('#slp', () => { - let req, res - - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - }) - - describe('#tokenStats', () => { - it('should get token stats for token with no mint baton', async () => { - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - // 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' - - const result = await slp.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that expected properties exist. - assert.property(result, 'decimals') - assert.property(result, 'timestamp') - assert.property(result, 'versionType') - assert.property(result, 'documentUri') - assert.property(result, 'symbol') - assert.property(result, 'name') - assert.property(result, 'containsBaton') - assert.property(result, 'id') - assert.property(result, 'documentHash') - assert.property(result, 'initialTokenQty') - assert.property(result, 'blockCreated') - assert.property(result, 'blockLastActiveSend') - assert.property(result, 'blockLastActiveMint') - assert.property(result, 'txnsSinceGenesis') - assert.property(result, 'validAddresses') - assert.property(result, 'mintingBatonStatus') - assert.property(result, 'timestampUnix') - assert.property(result, 'totalMinted') - assert.property(result, 'totalBurned') - assert.property(result, 'circulatingSupply') - - // baton was never created. - assert.equal(result.containsBaton, false) - }) - - it('should get token stats for token with a mint baton', async () => { - req.params.tokenId = - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2' - - const result = await slp.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that expected properties exist. - assert.property(result, 'decimals') - assert.property(result, 'timestamp') - assert.property(result, 'versionType') - assert.property(result, 'documentUri') - assert.property(result, 'symbol') - assert.property(result, 'name') - assert.property(result, 'containsBaton') - assert.property(result, 'id') - assert.property(result, 'documentHash') - assert.property(result, 'initialTokenQty') - assert.property(result, 'blockCreated') - assert.property(result, 'blockLastActiveSend') - assert.property(result, 'blockLastActiveMint') - assert.property(result, 'txnsSinceGenesis') - assert.property(result, 'validAddresses') - assert.property(result, 'mintingBatonStatus') - assert.property(result, 'timestampUnix') - assert.property(result, 'totalMinted') - assert.property(result, 'totalBurned') - assert.property(result, 'circulatingSupply') - - // baton was created. - assert.equal(result.containsBaton, true) - }) - }) - - describe('#generateSendOpReturn', () => { - it('should return OP_RETURN script', async () => { - req.body.tokenUtxos = [ - { - tokenId: - '0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1', - decimals: 0, - tokenQty: 2 - } - ] - req.body.sendQty = 1.5 - - const result = await slp.generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['script', 'outputs']) - assert.isNumber(result.outputs) - }) - }) - - describe('#hydrateUtxos', () => { - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - req.body.utxos = utxos - const result = await slp.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should process data directly from Electrumx', async () => { - const addrs = [ - 'bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg', - 'bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89', - 'bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk' - ] - - const utxos = await bchjs.Electrumx.utxo(addrs) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - req.body.utxos = utxos.utxos - const result = await slp.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 3) - assert.equal(result.slpUtxos[0].utxos.length, 1) - assert.equal(result.slpUtxos[1].utxos.length, 1) - assert.equal(result.slpUtxos[2].utxos.length, 2) - }) - }) - - describe('#validate2Single', () => { - it('should invalidate a known invalid TXID', async () => { - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slp.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - req.params.txid = txid - const result = await slp.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, true) - }) - - // CT 10-11-2020: This test is valid, but because of the cacheing built - // into slp-validate, it will not consistently pass or fail. To manually - // Run this test, re-start slp-api or find a token txid with a long DAG. - // it('should cancel if validation takes too long', async () => { - // const txid = - // 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - // - // req.params.txid = txid - // const result = await slp.validate2Single(req, res) - // // console.log(`result: ${JSON.stringify(result, null, 2)}`) - // - // assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - // assert.include( - // result.error, - // 'Could not communicate with full node', - // 'Error message expected' - // ) - // }) - }) -}) diff --git a/test/v3/integration/slpdb.js b/test/v3/integration/slpdb.js deleted file mode 100644 index 940d092..0000000 --- a/test/v3/integration/slpdb.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - These integration tests need to be run against a live SLPDB. They query - against a live SLPDB and test the results against known token stats. - */ - -'use strict' - -// const chai = require('chai') -// const assert = chai.assert -// const axios = require('axios') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// Exit if SLPDB URL is not defined. -if (!process.env.SLPDB_URL) { - throw new Error('SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.') -} - -const SLPDB = require('../../../src/routes/v3/services/slpdb') -const slpdb = new SLPDB() - -describe('#slpdb', () => { - describe('#getTotalCirculating', () => { - it('should get circulating supply', async () => { - const result = await slpdb.getTotalCirculating('b10677aef051b73e6b170c1c0824da33a3e0680ab5a01cd8d76aa77840fccfb4') - console.log('result: ', result) - }) - }) -}) diff --git a/test/v3/mining.js b/test/v3/mining.js deleted file mode 100644 index 0afc085..0000000 --- a/test/v3/mining.js +++ /dev/null @@ -1,224 +0,0 @@ -/* - TESTS FOR THE MINING.TS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const MiningRoute = require('../../src/routes/v3/full-node/mining') -const uut = new MiningRoute() - -// const nock = require('nock') // HTTP mocking -const sinon = require('sinon') - -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/mining-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#Mining', () => { - let req, res - let sandbox - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - // root route handler. - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'mining', 'Returns static string') - }) - }) - - describe('#getMiningInfo', async () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - await uut.getMiningInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getMiningInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getMiningInfo(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET mining information', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockMiningInfo } }) - } - - const result = await uut.getMiningInfo(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.property(result, 'blocks') - assert.property(result, 'difficulty') - assert.property(result, 'networkhashps') - assert.property(result, 'pooledtx') - assert.property(result, 'chain') - assert.property(result, 'warnings') - }) - }) - - describe('#getNetworkHashPS', async () => { - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - await uut.getNetworkHashPS(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getNetworkHashPS(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getNetworkHashPS(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should GET Network Hash per second', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: 517604755.6648782 } }) - } - - const result = await uut.getNetworkHashPS(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result) - }) - }) -}) diff --git a/test/v3/mocks/address-mock.js b/test/v3/mocks/address-mock.js deleted file mode 100644 index a2666a2..0000000 --- a/test/v3/mocks/address-mock.js +++ /dev/null @@ -1,160 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockAddressDetails = { - addrStr: '1Fg4r9iDrEkCcDmHTy2T79EusNfhyQpu7W', - balance: 0.00126419, - balanceSat: 126419, - totalReceived: 0.02175868, - totalReceivedSat: 2175868, - totalSent: 0.02049449, - totalSentSat: 2049449, - unconfirmedBalance: 0, - unconfirmedBalanceSat: 0, - unconfirmedTxApperances: 0, - txApperances: 3, - transactions: [ - '2dc053f55a666a3d2a08b1c680b704d62a55506d14ad884add87edcc56b9277d', - '544c15ce35c0f2e808d28f29d6587f1ec9276233e29856b7f2938cf0daef0026', - '81039b1d7b855b133f359f9dc65f776bd105650153a941675fedc504228ddbd3' - ], - legacyAddress: '1Fg4r9iDrEkCcDmHTy2T79EusNfhyQpu7W', - cashAddress: 'bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' -} - -const mockUtxoDetails = [ - { - txid: '15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e', - vout: 286, - amount: 0.00001, - satoshis: 1000, - height: 546083, - confirmations: 3490 - }, - { - txid: '15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e', - vout: 287, - amount: 0.00001, - satoshis: 1000, - height: 546083, - confirmations: 3490 - }, - { - txid: '15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e', - vout: 288, - amount: 0.00001, - satoshis: 1000, - height: 546083, - confirmations: 3490 - } -] - -const mockUnconfirmed = [ - { - address: '1EzdL6TBbkNhnB2fYiBaKmcs5fxaoqwdAp', - txid: '000c00a90fb5031da6e02f7625df2f8b35a4c16e6feb9fc72293e67e5ff75786', - vout: 0, - scriptPubKey: '76a914997fabcd94a1e2aaa13a7664362e5e7b96c169a988ac', - amount: 0.00999626, - satoshis: 999626, - confirmations: 0, - ts: 1537989425 - } -] - -const mockTransactions = { - pagesTotal: 1, - txs: [ - { - txid: '000c00a90fb5031da6e02f7625df2f8b35a4c16e6feb9fc72293e67e5ff75786', - version: 2, - locktime: 549565, - vin: [ - { - txid: - '45c891c6d44619fc85716ba1b593aa83ebc1e500fe611b1ab98531ea203a0f21', - vout: 209, - sequence: 4294967294, - n: 0, - scriptSig: { - hex: - '47304402205b136f348bedae61a87c0500979d47ab02c76f0e4aba866b94c4931fccb5d7dc0220757a141660475b0cd4cb8b1f54b1ace95105f5bea775e817b115fb589fe4477541210246daec651c506f353daa7468714399c773df42ac4d663022d0e446a3331ac64a', - asm: - '304402205b136f348bedae61a87c0500979d47ab02c76f0e4aba866b94c4931fccb5d7dc0220757a141660475b0cd4cb8b1f54b1ace95105f5bea775e817b115fb589fe44775[ALL|FORKID] 0246daec651c506f353daa7468714399c773df42ac4d663022d0e446a3331ac64a' - }, - addr: '14TdovsQUL69xL5g3zSzhLpmDb93d9rU9m', - valueSat: 1979534, - value: 0.01979534, - doubleSpentTxID: null - }, - { - txid: - '6e5fa79547112a912adf6082c975dc80e282045193512ea369adb6bac7420674', - vout: 147, - sequence: 4294967294, - n: 1, - scriptSig: { - hex: - '473044022056674ac83a37d54ffec2c6d33cfc2ae2256f821b8bd818e1b6783356b49f3d28022046d3dd147c8fc51f2da5d16996a3cea1d7314e327aa1ae1017418737ed20136a4121038f23af565f68d8455e07e5c33c5cfc5114de2f478f035db23eb02f8ff5fe7f6e', - asm: - '3044022056674ac83a37d54ffec2c6d33cfc2ae2256f821b8bd818e1b6783356b49f3d28022046d3dd147c8fc51f2da5d16996a3cea1d7314e327aa1ae1017418737ed20136a[ALL|FORKID] 038f23af565f68d8455e07e5c33c5cfc5114de2f478f035db23eb02f8ff5fe7f6e' - }, - addr: '1CEtC2fjELvmAzTd6MWuHb1gvuSq8x3Xpb', - valueSat: 10466, - value: 0.00010466, - doubleSpentTxID: null - } - ], - vout: [ - { - value: '0.00999626', - n: 0, - scriptPubKey: { - hex: '76a914997fabcd94a1e2aaa13a7664362e5e7b96c169a988ac', - asm: - 'OP_DUP OP_HASH160 997fabcd94a1e2aaa13a7664362e5e7b96c169a9 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['1EzdL6TBbkNhnB2fYiBaKmcs5fxaoqwdAp'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.00990000', - n: 1, - scriptPubKey: { - hex: '76a914e3335e4d6babe61ea58311293c1c6bfe802801cb88ac', - asm: - 'OP_DUP OP_HASH160 e3335e4d6babe61ea58311293c1c6bfe802801cb OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['1MiKvgaQTFCTEZbSDMZgw9ahaB52Cr4ofb'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - } - ], - blockhash: - '00000000000000000158b1a2883688873ec5ea076e3b0f576bcdfe1fd277880f', - blockheight: 549601, - confirmations: 5, - time: 1537990026, - blocktime: 1537990026, - valueOut: 0.01989626, - size: 372, - valueIn: 0.0199, - fees: 0.00000374 - } - ] -} - -module.exports = { - mockAddressDetails, - mockUtxoDetails, - mockUnconfirmed, - mockTransactions -} diff --git a/test/v3/mocks/bitcore-mock.js b/test/v3/mocks/bitcore-mock.js deleted file mode 100644 index 8a0e32a..0000000 --- a/test/v3/mocks/bitcore-mock.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - This library contains mocking data for running unit tests on the bitcore route. -*/ - -'use strict' - -const mockBalance = { - confirmed: 10000000, - unconfirmed: 0, - balance: 10000000 -} - -const mockUtxos = [ - { - _id: '5cf2c31a33bd46a95ec7e730', - chain: 'BCH', - network: 'testnet', - coinbase: false, - mintIndex: 1, - spentTxid: '', - mintTxid: - '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - mintHeight: 1265275, - spentHeight: -2, - address: 'qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4', - script: '76a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac', - value: 10000000, - confirmations: -1 - } -] - -module.exports = { - mockBalance, - mockUtxos -} diff --git a/test/v3/mocks/block-mock.js b/test/v3/mocks/block-mock.js deleted file mode 100644 index 616ad41..0000000 --- a/test/v3/mocks/block-mock.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockBlockDetails = { - hash: '00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79', - size: 1319, - height: 1267544, - version: 536870912, - merkleroot: - 'c2cd01ec2cf149acc7631385f89ae103d1a2ad212ab810c862d9680a811618ce', - tx: [ - '52bfa89d449fef8070ec33e7a61f5ec8b5417ff62b050cd6a144ebce9557e0fa', - '8988e8742d2523f667c2d1374861919e3a82903e059fec75c10795da2303b93b', - '41def004f22b196d675566a6983ecf97611e72c48375b8ba779983d5ccd369d7', - '8ca8efb06abb94395f8331f87e101504dcf66aee828f2111922f056971fa7502', - '9ab409cbb203be7cf22bd6598d787fcdd5b417d885b72207ecc7253470770649' - ], - time: 1542048973, - nonce: 3936284753, - bits: '1a03c148', - difficulty: 4467892.99177529, - chainwork: '00000000000000000000000000000000000000000000003ee27b503a064045bd', - confirmations: 3, - previousblockhash: - '00000000000001891202cbe18729a02a8763cf04da0ca7aded6e6c7d9b500785', - nextblockhash: - '000000000000039b93b3f3403a0eb21150904b15229d998cf3788fd8bf192cc2', - reward: 0.78125, - isMainChain: true, - poolInfo: {} -} - -const mockBlockHash = - '00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79' - -module.exports = { - mockBlockDetails, - mockBlockHash -} diff --git a/test/v3/mocks/blockbook-mock.js b/test/v3/mocks/blockbook-mock.js deleted file mode 100644 index 05edf53..0000000 --- a/test/v3/mocks/blockbook-mock.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - This library contains mocking data for running unit tests on the blockbook route. -*/ - -'use strict' - -const mockBalance = { - page: 1, - totalPages: 1, - itemsOnPage: 1000, - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - balance: '10000000', - totalReceived: '10000000', - totalSent: '0', - unconfirmedBalance: '0', - unconfirmedTxs: 0, - txs: 1, - txids: ['5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392'] -} - -const mockUtxos = [ - { - txid: '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - vout: 1, - value: '10000000', - height: 1265275, - confirmations: 42704 - } -] - -const mockTx = { - txid: '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - version: 2, - vin: [ - { - txid: '85ddb8215fc3701a493cf1c450644c5ef32c55aaa2f48ae2d008944394f3e4d3', - sequence: 4294967295, - n: 0, - addresses: ['bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35'], - value: '16983000648', - hex: - '47304402202378e55f4d02bb932498deef22dfc1f7a4984858c3b55017e225dd567172252e0220373d27710b5d42a72ac9725959f1605a912fee0ae86a7ad36e7d6d796f14ca29412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792' - } - ], - vout: [ - { - value: '16973000422', - n: 0, - spent: true, - hex: '76a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac', - addresses: ['bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35'] - }, - { - value: '10000000', - n: 1, - hex: '76a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac', - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - ], - blockHash: '00000000005242edac4635ac2375a454e801cc1be8b131b622328089731e5e30', - blockHeight: 1265275, - confirmations: 65402, - blockTime: 1540912733, - value: '16983000422', - valueIn: '16983000648', - fees: '226', - hex: - '0200000001d3e4f394439408d0e28af4a2aa552cf35e4c6450c4f13c491a70c35f21b8dd85000000006a47304402202378e55f4d02bb932498deef22dfc1f7a4984858c3b55017e225dd567172252e0220373d27710b5d42a72ac9725959f1605a912fee0ae86a7ad36e7d6d796f14ca29412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02e66eabf3030000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a9140e5b4ad9008bb9a027b7e2d0ef958914e12db20788ac00000000' -} - -module.exports = { - mockBalance, - mockUtxos, - mockTx -} diff --git a/test/v3/mocks/blockchain-mock.js b/test/v3/mocks/blockchain-mock.js deleted file mode 100644 index 21a699e..0000000 --- a/test/v3/mocks/blockchain-mock.js +++ /dev/null @@ -1,300 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockBlockHash = - '00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79' - -const mockBlockchainInfo = { - chain: 'test', - blocks: 1267694, - headers: 1267694, - bestblockhash: - '000000000000013eaf80d1e157e32804c36a58ad0bb26ca59833880c80298780', - difficulty: 4105763.969035785, - mediantime: 1542131305, - verificationprogress: 0.9999968911571566, - chainwork: '00000000000000000000000000000000000000000000003f0443b0b5f02ce255', - pruned: false, - softforks: [ - { id: 'bip34', version: 2, reject: { status: true } }, - { id: 'bip66', version: 3, reject: { status: true } }, - { id: 'bip65', version: 4, reject: { status: true } } - ], - bip9_softforks: { - csv: { - status: 'active', - startTime: 1456790400, - timeout: 1493596800, - since: 770112 - } - } -} - -const mockChainTips = [ - { - height: 1267696, - hash: '000000000000035bfc43a642ebbff8cfc1e88b1d564de8b0a6c7e2797eeafb21', - branchlen: 0, - status: 'active' - }, - { - height: 1267581, - hash: '000000000001bd12e4124207682563135b21353ca3087bc6b0c409f7f9e1da91', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1267375, - hash: '00000000702036979df70236bcc45dfc72d43d5d0e6834007afa1fa627e49587', - branchlen: 1036, - status: 'headers-only' - }, - { - height: 1266979, - hash: '00000000e851abbde2174ccdc5c2508b909f81f23c4b7abeac864eee1a4891c7', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266973, - hash: '000000007e1324d2900ed70947f89ab8fd4a267e87c9244c333215659a7370d5', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266967, - hash: '000000000f37f12843d4800f50ec4de44dce0432e4d448242d43ff04a7a2948d', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266963, - hash: '00000000ae11ec99e88898cbd72567cb4cb79e7fc13243357bef79632769deb4', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266962, - hash: '000000004f97851259b4460a049e44fa5bd4beadee04a85bf81bd2e90c4f24f9', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266950, - hash: '00000000d12a0e4a827f73ecaadb563df7e99817294939f2be8e943079dc60b2', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266944, - hash: '00000000612d16af273217818e8e7ac5014a19f68ed04e1bc897b1be0a9c744f', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266941, - hash: '0000000000002b91cdb15cacf3368da2c9ffce511d64092193b507dad75f4a27', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266896, - hash: '000000000002745f67a66617b1f9f65c2dfb4c4b8a9ed9b1320b98d3ca46cbe5', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266481, - hash: '0000000000036babee3ba7654cddb29a9fe5e85c9fbeb44bb53d5ffb694b9670', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266452, - hash: '00000000502c3ab1490e0839780a4b441bbe21507c454545941e6adcde73c2ff', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1266336, - hash: '000000001c9a0b7cd9eb2210f69d5a9a3f4546ba3707deb40993d729190582d7', - branchlen: 10, - status: 'headers-only' - }, - { - height: 1266097, - hash: '000000000028ac0fbd18afe6f001f70cb7549bd2b3082d754dde88ee236368a2', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1265522, - hash: '00000000000000c6df7a2063030e6ce51399e470cc46e4bf6d67aa3f0feca98d', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1265470, - hash: '0000000000168e6442a595a02340fccf8dc2b0e8912c632d39e1f870685f6d7c', - branchlen: 2, - status: 'valid-fork' - }, - { - height: 1265275, - hash: '000000008bc2b37ae6af1b886b52e7e1c1122c12badc4ae6c13df64f789267cf', - branchlen: 11027, - status: 'headers-only' - }, - { - height: 1265257, - hash: '00000000005bc3e9973d7273211eab02ae02549c7ebae48764c0bd648d4d7bbe', - branchlen: 114, - status: 'valid-fork' - }, - { - height: 1265254, - hash: '0000000000e5b909d3541857315e9ee103b042d9ba661f25cc3e8fb25a96c8ce', - branchlen: 111, - status: 'valid-fork' - }, - { - height: 1265245, - hash: '000000000066eb8832f6f990baceb379536f437216e22475a6c1b9133d250cb8', - branchlen: 102, - status: 'valid-fork' - }, - { - height: 1264455, - hash: '00000000000001befc1bb17a23208d0b77b2449e37093ce8fbb5e346b19cafcc', - branchlen: 1, - status: 'valid-headers' - }, - { - height: 1264373, - hash: '00000000000001ed3747e04fb95054cac0358fb4d8009d1999fc3ea34413e84a', - branchlen: 175, - status: 'valid-fork' - }, - { - height: 1263912, - hash: '00000000000004635aecbcb97edcd6f5396483338838cb40da804eb354c68bde', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1262906, - hash: '00000000b31643d92a3e7c38e9755844fc8607e3f1055580a8ff28854e8a8ece', - branchlen: 1, - status: 'valid-fork' - }, - { - height: 1255749, - hash: '000000001dde5b015a99137f4cba87871370f4b6fcfbcc8b126547e6c33177da', - branchlen: 129, - status: 'headers-only' - }, - { - height: 1188789, - hash: '00000000af942ce4eb60b3213cbcb7c98a7330f1f8f1adb4b6376f5e822e15b2', - branchlen: 92, - status: 'headers-only' - } -] - -const mockMempoolInfo = { - size: 87, - bytes: 16816, - usage: 66408, - maxmempool: 300000000, - mempoolminfee: 0 -} - -const mockRawMempool = [ - 'db045bc3bd1088fa91f5ebb05c35cb9e2a91a22377f79b465cc6920b9893123c', - '6b3df7febf2b9834f1409155f88b866dd516b36376eae00e2b455df82e290405' -] - -const mockBlockHeaderConcise = - '000000202ed2723e7590e2b937f6821a99d6764cb8799bf30f8e300000000000000000001d311c02df9a1e3f57b8dbdcf97ec8dbc3109a26779724c63e560b29ad9ea501e2af955d286403183049e39c' - -const mockBlockHeader = { - hash: '00000000000008c3679777df34f1a09565f98b2400a05b7c8da72525fdca3900', - confirmations: 7, - height: 1272859, - version: 2147418112, - versionHex: '7fff0000', - merkleroot: - '846f45604ec6cde2528933305dba80971e9ff0874f74d2811c7c690b2c32706d', - time: 1544207139, - mediantime: 1544202884, - nonce: 641041470, - bits: '1a09faea', - difficulty: 1681035.704111868, - chainwork: '00000000000000000000000000000000000000000000003fc4752e608403be04', - previousblockhash: - '0000000000003e467ebcb7e906645676c2f71b9ac520d6508bea45789b7c217d', - nextblockhash: - '00000000000006899041cdfd6c0b73a97730c362346dde479b77414ad7f25ace', - nTx: 'some value' -} - -const mockTxOut = { - bestblock: '00000000003a7f19730dd9f172d7466658a5c8833dd03ed8f4ba36e3d857b5e7', - confirmations: 10, - value: 0.0001, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 0ee020c07f39526ac5505c54fa1ab98490979b83 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a9140ee020c07f39526ac5505c54fa1ab98490979b8388ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bchtest:qq8wqgxq0uu4y6k92pw9f7s6hxzfp9umsvtg39pzqf'] - }, - coinbase: false -} - -const mockTxOutProof = - '000000200798039affceb7a381e15dc62443c286efe7cae852393b656807000000000000cf9a7d6c654fd1c4558229a619a550ff749b373fa0f0027eed68b1abf6175d5c07c50f5cffff001d0452403b34000000070d0765da76980d542b5670d27ccda9e717073270f8921ad017deb7ee83fe6c4c22830a48cc8392197f1742a3d81805bd2aa4eb7469f38c74f674429d682cb87efcff1713fd0c388a50f0d11adf05363397ef79d16cdb786d47c941c2cb89f2588f42aced9c05f4203b4440616a9e4266ec47909b9580a643f402b5d4e82cc3cddd73df05a33abf6af3975d973d3033d10c4168a73d58d01f685a96d5a2519cd0de9c77faf3f8725ddf155cbdc8ab8102f173e2d0a0d74767f3bff22f588158d6d5bc71c079c7659cc864819c43877635007650502eb4060fcc9feec3280a41d602ad0a' - -const mockAncestors = [ - '070504de968fba8a23a1362fd7fc0546a420663f2bcc18099d894a41ecf9cce5', - '20af6b7fcd21a3f38bb65d32fe2d66fb1ae554ed9c00eaf1b17d7e81362381a9', - '227e08663a812cf44d91106b09fc8493fe1a2b7c146f30ca09ebd86c9b84b2a5', - '228833be79d4a833165ea199be160efcaeb9325e71e9455f424b27abc9688ba8', - '2de27df02001e7243435cef6d41ea2312b94a5964912eb21466b936852c0b822', - '2ef76ae690d6f6c9559e20358a75a0ce02250b9b8e80e0aa6f782576239f292a', - '3e314678f52d52694726e6da88b94c9f0ea086d2f9af297b01d014b62247839f', - '4773f840d5255f16c0e5effce48316d8b8824a1d3ddd5da275c5fd99e7b3ee42', - '4804b97112da07e2c187516a915dd4cbd4a3814f3c900b8d08eb1d508ab0ac47', - '4ca69f31081a256cb4711315336fe4358d8f04b67792ca5ee17b5f4089a2446a', - '6ac17dce10ae3a3d1d8e6f42546ad9a4bb981e62cba8d48bab03e01b9ea38ae3', - '6ce71182ef341c481a90d8b081a99d9788bdd91a917f9a6594fdfad2c2024a14', - '8413343ac973fb0968ae1809faa8f0ddf18252975481daa683e88b04afa66ccd', - '9430300f3d7ecdabe39671b6fd0e9c36f763f01a202e619c070554ddc5e8713e', - '94f82b5a2cd1647d8168807ccfdebe41931ea1ab789a050d615f08000a63b624', - '9848db6a7d2835002ca7e1097a685d75eb629f44df94007674635b45d0e97199', - '98d809993353df6c4d55fda5e7b0ed2e383601cef1180a9aa9c87e67e8f4dd94', - '9f5513a35ae8fad3dd061865a97d8127c914e3464527641c089993c06d092535', - 'a7201ac90abd2aa42112faa809181c45047b6988a2f649a70a1d5bfb95f46884', - 'a9f1284710c655357412a062b01efaeb174820613784ccbfe44b33b86a8bdbc4', - 'b814f44c9c8ee24a218b9f295a8c1831106b70c15ffe8aeb7534e10d99935fd0', - 'e130ccdcc94dc5eaa4ae37fa50e3ee96283b0e157616bc6e28454b64bae34f48', - 'e68dad4a7292105cfa84fcaef7f99e5d4f2ece9613ca625d4d2ebf61efa84118', - 'fe94caf5da672be3772d2304a6272eb8bc3d3f5cb4a886f39b7981e1485cf74b' -] - -module.exports = { - mockBlockHash, - mockBlockchainInfo, - mockChainTips, - mockMempoolInfo, - mockRawMempool, - mockBlockHeaderConcise, - mockBlockHeader, - mockTxOut, - mockTxOutProof, - mockAncestors -} diff --git a/test/v3/mocks/control-mock.js b/test/v3/mocks/control-mock.js deleted file mode 100644 index 7e44cf0..0000000 --- a/test/v3/mocks/control-mock.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockGetInfo = { - version: 170200, - protocolversion: 70015, - walletversion: 160300, - balance: 0, - blocks: 1266726, - timeoffset: 0, - connections: 8, - proxy: '', - difficulty: 1, - testnet: true, - keypoololdest: 1536331195, - keypoolsize: 2000, - paytxfee: 0, - relayfee: 0.00001, - errors: 'Warning: unknown new rules activated (versionbit 28)' -} - -const mockGetNetworkInfo = { - version: 190700, - subversion: '/Bitcoin ABC:0.19.7(EB32.0)/', - protocolversion: 70015, - localservices: '0000000000000425', - localrelay: true, - timeoffset: 0, - networkactive: true, - connections: 23, - networks: [{}, {}, {}], - relayfee: 0.00001, - excessutxocharge: 0, - localaddresses: [], - warnings: - "Warning: Unknown block versions being mined! It's possible unknown rules are in effect" -} - -module.exports = { - mockGetInfo, - mockGetNetworkInfo -} diff --git a/test/v3/mocks/electrumx-mock.js b/test/v3/mocks/electrumx-mock.js deleted file mode 100644 index 41c96a1..0000000 --- a/test/v3/mocks/electrumx-mock.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - Mocking data for electrumx unit tests -*/ - -'use strict' - -const utxos = [ - { - height: 604392, - tx_hash: '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', - tx_pos: 0, - value: 1000 - }, - { - height: 630834, - tx_hash: '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', - tx_pos: 0, - value: 6000 - } -] - -const balance = { - confirmed: 7000, - unconfirmed: 0 -} - -const txHistory = [ - { - height: 601861, - tx_hash: '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - } -] - -const mempool = [ - { - tx_hash: '45381031132c57b2ff1cbe8d8d3920cf9ed25efd9a0beb764bdb2f24c7d1c7e3', - height: 0, - fee: 24310 - } -] - -const txDetails = { - blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc', - blocktime: 1578327094, - confirmations: 31861, - hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - hex: '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000', - locktime: 0, - size: 392, - time: 1578327094, - txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - version: 2, - vin: [ - { - scriptSig: { - asm: 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309', - hex: '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309' - }, - sequence: 4294967295, - txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', - vout: 1 - }, - { - scriptSig: { - asm: '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954', - hex: '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954' - }, - sequence: 4294967295, - txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', - vout: 0 - } - ], - vout: [ - { - n: 0, - scriptPubKey: { - addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'], - asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL', - hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287', - reqSigs: 1, - type: 'scripthash' - }, - value: 0.0562057 - }, - { - n: 1, - scriptPubKey: { - addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'], - asm: 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac', - reqSigs: 1, - type: 'pubkeyhash' - }, - value: 0.00589197 - }, - { - n: 2, - scriptPubKey: { - addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'], - asm: 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac', - reqSigs: 1, - type: 'pubkeyhash' - }, - value: 0.03272697 - } - ] -} - -const blockHeaders = [ - '010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6', - '01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c' -] - -module.exports = { - utxos, - balance, - txHistory, - mempool, - txDetails, - blockHeaders -} diff --git a/test/v3/mocks/encryption-mocks.js b/test/v3/mocks/encryption-mocks.js deleted file mode 100644 index 197bb3c..0000000 --- a/test/v3/mocks/encryption-mocks.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - Mock data for the encryption test library -*/ - -const mockNoSendTx = { - txid: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023', - hash: 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023', - version: 2, - size: 226, - locktime: 0, - vin: [ - { - txid: '681cc1d392975a7e78cbd490b8a940d7eb029c554f2cee332e3a7119d77fb189', - vout: 1, - scriptSig: { - asm: - '30450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f[ALL|FORKID] 02bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7', - hex: - '4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 0.000006, - n: 0, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 3c79ba54317c4be9cbfa5aaf357f74b97f3d9696 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bitcoincash:qq78nwj5x97yh6wtlfd27dtlwjuh70vkjc59h8tgtg'] - } - }, - { - value: 0.00003664, - n: 1, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 58502a73888e0a386fc69b44850af4b2a86ac9a6 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bitcoincash:qpv9q2nn3z8q5wr0c6d5fpg27je2s6kf5cavp4ney0'] - } - } - ], - hex: - '020000000189b17fd719713a2e33ee2c4f559c02ebd740a9b890d4cb787e5a9792d3c11c68010000006b4830450221008a7c86ce3a9f5765573440143a00631bb5afa9a58b24674a7fa794bb11ffffc30220397dd4c672c3e0dbe0c4eb6390b5727c5fdc3a42cf3ae275ac13b44d27bb234f412102bc9fc26ae2bd9a9a14e9baeda4b4fdd95a00bcbf375732c647488b3ce82a2fd7ffffffff0258020000000000001976a9143c79ba54317c4be9cbfa5aaf357f74b97f3d969688ac500e0000000000001976a91458502a73888e0a386fc69b44850af4b2a86ac9a688ac00000000' -} - -const mockFulcrumTxHistory = { - success: true, - transactions: [ - { - height: 511463, - tx_hash: - 'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3' - }, - { - height: 511464, - tx_hash: - '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153' - }, - { - height: 513373, - tx_hash: - '6960255abe64893073921e96bf3c053c82686e0fc22a565494fbe2a31e766975' - }, - { - height: 513373, - tx_hash: - '9ea667bcfc9cd337bd6c5583d8094c1b1942bd2015d95b54189deac5070eeff0' - }, - { - height: 560481, - tx_hash: - 'ecc1b51bac767880382bf3190ff17abf78d0936843a022a943d871116ed50368' - }, - { - height: 560615, - tx_hash: - 'b3792d28377b975560e1b6f09e48aeff8438d4c6969ca578bd406393bd50bd7d' - }, - { - height: 561568, - tx_hash: - '8bc2134c7e48e56e1769b3d7c4c1e3a0acc68e1e58160eee6fa67f3208c07262' - }, - { - height: 561569, - tx_hash: - 'ceb0cab0e37b59caf3ca29e1a698d19ff47f2827dd09cb2f3b91b9100b1dad1c' - }, - { - height: 561572, - tx_hash: - '0f9b49cafeb9ae1d741cdb12137c92816aa8470944c270a78ba2e610bd59190d' - }, - { - height: 561582, - tx_hash: - 'e4a0ac48ff3f42fc342717a2a3d34248e5e85bae79d59bd20e1b60e61b1c500f' - }, - { - height: 562106, - tx_hash: - '1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb' - }, - { - height: 562106, - tx_hash: - 'c42f8f16d3baa2ee343ea89ef110dfe094992379d08edd30887b8ca7ee671c9a' - } - ] -} - -const mockTxDetails2 = { - txid: '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153', - hash: '7e9aa7a74de2b30200a2d6fc748ff35a0c753221444194f720bb7f61ef1d9153', - version: 1, - size: 221, - locktime: 0, - vin: [ - { - txid: 'eff00a9538487ff44243c75fb13de19b5783454c42c81b9aff9afbfd09cbaec3', - vout: 0, - scriptSig: { - asm: - '30440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a[ALL|FORKID] 044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c', - hex: - '4730440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2c' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 0.01266023, - n: 0, - scriptPubKey: { - asm: 'OP_HASH160 91bacd6785e3f8c78d5f29b4020b94c753444130 OP_EQUAL', - hex: 'a91491bacd6785e3f8c78d5f29b4020b94c75344413087', - reqSigs: 1, - type: 'scripthash', - addresses: ['bitcoincash:pzgm4nt8sh3l33udtu5mgqstjnr4x3zpxqrrrndnw5'] - } - } - ], - hex: - '0100000001c3aecb09fdfb9aff9a1bc8424c4583579be13db15fc74342f47f4838950af0ef000000008a4730440220150e7c9b646fe1f7f576456c9808c89a710e8fbb753e9f5ca6ed2d39a0b69ba602202212968163d8dfe35eec5975fce38440082029cb2691a3e0ad1714f9c412c47a4141044eb40b025df18409f2a5197b010dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b61b81b88a1b5ce6f24eac6b8a34b2cffffffff01675113000000000017a91491bacd6785e3f8c78d5f29b4020b94c7534441308700000000', - blockhash: '000000000000000001aab6818b4ad0379a3f7a13940d9b3d4838b3dea1d0a083', - confirmations: 149222, - time: 1515088493, - blocktime: 1515088493 -} - -const mockFulcrumNoTxHistory = { - success: true, - transactions: [] -} - -const mockFulcrumNoSendBalance = { - success: true, - transactions: [ - { - height: 633578, - tx_hash: - 'a3b62cd4f4c56ba52139179db14bffd4ab22a2e077f3c62bd5cf0541bfcaf023' - } - ] -} - -module.exports = { - mockNoSendTx, - mockFulcrumTxHistory, - mockTxDetails2, - mockFulcrumNoTxHistory, - mockFulcrumNoSendBalance -} diff --git a/test/v3/mocks/express-mocks.js b/test/v3/mocks/express-mocks.js deleted file mode 100644 index d4753e7..0000000 --- a/test/v3/mocks/express-mocks.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - Contains mocks of Express req and res objects. -*/ - -'use strict' - -const sinon = require('sinon') - -// Inspect JS Objects. -const util = require('util') -util.inspect.defaultOptions = { - showHidden: true, - colors: true -} - -// mock for res.send() -function fakeSend (arg) { - // console.log(`res.send: ${util.inspect(arg)}`); - mockRes.output = arg - return arg -} - -// mock for res.json() -function fakeJson (arg) { - // console.log(`res.json: ${util.inspect(arg)}`); - mockRes.output = arg - return arg -} - -// mock for res.setStatus(num) -const setStatusCode = arg => { - mockRes.statusCode = arg -} - -const mockReq = { - accepts: sinon.stub().returns({}), - acceptsCharsets: sinon.stub().returns({}), - acceptsEncodings: sinon.stub().returns({}), - acceptsLanguages: sinon.stub().returns({}), - body: {}, - flash: sinon.stub().returns({}), - get: sinon.stub().returns({}), - is: sinon.stub().returns({}), - params: {}, - query: {}, - session: {}, - locals: {}, - headers: {} -} - -const mockRes = { - append: sinon.stub().returns({}), - attachement: sinon.stub().returns({}), - clearCookie: sinon.stub().returns({}), - cookie: sinon.stub().returns({}), - download: sinon.stub().returns({}), - end: sinon.stub().returns({}), - get: sinon.stub().returns({}), - headersSent: sinon.stub().returns({}), - json: sinon.stub().callsFake(fakeJson), - jsonp: sinon.stub().returns({}), - links: sinon.stub().returns({}), - locals: {}, - location: sinon.stub().returns({}), - output: null, // Used for retrieving output data. - redirect: sinon.stub().returns({}), - render: sinon.stub().returns({}), - send: sinon.stub().callsFake(fakeSend), - sendFile: sinon.stub().returns({}), - sendStatus: sinon.stub().returns({}), - set: sinon.stub().returns({}), - status: sinon.stub().callsFake(setStatusCode), - statusCode: null, // Default value before calling stats(); - type: sinon.stub().returns({}), - vary: sinon.stub().returns({}), - write: sinon.stub().returns({}), - setHeader: sinon.stub().returns({}), - format: sinon.stub().returns({}) -} - -// Dev-Note on Rate Limits: Since next() is mocked, I can call the Sinon untility -// functions on it, like called(), to see if this stub was called. -const mockNext = sinon.stub().returns() - -module.exports = { - mockReq, - mockRes, - mockNext -} diff --git a/test/v3/mocks/mining-mocks.js b/test/v3/mocks/mining-mocks.js deleted file mode 100644 index d8618d7..0000000 --- a/test/v3/mocks/mining-mocks.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockMiningInfo = { - blocks: 1270185, - currentblocksize: 0, - currentblocktx: 0, - difficulty: 1, - warnings: - "Warning: Unknown block versions being mined! It's possible unknown rules are in effect", - networkhashps: 517410290.9365583, - pooledtx: 5, - chain: 'test' -} - -module.exports = { - mockMiningInfo -} diff --git a/test/v3/mocks/price-mock.js b/test/v3/mocks/price-mock.js deleted file mode 100644 index 5bf3569..0000000 --- a/test/v3/mocks/price-mock.js +++ /dev/null @@ -1,241 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockCoinbaseFeed = { - data: { - currency: 'BCH', - rates: { - AED: '918.11634', - AFN: '19208.6584998', - ALGO: '823.28722002635056355', - ALL: '26365.4673517', - AMD: '120379.9761886', - ANG: '448.6632494', - AOA: '162813.68075', - ARS: '19392.84240565', - ATOM: '45.3670932026499700525', - AUD: '354.4385981', - AWG: '449.91', - AZN: '425.539875', - BAL: '18.824215193276753072', - BAM: '415.45514235', - BAND: '40.7768732564399590085', - BAT: '1168.9711697428450012', - BBD: '499.9', - BCH: '1.0', - BDT: '21194.1523216', - BGN: '415.36691', - BHD: '94.2501462', - BIF: '483670.78174295', - BMD: '249.95', - BND: '339.227141', - BOB: '1725.8732563', - BRL: '1401.494645', - BSD: '249.95', - BSV: '1.567361207606150942625', - BTC: '0.021275', - BTN: '18323.91073475', - BWP: '2859.81217315', - BYN: '640.5913561', - BYR: '6405913.561', - BZD: '503.8107177', - CAD: '329.6765515', - CDF: '490377.5463717', - CGLD: '121.6805004503079241715', - CHF: '227.5939721', - CLF: '7.13432285', - CLP: '196860.63474705', - CNH: '1669.50978125', - CNY: '1670.21589', - COMP: '2.429765723728978241', - COP: '961722.29431455', - CRC: '150790.4338804', - CUC: '250.01273745', - CVE: '23620.275', - CZK: '5790.09175', - DAI: '247.842348666936355635', - DASH: '3.338230383973288798', - DJF: '44495.8985401', - DKK: '1580.608815', - DOP: '14597.4179324', - DZD: '32214.830745', - EEK: '3652.69206545', - EGP: '3924.839875', - EOS: '96.8423091824874092665', - ERN: '3749.25274945', - ETB: '9313.6508972', - ETC: '45.04821122825989111', - ETH: '0.658777328255340453815', - EUR: '212.33', - FJD: '534.9054975', - FKP: '193.01413945', - GBP: '193.115', - GEL: '807.3385', - GGP: '193.01413945', - GHS: '1453.70845015', - GIP: '193.01413945', - GMD: '12934.9125', - GNF: '2447970.79115335', - GTQ: '1944.576007', - GYD: '52260.2868518', - HKD: '1937.122498', - HNL: '6129.328889', - HRK: '1610.802775', - HTG: '15746.55080985', - HUF: '77512.6058777', - IDR: '3675351.03275', - ILS: '846.1232415', - IMP: '193.01413945', - INR: '18345.655135', - IQD: '298009.02802165', - ISK: '34745.5495', - JEP: '193.01413945', - JMD: '36409.6696094', - JOD: '177.21455', - JPY: '26360.601825', - KES: '27184.562', - KGS: '20308.0755724', - KHR: '1025880.7533059', - KMF: '105029.03124175', - KNC: '274.68542227594924615', - KRW: '284915.23080495', - KWD: '76.4532063', - KYD: '208.2943328', - KZT: '107014.42658325', - LAK: '2310183.71158185', - LBP: '378370.54450325', - LINK: '22.894634759817007489', - LKR: '46090.95971405', - LRC: '1457.8594342373869446', - LRD: '48852.72175115', - LSL: '4122.3008749', - LTC: '5.2062070401999589123', - LTL: '806.05000775', - LVL: '164.03243695', - LYD: '341.5926678', - MAD: '2298.47346335', - MDL: '4228.5806147', - MGA: '982179.7557538', - MKD: '13087.73767885', - MKR: '0.43764476902003812305', - MMK: '322679.7621378', - MNT: '709349.8756452', - MOP: '1995.2583685', - MRO: '89232.15', - MTL: '170.9003131', - MUR: '9974.21650765', - MVR: '3849.23', - MWK: '188665.67006785', - MXN: '5298.265135', - MYR: '1036.517655', - MZN: '18229.3548997', - NAD: '4134.173', - NGN: '95481.03472305', - NIO: '8698.6554209', - NMR: '8.624556609112112958', - NOK: '2334.203066', - NPR: '29318.1806909', - NZD: '379.2351378', - OMG: '74.464123456422320125', - OMR: '96.23549905', - OXT: '1033.27821413807369045', - PAB: '249.95', - PEN: '896.6071427', - PGK: '874.90073485', - PHP: '12136.85639315', - PKR: '40613.9575836', - PLN: '971.640633', - PYG: '1756126.21474795', - QAR: '910.1304375', - REN: '769.6689761354887733', - REP: '18.2912550311013566725', - REPV2: '18.1582473904586151205', - RON: '1035.54285', - RSD: '24943.76025', - RUB: '19402.093805', - RWF: '244237.3512583', - SAR: '937.65668115', - SBD: '2019.805958', - SCR: '4576.6749819', - SEK: '2207.698372', - SGD: '339.57582125', - SHP: '193.01413945', - SLL: '2490751.74625075', - SOS: '145044.39506805', - SRD: '3537.7923', - SSP: '32558.487', - STD: '5250063.0293496', - SVC: '2187.04525345', - SZL: '4118.75383445', - THB: '7800.314625', - TJS: '2579.48624955', - TMT: '874.825', - TND: '688.2998125', - TOP: '579.953986', - TRY: '1969.70598', - TTD: '1696.3476626', - TWD: '7179.16412995', - TZS: '579879.84308155', - UAH: '7090.3476468', - UGX: '935325.80066935', - UMA: '29.276720351390922145', - UNI: '78.76161966283282792', - USD: '249.95', - USDC: '249.95', - UYU: '10724.30446005', - UZS: '2592145.88136715', - VEF: '62109486.17813795', - VES: '113073216.80555545', - VND: '5792476.0820382', - VUV: '28582.2184128', - WST: '655.5403657', - XAF: '139318.05656485', - XAG: '10.233577875', - XAU: '0.1313462255', - XCD: '675.5023725', - XDR: '177.0940741', - XLM: '2935.3219224332811736', - XOF: '139318.05656485', - XPD: '0.1065011955', - XPF: '25344.75028595', - XPT: '0.290981792', - XRP: '1016.6768354687817832', - XTZ: '114.010080507218287957', - YER: '62574.97275195', - YFI: '0.0181099566723181033515', - ZAR: '4130.57372', - ZEC: '3.88966697790227161905', - ZMK: '1313006.15998725', - ZMW: '5048.6210738', - ZRX: '642.804422350408735165', - ZWL: '80483.9' - } - } -} - -const mockCoinexFeed = { - code: 0, - data: { - date: 1605649499848, - ticker: { - vol: '26717.39062407', - low: '10.4000', - open: '11.8000', - high: '19.0029', - last: '18.5000', - buy: '18.0100', - buy_amount: '200.00000000', - sell: '18.5000', - sell_amount: '47.89580474' - } - }, - message: 'OK' -} - -module.exports = { - mockCoinbaseFeed, - mockCoinexFeed -} diff --git a/test/v3/mocks/raw-transactions-mocks.js b/test/v3/mocks/raw-transactions-mocks.js deleted file mode 100644 index 1649d52..0000000 --- a/test/v3/mocks/raw-transactions-mocks.js +++ /dev/null @@ -1,157 +0,0 @@ -/* - This library contains mocking data for running unit tests. -*/ - -'use strict' - -const mockDecodeRawTransaction = { - txid: 'a332237d82a2543af1b0e1ae3c8cea1610c290ebcaf084a7e9894a61de0be988', - hash: 'a332237d82a2543af1b0e1ae3c8cea1610c290ebcaf084a7e9894a61de0be988', - size: 226, - version: 2, - locktime: 0, - vin: [ - { - txid: '21cced645eab150585ed7ca7c96edebab5793cc0a3b3b286c42fd7d6d798b5b9', - vout: 1, - scriptSig: { - asm: - '3045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd41390[ALL|FORKID] 0360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413d', - hex: - '483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413d' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 0.0001, - n: 0, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 eb4b180def88e3f5625b2d8ae2c098ff7d85f664 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: [Array] - } - }, - { - value: 0.09989752, - n: 1, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 eb4b180def88e3f5625b2d8ae2c098ff7d85f664 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: [Array] - } - } - ] -} - -const mockDecodeScript = { - asm: - '0 0 -57 OP_NOP6 OP_LSHIFT OP_UNKNOWN OP_UNKNOWN OP_UNKNOWN c486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa OP_2OVER OP_NUMEQUALVERIFY OP_OR OP_INVERT OP_UNKNOWN OP_UNKNOWN 2ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab 67c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b [error]', - type: 'nonstandard', - p2sh: 'bchtest:pzy6dwfy6yf373w0dr05a6flfqksurjhwcl3awhdvm' -} - -const mockRawTransactionConcise = - '02000000014e6b52500110b1c30315b85805fb274f0f4afceffc1589f889b27709e59e987d000000006a473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02d1778f950a0000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac00000000' - -const mockRawTransactionVerbose = { - hex: - '02000000014e6b52500110b1c30315b85805fb274f0f4afceffc1589f889b27709e59e987d000000006a473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792ffffffff02d1778f950a0000001976a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac80969800000000001976a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac00000000', - txid: 'bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971', - hash: 'bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971', - size: 225, - version: 2, - locktime: 0, - vin: [ - { - txid: '7d989ee50977b289f88915fceffc4a0f4f27fb0558b81503c3b1100150526b4e', - vout: 0, - scriptSig: { - asm: - '3044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7[ALL|FORKID] 03c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792', - hex: - '473044022052762770baa71c1a0b9544ad0f1ea343d32c22aa87c5f8397b6852f464c15b1e02201f4390745cb470e21e0e3c14229f39fef55ea0643a4c997f99d9f3501eae09b7412103c346eee77a77a8d3e073dacc0532ca7a5b9747bc06d88bf091cac9f4bc8bb792' - }, - sequence: 4294967295 - } - ], - vout: [ - { - value: 454.58880465, - n: 0, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 36d2f27bbd826a86db1e93618ce3de89ef331693 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91436d2f27bbd826a86db1e93618ce3de89ef33169388ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35'] - } - }, - { - value: 0.1, - n: 1, - scriptPubKey: { - asm: - 'OP_DUP OP_HASH160 152ea3cd65f18cb8fa9146c84ea1a97af8f051de OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914152ea3cd65f18cb8fa9146c84ea1a97af8f051de88ac', - reqSigs: 1, - type: 'pubkeyhash', - addresses: ['bchtest:qq2jag7dvhccew86j9rvsn4p49a03uz3mcpw3d6aca'] - } - } - ], - blockhash: '000000000000026fa244de975ca89ea08008aa566564ce2e8ebb3144361b601b', - confirmations: 125, - time: 1542646373, - blocktime: 1542646373 -} - -const mockWHDecode = { - txid: 'f8a9857fe3b8a288b5fcafb1b0fc196731f433add6d962f77acd7c10b970ff89', - fee: '500', - sendingaddress: 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr', - referenceaddress: 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr', - ismine: false, - version: 0, - type_int: 0, - type: 'Simple Send', - propertyid: 368, - precision: '8', - amount: '10.00000000', - valid: true, - blockhash: '0000000046ba0bcef78caaa4492622176bbc563cf249ab52340a0449bc8e26f6', - blocktime: 1542814183, - positioninblock: 57, - block: 1269008, - confirmations: 2 -} - -const mockWHCreateInput = { - txid: 'f7ed9cf23dee85910f6269c9a101a75fcfd2f3c6fc81f17fad824ff7aaf99ab2', - vout: 1, - scriptPubKey: '76a914a4b98b0c118de83e7d39c834445f51dce62425f588ac', - amount: 0.09984138, - satoshis: 9984138, - height: 1269011, - confirmations: 4, - legacyAddress: 'mvXwPH74hW2yVTWwDwzsjGoaUAqJvWk7ZJ', - cashAddress: 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr', - value: 0.09984138 -} - -module.exports = { - mockDecodeRawTransaction, - mockDecodeScript, - mockRawTransactionConcise, - mockRawTransactionVerbose, - mockWHDecode, - mockWHCreateInput -} diff --git a/test/v3/mocks/slp-mocks.js b/test/v3/mocks/slp-mocks.js deleted file mode 100644 index 198e9b7..0000000 --- a/test/v3/mocks/slp-mocks.js +++ /dev/null @@ -1,545 +0,0 @@ -/* - This library contains mocking data for running unit tests. -*/ - -'use strict' - -const mockList = { - t: [ - { - tokenDetails: { - tokenIdHex: - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - documentUri: '', - documentSha256: '', - symbol: 'NAKAMOTO', - name: 'NAKAMOTO', - decimals: 8 - }, - tokenStats: { - qty_valid_txns_since_genesis: 241, - qty_valid_token_utxos: 151, - qty_valid_token_addresses: 113, - qty_token_circulating_supply: '20995990', - qty_token_burned: '4010', - qty_token_minted: '21000000', - qty_satoshis_locked_up: 81900 - } - } - ] -} - -const mockSingleToken = { - t: [ - { - tokenDetails: { - decimals: 0, - tokenIdHex: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - timestamp: '2019-06-14 06:31:37', - timestamp_unix: 1560493897, - transactionType: 'GENESIS', - versionType: 1, - documentUri: 'ot@ot.com', - documentSha256Hex: null, - symbol: 'OT', - name: 'Test First SLP oasis Token', - batonVout: 2, - containsBaton: true, - genesisOrMintQuantity: '10000', - sendOutputs: null - }, - tokenStats: { - block_created: 1308634, - block_last_active_send: 1308655, - block_last_active_mint: 1308636, - qty_valid_txns_since_genesis: 11, - qty_valid_token_utxos: 9, - qty_valid_token_addresses: 2, - qty_token_minted: '20000', - qty_token_burned: '0', - qty_token_circulating_supply: '20000', - qty_satoshis_locked_up: 4914, - minting_baton_status: 'ALIVE' - } - } - ] -} - -const mockSingleTokenError = { - t: [] -} - -const mockSingleAddress = { - g: [ - { - _id: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f', - balanceString: '0.002382', - slpAddress: 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - } - ], - t: [ - { - tokenDetails: { - decimals: 6, - tokenIdHex: - 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f' - } - } - ] -} - -const mockTx = { - txid: '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457822d446', - version: 2, - locktime: 0, - vin: [ - { - txid: '61e71554a3dc18158f30d9e8f5c9b6641a789690b32302899f81cbea9fe3bb49', - vout: 2, - sequence: 4294967295, - n: 0, - scriptSig: { - hex: - '4730440220409e79fec552f01203f41d3d621ae3db89c720af261c8268ce5f0453de009f5d022001e7ffefeba7b0716d32ea55cb6ace267b6ee9cbcc8a017bb9c3b6acf7889418412103c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585', - asm: - '30440220409e79fec552f01203f41d3d621ae3db89c720af261c8268ce5f0453de009f5d022001e7ffefeba7b0716d32ea55cb6ace267b6ee9cbcc8a017bb9c3b6acf7889418[ALL|FORKID] 03c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585' - }, - value: 546, - legacyAddress: 'mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL', - cashAddress: 'bchtest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvaga95c09' - }, - { - txid: '61e71554a3dc18158f30d9e8f5c9b6641a789690b32302899f81cbea9fe3bb49', - vout: 1, - sequence: 4294967295, - n: 1, - scriptSig: { - hex: - '483045022100a743bee56c99bd103be48a78fa4c7342100815d9d2448dbe6e1d338c3a13b241022066728b5279fc22eef5cd019582ff34771e29175835fc98aa3168a1548fd78ac8412103c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585', - asm: - '3045022100a743bee56c99bd103be48a78fa4c7342100815d9d2448dbe6e1d338c3a13b241022066728b5279fc22eef5cd019582ff34771e29175835fc98aa3168a1548fd78ac8[ALL|FORKID] 03c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585' - }, - value: 546, - legacyAddress: 'mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL', - cashAddress: 'bchtest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvaga95c09' - }, - { - txid: '61e71554a3dc18158f30d9e8f5c9b6641a789690b32302899f81cbea9fe3bb49', - vout: 3, - sequence: 4294967295, - n: 2, - scriptSig: { - hex: - '483045022100821473902eec5f1ce7d43b1ba7f9ec453bfe8b8dfc3de3e0723c883ab109922f02206162960e80618531fab2c16aee260fddd7979bab62471c8686af7de75f8732ec412103c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585', - asm: - '3045022100821473902eec5f1ce7d43b1ba7f9ec453bfe8b8dfc3de3e0723c883ab109922f02206162960e80618531fab2c16aee260fddd7979bab62471c8686af7de75f8732ec[ALL|FORKID] 03c87f0ec048a0771bdb60533d45cac88c6974afeb055a65edd663c2f947335585' - }, - value: 9997521, - legacyAddress: 'mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL', - cashAddress: 'bchtest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvaga95c09' - } - ], - vout: [ - { - value: '0.00000000', - n: 0, - scriptPubKey: { - hex: - '6a04534c500001010453454e44207ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796080000000049504f80080000001c71e64280', - asm: - 'OP_RETURN 5262419 1 1145980243 7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796 0000000049504f80 0000001c71e64280' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.00000546', - n: 1, - scriptPubKey: { - hex: '76a914396b8e57ad0cb58d30e2992f22047b3c20377aa688ac', - asm: - 'OP_DUP OP_HASH160 396b8e57ad0cb58d30e2992f22047b3c20377aa6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['mkkZf7T3fU3vHSzNPy51HBmM46ghN1gnN9'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.00000546', - n: 2, - scriptPubKey: { - hex: '76a914aa099b067337dc20993bbfdb5c4f3c6867ba2c5b88ac', - asm: - 'OP_DUP OP_HASH160 aa099b067337dc20993bbfdb5c4f3c6867ba2c5b OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.09996891', - n: 3, - scriptPubKey: { - hex: '76a914aa099b067337dc20993bbfdb5c4f3c6867ba2c5b88ac', - asm: - 'OP_DUP OP_HASH160 aa099b067337dc20993bbfdb5c4f3c6867ba2c5b OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['mw22g57T9YA7MZQQu5eBDKj3PKTdt99oDL'], - type: 'pubkeyhash' - }, - spentTxId: null, - spentIndex: null, - spentHeight: null - } - ], - blockhash: '000000000000ce978accc64a6bb567acf0c653c202309a0f8e220149bf0c6968', - blockheight: 1287490, - confirmations: 746, - time: 1550855104, - blocktime: 1550855104, - valueOut: 0.09997983, - size: 628, - valueIn: 0.09998613, - fees: 0.0000063, - tokenInfo: { - versionType: 1, - transactionType: 'SEND', - tokenIdHex: - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796', - sendOutputs: ['0', '1230000000', '122170000000'] - }, - tokenIsValid: true -} - -const mockConvert = { - slpAddress: 'slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5', - cashAddress: 'bchtest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2svtllzmlf', - legacyAddress: 'mvQPGnzRT6gMWASZBMg7NcT3vmvsSKSQtf' -} - -const mockTokenDetails = { - tokenIdHex: - 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', - documentUri: '', - symbol: 'NAKAMOTO', - name: 'NAKAMOTO', - decimals: 8, - timestamp: '', - containsBaton: true, - versionType: 1 -} - -const mockTokenStats = { - qty_valid_txns_since_genesis: 241, - qty_valid_token_utxos: 151, - qty_valid_token_addresses: 113, - qty_token_circulating_supply: '20995990', - qty_token_burned: '4010', - qty_token_minted: '21000000', - qty_satoshis_locked_up: 81900 -} - -const mockBalance = { - _id: 'simpleledger:qp9d8mn8ypryfvea2mev0ggc3wg6plpn4suuaeuss3', - token_balance: '1000' -} - -const mockTransactions = [ - { - txid: 'a302f045be8efa1cd982833a7f187ff4fac8baac36da0c887eb2787d8b45e2af', - tokenDetails: { - valid: true, - detail: { - decimals: null, - tokenIdHex: - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a', - timestamp: null, - transactionType: 'MINT', - versionType: 1, - documentUri: null, - documentSha256Hex: null, - symbol: null, - name: null, - batonVout: 2, - containsBaton: true, - genesisOrMintQuantity: { - $numberDecimal: '1000' - }, - sendOutputs: null - }, - invalidReason: null, - schema_version: 30 - } - } -] - -const mockFoobar = { - c: [], - u: [] -} - -const mockSingleValidTxid = { - c: [ - { - _id: '5d965fc27f1cf2184ca2fe73', - tx: { - h: '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockTwoValidTxid = { - c: [ - { - _id: '5d965fc27f1cf2184ca2fe73', - tx: { - h: '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - }, - slp: { - valid: true, - invalidReason: null - } - }, - { - _id: '5d71e69758380a002c492a90', - tx: { - h: '552112f9e458dc7d1d8b328b0a6685e8af74a64b60b6846e7c86407f27f47e42' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockTwoRedundentTxid = { - c: [ - { - _id: '5db99c72a391ae2afd604bde', - tx: { - h: 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56' - }, - slp: { - valid: true, - invalidReason: null - } - } - ], - u: [] -} - -const mockTxHistory = [ - { - tx: { - h: '3a7646b3976a8745928c7192c8dde989bfa275fa6d7bce950180ab8002cf6cef' - }, - in: [ - { - i: 0, - e: { - h: '7bdd586ebd1e5f3dfd5295ab6e896c48b25855ab77a72c035ce1e7aacc965c2d', - i: 1, - s: - 'RzBEAiAsu5o9EnxgZChjxKygxhGLhlfxJJbQfd4PP/vp2od3fAIgPpMqg+Sp4YpiInQZ6weJwELq8LEIrHXJpueBz7yF5gRBIQLsyL3809mRphc/+tPAcTQO5bi0kqZRFKpKIHXE3qN8Rw==', - a: 'simpleledger:qrrrpqmdkggpnw0czwg0jgcjd7yhu25jy5zxh2gqdq' - } - }, - { - i: 1, - e: { - h: 'df49feff24dc34a10a44a0cbd7c908d964801ec7218512c84574fbce698535f0', - i: 3, - s: - 'SDBFAiEAhW3zbKTlPrOXD2E2oEcNof6vCMPGYIg8vOVSE9c8IakCIFLg/gxydG9eL8HMAzkScGElKWJRnfhVMpMHU3evNcrZQSEDRS7F+pSC8OxSldsT4FctJLZBU7f2+FDiE05ae1xqtN0=', - a: 'simpleledger:qpkpeqfslejw5pptzcy25h2jxhsc9k0vts43n26up0' - } - } - ], - out: [ - { - e: { - v: 0, - i: 0, - s: - 'agRTTFAAAQEEU0VORCA46XxdfTWFosvz+VgMgsozmF+csIRdTcziIMtwn5U4sAgAAAAAAJiWgAgAAAAAJHfU+g==' - } - }, - { - e: { - v: 546, - i: 1, - s: 'dqkUqo4lVohqOK6rDd7nIIkFQ6Uf41aIrA==', - a: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - } - }, - { - e: { - v: 546, - i: 2, - s: 'dqkUgYSfxf90wVkERCiOMyh16iB4d92IrA==', - a: 'simpleledger:qzqcf879la6vzkgygs5guvegwh4zq7rhm5nu0pljjl' - } - }, - { - e: { - v: 21849, - i: 3, - s: 'dqkUxjCDbbIQGbn4E5D5IxJviX4qkiWIrA==', - a: 'simpleledger:qrrrpqmdkggpnw0czwg0jgcjd7yhu25jy5zxh2gqdq' - } - } - ], - slp: { - detail: { - decimals: 8, - tokenIdHex: - '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', - transactionType: 'SEND', - versionType: 1, - documentUri: 'psfoundation.cash', - documentSha256Hex: null, - symbol: 'PSF', - name: 'Permissionless Software Foundation', - txnBatonVout: null, - txnContainsBaton: false, - outputs: [ - { - address: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza', - amount: '0.1' - }, - { - address: 'simpleledger:qzqcf879la6vzkgygs5guvegwh4zq7rhm5nu0pljjl', - amount: '6.11833082' - } - ] - } - }, - blk: { - h: '000000000000000001a47bf337ca211c1e7218234d0d70117c64d6f213f5dacb', - i: 634833, - t: 1589300214 - } - }, - { - tx: { - h: '3b11b48cab1e7c8384facf482b3f6bfe659a58245ab4aa4147b42b5cb2a5fac5' - }, - in: [ - { - i: 0, - e: { - h: 'e4e1e1f6b502cbd42f69b919634cea3e37fc8595e6ca800e0a7d8f6dcdfb249e', - i: 3, - s: - 'SDBFAiEAjCGRmU28x24LMTwg5XqC+fLf3zGhTYCsSWpmF8Eshq0CIAJhxAKMbwKWC4ASmjWzplpno2ch+hGNUD6HNWOdbd4SQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - }, - { - i: 1, - e: { - h: '438420345dd8b7fb4ea74aaf2e3090f44899bf3c71662243946a441de6dae720', - i: 2, - s: - 'SDBFAiEAhmLYL4QY3mvZo3/i0XG9PJAYMruw3MaGOLJ3Z4si7hwCIAaaGwRnj/cZK3L/KKFBoMtItpGRZ10GowNN//vgn6PKQSECeRsZo5Fl29g0A9bfJo1E/WIdowWBsLblyxWnEB7ViFE=', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - } - ], - out: [ - { - e: { - v: 0, - i: 0, - s: - 'agRTTFAAAQEEU0VORCCk+1wtoaoGTiUBikP5FlBABx2emEuhkMIip/WQU6+EsggAAAAAAA9CQAgAAAAXQnHEwA==' - } - }, - { - e: { - v: 546, - i: 1, - s: 'dqkUqo4lVohqOK6rDd7nIIkFQ6Uf41aIrA==', - a: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - } - }, - { - e: { - v: 546, - i: 2, - s: 'dqkUWQQVny9pv6Y+76cSYzoNltwufoiIrA==', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - }, - { - e: { - v: 43074, - i: 3, - s: 'dqkUWQQVny9pv6Y+76cSYzoNltwufoiIrA==', - a: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7' - } - } - ], - slp: { - detail: { - decimals: 2, - tokenIdHex: - 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', - transactionType: 'SEND', - versionType: 1, - documentUri: 'troutsblog.com', - documentSha256Hex: null, - symbol: 'TROUT', - name: "Trout's test token", - txnBatonVout: null, - txnContainsBaton: false, - outputs: [ - { - address: 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza', - amount: '10000' - }, - { - address: 'simpleledger:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qznkmw3s7', - amount: '998990000' - } - ] - } - }, - blk: { - h: '000000000000000002a4ee2ed6fe8a764ffef0b2556f545ca0b3ef39ecd2b7c4', - i: 634832, - t: 1589297145 - } - } -] - -module.exports = { - mockList, - mockSingleToken, - mockConvert, - mockTokenDetails, - mockTokenStats, - mockTx, - mockBalance, - mockTransactions, - mockSingleTokenError, - mockSingleAddress, - mockFoobar, - mockSingleValidTxid, - mockTwoValidTxid, - mockTwoRedundentTxid, - mockTxHistory -} diff --git a/test/v3/mocks/slpjs-mocks.js b/test/v3/mocks/slpjs-mocks.js deleted file mode 100644 index e1294f6..0000000 --- a/test/v3/mocks/slpjs-mocks.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - Mocks used for unit tests that interact with slpjs. -*/ - -'use strict' - -// const sinon = require('sinon') -// const proxyquire = require('proxyquire') -const BigNumber = require('bignumber.js') -const slpMocks = require('./slp-mocks') - -// Mock the BitboxNetwork class. -class BitboxNetwork { - // constructor () {} - - async getAllSlpBalancesAndUtxos (address) { - return { - satoshis_available_bch: 9996891, - satoshis_in_slp_baton: 546, - satoshis_in_slp_token: 546, - satoshis_in_invalid_token_dag: 0, - satoshis_in_invalid_baton_dag: 0, - slpTokenBalances: { - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796': new BigNumber( - 123400000000 - ) - }, - slpTokenUtxos: { - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796': [] - }, - slpBatonUtxos: { - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796': [] - }, - nonSlpUtxos: [{}], - invalidTokenUtxos: [], - invalidBatonUtxos: [] - } - } - - async getTokenInformation (txid) { - BigNumber.set({ DECIMAL_PLACES: 8, ROUNDING_MODE: 4 }) - - const obj = { - versionType: 1, - transactionType: 0, - symbol: 'SLPSDK', - name: 'SLP SDK example using BITBOX', - documentUri: 'developer.bitcoin.com', - documentSha256: null, - decimals: 8, - batonVout: 2, - containsBaton: true, - genesisOrMintQuantity: new BigNumber(123400000000) - } - - return obj - } - - async getTransactionDetails (txid) { - return slpMocks.mockTx - } -} - -// Mock the slpjs library. -const slpjs = { - BitboxNetwork, - slp: {}, - validator: {} -} - -module.exports = slpjs diff --git a/test/v3/mocks/transaction-mocks.js b/test/v3/mocks/transaction-mocks.js deleted file mode 100644 index 89d67bf..0000000 --- a/test/v3/mocks/transaction-mocks.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -/* -const mockDetails = { - txid: "6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40", - version: 2, - locktime: 0, - vin: [Array], - vout: [Array], - blockhash: "00000000e7232ff12462dedf9c11985f5b54202515277c337ccc59812758f28b", - blockheight: 1270188, - confirmations: 2, - time: 1543436253, - blocktime: 1543436253, - valueOut: 450.78867333, - size: 226, - valueIn: 450.78867559, - fees: 0.00000226 -} -*/ - -const mockDetails = { - txid: '6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40', - version: 2, - locktime: 0, - vin: [ - { - txid: '273d616d1c48f4b075c497f36ffdc79da5c8d6ed75485808b3599aac504f8525', - vout: 0, - sequence: 4294967295, - n: 0, - scriptSig: [{}], - addr: 'bchtest:qqmd9unmhkpx4pkmr6fkrr8rm6y77vckjvqe8aey35', - valueSat: 45078867559, - value: 450.78867559, - doubleSpentTxID: null - } - ], - vout: [ - { - value: '450.68867333', - n: 0, - scriptPubKey: [{}], - spentTxId: null, - spentIndex: null, - spentHeight: null - }, - { - value: '0.10000000', - n: 1, - scriptPubKey: [{}], - spentTxId: null, - spentIndex: null, - spentHeight: null - } - ], - blockhash: '00000000e7232ff12462dedf9c11985f5b54202515277c337ccc59812758f28b', - blockheight: 1270188, - confirmations: 3, - time: 1543436253, - blocktime: 1543436253, - valueOut: 450.78867333, - size: 226, - valueIn: 450.78867559, - fees: 0.00000226 -} - -module.exports = { - mockDetails -} diff --git a/test/v3/mocks/util-mocks.js b/test/v3/mocks/util-mocks.js deleted file mode 100644 index 6b7f46b..0000000 --- a/test/v3/mocks/util-mocks.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - This library contains mocking data for running unit tests on the address route. -*/ - -'use strict' - -const mockAddress = { - isvalid: true, - address: 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y', - scriptPubKey: '76a914016a935f87e8deeab04501889b22d87e4e52db0988ac', - ismine: false, - iswatchonly: false, - isscript: false -} - -const mockBalance = { - page: 1, - totalPages: 1, - itemsOnPage: 1000, - address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv33u3mdxyc77j3u6k', - balance: '2546', - totalReceived: '2546', - totalSent: '0', - unconfirmedBalance: '0', - unconfirmedTxs: 0, - txs: 2, - txids: [ - 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6' - ] -} - -const mockUtxos = [ - { - txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - vout: 0, - value: '2000', - height: 605873, - confirmations: 298, - satoshis: 2000 - }, - { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546 - } -] - -const mockThreeUtxos = [ - { - txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - vout: 0, - value: '2000', - height: 605873, - confirmations: 298, - satoshis: 2000 - }, - { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546 - }, - { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546 - } -] - -const mockIsTokenUtxos = [ - false, - { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7', - tokenTicker: 'TOK-CH', - tokenName: 'TokyoCash', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 8, - tokenQty: 2 - } -] - -const tapUtxo = { - txid: '2e030df12390186baf817fa2760540b886511e04bc520e88f6b4c2124cc2a7d4', - vout: 1, - value: '546', - height: 606564, - confirmations: 3, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenQty: 1 -} - -const tokensOnly = [mockIsTokenUtxos[1], tapUtxo] - -const multipleTokens = [false, mockIsTokenUtxos[1], tapUtxo] - -module.exports = { - mockAddress, - mockBalance, - mockUtxos, - mockThreeUtxos, - mockIsTokenUtxos, - tokensOnly, - multipleTokens -} diff --git a/test/v3/price.js b/test/v3/price.js deleted file mode 100644 index 691b4eb..0000000 --- a/test/v3/price.js +++ /dev/null @@ -1,280 +0,0 @@ -/* - TESTS FOR THE PRICE.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const sinon = require('sinon') - -const Price = require('../../src/routes/v3/price') -let uut - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/price-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#PriceRouter', () => { - let req, res - let sandbox - - before(() => { - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - - uut = new Price() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => {}) - - describe('#root', async () => { - // root route handler. - // const root = controlRoute.testableComponents.root - - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'price', 'Returns static string') - }) - }) - - describe('#getUSD', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - uut.priceUrl = 'http://fakeurl/api/' - - await uut.getUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get the USD price of BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinbaseFeed }) - } - - const result = await uut.getUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) - - describe('#getBCHRate', () => { - it('should throw 500 when network issues', async () => { - uut.priceUrl = 'http://fakeurl/api/' - - await uut.getBCHRate(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBCHRate(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBCHRate(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get several rates for BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinbaseFeed }) - } - - const result = await uut.getBCHRate(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // assert.isNumber(result) - assert.property(result, 'USD') - assert.property(result, 'CAD') - }) - }) - - describe('#errorHandler', () => { - it('should handle unexpected errors', () => { - sandbox.stub(uut.routeUtils, 'decodeError').returns({ msg: false }) - - const result = uut.errorHandler(new Error('test error'), res) - // console.log('result: ', result) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.property(result, 'error') - }) - }) - - describe('#getBCHAUSD', () => { - // const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo - - it('should throw 500 when network issues', async () => { - uut.coinexPriceUrl = 'http://fakeurl/api/' - - await uut.getBCHAUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // console.log(res) - assert.include( - res.output.error, - 'Network error: Could not communicate with full node or other external service' - ) - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getBCHAUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getBCHAUSD(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get the USD price of BCH', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: mockData.mockCoinexFeed }) - } - - const result = await uut.getBCHAUSD(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isNumber(result.usd) - }) - }) -}) diff --git a/test/v3/rate-limits.js b/test/v3/rate-limits.js deleted file mode 100644 index c60473f..0000000 --- a/test/v3/rate-limits.js +++ /dev/null @@ -1,460 +0,0 @@ -'use strict' - -const chai = require('chai') -const assert = chai.assert -// const nock = require('nock') // HTTP mocking -const sinon = require('sinon') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// Mocking data. -const { mockReq, mockRes, mockNext } = require('./mocks/express-mocks') - -// Libraries under test -const RateLimits = require('../../src/middleware/route-ratelimit') -let rateLimits = new RateLimits() - -// const controlRoute = require('../../src/routes/v3/full-node/control') -const jwtAuth = require('../../src/middleware/jwt-auth') - -let req, res, next -// let originalEnvVars // Used during transition from integration to unit tests. - -// JWT token used in tests. -const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYWRlM2Y1NzM5ZTZjMGZmMDM0YjlhMSIsImlhdCI6MTU3MTY3NzQ1MCwiZXhwIjoxNTc0MjY5NDUwfQ.SSz7F7ETyBB3eoNG2VKCzPOhddtB-vrtmEoj7PxicrQ' - -describe('#route-ratelimits & jwt-auth', () => { - let sandbox - - before(async () => { - // Save existing environment variables. - // originalEnvVars = { - // BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - // RPC_BASEURL: process.env.RPC_BASEURL, - // RPC_USERNAME: process.env.RPC_USERNAME, - // RPC_PASSWORD: process.env.RPC_PASSWORD - // } - - if (!process.env.JWT_AUTH_SERVER) { process.env.JWT_AUTH_SERVER = 'http://fakeurl.com/' } - - // Wipe the Redis DB, which prevents false negatives when running integration - // tests back-to-back. - await rateLimits.wipeRedis() - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = Object.assign({}, mockReq) - res = Object.assign({}, mockRes) - next = mockNext - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - req.locals = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - rateLimits.closeRedis() - }) - - describe('#jwt-auth.js', () => { - describe('#getTokenFromHeaders', () => { - it('should populate the req.locals object correctly', () => { - // Initialize req.locals - req.locals = { - proLimit: false, - apiLevel: 0 - } - - const header = `Token ${jwt}` - req.headers.authorization = header - - jwtAuth.getTokenFromHeaders(req, res, next) - - // console.log(`req.locals: ${JSON.stringify(req.locals, null, 2)}`) - - assert.property(req.locals, 'proLimit') - assert.property(req.locals, 'apiLevel') - assert.property(req.locals, 'jwtToken') - assert.equal(req.locals.jwtToken, jwt) - }) - }) - }) - - describe('#getResource', () => { - it('should decode a blockchain request', () => { - const url = - '/blockchain/getTxOut/62a3ea958a463a372bc0caf2c374a7f60be9c624be63a0db8db78f05809df6d8/0?include_mempool=true' - - const result = rateLimits.getResource(url) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, 'blockchain') - }) - }) - - describe('#calcPoints', () => { - it('should return 50 points for anonymous user', () => { - const result = rateLimits.calcPoints() - // console.log(`result: ${result}`) - - assert.equal(result, 50) - }) - - it('should return 50 points for free tier requesting full node access', () => { - const jwtInfo = { - apiLevel: 10, - resource: 'blockchain', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 50) - }) - - it('should return 50 points for free tier requesting indexer access', () => { - const jwtInfo = { - apiLevel: 10, - resource: 'blockbook', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 50) - }) - - it('should return 50 points for free tier requesting SLPDB access', () => { - const jwtInfo = { - apiLevel: 10, - resource: 'slp', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 50) - }) - - it('should return 10 points for full node tier requesting full node access', () => { - const jwtInfo = { - apiLevel: 20, - resource: 'blockchain', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 10) - }) - - it('should return 50 points for full-node tier requesting indexer access', () => { - const jwtInfo = { - apiLevel: 20, - resource: 'blockbook', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 50) - }) - - it('should return 50 points for full node tier requesting SLPDB access', () => { - const jwtInfo = { - apiLevel: 20, - resource: 'slp', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 50) - }) - - it('should return 10 point for indexer tier requesting full node access', () => { - const jwtInfo = { - apiLevel: 30, - resource: 'blockchain', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 10) - }) - - it('should return 10 points for indexer tier requesting indexer access', () => { - const jwtInfo = { - apiLevel: 30, - resource: 'blockbook', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 10) - }) - - it('should return 50 points for indexer tier requesting SLPDB access', () => { - const jwtInfo = { - apiLevel: 30, - resource: 'slp', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 50) - }) - - it('should return 10 point for SLP tier requesting full node access', () => { - const jwtInfo = { - apiLevel: 40, - resource: 'blockchain', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 10) - }) - - it('should return 10 points for SLP tier requesting indexer access', () => { - const jwtInfo = { - apiLevel: 40, - resource: 'blockbook', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 10) - }) - - it('should return 10 points for SLP tier requesting SLPDB access', () => { - const jwtInfo = { - apiLevel: 40, - resource: 'slp', - id: '5e3a0415eb29a962da2708b4' - } - - const result = rateLimits.calcPoints(jwtInfo) - assert.equal(result, 10) - }) - }) - - describe('#rateLimitByResource', () => { - // NOTE: this test will fail if you run multiple integration tests in a - // short period. Because it talks to the Redis DB. - it('should pass through rate-limit middleware', async () => { - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - // Call the route twice to trigger the rate handling. - await rateLimits.rateLimitByResource(req, res, next) - await rateLimits.rateLimitByResource(req, res, next) - - // next() will be called if rate-limit is not triggered - assert.equal(next.called, true) - }) - - it('should trigger rate-limit handler if rate limits exceeds 5 request per minute', async () => { - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - for (let i = 0; i < 5; i++) { - next.reset() // reset the stubbed next() function. - - await rateLimits.rateLimitByResource(req, res, next) - // console.log(`next() called: ${next.called}`) - } - - // Note: next() will be called unless the rate-limit kicks in. - assert.equal( - next.called, - false, - 'next should not be called if rate limit was triggered.' - ) - }) - - it('should NOT trigger rate-limit for free-tier at 5 RPM', async () => { - // Create a new instance of the rate limit so we start with zeroed tracking. - rateLimits = new RateLimits() - - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - req.locals.jwtToken = 'some-token' - - const jwtInfo = { - apiLevel: 10, - id: '5e3a0415eb29a962da2708b1' - } - - // Mock the call to the jwt library. - sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo) - - for (let i = 0; i < 5; i++) { - next.reset() // reset the stubbed next() function. - - await rateLimits.rateLimitByResource(req, res, next) - // console.log(`next() called: ${next.called}`) - } - - // console.log(`req.locals after test: ${util.inspect(req.locals)}`) - - // Note: next() will be called unless the rate-limit kicks in. - assert.equal( - next.called, - true, - 'next should be called if rate limit was not triggered.' - ) - }) - - it('should trigger rate-limit for free tier after 20 RPM', async () => { - // Create a new instance of the rate limit so we start with zeroed tracking. - rateLimits = new RateLimits() - - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - req.locals.jwtToken = 'some-token' - - const jwtInfo = { - apiLevel: 10, - id: '5e3a0415eb29a962da2708b2' - } - - // Mock the call to the jwt library. - sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo) - - for (let i = 0; i < 22; i++) { - next.reset() // reset the stubbed next() function. - - await rateLimits.rateLimitByResource(req, res, next) - // console.log(`next() called: ${next.called}`) - } - - // Note: next() will be called unless the rate-limit kicks in. - assert.equal( - next.called, - false, - 'next should not be called if rate limit was triggered.' - ) - }) - - it('should NOT trigger rate-limit handler for indexer-tier at 25 RPM', async () => { - // Create a new instance of the rate limit so we start with zeroed tracking. - rateLimits = new RateLimits() - - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - req.locals.jwtToken = 'some-token' - - const jwtInfo = { - apiLevel: 20, - id: '5e3a0415eb29a962da2708b3' - } - - // Mock the call to the jwt library. - sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo) - - for (let i = 0; i < 25; i++) { - next.reset() // reset the stubbed next() function. - - await rateLimits.rateLimitByResource(req, res, next) - // console.log(`next() called: ${next.called}`) - } - - // console.log(`req.locals after test: ${util.inspect(req.locals)}`) - - // Note: next() will be called unless the rate-limit kicks in. - assert.equal( - next.called, - true, - 'next should be called if rate limit was not triggered.' - ) - }) - - it('should still rate-limit at a higher RPM for pro-tier', async () => { - // Create a new instance of the rate limit so we start with zeroed tracking. - rateLimits = new RateLimits() - - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - req.locals.jwtToken = 'some-token' - - const jwtInfo = { - apiLevel: 20, - id: '5e3a0415eb29a962da2708b5' - } - - // Mock the call to the jwt library. - sandbox.stub(rateLimits.jwt, 'verify').returns(jwtInfo) - - for (let i = 0; i < 150; i++) { - next.reset() // reset the stubbed next() function. - - await rateLimits.rateLimitByResource(req, res, next) - // console.log(`next() called: ${next.called}`) - } - - // console.log(`req.locals after test: ${util.inspect(req.locals)}`) - - // Note: next() will be called unless the rate-limit kicks in. - assert.equal( - next.called, - false, - 'next should NOT be called if rate limit was triggered.' - ) - }) - - it('should handle misconfigured token secret', async () => { - // Create a new instance of the rate limit so we start with zeroed tracking. - rateLimits = new RateLimits() - - req.baseUrl = '/v3' - req.path = '/control/getNetworkInfo' - req.url = req.path - req.method = 'GET' - - req.locals.jwtToken = 'some-token' - - next.reset() // reset the stubbed next() function. - - await rateLimits.rateLimitByResource(req, res, next) - - // Issues with token secret should treat incoming requests as anonymous - // calls with 30 points or 3 RPM. - assert.equal(res.locals.pointsToConsume, 50) - }) - }) -}) - -// Generates a Basic authorization header. -// function generateAuthHeader (pass) { -// // https://en.wikipedia.org/wiki/Basic_access_authentication -// const username = 'BITBOX' -// const combined = `${username}:${pass}` -// -// var base64Credential = Buffer.from(combined).toString('base64') -// var readyCredential = `Basic ${base64Credential}` -// -// return readyCredential -// } diff --git a/test/v3/raw-transactions.js b/test/v3/raw-transactions.js deleted file mode 100644 index 6b4f772..0000000 --- a/test/v3/raw-transactions.js +++ /dev/null @@ -1,1177 +0,0 @@ -/* - TESTS FOR THE RAWTRANSACTIONS.TS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - TODO: - -Create e2e test for sendRawTransaction. - -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const Rawtransactions = require('../../src/routes/v3/full-node/rawtransactions') -const uut = new Rawtransactions() - -// const nock = require('nock') // HTTP mocking - -const sinon = require('sinon') -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -// delete require.cache[require.resolve("./mocks/express-mocks")] // Fixes bug -// const { mockReq, mockRes, mockNext } = require('./mocks/express-mocks') -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/raw-transactions-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 5 } - -describe('#Raw-Transactions', () => { - // let req, res, next - let req, res - let sandbox - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - // next = mockNext - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - req.locals = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Restore Sandbox - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - it('should respond to GET for base route', async () => { - const result = uut.root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'rawtransactions', 'Returns static string') - }) - }) - - describe('decodeRawTransactionSingle()', () => { - it('should throw error if hex is missing', async () => { - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hex can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should GET /decodeRawTransaction', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) - } - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout' - ]) - assert.isArray(result.vin) - assert.isArray(result.vout) - }) - }) - - describe('decodeRawTransactionBulk()', () => { - it('should throw 400 error if hexes array is missing', async () => { - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hexes must be an array') - }) - - it('should throw 400 error if hexes array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.hexes = testArray - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if hexes is empty', async () => { - req.body.hexes = [''] - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty hex') - }) - - it('should error on non-array single hex', async () => { - req.body.hexes = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeRawTransactionBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hexes must be an array', - 'Proper error message' - ) - }) - it('returns proper error when downstream service stalls', async () => { - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should decode an array with a single hex', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) - } - - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout' - ]) - assert.isArray(result[0].vin) - assert.isArray(result[0].vout) - }) - - it('should decode an array with multiple hexes', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeRawTransaction } }) - } - - req.body.hexes = [ - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000', - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - ] - - const result = await uut.decodeRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout' - ]) - assert.isArray(result[0].vin) - assert.isArray(result[0].vout) - }) - }) - - describe('decodeScriptSingle()', () => { - it('should throw error if hex is missing', async () => { - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hex can not be empty') - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - await uut.decodeScriptSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - it('should GET /decodeScriptSingle', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeScript } }) - } - - req.params.hex = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeScriptSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['asm', 'type', 'p2sh']) - }) - }) - - describe('decodeScriptBulk()', () => { - it('should throw 400 error if hexes array is missing', async () => { - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hexes must be an array') - }) - - it('should throw 400 error if hexes array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.hexes = testArray - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if hexes is empty', async () => { - req.body.hexes = [''] - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty hex') - }) - - it('should error on non-array single hex', async () => { - req.body.hexes = - '0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000' - - const result = await uut.decodeScriptBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hexes must be an array', - 'Proper error message' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should decode an array with a single hex', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeScript } }) - } - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['asm', 'type', 'p2sh']) - }) - - it('should decode an array with a multiple hexes', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockDecodeScript } }) - } - - req.body.hexes = [ - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16', - '4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16' - ] - - const result = await uut.decodeScriptBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2) - assert.hasAllKeys(result[0], ['asm', 'type', 'p2sh']) - }) - }) - - describe('getRawTransactionBulk()', () => { - it('should throw 400 error if txids array is missing', async () => { - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids must be an array') - }) - - it('should throw 400 error if txids array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if txid is empty', async () => { - req.body.txids = [''] - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty TXID') - }) - - it('should throw 400 error if txid is invalid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .rejects('parameter 1 must be of length 64 (not 6)') - } - - req.body.txids = ['abc123'] - - const result = await uut.getRawTransactionBulk(req, res) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'parameter 1 must be of length 64 (not 6)') - }) - - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should get concise transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionConcise } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.isString(result[0]) - }) - - it('should get verbose transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionVerbose } }) - } - - req.body.txids = [ - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - ] - req.body.verbose = true - - const result = await uut.getRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'hex', - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout', - 'blockhash', - 'confirmations', - 'time', - 'blocktime' - ]) - assert.isArray(result[0].vin) - assert.isArray(result[0].vout) - }) - }) - - describe('getRawTransactionSingle()', () => { - it('should throw 400 error if txid is missing', async () => { - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 error if txid is invalid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .rejects('parameter 1 must be of length 64 (not 6)') - } - - req.params.txid = 'abc123' - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - // console.log(`res.statusCode: ${res.statusCode}`) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'parameter 1 must be of length 64 (not 6)') - }) - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should get concise transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionConcise } }) - } - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isString(result) - }) - - it('should get verbose transaction data', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox - .stub(uut.axios, 'request') - .resolves({ data: { result: mockData.mockRawTransactionVerbose } }) - } - - req.params.txid = - '2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266' - req.query.verbose = 'true' - - const result = await uut.getRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'hex', - 'txid', - 'hash', - 'size', - 'version', - 'locktime', - 'vin', - 'vout', - 'blockhash', - 'confirmations', - 'time', - 'blocktime' - ]) - assert.isArray(result.vin) - assert.isArray(result.vout) - }) - }) - - describe('sendRawTransactionBulk()', () => { - it('should throw 400 error if hexs array is missing', async () => { - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'hex must be an array') - }) - - it('should throw 400 error if hexs array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.hexes = testArray - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw 400 error if hex array element is empty', async () => { - req.body.hexes = [''] - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Encountered empty hex') - }) - - it('should throw 400 error if hex is invalid', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').rejects({ - response: { - data: { error: { code: -22, message: 'TX decode failed' } } - } - }) - } - - req.body.hexes = ['abc123'] - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'TX decode failed') - }) - - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.hexes = [ - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.body.hexes = [ - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - ] - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should submit hex encoded transaction', async () => { - // This is a difficult test to run as transaction hex is invalid after a - // block confirmation. So the unit tests simulates what the output 'should' - // be, but the integration asserts an expected failure. - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { - result: - 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' - } - }) - } - - req.body.hexes = [ - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - ] - - const result = await uut.sendRawTransactionBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - if (process.env.TEST === 'unit') { - assert.isArray(result) - assert.isString(result[0]) - - // Integration test - } else { - if (process.env.ISBCHN) { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Missing inputs') - } else { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'bad-txns-inputs-missingorspent') - } - } - }) - }) - - describe('sendRawTransactionSingle()', () => { - it('should throw an error for an empty hex', async () => { - req.params.hex = '' - - const result = await uut.sendRawTransactionSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Encountered empty hex', - 'Proper error message' - ) - }) - - it('should throw an error for a non-string', async () => { - req.params.hex = 456 - - const result = await uut.sendRawTransactionSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'hex must be a string', - 'Proper error message' - ) - }) - - it('should throw 500 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl = process.env.BITCOINCOM_BASEURL - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or great expected.' - ) - }) - - it('should throw an error for invalid hex', async () => { - req.params.hex = 'abc123' - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').rejects({ - response: { - data: { error: { code: -22, message: 'TX decode failed' } } - } - }) - } - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'TX decode failed') - }) - - it('returns proper error when downstream service stalls', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' }) - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('returns proper error when downstream service is down', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - - // Mock the timeout error. - sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - }) - - it('should GET /sendRawTransaction/:hex', async () => { - // This is a difficult test to run as transaction hex is invalid after a - // block confirmation. So the unit tests simulates what the output 'should' - // be, but the integration asserts an expected failure. - - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(uut.axios, 'request').resolves({ - data: { - result: - 'aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118' - } - }) - } - - req.params.hex = - '020000000136697692fed77bc4f5b6885295d0c56d1d0280fb578f445ce42be4eb6db381f2010000006a4730440220473adba0e7da14f0abf4817bbd591741ecb8da6544b998f10341f6704f5f05280220405221c626cb7edcf333367ebd469aff3f5a2169e37ee58eebb811ffc2fbc9e0412102202ff86325c5d903171fa5a2895c4efb3765105115460dc96f113048ddb69b47feffffff027a621b00000000001976a914e0a8ffc3b91e35f46618d6db90f66397989abf0588ac38041300000000001976a914a741f282af390bc7ea8c4375a3a56401d668564288ac2c330900' - - const result = await uut.sendRawTransactionSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - if (process.env.TEST === 'unit') { - assert.isString(result) - - // Integration test - } else { - if (process.env.ISBCHN) { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Missing inputs') - } else { - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'bad-txns-inputs-missingorspent') - } - } - }) - }) -}) diff --git a/test/v3/slp.js b/test/v3/slp.js deleted file mode 100644 index e847904..0000000 --- a/test/v3/slp.js +++ /dev/null @@ -1,1655 +0,0 @@ -/* - TESTS FOR THE SLP.TS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - TODO: - -See listSingleToken() tests. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const sinon = require('sinon') -// const proxyquire = require('proxyquire').noPreserveCache() -// const axios = require('axios') - -// Save existing environment variables. -// Used during transition from integration to unit tests. - -// eslint-disable-next-line no-unused-vars -let mockServerUrl -const originalEnvVars = { - BITDB_URL: process.env.BITDB_URL, - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - SLPDB_URL: process.env.SLPDB_URL -} - -// Set default environment variables for unit tests. -if (!process.env.TEST) process.env.TEST = 'unit' - -// Block network connections for unit tests. -if (process.env.TEST === 'unit') { - process.env.BITDB_URL = 'http://fakeurl/' - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/' - process.env.SLPDB_URL = 'http://fakeurl/' - mockServerUrl = 'http://fakeurl' -} - -// Prepare the slpRoute for stubbing dependcies on slpjs. -const SlpRoute = require('../../src/routes/v3/slp') -const slpRoute = new SlpRoute() - -// const pathStub = {} // Used to stub methods within slpjs. -// const slpRouteStub = proxyquire('../../src/routes/v3/slp', { slpjs: pathStub }) - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/slp-mocks') -// const slpjsMock = require('./mocks/slpjs-mocks') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#SLP', () => { - let req, res - let sandbox - - before(() => {}) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - req.locals = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITDB_URL = originalEnvVars.BITDB_URL - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.SLPDB_URL = originalEnvVars.SLPDB_URL - }) - - describe('#root', async () => { - // root route handler. - const root = slpRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'slp', 'Returns static string') - }) - }) - - describe('balancesForAddress()', () => { - const balancesForAddress = slpRoute.balancesForAddress - - it('should throw 400 if address is empty', async () => { - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get token balance for an address', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockSingleAddress - }) - } - - req.params.address = - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - - const result = await balancesForAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - - assert.property(result[0], 'tokenId') - assert.property(result[0], 'balanceString') - assert.property(result[0], 'slpAddress') - assert.property(result[0], 'balance') - assert.property(result[0], 'decimalCount') - - assert.isNumber(result[0].balance) - assert.isNumber(result[0].decimalCount) - }) - }) - - describe('balancesForAddressBulk()', () => { - const balancesForAddressBulk = slpRoute.balancesForAddressBulk - - it('should throw 400 if addresses is empty', async () => { - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'addresses needs to be an array') - }) - - it('should throw 400 if address is invalid', async () => { - req.body.addresses = ['badAddress'] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.body.addresses = [ - 'slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should throw 5XX error when network issues', async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = 'http://fakeurl/api/' - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - // Only run as an integration test. Too complex to stub accurately. - if (process.env.TEST !== 'unit') { - it('should get token balance for an address', async () => { - req.body.addresses = [ - 'simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn' - ] - - const result = await balancesForAddressBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.isArray(result[0]) - assert.hasAnyKeys(result[0][0], [ - 'tokenId', - 'balance', - 'balanceString', - 'slpAddress', - 'decimalCount' - ]) - }) - } - }) - - describe('validateBulk()', () => { - const validateBulk = slpRoute.validateBulk - - it('should throw 400 if txid array is empty', async () => { - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txids needs to be an array') - assert.equal(res.statusCode, 400) - }) - - it('should throw 400 error if array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - const result = await validateBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should validate array with single element', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockSingleValidTxid - }) - } - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d' - ] - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - }) - - it('should validate array with two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockTwoValidTxid - }) - } - - req.body.txids = [ - '77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d', - '552112f9e458dc7d1d8b328b0a6685e8af74a64b60b6846e7c86407f27f47e42' - ] - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - - // Captures a regression bug that went out to production, captured in this - // GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518 - it('should return two elements if given two elements', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: mockData.mockTwoRedundentTxid - }) - } - - req.body.txids = [ - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56' - ] - - const result = await validateBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ['txid', 'valid']) - assert.equal(result.length, 2) - }) - }) - - describe('#validate2Single', () => { - it('should throw 400 if txid is empty', async () => { - req.params.txid = '' - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should invalidate a known invalid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { isValid: false } }) - } - - const txid = - 'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, false) - }) - - it('should validate a known valid TXID', async () => { - if (process.env.TEST === 'unit') { - // Mock to prevent live network connection. - sandbox - .stub(slpRoute.axios, 'request') - .resolves({ data: { isValid: true } }) - } - - const txid = - '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result.txid, txid) - assert.equal(result.isValid, true) - }) - - // This test can only be run as a mocked unit test. It's too inconsistent - // to run as an integration test, due to the caching built into slp-validate. - if (process.env.TEST === 'unit') { - it('should cancel if validation takes too long', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const txid = - 'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579' - - req.params.txid = txid - const result = await slpRoute.validate2Single(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - } - }) - - describe('tokenStats()', () => { - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox - .stub(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox - .stub(slpRoute.slpdb, 'getTokenStats') - .throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await slpRoute.tokenStats(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - }) - - describe('balancesForTokenSingle()', () => { - const balancesForTokenSingle = slpRoute.balancesForTokenSingle - - it('should throw 400 if tokenID is empty', async () => { - req.params.tokenId = '' - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get balances for tokenId', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(slpRoute.axios, 'request').resolves({ - data: { - g: [mockData.mockBalance] - } - }) - } - - req.params.tokenId = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await balancesForTokenSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.property(result[0], 'tokenId') - assert.property(result[0], 'slpAddress') - assert.property(result[0], 'tokenBalanceString') - }) - }) - - describe('txDetails()', () => { - const txDetails = slpRoute.txDetails - - it('should throw 400 if txid is empty', async () => { - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should throw 400 for malformed txid', async () => { - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457' - - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'This is not a txid') - }) - - it('should throw 400 for non-existant txid', async () => { - // Integration test - if (process.env.TEST !== 'unit') { - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'TXID not found') - } - }) - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.txid = - '57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b94578223333' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - if (process.env.TEST === 'integration') { - it('should get tx details with token info', async () => { - // TODO: add mocking for unit testing. How do I mock reponse form SLPDB - // since it's not an object? - - // if (process.env.TEST === "unit") { - // // Mock the slpjs library for unit tests. - // pathStub.BitboxNetwork = slpjsMock.BitboxNetwork - // txDetails = slpRouteStub.testableComponents.txDetails - // } - - req.params.txid = - '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7' - - const result = await txDetails(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result, ['tokenIsValid', 'tokenInfo']) - }) - } - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = slpRoute.txsTokenIdAddressSingle - - it('should throw 400 if tokenId is empty', async () => { - req.params.tokenId = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenId can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.tokenId = - '495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a' - req.params.address = '' - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('returns proper error when downstream service stalls', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - // Mock the timeout error. - sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' }) - - req.params.tokenId = - '7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796' - req.params.address = 'slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h' - - const result = await txsTokenIdAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - }) - - describe('txsByAddressSingle()', () => { - const txsByAddressSingle = slpRoute.txsByAddressSingle - - it('should throw 400 if address is missing', async () => { - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is empty', async () => { - req.params.address = '' - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should throw 400 if address is invalid', async () => { - req.params.address = 'badAddress' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid BCH address.') - }) - - it('should throw 400 if address network mismatch', async () => { - req.params.address = 'slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0' - - const result = await txsByAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Invalid') - }) - - it('should get tx history', async () => { - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute.slpdb, 'getHistoricalSlpTransactions') - .resolves(mockData.mockTxHistory) - } - - // req.params.address = 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk' - req.params.address = - 'simpleledger:qz4guf2k3p4r3t4tph0wwgyfq4p628lr2c0cvqplza' - - const result = await slpRoute.txsByAddressSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - }) - }) - - describe('generateSendOpReturn()', () => { - const generateSendOpReturn = slpRoute.generateSendOpReturn - // Validate tokenUtxos input - it('should throw 400 if tokenUtxos is missing', async () => { - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos needs to be an array.') - }) - - it('should throw 400 if tokenUtxos is empty', async () => { - req.body.tokenUtxos = '' - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos needs to be an array.') - }) - - it('should throw 400 if tokenUtxos is not array', async () => { - req.body.tokenUtxos = 'tokenUtxos' - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos needs to be an array.') - }) - - it('should throw 400 if tokenUtxos is empty array', async () => { - req.body.tokenUtxos = [] - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'tokenUtxos array can not be empty.') - }) - - // Validate sendQty input - it('should throw 400 if sendQty is missing', async () => { - req.body.tokenUtxos = [{}, {}, {}] - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'sendQty must be a number') - }) - - it('should throw 400 if sendQty is empty', async () => { - req.body.tokenUtxos = [{}, {}, {}] - req.body.sendQty = '' - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'sendQty must be a number') - }) - - it('should throw 400 if sendQty is not a number', async () => { - req.body.tokenUtxos = [{}, {}, {}] - req.body.sendQty = 'sendQty' - - const result = await generateSendOpReturn(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'sendQty must be a number') - }) - - it('should return OP_RETURN script', async () => { - req.body.tokenUtxos = [ - { - tokenId: - '0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1', - decimals: 0, - tokenQty: 2 - } - ] - req.body.sendQty = 1.5 - - if (process.env.TEST === 'unit') { - sandbox - .stub(slpRoute.bchjs.SLP.TokenType1, 'generateSendOpReturn') - .resolves({ script: ' { - it('should throw error if input is not an array.', async () => { - req.body.utxos = 'test' - - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: `, result) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Input must be an array') - }) - - it('should throw error if Array is empty', async () => { - req.body.utxos = [] - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array should not be empty') - }) - - it('should throw error if Array is too long', async () => { - const utxo = { - txid: - 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90', - vout: 3, - amount: 0.00002015, - satoshis: 2015, - height: 594892, - confirmations: 5 - } - - const utxos = [] - - // Populate array with 21 utxos - for (let i = 0; i < 21; i++) { - utxos.push(utxo) - } - - req.body.utxos = utxos - - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too long, max length is 20') - }) - - it('should return utxo details', async () => { - const utxos = [ - { - utxos: [ - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816 - }, - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546 - } - ] - } - ] - - // Mock the external network call. - sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([ - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 3, - value: '6816', - height: 606848, - confirmations: 13, - satoshis: 6816, - isValid: false - }, - { - txid: - 'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56', - vout: 2, - value: '546', - height: 606848, - confirmations: 13, - satoshis: 546, - utxoType: 'token', - transactionType: 'send', - tokenId: - 'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d', - tokenTicker: 'TAP', - tokenName: 'Thoughts and Prayers', - tokenDocumentUrl: '', - tokenDocumentHash: '', - decimals: 0, - tokenType: 1, - tokenQty: 5, - isValid: true - } - ]) - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - // Test the general structure of the output. - assert.isArray(result.slpUtxos) - assert.equal(result.slpUtxos.length, 1) - assert.equal(result.slpUtxos[0].utxos.length, 2) - - // Test the non-slp UTXO. - assert.property(result.slpUtxos[0].utxos[0], 'txid') - assert.property(result.slpUtxos[0].utxos[0], 'vout') - assert.property(result.slpUtxos[0].utxos[0], 'value') - assert.property(result.slpUtxos[0].utxos[0], 'height') - assert.property(result.slpUtxos[0].utxos[0], 'confirmations') - assert.property(result.slpUtxos[0].utxos[0], 'satoshis') - assert.property(result.slpUtxos[0].utxos[0], 'isValid') - assert.equal(result.slpUtxos[0].utxos[0].isValid, false) - - // Test the slp UTXO. - assert.property(result.slpUtxos[0].utxos[1], 'txid') - assert.property(result.slpUtxos[0].utxos[1], 'vout') - assert.property(result.slpUtxos[0].utxos[1], 'value') - assert.property(result.slpUtxos[0].utxos[1], 'height') - assert.property(result.slpUtxos[0].utxos[1], 'confirmations') - assert.property(result.slpUtxos[0].utxos[1], 'satoshis') - assert.property(result.slpUtxos[0].utxos[1], 'isValid') - assert.equal(result.slpUtxos[0].utxos[1].isValid, true) - assert.property(result.slpUtxos[0].utxos[1], 'transactionType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenId') - assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker') - assert.property(result.slpUtxos[0].utxos[1], 'tokenName') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl') - assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash') - assert.property(result.slpUtxos[0].utxos[1], 'decimals') - assert.property(result.slpUtxos[0].utxos[1], 'tokenType') - assert.property(result.slpUtxos[0].utxos[1], 'tokenQty') - }) - - it('should throw error for missing properties', async () => { - const utxos = [ - { - height: 639443, - tx_hash: - '30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df', - tx_pos: 0, - value: 6000 - }, - { - height: 639443, - tx_hash: - '8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50', - tx_pos: 1, - value: 546 - } - ] - - req.body.utxos = utxos - const result = await slpRoute.hydrateUtxos(req, res) - - assert.hasAllKeys(result, ['error']) - assert.include( - result.error, - 'Each element in array should have a utxos property' - ) - }) - }) -}) - -/* - describe("listSingleToken()", () => { - const listSingleToken = slpRoute.testableComponents.listSingleToken - - it("should throw 400 if tokenId is empty", async () => { - const result = await listSingleToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.params.tokenId = - // testnet - "d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e" - // mainnet - //"259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["id"]) - assert.include(result.id, "not found") - }) - - it("should get token information", async () => { - // testnet - const tokenIdToTest = - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - - // console.log(`mockServerUrl: ${mockServerUrl}`) - - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - // sandbox.stub(axios, "get").resolves(mockData.mockSingleToken) - } - - req.params.tokenId = tokenIdToTest - - const result = await listSingleToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("listBulkToken()", () => { - const listBulkToken = slpRoute.testableComponents.listBulkToken - - it("should throw 400 if tokenIds array is empty", async () => { - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenIds needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.tokenIds = testArray - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should throw 400 if tokenId is empty", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockEmptyTokenId) - } - req.body.tokenIds = "" - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include( - result.error, - "tokenIds needs to be an array. Use GET for single tokenId." - ) - }) - - it("should throw 503 when network issues", async () => { - // Save the existing BITDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.body.tokenIds = [ - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - ] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - //assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it("should return 'not found' for testnet txid on mainnet", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleTokenError) - } - - req.body.tokenIds = - // testnet - ["d284e71227ec89f713b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e"] - // mainnet - // ["0b314bc2b2905b8844222871c6b665ae3494117c83b11302824561bb904efb6b"] - - const result = await listBulkToken(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], ["id", "valid"]) - assert.strictEqual(result[0].valid, false) - }) - - it("should get token information for single token ID", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "id", - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - - it("should get token information for multiple token IDs", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleToken) - } - - req.body.tokenIds = [ - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", - "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" - ] - - const result = await listBulkToken(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "blockCreated", - "blockLastActiveMint", - "blockLastActiveSend", - "circulatingSupply", - "containsBaton", - "mintingBatonStatus", - "txnsSinceGenesis", - "versionType", - "timestamp", - "symbol", - "name", - "documentUri", - "documentHash", - "decimals", - "initialTokenQty", - "id", - "totalBurned", - "totalMinted", - "validAddresses", - "timestamp_unix" - ]) - }) - }) - - describe("balancesForAddressByTokenID()", () => { - const balancesForAddressByTokenID = - slpRoute.testableComponents.balancesForAddressByTokenID - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - - it("should throw 400 if tokenId is empty", async () => { - req.params.address = - "simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk" - req.params.tokenId = "" - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "tokenId can not be empty") - }) - - it("should throw 400 if address is invalid", async () => { - req.params.address = "badAddress" - req.params.tokenId = - "650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid BCH address.") - }) - - it("should throw 400 if address network mismatch", async () => { - req.params.address = "slptest:qzcvpw3ah7r880d49wsqzrsl90pg0rqjjurmj3g4nk" - - const result = await balancesForAddressByTokenID(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Invalid") - }) - - it("should throw 5XX error when network issues", async () => { - // Save the existing SLPDB_URL. - const savedUrl2 = process.env.SLPDB_URL - - // Manipulate the URL to cause a 500 network error. - process.env.SLPDB_URL = "http://fakeurl/api/" - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.SLPDB_URL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - "HTTP status code 500 or greater expected." - ) - assert.include( - result.error, - "Network error: Could not communicate", - "Error message expected" - ) - }) - - it("should get token information", async () => { - if (process.env.TEST === "unit") { - nock(mockServerUrl) - .get(uri => uri.includes("/")) - .times(2) - .reply(200, mockData.mockSingleAddress) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - req.params.tokenId = - "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" - - const result = await balancesForAddressByTokenID(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // TODO - add decimalCount - // assert.hasAllKeys(result, ["tokenId", "balance", "decimalCount"]) - assert.hasAllKeys(result, ["tokenId", "balance"]) - }) - }) - - describe("convertAddressSingle()", () => { - const convertAddressSingle = - slpRoute.testableComponents.convertAddressSingle - - it("should throw 400 if address is empty", async () => { - req.params.address = "" - const result = await convertAddressSingle(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "address can not be empty") - }) - // - it("should convert address", async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .post(uri => uri.includes("/")) - .reply(200, { result: mockData.mockConvert }) - } - - req.params.address = - "simpleledger:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5me5fdrdn" - - const result = await convertAddressSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["cashAddress", "legacyAddress", "slpAddress"]) - }) - }) - - describe("convertAddressBulk()", () => { - const convertAddressBulk = slpRoute.testableComponents.convertAddressBulk - - it("should throw 400 if addresses array is empty", async () => { - const result = await convertAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "addresses needs to be an array") - assert.equal(res.statusCode, 400) - }) - - it("should throw 400 error if array is too large", async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push("") - - req.body.addresses = testArray - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ["error"]) - assert.include(result.error, "Array too large") - }) - - it("should error on malformed address", async () => { - try { - req.body.addresses = ["bitcoincash:qzs02v05l7qs5s5dwuj0cx5ehjm2c"] - - await convertAddressBulk(req, res) - - assert.equal(true, false, "Unexpected result!") - } catch (err) { - // console.log(`err.message: ${util.inspect(err.message)}`) - - assert.include( - err.message, - `Invalid BCH address. Double check your address is valid` - ) - } - }) - - it("should validate array with single element", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - - it("should validate array with multiple elements", async () => { - req.body.addresses = [ - "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", - "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0" - ] - - const result = await convertAddressBulk(req, res) - //console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAllKeys(result[0], [ - "slpAddress", - "cashAddress", - "legacyAddress" - ]) - }) - }) - - describe('txsTokenIdAddressSingle()', () => { - const txsTokenIdAddressSingle = - slpRoute.txsTokenIdAddressSingle - - it("should get tx details with tokenId and address", async () => { - if (process.env.TEST === "unit") { - nock(`${process.env.SLPDB_URL}`) - .get(uri => uri.includes("/")) - .reply(200, { - c: mockData.mockTransactions - }) - } - - //req.params.tokenId = - // "37279c7dc81ceb34d12f03344b601c582e931e05d0e552c29c428bfa39d39af3" - //req.params.address = "slptest:qr83cu3p7yg9yac7qthwm0nul2ev2kukvsqmes3vl0" - - req.params.tokenId = - "7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796" - req.params.address = "slptest:qpwa35xq0q0cnmdu0rwzkct369hddzsqpsqdzw6h9h" - - const result = await txsTokenIdAddressSingle(req, res) - console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result[0], ["txid", "tokenDetails"]) - }) - -}) - -*/ diff --git a/test/v3/util.js b/test/v3/util.js deleted file mode 100644 index 76dbbe7..0000000 --- a/test/v3/util.js +++ /dev/null @@ -1,519 +0,0 @@ -/* - TESTS FOR THE UTIL.TS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const utilRoute = require('../../src/routes/v3/util') -const nock = require('nock') // HTTP mocking -const sinon = require('sinon') - -let originalEnvVars // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/util-mocks') - -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -// const UtilRoute = utilRoute.UtilRoute -const utilRouteInst = new utilRoute.UtilRoute() - -describe('#Util', () => { - let req, res - let sandbox - - before(() => { - // Save existing environment variables. - originalEnvVars = { - BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, - RPC_BASEURL: process.env.RPC_BASEURL, - RPC_USERNAME: process.env.RPC_USERNAME, - RPC_PASSWORD: process.env.RPC_PASSWORD - } - - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - if (process.env.TEST === 'unit') { - process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/' - process.env.RPC_BASEURL = 'http://fakeurl/api' - process.env.RPC_USERNAME = 'fakeusername' - process.env.RPC_PASSWORD = 'fakepassword' - } - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() - - sandbox.restore() - }) - - after(() => { - // Restore any pre-existing environment variables. - process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL - process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL - process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME - process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD - }) - - describe('#root', async () => { - // root route handler. - const root = utilRouteInst.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(result.status, 'util', 'Returns static string') - }) - }) - - describe('#validateAddressSingle', async () => { - const validateAddress = utilRouteInst.validateAddressSingle - - it('should throw an error for an empty address', async () => { - const result = await validateAddress(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be empty', - 'Proper error message' - ) - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.params.address = 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y' - - await validateAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - // assert.include(result.error,"Network error: Could not communicate with full node","Error message expected") - }) - - it('should validate address', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post((uri) => uri.includes('/')) - .reply(200, { result: mockData.mockAddress }) - } - - req.params.address = - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - - const result = await validateAddress(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'isvalid', - 'address', - 'scriptPubKey', - 'ismine', - 'iswatchonly', - 'isscript' - ]) - }) - }) - - describe('#validateAddressBulk', async () => { - const validateAddressBulk = utilRouteInst.validateAddressBulk - - it('should throw an error for an empty body', async () => { - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array. Use GET for single address.', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - addresses: 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y' - } - - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array. Use GET for single address.', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should error on invalid address', async () => { - req.body = { - addresses: ['bchtest:qqqk4y6lsl5da64sg5qc3xezmpl'] - } - - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address. Double check your address is valid', - 'Proper error message' - ) - }) - - it('should error on mainnet address when using testnet', async () => { - req.body = { - addresses: ['bchtest:qrls6vzjkkxlds7aqv9075u0fttwc7u9jvczn5fdt9'] - } - - const result = await validateAddressBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid network. Trying to use a testnet address on mainnet, or vice versa.', - 'Proper error message' - ) - }) - - it('should throw 503 when network issues', async () => { - // Save the existing RPC URL. - const savedUrl2 = process.env.RPC_BASEURL - - // Manipulate the URL to cause a 500 network error. - process.env.RPC_BASEURL = 'http://fakeurl/api/' - - req.body.addresses = [ - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - ] - - await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.RPC_BASEURL = savedUrl2 - - assert.isAbove( - res.statusCode, - 499, - 'HTTP status code 500 or greater expected.' - ) - }) - - it('should validate a single address', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post((uri) => uri.includes('/')) - .reply(200, { result: mockData.mockAddress }) - } - - req.body.addresses = [ - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - ] - - const result = await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'isvalid', - 'address', - 'scriptPubKey', - 'ismine', - 'iswatchonly', - 'isscript' - ]) - }) - - it('should validate a multiple addresses', async () => { - // Mock the RPC call for unit tests. - if (process.env.TEST === 'unit') { - nock(`${process.env.RPC_BASEURL}`) - .post((uri) => uri.includes('/')) - .times(2) - .reply(200, { result: mockData.mockAddress }) - } - - req.body.addresses = [ - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd', - 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' - ] - - const result = await validateAddressBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'isvalid', - 'address', - 'scriptPubKey', - 'ismine', - 'iswatchonly', - 'isscript' - ]) - }) - }) - - describe('#sweepWif', () => { - it('should throw 400 if WIF is not included', async () => { - req.body = {} - - const result = await utilRouteInst.sweepWif(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'WIF needs to a proper compressed WIF starting with K or L', - 'Proper error message' - ) - }) - - it('should throw 400 if WIF is malformed', async () => { - req.body = { - wif: 'abc123' - } - - const result = await utilRouteInst.sweepWif(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'WIF needs to a proper compressed WIF starting with K or L', - 'Proper error message' - ) - }) - - it('should throw 400 if destination address is not included', async () => { - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt' - } - - const result = await utilRouteInst.sweepWif(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be empty', - 'Proper error message' - ) - }) - - // Unit test only. - if (process.env.TEST === 'unit') { - it('should generate transaction for valid token sweep', async () => { - // Mock the RPC call for unit tests. - - sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') - .resolves(mockData.mockUtxos) - - sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves(mockData.mockIsTokenUtxos) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRouteInst.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, 'test-txid') - }) - - it('should return balance if balance-only is true', async () => { - // Mock the RPC call for unit tests. - - sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') - .resolves(mockData.mockBalance) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - balanceOnly: true - } - - const result = await utilRouteInst.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isNumber(result) - }) - } - - // Unit tests only - if (process.env.TEST === 'unit') { - it('should generate transaction for valid BCH-only sweep', async () => { - sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') - .resolves(mockData.mockUtxos) - - // Force token utxo to appear as regular BCH utxo. - sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves([false, false]) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRouteInst.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(result, 'test-txid') - }) - - it('should throw 422 error if no non-token UTXOs', async () => { - sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') - .resolves(mockData.mockUtxos) - - // Force token utxo to appear as regular BCH utxo. - sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves(mockData.tokensOnly) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRouteInst.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(res.statusCode, 422) - assert.property(result, 'error') - assert.include( - result.error, - 'Tokens found, but no BCH UTXOs found. Add BCH to wallet to move tokens' - ) - }) - - it('should detect and throw error for multiple token classes', async () => { - sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') - .resolves(mockData.mockBalance) - - sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') - .resolves(mockData.mockThreeUtxos) - - // Force token utxo to appear as regular BCH utxo. - sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') - .resolves(mockData.multipleTokens) - - // Mock sendRawTransaction() so that the hex does not actually get broadcast - // to the network. - sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') - .resolves('test-txid') - - req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' - } - - const result = await utilRouteInst.sweepWif(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.equal(res.statusCode, 422) - assert.property(result, 'error') - assert.include( - result.error, - 'Multiple token classes detected. This function only supports a single class of token' - ) - }) - } - }) -}) diff --git a/test/v3/xpub.js b/test/v3/xpub.js deleted file mode 100644 index e921d83..0000000 --- a/test/v3/xpub.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - TESTS FOR THE XPUB.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert -const xpubRoute = require('../../src/routes/v3/xpub') -const nock = require('nock') // HTTP mocking - -// let originalUrl // Used during transition from integration to unit tests. - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -// const mockData = require('./mocks/address-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#XPUBRouter', () => { - let req, res - - before(() => { - // Set default environment variables for unit tests. - if (!process.env.TEST) process.env.TEST = 'unit' - // if (process.env.TEST === "unit") - // process.env.BITCOINCOM_BASEURL = "http://fakeurl/api - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - // Activate nock if it's inactive. - if (!nock.isActive()) nock.activate() - }) - - afterEach(() => { - // Clean up HTTP mocks. - nock.cleanAll() // clear interceptor list. - nock.restore() - }) - - after(() => { - // process.env.BITCOINCOM_BASEURL = originalUrl - }) - - describe('#root', () => { - // root route handler. - const root = xpubRoute.testableComponents.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'address', 'Returns static string') - }) - }) - - describe('#FromXPubSingle', () => { - // details route handler. - const fromXPubSingle = xpubRoute.testableComponents.fromXPubSingle - - it('should throw 400 if xpub is empty', async () => { - const result = await fromXPubSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'xpub can not be empty') - }) - - it('should error on an array', async () => { - req.params.xpub = [ - 'tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM' - ] - - const result = await fromXPubSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'xpub can not be an array', - 'Proper error message' - ) - }) - /* - it("should throw 500 when network issues", async () => { - const savedUrl = process.env.BITCOINCOM_BASEURL - - try { - req.params.xpub = `tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM` - - // Switch the Insight URL to something that will error out. - process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/" - - const result = await fromXPubSingle(req, res) - - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - - assert.equal(res.statusCode, 500, "HTTP status code 500 expected.") - assert.include(result.error, "ENOTFOUND", "Error message expected") - } catch (err) { - // Restore the saved URL. - process.env.BITCOINCOM_BASEURL = savedUrl - } - }) -*/ - it('should create an address from xpub', async () => { - req.params.xpub = 'tpubDHTK2jqg73w3GwoiHfAMbMYML1HN8FhrUxD9rFgbSgHXdwwrY6pAFqKDfUHhqw7vreaZty5hPGjb1S7ZPQeMmu6TFHAKfY9tJpYbvaGjPRM' - - // Mock the Insight URL for unit tests. - // TODO add unit test - // if (process.env.TEST === "unit") { - // nock(`${process.env.BITCOINCOM_BASEURL}`) - // .get( - // `/txs/?address=bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4&pageNum=0` - // ) - // .reply(200, mockData.mockTransactions) - // } - - // Call the details API. - const result = await fromXPubSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - // Assert that required fields exist in the returned object. - assert.exists(result.legacyAddress) - assert.exists(result.cashAddress) - }) - }) -}) diff --git a/test/v4/blockbook.js b/test/v4/blockbook.js deleted file mode 100644 index 5bfe1be..0000000 --- a/test/v4/blockbook.js +++ /dev/null @@ -1,1122 +0,0 @@ -/* - TESTS FOR THE BLOCKBOOK.JS LIBRARY - - This test file uses the environment variable TEST to switch between unit - and integration tests. By default, TEST is set to 'unit'. Set this variable - to 'integration' to run the tests against BCH mainnet. - - To-Do: -*/ - -'use strict' - -const chai = require('chai') -const assert = chai.assert - -const sinon = require('sinon') - -// Used during transition from integration to unit tests. -// let originalUrl -const originalUrl = process.env.BLOCKBOOK_URL - -// Set default environment variables for unit tests. -if (!process.env.TEST) process.env.TEST = 'unit' -if (process.env.TEST === 'unit') { - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' -} - -// Only load blockbook library after setting BLOCKBOOK_URL env var. -const BlockbookRoute = require('../../src/routes/v4/blockbook') -const blockbookRoute = new BlockbookRoute() - -// Mocking data. -const { mockReq, mockRes } = require('./mocks/express-mocks') -const mockData = require('./mocks/blockbook-mock') - -// Used for debugging. -const util = require('util') -util.inspect.defaultOptions = { depth: 1 } - -describe('#Blockbook Router', () => { - let req, res - let sandbox - before(() => { - // console.log(`Testing type is: ${process.env.TEST}`) - - if (!process.env.NETWORK) process.env.NETWORK = 'testnet' - }) - - // Setup the mocks before each test. - beforeEach(() => { - // Mock the req and res objects used by Express routes. - req = mockReq - res = mockRes - - // Explicitly reset the parmas and body. - req.params = {} - req.body = {} - req.query = {} - - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(() => { - process.env.BLOCKBOOK_URL = originalUrl - }) - - describe('#root', () => { - // root route handler. - const root = blockbookRoute.root - - it('should respond to GET for base route', async () => { - const result = root(req, res) - - assert.equal(result.status, 'address', 'Returns static string') - }) - }) - - describe('#Balance Single', () => { - // details route handler. - // const balanceSingle = blockbookRoute.balanceSingle - - it('should throw 400 if address is empty', async () => { - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should error on an array', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await blockbookRoute.balanceSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await blockbookRoute.balanceSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await blockbookRoute.balanceSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.params.address = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await blockbookRoute.balanceSingle(req, res) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - - it('returns proper error when downstream service stalls', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get balance for a single address', async () => { - req.params.address = - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - - // console.log(`process.env.BLOCKBOOK_URL: ${process.env.BLOCKBOOK_URL}`) - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockBalance - }) - } - - // Call the details API. - const result = await blockbookRoute.balanceSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAnyKeys(result, [ - 'page', - 'totalPages', - 'itemsOnPage', - 'address', - 'balance', - 'totalReceived', - 'totalSent', - 'unconfirmedBalance', - 'unconfirmedTxs', - 'txs', - 'txids' - ]) - assert.isArray(result.txids) - }) - }) - - describe('#Balance Bulk', () => { - // details route handler. - const balanceBulk = blockbookRoute.balanceBulk - - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await balanceBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await balanceBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.body = { - addresses: ['bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3'] - } - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await balanceBulk(req, res) - // console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.isAbove(res.statusCode, 499, 'HTTP status code 500 expected.') - // assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await balanceBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockBalance - }) - } - - // Call the details API. - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'page', - 'totalPages', - 'itemsOnPage', - 'address', - 'balance', - 'totalReceived', - 'totalSent', - 'unconfirmedBalance', - 'unconfirmedTxs', - 'txs', - 'txids' - ]) - assert.isArray(result[0].txids) - }) - - it('should get details for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockBalance - }) - } - - // Call the details API. - const result = await balanceBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#UTXOs Single', () => { - // details route handler. - const utxosSingle = blockbookRoute.utxosSingle - - it('should throw 400 if address is empty', async () => { - const result = await utxosSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'address can not be empty') - }) - - it('should error on an array', async () => { - req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - - const result = await utxosSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'address can not be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - const result = await utxosSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should detect a network mismatch', async () => { - req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4' - - const result = await utxosSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.params.address = 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await utxosSingle(req, res) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await utxosSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await utxosSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get utxos for a single address', async () => { - req.params.address = - 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockUtxos - }) - } - - // Call the details API. - const result = await utxosSingle(req, res) - // console.log(`result utxosSingle: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'vout', - 'value', - 'height', - 'confirmations' - ]) - }) - }) - - describe('#UTXO Bulk', () => { - // details route handler. - const utxosBulk = blockbookRoute.utxosBulk - - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body = { - address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c' - } - - const result = await utxosBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'addresses needs to be an array', - 'Proper error message' - ) - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'] - } - - const result = await utxosBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'Invalid BCH address', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.addresses = testArray - - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should detect a network mismatch', async () => { - req.body = { - addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'] - } - - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include(result.error, 'Invalid network', 'Proper error message') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.body = { - addresses: ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'] - } - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await utxosBulk(req, res) - // console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.isAbove(res.statusCode, 499, 'HTTP status code 500 expected.') - // assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - - it('returns proper error when downstream service stalls', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('returns proper error when downstream service is down', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await utxosBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get details for a single address', async () => { - req.body = { - addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockUtxos - }) - } - - // Call the details API. - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.isArray(result[0]) - assert.hasAnyKeys(result[0][0], [ - 'txid', - 'vout', - 'value', - 'height', - 'confirmations' - ]) - }) - - it('should get details for multiple addresses', async () => { - req.body = { - addresses: [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockUtxos - }) - } - - // Call the details API. - const result = await utxosBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.isArray(result[0]) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) - - describe('#txSingle', () => { - // route handler - const txSingle = blockbookRoute.txSingle - - it('should throw 400 if txid is empty', async () => { - const result = await txSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid can not be empty') - }) - - it('should error on an array', async () => { - req.params.txid = [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - - const result = await txSingle(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txid can not be an array', - 'Proper error message' - ) - }) - - it('should throw 400 if txid is not a valid txid', async () => { - req.params.txid = 'abc' - - const result = await txSingle(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'txid must be of length 64') - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await txSingle(req, res) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.equal(res.statusCode, 500, 'HTTP status code 500 expected.') - assert.include(result.error, 'ENOTFOUND', 'Error message expected') - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await txSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await txSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - - it('should get tx details for a single txid', async () => { - req.params.txid = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockTx - }) - } - - // process.env.BLOCKBOOK_URL = `https://157.230.178.198:19131/` - // process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0 - - // Call the details API. - const result = await txSingle(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.hasAnyKeys(result, [ - 'txid', - 'version', - 'vin', - 'vout', - 'blockHash', - 'blockHeight', - 'confirmations', - 'blockTime', - 'value', - 'valueIn', - 'fees', - 'hex' - ]) - - // Vin - assert.isArray(result.vin) - assert.hasAnyKeys(result.vin[0], [ - 'txid', - 'sequence', - 'n', - 'addresses', - 'value', - 'hex' - ]) - - // Vout - assert.isArray(result.vout) - assert.hasAnyKeys(result.vout[0], [ - 'value', - 'n', - 'spent', - 'hex', - 'addresses' - ]) - }) - }) - - describe('#txBulk', () => { - // route handler - const txBulk = blockbookRoute.txBulk - - it('should throw an error for an empty body', async () => { - req.body = {} - - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids need to be an array', - 'Proper error message' - ) - }) - - it('should error on non-array single address', async () => { - req.body.txids = - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - - const result = await txBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txids need to be an array', - 'Proper error message' - ) - }) - - it('should throw 400 error if addresses array is too large', async () => { - const testArray = [] - for (var i = 0; i < 25; i++) testArray.push('') - - req.body.txids = testArray - - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.hasAllKeys(result, ['error']) - assert.include(result.error, 'Array too large') - }) - - it('should throw an error for an invalid address', async () => { - req.body = { - txids: [ - '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392', - 'abc' - ] - } - - const result = await txBulk(req, res) - - assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.') - assert.include( - result.error, - 'txid must be of length 64', - 'Proper error message' - ) - }) - - it('should throw 500 when network issues', async () => { - const savedUrl = process.env.BLOCKBOOK_URL - - try { - req.body = { - txids: [ - '5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392' - ] - } - - // Switch the Insight URL to something that will error out. - process.env.BLOCKBOOK_URL = 'http://fakeurl/api/' - - const result = await txBulk(req, res) - // console.log(`network issue result: ${util.inspect(result)}`) - - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - - assert.isAbove(res.statusCode, 499, 'HTTP status code 500 expected.') - // assert.include(result.error, "ENOTFOUND", "Error message expected") - assert.include( - result.error, - 'Network error: Could not communicate', - 'Error message expected' - ) - } catch (err) { - // Restore the saved URL. - process.env.BLOCKBOOK_URL = savedUrl - } - }) - it('returns proper error when downstream service stalls', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNABORTED' - }) - - const result = await txBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('returns proper error when downstream service is down', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the timeout error. - sandbox.stub(blockbookRoute.axios, 'request').throws({ - code: 'ECONNREFUSED' - }) - - const result = await txBulk(req, res) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.') - assert.include( - result.error, - 'Could not communicate with full node', - 'Error message expected' - ) - }) - it('should get details for a single txid', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockTx - }) - } - - // Call the details API. - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.hasAnyKeys(result[0], [ - 'txid', - 'version', - 'vin', - 'vout', - 'blockHash', - 'blockHeight', - 'confirmations', - 'blockTime', - 'value', - 'valueIn', - 'fees', - 'hex' - ]) - - // Vin - assert.isArray(result[0].vin) - assert.hasAnyKeys(result[0].vin[0], [ - 'txid', - 'sequence', - 'n', - 'addresses', - 'value', - 'hex' - ]) - - // Vout - assert.isArray(result[0].vout) - assert.hasAnyKeys(result[0].vout[0], [ - 'value', - 'n', - 'spent', - 'hex', - 'addresses' - ]) - }) - - it('should get details for multiple txid', async () => { - req.body = { - txids: [ - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d', - '6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d' - ] - } - - // Mock the Insight URL for unit tests. - if (process.env.TEST === 'unit') { - sandbox.stub(blockbookRoute.axios, 'request').resolves({ - data: mockData.mockTx - }) - } - - // Call the details API. - const result = await txBulk(req, res) - // console.log(`result: ${util.inspect(result)}`) - - assert.isArray(result) - assert.equal(result.length, 2, '2 outputs for 2 inputs') - }) - }) -}) From fca0b50a2ad1248de5eec0ae99f3366c8a8483cc Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Mar 2021 08:55:16 -0800 Subject: [PATCH 3/6] Refactored util.js lib --- src/app.js | 5 +++-- src/routes/v4/util.js | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app.js b/src/app.js index dc1318d..ce2265d 100644 --- a/src/app.js +++ b/src/app.js @@ -32,7 +32,7 @@ const ControlV4 = require('./routes/v4/full-node/control') const MiningV4 = require('./routes/v4/full-node/mining') const networkV4 = require('./routes/v4/full-node/network') const RawtransactionsV4 = require('./routes/v4/full-node/rawtransactions') -const utilV4 = require('./routes/v4/util') +const UtilV4 = require('./routes/v4/util') const SlpV4 = require('./routes/v4/slp') const xpubV4 = require('./routes/v4/xpub') const ElectrumXV4 = require('./routes/v4/electrumx') @@ -52,6 +52,7 @@ const electrumxv4 = new ElectrumXV4() electrumxv4.connect() const encryptionv4 = new EncryptionV4() const pricev4 = new PriceV4() +const utilV4 = new UtilV4() const app = express() @@ -114,12 +115,12 @@ app.use(`/${v4prefix}/` + 'control', controlV4.router) app.use(`/${v4prefix}/` + 'mining', miningV4.router) app.use(`/${v4prefix}/` + 'network', networkV4) app.use(`/${v4prefix}/` + 'rawtransactions', rawtransactionsV4.router) -app.use(`/${v4prefix}/` + 'util', utilV4.router) app.use(`/${v4prefix}/` + 'slp', slpV4.router) app.use(`/${v4prefix}/` + 'xpub', xpubV4.router) app.use(`/${v4prefix}/` + 'electrumx', electrumxv4.router) app.use(`/${v4prefix}/` + 'encryption', encryptionv4.router) app.use(`/${v4prefix}/` + 'price', pricev4.router) +app.use(`/${v4prefix}/` + 'util', utilV4.router) const ninsight = new Ninsight() app.use(`/${v4prefix}/` + 'ninsight', ninsight.router) diff --git a/src/routes/v4/util.js b/src/routes/v4/util.js index 033c740..e7a3506 100644 --- a/src/routes/v4/util.js +++ b/src/routes/v4/util.js @@ -6,6 +6,7 @@ const axios = require('axios') const routeUtils = require('./route-utils') const wlogger = require('../../util/winston-logging') + const Blockbook = require('./blockbook') const blockbook = new Blockbook() @@ -41,6 +42,12 @@ class UtilRoute { this.bchjs = bchjs this.blockbook = blockbook + this.router = router + this.router.get('/', this.root) + this.router.get('/validateAddress/:address', this.validateAddressSingle) + this.router.post('/validateAddress', this.validateAddressBulk) + this.router.post('/sweep', this.sweepWif) + _this = this } @@ -585,13 +592,6 @@ class UtilRoute { } } -const utilRoute = new UtilRoute() - -router.get('/', utilRoute.root) -router.get('/validateAddress/:address', utilRoute.validateAddressSingle) -router.post('/validateAddress', utilRoute.validateAddressBulk) -router.post('/sweep', utilRoute.sweepWif) - module.exports = { router, // testableComponents: { From 64e59f607ac36ff17569984a11c9e921798fc4fc Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Mar 2021 10:29:49 -0800 Subject: [PATCH 4/6] Refactored Util route class --- src/routes/v4/util.js | 22 ++++++++++++---------- test/v4/util.js | 5 +++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/routes/v4/util.js b/src/routes/v4/util.js index e7a3506..986f3d6 100644 --- a/src/routes/v4/util.js +++ b/src/routes/v4/util.js @@ -592,13 +592,15 @@ class UtilRoute { } } -module.exports = { - router, - // testableComponents: { - // root, - // validateAddressSingle, - // validateAddressBulk, - // sweepWif - // } - UtilRoute -} +// module.exports = { +// router, +// // testableComponents: { +// // root, +// // validateAddressSingle, +// // validateAddressBulk, +// // sweepWif +// // } +// UtilRoute +// } + +module.exports = UtilRoute diff --git a/test/v4/util.js b/test/v4/util.js index a04a65a..75e309b 100644 --- a/test/v4/util.js +++ b/test/v4/util.js @@ -10,10 +10,11 @@ const chai = require('chai') const assert = chai.assert -const utilRoute = require('../../src/routes/v4/util') const nock = require('nock') // HTTP mocking const sinon = require('sinon') +const UtilRoute = require('../../src/routes/v4/util') + let originalEnvVars // Used during transition from integration to unit tests. // Mocking data. @@ -24,7 +25,7 @@ const util = require('util') util.inspect.defaultOptions = { depth: 1 } // const UtilRoute = utilRoute.UtilRoute -const utilRouteInst = new utilRoute.UtilRoute() +const utilRouteInst = new UtilRoute() describe('#Util', () => { let req, res From b4d29a58566389433a6089b01fc0f5c5e2ecb06d Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Mar 2021 11:35:07 -0800 Subject: [PATCH 5/6] fix(Util.sweepWif): Replaced blockbook with electrumx --- src/app.js | 6 +-- src/routes/v4/util.js | 58 ++++++++++++------------ test/v4/mocks/util-mocks.js | 89 +++++++++++++++++-------------------- test/v4/util.js | 76 ++++++++++++++++++------------- 4 files changed, 117 insertions(+), 112 deletions(-) diff --git a/src/app.js b/src/app.js index ce2265d..1863839 100644 --- a/src/app.js +++ b/src/app.js @@ -52,7 +52,7 @@ const electrumxv4 = new ElectrumXV4() electrumxv4.connect() const encryptionv4 = new EncryptionV4() const pricev4 = new PriceV4() -const utilV4 = new UtilV4() +const utilV4 = new UtilV4({ electrumx: electrumxv4 }) const app = express() @@ -208,11 +208,11 @@ function onError (error) { case 'EACCES': console.error(`${bind} requires elevated privileges`) process.exit(1) - // break + // break case 'EADDRINUSE': console.error(`${bind} is already in use`) process.exit(1) - // break + // break default: throw error } diff --git a/src/routes/v4/util.js b/src/routes/v4/util.js index 986f3d6..a50ee24 100644 --- a/src/routes/v4/util.js +++ b/src/routes/v4/util.js @@ -7,9 +7,6 @@ const axios = require('axios') const routeUtils = require('./route-utils') const wlogger = require('../../util/winston-logging') -const Blockbook = require('./blockbook') -const blockbook = new Blockbook() - const util = require('util') util.inspect.defaultOptions = { depth: 1 } @@ -38,9 +35,22 @@ const bchjs = new BCHJS() let _this class UtilRoute { - constructor () { + constructor (utilConfig) { this.bchjs = bchjs - this.blockbook = blockbook + // this.blockbook = blockbook + + if (!utilConfig) { + throw new Error( + 'Must pass a config object when instantiating the Util library.' + ) + } + if (!utilConfig.electrumx) { + throw new Error( + 'Must pass an instance of Electrumx when instantiating the Util library.' + ) + } + + this.electrumx = utilConfig.electrumx this.router = router this.router.get('/', this.root) @@ -172,7 +182,7 @@ class UtilRoute { } = routeUtils.setEnvVars() // Loop through each address and creates an array of requests to call in parallel - const promises = addresses.map(async address => { + const promises = addresses.map(async (address) => { requestConfig.data.id = 'validateaddress' requestConfig.data.method = 'validateaddress' requestConfig.data.params = [address] @@ -184,7 +194,7 @@ class UtilRoute { const axiosResult = await axios.all(promises) // Retrieve the data part of the result. - const result = axiosResult.map(x => x.data.result) + const result = axiosResult.map((x) => x.data.result) res.status(200) return res.json(result) @@ -250,12 +260,11 @@ class UtilRoute { const fromAddr = bchjs.ECPair.toCashAddress(ecPair) // Get a balance on the public address - const balances = await _this.blockbook.balanceFromBlockbook(fromAddr) + const balances = await _this.electrumx._balanceFromElectrumx(fromAddr) // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) // Total balance is the sum of the confirmed and unconfirmed balance. - const totalBalance = - Number(balances.balance) + Number(balances.unconfirmedBalance) + const totalBalance = balances.confirmed + balances.unconfirmed // Exit if balance is zero. if (isNaN(totalBalance) || totalBalance === 0) { @@ -270,7 +279,7 @@ class UtilRoute { } // Get all UTXOs help by the address. - const utxos = await _this.blockbook.utxosFromBlockbook(fromAddr) + const utxos = await _this.electrumx._utxosFromElectrumx(fromAddr) // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) const tokenUtxos = [] @@ -375,7 +384,7 @@ class UtilRoute { let utxos = options.utxos // Ensure all utxos have the satoshis property. - utxos = utxos.map(x => { + utxos = utxos.map((x) => { x.satoshis = Number(x.value) return x }) @@ -393,9 +402,9 @@ class UtilRoute { for (let i = 0; i < utxos.length; i++) { const utxo = utxos[i] - originalAmount = originalAmount + utxo.satoshis + originalAmount = originalAmount + utxo.value - transactionBuilder.addInput(utxo.txid, utxo.vout) + transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos) } if (originalAmount < 546) { @@ -430,7 +439,7 @@ class UtilRoute { ecPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, - utxo.satoshis + utxo.value ) } @@ -468,7 +477,7 @@ class UtilRoute { // Ensure there is only one class of token in the wallet. Throw an error if // there is more than one. const tokenId = tokenUtxos[0].tokenId - const otherTokens = tokenUtxos.filter(x => x.tokenId !== tokenId) + const otherTokens = tokenUtxos.filter((x) => x.tokenId !== tokenId) if (otherTokens.length > 0) { throw new Error( 'Multiple token classes detected. This function only supports a single class of token.' @@ -490,9 +499,9 @@ class UtilRoute { for (let i = 0; i < allUtxos.length; i++) { const utxo = allUtxos[i] - originalAmount = originalAmount + utxo.satoshis + originalAmount = originalAmount + utxo.value - transactionBuilder.addInput(utxo.txid, utxo.vout) + transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos) } if (originalAmount < 300) { @@ -572,7 +581,7 @@ class UtilRoute { ecPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, - thisUtxo.satoshis + thisUtxo.value ) } @@ -592,15 +601,4 @@ class UtilRoute { } } -// module.exports = { -// router, -// // testableComponents: { -// // root, -// // validateAddressSingle, -// // validateAddressBulk, -// // sweepWif -// // } -// UtilRoute -// } - module.exports = UtilRoute diff --git a/test/v4/mocks/util-mocks.js b/test/v4/mocks/util-mocks.js index 30987a7..bb45273 100644 --- a/test/v4/mocks/util-mocks.js +++ b/test/v4/mocks/util-mocks.js @@ -13,75 +13,70 @@ const mockAddress = { isscript: false } +// const mockBalance = { +// page: 1, +// totalPages: 1, +// itemsOnPage: 1000, +// address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k', +// balance: '2546', +// totalReceived: '2546', +// totalSent: '0', +// unconfirmedBalance: '0', +// unconfirmedTxs: 0, +// txs: 2, +// txids: [ +// 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', +// '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6' +// ] +// } + const mockBalance = { - page: 1, - totalPages: 1, - itemsOnPage: 1000, - address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k', - balance: '2546', - totalReceived: '2546', - totalSent: '0', - unconfirmedBalance: '0', - unconfirmedTxs: 0, - txs: 2, - txids: [ - 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6' - ] + confirmed: 2546, + unconfirmed: 0 } const mockUtxos = [ { - txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - vout: 0, - value: '2000', - height: 605873, - confirmations: 298, - satoshis: 2000 + tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', + tx_pos: 0, + value: 2000, + height: 605873 }, { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546 + tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', + tx_pos: 1, + value: 546, + height: 605873 } ] const mockThreeUtxos = [ { - txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', - vout: 0, - value: '2000', - height: 605873, - confirmations: 298, - satoshis: 2000 + tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56', + tx_pos: 0, + value: 2000, + height: 605873 }, { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546 + tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', + tx_pos: 1, + value: 546, + height: 605873 }, { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', - height: 605873, - confirmations: 298, - satoshis: 546 + tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', + tx_pos: 1, + value: 546, + height: 605873 } ] const mockIsTokenUtxos = [ false, { - txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', - vout: 1, - value: '546', + tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6', + tx_out: 1, + value: 546, height: 605873, confirmations: 298, satoshis: 546, diff --git a/test/v4/util.js b/test/v4/util.js index 75e309b..939936b 100644 --- a/test/v4/util.js +++ b/test/v4/util.js @@ -4,15 +4,22 @@ This test file uses the environment variable TEST to switch between unit and integration tests. By default, TEST is set to 'unit'. Set this variable to 'integration' to run the tests against BCH mainnet. + + These tests use this private key: + L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq + Which corresponds to this address: + bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r */ 'use strict' -const chai = require('chai') -const assert = chai.assert +// Public npm libraries +const assert = require('chai').assert const nock = require('nock') // HTTP mocking const sinon = require('sinon') +// Local libraries +const Electrumx = require('../../src/routes/v4/electrumx') const UtilRoute = require('../../src/routes/v4/util') let originalEnvVars // Used during transition from integration to unit tests. @@ -25,13 +32,16 @@ const util = require('util') util.inspect.defaultOptions = { depth: 1 } // const UtilRoute = utilRoute.UtilRoute -const utilRouteInst = new UtilRoute() +// const electrumx = new Electrumx() +const utilRouteInst = new UtilRoute({ electrumx: {} }) describe('#Util', () => { let req, res let sandbox + let electrumx + let utilRoute - before(() => { + before(async () => { // Save existing environment variables. originalEnvVars = { BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL, @@ -48,6 +58,9 @@ describe('#Util', () => { process.env.RPC_USERNAME = 'fakeusername' process.env.RPC_PASSWORD = 'fakepassword' } + + electrumx = new Electrumx() + // await electrumx.connect() }) // Setup the mocks before each test. @@ -65,6 +78,8 @@ describe('#Util', () => { if (!nock.isActive()) nock.activate() sandbox = sinon.createSandbox() + + utilRoute = new UtilRoute({ electrumx }) }) afterEach(() => { @@ -358,29 +373,29 @@ describe('#Util', () => { // Mock the RPC call for unit tests. sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') + .stub(utilRoute.electrumx, '_balanceFromElectrumx') .resolves(mockData.mockBalance) sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') + .stub(utilRoute.electrumx, '_utxosFromElectrumx') .resolves(mockData.mockUtxos) sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') + .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .resolves(mockData.mockIsTokenUtxos) // Mock sendRawTransaction() so that the hex does not actually get broadcast // to the network. sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') + .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .resolves('test-txid') req.body = { - wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', - toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' + wif: 'L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq', + toAddr: 'bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r' } - const result = await utilRouteInst.sweepWif(req, res) + const result = await utilRoute.sweepWif(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.equal(result, 'test-txid') @@ -390,13 +405,13 @@ describe('#Util', () => { // Mock the RPC call for unit tests. sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') + .stub(utilRoute.electrumx, '_balanceFromElectrumx') .resolves(mockData.mockBalance) // Mock sendRawTransaction() so that the hex does not actually get broadcast // to the network. sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') + .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .resolves('test-txid') req.body = { @@ -404,33 +419,30 @@ describe('#Util', () => { balanceOnly: true } - const result = await utilRouteInst.sweepWif(req, res) + const result = await utilRoute.sweepWif(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.isNumber(result) }) - } - // Unit tests only - if (process.env.TEST === 'unit') { it('should generate transaction for valid BCH-only sweep', async () => { sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') + .stub(utilRoute.electrumx, '_balanceFromElectrumx') .resolves(mockData.mockBalance) sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') + .stub(utilRoute.electrumx, '_utxosFromElectrumx') .resolves(mockData.mockUtxos) // Force token utxo to appear as regular BCH utxo. sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') + .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .resolves([false, false]) // Mock sendRawTransaction() so that the hex does not actually get broadcast // to the network. sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') + .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .resolves('test-txid') req.body = { @@ -438,7 +450,7 @@ describe('#Util', () => { toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' } - const result = await utilRouteInst.sweepWif(req, res) + const result = await utilRoute.sweepWif(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.equal(result, 'test-txid') @@ -446,22 +458,22 @@ describe('#Util', () => { it('should throw 422 error if no non-token UTXOs', async () => { sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') + .stub(utilRoute.electrumx, '_balanceFromElectrumx') .resolves(mockData.mockBalance) sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') + .stub(utilRoute.electrumx, '_utxosFromElectrumx') .resolves(mockData.mockUtxos) // Force token utxo to appear as regular BCH utxo. sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') + .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .resolves(mockData.tokensOnly) // Mock sendRawTransaction() so that the hex does not actually get broadcast // to the network. sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') + .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .resolves('test-txid') req.body = { @@ -469,7 +481,7 @@ describe('#Util', () => { toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' } - const result = await utilRouteInst.sweepWif(req, res) + const result = await utilRoute.sweepWif(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.equal(res.statusCode, 422) @@ -482,22 +494,22 @@ describe('#Util', () => { it('should detect and throw error for multiple token classes', async () => { sandbox - .stub(utilRouteInst.blockbook, 'balanceFromBlockbook') + .stub(utilRoute.electrumx, '_balanceFromElectrumx') .resolves(mockData.mockBalance) sandbox - .stub(utilRouteInst.blockbook, 'utxosFromBlockbook') + .stub(utilRoute.electrumx, '_utxosFromElectrumx') .resolves(mockData.mockThreeUtxos) // Force token utxo to appear as regular BCH utxo. sandbox - .stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails') + .stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails') .resolves(mockData.multipleTokens) // Mock sendRawTransaction() so that the hex does not actually get broadcast // to the network. sandbox - .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') + .stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction') .resolves('test-txid') req.body = { @@ -505,7 +517,7 @@ describe('#Util', () => { toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p' } - const result = await utilRouteInst.sweepWif(req, res) + const result = await utilRoute.sweepWif(req, res) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.equal(res.statusCode, 422) From b97d94edaf044027b60ecb9f6025e1df63279468 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 1 Mar 2021 11:37:23 -0800 Subject: [PATCH 6/6] Bumping bch-js to v4.15.19 --- package-lock.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47f089b..24ada43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "version": "1.16.0", "license": "MIT", "dependencies": { - "@psf/bch-js": "^4.15.18", + "@psf/bch-js": "^4.15.19", "apidoc": "^0.26.0", "axios": "^0.21.1", "bitcore-lib-cash": "^8.23.1", @@ -458,9 +458,9 @@ } }, "node_modules/@psf/bch-js": { - "version": "4.15.18", - "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-4.15.18.tgz", - "integrity": "sha512-aje0Kzy+K7XuaDwTZaao6SnFQDPsAmsGCW5pZ9WtNhvM5MAt3//Attq8Jv7Y+nnIoJQov5RWXQvOexkgAgy4sQ==", + "version": "4.15.19", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-4.15.19.tgz", + "integrity": "sha512-lQMxOEvHk7XhbJ0wYSamliZclxXomuq0pbEk8XTCEv+fJ5pgme0lCygFxubHE8X01ZRL1W0hLgiERKHwj5GSsg==", "dependencies": { "@psf/bip21": "^2.0.1", "@psf/bip32-utils": "^1.0.0", @@ -11179,7 +11179,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -11199,7 +11199,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -11207,7 +11207,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -11215,7 +11215,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -11234,7 +11234,7 @@ "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -11258,7 +11258,7 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true, + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -18678,9 +18678,9 @@ } }, "@psf/bch-js": { - "version": "4.15.18", - "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-4.15.18.tgz", - "integrity": "sha512-aje0Kzy+K7XuaDwTZaao6SnFQDPsAmsGCW5pZ9WtNhvM5MAt3//Attq8Jv7Y+nnIoJQov5RWXQvOexkgAgy4sQ==", + "version": "4.15.19", + "resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-4.15.19.tgz", + "integrity": "sha512-lQMxOEvHk7XhbJ0wYSamliZclxXomuq0pbEk8XTCEv+fJ5pgme0lCygFxubHE8X01ZRL1W0hLgiERKHwj5GSsg==", "requires": { "@psf/bip21": "^2.0.1", "@psf/bip32-utils": "^1.0.0", @@ -27170,7 +27170,7 @@ "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", "bundled": true, - "dev": true + "extraneous": true }, "lodash._baseuniq": { "version": "4.6.0", @@ -27188,21 +27188,21 @@ "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", "bundled": true, - "dev": true + "extraneous": true }, "lodash._cacheindexof": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", "bundled": true, - "dev": true + "extraneous": true }, "lodash._createcache": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", "bundled": true, - "dev": true, + "extraneous": true, "requires": { "lodash._getnative": "^3.0.0" } @@ -27219,7 +27219,7 @@ "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", "bundled": true, - "dev": true + "extraneous": true }, "lodash._root": { "version": "3.0.1", @@ -27240,7 +27240,7 @@ "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", "bundled": true, - "dev": true + "extraneous": true }, "lodash.union": { "version": "4.6.0",