Merge branch 'master' into greenkeeper/nock-11.7.0

This commit is contained in:
Chris Troutner
2020-01-11 11:44:32 -08:00
83 changed files with 2830 additions and 20702 deletions
+4
View File
@@ -7,6 +7,9 @@ start-decatur-main
start-decatur-test
start-bitcoincom.sh
test-remote-server.sh
start-bitcoincom-testnet.sh
start-local-server.sh
start-local-testnet-server.sh
coverage
start-my-infra
@@ -16,3 +19,4 @@ nohup.out
slp-tx-db/
logs/
docs/*
+19 -13
View File
@@ -9,12 +9,14 @@ the [rest.bitcoin.com](https://github.com/Bitcoin-com/rest.bitcoin.com) reposito
The purpose of this code is to create a REST API server that provides a common
interface for working with a Bitcoin Cash full node and various indexers.
This repository is intended to be paired with my alternative implementation
of [BITBOX SDK](https://github.com/Bitcoin-com/bitbox-sdk):
[bch-js](https://github.com/christroutner/bch-js), and is part of the product
offering at [bchjs.cash](https://bchjs.cash).
This repository is intended to be paired with [bch-js](https://github.com/christroutner/bch-js),
an npm JavaScript library, and an alternative implementation
of [BITBOX SDK](https://github.com/Bitcoin-com/bitbox-sdk).
- [API Documentation](https://bchjs.cash/bch-api/index.html)
Both bch-api and bch-js are part of the
[full stack of BCH software](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer).
- [API Documentation](https://api.bchjs.cash/docs/)
- Video: [Basic Concepts](https://www.youtube.com/watch?v=o0FfW5rZPFs)
- Video: [Application Stack](https://youtu.be/8w0CpQ8oydA)
- [bchjs.cash](https://bchjs.cash): Buy a turn-key REST API microserver.
@@ -25,8 +27,11 @@ Have questions? Need help? Join our community support
## Features
The following features set this repository apart from rest.bitcoin.com:
- Address balance and UTXO queries for [Blockbook](https://github.com/trezor/blockbook) and [Bitcore](https://github.com/bitpay/bitcore/tree/master/packages/bitcore-node) added.
- Rate limits are set to 10 RPM by default, and 60 RPM if Basic Authentiction header is used.
- Address balance and UTXO queries use the [Blockbook](https://github.com/trezor/blockbook)
indexer instead of Insight.
- Fine grain access is controlled with a JWT token using
[this back end auth server](https://github.com/Permissionless-Software-Foundation/jwt-bch-api) and [this front end](https://github.com/Permissionless-Software-Foundation/jwt-bch-frontend).
- Default rate limits are set to 3 RPM for anonymous connections, up to 100 RPM for a full-access JWT token is used.
- Typescript removed and ES8 JavaScript used instead.
- npm audit run on all dependencies.
- [Greenkeeper](https://greenkeeper.io/) implemented for automatic dependency management
@@ -34,15 +39,16 @@ and security updates.
## Live Demo
You can test a live demo of the REST API by running the
[bch-js examples](https://github.com/christroutner/bch-js/tree/master/examples).
Rate limits are 10 requests per minute. This is fast enough to try out the examples
[bch-js examples](https://github.com/Permissionless-Software-Foundation/bch-js-examples).
Rate limits are 3 requests per minute, but you can increase them to 10 with a free
account at [account.bchjs.cash](https://account.bchjs.cash).
This is fast enough to try out the examples
but these servers are not intended as a freemium service. You can run your own
REST server by purchasing the hard drive at [bchjs.cash](https://bchjs.cash).
- Mainnet REST API server: http://decatur.hopto.org:12400/v3/
- Check server status: http://decatur.hopto.org:12401
- Testnet REST API server: http://decatur.hopto.org:13400/v3/
- Check server status: http://decatur.hopto.org:13401
- Mainnet REST API server: https://api.bchjs.cash/v3/
- Testnet REST API server: https://tapi.bchjs.cash/v3/
- Check server status: https://metrics.bchjs.cash
## Installation
There are two installation paths, depending if you want a *development* or
+4 -10
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Harddrive Mainnet full node
# Full node
export RPC_BASEURL=http://172.17.0.1:8332/
export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password
@@ -8,17 +8,11 @@ export ZEROMQ_PORT=28332
export ZEROMQ_URL=172.17.0.1
export NETWORK=mainnet
# Team DO QA Testnet Insight Server
export BITCOINCOM_BASEURL=http://172.17.0.1:12100/api/
# Testnet SLPDB
#export SLPDB_URL=https://slpdb.bchdata.cash/
# SLPDB
#export SLPDB_URL=https://slpdb.bitcoin.com/
export SLPDB_URL=http://172.17.0.1:12300/
# Testnet Bitcore Node API
export BITCORE_URL=http://172.17.0.1:12200/
# Testnet Blockbook
# Blockbook Indexer
export BLOCKBOOK_URL=https://172.17.0.1:9131/
# Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
+4 -10
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Harddrive Mainnet full node
# Full node
export RPC_BASEURL=http://172.17.0.1:18332/
export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password
@@ -8,17 +8,11 @@ export ZEROMQ_PORT=38332
export ZEROMQ_URL=172.17.0.1
export NETWORK=testnet
# Team DO QA Testnet Insight Server
export BITCOINCOM_BASEURL=http://172.17.0.1:13100/api/
# Testnet SLPDB
#export SLPDB_URL=https://slpdb.bchdata.cash/
# SLPDB
#export SLPDB_URL=https://tslpdb.bitcoin.com/
export SLPDB_URL=http://172.17.0.1:13300/
# Testnet Bitcore Node API
export BITCORE_URL=http://172.17.0.1:13200/
# Testnet Blockbook
# Blockbook
export BLOCKBOOK_URL=https://172.17.0.1:19131/
# Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
+2106 -2890
View File
File diff suppressed because it is too large Load Diff
+5 -9
View File
@@ -11,7 +11,6 @@
"start": "node ./src/app.js",
"dev": "nodemon ./dist/app.js",
"test": "npm run test-v3",
"test-v2": "export NETWORK=testnet && nyc --reporter=text mocha --timeout 25000 test/v2/",
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 25000 test/v3/",
"test:integration": "mocha test/v3/integration",
"coverage": "nyc report --reporter=text-lcov | coveralls",
@@ -22,8 +21,8 @@
"node": ">=10.15.1"
},
"dependencies": {
"@chris.troutner/bch-js": "^1.6.1",
"apidoc": "^0.17.7",
"apidoc": "^0.18.0",
"@chris.troutner/bch-js": "^2.0.0",
"axios": "^0.19.0",
"body-parser": "^1.18.3",
"cookie-parser": "~1.4.3",
@@ -44,8 +43,6 @@
"passport-http": "^0.3.0",
"pg": "^7.11.0",
"pg-hstore": "^2.3.2",
"slp-sdk": "^4.4.1",
"slpjs": "0.21.4",
"strftime": "^0.10.0",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.0.0"
@@ -62,14 +59,13 @@
"fs-extra": "^8.0.1",
"nock": "^11.7.0",
"node-mocks-http": "^1.7.0",
"nodemon": "^1.18.1",
"nyc": "^14.1.1",
"nyc": "^15.0.0",
"prettier": "^1.18.2",
"proxyquire": "^2.1.0",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"semantic-release": "^15.13.19",
"sinon": "^7.3.2",
"sinon": "^8.0.0",
"semantic-release": "^16.0.0",
"typescript": "^3.1.4"
},
"release": {
+26 -60
View File
@@ -15,39 +15,22 @@ const helmet = require("helmet")
const debug = require("debug")("rest-cloud:server")
const http = require("http")
const cors = require("cors")
const AuthMW = require("./middleware/auth")
// v2
const indexV2 = require("./routes/v2/index")
const healthCheckV2 = require("./routes/v2/health-check")
const addressV2 = require("./routes/v2/address")
const blockV2 = require("./routes/v2/block")
const blockchainV2 = require("./routes/v2/blockchain")
const controlV2 = require("./routes/v2/control")
const generatingV2 = require("./routes/v2/generating")
const miningV2 = require("./routes/v2/mining")
const networkV2 = require("./routes/v2/network")
const rawtransactionsV2 = require("./routes/v2/rawtransactions")
const transactionV2 = require("./routes/v2/transaction")
const utilV2 = require("./routes/v2/util")
const slpV2 = require("./routes/v2/slp")
// Auth and rate limiting middleware libraries.
const AuthMW = require("./middleware/auth")
const jwtAuth = require("./middleware/jwt-auth")
// v3
const healthCheckV3 = require("./routes/v3/health-check")
const blockchainV3 = require("./routes/v3/blockchain")
const controlV3 = require("./routes/v3/control")
const miningV3 = require("./routes/v3/mining")
const networkV3 = require("./routes/v3/network")
const rawtransactionsV3 = require("./routes/v3/rawtransactions")
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 bitcoreV3 = require("./routes/v3/bitcore")
const blockbookV3 = require("./routes/v3/blockbook")
const insightV3 = require("./routes/v3/insight")
const insightBlockV3 = require("./routes/v3/insight/block")
const insightTranactionV3 = require("./routes/v3/insight/transaction")
const insightAddressV3 = require("./routes/v3/insight/address")
require("dotenv").config()
@@ -69,48 +52,36 @@ app.set("view engine", "jade")
// Mount the docs
app.use("/docs", express.static(`${__dirname}/../docs`))
app.use(logger("dev"))
// Log each request to the console with IP addresses.
// app.use(logger("dev"))
const morganFormat = `:remote-addr :remote-user :method :url :status :response-time ms - :res[content-length] :user-agent`
app.use(logger(morganFormat))
// Log the same data to the winston logs.
const logStream = {
write: function(message, encoding) {
wlogger.info(`request: ${message}`)
}
}
app.use(logger(morganFormat, { stream: logStream }))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, "public")))
//
// let username = process.env.USERNAME;
// let password = process.env.PASSWORD;
//
// app.use(basicAuth(
// {
// users: { username: password }
// }
// ));
const v2prefix = "v2"
// const v2prefix = "v2"
const v3prefix = "v3"
// Inspect the header for a JWT token.
app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders)
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
const auth = new AuthMW()
app.use(`/${v2prefix}/`, auth.mw())
app.use(`/${v3prefix}/`, auth.mw())
// Rate limit on all v2 routes
app.use(`/${v2prefix}/`, routeRateLimit)
app.use("/", indexV2)
app.use(`/${v2prefix}/` + `health-check`, healthCheckV2)
app.use(`/${v2prefix}/` + `address`, addressV2.router)
app.use(`/${v2prefix}/` + `blockchain`, blockchainV2.router)
app.use(`/${v2prefix}/` + `block`, blockV2.router)
app.use(`/${v2prefix}/` + `control`, controlV2.router)
app.use(`/${v2prefix}/` + `generating`, generatingV2)
app.use(`/${v2prefix}/` + `mining`, miningV2.router)
app.use(`/${v2prefix}/` + `network`, networkV2)
app.use(`/${v2prefix}/` + `rawtransactions`, rawtransactionsV2.router)
app.use(`/${v2prefix}/` + `transaction`, transactionV2.router)
app.use(`/${v2prefix}/` + `util`, utilV2.router)
app.use(`/${v2prefix}/` + `slp`, slpV2.router)
// Rate limit on all v3 routes
app.use(`/${v3prefix}/`, routeRateLimit)
app.use(`/${v3prefix}/`, routeRateLimit) // Establish and enforce rate limits.
app.use(`/${v3prefix}/` + `health-check`, healthCheckV3)
app.use(`/${v3prefix}/` + `blockchain`, blockchainV3.router)
app.use(`/${v3prefix}/` + `control`, controlV3.router)
@@ -120,12 +91,7 @@ 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}/` + `bitcore`, bitcoreV3.router)
app.use(`/${v3prefix}/` + `blockbook`, blockbookV3.router)
app.use(`/${v3prefix}/` + `insight`, insightV3.router)
app.use(`/${v3prefix}/` + `insight/block`, insightBlockV3.router)
app.use(`/${v3prefix}/` + `insight/transaction`, insightTranactionV3.router)
app.use(`/${v3prefix}/` + `insight/address`, insightAddressV3.router)
// catch 404 and forward to error handler
app.use((req, res, next) => {
+14 -13
View File
@@ -1,6 +1,13 @@
/*
Handle authorization for bypassing rate limits.
1) Default is 'Anonymous Authentication', which unlocks the freemimum tier by
default.
2) Hard-coded 'Basic Authentication' is a token that does not expire and is
provided to buisiness partners.
3) JWT-based 'Local Authentication' is used for normal users that pay to
access the premium pro-tier services.
This file uses the passport npm library to check the header of each REST API
call for the prescence of a Basic authorization header:
https://en.wikipedia.org/wiki/Basic_access_authentication
@@ -35,18 +42,6 @@ class AuthMW {
_this = this
// Initialize passport for 'anonymous' authentication.
/*
passport.use(
new AnonymousStrategy({ passReqToCallback: true }, function(
req,
username,
password,
done
) {
console.log(`anonymous auth handler triggered.`)
})
)
*/
passport.use(new AnonymousStrategy())
// Initialize passport for 'basic' authentication.
@@ -62,7 +57,13 @@ class AuthMW {
//console.log(`password: ${password}`)
// Create the req.locals property if it does not yet exist.
if (!req.locals) req.locals = {}
if (!req.locals) {
req.locals = {
// default values
proLimit: false,
apiLevel: 0
}
}
// Set pro-tier rate limit to flag to false by default.
req.locals.proLimit = false
+47
View File
@@ -0,0 +1,47 @@
/*
This middleware inspects the request header for a JWT token.
If found, will populate req.locals.jwtToken with the JWT token.
*/
"use strict"
// This function searches the header for the a JWT token in the authorization header.
// If one is found, this middleware passes the JWT token through the
// req.locals.jwtToken property.
const getTokenFromHeaders = (req, res, next) => {
try {
// Only executes if the authorization header exists.
if (req.headers.authorization) {
// Retrieve the auth string from the header object.
const authStr = req.headers.authorization
// If the header is proceeded by the word 'Token'
if (authStr.split(" ")[0] === "Token") {
const token = authStr.split(" ")[1]
// console.log(`JWT found: ${token}`)
// Create the req.locals property if it does not yet exist.
if (!req.locals) {
req.locals = {
jwtToken: token,
proLimit: false,
apiLevel: 0
}
} else {
req.locals.jwtToken = token
}
}
}
} catch (err) {
console.log(`Error in getTokenFromHeaders: `, err)
}
next()
}
const jwtAuth = {
getTokenFromHeaders
}
module.exports = jwtAuth
+114 -15
View File
@@ -1,35 +1,86 @@
/*
This file controls the request-per-minute (RPM) rate limits.
It is assumed that this middleware is run AFTER the auth.js middleware which
checks for Basic auth. If the user adds the correct Basic auth to the header
of their API request, they will get pro-tier rate limits. By default, the
freemium rate limits apply.
It is assumed that this middleware is run AFTER the jwt-auth.js and auth.js
middleware.
Current rate limiting rules in requests-per-minute:
- anonymous access: 3
- free access: 10, apiLevel = 0
- any paid tier: 100, apiLevel > 0
If a person signs up for full node access but not indexer access, then the
apiLevel will be 10. If they call an endpoint that uses an indexer, the apiLevel
will be downgraded to 0 on-the-fly. Indexer endpoints will effectively be
downgraded to the anonymous access tier.
*/
"use strict"
const express = require("express")
const RateLimit = require("express-rate-limit")
const axios = require("axios")
// Set max requests per minute
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
: 30
: 3
// Pro-tier rate limits are 10x the freemium limits.
const PRO_RPM = 10 * maxRequests
// const PRO_RPM = 10 * maxRequests
// Unique route mapped to its rate limit
const uniqueRateLimits = {}
const routeRateLimit = function(req, res, next) {
// Create a res.locals object if not passed in.
if (!req.locals) req.locals = {}
const routeRateLimit = async function(req, res, next) {
// Disable rate limiting if 0 passed from RATE_LIMIT_MAX_REQUESTS
if (maxRequests === 0) return next()
// Create a res.locals object if not passed in.
if (!req.locals) {
req.locals = {
// default values
jwtToken: "",
proLimit: false,
apiLevel: 0
}
}
// Warn if JWT_AUTH_SERVER env var is not set.
const authServer = process.env.JWT_AUTH_SERVER
if (!authServer || authServer === "") {
console.warn(
"JWT_AUTH_SERVER env var is not set. JWT tokens not being evaluated."
)
} else {
// If a JWT token is passed in, validate it and enable pro-tier rate limits
// if it's valid.
if (req.locals.jwtToken) {
// console.log(`req.locals.jwtToken: ${req.locals.jwtToken}`)
// URL for the auth server.
const path = `${authServer}apitoken/isvalid/${req.locals.jwtToken}`
// Ask Auth server if the JWT token is valid.
// Get the API level for this user.
let jwtInfo = await axios.get(path)
jwtInfo = jwtInfo.data
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
// If JWT if valid, evaluate the API level for the user.
if (jwtInfo.isValid) {
// Set fine-grain permissions for each user based on the JWT token.
const userPermissions = evalUserPermissioins(req, jwtInfo)
// console.log(
// `userPermissions: ${JSON.stringify(userPermissions, null, 2)}`
// )
req.locals.proLimit = userPermissions.proLimit
req.locals.apiLevel = userPermissions.apiLevel
}
}
}
// Current route
const rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC"
const path = req.baseUrl + req.path
@@ -38,19 +89,29 @@ const routeRateLimit = function(req, res, next) {
const route =
rateLimitTier +
req.method +
req.locals.apiLevel + // Generates new rate limit when user upgrades JWT token.
path
.split("/")
.slice(0, 4)
.join("/")
//console.log(`route identifier: ${JSON.stringify(route, null, 2)}`)
// console.log(`req.locals: ${JSON.stringify(req.locals, null, 2)}`)
// This boolean value is passed from the auth.js middleware.
const proRateLimits = req.locals.proLimit
// console.log(`proRateLimits: ${proRateLimits}`)
// Pro level rate limits
if (proRateLimits) {
if (proRateLimits || proRateLimits === 0) {
// TODO: replace the console.logs with calls to our logging system.
//console.log(`applying pro-rate limits`)
// console.log(`applying pro-rate limits`)
let PRO_RPM = 10 // Default value for free tier
if (req.locals.apiLevel > 0) PRO_RPM = 100 // RPM for paid tiers.
// console.log(`PRO_RPM: ${PRO_RPM}, apiLevel: ${req.locals.apiLevel}`)
// Create new RateLimit if none exists for this route
if (!uniqueRateLimits[route]) {
@@ -63,7 +124,7 @@ const routeRateLimit = function(req, res, next) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Limits are ${PRO_RPM} requests per minute.`
error: `Too many requests. Limits are ${PRO_RPM} requests per minute. Increase rate limits at https://account.bchjs.cash`
})
}
})
@@ -72,7 +133,7 @@ const routeRateLimit = function(req, res, next) {
// Freemium level rate limits
} else {
// TODO: replace the console.logs with calls to our logging system.
//console.log(`applying freemium limits`)
// console.log(`applying freemium limits`)
// Create new RateLimit if none exists for this route
if (!uniqueRateLimits[route]) {
@@ -85,7 +146,7 @@ const routeRateLimit = function(req, res, next) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Too many requests. Limits are ${maxRequests} requests per minute.`
error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://account.bchjs.cash`
})
}
})
@@ -98,4 +159,42 @@ const routeRateLimit = function(req, res, next) {
uniqueRateLimits[route](req, res, next)
}
// This function returns the an object with proLimit and apiLevel properties.
// It does fine-grane analysis on the data coming from the auth servers and
// uses its output to adjust rate limits on-the-fly based on the users
// permission level.
function evalUserPermissioins(req, authData) {
// console.log(`authData: ${JSON.stringify(authData, null, 2)}`)
// Return object with default values
const retObj = {
proLimit: authData.isValid,
apiLevel: authData.apiLevel
}
// if apiLevel = 0 (free tier), then return the default values.
if (retObj.apiLevel === 0) return retObj
const level20Routes = ["insight", "bitcore", "blockbook"]
const locals = req.locals
// console.log(`locals: ${JSON.stringify(locals, null, 2)}`)
const url = req.url
// console.log(`url: ${JSON.stringify(url, null, 2)}`)
if (authData.apiLevel < 20) {
// Loop through the routes that are not accessible to this tier.
for (let i = 0; i < level20Routes.length; i++) {
// If the requested route is for a higher tier,
// revert to anonymous level permissions.
if (url.indexOf(level20Routes[i]) > -1) {
retObj.proLimit = false
retObj.apiLevel = 0
}
}
}
return retObj
}
module.exports = { routeRateLimit }
+186 -27
View File
@@ -1,35 +1,194 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<head>
<meta charset="utf-8" />
<title>BCH API</title>
<meta name="description" content="BCH API">
<meta name="author" content="Chris Troutner">
<title>BCH API</title>
<meta name="description" content="BCH API" />
<meta name="author" content="Chris Troutner" />
<link rel="stylesheet" href="css/styles.css?v=1.0">
<link rel="stylesheet" href="css/styles.css?v=1.0" />
</head>
</head>
<body>
<h1>BCH API</h1>
<h4>The REST API for the Bitcoin Cash blockchain.</h4>
<br />
<body>
<h1>BCH API</h1>
<p>
This web server is based on <a href="https://rest.bitcoin.com">rest.bitcoin.com</a>.
Go there to view an interactive UI of the REST API endpoints.
</p>
<ul>
<li>
<a href="/docs">
View the BCH-API documentation
</a>
</li>
<li>
View the original rest.bitcoin.com API documentation
on <a href="https://developer.bitcoin.com/rest/docs/getting-started">developer.bitcoin.com</a>.
</li>
</ul>
<p>
bch-api is a
<a
href="https://en.wikipedia.org/wiki/Representational_state_transfer"
target="_blank"
>REST API</a
>, allowing modern applications to interact with the Bitcoin Cash (BCH)
blockchain. New to the BCH blockchain? Start by reading more about
<a
href="https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer"
target="_blank"
>building full stack applications on BCH</a
>.
</p><br />
<p>
A REST API can work with any programming language, but bch-api is intended
to be paired with the
<a
href="https://www.npmjs.com/package/@chris.troutner/bch-js"
target="_blank"
>npm bch-js</a
>
JavaScript library.
<a
href="https://github.com/Permissionless-Software-Foundation/bch-js-examples"
target="_blank"
>Here is example code</a
>
for common BCH use cases.
<a href="https://www.npmjs.com/package/slp-cli-wallet" target="_blank">
slp-cli-wallet</a
>
is another npm library that provides common wallet functionality, to get
your apps up and running as quickly as possible.
</p><br />
<p>
While you'll be able to run through the examples without one, you should
<a href="https://account.bchjs.cash/" target="_blank"
>sign-up for a free API key</a
>
in order to unlock higher rate limits.
</p>
<script src="js/scripts.js"></script>
</body>
<br />
<h2>Quick Links</h2>
<ul>
<li>
<h3>bch-api</h3>
<ul>
<li>
<a href="https://account.bchjs.cash" target="_blank">
Get an API key to increase rate limits
</a>
</li>
<br />
<li>
<a href="/docs">
bch-api documentation
</a>
</li>
<br />
<li>
<a href="https://tapi.bchjs.cash">
testnet REST API
</a>
</li>
<br />
<li>
<a href="https://github.com/christroutner/bch-api" target="_blank">
bch-api GitHub repository
</a>
</li>
<br />
<li>
<a href="https://t.me/bch_js_toolkit" target="_blank">
Community Telegram Channel
</a>
</li>
<br />
</ul>
</li>
<br /><br />
<li>
<h3>Bitcoin Cash</h3>
<ul>
<li>
<a href="https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer"
target="_blank">
How to become a BCH Full Stack Developer
</a>
</li>
<br />
<li>
<a href="https://bchjs.cash/" target="_blank">
Run your own infrastructure
</a>
</li>
<br />
<li>
<a href="https://github.com/bitcoinbook/bitcoinbook#chapters" target="_blank">
Mastering Bitcoin (great free reference)
</a>
</li>
<br />
<li>
<a href="https://explorer.bitcoin.com/bch" target="_blank">
Bitcoin Cash Block Explorer
</a>
</li>
<br />
<li>
<a href="https://developer.bitcoin.com/faucets/bch/" target="_blank">
Testnet Faucet
</a>
</li>
<br />
</ul>
</li>
<br /><br />
<li>
<h3>bch-js</h3>
<ul>
<li>
<a href="https://www.npmjs.com/package/@chris.troutner/bch-js"
target="_blank">
npm library
</a>
</li>
<br />
<li>
<a href="https://github.com/Permissionless-Software-Foundation/bch-js-examples" target="_blank">
Code examples
</a>
</li>
<br />
<li>
<a href="https://github.com/christroutner/bch-js" target="_blank">
GitHub code repository
</a>
</li>
<br />
</ul>
</li>
</ul>
<br />
<h2>Credit</h2>
<p>
This web server is based on
<a href="https://rest.bitcoin.com">rest.bitcoin.com</a>. Most endpoints
are common between the two projects. You may want to plan with their
interactive UI to explore the API endpoints.
</p>
<ul>
<li>
View the original rest.bitcoin.com API documentation on
<a href="https://developer.bitcoin.com/rest/docs/getting-started"
>developer.bitcoin.com</a
>.
</li>
</ul>
<script src="js/scripts.js"></script>
</body>
</html>
-805
View File
@@ -1,805 +0,0 @@
/*
Address route
*/
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const logger = require("./logging.js")
const routeUtils = require("./route-utils")
const wlogger = require("../../util/winston-logging")
//const router = express.Router()
const router = express.Router()
// Used for processing error messages before sending them to the user.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const BITBOXJS = require("@chris.troutner/bch-js")
const BITBOX = new BITBOXJS()
// Use the default (and max) page size of 1000
// https://github.com/bitpay/insight-api#notes-on-upgrading-from-v03
const PAGE_SIZE = 1000
// Connect the route endpoints to their handler functions.
router.get("/", root)
router.get("/details/:address", detailsSingle)
router.post("/details", detailsBulk)
router.post("/utxo", utxoBulk)
router.get("/utxo/:address", utxoSingle)
router.post("/unconfirmed", unconfirmedBulk)
router.get("/unconfirmed/:address", unconfirmedSingle)
router.get("/transactions/:address", transactionsSingle)
router.post("/transactions", transactionsBulk)
router.get("/fromXPub/:xpub", fromXPubSingle)
// Root API endpoint. Simply acknowledges that it exists.
function root(req, res, next) {
return res.json({ status: "address" })
}
// Query the Insight API for details on a single BCH address.
// Returns a Promise.
async function detailsFromInsight(thisAddress, currentPage = 0) {
try {
let addr
if (
process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/"
)
addr = BITBOX.Address.toCashAddress(thisAddress)
else addr = BITBOX.Address.toLegacyAddress(thisAddress)
let path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}`
// Set from and to params based on currentPage and pageSize
// https://github.com/bitpay/insight-api/blob/master/README.md#notes-on-upgrading-from-v02
const from = currentPage * PAGE_SIZE
const to = from + PAGE_SIZE
path = `${path}?from=${from}&to=${to}`
// Query the Insight server.
const axiosResponse = await axios.get(path)
const retData = axiosResponse.data
//console.log(`retData: ${util.inspect(retData)}`)
// Calculate pagesTotal from response
const pagesTotal = Math.ceil(retData.txApperances / PAGE_SIZE)
// Append different address formats to the return data.
retData.legacyAddress = BITBOX.Address.toLegacyAddress(retData.addrStr)
retData.cashAddress = BITBOX.Address.toCashAddress(retData.addrStr)
delete retData.addrStr
// Append pagination information to the return data.
retData.currentPage = currentPage
retData.pagesTotal = pagesTotal
return retData
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
// POST handler for bulk queries on address details
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details
async function detailsBulk(req, res, next) {
try {
let addresses = req.body.addresses
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
// Reject if addresses is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({
error: "addresses needs to be an array. Use GET for single address."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
logger.debug(`Executing address/details with these addresses: `, addresses)
wlogger.debug(`Executing address/details with these addresses: `, addresses)
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
BITBOX.Address.toLegacyAddress(thisAddress)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Loops through each address and creates an array of Promises, querying
// Insight API in parallel.
addresses = addresses.map(async (address, index) =>
detailsFromInsight(address, currentPage)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
//logger.error(`Error in detailsBulk(): `, err)
wlogger.error(`Error in address.ts/detailsBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler for single address details
async function detailsSingle(req, res, next) {
try {
const address = req.params.address
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
logger.debug(`Executing address/detailsSingle with this address: `, address)
wlogger.debug(
`Executing address/detailsSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${address}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(address)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
})
}
// Query the Insight API.
const retData = await detailsFromInsight(address, currentPage)
// Return the retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in address.ts/detailsSingle: `, err)
wlogger.error(`Error in address.ts/detailsSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Retrieve UTXO data from the Insight API
async function utxoFromInsight(thisAddress) {
try {
let addr
if (
process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/"
)
addr = BITBOX.Address.toCashAddress(thisAddress)
else addr = BITBOX.Address.toLegacyAddress(thisAddress)
const path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}/utxo`
// Query the Insight server.
const response = await axios.get(path)
// Append different address formats to the return data.
const retData = {
utxos: Array,
legacyAddress: String,
cashAddress: String,
scriptPubKey: String
}
if (response.data.length && response.data[0].scriptPubKey) {
const spk = response.data[0].scriptPubKey
retData.scriptPubKey = spk
}
retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress)
retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress)
retData.utxos = response.data.map(utxo => {
delete utxo.address
delete utxo.scriptPubKey
return utxo
})
//console.log(`utxoFromInsight retData: ${util.inspect(retData)}`)
return retData
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
// Retrieve UTXO information for an address.
async function utxoBulk(req, res, next) {
try {
let addresses = req.body.addresses
// Reject if address is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({ error: "addresses needs to be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
BITBOX.Address.toLegacyAddress(thisAddress)
} catch (er) {
//if (er.message.includes("Unsupported address format"))
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
logger.debug(`Executing address/utxoBulk with these addresses: `, addresses)
wlogger.debug(
`Executing address/utxoBulk with these addresses: `,
addresses
)
// Loops through each address and creates an array of Promises, querying
// Insight API in parallel.
addresses = addresses.map(async (address, index) =>
utxoFromInsight(address)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/utxoBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler for single address details
async function utxoSingle(req, res, next) {
try {
const address = req.params.address
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
logger.debug(`Executing address/utxoSingle with this address: `, address)
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${address}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(address)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
})
}
// Query the Insight API.
const retData = await utxoFromInsight(address)
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/utxoSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Retrieve any unconfirmed TX information for a given address.
async function unconfirmedBulk(req, res, next) {
try {
const addresses = req.body.addresses
// Reject if address is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({ error: "addresses needs to be an array" })
}
logger.debug(`Executing address/utxo with these addresses: `, addresses)
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
BITBOX.Address.toLegacyAddress(thisAddress)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Collect an array of promises.
const promises = addresses.map(address => utxoFromInsight(address))
// Wait for all parallel Insight requests to return.
const result = await axios.all(promises)
// Loop through each result
const finalResult = result.map(elem => {
//console.log(`elem: ${util.inspect(elem)}`)
// Filter out confirmed transactions.
const unconfirmedUtxos = elem.utxos.filter(
utxo => utxo.confirmations === 0
)
elem.utxos = unconfirmedUtxos
return elem
})
// Return the array of retrieved address information.
res.status(200)
return res.json(finalResult)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/unconfirmedBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler. Retrieve any unconfirmed TX information for a given address.
async function unconfirmedSingle(req, res, next) {
try {
const address = req.params.address
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
logger.debug(`Executing address/utxoSingle with this address: `, address)
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${address}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(address)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
})
}
// Query the Insight API.
const retData = await utxoFromInsight(address)
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// Loop through each returned UTXO.
const unconfirmedUTXOs = []
for (let j = 0; j < retData.utxos.length; j++) {
const thisUtxo = retData.utxos[j]
// Only interested in UTXOs with no confirmations.
if (thisUtxo.confirmations === 0) unconfirmedUTXOs.push(thisUtxo)
}
retData.utxos = unconfirmedUTXOs
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/unconfirmedSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Retrieve transaction data from the Insight API
async function transactionsFromInsight(thisAddress, currentPage = 0) {
try {
const path = `${process.env.BITCOINCOM_BASEURL}txs/?address=${thisAddress}&pageNum=${currentPage}`
// Query the Insight server.
const response = await axios.get(path)
// Append different address formats to the return data.
const retData = response.data
retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress)
retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress)
retData.currentPage = currentPage
return retData
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
// Get an array of TX information for a given address.
async function transactionsBulk(req, res, next) {
try {
let addresses = req.body.addresses
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
// Reject if address is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({ error: "addresses needs to be an array" })
}
logger.debug(`Executing address/utxo with these addresses: `, addresses)
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
BITBOX.Address.toLegacyAddress(thisAddress)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Loop through each address and collect an array of promises.
addresses = addresses.map(async (address, index) =>
transactionsFromInsight(address, currentPage)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/transactionsBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler. Retrieve any unconfirmed TX information for a given address.
async function transactionsSingle(req, res, next) {
try {
const address = req.params.address
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
logger.debug(
`Executing address/transactionsSingle with this address: `,
address
)
wlogger.debug(
`Executing address/transactionsSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${address}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(address)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
})
}
// Query the Insight API.
const retData = await transactionsFromInsight(address, currentPage)
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/transactionsSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function fromXPubSingle(req, res, next) {
try {
const xpub = req.params.xpub
const hdPath = req.query.hdPath ? req.query.hdPath : "0"
if (!xpub || xpub === "") {
res.status(400)
return res.json({ error: "xpub can not be empty" })
}
// Reject if xpub is an array.
if (Array.isArray(xpub)) {
res.status(400)
return res.json({
error: "xpub can not be an array. Use POST for bulk upload."
})
}
logger.debug(`Executing address/fromXPub with this xpub: `, xpub)
wlogger.debug(`Executing address/fromXPub with this xpub: `, xpub)
const cashAddr = BITBOX.Address.fromXPub(xpub, hdPath)
const legacyAddr = BITBOX.Address.toLegacyAddress(cashAddr)
res.status(200)
return res.json({
cashAddress: cashAddr,
legacyAddress: legacyAddr
})
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/fromXPubSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
testableComponents: {
root,
detailsBulk,
detailsSingle,
utxoBulk,
utxoSingle,
unconfirmedBulk,
unconfirmedSingle,
transactionsBulk,
transactionsSingle,
fromXPubSingle
}
}
-280
View File
@@ -1,280 +0,0 @@
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const bitbox = require("./services/bitbox")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
const routeUtils = require("./route-utils")
// Used for processing error messages before sending them to the user.
const util = require("util")
util.inspect.defaultOptions = { depth: 3 }
const router = express.Router()
//const BitboxHTTP = bitbox.getInstance()
router.get("/", root)
router.get("/detailsByHash/:hash", detailsByHashSingle)
router.post("/detailsByHash", detailsByHashBulk)
router.get("/detailsByHeight/:height", detailsByHeightSingle)
router.post("/detailsByHeight", detailsByHeightBulk)
function root(req, res, next) {
return res.json({ status: "block" })
}
// Call the insight server to get block details based on the hash.
async function detailsByHashSingle(req, res, next) {
try {
const hash = req.params.hash
// Reject if hash is empty
if (!hash || hash === "") {
res.status(400)
return res.json({ error: "hash must not be empty" })
}
const response = await axios.get(
`${process.env.BITCOINCOM_BASEURL}block/${hash}`
)
//console.log(`response.data: ${JSON.stringify(response.data,null,2)}`)
const parsed = response.data
return res.json(parsed)
} catch (error) {
//console.log(`error object: ${util.inspect(error)}`)
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(error)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
if (error.response && error.response.status === 404) {
res.status(404)
return res.json({ error: "Not Found" })
}
// Write out error to error log.
//logger.error(`Error in block/detailsByHash: `, error)
wlogger.error(`Error in block.ts/detailsByHashSingle().`, error)
res.status(500)
return res.json({ error: util.inspect(error) })
}
}
async function detailsByHashBulk(req, res, next) {
try {
const hashes = req.body.hashes
// Reject if hashes is not an array.
if (!Array.isArray(hashes)) {
res.status(400)
return res.json({
error: "hashes needs to be an array. Use GET for single address."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, hashes)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each hash in the array.
for (let i = 0; i < hashes.length; i++) {
const thisHash = hashes[i]
if (thisHash.length !== 64) {
res.status(400)
return res.json({
error: `Invalid hash. Double check your hash is valid: ${thisHash}`
})
}
}
// Loop through each hash and creates an array of promises
const axiosPromises = hashes.map(async hash =>
axios.get(`${process.env.BITCOINCOM_BASEURL}block/${hash}`)
)
// Wait for all parallel promises to return.
const axiosResult = await axios.all(axiosPromises)
// Extract the data component from the axios response.
const result = axiosResult.map(x => x.data)
//console.log(`result: ${util.inspect(result)}`)
res.status(200)
return res.json(result)
} catch (error) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(error)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
if (error.response && error.response.status === 404) {
res.status(404)
return res.json({ error: "Not Found" })
}
// Write out error to error log.
//logger.error(`Error in block/detailsByHash: `, error)
wlogger.error(`Error in block.ts/detailsByHashBulk().`, error)
res.status(500)
return res.json({ error: util.inspect(error) })
}
}
// Call the Full Node to get block hash based on height, then call the Insight
// server to get details from that hash.
async function detailsByHeightSingle(req, res, next) {
try {
const height = req.params.height
// Reject if id is empty
if (!height || height === "") {
res.status(400)
return res.json({ error: "height must not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getblockhash"
requestConfig.data.method = "getblockhash"
requestConfig.data.params = [parseInt(height)]
const response = await BitboxHTTP(requestConfig)
const hash = response.data.result
//console.log(`response.data: ${util.inspect(response.data)}`)
// Call detailsByHashSingle now that the hash has been retrieved.
req.params.hash = hash
return detailsByHashSingle(req, res, next)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in control/getInfo: `, error)
wlogger.error(`Error in block.ts/detailsByHeightSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function detailsByHeightBulk(req, res, next) {
try {
const heights = req.body.heights
// Reject if heights is not an array.
if (!Array.isArray(heights)) {
res.status(400)
return res.json({
error: "heights needs to be an array. Use GET for single height."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, heights)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
logger.debug(`Executing detailsByHeight with these heights: `, heights)
// Validate each element in the address array.
for (let i = 0; i < heights.length; i++) {
const thisHeight = heights[i]
// Reject if id is empty
if (!thisHeight || thisHeight === "") {
res.status(400)
return res.json({ error: "height must not be empty" })
}
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each height and creates an array of requests to call in parallel
const promises = heights.map(async height => {
requestConfig.data.id = "getblockhash"
requestConfig.data.method = "getblockhash"
requestConfig.data.params = [parseInt(height)]
const response = await BitboxHTTP(requestConfig)
const hash = response.data.result
const axiosResult = await axios.get(
`${process.env.BITCOINCOM_BASEURL}block/${hash}`
)
return axiosResult.data
})
// Wait for all parallel Insight requests to return.
const result = await axios.all(promises)
res.status(200)
return res.json(result)
} catch (error) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(error)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
if (error.response && error.response.status === 404) {
res.status(404)
return res.json({ error: "Not Found" })
}
// Write out error to error log.
//logger.error(`Error in block/detailsByHash: `, error)
wlogger.error(`Error in block.ts/detailsByHeightBulk().`, error)
res.status(500)
return res.json({ error: util.inspect(error) })
}
}
module.exports = {
router,
testableComponents: {
root,
detailsByHashSingle,
detailsByHashBulk,
detailsByHeightSingle,
detailsByHeightBulk
}
}
-890
View File
@@ -1,890 +0,0 @@
/*
TODO
- Add blockhash functionality back into getTxOutProof
*/
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
// Define routes.
router.get("/", root)
router.get("/getBestBlockHash", getBestBlockHash)
// Dev Note: getBlock/:hash ommited because its the same as block/detailsByHash
//router.get("/getBlock/:hash", getBlock)
router.get("/getBlockchainInfo", getBlockchainInfo)
router.get("/getBlockCount", getBlockCount)
router.get("/getBlockHeader/:hash", getBlockHeaderSingle)
router.post("/getBlockHeader", getBlockHeaderBulk)
router.get("/getChainTips", getChainTips)
router.get("/getDifficulty", getDifficulty)
router.get("/getMempoolEntry/:txid", getMempoolEntrySingle)
router.post("/getMempoolEntry", getMempoolEntryBulk)
router.get("/getMempoolInfo", getMempoolInfo)
router.get("/getRawMempool", getRawMempool)
router.get("/getTxOut/:txid/:n", getTxOut)
router.get("/getTxOutProof/:txid", getTxOutProofSingle)
router.post("/getTxOutProof", getTxOutProofBulk)
router.get("/verifyTxOutProof/:proof", verifyTxOutProofSingle)
router.post("/verifyTxOutProof", verifyTxOutProofBulk)
function root(req, res, next) {
return res.json({ status: "blockchain" })
}
// Returns the hash of the best (tip) block in the longest block chain.
async function getBestBlockHash(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getbestblockhash"
requestConfig.data.method = "getbestblockhash"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getBlockchainInfo(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getblockchaininfo"
requestConfig.data.method = "getblockchaininfo"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getBlockCount(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getblockcount"
requestConfig.data.method = "getblockcount"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getBlockCount().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getBlockHeaderSingle(req, res, next) {
try {
let verbose = false
if (req.query.verbose && req.query.verbose.toString() === "true")
verbose = true
const hash = req.params.hash
if (!hash || hash === "") {
res.status(400)
return res.json({ error: "hash can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getblockheader"
requestConfig.data.method = "getblockheader"
requestConfig.data.params = [hash, verbose]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getBlockHeaderSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getBlockHeaderBulk(req, res, next) {
try {
const hashes = req.body.hashes
const verbose = req.body.verbose ? req.body.verbose : false
if (!Array.isArray(hashes)) {
res.status(400)
return res.json({
error: "hashes needs to be an array. Use GET for single hash."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, hashes)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
logger.debug(
`Executing blockchain/getBlockHeaderBulk with these hashes: `,
hashes
)
// Validate each hash in the array.
for (let i = 0; i < hashes.length; i++) {
const hash = hashes[i]
if (hash.length !== 64) {
res.status(400)
return res.json({ error: `This is not a hash: ${hash}` })
}
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each hash and creates an array of requests to call in parallel
const promises = hashes.map(async hash => {
requestConfig.data.id = "getblockheader"
requestConfig.data.method = "getblockheader"
requestConfig.data.params = [hash, verbose]
return await BitboxHTTP(requestConfig)
})
const axiosResult = await axios.all(promises)
// Extract the data component from the axios response.
const result = axiosResult.map(x => x.data.result)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getBlockHeaderBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getChainTips(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getchaintips"
requestConfig.data.method = "getchaintips"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getChainTips().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Get the current difficulty value, used to regulate mining power on the network.
async function getDifficulty(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getdifficulty"
requestConfig.data.method = "getdifficulty"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getDifficulty().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Returns mempool data for given transaction. TXID must be in mempool (unconfirmed)
async function getMempoolEntrySingle(req, res, next) {
try {
// Validate input parameter
const txid = req.params.txid
if (!txid || txid === "") {
res.status(400)
return res.json({ error: "txid can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getmempoolentry"
requestConfig.data.method = "getmempoolentry"
requestConfig.data.params = [txid]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getMempoolEntrySingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getMempoolEntryBulk(req, res, next) {
try {
const txids = req.body.txids
if (!Array.isArray(txids)) {
res.status(400)
return res.json({
error: "txids needs to be an array. Use GET for single txid."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
logger.debug(
`Executing blockchain/getMempoolEntry with these txids: `,
txids
)
// Validate each element in the array
for (let i = 0; i < txids.length; i++) {
const txid = txids[i]
if (txid.length !== 64) {
res.status(400)
return res.json({ error: "This is not a txid" })
}
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each txid and creates an array of requests to call in parallel
const promises = txids.map(async txid => {
requestConfig.data.id = "getmempoolentry"
requestConfig.data.method = "getmempoolentry"
requestConfig.data.params = [txid]
return await BitboxHTTP(requestConfig)
})
const axiosResult = await axios.all(promises)
// Extract the data component from the axios response.
const result = axiosResult.map(x => x.data.result)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getMempoolEntryBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getMempoolInfo(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getmempoolinfo"
requestConfig.data.method = "getmempoolinfo"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getMempoolInfo().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getRawMempool(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
let verbose = false
if (req.query.verbose && req.query.verbose === "true") verbose = true
requestConfig.data.id = "getrawmempool"
requestConfig.data.method = "getrawmempool"
requestConfig.data.params = [verbose]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getRawMempool().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Returns details about an unspent transaction output.
async function getTxOut(req, res, next) {
try {
// Validate input parameter
const txid = req.params.txid
if (!txid || txid === "") {
res.status(400)
return res.json({ error: "txid can not be empty" })
}
let n = req.params.n
if (n === undefined || n === "") {
res.status(400)
return res.json({ error: "n can not be empty" })
}
n = parseInt(n)
let include_mempool = false
if (req.query.include_mempool && req.query.include_mempool === "true")
include_mempool = true
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "gettxout"
requestConfig.data.method = "gettxout"
requestConfig.data.params = [txid, n, include_mempool]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getTxOut().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Returns a hex-encoded proof that 'txid' was included in a block.
async function getTxOutProofSingle(req, res, next) {
try {
// Validate input parameter
const txid = req.params.txid
if (!txid || txid === "") {
res.status(400)
return res.json({ error: "txid can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "gettxoutproof"
requestConfig.data.method = "gettxoutproof"
requestConfig.data.params = [[txid]]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getTxOutProofSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Returns a hex-encoded proof that 'txid' was included in a block.
async function getTxOutProofBulk(req, res, next) {
try {
const txids = req.body.txids
// Reject if txids is not an array.
if (!Array.isArray(txids)) {
res.status(400)
return res.json({
error: "txids needs to be an array. Use GET for single txid."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Validate each element in the array.
for (let i = 0; i < txids.length; i++) {
const txid = txids[i]
if (txid.length !== 64) {
res.status(400)
return res.json({
error: `Invalid txid. Double check your txid is valid: ${txid}`
})
}
}
logger.debug(`Executing blockchain/getTxOutProof with these txids: `, txids)
// Loop through each txid and creates an array of requests to call in parallel
const promises = txids.map(async txid => {
requestConfig.data.id = "gettxoutproof"
requestConfig.data.method = "gettxoutproof"
requestConfig.data.params = [[txid]]
return await BitboxHTTP(requestConfig)
})
// Wait for all parallel promisses to resolve.
const axiosResult = await axios.all(promises)
// Extract the data component from the axios response.
const result = axiosResult.map(x => x.data.result)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/getTxOutProofBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/*
//
// router.get('/preciousBlock/:hash', async (req, res, next) => {
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"preciousblock",
// method: "preciousblock",
// params: [
// req.params.hash
// ]
// }
// })
// .then((response) => {
// res.json(JSON.stringify(response.data.result));
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
//
// router.post('/pruneBlockchain/:height', async (req, res, next) => {
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"pruneblockchain",
// method: "pruneblockchain",
// params: [
// req.params.height
// ]
// }
// })
// .then((response) => {
// res.json(response.data.result);
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
//
// router.get('/verifyChain', async (req, res, next) => {
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"verifychain",
// method: "verifychain"
// }
// })
// .then((response) => {
// res.json(response.data.result);
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
*/
async function verifyTxOutProofSingle(req, res, next) {
try {
// Validate input parameter
const proof = req.params.proof
if (!proof || proof === "") {
res.status(400)
return res.json({ error: "proof can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "verifytxoutproof"
requestConfig.data.method = "verifytxoutproof"
requestConfig.data.params = [req.params.proof]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/verifyTxOutProofSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function verifyTxOutProofBulk(req, res, next) {
try {
const proofs = req.body.proofs
// Reject if proofs is not an array.
if (!Array.isArray(proofs)) {
res.status(400)
return res.json({
error: "proofs needs to be an array. Use GET for single proof."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, proofs)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Validate each element in the array.
for (let i = 0; i < proofs.length; i++) {
const proof = proofs[i]
if (!proof || proof === "") {
res.status(400)
return res.json({ error: `proof can not be empty: ${proof}` })
}
}
logger.debug(
`Executing blockchain/verifyTxOutProof with these proofs: `,
proofs
)
// Loop through each proof and creates an array of requests to call in parallel
const promises = proofs.map(async proof => {
requestConfig.data.id = "verifytxoutproof"
requestConfig.data.method = "verifytxoutproof"
requestConfig.data.params = [proof]
return await BitboxHTTP(requestConfig)
})
// Wait for all parallel promisses to resolve.
const axiosResult = await axios.all(promises)
// Extract the data component from the axios response.
const result = axiosResult.map(x => x.data.result[0])
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in blockchain.ts/verifyTxOutProofBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
testableComponents: {
root,
getBestBlockHash,
//getBlock,
getBlockchainInfo,
getBlockCount,
getBlockHeaderSingle,
getBlockHeaderBulk,
getChainTips,
getDifficulty,
getMempoolInfo,
getRawMempool,
getMempoolEntrySingle,
getMempoolEntryBulk,
getTxOut,
getTxOutProofSingle,
getTxOutProofBulk,
verifyTxOutProofSingle,
verifyTxOutProofBulk
}
}
-51
View File
@@ -1,51 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
//const axios = require("axios");
//const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default;
//const BITBOX = new BITBOXCli();
//const BitboxHTTP = axios.create({
// baseURL: process.env.RPC_BASEURL,
//});
//const username = process.env.RPC_USERNAME;
//const password = process.env.RPC_PASSWORD;
router.get("/", (req, res, next) => {
res.json({ status: "generating" })
})
//
// router.post('/generateToAddress/:nblocks/:address', (req, res, next) => {
// let maxtries = 1000000;
// if(req.query.maxtries) {
// maxtries = parseInt(req.query.maxtries);
// }
//
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"generatetoaddress",
// method: "generatetoaddress",
// params: [
// req.params.nblocks,
// req.params.address,
// maxtries
// ]
// }
// })
// .then((response) => {
// res.json(response.data.result);
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
module.exports = router
-11
View File
@@ -1,11 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
/* GET home page. */
router.get("/", (req, res, next) => {
res.json({ status: "winning v2" })
})
module.exports = router
-15
View File
@@ -1,15 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
/* GET home page. */
router.get("/", (req, res, next) => {
res.render("swagger-v2")
})
router.get("/v2", (req, res, next) => {
res.render("swagger-v2")
})
module.exports = router
-51
View File
@@ -1,51 +0,0 @@
/*
A utility library for setting up greylog2 logging.
*/
"use strict"
// This will be uncommented and correct once we have our logging server functioning.
/*
var graylog2 = require("graylog2");
var logger = new graylog2.graylog({
servers: [
{ 'host': '127.0.0.1', port: 12201 },
{ 'host': '127.0.0.2', port: 12201 }
],
hostname: 'server.name', // the name of this host
// (optional, default: os.hostname())
facility: 'Node.js', // the facility for these log messages
// (optional, default: "Node.js")
bufferSize: 1350 // max UDP packet size, should never exceed the
// MTU of your system (optional, default: 1400)
});
logger.on('error', function (error) {
console.error('Error while trying to write to graylog2:', error);
});
*/
// This is just a placeholder function that will be replaced once we get the
// greylog server working.
function log(msg, obj) {
//console.log(msg, obj)
}
// This is just a placeholder function that will be replaced once we get the
// greylog server working.
function error(msg, obj) {
if (!obj) console.error(msg)
else console.error(msg, obj)
}
// This is just a placeholder function that will be replaced once we get the
// greylog server working.
function debug(msg, obj) {
//console.log(msg, obj)
}
module.exports = {
log,
error,
debug
}
-170
View File
@@ -1,170 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", root)
router.get("/getMiningInfo", getMiningInfo)
router.get("/getNetworkHashps", getNetworkHashPS)
function root(req, res, next) {
return res.json({ status: "mining" })
}
//
// router.get('/getBlockTemplate/:templateRequest', (req, res, next) => {
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"getblocktemplate",
// method: "getblocktemplate",
// params: [
// req.params.templateRequest
// ]
// }
// })
// .then((response) => {
// res.json(response.data.result);
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
async function getMiningInfo(req, res, next) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getmininginfo"
requestConfig.data.method = "getmininginfo"
requestConfig.data.params = []
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in mining.ts/getMiningInfo().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function getNetworkHashPS(req, res, next) {
try {
let nblocks = 120 // Default
let height = -1 // Default
if (req.query.nblocks) nblocks = parseInt(req.query.nblocks)
if (req.query.height) height = parseInt(req.query.height)
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getnetworkhashps"
requestConfig.data.method = "getnetworkhashps"
requestConfig.data.params = [nblocks, height]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in mining.ts/getNetworkHashPS().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
//
// router.post('/submitBlock/:hex', (req, res, next) => {
// let parameters = '';
// if(req.query.parameters && req.query.parameters !== '') {
// parameters = true;
// }
//
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"submitblock",
// method: "submitblock",
// params: [
// req.params.hex,
// parameters
// ]
// }
// })
// .then((response) => {
// res.json(response.data.result);
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
module.exports = {
router,
testableComponents: {
root,
getMiningInfo,
getNetworkHashPS
}
}
-609
View File
@@ -1,609 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", root)
router.get("/decodeRawTransaction/:hex", decodeRawTransactionSingle)
router.post("/decodeRawTransaction", decodeRawTransactionBulk)
router.get("/decodeScript/:hex", decodeScriptSingle)
router.post("/decodeScript", decodeScriptBulk)
router.post("/getRawTransaction", getRawTransactionBulk)
router.get("/getRawTransaction/:txid", getRawTransactionSingle)
router.post("/sendRawTransaction", sendRawTransactionBulk)
router.get("/sendRawTransaction/:hex", sendRawTransactionSingle)
function root(req, res, next) {
return res.json({ status: "rawtransactions" })
}
// Decode transaction hex into a JSON object.
// GET
async function decodeRawTransactionSingle(req, res, next) {
try {
const hex = req.params.hex
// Throw an error if hex is empty.
if (!hex || hex === "") {
res.status(400)
return res.json({ error: "hex can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "decoderawtransaction"
requestConfig.data.method = "decoderawtransaction"
requestConfig.data.params = [hex]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(
`Error in rawtransactions.ts/decodeRawTransactionSingle().`,
err
)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function decodeRawTransactionBulk(req, res, next) {
try {
const hexes = req.body.hexes
if (!Array.isArray(hexes)) {
res.status(400)
return res.json({ error: "hexes must be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, hexes)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
const results = []
// Validate each element in the address array.
for (let i = 0; i < hexes.length; i++) {
const thisHex = hexes[i]
// Reject if id is empty
if (!thisHex || thisHex === "") {
res.status(400)
return res.json({ error: "Encountered empty hex" })
}
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each height and creates an array of requests to call in parallel
const promises = hexes.map(async hex => {
requestConfig.data.id = "decoderawtransaction"
requestConfig.data.method = "decoderawtransaction"
requestConfig.data.params = [hex]
return await BitboxHTTP(requestConfig)
})
// Wait for all parallel Insight requests to return.
const axiosResult = await axios.all(promises)
// Retrieve the data part of the result.
const result = axiosResult.map(x => x.data.result)
res.status(200)
return res.json(result)
/*
// Loop through each hex and creates an array of requests to call in parallel
hexes = hexes.map(async (hex: any) => {
if (!hex || hex === "") {
res.status(400)
return res.json({ error: "Encountered empty hex" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "decoderawtransaction"
requestConfig.data.method = "decoderawtransaction"
requestConfig.data.params = [hex]
return await BitboxHTTP(requestConfig)
})
const result: Array<any> = []
return axios.all(hexes).then(
axios.spread((...args) => {
args.forEach((arg: any) => {
if (arg) {
result.push(arg.data.result)
}
})
res.status(200)
return res.json(result)
})
)
*/
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/getRawTransaction: `, err)
wlogger.error(
`Error in rawtransactions.ts/decodeRawTransactionBulk().`,
err
)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Decode a raw transaction from hex to assembly.
// GET single
async function decodeScriptSingle(req, res, next) {
try {
const hex = req.params.hex
// Throw an error if hex is empty.
if (!hex || hex === "") {
res.status(400)
return res.json({ error: "hex can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "decodescript"
requestConfig.data.method = "decodescript"
requestConfig.data.params = [hex]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeScript: `, err)
wlogger.error(`Error in rawtransactions.ts/decodeScriptSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Decode a raw transaction from hex to assembly.
// POST bulk
async function decodeScriptBulk(req, res, next) {
try {
const hexes = req.body.hexes
// Validation
if (!Array.isArray(hexes)) {
res.status(400)
return res.json({ error: "hexes must be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, hexes)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each hex in the array
for (let i = 0; i < hexes.length; i++) {
const hex = hexes[i]
// Throw an error if hex is empty.
if (!hex || hex === "") {
res.status(400)
return res.json({ error: "Encountered empty hex" })
}
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each hex and create an array of promises
const promises = hexes.map(async hex => {
requestConfig.data.id = "decodescript"
requestConfig.data.method = "decodescript"
requestConfig.data.params = [hex]
const response = await BitboxHTTP(requestConfig)
return response
})
// Wait for all parallel promises to return.
const resolved = await Promise.all(promises)
// Retrieve the data from each resolved promise.
const result = resolved.map(x => x.data.result)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeScript: `, err)
wlogger.error(`Error in rawtransactions.ts/decodeScriptBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Retrieve raw transactions details from the full node.
async function getRawTransactionsFromNode(txid, verbose) {
try {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getrawtransaction"
requestConfig.data.method = "getrawtransaction"
requestConfig.data.params = [txid, verbose]
const response = await BitboxHTTP(requestConfig)
return response.data.result
} catch (err) {
wlogger.error(`Error in rawtransactions.ts/getRawTransactionsFromNode().`)
throw err
}
}
// Get a JSON object breakdown of transaction details.
// POST
async function getRawTransactionBulk(req, res, next) {
try {
let verbose = 0
if (req.body.verbose) verbose = 1
const txids = req.body.txids
if (!Array.isArray(txids)) {
res.status(400)
return res.json({ error: "txids must be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// stub response object
const returnResponse = {
status: 100,
json: {
error: ""
}
}
// Validate each txid in the array.
for (let i = 0; i < txids.length; i++) {
const txid = txids[i]
if (!txid || txid === "") {
res.status(400)
return res.json({ error: `Encountered empty TXID` })
}
if (txid.length !== 64) {
res.status(400)
return res.json({
error: `parameter 1 must be of length 64 (not ${txid.length})`
})
}
}
// Loop through each txid and create an array of promises
const promises = txids.map(async txid =>
getRawTransactionsFromNode(txid, verbose)
)
// Wait for all parallel promises to return.
const axiosResult = await axios.all(promises)
res.status(200)
return res.json(axiosResult)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/getRawTransaction: `, err)
wlogger.error(`Error in rawtransactions.ts/getRawTransactionBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Get a JSON object breakdown of transaction details.
// GET
async function getRawTransactionSingle(req, res, next) {
try {
let verbose = 0
if (req.query.verbose === "true") verbose = 1
const txid = req.params.txid
if (!txid || txid === "") {
res.status(400)
return res.json({ error: "txid can not be empty" })
}
const data = await getRawTransactionsFromNode(txid, verbose)
return res.json(data)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/getRawTransaction: `, err)
wlogger.error(`Error in rawtransactions.ts/getRawTransactionSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Transmit a raw transaction to the BCH network.
async function sendRawTransactionBulk(req, res, next) {
try {
// Validation
const hexes = req.body.hexes
// Reject if input is not an array
if (!Array.isArray(hexes)) {
res.status(400)
return res.json({ error: "hex must be an array" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, hexes)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element
for (let i = 0; i < hexes.length; i++) {
const hex = hexes[i]
if (hex === "") {
res.status(400)
return res.json({
error: `Encountered empty hex`
})
}
}
// Dev Note CT 1/31/2019:
// Sending the 'sendrawtrnasaction' RPC call to a full node in parallel will
// not work. Testing showed that the full node will return the same TXID for
// different TX hexes. I believe this is by design, to prevent double spends.
// In parallel, we are essentially asking the node to broadcast a new TX before
// it's finished broadcast the previous one. Serial execution is required.
// How to send TX hexes in parallel the WRONG WAY:
/*
// Collect an array of promises.
const promises = hexes.map(async (hex: any) => {
requestConfig.data.id = "sendrawtransaction"
requestConfig.data.method = "sendrawtransaction"
requestConfig.data.params = [hex]
return await BitboxHTTP(requestConfig)
})
// Wait for all parallel Insight requests to return.
const axiosResult: Array<any> = await axios.all(promises)
// Retrieve the data part of the result.
const result = axiosResult.map(x => x.data.result)
*/
// Sending them serially.
const result = []
for (let i = 0; i < hexes.length; i++) {
const hex = hexes[i]
requestConfig.data.id = "sendrawtransaction"
requestConfig.data.method = "sendrawtransaction"
requestConfig.data.params = [hex]
const rpcResult = await BitboxHTTP(requestConfig)
result.push(rpcResult.data.result)
}
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in rawtransactions.ts/sendRawTransactionBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Transmit a raw transaction to the BCH network.
async function sendRawTransactionSingle(req, res, next) {
try {
const hex = req.params.hex // URL parameter
// Reject if input is not an array or a string
if (typeof hex !== "string") {
res.status(400)
return res.json({ error: "hex must be a string" })
}
// Validation
if (hex === "") {
res.status(400)
return res.json({
error: `Encountered empty hex`
})
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// RPC call
requestConfig.data.id = "sendrawtransaction"
requestConfig.data.method = "sendrawtransaction"
requestConfig.data.params = [hex]
const rpcResult = await BitboxHTTP(requestConfig)
const result = rpcResult.data.result
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(
`Error in rawtransactions.ts/sendRawTransactionSingle().`,
err
)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
getRawTransactionsFromNode,
testableComponents: {
root,
decodeRawTransactionSingle,
decodeRawTransactionBulk,
decodeScriptSingle,
decodeScriptBulk,
getRawTransactionBulk,
getRawTransactionSingle,
sendRawTransactionBulk,
sendRawTransactionSingle
}
}
-139
View File
@@ -1,139 +0,0 @@
/*
A private library of utility functions used by several different routes.
*/
"use strict"
const axios = require("axios")
const wlogger = require("../../util/winston-logging")
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const BITBOXJS = require("@chris.troutner/bch-js")
const BITBOX = new BITBOXJS()
module.exports = {
validateNetwork, // Prevents a common user error
setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars.
decodeError, // Extract and interpret error messages.
validateArraySize // Ensure the passed array meets rate limiting requirements.
}
// This function expects the Request Express.js object and an array as input.
// The array is then validated against freemium and pro-tier rate limiting
// requirements. A boolean is returned to indicate if the array size if valid
// or not.
function validateArraySize(req, array) {
const FREEMIUM_INPUT_SIZE = 20
const PRO_INPUT_SIZE = 100
if (req.locals && req.locals.proLimit) {
if (array.length <= PRO_INPUT_SIZE) return true
} else if (array.length <= FREEMIUM_INPUT_SIZE) {
return true
}
return false
}
// Returns true if user-provided cash address matches the correct network,
// mainnet or testnet. If NETWORK env var is not defined, it returns false.
// This prevent a common user-error issue that is easy to make: passing a
// testnet address into rest.bitcoin.com or passing a mainnet address into
// trest.bitcoin.com.
function validateNetwork(addr) {
try {
const network = process.env.NETWORK
// Return false if NETWORK is not defined.
if (!network || network === "") {
console.log(`Warning: NETWORK environment variable is not defined!`)
return false
}
// Convert the user-provided address to a cashaddress, for easy detection
// of the intended network.
const cashAddr = BITBOX.Address.toCashAddress(addr)
// Return true if the network and address both match testnet
const addrIsTest = BITBOX.Address.isTestnetAddress(cashAddr)
if (network === "testnet" && addrIsTest) return true
// Return true if the network and address both match mainnet
const addrIsMain = BITBOX.Address.isMainnetAddress(cashAddr)
if (network === "mainnet" && addrIsMain) return true
return false
} catch (err) {
logger.error(`Error in validateNetwork()`)
return false
}
}
// Dynamically set these based on env vars. Allows unit testing.
function setEnvVars() {
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
return { BitboxHTTP, username, password, requestConfig }
}
// Error messages returned by a full node can be burried pretty deep inside the
// error object returned by Axios. This function attempts to extract and interpret
// error messages.
// Returns an object. If successful, obj.msg is a string.
// If there is a failure, obj.msg is false.
function decodeError(err) {
try {
// Attempt to extract the full node error message.
if (
err.response &&
err.response.data &&
err.response.data.error &&
err.response.data.error.message
)
return { msg: err.response.data.error.message, status: 400 }
// Attempt to extract the Insight error message
if (err.response && err.response.data)
return { msg: err.response.data, status: err.response.status }
// Attempt to detect a network connection error.
if (err.message && err.message.indexOf("ENOTFOUND") > -1) {
return {
msg:
"Network error: Could not communicate with full node or other external service.",
status: 503
}
}
// Different kind of network error
if (err.message && err.message.indexOf("ENETUNREACH") > -1) {
return {
msg:
"Network error: Could not communicate with full node or other external service.",
status: 503
}
}
return { msg: false, status: 500 }
} catch (err) {
wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err)
return { msg: false, status: 500 }
}
}
-11
View File
@@ -1,11 +0,0 @@
"use strict"
const axios = require("axios")
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const getInstance = () => BitboxHTTP
module.exports = getInstance
-20
View File
@@ -1,20 +0,0 @@
"use strict"
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const getRequestConfig = (method, params) => ({
method: "post",
auth: {
username,
password
},
data: {
jsonrpc: "1.0",
id: method,
method,
params
}
})
module.exports = getRequestConfig
-1369
View File
File diff suppressed because it is too large Load Diff
-188
View File
@@ -1,188 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
const BITBOXJS = require("@chris.troutner/bch-js")
const BITBOX = new BITBOXJS()
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 3 }
// Manipulates and formats the raw data comming from Insight API.
const processInputs = tx => {
// Add legacy and cashaddr to tx vin
if (tx.vin) {
tx.vin.forEach(vin => {
if (!vin.coinbase) {
vin.value = vin.valueSat
const address = vin.addr
if (address) {
vin.legacyAddress = BITBOX.Address.toLegacyAddress(address)
vin.cashAddress = BITBOX.Address.toCashAddress(address)
delete vin.addr
}
delete vin.valueSat
delete vin.doubleSpentTxID
}
})
}
// Add legacy and cashaddr to tx vout
if (tx.vout) {
tx.vout.forEach(vout => {
// Overwrite value string with value in satoshis
//vout.value = parseFloat(vout.value) * 100000000
if (vout.scriptPubKey) {
if (vout.scriptPubKey.addresses) {
const cashAddrs = []
vout.scriptPubKey.addresses.forEach(addr => {
const cashAddr = BITBOX.Address.toCashAddress(addr)
cashAddrs.push(cashAddr)
})
vout.scriptPubKey.cashAddrs = cashAddrs
}
}
})
}
}
router.get("/", root)
router.post("/details", detailsBulk)
router.get("/details/:txid", detailsSingle)
function root(req, res, next) {
return res.json({ status: "transaction" })
}
// Retrieve transaction data from the Insight API
// This function is also used by the SLP route library.
async function transactionsFromInsight(txid) {
try {
const path = `${process.env.BITCOINCOM_BASEURL}tx/${txid}`
// Query the Insight server.
const response = await axios.get(path)
//console.log(`Insight output: ${JSON.stringify(response.data, null, 2)}`)
// Parse the data.
const parsed = response.data
if (parsed) processInputs(parsed)
return parsed
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
async function detailsBulk(req, res, next) {
try {
const txids = req.body.txids
// Reject if address is not an array.
if (!Array.isArray(txids)) {
res.status(400)
return res.json({ error: "txids needs to be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
logger.debug(`Executing transaction/details with these txids: `, txids)
// Collect an array of promises
const promises = txids.map(
async txid => await transactionsFromInsight(txid)
)
// Wait for all parallel promises to return.
const result = await Promise.all(promises)
// Return the array of retrieved transaction information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in transactions.ts/detailsBulk().`, err)
//console.log(`Error in transaction details: `, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler. Retrieve any unconfirmed TX information for a given address.
async function detailsSingle(req, res, next) {
try {
const txid = req.params.txid
if (!txid || txid === "") {
res.status(400)
return res.json({ error: "txid can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(txid)) {
res.status(400)
return res.json({
error: "txid can not be an array. Use POST for bulk upload."
})
}
logger.debug(
`Executing transaction.ts/detailsSingle with this txid: `,
txid
)
// Query the Insight API.
const retData = await transactionsFromInsight(txid)
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in transactions.ts/detailsSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
transactionsFromInsight,
testableComponents: {
root,
detailsBulk,
detailsSingle
}
}
-173
View File
@@ -1,173 +0,0 @@
"use strict"
const express = require("express")
const router = express.Router()
const axios = require("axios")
const routeUtils = require("./route-utils")
const logger = require("./logging.js")
const wlogger = require("../../util/winston-logging")
const BITBOXJS = require("@chris.troutner/bch-js")
const BITBOX = new BITBOXJS()
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const requestConfig = {
method: "post",
auth: {
username: username,
password: password
},
data: {
jsonrpc: "1.0"
}
}
router.get("/", root)
router.get("/validateAddress/:address", validateAddressSingle)
router.post("/validateAddress", validateAddressBulk)
function root(req, res, next) {
return res.json({ status: "util" })
}
async function validateAddressSingle(req, res, next) {
try {
const address = req.params.address
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "validateaddress"
requestConfig.data.method = "validateaddress"
requestConfig.data.params = [address]
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in util.ts/validateAddressSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
async function validateAddressBulk(req, res, next) {
try {
const addresses = req.body.addresses
// Reject if addresses is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({
error: "addresses needs to be an array. Use GET for single address."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the array.
for (let i = 0; i < addresses.length; i++) {
const address = addresses[i]
// Ensure the input is a valid BCH address.
try {
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${address}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(address)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
logger.debug(`Executing util/validate with these addresses: `, addresses)
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each address and creates an array of requests to call in parallel
const promises = addresses.map(async address => {
requestConfig.data.id = "validateaddress"
requestConfig.data.method = "validateaddress"
requestConfig.data.params = [address]
return await BitboxHTTP(requestConfig)
})
// Wait for all parallel Insight requests to return.
const axiosResult = await axios.all(promises)
// Retrieve the data part of the result.
const result = axiosResult.map(x => x.data.result)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in util.ts/validateAddressSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
testableComponents: {
root,
validateAddressSingle,
validateAddressBulk
}
}
-396
View File
@@ -1,396 +0,0 @@
/*
Bitcore Node API route
*/
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const routeUtils = require("./route-utils")
const wlogger = require("../../util/winston-logging")
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("@chris.troutner/bch-js")
const bchjs = new BCHJS()
//const BITCORE_URL = process.env.BITCORE_URL
// Connect the route endpoints to their handler functions.
router.get("/", root)
router.get("/balance/:address", balanceSingle)
router.post("/balance", balanceBulk)
router.get("/utxos/:address", utxosSingle)
router.post("/utxos", utxosBulk)
// Root API endpoint. Simply acknowledges that it exists.
function root(req, res, next) {
return res.json({ status: "address" })
}
// Query the Bitcore Node API for a balance on a single BCH address.
// Returns a Promise.
async function balanceFromBitcore(thisAddress) {
try {
//console.log(`BITCORE_URL: ${BITCORE_URL}`)
// Convert the address to a cashaddr without a prefix.
const addr = bchjs.Address.toCashAddress(thisAddress, false)
// Determine if we are working with the testnet or mainnet networks.
let network = "mainnet"
if (process.env.NETWORK === "testnet") network = "testnet"
const path = `${process.env.BITCORE_URL}api/BCH/${network}/address/${addr}/balance`
//console.log(`path: ${path}`)
// Query the Bitcore Node API.
const axiosResponse = await axios.get(path)
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.
throw err
}
}
/**
* @api {get} /bitcore/balance/:address Get BCH balance for an address
* @apiName GetBalance
* @apiGroup Bitcore
*
* @apiParam {String} address Bitcoin Cash address.
*
* @apiSuccess {String} firstname Firstname of the User.
* @apiSuccess {String} lastname Lastname of the User.
*/
// GET handler for single balance
async function 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 bitcore/balanceSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
const legacyAddr = 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.`
})
}
// Query the Bitcore Node API.
const retData = await balanceFromBitcore(address)
// Return the retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
wlogger.error(`Error in bitcore.js/balanceSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// POST handler for bulk queries on address details
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v2/address/details
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v2/address/details
async function 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 (!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 bitcore.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 {
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 = 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) =>
balanceFromBitcore(address)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in bitcore.js/balanceBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Query the Bitcore Node API for utxos associated with a BCH address.
// Returns a Promise.
async function utxosFromBitcore(thisAddress) {
try {
//console.log(`BITCORE_URL: ${BITCORE_URL}`)
// Convert the address to a cashaddr without a prefix.
const addr = bchjs.Address.toCashAddress(thisAddress, false)
// Determine if we are working with the testnet or mainnet networks.
let network = "mainnet"
if (process.env.NETWORK === "testnet") network = "testnet"
const path = `${process.env.BITCORE_URL}api/BCH/${network}/address/${addr}/?unspent=true`
// Query the Bitcore Node API.
const axiosResponse = await axios.get(path)
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.
throw err
}
}
// GET handler for single balance
async function 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 bitcore/utxoSingle with this address: `, address)
// Ensure the input is a valid BCH address.
try {
const legacyAddr = 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.`
})
}
// Query the Bitcore Node API.
const retData = await utxosFromBitcore(address)
// Return the retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
wlogger.error(`Error in bitcore.js/utxosSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// POST handler for bulk queries on address utxos
async function 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 (!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 bitcore.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 {
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 = 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) =>
utxosFromBitcore(address)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in bitcore.js/utxosBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
testableComponents: {
root,
balanceSingle,
balanceBulk,
utxosSingle,
utxosBulk
}
}
-1
View File
@@ -5,7 +5,6 @@
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const routeUtils = require("./route-utils")
const wlogger = require("../../util/winston-logging")
-156
View File
@@ -1,156 +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")
// Used for processing error messages before sending them to the user.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
router.get("/", root)
router.get("/getinfo", getInfo)
router.get("/getnetworkinfo", getNetworkInfo)
function root(req, res, next) {
return res.json({ status: "control" })
}
/**
* @api {get} /control/getinfo Get full node info
* @apiName GetInfo
* @apiGroup Control
* @apiDescription RPC call which gets basic full node information.
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/control/getinfo" -H "accept: application/json"
*
* @apiSuccess {Object} object Object containing data
* @apiSuccess {Number} object.version Full node version
* @apiSuccess {Number} object.protocolversion Protocol version
* @apiSuccess {Number} object.blocks Current block
* @apiSuccess {Number} object.timeoffset ?
* @apiSuccess {Number} object.connections Number of connected peers
* @apiSuccess {String} object.proxy ?
* @apiSuccess {Number} object.difficulty Current difficulty setting
* @apiSuccess {Boolean} object.testnet testnet = true, mainnet = false
* @apiSuccess {Number} object.paytxfee ?
* @apiSuccess {Number} object.relayfee ?
* @apiSuccess {String} object.errors Recent errors
*/
async function getInfo(req, res, next) {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getinfo"
requestConfig.data.method = "getinfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (error) {
wlogger.error(`Error in control.ts/getInfo().`, error)
// Write out error to error log.
//logger.error(`Error in control/getInfo: `, error)
res.status(500)
if (error.response && error.response.data && error.response.data.error)
return res.json({ error: error.response.data.error })
return res.json({ error: util.inspect(error) })
}
}
/**
* @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://mainnet.bchjs.cash/v3/control/getnetworkinfo" -H "accept: application/json"
*
*/
async function getNetworkInfo(req, res, next) {
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getnetworkinfo"
requestConfig.data.method = "getnetworkinfo"
requestConfig.data.params = []
try {
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
} catch (error) {
wlogger.error(`Error in control.ts/getNetworkInfo().`, error)
// Write out error to error log.
//logger.error(`Error in control/getInfo: `, error)
res.status(500)
if (error.response && error.response.data && error.response.data.error)
return res.json({ error: error.response.data.error })
return res.json({ error: util.inspect(error) })
}
}
// router.get('/getMemoryInfo', (req, res, next) => {
// BitboxHTTP({
// method: 'post',
// auth: {
// username: username,
// password: password
// },
// data: {
// jsonrpc: "1.0",
// id:"getmemoryinfo",
// method: "getmemoryinfo"
// }
// })
// .then((response) => {
// res.json(response.data.result);
// })
// .catch((error) => {
// res.send(error.response.data.error.message);
// });
// });
//
// router.get('/help', (req, res, next) => {
// BITBOX.Control.help()
// .then((result) => {
// res.json(result);
// }, (err) => { console.log(err);
// });
// });
//
// router.post('/stop', (req, res, next) => {
// BITBOX.Control.stop()
// .then((result) => {
// res.json(result);
// }, (err) => { console.log(err);
// });
// });
module.exports = {
router,
testableComponents: {
root,
getInfo,
getNetworkInfo
}
}
@@ -9,8 +9,8 @@ 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("../route-utils")
const wlogger = require("../../../util/winston-logging")
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
@@ -732,6 +732,8 @@ async function getTxOut(req, res, next) {
requestConfig.data.method = "gettxout"
requestConfig.data.params = [txid, n, include_mempool]
// console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`)
const response = await BitboxHTTP(requestConfig)
return res.json(response.data.result)
@@ -4,23 +4,31 @@ const express = require("express")
const router = express.Router()
const axios = require("axios")
const logger = require("./logging.js")
const routeUtils = require("./route-utils")
const wlogger = require("../../util/winston-logging")
const routeUtils = require("../route-utils")
const wlogger = require("../../../util/winston-logging")
// Used for processing error messages before sending them to the user.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
router.get("/", root)
router.get("/getInfo", getInfo)
router.get("/getnetworkinfo", getNetworkInfo)
function root(req, res, next) {
return res.json({ status: "control" })
}
// Execute the RPC getinfo call.
async function getInfo(req, res, next) {
/**
* @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://mainnet.bchjs.cash/v3/control/getnetworkinfo" -H "accept: application/json"
*
*/
async function getNetworkInfo(req, res, next) {
const {
BitboxHTTP,
username,
@@ -28,8 +36,8 @@ async function getInfo(req, res, next) {
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getinfo"
requestConfig.data.method = "getinfo"
requestConfig.data.id = "getnetworkinfo"
requestConfig.data.method = "getnetworkinfo"
requestConfig.data.params = []
try {
@@ -37,7 +45,7 @@ async function getInfo(req, res, next) {
return res.json(response.data.result)
} catch (error) {
wlogger.error(`Error in control.ts/getInfo().`, error)
wlogger.error(`Error in control.ts/getNetworkInfo().`, error)
// Write out error to error log.
//logger.error(`Error in control/getInfo: `, error)
@@ -90,6 +98,6 @@ module.exports = {
router,
testableComponents: {
root,
getInfo
getNetworkInfo
}
}
@@ -4,8 +4,8 @@ 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("../route-utils")
const wlogger = require("../../../util/winston-logging")
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
@@ -4,8 +4,8 @@ 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("../route-utils")
const wlogger = require("../../../util/winston-logging")
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
-887
View File
@@ -1,887 +0,0 @@
/*
Address route
*/
"use strict"
const express = require("express")
const requestUtils = require("../services/requestUtils")
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("@chris.troutner/bch-js")
const bchjs = new BCHJS()
// Use the default (and max) page size of 1000
// https://github.com/bitpay/insight-api#notes-on-upgrading-from-v03
const PAGE_SIZE = 1000
// Connect the route endpoints to their handler functions.
router.get("/", root)
router.get("/details/:address", detailsSingle)
router.post("/details", detailsBulk)
router.post("/utxo", utxoBulk)
router.get("/utxo/:address", utxoSingle)
router.post("/unconfirmed", unconfirmedBulk)
router.get("/unconfirmed/:address", unconfirmedSingle)
router.get("/transactions/:address", transactionsSingle)
router.post("/transactions", transactionsBulk)
router.get("/fromXPub/:xpub", fromXPubSingle)
// Root API endpoint. Simply acknowledges that it exists.
function root(req, res, next) {
return res.json({ status: "address" })
}
// Query the Insight API for details on a single BCH address.
// Returns a Promise.
async function detailsFromInsight(thisAddress, currentPage = 0) {
try {
//const addr = bchjs.Address.toLegacyAddress(thisAddress)
const addr = bchjs.Address.toCashAddress(thisAddress)
let path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}`
// Set from and to params based on currentPage and pageSize
// https://github.com/bitpay/insight-api/blob/master/README.md#notes-on-upgrading-from-v02
const from = currentPage * PAGE_SIZE
const to = from + PAGE_SIZE
path = `${path}?from=${from}&to=${to}`
//console.log(`insight path: ${path}`)
// Query the Insight server.
const axiosResponse = await axios.get(path)
const retData = axiosResponse.data
//console.log(`retData: ${util.inspect(retData)}`)
// Calculate pagesTotal from response
const pagesTotal = Math.ceil(retData.txApperances / PAGE_SIZE)
// Append different address formats to the return data.
retData.legacyAddress = bchjs.Address.toLegacyAddress(retData.addrStr)
retData.cashAddress = bchjs.Address.toCashAddress(retData.addrStr)
delete retData.addrStr
// Append pagination information to the return data.
retData.currentPage = currentPage
retData.pagesTotal = pagesTotal
return retData
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
// POST handler for bulk queries on address details
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v2/address/details
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v2/address/details
/**
* @api {post} /address/details Get Address details bulk.
* @apiName Address details bulk
* @apiGroup Address
* @apiDescription Returns the details of multiple addresses.
*
*
* @apiExample Example usage:
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"]}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/details
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"], "from": 1, "to": 5}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/details
*
*/
async function detailsBulk(req, res, next) {
try {
let addresses = req.body.addresses
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
// Reject if addresses is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({
error: "addresses needs to be an array. Use GET for single address."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
wlogger.debug(`Executing address/details with these addresses: `, addresses)
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
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 = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Loops through each address and creates an array of Promises, querying
// Insight API in parallel.
addresses = addresses.map(async (address, index) =>
detailsFromInsight(address, currentPage)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
//logger.error(`Error in detailsBulk(): `, err)
wlogger.error(`Error in address.ts/detailsBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler for single address details
/**
* @api {get} insight/address/details/{address} Get Address details single.
* @apiName Address details single
* @apiGroup Address
* @apiDescription Returns the details of an address including balance.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/address/details/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
*
*
*/
async function detailsSingle(req, res, next) {
try {
const address = req.params.address
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
wlogger.debug(
`Executing address/detailsSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = 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.`
})
}
// Query the Insight API.
const retData = await detailsFromInsight(address, currentPage)
// Return the retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in address.ts/detailsSingle: `, err)
wlogger.error(`Error in address.ts/detailsSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Retrieve UTXO data from the Insight API
async function utxoFromInsight(thisAddress) {
try {
const addr = bchjs.Address.toCashAddress(thisAddress)
const path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}/utxo`
// Query the Insight server.
const response = await axios.get(path)
// Append different address formats to the return data.
const retData = {
utxos: Array,
legacyAddress: String,
cashAddress: String,
scriptPubKey: String
}
if (response.data.length && response.data[0].scriptPubKey) {
const spk = response.data[0].scriptPubKey
retData.scriptPubKey = spk
}
retData.legacyAddress = bchjs.Address.toLegacyAddress(thisAddress)
retData.cashAddress = bchjs.Address.toCashAddress(thisAddress)
retData.utxos = response.data.map(utxo => {
delete utxo.address
delete utxo.scriptPubKey
return utxo
})
//console.log(`utxoFromInsight retData: ${util.inspect(retData)}`)
return retData
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
/**
* @api {post} /address/utxo Get Address utxos bulk.
* @apiName Address utxos bulk.
* @apiGroup Address
* @apiDescription Returns the utxo of multiple addresses.
*
*
* @apiExample Example usage:
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"]}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/utxo
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"], "from": 1, "to": 5}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/utxo
*
*/
// Retrieve UTXO information for an address.
async function utxoBulk(req, res, next) {
try {
let addresses = req.body.addresses
// Reject if address is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({ error: "addresses needs to be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
bchjs.Address.toLegacyAddress(thisAddress)
} catch (er) {
//if (er.message.includes("Unsupported address format"))
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
wlogger.debug(
`Executing address/utxoBulk with these addresses: `,
addresses
)
// Loops through each address and creates an array of Promises, querying
// Insight API in parallel.
addresses = addresses.map(async (address, index) =>
utxoFromInsight(address)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/utxoBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler for single address details
/**
* @api {get} /insight/address/utxo/{address} Get Address utxos single.
* @apiName Address utxos single.
* @apiGroup Address
* @apiDescription Returns the list of utxo for an address.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/address/utxo/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
*
*
*/
async function utxoSingle(req, res, next) {
try {
const address = req.params.address
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = 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.`
})
}
// Query the Insight API.
const retData = await utxoFromInsight(address)
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/utxoSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/**
* @api {post} /address/utxo Get Unconfirmed transactions bulk.
* @apiName Unconfirmed transactions bulk
* @apiGroup Address
* @apiDescription Returns the list of unconfirmed transactions for an array of addresses.
*
*
* @apiExample Example usage:
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"]}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/unconfirmed
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"], "from": 1, "to": 5}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/unconfirmed
*
*/
// Retrieve any unconfirmed TX information for a given address.
async function unconfirmedBulk(req, res, next) {
try {
const addresses = req.body.addresses
// Reject if address is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({ error: "addresses needs to be an array" })
}
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
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 = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Collect an array of promises.
const promises = addresses.map(address => utxoFromInsight(address))
// Wait for all parallel Insight requests to return.
const result = await axios.all(promises)
// Loop through each result
const finalResult = result.map(elem => {
//console.log(`elem: ${util.inspect(elem)}`)
// Filter out confirmed transactions.
const unconfirmedUtxos = elem.utxos.filter(
utxo => utxo.confirmations === 0
)
elem.utxos = unconfirmedUtxos
return elem
})
// Return the array of retrieved address information.
res.status(200)
return res.json(finalResult)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/unconfirmedBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/**
* @api {get} /insight/address/utxo/{address} Get Unconfirmed transactions single.
* @apiName Unconfirmed transactions single.
* @apiGroup Address
* @apiDescription Returns the list of unconfirmed transactions for an address.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/address/unconfirmed/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
*
*
*/
// GET handler. Retrieve any unconfirmed TX information for a given address.
async function unconfirmedSingle(req, res, next) {
try {
const address = req.params.address
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = 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.`
})
}
// Query the Insight API.
const retData = await utxoFromInsight(address)
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// Loop through each returned UTXO.
const unconfirmedUTXOs = []
for (let j = 0; j < retData.utxos.length; j++) {
const thisUtxo = retData.utxos[j]
// Only interested in UTXOs with no confirmations.
if (thisUtxo.confirmations === 0) unconfirmedUTXOs.push(thisUtxo)
}
retData.utxos = unconfirmedUTXOs
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/unconfirmedSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// Retrieve transaction data from the Insight API
async function transactionsFromInsight(thisAddress, currentPage = 0) {
try {
const path = `${process.env.BITCOINCOM_BASEURL}txs/?address=${thisAddress}&pageNum=${currentPage}`
// Query the Insight server.
const response = await axios.get(path)
// Append different address formats to the return data.
const retData = response.data
retData.legacyAddress = bchjs.Address.toLegacyAddress(thisAddress)
retData.cashAddress = bchjs.Address.toCashAddress(thisAddress)
retData.currentPage = currentPage
return retData
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
/**
* @api {post} /address/transactions Get Transactions bulk.
* @apiName Transactions bulk.
* @apiGroup Address
* @apiDescription Returns the list of transactions for an array of addresses.
*
*
* @apiExample Example usage:
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"]}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/transactions
* curl -d '{"addresses": ["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c"], "from": 1, "to": 5}' -H "Content-Type: application/json" https://mainnet.bchjs.cash/v3/insight/address/transactions
*
*/
// Get an array of TX information for a given address.
async function transactionsBulk(req, res, next) {
try {
let addresses = req.body.addresses
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
// Reject if address is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({ error: "addresses needs to be an array" })
}
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
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 = routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Loop through each address and collect an array of promises.
addresses = addresses.map(async (address, index) =>
transactionsFromInsight(address, currentPage)
)
// Wait for all parallel Insight requests to return.
const result = await axios.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/transactionsBulk().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/**
* @api {get} /insight/address/transactions/{address} Get Transactions single.
* @apiName Transactions single.
* @apiGroup Address
* @apiDescription Returns the list of transactions for an address.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/address/transactions/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
*
*
*/
// GET handler. Retrieve any unconfirmed TX information for a given address.
async function transactionsSingle(req, res, next) {
try {
const address = req.params.address
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
if (!address || address === "") {
res.status(400)
return res.json({ error: "address can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
error: "address can not be an array. Use POST for bulk upload."
})
}
wlogger.debug(
`Executing address/transactionsSingle with this address: `,
address
)
// Ensure the input is a valid BCH address.
try {
var legacyAddr = 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.`
})
}
// Query the Insight API.
const retData = await transactionsFromInsight(address, currentPage)
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/transactionsSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/**
* @api {get} /insight/address/fromXPub/{xpub} Get FromXPub
* @apiName FromXPub
* @apiGroup Address
* @apiDescription Returns an address for an extended public key and hdpath.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/address/fromXPub/xpub661MyMwAqRbcG4CnhNYoK1r1TKLwQQ1UdC3LHoWFK61rsnzh7Hx35qQ9Z53ucYcE5WvA7GEDXhqqKjSY2e6Y8n7WNVLYHpXCuuX945VPuYn" -H "accept: application/json"
*
*
*/
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.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in address.ts/fromXPubSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
testableComponents: {
root,
detailsBulk,
detailsSingle,
utxoBulk,
utxoSingle,
unconfirmedBulk,
unconfirmedSingle,
transactionsBulk,
transactionsSingle,
fromXPubSingle
}
}
-324
View File
@@ -1,324 +0,0 @@
"use strict"
const express = require("express")
const requestUtils = require("../services/requestUtils")
const axios = require("axios")
const bitbox = require("../services/bitbox")
const wlogger = require("../../../util/winston-logging")
const routeUtils = require("../route-utils")
// Used for processing error messages before sending them to the user.
const util = require("util")
util.inspect.defaultOptions = { depth: 3 }
const router = express.Router()
//const BitboxHTTP = bitbox.getInstance()
router.get("/", root)
router.get("/detailsByHash/:hash", detailsByHashSingle)
router.post("/detailsByHash", detailsByHashBulk)
router.get("/detailsByHeight/:height", detailsByHeightSingle)
router.post("/detailsByHeight", detailsByHeightBulk)
function root(req, res, next) {
return res.json({ status: "block" })
}
/**
* @api {get} /block/detailsByHash/{hash} Get Block details single.
* @apiName Block details single
* @apiGroup Block
* @apiDescription Returns details about a single block by hash.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/block/detailsByHash/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" -H "accept: application/json"
*
*
*/
// Call the insight server to get block details based on the hash.
async function detailsByHashSingle(req, res, next) {
try {
const hash = req.params.hash
// Reject if hash is empty
if (!hash || hash === "") {
res.status(400)
return res.json({ error: "hash must not be empty" })
}
const response = await axios.get(
`${process.env.BITCOINCOM_BASEURL}block/${hash}`
)
//console.log(`response.data: ${JSON.stringify(response.data,null,2)}`)
const parsed = response.data
return res.json(parsed)
} catch (error) {
//console.log(`error object: ${util.inspect(error)}`)
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(error)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
if (error.response && error.response.status === 404) {
res.status(404)
return res.json({ error: "Not Found" })
}
// Write out error to error log.
//logger.error(`Error in block/detailsByHash: `, error)
wlogger.error(`Error in block.ts/detailsByHashSingle().`, error)
res.status(500)
return res.json({ error: util.inspect(error) })
}
}
/**
* @api {post} /block/detailsByHash Get Block details bulk.
* @apiName Block details bulk
* @apiGroup Block
* @apiDescription Returns retails about multiple blocks by hashes.
*
*
* @apiExample Example usage:
* curl -d '{"hashes":["0000000000000000040e83398a79a16390897f0d18c92bada6350a19a32ec984","000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201"]}' -H "Content-Type: application/json" "https://mainnet.bchjs.cash/v3/insight/block/detailsByHash"
* curl -d '{"hashes":["0000000000000000040e83398a79a16390897f0d18c92bada6350a19a32ec984","000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201"], "from": 1, "to": 5}' -H "Content-Type: application/json" "https://mainnet.bchjs.cash/v3/insight/block/detailsByHash"
*
*/
async function detailsByHashBulk(req, res, next) {
try {
const hashes = req.body.hashes
//console.log(`hashes: ${JSON.stringify(hashes, null, 2)}`)
// Reject if hashes is not an array.
if (!Array.isArray(hashes)) {
res.status(400)
return res.json({
error: "hashes needs to be an array. Use GET for single address."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, hashes)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
// Validate each hash in the array.
for (let i = 0; i < hashes.length; i++) {
const thisHash = hashes[i]
if (thisHash.length !== 64) {
res.status(400)
return res.json({
error: `Invalid hash. Double check your hash is valid: ${thisHash}`
})
}
}
// Loop through each hash and creates an array of promises
const axiosPromises = hashes.map(async hash =>
axios.get(`${process.env.BITCOINCOM_BASEURL}block/${hash}`)
)
// Wait for all parallel promises to return.
const axiosResult = await axios.all(axiosPromises)
// Extract the data component from the axios response.
const result = axiosResult.map(x => x.data)
//console.log(`result: ${util.inspect(result)}`)
res.status(200)
return res.json(result)
} catch (error) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(error)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
if (error.response && error.response.status === 404) {
res.status(404)
return res.json({ error: "Not Found" })
}
// Write out error to error log.
//logger.error(`Error in block/detailsByHash: `, error)
wlogger.error(`Error in block.ts/detailsByHashBulk().`, error)
res.status(500)
return res.json({ error: util.inspect(error) })
}
}
/**
* @api {get} /block/detailsByHeight/{hash} Get Block details single by height.
* @apiName Block details single by height
* @apiGroup Block
* @apiDescription Returns Details about a single block by height.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/block/block/detailsByHeight/500000" -H "accept: application/json"
*
*
*/
// Call the Full Node to get block hash based on height, then call the Insight
// server to get details from that hash.
async function detailsByHeightSingle(req, res, next) {
try {
const height = req.params.height
// Reject if id is empty
if (!height || height === "") {
res.status(400)
return res.json({ error: "height must not be empty" })
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
requestConfig.data.id = "getblockhash"
requestConfig.data.method = "getblockhash"
requestConfig.data.params = [parseInt(height)]
const response = await BitboxHTTP(requestConfig)
const hash = response.data.result
//console.log(`response.data: ${util.inspect(response.data)}`)
// Call detailsByHashSingle now that the hash has been retrieved.
req.params.hash = hash
return detailsByHashSingle(req, res, next)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in control/getInfo: `, error)
wlogger.error(`Error in block.ts/detailsByHeightSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
/**
* @api {post} /block/detailsByHash Get Block details bulk by height.
* @apiName Block details bulk by height
* @apiGroup Block
* @apiDescription Returns Bulk details about blocks by heights.
*
*
* @apiExample Example usage:
* curl -d '{"heights":[499000,500000]}' -H "Content-Type: application/json" "https://mainnet.bchjs.cash/v3/insight/block/detailsByHeight"
* curl -d '{"heights":[499000,500000], "from": 1, "to": 5}' -H "Content-Type: application/json" "https://mainnet.bchjs.cash/v3/insight/block/detailsByHeight"
*
*/
async function detailsByHeightBulk(req, res, next) {
try {
const heights = req.body.heights
// Reject if heights is not an array.
if (!Array.isArray(heights)) {
res.status(400)
return res.json({
error: "heights needs to be an array. Use GET for single height."
})
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, heights)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
wlogger.debug(`Executing detailsByHeight with these heights: `, heights)
// Validate each element in the address array.
for (let i = 0; i < heights.length; i++) {
const thisHeight = heights[i]
// Reject if id is empty
if (!thisHeight || thisHeight === "") {
res.status(400)
return res.json({ error: "height must not be empty" })
}
}
const {
BitboxHTTP,
username,
password,
requestConfig
} = routeUtils.setEnvVars()
// Loop through each height and creates an array of requests to call in parallel
const promises = heights.map(async height => {
requestConfig.data.id = "getblockhash"
requestConfig.data.method = "getblockhash"
requestConfig.data.params = [parseInt(height)]
const response = await BitboxHTTP(requestConfig)
const hash = response.data.result
const axiosResult = await axios.get(
`${process.env.BITCOINCOM_BASEURL}block/${hash}`
)
return axiosResult.data
})
// Wait for all parallel Insight requests to return.
const result = await axios.all(promises)
res.status(200)
return res.json(result)
} catch (error) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(error)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
if (error.response && error.response.status === 404) {
res.status(404)
return res.json({ error: "Not Found" })
}
// Write out error to error log.
//logger.error(`Error in block/detailsByHash: `, error)
wlogger.error(`Error in block.ts/detailsByHeightBulk().`, error)
res.status(500)
return res.json({ error: util.inspect(error) })
}
}
module.exports = {
router,
testableComponents: {
root,
detailsByHashSingle,
detailsByHashBulk,
detailsByHeightSingle,
detailsByHeightBulk
}
}
-38
View File
@@ -1,38 +0,0 @@
/*
Insight API route
A parent route that encompases the other libraries.
*/
"use strict"
const express = require("express")
const requestUtils = require("../services/requestUtils")
const axios = require("axios")
const routeUtils = require("../route-utils")
const wlogger = require("../../../util/winston-logging")
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("@chris.troutner/bch-js")
//const bchjs = new BCHJS()
//const BITCORE_URL = process.env.BITCORE_URL
// Connect the route endpoints to their handler functions.
router.get("/", root)
// Root API endpoint. Simply acknowledges that it exists.
function root(req, res, next) {
return res.json({ status: "address" })
}
module.exports = {
router,
testableComponents: {
root
}
}
-210
View File
@@ -1,210 +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 BCHJS = require("@chris.troutner/bch-js")
const bchjs = new BCHJS()
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 3 }
// Manipulates and formats the raw data comming from Insight API.
const processInputs = tx => {
// Add legacy and cashaddr to tx vin
if (tx.vin) {
tx.vin.forEach(vin => {
if (!vin.coinbase) {
vin.value = vin.valueSat
const address = vin.addr
if (address) {
vin.legacyAddress = bchjs.Address.toLegacyAddress(address)
vin.cashAddress = bchjs.Address.toCashAddress(address)
delete vin.addr
}
delete vin.valueSat
delete vin.doubleSpentTxID
}
})
}
// Add legacy and cashaddr to tx vout
if (tx.vout) {
tx.vout.forEach(vout => {
// Overwrite value string with value in satoshis
//vout.value = parseFloat(vout.value) * 100000000
if (vout.scriptPubKey) {
if (vout.scriptPubKey.addresses) {
const cashAddrs = []
vout.scriptPubKey.addresses.forEach(addr => {
const cashAddr = bchjs.Address.toCashAddress(addr)
cashAddrs.push(cashAddr)
})
vout.scriptPubKey.cashAddrs = cashAddrs
}
}
})
}
}
router.get("/", root)
router.post("/details", detailsBulk)
router.get("/details/:txid", detailsSingle)
function root(req, res, next) {
return res.json({ status: "transaction" })
}
// Retrieve transaction data from the Insight API
// This function is also used by the SLP route library.
async function transactionsFromInsight(txid) {
try {
const path = `${process.env.BITCOINCOM_BASEURL}tx/${txid}`
// Query the Insight server.
const response = await axios.get(path)
//console.log(`Insight output: ${JSON.stringify(response.data, null, 2)}`)
// Parse the data.
const parsed = response.data
if (parsed) processInputs(parsed)
return parsed
} catch (err) {
// Dev Note: Do not log error messages here. Throw them instead and let the
// parent function handle it.
throw err
}
}
/**
* @api {post} /transaction/details Get Transaction details bulk.
* @apiName Transaction details bulk
* @apiGroup Transaction
* @apiDescription Returns Bulk Details about a transaction.
*
*
* @apiExample Example usage:
* curl -X POST "https://mainnet.bchjs.cash/v3/insight/transaction/details" -H "Content-Type: application/json" -d "{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"]}"
*
*/
async function detailsBulk(req, res, next) {
try {
const txids = req.body.txids
// Reject if address is not an array.
if (!Array.isArray(txids)) {
res.status(400)
return res.json({ error: "txids needs to be an array" })
}
// Enforce array size rate limits
if (!routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: `Array too large.`
})
}
wlogger.debug(`Executing transaction/details with these txids: `, txids)
// Collect an array of promises
const promises = txids.map(
async txid => await transactionsFromInsight(txid)
)
// Wait for all parallel promises to return.
const result = await Promise.all(promises)
// Return the array of retrieved transaction information.
res.status(200)
return res.json(result)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
wlogger.error(`Error in transactions.ts/detailsBulk().`, err)
//console.log(`Error in transaction details: `, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
// GET handler. Retrieve any unconfirmed TX information for a given address.
/**
* @api {get} insight/transaction/details/{txid} Get Transaction details single.
* @apiName Transaction details single
* @apiGroup Transaction
* @apiDescription Returns the details about a single transaction.
*
*
* @apiExample Example usage:
* curl -X GET "https://mainnet.bchjs.cash/v3/insight/transaction/details/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
*
*
*/
async function detailsSingle(req, res, next) {
try {
const txid = req.params.txid
if (!txid || txid === "") {
res.status(400)
return res.json({ error: "txid can not be empty" })
}
// Reject if address is an array.
if (Array.isArray(txid)) {
res.status(400)
return res.json({
error: "txid can not be an array. Use POST for bulk upload."
})
}
wlogger.debug(
`Executing transaction.ts/detailsSingle with this txid: `,
txid
)
// Query the Insight API.
const retData = await transactionsFromInsight(txid)
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// Return the array of retrieved address information.
res.status(200)
return res.json(retData)
} catch (err) {
// Attempt to decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
// Write out error to error log.
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
wlogger.error(`Error in transactions.ts/detailsSingle().`, err)
res.status(500)
return res.json({ error: util.inspect(err) })
}
}
module.exports = {
router,
transactionsFromInsight,
testableComponents: {
root,
detailsBulk,
detailsSingle
}
}
-168
View File
@@ -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
+10 -1
View File
@@ -26,7 +26,7 @@ module.exports = {
// or not.
function validateArraySize(req, array) {
const FREEMIUM_INPUT_SIZE = 20
const PRO_INPUT_SIZE = 100
const PRO_INPUT_SIZE = 20
if (req.locals && req.locals.proLimit) {
if (array.length <= PRO_INPUT_SIZE) return true
@@ -131,6 +131,15 @@ function decodeError(err) {
}
}
// 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
}
}
return { msg: false, status: 500 }
} catch (err) {
wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err)
-11
View File
@@ -1,11 +0,0 @@
"use strict"
const axios = require("axios")
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
})
const getInstance = () => BitboxHTTP
module.exports = getInstance
-20
View File
@@ -1,20 +0,0 @@
"use strict"
const username = process.env.RPC_USERNAME
const password = process.env.RPC_PASSWORD
const getRequestConfig = (method, params) => ({
method: "post",
auth: {
username,
password
},
data: {
jsonrpc: "1.0",
id: method,
method,
params
}
})
module.exports = getRequestConfig
+15 -22
View File
@@ -9,22 +9,13 @@ const routeUtils = require("./route-utils")
const strftime = require("strftime")
const wlogger = require("../../util/winston-logging")
const BCHJS = require("@chris.troutner/bch-js")
const bchjs = new BCHJS()
// Used to convert error messages to strings, to safely pass to users.
const util = require("util")
util.inspect.defaultOptions = { depth: 5 }
const SLPSDK = require("slp-sdk")
const SLP = new SLPSDK()
// Instantiate SLPJS.
const slp = require("slpjs")
const slpjs = new slp.Slp(SLP)
const utils = slp.Utils
// SLP tx db (LevelDB for caching)
// const level = require("level")
// const slpTxDb = level("./slp-tx-db")
// Setup JSON RPC
const BitboxHTTP = axios.create({
baseURL: process.env.RPC_BASEURL
@@ -36,7 +27,8 @@ const password = process.env.RPC_PASSWORD
// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2
const SLPDB_PASS = process.env.SLPDB_PASS ? process.env.SLPDB_PASS : "BITBOX"
const transactions = require("./insight/transaction")
// const transactions = require("./insight/transaction")
const rawtransactions = require("./full-node/rawtransactions")
// Setup REST and TREST URLs used by slpjs
// Dev note: this allows for unit tests to mock the URL.
@@ -375,7 +367,7 @@ async function balancesForAddress(req, res, next) {
// Ensure the input is a valid BCH address.
try {
const cash = utils.toCashAddress(address)
const cash = bchjs.SLP.Address.toCashAddress(address)
} catch (err) {
res.status(400)
return res.json({
@@ -384,7 +376,7 @@ async function balancesForAddress(req, res, next) {
}
// Prevent a common user error. Ensure they are using the correct network address.
const cashAddr = utils.toCashAddress(address)
const cashAddr = bchjs.SLP.Address.toCashAddress(address)
const networkIsValid = routeUtils.validateNetwork(cashAddr)
if (!networkIsValid) {
res.status(400)
@@ -398,7 +390,7 @@ async function balancesForAddress(req, res, next) {
q: {
db: ["a"],
find: {
address: SLP.Address.toSLPAddress(address),
address: bchjs.SLP.Address.toSLPAddress(address),
token_balance: { $gte: 0 }
},
limit: 10000
@@ -481,7 +473,7 @@ async function balancesForAddress(req, res, next) {
res.status(500)
return res.json({
error: `Error in /ddress/:address: ${err.message}`
error: `Error in /address/:address: ${err.message}`
})
}
}
@@ -533,7 +525,7 @@ async function balancesForAddressBulk(req, res, next) {
// Ensure the input is a valid BCH address.
try {
utils.toCashAddress(address)
bchjs.SLP.Address.toCashAddress(address)
} catch (err) {
res.status(400)
return res.json({
@@ -542,7 +534,7 @@ async function balancesForAddressBulk(req, res, next) {
}
// Prevent a common user error. Ensure they are using the correct network address.
const cashAddr = utils.toCashAddress(address)
const cashAddr = bchjs.SLP.Address.toCashAddress(address)
const networkIsValid = routeUtils.validateNetwork(cashAddr)
if (!networkIsValid) {
res.status(400)
@@ -561,7 +553,7 @@ async function balancesForAddressBulk(req, res, next) {
q: {
db: ["a"],
find: {
address: SLP.Address.toSLPAddress(address),
address: bchjs.SLP.Address.toSLPAddress(address),
token_balance: { $gte: 0 }
},
limit: 10000
@@ -1210,8 +1202,9 @@ async function txDetails(req, res, next) {
// console.log(`formatted: ${JSON.stringify(formatted,null,2)}`)
// Get information on the transaction from Insight API.
const retData = await transactions.transactionsFromInsight(txid)
// console.log(`retData: ${JSON.stringify(retData,null,2)}`)
// const retData = await transactions.transactionsFromInsight(txid)
const retData = await 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 = {
-1
View File
@@ -5,7 +5,6 @@
"use strict"
const express = require("express")
const requestUtils = require("./services/requestUtils")
const axios = require("axios")
const routeUtils = require("./route-utils")
const wlogger = require("../../util/winston-logging")
+3 -9
View File
@@ -1,21 +1,15 @@
#!/bin/bash
# Harddrive Mainnet full node
# Full node
export RPC_BASEURL=http://<full node ip>:8332/
export RPC_USERNAME=<RPC username>
export RPC_PASSWORD=<RPC password>
export NETWORK=mainnet
# Team DO QA Testnet Insight Server
export BITCOINCOM_BASEURL=http://<Insight API IP>:12100/api/
# Testnet SLPDB
# SLPDB
export SLPDB_URL=http://<SLPDB IP>:12300/
# Testnet Bitcore Node API
export BITCORE_URL=http://<Bitcore Node IP>:12200/
# Testnet Blockbook
# Blockbook
export BLOCKBOOK_URL=https://<Blockbook IP>:9131/
# Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
-1373
View File
File diff suppressed because it is too large Load Diff
-610
View File
@@ -1,610 +0,0 @@
"use strict"
const blockRoute = require("../../src/routes/v2/block")
const chai = require("chai")
const assert = chai.assert
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/block-mock")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Block", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
// Activate nock if it's inactive.
if (!nock.isActive()) nock.activate()
})
afterEach(() => {
// Clean up HTTP mocks.
nock.cleanAll() // clear interceptor list.
nock.restore()
})
after(() => {
// Restore any pre-existing environment variables.
process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL
process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL
process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME
process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD
})
describe("#root", () => {
// root route handler.
const root = blockRoute.testableComponents.root
it("should respond to GET for base route", async () => {
const result = root(req, res)
assert.equal(result.status, "block", "Returns static string")
})
})
describe("#detailsByHashSingle", () => {
const detailsByHash = blockRoute.testableComponents.detailsByHashSingle
it("should throw an error for an empty hash", async () => {
req.params.hash = ""
const result = await detailsByHash(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hash must not be empty",
"Proper error message"
)
})
it("should throw 50X when network issues", async () => {
// Save the existing RPC URL.
const savedUrl = process.env.BITCOINCOM_BASEURL
// Manipulate the URL to cause a 500 network error.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
req.params.hash = "abc123"
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.isAbove(res.statusCode, 499, "HTTP status code 50X expected.")
//assert.include(result.error, "ENOTFOUND", "Error message expected")
})
it("should throw an error for invalid hash", async () => {
req.params.hash = "abc123"
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.params.hash}`)
.reply(404, "Not found")
}
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.include(result.error, "Not found", "Proper error message")
})
it("should GET /detailsByHash/:hash", async () => {
req.params.hash =
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.params.hash}`)
.reply(200, mockData.mockBlockDetails)
}
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"hash",
"size",
"height",
"version",
"merkleroot",
"tx",
"time",
"nonce",
"bits",
"difficulty",
"chainwork",
"confirmations",
"previousblockhash",
"nextblockhash",
"reward",
"isMainChain",
"poolInfo"
])
assert.isArray(result.tx)
})
})
describe("#detailsByHashBulk", () => {
// details route handler.
const detailsByHashBulk = blockRoute.testableComponents.detailsByHashBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsByHashBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hashes needs to be an array",
"Proper error message"
)
})
it("should error on non-array single address", async () => {
req.body = {
hashes:
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
}
const result = await detailsByHashBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hashes needs to be an array",
"Proper error message"
)
})
it("should throw 400 error if addresses array is too large", async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push("")
req.body.hashes = testArray
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Array too large")
})
it("should throw an error for an invalid hash", async () => {
req.body = {
hashes: [`abc123`]
}
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(result.error, "Invalid hash", "Proper error message")
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.body = {
hashes: [
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
]
}
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsByHashBulk(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single hash", async () => {
req.body = {
hashes: [
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.equal(result.length, 1, "Array with one entry")
assert.hasAllKeys(result[0], [
"bits",
"chainwork",
"confirmations",
"difficulty",
"hash",
"height",
"isMainChain",
"merkleroot",
"nextblockhash",
"nonce",
"poolInfo",
"previousblockhash",
"reward",
"size",
"time",
"tx",
"version"
])
})
it("should get details for multiple hashes", async () => {
req.body = {
hashes: [
`00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79`,
`00000000c2b2c19cf499f57d5b0f724c6df753330d7acc7d4a8ebe412d427bd0`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
.reply(200, mockData.mockBlockDetails)
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[1]}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
it("should throw an error if hash not found", async () => {
req.body = {
hashes: [
`00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44abcdef`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
//.reply(404, { error: { message: "Not Found" } })
.reply(404, "Not found")
}
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 404, "HTTP status code 404 expected.")
assert.include(result.error, "Not found", "Proper error message")
})
})
describe("Block Details By Height", () => {
// block route handler.
const detailsByHeight = blockRoute.testableComponents.detailsByHeightSingle
it("should throw an error for an empty height", async () => {
req.params.height = ""
const result = await detailsByHeight(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"height must not be empty",
"Proper error message"
)
})
it("should throw 500 when network issues", async () => {
// Save the existing RPC URL.
const savedUrl = process.env.BITCOINCOM_BASEURL
const savedUrl2 = process.env.RPC_BASEURL
// Manipulate the URL to cause a 500 network error.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api/"
req.params.height = "abc123"
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
process.env.RPC_BASEURL = savedUrl2
assert.isAbove(
res.statusCode,
499,
"HTTP status code 500 or great expected."
)
//assert.include(result.error, "ENOTFOUND", "Error message expected")
})
it("should throw an error for invalid height", async () => {
req.params.height = "abc123"
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: {
code: -1,
message: "JSON value is not an integer as expected"
}
})
}
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"JSON value is not an integer as expected",
"Proper error message"
)
})
it("should GET /detailsByHeight/:height", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockBlockHash })
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(
`/block/00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79`
)
.reply(200, mockData.mockBlockDetails)
}
req.params.height = 500000
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"hash",
"size",
"height",
"version",
"merkleroot",
"tx",
"time",
"nonce",
"bits",
"difficulty",
"chainwork",
"confirmations",
"previousblockhash",
"nextblockhash",
"reward",
"isMainChain",
"poolInfo"
])
assert.isArray(result.tx)
})
})
describe("#detailsByHeightBulk", () => {
// details route handler.
const detailsByHeightBulk =
blockRoute.testableComponents.detailsByHeightBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsByHeightBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"heights needs to be an array",
"Proper error message"
)
})
it("should error on non-array single height", async () => {
req.body = {
heights: 500000
}
const result = await detailsByHeightBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"heights needs to be an array",
"Proper error message"
)
})
it("should throw 400 error if addresses array is too large", async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push("")
req.body.heights = testArray
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Array too large")
})
it("should throw error for an invalid height", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: {
code: -1,
message: "JSON value is not an integer as expected"
}
})
}
req.body.heights = [`abc123`]
const result = await detailsByHeightBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.body.heights = [`500000`]
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsByHeightBulk(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single height", async () => {
req.body.heights = [`500000`]
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockBlockHash })
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${mockData.mockBlockHash}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.equal(result.length, 1, "Array with one entry")
assert.hasAllKeys(result[0], [
"bits",
"chainwork",
"confirmations",
"difficulty",
"hash",
"height",
"isMainChain",
"merkleroot",
"nextblockhash",
"nonce",
"poolInfo",
"previousblockhash",
"reward",
"size",
"time",
"tx",
"version"
])
})
it("should get details for multiple block heights", async () => {
req.body = {
heights: [`500000`, `500001`]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.times(2)
.reply(200, { result: mockData.mockBlockHash })
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${mockData.mockBlockHash}`)
.times(2)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
})
})
File diff suppressed because it is too large Load Diff
-140
View File
@@ -1,140 +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/v2/control")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/control-mock")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#ControlRouter", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
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(() => {
// 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 = root(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(result.status, "control", "Returns static string")
})
})
describe("#GetInfo", () => {
const getInfo = controlRoute.testableComponents.getInfo
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/"
const result = await getInfo(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("should get info on the full node", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockGetInfo })
}
const result = await getInfo(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"version",
"protocolversion",
"blocks",
"timeoffset",
"connections",
"proxy",
"difficulty",
"testnet",
"paytxfee",
"relayfee",
"errors"
])
})
})
})
-60
View File
@@ -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
}
const result = 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]
}
}
const result = await rp(options)
// console.log(`result.body: ${JSON.stringify(result.body, null, 2)}`)
} catch (err) {
// console.log(`Error: `, err)
}
}
testDoubleUnconfirmed()
-29
View File
@@ -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
}
-7
View File
@@ -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.
-94
View File
@@ -1,94 +0,0 @@
/*
*/
"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 = `https://rest.btctest.net/v2/`
//const SERVER = `http://localhost:3000/v2/`
describe("#rate limits", () => {
it("should get control/getInfo() with no auth", async () => {
const options = {
method: "GET",
url: `${SERVER}control/getInfo`
}
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 60 request per minute", async () => {
try {
// Actual rate limit is 60 per minute X 4 nodes = 240 rpm.
const options = {
method: "GET",
url: `${SERVER}control/getInfo`
}
const promises = []
for (let i = 0; i < 250; 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 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/getInfo`,
headers: { Authorization: readyCredential }
}
const promises = []
for (let i = 0; i < 250; 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?"
)
}
})
})
-191
View File
@@ -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)
})
})
})
-176
View File
@@ -1,176 +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/v2/mining")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/mining-mocks")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Mining", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
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(() => {
// 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 = miningRoute.testableComponents.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, "mining", "Returns static string")
})
})
describe("#getMiningInfo", async () => {
const getMiningInfo = miningRoute.testableComponents.getMiningInfo
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 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("should GET mining information", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockMiningInfo })
}
const result = await getMiningInfo(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, [
"blocks",
"currentblocksize",
"currentblocktx",
"difficulty",
"blockprioritypercentage",
"errors",
"networkhashps",
"pooledtx",
"chain"
])
})
})
describe("#getNetworkHashPS", async () => {
const getNetworkHashPS = miningRoute.testableComponents.getNetworkHashPS
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 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("should GET Network Hash per second", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: 517604755.6648782 })
}
const result = await getNetworkHashPS(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result)
})
})
})
-160
View File
@@ -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
}
-42
View File
@@ -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
}
-271
View File
@@ -1,271 +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 =
"0000ff7f7d217c9b7845ea8b50d620c59a1bf7c276566406e9b7bc7e463e0000000000006d70322c0b697c1c81d2744f87f09f1e9780ba5d30338952e2cdc64e60456f8423bb0a5ceafa091a3e843526"
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"
}
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"
module.exports = {
mockBlockHash,
mockBlockchainInfo,
mockChainTips,
mockMempoolInfo,
mockRawMempool,
mockBlockHeaderConcise,
mockBlockHeader,
mockTxOut,
mockTxOutProof
}
-27
View File
@@ -1,27 +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)"
}
module.exports = {
mockGetInfo
}
-88
View File
@@ -1,88 +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({}),
format: {},
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({})
}
const mockNext = sinon.stub().returns()
module.exports = {
mockReq,
mockRes,
mockNext
}
-22
View File
@@ -1,22 +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,
blockprioritypercentage: 5,
errors:
"Warning: Unknown block versions being mined! It's possible unknown rules are in effect",
networkhashps: 517410290.9365583,
pooledtx: 5,
chain: "test"
}
module.exports = {
mockMiningInfo
}
-157
View File
@@ -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
}
-327
View File
@@ -1,327 +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: 8,
tokenIdHex:
"650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a",
timestamp: "2018-08-26 07:06",
transactionType: "GENESIS",
versionType: 1,
documentUri: "",
documentSha256Hex: null,
symbol: "",
name: "TESTYCOIN",
batonVout: null,
containsBaton: false,
genesisOrMintQuantity: "9999",
sendOutputs: null
},
tokenStats: {
block_created: 1253802,
block_last_active_send: 1253802,
block_last_active_mint: null,
qty_valid_txns_since_genesis: 2,
qty_valid_token_utxos: 0,
qty_valid_token_addresses: 0,
qty_token_minted: "9999",
qty_token_burned: "9999",
qty_token_circulating_supply: "0",
qty_satoshis_locked_up: 0,
minting_baton_status: "NEVER_CREATED"
}
}
]
}
const mockSingleTokenError = {
t: []
}
const mockSingleAddress = {
a: [
{
_id: "5c93ed62a19119333d1595bc",
tokenDetails: {
tokenIdHex:
"6b081fcd1f78b187be1464313dac8ff257251b727a42b613552a4040870aeb29"
},
address: "slptest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqv7sq3kk7",
satoshis_balance: 546,
token_balance: "4616984"
}
],
t: [
{
tokenDetails: {
decimals: 8,
tokenIdHex:
"6b081fcd1f78b187be1464313dac8ff257251b727a42b613552a4040870aeb29",
timestamp: "2019-02-25 10:05",
transactionType: "GENESIS",
versionType: 1,
documentUri: "https://developer.bitcoin.com",
documentSha256Hex: "",
symbol: "DEV",
name: "DEVCOIN",
batonVout: 2,
containsBaton: true,
genesisOrMintQuantity: "500000000",
sendOutputs: null
},
tokenStats: {
block_created: 571272,
block_last_active_send: 574758,
block_last_active_mint: 571272,
qty_valid_txns_since_genesis: 172,
qty_valid_token_utxos: 170,
qty_valid_token_addresses: 170,
qty_token_minted: "1000000000",
qty_token_burned: "4.99999995",
qty_token_circulating_supply: "999999995.00000005",
qty_satoshis_locked_up: 92820,
minting_baton_status: "ALIVE"
}
}
]
}
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 = {
slpAddress: "simpleledger:qp9d8mn8ypryfvea2mev0ggc3wg6plpn4suuaeuss3",
satoshis_balance: 546,
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: []
}
module.exports = {
mockList,
mockSingleToken,
mockConvert,
mockTokenDetails,
mockTokenStats,
mockTx,
mockBalance,
mockTransactions,
mockSingleTokenError,
mockSingleAddress,
mockFoobar
}
-71
View File
@@ -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
-74
View File
@@ -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
}
-18
View File
@@ -1,18 +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
}
module.exports = {
mockAddress
}
-166
View File
@@ -1,166 +0,0 @@
"use strict"
const chai = require("chai")
const assert = chai.assert
const nock = require("nock") // HTTP mocking
// 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 rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
const controlRoute = require("../../src/routes/v2/control")
let req, res, next
let originalEnvVars // Used during transition from integration to unit tests.
describe("#route-ratelimits", () => {
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
}
})
// 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 = {}
})
describe("#routeRateLimit", () => {
const routeRateLimit = rateLimitMiddleware.routeRateLimit
const getInfo = controlRoute.testableComponents.getInfo
/*
it("should pass through rate-limit middleware", async () => {
req.baseUrl = "/v2"
req.path = "/control/getInfo"
req.method = "GET"
await routeRateLimit(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 60 request per minute", async () => {
req.baseUrl = "/v2"
req.path = "/control/getInfo"
req.method = "GET"
for (let i = 0; i < 65; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(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 pro-tier at 65 RPM", async () => {
// Clear the require cache before running this test.
delete require.cache[
require.resolve("../../dist/middleware/route-ratelimit")
]
rateLimitMiddleware = require("../../dist/middleware/route-ratelimit")
routeRateLimit = rateLimitMiddleware.routeRateLimit
req.baseUrl = "/v2"
req.path = "/control/getInfo"
req.method = "GET"
req.locals.proLimit = true
//console.log(`req.locals before test: ${util.inspect(req.locals)}`)
// Prepare the authorization header
//req.headers.authorization = generateAuthHeader("BITBOX")
for (let i = 0; i < 65; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(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("rate-limiting should still kick in at a higher RPM for pro-tier", async () => {
// Clear the require cache before running this test.
delete require.cache[
require.resolve("../../dist/middleware/route-ratelimit")
]
rateLimitMiddleware = require("../../dist/middleware/route-ratelimit")
routeRateLimit = rateLimitMiddleware.routeRateLimit
req.baseUrl = "/v2"
req.path = "/control/getInfo"
req.method = "GET"
req.locals.proLimit = true
//console.log(`req.locals before test: ${util.inspect(req.locals)}`)
// Prepare the authorization header
//req.headers.authorization = generateAuthHeader("BITBOX")
for (let i = 0; i < 650; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(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.`
)
})
*/
})
})
// 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
}
-807
View File
@@ -1,807 +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/v2/rawtransactions")
const nock = require("nock") // HTTP mocking
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 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
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 = {}
// Activate nock if it's inactive.
if (!nock.isActive()) nock.activate()
})
afterEach(() => {
// Clean up HTTP mocks.
nock.cleanAll() // clear interceptor list.
nock.restore()
})
after(() => {
// Restore any pre-existing environment variables.
process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL
process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL
process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME
process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD
})
describe("#root", async () => {
// root route handler.
const root = rawtransactions.testableComponents.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, "rawtransactions", "Returns static string")
})
})
describe("decodeRawTransactionSingle()", () => {
// block route handler.
const decodeRawTransaction =
rawtransactions.testableComponents.decodeRawTransactionSingle
it("should throw error if hex is missing", async () => {
const result = await decodeRawTransaction(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"
const result = await decodeRawTransaction(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 GET /decodeRawTransaction", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockDecodeRawTransaction })
}
req.params.hex =
"0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000"
const result = await decodeRawTransaction(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()", () => {
const decodeRawTransactionBulk =
rawtransactions.testableComponents.decodeRawTransactionBulk
it("should throw 400 error if hexes array is missing", async () => {
const result = await 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 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 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 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("should decode an array with a single hex", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockDecodeRawTransaction })
}
req.body.hexes = [
"0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000"
]
const result = await 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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.times(2)
.reply(200, { result: mockData.mockDecodeRawTransaction })
}
req.body.hexes = [
"0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000",
"0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000"
]
const result = await 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()", () => {
// block route handler.
const decodeScriptSingle =
rawtransactions.testableComponents.decodeScriptSingle
it("should throw error if hex is missing", async () => {
const result = await 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"
const result = await 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("should GET /decodeScriptSingle", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockDecodeScript })
}
req.params.hex =
"0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000"
const result = await decodeScriptSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["asm", "type", "p2sh"])
})
})
describe("decodeScriptBulk()", () => {
const decodeScriptBulk = rawtransactions.testableComponents.decodeScriptBulk
it("should throw 400 error if hexes array is missing", async () => {
const result = await 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 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 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 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("should decode an array with a single hex", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockDecodeScript })
}
req.body.hexes = [
"4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16"
]
const result = await 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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.times(2)
.reply(200, { result: mockData.mockDecodeScript })
}
req.body.hexes = [
"4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16",
"4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16"
]
const result = await 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()", () => {
// block route handler.
const getRawTransactionBulk =
rawtransactions.testableComponents.getRawTransactionBulk
it("should throw 400 error if txids array is missing", async () => {
const result = await 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 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 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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: { message: "parameter 1 must be of length 64 (not 6)" }
})
}
req.body.txids = ["abc123"]
const result = await getRawTransactionBulk(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, "parameter 1 must be of length 64 (not 6)")
})
it("should get concise transaction data", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockRawTransactionConcise })
}
req.body.txids = [
"bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971"
]
const result = await 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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockRawTransactionVerbose })
}
req.body.txids = [
"bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971"
]
req.body.verbose = true
const result = await 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()", () => {
// block route handler.
const getRawTransactionSingle =
rawtransactions.testableComponents.getRawTransactionSingle
it("should throw 400 error if txid is missing", async () => {
const result = await 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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: { message: "parameter 1 must be of length 64 (not 6)" }
})
}
req.params.txid = "abc123"
const result = await getRawTransactionSingle(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, "parameter 1 must be of length 64 (not 6)")
})
it("should get concise transaction data", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockRawTransactionConcise })
}
req.params.txid =
"bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971"
const result = await 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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, { result: mockData.mockRawTransactionVerbose })
}
req.params.txid =
"bd320377db7026a3dd5c7ec444596c0ee18fc25c4f34ee944adc03e432ce1971"
req.query.verbose = "true"
const result = await 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()", () => {
const sendRawTransaction =
rawtransactions.testableComponents.sendRawTransactionBulk
it("should throw 400 error if hexs array is missing", async () => {
const result = await sendRawTransaction(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 sendRawTransaction(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 sendRawTransaction(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Encountered empty hex")
})
it("should throw 500 error if hex is invalid", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: { message: "TX decode failed" }
})
}
req.body.hexes = ["abc123"]
const result = await sendRawTransaction(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("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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, {
result:
"aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118"
})
}
req.body.hexes = [
"0200000001189f7cf4303e2e0bcc5af4be323b9b397dd4104ca2de09528eb90a1450b8a999010000006a4730440220212ec2ffce136a30cec1bc86a40b08a2afdeb6f8dbd652d7bcb07b1aad6dfa8c022041f59585273b89d88879a9a531ba3272dc953f48ff57dad955b2dee70e76c0624121030143ffd18f1c4add75c86b2f930d9551d51f7a6bd786314247022b7afc45d231ffffffff0230d39700000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac58c20000000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac00000000"
]
const result = await sendRawTransaction(req, res)
//console.log(`result: ${util.inspect(result)}`)
if (process.env.TEST === "unit") {
assert.isArray(result)
assert.isString(result[0])
// Integration test
} else {
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "transaction already in block chain")
}
})
})
describe("sendRawTransactionSingle()", () => {
// block route handler.
const sendRawTransaction =
rawtransactions.testableComponents.sendRawTransactionSingle
it("should throw an error for an empty hex", async () => {
req.params.hex = ""
const result = await sendRawTransaction(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 sendRawTransaction(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 =
"0200000001189f7cf4303e2e0bcc5af4be323b9b397dd4104ca2de09528eb90a1450b8a999010000006a4730440220212ec2ffce136a30cec1bc86a40b08a2afdeb6f8dbd652d7bcb07b1aad6dfa8c022041f59585273b89d88879a9a531ba3272dc953f48ff57dad955b2dee70e76c0624121030143ffd18f1c4add75c86b2f930d9551d51f7a6bd786314247022b7afc45d231ffffffff0230d39700000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac58c20000000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac00000000"
const result = await sendRawTransaction(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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(500, {
error: { message: "TX decode failed" }
})
}
const result = await sendRawTransaction(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("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") {
nock(`${process.env.RPC_BASEURL}`)
.post(``)
.reply(200, {
result:
"aef8848396e67532b42008b9d75b5a5a3459a6717740f31f0553b74102b4b118"
})
}
req.params.hex =
"0200000001189f7cf4303e2e0bcc5af4be323b9b397dd4104ca2de09528eb90a1450b8a999010000006a4730440220212ec2ffce136a30cec1bc86a40b08a2afdeb6f8dbd652d7bcb07b1aad6dfa8c022041f59585273b89d88879a9a531ba3272dc953f48ff57dad955b2dee70e76c0624121030143ffd18f1c4add75c86b2f930d9551d51f7a6bd786314247022b7afc45d231ffffffff0230d39700000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac58c20000000000001976a914af64a026e06910c59463b000d18c3d125d7e951a88ac00000000"
const result = await sendRawTransaction(req, res)
//console.log(`result: ${util.inspect(result)}`)
if (process.env.TEST === "unit") {
assert.isString(result)
// Integration test
} else {
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "transaction already in block chain")
}
})
})
})
-971
View File
@@ -1,971 +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 nock = require("nock") // HTTP mocking
const sinon = require("sinon")
const proxyquire = require("proxyquire").noPreserveCache()
// Prepare the slpRoute for stubbing dependcies on slpjs.
const slpRoute = require("../../src/routes/v2/slp")
const pathStub = {} // Used to stub methods within slpjs.
const slpRouteStub = proxyquire("../../src/routes/v2/slp", { slpjs: pathStub })
let originalEnvVars // Used during transition from integration to unit tests.
// 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, mockServerUrl
let sandbox
before(() => {
// Save existing environment variables.
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`
}
})
// 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 = {}
// 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.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.testableComponents.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("list()", () => {
// list route handler
const list = slpRoute.testableComponents.list
it("should throw 500 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/"
const result = await list(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 GET list", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
const b64 = `eyJ2IjozLCJxIjp7ImRiIjpbInQiXSwiZmluZCI6eyIkcXVlcnkiOnt9fSwicHJvamVjdCI6eyJ0b2tlbkRldGFpbHMiOjEsInRva2VuU3RhdHMiOjEsIl9pZCI6MH0sImxpbWl0IjoxMDB9fQ==`
nock(process.env.SLPDB_URL)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockList)
}
const result = await list(req, res)
// console.log(`test result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"id",
"timestamp",
"symbol",
"name",
"documentUri",
"documentHash",
"decimals",
"initialTokenQty"
])
})
})
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 mainnet txid on testnet", 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
//"650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a"
// 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 =
"650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a"
// 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 = 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"
])
})
})
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 mainnet txid on testnet", 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
//"650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a"
// 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 =
// testnet
["650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a"]
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"
])
})
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 =
// testnet
[
"650dea14c77f4d749608e36e375450c9ac91deb8b1b53e50cb0de2059a52d19a",
"c35a87afad11c8d086c1449ffd8b0a84324e72b15b1bcfdf166a493551b4eea6"
]
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"
])
})
})
describe("balancesForAddress()", () => {
const balancesForAddress = slpRoute.testableComponents.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 =
"simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk"
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 = "slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5"
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("should get token balance for an address", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(mockServerUrl)
.get(uri => uri.includes("/"))
.times(2)
.reply(200, mockData.mockSingleAddress)
}
req.params.address = "slptest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqv7sq3kk7"
const result = await balancesForAddress(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], [
"tokenId",
"balance",
"slpAddress",
"decimalCount"
])
})
})
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 =
"simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk"
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 = "slptest:qz4qnxcxwvmacgye8wlakhz0835x0w3vtvxu67w0ac"
req.params.tokenId =
"7ac7f4bb50b019fe0f5c81e3fc13fc0720e130282ea460768cafb49785eb2796"
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 = "slptest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqv7sq3kk7"
req.params.tokenId =
"6b081fcd1f78b187be1464313dac8ff257251b727a42b613552a4040870aeb29"
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(``)
.reply(200, { result: mockData.mockConvert })
}
req.params.address = "slptest:qz35h5mfa8w2pqma2jq06lp7dnv5fxkp2shlcycvd5"
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, "Unsupported address format")
} catch (err) {
// console.log(`err.message: ${util.inspect(err.message)}`)
assert.include(err.message, `Unsupported address format`)
}
})
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("validateBulk()", () => {
const validateBulk = slpRoute.testableComponents.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("should validate array with single element", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
sandbox
.stub(slpRoute.testableComponents, "isValidSlpTxid")
.resolves(true)
}
req.body.txids = [
"78d57a82a0dd9930cc17843d9d06677f267777dd6b25055bad0ae43f1b884091"
]
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.testableComponents, "isValidSlpTxid")
.resolves(true)
}
req.body.txids = [
"78d57a82a0dd9930cc17843d9d06677f267777dd6b25055bad0ae43f1b884091",
"82d996847a861b08b1601284ef7d40a1777d019154a6c4ed11571609dd3555ac"
]
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("tokenStatsSingle()", () => {
const tokenStatsSingle = slpRoute.testableComponents.tokenStats
it("should throw 400 if tokenID is empty", async () => {
req.params.tokenId = ""
const result = await tokenStatsSingle(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "tokenId can not be empty")
})
//
it("should get token stats for tokenId", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.SLPDB_URL}`)
.get(uri => uri.includes("/"))
.reply(200, {
t: [
{
tokenDetails: mockData.mockTokenDetails,
tokenStats: mockData.mockTokenStats
}
]
})
}
req.params.tokenId =
"37279c7dc81ceb34d12f03344b601c582e931e05d0e552c29c428bfa39d39af3"
const result = await tokenStatsSingle(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, [
"blockCreated",
"blockLastActiveMint",
"blockLastActiveSend",
"containsBaton",
"initialTokenQty",
"mintingBatonStatus",
"circulatingSupply",
"decimals",
"documentHash",
"versionType",
"timestamp",
"documentUri",
"name",
"symbol",
"id",
"totalBurned",
"totalMinted",
"txnsSinceGenesis",
"validAddresses"
])
})
})
describe("balancesForTokenSingle()", () => {
const balancesForTokenSingle =
slpRoute.testableComponents.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("should get balances for tokenId", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.SLPDB_URL}`)
.get(uri => uri.includes("/"))
.reply(200, {
a: [mockData.mockBalance]
})
}
req.params.tokenId =
"37279c7dc81ceb34d12f03344b601c582e931e05d0e552c29c428bfa39d39af3"
const result = await balancesForTokenSingle(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result[0], ["tokenId", "slpAddress", "tokenBalance"])
})
})
describe("txDetails()", () => {
let txDetails = slpRoute.testableComponents.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("should get tx details with token info", async () => {
if (process.env.TEST === "unit") {
// Mock the slpjs library for unit tests.
pathStub.BitboxNetwork = slpjsMock.BitboxNetwork
txDetails = slpRouteStub.testableComponents.txDetails
}
req.params.txid =
"57b3082a2bf269b3d6f40fee7fb9c664e8256a88ca5ee2697c05b9457822d446"
const result = await txDetails(req, res)
//console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAnyKeys(result, ["tokenIsValid", "tokenInfo"])
})
})
describe("txsTokenIdAddressSingle()", () => {
const txsTokenIdAddressSingle =
slpRoute.testableComponents.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("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"])
})
*/
})
})
-333
View File
@@ -1,333 +0,0 @@
/*
TESTS FOR THE TRANSACTION.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 "should throw an error for an invalid txid" for detailsSingle:
--The error handler should be refactored to return an intelligent error message,
instead of the 503 error it is returning now.
*/
"use strict"
const chai = require("chai")
const assert = chai.assert
const transactionRoute = require("../../src/routes/v2/transaction")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/transaction-mocks")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Transactions", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
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(() => {
// 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 = transactionRoute.testableComponents.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, "transaction", "Returns static string")
})
})
describe("#detailsBulk", async () => {
const detailsBulk = transactionRoute.testableComponents.detailsBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsBulk(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: `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
}
const result = await detailsBulk(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 an error for an invalid txid", async () => {
const fakeTXID = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${fakeTXID}`)
.reply(400, {
result: { error: "parameter 1 must be hexadecimal string" }
})
}
req.body = {
txids: [fakeTXID]
}
const result = await detailsBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
})
it("should process a single txid", async () => {
const txid = `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid}`)
.reply(200, mockData.mockDetails)
}
req.body = {
txids: [txid]
}
const result = await detailsBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"txid",
"version",
"locktime",
"vin",
"vout",
"blockhash",
"blockheight",
"confirmations",
"time",
"blocktime",
"valueOut",
"size",
"valueIn",
"fees"
])
})
it("should process a multiple txids", async () => {
const txid1 = `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
const txid2 = `8d4fd4dcaa9d8051dc7d862dc23d8aa23e20b77b9c928c49380685459caa7043`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid1}`)
.reply(200, mockData.mockDetails)
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid2}`)
.reply(200, mockData.mockDetails)
}
req.body = {
txids: [txid1, txid2]
}
const result = await detailsBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"txid",
"version",
"locktime",
"vin",
"vout",
"blockhash",
"blockheight",
"confirmations",
"time",
"blocktime",
"valueOut",
"size",
"valueIn",
"fees"
])
})
})
describe("#detailsSingle", () => {
// details route handler.
const detailsSingle = transactionRoute.testableComponents.detailsSingle
it("should throw 400 if txid is empty", async () => {
const result = await detailsSingle(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 = [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`]
const result = await detailsSingle(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 an error for an invalid txid", async () => {
if (process.env.TEST !== "unit") {
req.params.txid = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
const result = await detailsSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
// The error handling code should probably be updated to respond with a better
// error message.
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"parameter 1 must be hexadecimal string",
"Proper error message"
)
}
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.params.txid = `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsSingle(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
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")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single address", async () => {
const txid = `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
req.params.txid = txid
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid}`)
.reply(200, mockData.mockDetails)
}
// Call the details API.
const result = await detailsSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.hasAllKeys(result, [
"txid",
"version",
"locktime",
"vin",
"vout",
"blockhash",
"blockheight",
"confirmations",
"time",
"blocktime",
"valueOut",
"size",
"valueIn",
"fees"
])
assert.isArray(result.vin)
assert.isArray(result.vout)
})
})
})
-300
View File
@@ -1,300 +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/v2/util")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/util-mocks")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Util", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
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(() => {
// 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 = utilRoute.testableComponents.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 = utilRoute.testableComponents.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`
const result = 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(``)
.reply(200, { result: mockData.mockAddress })
}
req.params.address = `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`
const result = await validateAddress(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, [
"isvalid",
"address",
"scriptPubKey",
"ismine",
"iswatchonly",
"isscript"
])
})
})
describe("#validateAddressBulk", async () => {
const validateAddressBulk = utilRoute.testableComponents.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: [`bitcoincash:qrcc3jsqpgwqcdru70sk54sd0g3l04q7c53ycm6ucj`]
}
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 = [
`bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`
]
const result = 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(``)
.reply(200, { result: mockData.mockAddress })
}
req.body.addresses = [
`bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`
]
const result = await validateAddressBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(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(``)
.times(2)
.reply(200, { result: mockData.mockAddress })
}
req.body.addresses = [
`bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`,
`bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`
]
const result = await validateAddressBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], [
"isvalid",
"address",
"scriptPubKey",
"ismine",
"iswatchonly",
"isscript"
])
})
})
})
-611
View File
@@ -1,611 +0,0 @@
"use strict"
const blockRoute = require("../../src/routes/v3/insight/block")
const chai = require("chai")
const assert = chai.assert
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/block-mock")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Block", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
// Activate nock if it's inactive.
if (!nock.isActive()) nock.activate()
})
afterEach(() => {
// Clean up HTTP mocks.
nock.cleanAll() // clear interceptor list.
nock.restore()
})
after(() => {
// Restore any pre-existing environment variables.
process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL
process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL
process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME
process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD
})
describe("#root", () => {
// root route handler.
const root = blockRoute.testableComponents.root
it("should respond to GET for base route", async () => {
const result = root(req, res)
assert.equal(result.status, "block", "Returns static string")
})
})
describe("#detailsByHashSingle", () => {
const detailsByHash = blockRoute.testableComponents.detailsByHashSingle
it("should throw an error for an empty hash", async () => {
req.params.hash = ""
const result = await detailsByHash(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hash must not be empty",
"Proper error message"
)
})
it("should throw 50X when network issues", async () => {
// Save the existing RPC URL.
const savedUrl = process.env.BITCOINCOM_BASEURL
// Manipulate the URL to cause a 500 network error.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
req.params.hash = "abc123"
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.isAbove(res.statusCode, 499, "HTTP status code 50X expected.")
//assert.include(result.error, "ENOTFOUND", "Error message expected")
})
it("should throw an error for invalid hash", async () => {
req.params.hash = "abc123"
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.params.hash}`)
.reply(404, "Not found")
}
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.include(result.error, "Not found", "Proper error message")
})
it("should GET /detailsByHash/:hash", async () => {
req.params.hash =
"0000000000000000009ff8b38118d540e33c262bf6f78fc6863cc444526dc086"
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.params.hash}`)
.reply(200, mockData.mockBlockDetails)
}
const result = await detailsByHash(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"hash",
"size",
"height",
"version",
"merkleroot",
"tx",
"time",
"nonce",
"bits",
"difficulty",
"chainwork",
"confirmations",
"previousblockhash",
"nextblockhash",
"reward",
"isMainChain",
"poolInfo"
])
assert.isArray(result.tx)
})
})
describe("#detailsByHashBulk", () => {
// details route handler.
const detailsByHashBulk = blockRoute.testableComponents.detailsByHashBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsByHashBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hashes needs to be an array",
"Proper error message"
)
})
it("should error on non-array single address", async () => {
req.body = {
hashes:
"0000000000000000009ff8b38118d540e33c262bf6f78fc6863cc444526dc086"
}
const result = await detailsByHashBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"hashes needs to be an array",
"Proper error message"
)
})
it("should throw 400 error if addresses array is too large", async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push("")
req.body.hashes = testArray
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Array too large")
})
it("should throw an error for an invalid hash", async () => {
req.body = {
hashes: [`abc123`]
}
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(result.error, "Invalid hash", "Proper error message")
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.body = {
hashes: [
"00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79"
]
}
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsByHashBulk(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single hash", async () => {
req.body = {
hashes: [
"0000000000000000009ff8b38118d540e33c262bf6f78fc6863cc444526dc086"
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.equal(result.length, 1, "Array with one entry")
assert.hasAllKeys(result[0], [
"bits",
"chainwork",
"confirmations",
"difficulty",
"hash",
"height",
"isMainChain",
"merkleroot",
"nextblockhash",
"nonce",
"poolInfo",
"previousblockhash",
"reward",
"size",
"time",
"tx",
"version"
])
})
it("should get details for multiple hashes", async () => {
req.body = {
hashes: [
`0000000000000000009ff8b38118d540e33c262bf6f78fc6863cc444526dc086`,
`0000000000000000009ff8b38118d540e33c262bf6f78fc6863cc444526dc086`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
.reply(200, mockData.mockBlockDetails)
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[1]}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
it("should throw an error if hash not found", async () => {
req.body = {
hashes: [
`00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44abcdef`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${req.body.hashes[0]}`)
//.reply(404, { error: { message: "Not Found" } })
.reply(404, "Not found")
}
const result = await detailsByHashBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 404, "HTTP status code 404 expected.")
assert.include(result.error, "Not found", "Proper error message")
})
})
describe("Block Details By Height", () => {
// block route handler.
const detailsByHeight = blockRoute.testableComponents.detailsByHeightSingle
it("should throw an error for an empty height", async () => {
req.params.height = ""
const result = await detailsByHeight(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"height must not be empty",
"Proper error message"
)
})
it("should throw 500 when network issues", async () => {
// Save the existing RPC URL.
const savedUrl = process.env.BITCOINCOM_BASEURL
const savedUrl2 = process.env.RPC_BASEURL
// Manipulate the URL to cause a 500 network error.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api/"
req.params.height = "abc123"
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
process.env.RPC_BASEURL = savedUrl2
assert.isAbove(
res.statusCode,
499,
"HTTP status code 500 or great expected."
)
//assert.include(result.error, "ENOTFOUND", "Error message expected")
})
it("should throw an error for invalid height", async () => {
req.params.height = "abc123"
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
console.log(`process.env.RPC_BASEURL: ${process.env.RPC_BASEURL}`)
nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes("/"))
.reply(500, {
error: {
code: -1,
message: "JSON value is not an integer as expected"
}
})
}
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"JSON value is not an integer as expected",
"Proper error message"
)
})
it("should GET /detailsByHeight/:height", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes("/"))
.reply(200, { result: mockData.mockBlockHash })
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(
`/block/00000000000000645dec6503d3f5eafb0d2537a7a28f181d721dec7c44154c79`
)
.reply(200, mockData.mockBlockDetails)
}
req.params.height = 500000
const result = await detailsByHeight(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [
"hash",
"size",
"height",
"version",
"merkleroot",
"tx",
"time",
"nonce",
"bits",
"difficulty",
"chainwork",
"confirmations",
"previousblockhash",
"nextblockhash",
"reward",
"isMainChain",
"poolInfo"
])
assert.isArray(result.tx)
})
})
describe("#detailsByHeightBulk", () => {
// details route handler.
const detailsByHeightBulk =
blockRoute.testableComponents.detailsByHeightBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsByHeightBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"heights needs to be an array",
"Proper error message"
)
})
it("should error on non-array single height", async () => {
req.body = {
heights: 500000
}
const result = await detailsByHeightBulk(req, res)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"heights needs to be an array",
"Proper error message"
)
})
it("should throw 400 error if addresses array is too large", async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push("")
req.body.heights = testArray
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "Array too large")
})
it("should throw error for an invalid height", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes("/"))
.reply(500, {
error: {
code: -1,
message: "JSON value is not an integer as expected"
}
})
}
req.body.heights = [`abc123`]
const result = await detailsByHeightBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.body.heights = [`500000`]
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsByHeightBulk(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
assert.equal(res.statusCode, 500, "HTTP status code 500 expected.")
assert.include(result.error, "ENOTFOUND", "Error message expected")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single height", async () => {
req.body.heights = [`500000`]
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes("/"))
.reply(200, { result: mockData.mockBlockHash })
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${mockData.mockBlockHash}`)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.equal(result.length, 1, "Array with one entry")
assert.hasAllKeys(result[0], [
"bits",
"chainwork",
"confirmations",
"difficulty",
"hash",
"height",
"isMainChain",
"merkleroot",
"nextblockhash",
"nonce",
"poolInfo",
"previousblockhash",
"reward",
"size",
"time",
"tx",
"version"
])
})
it("should get details for multiple block heights", async () => {
req.body = {
heights: [`500000`, `500001`]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes("/"))
.times(2)
.reply(200, { result: mockData.mockBlockHash })
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/block/${mockData.mockBlockHash}`)
.times(2)
.reply(200, mockData.mockBlockDetails)
}
// Call the details API.
const result = await detailsByHeightBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.equal(result.length, 2, "2 outputs for 2 inputs")
})
})
})
+17 -16
View File
@@ -11,7 +11,7 @@
const chai = require("chai")
const assert = chai.assert
const nock = require("nock") // HTTP mocking
const blockchainRoute = require("../../src/routes/v3/blockchain")
const blockchainRoute = require("../../src/routes/v3/full-node/blockchain")
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
@@ -107,7 +107,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -149,7 +149,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -201,7 +201,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -252,7 +252,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Network error: Could not communicate with full node",
@@ -499,7 +499,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -540,7 +540,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -580,7 +580,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -626,7 +626,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -678,7 +678,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -823,7 +823,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -840,11 +840,12 @@ describe("#BlockchainRouter", () => {
.reply(200, { result: mockData.mockTxOut })
}
req.params.txid = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`
req.params.n = 1
req.params.txid = `197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d`
req.params.n = 0
req.query.include_mempool = "true"
const result = await getTxOut(req, res)
//console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
"bestblock",
@@ -890,7 +891,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
@@ -1027,7 +1028,7 @@ describe("#BlockchainRouter", () => {
// Restore the saved URL.
process.env.RPC_BASEURL = savedUrl2
assert.equal(res.statusCode, 503, "HTTP status code 503 expected.")
assert.isAbove(res.statusCode, 499, "HTTP status code 503 expected.")
assert.include(
result.error,
"Could not communicate with full node",
+1 -1
View File
@@ -11,7 +11,7 @@
const chai = require("chai")
const assert = chai.assert
const controlRoute = require("../../src/routes/v3/control")
const controlRoute = require("../../src/routes/v3/full-node/control")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
File diff suppressed because it is too large Load Diff
-599
View File
@@ -1,599 +0,0 @@
/*
TESTS FOR THE BITCORE.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 bitcoreRoute = require("../../src/routes/v3/bitcore")
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/bitcore-mock")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Bitcore Router", () => {
let req, res, mockServerUrl
before(() => {
originalUrl = process.env.BITCORE_URL
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCORE_URL = "http://fakeurl/api/"
mockServerUrl = `http://fakeurl`
}
// console.log(`Testing type is: ${process.env.TEST}`)
})
// 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.BITCORE_URL = originalUrl
})
describe("#root", () => {
// root route handler.
const root = bitcoreRoute.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("#Balance Single", () => {
// details route handler.
const balanceSingle = bitcoreRoute.testableComponents.balanceSingle
it("should throw 400 if address is empty", async () => {
const result = await 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 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 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 = `bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`
const result = await 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.BITCORE_URL
try {
req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
// Switch the Insight URL to something that will error out.
process.env.BITCORE_URL = "http://fakeurl/api/"
const result = await balanceSingle(req, res)
// Restore the saved URL.
process.env.BITCORE_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.BITCORE_URL = savedUrl
}
})
it("should get balance for a single address", async () => {
req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCORE_URL}`)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockBalance)
}
// Call the details API.
const result = await balanceSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ["confirmed", "unconfirmed", "balance"])
assert.isNumber(result.confirmed)
assert.isNumber(result.unconfirmed)
assert.isNumber(result.balance)
})
})
describe("#Balance Bulk", () => {
// details route handler.
const balanceBulk = bitcoreRoute.testableComponents.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: [`bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`]
}
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.BITCOINCOM_BASEURL
try {
req.body = {
addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`]
}
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await balanceBulk(req, res)
//console.log(`network issue result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = 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.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single address", async () => {
req.body = {
addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(mockServerUrl)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockBalance)
}
// Call the details API.
const result = await balanceBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ["confirmed", "unconfirmed", "balance"])
assert.isNumber(result[0].confirmed)
assert.isNumber(result[0].unconfirmed)
assert.isNumber(result[0].balance)
})
it("should get details for multiple addresses", async () => {
req.body = {
addresses: [
`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`,
`bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(mockServerUrl)
.get(uri => uri.includes("/"))
.times(2)
.reply(200, 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 = bitcoreRoute.testableComponents.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 = `bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`
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.BITCORE_URL
try {
req.params.address = `qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
// Switch the Insight URL to something that will error out.
process.env.BITCORE_URL = "http://fakeurl/api/"
const result = await utxosSingle(req, res)
// Restore the saved URL.
process.env.BITCORE_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.BITCORE_URL = savedUrl
}
})
it("should get utxos for a single address", async () => {
req.params.address = `bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCORE_URL}`)
.get(uri => uri.includes("/"))
.reply(200, mockData.mockUtxos)
}
// Call the details API.
const result = await utxosSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"_id",
"chain",
"network",
"coinbase",
"mintIndex",
"spentTxid",
"mintTxid",
"mintHeight",
"spentHeight",
"address",
"script",
"value",
"confirmations"
])
})
})
describe("#UTXO Bulk", () => {
// details route handler.
const utxosBulk = bitcoreRoute.testableComponents.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: [`bitcoincash:qqqvv56zepke5k0xeaehlmjtmkv9ly2uzgkxpajdx3`]
}
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.BITCOINCOM_BASEURL
try {
req.body = {
addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`]
}
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await utxosBulk(req, res)
//console.log(`network issue result: ${util.inspect(result)}`)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = 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.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single address", async () => {
req.body = {
addresses: [`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(mockServerUrl)
.get(uri => uri.includes("/"))
.reply(200, 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], [
"_id",
"chain",
"network",
"coinbase",
"mintIndex",
"spentTxid",
"mintTxid",
"mintHeight",
"spentHeight",
"address",
"script",
"value",
"confirmations"
])
})
it("should get details for multiple addresses", async () => {
req.body = {
addresses: [
`bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4`,
`bchtest:qzknfggae0av6yvxk77gmyq7syc67yux6sk80haqyr`
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(mockServerUrl)
.get(uri => uri.includes("/"))
.times(2)
.reply(200, 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], [
"_id",
"chain",
"network",
"coinbase",
"mintIndex",
"spentTxid",
"mintTxid",
"mintHeight",
"spentHeight",
"address",
"script",
"value",
"confirmations"
])
})
})
})
+35 -3
View File
@@ -14,8 +14,11 @@ const axios = require("axios")
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
const SERVER = `http://192.168.0.36:12400/v3/`
//const SERVER = `http://localhost:3000/v2/`
// const SERVER = `http://192.168.0.36:12400/v3/`
const SERVER = `http://localhost:3000/v3/`
const TEST_JWT =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYTc5ZDk4OTYyMjRjNjM2MmQwYzkwMiIsImlhdCI6MTU3MTUzOTU1MSwiZXhwIjoxNTc0MTMxNTUxfQ.PfPW_Z2NYT1O2zUHXopcz2aLGHSGudaKOIGnt7SuAi4"
describe("#rate limits", () => {
it("should get control/getNetworkInfo() with no auth", async () => {
@@ -26,7 +29,7 @@ describe("#rate limits", () => {
const result = await axios(options)
//console.log(`result.status: ${result.status}`)
//console.log(`result.data: ${util.inspect(result.data)}`)
// console.log(`result.data: ${util.inspect(result.data)}`)
assert.equal(result.status, 200)
assert.hasAnyKeys(result.data, ["version"])
@@ -130,4 +133,33 @@ describe("#rate limits", () => {
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)
})
+1 -1
View File
@@ -10,7 +10,7 @@
const chai = require("chai")
const assert = chai.assert
const miningRoute = require("../../src/routes/v3/mining")
const miningRoute = require("../../src/routes/v3/full-node/mining")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
+169 -17
View File
@@ -13,12 +13,16 @@ const { mockReq, mockRes, mockNext } = require("./mocks/express-mocks")
// Libraries under test
let rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
const controlRoute = require("../../src/routes/v3/control")
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.
describe("#route-ratelimits", () => {
// JWT token used in tests.
const jwt = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYWRlM2Y1NzM5ZTZjMGZmMDM0YjlhMSIsImlhdCI6MTU3MTY3NzQ1MCwiZXhwIjoxNTc0MjY5NDUwfQ.SSz7F7ETyBB3eoNG2VKCzPOhddtB-vrtmEoj7PxicrQ`
describe("#route-ratelimits & jwt-auth", () => {
before(() => {
// Save existing environment variables.
originalEnvVars = {
@@ -27,13 +31,16 @@ describe("#route-ratelimits", () => {
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/`
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
req = Object.assign({}, mockReq)
res = Object.assign({}, mockRes)
next = mockNext
// Explicitly reset the parmas and body.
@@ -42,6 +49,104 @@ describe("#route-ratelimits", () => {
req.query = {}
})
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)
})
})
// TODO: This code has been refactored and these unit tests no longer apply.
// I think I forgot to create new unit tests to reflect the change in code though.
/*
describe("#routeAccess", () => {
it("should do nothing if req.locals.jwtToken is undefined", () => {
// Initialize req.locals
req.locals = {
proLimit: false,
apiLevel: 0
}
req.url = "/insight/address/details"
// Reset the history of the stub and assert it has not been called.
res.status.resetHistory()
assert.equal(res.status.called, false, "stub history reset")
jwtAuth.routeAccess(req, res, next)
// console.log(`req.locals: ${JSON.stringify(req.locals, null, 2)}`)
assert.equal(
res.status.called,
false,
"stub should NOT have been called."
)
})
it("should throw error if full-node tier tries to access indexer", () => {
// Initialize req.locals
req.locals = {
proLimit: true,
apiLevel: 10,
jwtToken: jwt
}
req.url = "/insight/address/details"
try {
res.status.resetHistory()
assert.equal(res.status.called, false, "stub history reset")
jwtAuth.routeAccess(req, res, next)
assert.equal(res.status.called, true, "stub should have been called.")
} catch (err) {
console.log(`caught error: `, err)
}
})
it("should allow indexer tier tries access indexer endpoints", () => {
// Initialize req.locals
req.locals = {
proLimit: true,
apiLevel: 20,
jwtToken: jwt
}
req.url = "/insight/address/details"
try {
res.status.resetHistory()
assert.equal(res.status.called, false, "stub history reset")
jwtAuth.routeAccess(req, res, next)
assert.equal(
res.status.called,
false,
"stub should NOT have been called."
)
} catch (err) {
console.log(`caught error: `, err)
}
})
})
*/
})
describe("#routeRateLimit", () => {
let routeRateLimit = rateLimitMiddleware.routeRateLimit
const getInfo = controlRoute.testableComponents.getInfo
@@ -59,12 +164,67 @@ describe("#route-ratelimits", () => {
assert.equal(next.called, true)
})
it("should trigger rate-limit handler if rate limits exceeds 25 request per minute", async () => {
it("should trigger rate-limit handler if rate limits exceeds 5 request per minute", async () => {
req.baseUrl = "/v3"
req.path = "/control/getNetworkInfo"
req.method = "GET"
for (let i = 0; i < 35; i++) {
for (let i = 0; i < 5; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(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 () => {
// Clear the require cache before running this test.
delete require.cache[
require.resolve("../../src/middleware/route-ratelimit")
]
rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
routeRateLimit = rateLimitMiddleware.routeRateLimit
req.baseUrl = "/v3"
req.path = "/control/getNetworkInfo"
req.method = "GET"
req.locals.proLimit = true
req.locals.apiLevel = 0
for (let i = 0; i < 5; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(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 10 RPM", async () => {
req.baseUrl = "/v3"
req.path = "/control/getNetworkInfo"
req.method = "GET"
req.locals.proLimit = true
req.locals.apiLevel = 0
for (let i = 0; i < 12; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(req, res, next)
@@ -92,11 +252,7 @@ describe("#route-ratelimits", () => {
req.method = "GET"
req.locals.proLimit = true
//console.log(`req.locals before test: ${util.inspect(req.locals)}`)
// Prepare the authorization header
//req.headers.authorization = generateAuthHeader("BITBOX")
req.locals.apiLevel = 10
for (let i = 0; i < 25; i++) {
next.reset() // reset the stubbed next() function.
@@ -128,13 +284,9 @@ describe("#route-ratelimits", () => {
req.method = "GET"
req.locals.proLimit = true
req.locals.apiLevel = 10
//console.log(`req.locals before test: ${util.inspect(req.locals)}`)
// Prepare the authorization header
//req.headers.authorization = generateAuthHeader("BITBOX")
for (let i = 0; i < 400; i++) {
for (let i = 0; i < 150; i++) {
next.reset() // reset the stubbed next() function.
await routeRateLimit(req, res, next)
+3 -3
View File
@@ -14,7 +14,7 @@
const chai = require("chai")
const assert = chai.assert
const rawtransactions = require("../../src/routes/v3/rawtransactions")
const rawtransactions = require("../../src/routes/v3/full-node/rawtransactions")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
@@ -692,7 +692,7 @@ describe("#Raw-Transactions", () => {
// Integration test
} else {
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "transaction already in block chain")
assert.include(result.error, "Missing inputs")
}
})
})
@@ -800,7 +800,7 @@ describe("#Raw-Transactions", () => {
// Integration test
} else {
assert.hasAllKeys(result, ["error"])
assert.include(result.error, "transaction already in block chain")
assert.include(result.error, "Missing inputs")
}
})
})
+2 -2
View File
@@ -429,7 +429,7 @@ describe("#SLP", () => {
])
})
})
*/
describe("balancesForAddress()", () => {
const balancesForAddress = slpRoute.testableComponents.balancesForAddress
@@ -599,7 +599,7 @@ describe("#SLP", () => {
})
}
})
/*
describe("balancesForAddressByTokenID()", () => {
const balancesForAddressByTokenID =
slpRoute.testableComponents.balancesForAddressByTokenID
-333
View File
@@ -1,333 +0,0 @@
/*
TESTS FOR THE TRANSACTION.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 "should throw an error for an invalid txid" for detailsSingle:
--The error handler should be refactored to return an intelligent error message,
instead of the 503 error it is returning now.
*/
"use strict"
const chai = require("chai")
const assert = chai.assert
const transactionRoute = require("../../src/routes/v3/insight/transaction")
const nock = require("nock") // HTTP mocking
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require("./mocks/express-mocks")
const mockData = require("./mocks/transaction-mocks")
// Used for debugging.
const util = require("util")
util.inspect.defaultOptions = { depth: 1 }
describe("#Transactions", () => {
let req, res
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = "unit"
if (process.env.TEST === "unit") {
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
process.env.RPC_BASEURL = "http://fakeurl/api"
process.env.RPC_USERNAME = "fakeusername"
process.env.RPC_PASSWORD = "fakepassword"
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
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(() => {
// 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 = transactionRoute.testableComponents.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, "transaction", "Returns static string")
})
})
describe("#detailsBulk", async () => {
const detailsBulk = transactionRoute.testableComponents.detailsBulk
it("should throw an error for an empty body", async () => {
req.body = {}
const result = await detailsBulk(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: `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
}
const result = await detailsBulk(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 an error for an invalid txid", async () => {
const fakeTXID = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${fakeTXID}`)
.reply(400, {
result: { error: "parameter 1 must be hexadecimal string" }
})
}
req.body = {
txids: [fakeTXID]
}
const result = await detailsBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
})
it("should process a single txid", async () => {
const txid = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid}`)
.reply(200, mockData.mockDetails)
}
req.body = {
txids: [txid]
}
const result = await detailsBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"txid",
"version",
"locktime",
"vin",
"vout",
"blockhash",
"blockheight",
"confirmations",
"time",
"blocktime",
"valueOut",
"size",
"valueIn",
"fees"
])
})
it("should process a multiple txids", async () => {
const txid1 = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`
const txid2 = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid1}`)
.reply(200, mockData.mockDetails)
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid2}`)
.reply(200, mockData.mockDetails)
}
req.body = {
txids: [txid1, txid2]
}
const result = await detailsBulk(req, res)
//console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAnyKeys(result[0], [
"txid",
"version",
"locktime",
"vin",
"vout",
"blockhash",
"blockheight",
"confirmations",
"time",
"blocktime",
"valueOut",
"size",
"valueIn",
"fees"
])
})
})
describe("#detailsSingle", () => {
// details route handler.
const detailsSingle = transactionRoute.testableComponents.detailsSingle
it("should throw 400 if txid is empty", async () => {
const result = await detailsSingle(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 = [`qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`]
const result = await detailsSingle(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 an error for an invalid txid", async () => {
if (process.env.TEST !== "unit") {
req.params.txid = `02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c`
const result = await detailsSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
// The error handling code should probably be updated to respond with a better
// error message.
assert.equal(res.statusCode, 400, "HTTP status code 400 expected.")
assert.include(
result.error,
"parameter 1 must be hexadecimal string",
"Proper error message"
)
}
})
it("should throw 500 when network issues", async () => {
const savedUrl = process.env.BITCOINCOM_BASEURL
try {
req.params.txid = `6f235bd3a689f03c11969cd649ccad592462ca958bc519a30194e7a67b349a40`
// Switch the Insight URL to something that will error out.
process.env.BITCOINCOM_BASEURL = "http://fakeurl/api/"
const result = await detailsSingle(req, res)
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
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")
} catch (err) {
// Restore the saved URL.
process.env.BITCOINCOM_BASEURL = savedUrl
}
})
it("should get details for a single txid", async () => {
const txid = `2c7ae9f865f7ce0c33c189b2f83414176903ce4b06ed9f8b7bcf55efbd4a7266`
req.params.txid = txid
// Mock the Insight URL for unit tests.
if (process.env.TEST === "unit") {
nock(`${process.env.BITCOINCOM_BASEURL}`)
.get(`/tx/${txid}`)
.reply(200, mockData.mockDetails)
}
// Call the details API.
const result = await detailsSingle(req, res)
//console.log(`result: ${util.inspect(result)}`)
// Assert that required fields exist in the returned object.
assert.hasAllKeys(result, [
"txid",
"version",
"locktime",
"vin",
"vout",
"blockhash",
"blockheight",
"confirmations",
"time",
"blocktime",
"valueOut",
"size",
"valueIn",
"fees"
])
assert.isArray(result.vin)
assert.isArray(result.vout)
})
})
})
+19 -17
View File
@@ -350,9 +350,11 @@ describe("#Util", () => {
)
})
it("should generate transaction for valid token sweep", async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === "unit") {
// 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.testableComponents,
@@ -370,24 +372,24 @@ describe("#Util", () => {
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")
// 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"
}
req.body = {
wif: "L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt",
toAddr: "bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p"
}
const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, "test-txid")
})
assert.equal(result, "test-txid")
})
}
it("should return balance if balance-only is true", async () => {
// Mock the RPC call for unit tests.