mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-21 16:52:04 -07:00
Merge branch 'master' into greenkeeper/nock-11.7.0
This commit is contained in:
@@ -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/*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+2106
-2890
File diff suppressed because it is too large
Load Diff
+5
-9
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -1,805 +0,0 @@
|
||||
/*
|
||||
Address route
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const requestUtils = require("./services/requestUtils")
|
||||
const axios = require("axios")
|
||||
const logger = require("./logging.js")
|
||||
const routeUtils = require("./route-utils")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
//const router = express.Router()
|
||||
const router = express.Router()
|
||||
|
||||
// Used for processing error messages before sending them to the user.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
// Use the default (and max) page size of 1000
|
||||
// https://github.com/bitpay/insight-api#notes-on-upgrading-from-v03
|
||||
const PAGE_SIZE = 1000
|
||||
|
||||
// Connect the route endpoints to their handler functions.
|
||||
router.get("/", root)
|
||||
router.get("/details/:address", detailsSingle)
|
||||
router.post("/details", detailsBulk)
|
||||
router.post("/utxo", utxoBulk)
|
||||
router.get("/utxo/:address", utxoSingle)
|
||||
router.post("/unconfirmed", unconfirmedBulk)
|
||||
router.get("/unconfirmed/:address", unconfirmedSingle)
|
||||
router.get("/transactions/:address", transactionsSingle)
|
||||
router.post("/transactions", transactionsBulk)
|
||||
router.get("/fromXPub/:xpub", fromXPubSingle)
|
||||
|
||||
// Root API endpoint. Simply acknowledges that it exists.
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "address" })
|
||||
}
|
||||
|
||||
// Query the Insight API for details on a single BCH address.
|
||||
// Returns a Promise.
|
||||
async function detailsFromInsight(thisAddress, currentPage = 0) {
|
||||
try {
|
||||
let addr
|
||||
if (
|
||||
process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/"
|
||||
)
|
||||
addr = BITBOX.Address.toCashAddress(thisAddress)
|
||||
else addr = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
|
||||
let path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}`
|
||||
|
||||
// Set from and to params based on currentPage and pageSize
|
||||
// https://github.com/bitpay/insight-api/blob/master/README.md#notes-on-upgrading-from-v02
|
||||
const from = currentPage * PAGE_SIZE
|
||||
const to = from + PAGE_SIZE
|
||||
path = `${path}?from=${from}&to=${to}`
|
||||
|
||||
// Query the Insight server.
|
||||
const axiosResponse = await axios.get(path)
|
||||
const retData = axiosResponse.data
|
||||
//console.log(`retData: ${util.inspect(retData)}`)
|
||||
|
||||
// Calculate pagesTotal from response
|
||||
const pagesTotal = Math.ceil(retData.txApperances / PAGE_SIZE)
|
||||
|
||||
// Append different address formats to the return data.
|
||||
retData.legacyAddress = BITBOX.Address.toLegacyAddress(retData.addrStr)
|
||||
retData.cashAddress = BITBOX.Address.toCashAddress(retData.addrStr)
|
||||
delete retData.addrStr
|
||||
|
||||
// Append pagination information to the return data.
|
||||
retData.currentPage = currentPage
|
||||
retData.pagesTotal = pagesTotal
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// POST handler for bulk queries on address details
|
||||
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"]}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details
|
||||
// curl -d '{"addresses": ["bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr", "bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul"], "from": 1, "to": 5}' -H "Content-Type: application/json" http://localhost:3000/v2/address/details
|
||||
async function detailsBulk(req, res, next) {
|
||||
try {
|
||||
let addresses = req.body.addresses
|
||||
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
|
||||
|
||||
// Reject if addresses is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "addresses needs to be an array. Use GET for single address."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/details with these addresses: `, addresses)
|
||||
wlogger.debug(`Executing address/details with these addresses: `, addresses)
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loops through each address and creates an array of Promises, querying
|
||||
// Insight API in parallel.
|
||||
addresses = addresses.map(async (address, index) =>
|
||||
detailsFromInsight(address, currentPage)
|
||||
)
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(addresses)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
//logger.error(`Error in detailsBulk(): `, err)
|
||||
wlogger.error(`Error in address.ts/detailsBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler for single address details
|
||||
async function detailsSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
|
||||
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/detailsSingle with this address: `, address)
|
||||
wlogger.debug(
|
||||
`Executing address/detailsSingle with this address: `,
|
||||
address
|
||||
)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await detailsFromInsight(address, currentPage)
|
||||
|
||||
// Return the retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in address.ts/detailsSingle: `, err)
|
||||
wlogger.error(`Error in address.ts/detailsSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve UTXO data from the Insight API
|
||||
async function utxoFromInsight(thisAddress) {
|
||||
try {
|
||||
let addr
|
||||
if (
|
||||
process.env.BITCOINCOM_BASEURL === "https://bch-insight.bitpay.com/api/"
|
||||
)
|
||||
addr = BITBOX.Address.toCashAddress(thisAddress)
|
||||
else addr = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
|
||||
const path = `${process.env.BITCOINCOM_BASEURL}addr/${addr}/utxo`
|
||||
|
||||
// Query the Insight server.
|
||||
const response = await axios.get(path)
|
||||
|
||||
// Append different address formats to the return data.
|
||||
const retData = {
|
||||
utxos: Array,
|
||||
legacyAddress: String,
|
||||
cashAddress: String,
|
||||
scriptPubKey: String
|
||||
}
|
||||
if (response.data.length && response.data[0].scriptPubKey) {
|
||||
const spk = response.data[0].scriptPubKey
|
||||
retData.scriptPubKey = spk
|
||||
}
|
||||
retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress)
|
||||
retData.utxos = response.data.map(utxo => {
|
||||
delete utxo.address
|
||||
delete utxo.scriptPubKey
|
||||
return utxo
|
||||
})
|
||||
//console.log(`utxoFromInsight retData: ${util.inspect(retData)}`)
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve UTXO information for an address.
|
||||
async function utxoBulk(req, res, next) {
|
||||
try {
|
||||
let addresses = req.body.addresses
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "addresses needs to be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (er) {
|
||||
//if (er.message.includes("Unsupported address format"))
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxoBulk with these addresses: `, addresses)
|
||||
wlogger.debug(
|
||||
`Executing address/utxoBulk with these addresses: `,
|
||||
addresses
|
||||
)
|
||||
|
||||
// Loops through each address and creates an array of Promises, querying
|
||||
// Insight API in parallel.
|
||||
addresses = addresses.map(async (address, index) =>
|
||||
utxoFromInsight(address)
|
||||
)
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(addresses)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/utxoBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler for single address details
|
||||
async function utxoSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await utxoFromInsight(address)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/utxoSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve any unconfirmed TX information for a given address.
|
||||
async function unconfirmedBulk(req, res, next) {
|
||||
try {
|
||||
const addresses = req.body.addresses
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "addresses needs to be an array" })
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Collect an array of promises.
|
||||
const promises = addresses.map(address => utxoFromInsight(address))
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(promises)
|
||||
|
||||
// Loop through each result
|
||||
const finalResult = result.map(elem => {
|
||||
//console.log(`elem: ${util.inspect(elem)}`)
|
||||
|
||||
// Filter out confirmed transactions.
|
||||
const unconfirmedUtxos = elem.utxos.filter(
|
||||
utxo => utxo.confirmations === 0
|
||||
)
|
||||
|
||||
elem.utxos = unconfirmedUtxos
|
||||
|
||||
return elem
|
||||
})
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(finalResult)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/unconfirmedBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler. Retrieve any unconfirmed TX information for a given address.
|
||||
async function unconfirmedSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
wlogger.debug(`Executing address/utxoSingle with this address: `, address)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await utxoFromInsight(address)
|
||||
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
|
||||
|
||||
// Loop through each returned UTXO.
|
||||
const unconfirmedUTXOs = []
|
||||
for (let j = 0; j < retData.utxos.length; j++) {
|
||||
const thisUtxo = retData.utxos[j]
|
||||
|
||||
// Only interested in UTXOs with no confirmations.
|
||||
if (thisUtxo.confirmations === 0) unconfirmedUTXOs.push(thisUtxo)
|
||||
}
|
||||
|
||||
retData.utxos = unconfirmedUTXOs
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/unconfirmedSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve transaction data from the Insight API
|
||||
async function transactionsFromInsight(thisAddress, currentPage = 0) {
|
||||
try {
|
||||
const path = `${process.env.BITCOINCOM_BASEURL}txs/?address=${thisAddress}&pageNum=${currentPage}`
|
||||
|
||||
// Query the Insight server.
|
||||
const response = await axios.get(path)
|
||||
|
||||
// Append different address formats to the return data.
|
||||
const retData = response.data
|
||||
retData.legacyAddress = BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
retData.cashAddress = BITBOX.Address.toCashAddress(thisAddress)
|
||||
retData.currentPage = currentPage
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get an array of TX information for a given address.
|
||||
async function transactionsBulk(req, res, next) {
|
||||
try {
|
||||
let addresses = req.body.addresses
|
||||
const currentPage = req.body.page ? parseInt(req.body.page, 10) : 0
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "addresses needs to be an array" })
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
wlogger.debug(`Executing address/utxo with these addresses: `, addresses)
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const thisAddress = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
BITBOX.Address.toLegacyAddress(thisAddress)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(thisAddress)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through each address and collect an array of promises.
|
||||
addresses = addresses.map(async (address, index) =>
|
||||
transactionsFromInsight(address, currentPage)
|
||||
)
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(addresses)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/transactionsBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler. Retrieve any unconfirmed TX information for a given address.
|
||||
async function transactionsSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
const currentPage = req.query.page ? parseInt(req.query.page, 10) : 0
|
||||
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(address)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "address can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing address/transactionsSingle with this address: `,
|
||||
address
|
||||
)
|
||||
wlogger.debug(
|
||||
`Executing address/transactionsSingle with this address: `,
|
||||
address
|
||||
)
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await transactionsFromInsight(address, currentPage)
|
||||
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/transactionsSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function fromXPubSingle(req, res, next) {
|
||||
try {
|
||||
const xpub = req.params.xpub
|
||||
const hdPath = req.query.hdPath ? req.query.hdPath : "0"
|
||||
|
||||
if (!xpub || xpub === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "xpub can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if xpub is an array.
|
||||
if (Array.isArray(xpub)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "xpub can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing address/fromXPub with this xpub: `, xpub)
|
||||
wlogger.debug(`Executing address/fromXPub with this xpub: `, xpub)
|
||||
|
||||
const cashAddr = BITBOX.Address.fromXPub(xpub, hdPath)
|
||||
const legacyAddr = BITBOX.Address.toLegacyAddress(cashAddr)
|
||||
res.status(200)
|
||||
return res.json({
|
||||
cashAddress: cashAddr,
|
||||
legacyAddress: legacyAddr
|
||||
})
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in address.ts/fromXPubSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
detailsBulk,
|
||||
detailsSingle,
|
||||
utxoBulk,
|
||||
utxoSingle,
|
||||
unconfirmedBulk,
|
||||
unconfirmedSingle,
|
||||
transactionsBulk,
|
||||
transactionsSingle,
|
||||
fromXPubSingle
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const requestUtils = require("./services/requestUtils")
|
||||
const axios = require("axios")
|
||||
const bitbox = require("./services/bitbox")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
|
||||
// Used for processing error messages before sending them to the user.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 3 }
|
||||
|
||||
const router = express.Router()
|
||||
//const BitboxHTTP = bitbox.getInstance()
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/detailsByHash/:hash", detailsByHashSingle)
|
||||
router.post("/detailsByHash", detailsByHashBulk)
|
||||
router.get("/detailsByHeight/:height", detailsByHeightSingle)
|
||||
router.post("/detailsByHeight", detailsByHeightBulk)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "block" })
|
||||
}
|
||||
|
||||
// Call the insight server to get block details based on the hash.
|
||||
async function detailsByHashSingle(req, res, next) {
|
||||
try {
|
||||
const hash = req.params.hash
|
||||
|
||||
// Reject if hash is empty
|
||||
if (!hash || hash === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hash must not be empty" })
|
||||
}
|
||||
|
||||
const response = await axios.get(
|
||||
`${process.env.BITCOINCOM_BASEURL}block/${hash}`
|
||||
)
|
||||
//console.log(`response.data: ${JSON.stringify(response.data,null,2)}`)
|
||||
|
||||
const parsed = response.data
|
||||
return res.json(parsed)
|
||||
} catch (error) {
|
||||
//console.log(`error object: ${util.inspect(error)}`)
|
||||
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(error)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
if (error.response && error.response.status === 404) {
|
||||
res.status(404)
|
||||
return res.json({ error: "Not Found" })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in block/detailsByHash: `, error)
|
||||
wlogger.error(`Error in block.ts/detailsByHashSingle().`, error)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(error) })
|
||||
}
|
||||
}
|
||||
|
||||
async function detailsByHashBulk(req, res, next) {
|
||||
try {
|
||||
const hashes = req.body.hashes
|
||||
|
||||
// Reject if hashes is not an array.
|
||||
if (!Array.isArray(hashes)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "hashes needs to be an array. Use GET for single address."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hashes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each hash in the array.
|
||||
for (let i = 0; i < hashes.length; i++) {
|
||||
const thisHash = hashes[i]
|
||||
|
||||
if (thisHash.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid hash. Double check your hash is valid: ${thisHash}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through each hash and creates an array of promises
|
||||
const axiosPromises = hashes.map(async hash =>
|
||||
axios.get(`${process.env.BITCOINCOM_BASEURL}block/${hash}`)
|
||||
)
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const axiosResult = await axios.all(axiosPromises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data)
|
||||
//console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (error) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(error)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
if (error.response && error.response.status === 404) {
|
||||
res.status(404)
|
||||
return res.json({ error: "Not Found" })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in block/detailsByHash: `, error)
|
||||
wlogger.error(`Error in block.ts/detailsByHashBulk().`, error)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(error) })
|
||||
}
|
||||
}
|
||||
|
||||
// Call the Full Node to get block hash based on height, then call the Insight
|
||||
// server to get details from that hash.
|
||||
async function detailsByHeightSingle(req, res, next) {
|
||||
try {
|
||||
const height = req.params.height
|
||||
|
||||
// Reject if id is empty
|
||||
if (!height || height === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "height must not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockhash"
|
||||
requestConfig.data.method = "getblockhash"
|
||||
requestConfig.data.params = [parseInt(height)]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
const hash = response.data.result
|
||||
//console.log(`response.data: ${util.inspect(response.data)}`)
|
||||
|
||||
// Call detailsByHashSingle now that the hash has been retrieved.
|
||||
req.params.hash = hash
|
||||
return detailsByHashSingle(req, res, next)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in control/getInfo: `, error)
|
||||
wlogger.error(`Error in block.ts/detailsByHeightSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function detailsByHeightBulk(req, res, next) {
|
||||
try {
|
||||
const heights = req.body.heights
|
||||
|
||||
// Reject if heights is not an array.
|
||||
if (!Array.isArray(heights)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "heights needs to be an array. Use GET for single height."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, heights)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing detailsByHeight with these heights: `, heights)
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < heights.length; i++) {
|
||||
const thisHeight = heights[i]
|
||||
|
||||
// Reject if id is empty
|
||||
if (!thisHeight || thisHeight === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "height must not be empty" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each height and creates an array of requests to call in parallel
|
||||
const promises = heights.map(async height => {
|
||||
requestConfig.data.id = "getblockhash"
|
||||
requestConfig.data.method = "getblockhash"
|
||||
requestConfig.data.params = [parseInt(height)]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
const hash = response.data.result
|
||||
|
||||
const axiosResult = await axios.get(
|
||||
`${process.env.BITCOINCOM_BASEURL}block/${hash}`
|
||||
)
|
||||
|
||||
return axiosResult.data
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const result = await axios.all(promises)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (error) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(error)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
if (error.response && error.response.status === 404) {
|
||||
res.status(404)
|
||||
return res.json({ error: "Not Found" })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in block/detailsByHash: `, error)
|
||||
wlogger.error(`Error in block.ts/detailsByHeightBulk().`, error)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(error) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
detailsByHashSingle,
|
||||
detailsByHashBulk,
|
||||
detailsByHeightSingle,
|
||||
detailsByHeightBulk
|
||||
}
|
||||
}
|
||||
@@ -1,890 +0,0 @@
|
||||
/*
|
||||
TODO
|
||||
- Add blockhash functionality back into getTxOutProof
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
// Define routes.
|
||||
router.get("/", root)
|
||||
router.get("/getBestBlockHash", getBestBlockHash)
|
||||
// Dev Note: getBlock/:hash ommited because its the same as block/detailsByHash
|
||||
//router.get("/getBlock/:hash", getBlock)
|
||||
router.get("/getBlockchainInfo", getBlockchainInfo)
|
||||
router.get("/getBlockCount", getBlockCount)
|
||||
router.get("/getBlockHeader/:hash", getBlockHeaderSingle)
|
||||
router.post("/getBlockHeader", getBlockHeaderBulk)
|
||||
|
||||
router.get("/getChainTips", getChainTips)
|
||||
router.get("/getDifficulty", getDifficulty)
|
||||
router.get("/getMempoolEntry/:txid", getMempoolEntrySingle)
|
||||
router.post("/getMempoolEntry", getMempoolEntryBulk)
|
||||
router.get("/getMempoolInfo", getMempoolInfo)
|
||||
router.get("/getRawMempool", getRawMempool)
|
||||
router.get("/getTxOut/:txid/:n", getTxOut)
|
||||
router.get("/getTxOutProof/:txid", getTxOutProofSingle)
|
||||
router.post("/getTxOutProof", getTxOutProofBulk)
|
||||
router.get("/verifyTxOutProof/:proof", verifyTxOutProofSingle)
|
||||
router.post("/verifyTxOutProof", verifyTxOutProofBulk)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "blockchain" })
|
||||
}
|
||||
|
||||
// Returns the hash of the best (tip) block in the longest block chain.
|
||||
async function getBestBlockHash(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getbestblockhash"
|
||||
requestConfig.data.method = "getbestblockhash"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockchainInfo(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockchaininfo"
|
||||
requestConfig.data.method = "getblockchaininfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockCount(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockcount"
|
||||
requestConfig.data.method = "getblockcount"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockCount().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockHeaderSingle(req, res, next) {
|
||||
try {
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose.toString() === "true")
|
||||
verbose = true
|
||||
|
||||
const hash = req.params.hash
|
||||
if (!hash || hash === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hash can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getblockheader"
|
||||
requestConfig.data.method = "getblockheader"
|
||||
requestConfig.data.params = [hash, verbose]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockHeaderSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlockHeaderBulk(req, res, next) {
|
||||
try {
|
||||
const hashes = req.body.hashes
|
||||
const verbose = req.body.verbose ? req.body.verbose : false
|
||||
|
||||
if (!Array.isArray(hashes)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "hashes needs to be an array. Use GET for single hash."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hashes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing blockchain/getBlockHeaderBulk with these hashes: `,
|
||||
hashes
|
||||
)
|
||||
|
||||
// Validate each hash in the array.
|
||||
for (let i = 0; i < hashes.length; i++) {
|
||||
const hash = hashes[i]
|
||||
|
||||
if (hash.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({ error: `This is not a hash: ${hash}` })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each hash and creates an array of requests to call in parallel
|
||||
const promises = hashes.map(async hash => {
|
||||
requestConfig.data.id = "getblockheader"
|
||||
requestConfig.data.method = "getblockheader"
|
||||
requestConfig.data.params = [hash, verbose]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getBlockHeaderBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getChainTips(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getchaintips"
|
||||
requestConfig.data.method = "getchaintips"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getChainTips().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current difficulty value, used to regulate mining power on the network.
|
||||
async function getDifficulty(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getdifficulty"
|
||||
requestConfig.data.method = "getdifficulty"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getDifficulty().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns mempool data for given transaction. TXID must be in mempool (unconfirmed)
|
||||
async function getMempoolEntrySingle(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getmempoolentry"
|
||||
requestConfig.data.method = "getmempoolentry"
|
||||
requestConfig.data.params = [txid]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getMempoolEntrySingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getMempoolEntryBulk(req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "txids needs to be an array. Use GET for single txid."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing blockchain/getMempoolEntry with these txids: `,
|
||||
txids
|
||||
)
|
||||
|
||||
// Validate each element in the array
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const txid = txids[i]
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({ error: "This is not a txid" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async txid => {
|
||||
requestConfig.data.id = "getmempoolentry"
|
||||
requestConfig.data.method = "getmempoolentry"
|
||||
requestConfig.data.params = [txid]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getMempoolEntryBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getMempoolInfo(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getmempoolinfo"
|
||||
requestConfig.data.method = "getmempoolinfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getMempoolInfo().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getRawMempool(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose === "true") verbose = true
|
||||
|
||||
requestConfig.data.id = "getrawmempool"
|
||||
requestConfig.data.method = "getrawmempool"
|
||||
requestConfig.data.params = [verbose]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getRawMempool().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns details about an unspent transaction output.
|
||||
async function getTxOut(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
let n = req.params.n
|
||||
if (n === undefined || n === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "n can not be empty" })
|
||||
}
|
||||
n = parseInt(n)
|
||||
|
||||
let include_mempool = false
|
||||
if (req.query.include_mempool && req.query.include_mempool === "true")
|
||||
include_mempool = true
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "gettxout"
|
||||
requestConfig.data.method = "gettxout"
|
||||
requestConfig.data.params = [txid, n, include_mempool]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getTxOut().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a hex-encoded proof that 'txid' was included in a block.
|
||||
async function getTxOutProofSingle(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "gettxoutproof"
|
||||
requestConfig.data.method = "gettxoutproof"
|
||||
requestConfig.data.params = [[txid]]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getTxOutProofSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a hex-encoded proof that 'txid' was included in a block.
|
||||
async function getTxOutProofBulk(req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
// Reject if txids is not an array.
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "txids needs to be an array. Use GET for single txid."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Validate each element in the array.
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const txid = txids[i]
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid txid. Double check your txid is valid: ${txid}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Executing blockchain/getTxOutProof with these txids: `, txids)
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async txid => {
|
||||
requestConfig.data.id = "gettxoutproof"
|
||||
requestConfig.data.method = "gettxoutproof"
|
||||
requestConfig.data.params = [[txid]]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel promisses to resolve.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/getTxOutProofBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//
|
||||
// router.get('/preciousBlock/:hash', async (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"preciousblock",
|
||||
// method: "preciousblock",
|
||||
// params: [
|
||||
// req.params.hash
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(JSON.stringify(response.data.result));
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.post('/pruneBlockchain/:height', async (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"pruneblockchain",
|
||||
// method: "pruneblockchain",
|
||||
// params: [
|
||||
// req.params.height
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// router.get('/verifyChain', async (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"verifychain",
|
||||
// method: "verifychain"
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
*/
|
||||
|
||||
async function verifyTxOutProofSingle(req, res, next) {
|
||||
try {
|
||||
// Validate input parameter
|
||||
const proof = req.params.proof
|
||||
if (!proof || proof === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "proof can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "verifytxoutproof"
|
||||
requestConfig.data.method = "verifytxoutproof"
|
||||
requestConfig.data.params = [req.params.proof]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/verifyTxOutProofSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyTxOutProofBulk(req, res, next) {
|
||||
try {
|
||||
const proofs = req.body.proofs
|
||||
|
||||
// Reject if proofs is not an array.
|
||||
if (!Array.isArray(proofs)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "proofs needs to be an array. Use GET for single proof."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, proofs)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Validate each element in the array.
|
||||
for (let i = 0; i < proofs.length; i++) {
|
||||
const proof = proofs[i]
|
||||
|
||||
if (!proof || proof === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: `proof can not be empty: ${proof}` })
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing blockchain/verifyTxOutProof with these proofs: `,
|
||||
proofs
|
||||
)
|
||||
|
||||
// Loop through each proof and creates an array of requests to call in parallel
|
||||
const promises = proofs.map(async proof => {
|
||||
requestConfig.data.id = "verifytxoutproof"
|
||||
requestConfig.data.method = "verifytxoutproof"
|
||||
requestConfig.data.params = [proof]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel promisses to resolve.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result[0])
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in blockchain.ts/verifyTxOutProofBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
getBestBlockHash,
|
||||
//getBlock,
|
||||
getBlockchainInfo,
|
||||
getBlockCount,
|
||||
getBlockHeaderSingle,
|
||||
getBlockHeaderBulk,
|
||||
getChainTips,
|
||||
getDifficulty,
|
||||
getMempoolInfo,
|
||||
getRawMempool,
|
||||
getMempoolEntrySingle,
|
||||
getMempoolEntryBulk,
|
||||
getTxOut,
|
||||
getTxOutProofSingle,
|
||||
getTxOutProofBulk,
|
||||
verifyTxOutProofSingle,
|
||||
verifyTxOutProofBulk
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
//const axios = require("axios");
|
||||
|
||||
//const BITBOXCli = require("bitbox-sdk/lib/bitbox-sdk").default;
|
||||
//const BITBOX = new BITBOXCli();
|
||||
|
||||
//const BitboxHTTP = axios.create({
|
||||
// baseURL: process.env.RPC_BASEURL,
|
||||
//});
|
||||
//const username = process.env.RPC_USERNAME;
|
||||
//const password = process.env.RPC_PASSWORD;
|
||||
|
||||
router.get("/", (req, res, next) => {
|
||||
res.json({ status: "generating" })
|
||||
})
|
||||
//
|
||||
// router.post('/generateToAddress/:nblocks/:address', (req, res, next) => {
|
||||
// let maxtries = 1000000;
|
||||
// if(req.query.maxtries) {
|
||||
// maxtries = parseInt(req.query.maxtries);
|
||||
// }
|
||||
//
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"generatetoaddress",
|
||||
// method: "generatetoaddress",
|
||||
// params: [
|
||||
// req.params.nblocks,
|
||||
// req.params.address,
|
||||
// maxtries
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = router
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
|
||||
/* GET home page. */
|
||||
router.get("/", (req, res, next) => {
|
||||
res.json({ status: "winning v2" })
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -1,15 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
|
||||
/* GET home page. */
|
||||
router.get("/", (req, res, next) => {
|
||||
res.render("swagger-v2")
|
||||
})
|
||||
|
||||
router.get("/v2", (req, res, next) => {
|
||||
res.render("swagger-v2")
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
A utility library for setting up greylog2 logging.
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
// This will be uncommented and correct once we have our logging server functioning.
|
||||
/*
|
||||
var graylog2 = require("graylog2");
|
||||
var logger = new graylog2.graylog({
|
||||
servers: [
|
||||
{ 'host': '127.0.0.1', port: 12201 },
|
||||
{ 'host': '127.0.0.2', port: 12201 }
|
||||
],
|
||||
hostname: 'server.name', // the name of this host
|
||||
// (optional, default: os.hostname())
|
||||
facility: 'Node.js', // the facility for these log messages
|
||||
// (optional, default: "Node.js")
|
||||
bufferSize: 1350 // max UDP packet size, should never exceed the
|
||||
// MTU of your system (optional, default: 1400)
|
||||
});
|
||||
|
||||
logger.on('error', function (error) {
|
||||
console.error('Error while trying to write to graylog2:', error);
|
||||
});
|
||||
*/
|
||||
|
||||
// This is just a placeholder function that will be replaced once we get the
|
||||
// greylog server working.
|
||||
function log(msg, obj) {
|
||||
//console.log(msg, obj)
|
||||
}
|
||||
|
||||
// This is just a placeholder function that will be replaced once we get the
|
||||
// greylog server working.
|
||||
function error(msg, obj) {
|
||||
if (!obj) console.error(msg)
|
||||
else console.error(msg, obj)
|
||||
}
|
||||
|
||||
// This is just a placeholder function that will be replaced once we get the
|
||||
// greylog server working.
|
||||
function debug(msg, obj) {
|
||||
//console.log(msg, obj)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
error,
|
||||
debug
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const requestConfig = {
|
||||
method: "post",
|
||||
auth: {
|
||||
username: username,
|
||||
password: password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/getMiningInfo", getMiningInfo)
|
||||
router.get("/getNetworkHashps", getNetworkHashPS)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "mining" })
|
||||
}
|
||||
|
||||
//
|
||||
// router.get('/getBlockTemplate/:templateRequest', (req, res, next) => {
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"getblocktemplate",
|
||||
// method: "getblocktemplate",
|
||||
// params: [
|
||||
// req.params.templateRequest
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
|
||||
async function getMiningInfo(req, res, next) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getmininginfo"
|
||||
requestConfig.data.method = "getmininginfo"
|
||||
requestConfig.data.params = []
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in mining.ts/getMiningInfo().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function getNetworkHashPS(req, res, next) {
|
||||
try {
|
||||
let nblocks = 120 // Default
|
||||
let height = -1 // Default
|
||||
if (req.query.nblocks) nblocks = parseInt(req.query.nblocks)
|
||||
if (req.query.height) height = parseInt(req.query.height)
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getnetworkhashps"
|
||||
requestConfig.data.method = "getnetworkhashps"
|
||||
requestConfig.data.params = [nblocks, height]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in mining.ts/getNetworkHashPS().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// router.post('/submitBlock/:hex', (req, res, next) => {
|
||||
// let parameters = '';
|
||||
// if(req.query.parameters && req.query.parameters !== '') {
|
||||
// parameters = true;
|
||||
// }
|
||||
//
|
||||
// BitboxHTTP({
|
||||
// method: 'post',
|
||||
// auth: {
|
||||
// username: username,
|
||||
// password: password
|
||||
// },
|
||||
// data: {
|
||||
// jsonrpc: "1.0",
|
||||
// id:"submitblock",
|
||||
// method: "submitblock",
|
||||
// params: [
|
||||
// req.params.hex,
|
||||
// parameters
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
// .then((response) => {
|
||||
// res.json(response.data.result);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// res.send(error.response.data.error.message);
|
||||
// });
|
||||
// });
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
getMiningInfo,
|
||||
getNetworkHashPS
|
||||
}
|
||||
}
|
||||
@@ -1,609 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const requestConfig = {
|
||||
method: "post",
|
||||
auth: {
|
||||
username: username,
|
||||
password: password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/decodeRawTransaction/:hex", decodeRawTransactionSingle)
|
||||
router.post("/decodeRawTransaction", decodeRawTransactionBulk)
|
||||
router.get("/decodeScript/:hex", decodeScriptSingle)
|
||||
router.post("/decodeScript", decodeScriptBulk)
|
||||
router.post("/getRawTransaction", getRawTransactionBulk)
|
||||
router.get("/getRawTransaction/:txid", getRawTransactionSingle)
|
||||
router.post("/sendRawTransaction", sendRawTransactionBulk)
|
||||
router.get("/sendRawTransaction/:hex", sendRawTransactionSingle)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "rawtransactions" })
|
||||
}
|
||||
|
||||
// Decode transaction hex into a JSON object.
|
||||
// GET
|
||||
async function decodeRawTransactionSingle(req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "decoderawtransaction"
|
||||
requestConfig.data.method = "decoderawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
`Error in rawtransactions.ts/decodeRawTransactionSingle().`,
|
||||
err
|
||||
)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function decodeRawTransactionBulk(req, res, next) {
|
||||
try {
|
||||
const hexes = req.body.hexes
|
||||
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "hexes must be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
const results = []
|
||||
|
||||
// Validate each element in the address array.
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const thisHex = hexes[i]
|
||||
|
||||
// Reject if id is empty
|
||||
if (!thisHex || thisHex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "Encountered empty hex" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each height and creates an array of requests to call in parallel
|
||||
const promises = hexes.map(async hex => {
|
||||
requestConfig.data.id = "decoderawtransaction"
|
||||
requestConfig.data.method = "decoderawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
|
||||
/*
|
||||
// Loop through each hex and creates an array of requests to call in parallel
|
||||
hexes = hexes.map(async (hex: any) => {
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "Encountered empty hex" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "decoderawtransaction"
|
||||
requestConfig.data.method = "decoderawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
const result: Array<any> = []
|
||||
return axios.all(hexes).then(
|
||||
axios.spread((...args) => {
|
||||
args.forEach((arg: any) => {
|
||||
if (arg) {
|
||||
result.push(arg.data.result)
|
||||
}
|
||||
})
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
})
|
||||
)
|
||||
*/
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(
|
||||
`Error in rawtransactions.ts/decodeRawTransactionBulk().`,
|
||||
err
|
||||
)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw transaction from hex to assembly.
|
||||
// GET single
|
||||
async function decodeScriptSingle(req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "decodescript"
|
||||
requestConfig.data.method = "decodescript"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeScript: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/decodeScriptSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw transaction from hex to assembly.
|
||||
// POST bulk
|
||||
async function decodeScriptBulk(req, res, next) {
|
||||
try {
|
||||
const hexes = req.body.hexes
|
||||
|
||||
// Validation
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "hexes must be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each hex in the array
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const hex = hexes[i]
|
||||
|
||||
// Throw an error if hex is empty.
|
||||
if (!hex || hex === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "Encountered empty hex" })
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each hex and create an array of promises
|
||||
const promises = hexes.map(async hex => {
|
||||
requestConfig.data.id = "decodescript"
|
||||
requestConfig.data.method = "decodescript"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
return response
|
||||
})
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const resolved = await Promise.all(promises)
|
||||
|
||||
// Retrieve the data from each resolved promise.
|
||||
const result = resolved.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeScript: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/decodeScriptBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve raw transactions details from the full node.
|
||||
async function getRawTransactionsFromNode(txid, verbose) {
|
||||
try {
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "getrawtransaction"
|
||||
requestConfig.data.method = "getrawtransaction"
|
||||
requestConfig.data.params = [txid, verbose]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return response.data.result
|
||||
} catch (err) {
|
||||
wlogger.error(`Error in rawtransactions.ts/getRawTransactionsFromNode().`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get a JSON object breakdown of transaction details.
|
||||
// POST
|
||||
async function getRawTransactionBulk(req, res, next) {
|
||||
try {
|
||||
let verbose = 0
|
||||
if (req.body.verbose) verbose = 1
|
||||
|
||||
const txids = req.body.txids
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "txids must be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// stub response object
|
||||
const returnResponse = {
|
||||
status: 100,
|
||||
json: {
|
||||
error: ""
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each txid in the array.
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const txid = txids[i]
|
||||
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: `Encountered empty TXID` })
|
||||
}
|
||||
|
||||
if (txid.length !== 64) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `parameter 1 must be of length 64 (not ${txid.length})`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through each txid and create an array of promises
|
||||
const promises = txids.map(async txid =>
|
||||
getRawTransactionsFromNode(txid, verbose)
|
||||
)
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
res.status(200)
|
||||
return res.json(axiosResult)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/getRawTransactionBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Get a JSON object breakdown of transaction details.
|
||||
// GET
|
||||
async function getRawTransactionSingle(req, res, next) {
|
||||
try {
|
||||
let verbose = 0
|
||||
if (req.query.verbose === "true") verbose = 1
|
||||
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
const data = await getRawTransactionsFromNode(txid, verbose)
|
||||
|
||||
return res.json(data)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/getRawTransaction: `, err)
|
||||
wlogger.error(`Error in rawtransactions.ts/getRawTransactionSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit a raw transaction to the BCH network.
|
||||
async function sendRawTransactionBulk(req, res, next) {
|
||||
try {
|
||||
// Validation
|
||||
const hexes = req.body.hexes
|
||||
|
||||
// Reject if input is not an array
|
||||
if (!Array.isArray(hexes)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex must be an array" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hexes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const hex = hexes[i]
|
||||
|
||||
if (hex === "") {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Encountered empty hex`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Dev Note CT 1/31/2019:
|
||||
// Sending the 'sendrawtrnasaction' RPC call to a full node in parallel will
|
||||
// not work. Testing showed that the full node will return the same TXID for
|
||||
// different TX hexes. I believe this is by design, to prevent double spends.
|
||||
// In parallel, we are essentially asking the node to broadcast a new TX before
|
||||
// it's finished broadcast the previous one. Serial execution is required.
|
||||
|
||||
// How to send TX hexes in parallel the WRONG WAY:
|
||||
/*
|
||||
// Collect an array of promises.
|
||||
const promises = hexes.map(async (hex: any) => {
|
||||
requestConfig.data.id = "sendrawtransaction"
|
||||
requestConfig.data.method = "sendrawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult: Array<any> = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
*/
|
||||
|
||||
// Sending them serially.
|
||||
const result = []
|
||||
for (let i = 0; i < hexes.length; i++) {
|
||||
const hex = hexes[i]
|
||||
|
||||
requestConfig.data.id = "sendrawtransaction"
|
||||
requestConfig.data.method = "sendrawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const rpcResult = await BitboxHTTP(requestConfig)
|
||||
|
||||
result.push(rpcResult.data.result)
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in rawtransactions.ts/sendRawTransactionBulk().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// Transmit a raw transaction to the BCH network.
|
||||
async function sendRawTransactionSingle(req, res, next) {
|
||||
try {
|
||||
const hex = req.params.hex // URL parameter
|
||||
|
||||
// Reject if input is not an array or a string
|
||||
if (typeof hex !== "string") {
|
||||
res.status(400)
|
||||
return res.json({ error: "hex must be a string" })
|
||||
}
|
||||
|
||||
// Validation
|
||||
if (hex === "") {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Encountered empty hex`
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// RPC call
|
||||
requestConfig.data.id = "sendrawtransaction"
|
||||
requestConfig.data.method = "sendrawtransaction"
|
||||
requestConfig.data.params = [hex]
|
||||
|
||||
const rpcResult = await BitboxHTTP(requestConfig)
|
||||
|
||||
const result = rpcResult.data.result
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(
|
||||
`Error in rawtransactions.ts/sendRawTransactionSingle().`,
|
||||
err
|
||||
)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
getRawTransactionsFromNode,
|
||||
testableComponents: {
|
||||
root,
|
||||
decodeRawTransactionSingle,
|
||||
decodeRawTransactionBulk,
|
||||
decodeScriptSingle,
|
||||
decodeScriptBulk,
|
||||
getRawTransactionBulk,
|
||||
getRawTransactionSingle,
|
||||
sendRawTransactionBulk,
|
||||
sendRawTransactionSingle
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
A private library of utility functions used by several different routes.
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
const axios = require("axios")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
module.exports = {
|
||||
validateNetwork, // Prevents a common user error
|
||||
setEnvVars, // Allows RPC variables to be set dynamically based on changing env vars.
|
||||
decodeError, // Extract and interpret error messages.
|
||||
validateArraySize // Ensure the passed array meets rate limiting requirements.
|
||||
}
|
||||
|
||||
// This function expects the Request Express.js object and an array as input.
|
||||
// The array is then validated against freemium and pro-tier rate limiting
|
||||
// requirements. A boolean is returned to indicate if the array size if valid
|
||||
// or not.
|
||||
function validateArraySize(req, array) {
|
||||
const FREEMIUM_INPUT_SIZE = 20
|
||||
const PRO_INPUT_SIZE = 100
|
||||
|
||||
if (req.locals && req.locals.proLimit) {
|
||||
if (array.length <= PRO_INPUT_SIZE) return true
|
||||
} else if (array.length <= FREEMIUM_INPUT_SIZE) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if user-provided cash address matches the correct network,
|
||||
// mainnet or testnet. If NETWORK env var is not defined, it returns false.
|
||||
// This prevent a common user-error issue that is easy to make: passing a
|
||||
// testnet address into rest.bitcoin.com or passing a mainnet address into
|
||||
// trest.bitcoin.com.
|
||||
function validateNetwork(addr) {
|
||||
try {
|
||||
const network = process.env.NETWORK
|
||||
|
||||
// Return false if NETWORK is not defined.
|
||||
if (!network || network === "") {
|
||||
console.log(`Warning: NETWORK environment variable is not defined!`)
|
||||
return false
|
||||
}
|
||||
|
||||
// Convert the user-provided address to a cashaddress, for easy detection
|
||||
// of the intended network.
|
||||
const cashAddr = BITBOX.Address.toCashAddress(addr)
|
||||
|
||||
// Return true if the network and address both match testnet
|
||||
const addrIsTest = BITBOX.Address.isTestnetAddress(cashAddr)
|
||||
if (network === "testnet" && addrIsTest) return true
|
||||
|
||||
// Return true if the network and address both match mainnet
|
||||
const addrIsMain = BITBOX.Address.isMainnetAddress(cashAddr)
|
||||
if (network === "mainnet" && addrIsMain) return true
|
||||
|
||||
return false
|
||||
} catch (err) {
|
||||
logger.error(`Error in validateNetwork()`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically set these based on env vars. Allows unit testing.
|
||||
function setEnvVars() {
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const requestConfig = {
|
||||
method: "post",
|
||||
auth: {
|
||||
username: username,
|
||||
password: password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
return { BitboxHTTP, username, password, requestConfig }
|
||||
}
|
||||
|
||||
// Error messages returned by a full node can be burried pretty deep inside the
|
||||
// error object returned by Axios. This function attempts to extract and interpret
|
||||
// error messages.
|
||||
// Returns an object. If successful, obj.msg is a string.
|
||||
// If there is a failure, obj.msg is false.
|
||||
function decodeError(err) {
|
||||
try {
|
||||
// Attempt to extract the full node error message.
|
||||
if (
|
||||
err.response &&
|
||||
err.response.data &&
|
||||
err.response.data.error &&
|
||||
err.response.data.error.message
|
||||
)
|
||||
return { msg: err.response.data.error.message, status: 400 }
|
||||
|
||||
// Attempt to extract the Insight error message
|
||||
if (err.response && err.response.data)
|
||||
return { msg: err.response.data, status: err.response.status }
|
||||
|
||||
// Attempt to detect a network connection error.
|
||||
if (err.message && err.message.indexOf("ENOTFOUND") > -1) {
|
||||
return {
|
||||
msg:
|
||||
"Network error: Could not communicate with full node or other external service.",
|
||||
status: 503
|
||||
}
|
||||
}
|
||||
|
||||
// Different kind of network error
|
||||
if (err.message && err.message.indexOf("ENETUNREACH") > -1) {
|
||||
return {
|
||||
msg:
|
||||
"Network error: Could not communicate with full node or other external service.",
|
||||
status: 503
|
||||
}
|
||||
}
|
||||
|
||||
return { msg: false, status: 500 }
|
||||
} catch (err) {
|
||||
wlogger.error(`unhandled error in route-utils.js/decodeError(): `, err)
|
||||
return { msg: false, status: 500 }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const axios = require("axios")
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
|
||||
const getInstance = () => BitboxHTTP
|
||||
|
||||
module.exports = getInstance
|
||||
@@ -1,20 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const getRequestConfig = (method, params) => ({
|
||||
method: "post",
|
||||
auth: {
|
||||
username,
|
||||
password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0",
|
||||
id: method,
|
||||
method,
|
||||
params
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = getRequestConfig
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,188 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
const BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 3 }
|
||||
|
||||
// Manipulates and formats the raw data comming from Insight API.
|
||||
const processInputs = tx => {
|
||||
// Add legacy and cashaddr to tx vin
|
||||
if (tx.vin) {
|
||||
tx.vin.forEach(vin => {
|
||||
if (!vin.coinbase) {
|
||||
vin.value = vin.valueSat
|
||||
const address = vin.addr
|
||||
if (address) {
|
||||
vin.legacyAddress = BITBOX.Address.toLegacyAddress(address)
|
||||
vin.cashAddress = BITBOX.Address.toCashAddress(address)
|
||||
delete vin.addr
|
||||
}
|
||||
delete vin.valueSat
|
||||
delete vin.doubleSpentTxID
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add legacy and cashaddr to tx vout
|
||||
if (tx.vout) {
|
||||
tx.vout.forEach(vout => {
|
||||
// Overwrite value string with value in satoshis
|
||||
//vout.value = parseFloat(vout.value) * 100000000
|
||||
|
||||
if (vout.scriptPubKey) {
|
||||
if (vout.scriptPubKey.addresses) {
|
||||
const cashAddrs = []
|
||||
vout.scriptPubKey.addresses.forEach(addr => {
|
||||
const cashAddr = BITBOX.Address.toCashAddress(addr)
|
||||
cashAddrs.push(cashAddr)
|
||||
})
|
||||
vout.scriptPubKey.cashAddrs = cashAddrs
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.post("/details", detailsBulk)
|
||||
router.get("/details/:txid", detailsSingle)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "transaction" })
|
||||
}
|
||||
|
||||
// Retrieve transaction data from the Insight API
|
||||
// This function is also used by the SLP route library.
|
||||
async function transactionsFromInsight(txid) {
|
||||
try {
|
||||
const path = `${process.env.BITCOINCOM_BASEURL}tx/${txid}`
|
||||
|
||||
// Query the Insight server.
|
||||
const response = await axios.get(path)
|
||||
//console.log(`Insight output: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
// Parse the data.
|
||||
const parsed = response.data
|
||||
if (parsed) processInputs(parsed)
|
||||
|
||||
return parsed
|
||||
} catch (err) {
|
||||
// Dev Note: Do not log error messages here. Throw them instead and let the
|
||||
// parent function handle it.
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function detailsBulk(req, res, next) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
// Reject if address is not an array.
|
||||
if (!Array.isArray(txids)) {
|
||||
res.status(400)
|
||||
return res.json({ error: "txids needs to be an array" })
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(`Executing transaction/details with these txids: `, txids)
|
||||
|
||||
// Collect an array of promises
|
||||
const promises = txids.map(
|
||||
async txid => await transactionsFromInsight(txid)
|
||||
)
|
||||
|
||||
// Wait for all parallel promises to return.
|
||||
const result = await Promise.all(promises)
|
||||
|
||||
// Return the array of retrieved transaction information.
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in transactions.ts/detailsBulk().`, err)
|
||||
|
||||
//console.log(`Error in transaction details: `, err)
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
// GET handler. Retrieve any unconfirmed TX information for a given address.
|
||||
async function detailsSingle(req, res, next) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
if (!txid || txid === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "txid can not be empty" })
|
||||
}
|
||||
|
||||
// Reject if address is an array.
|
||||
if (Array.isArray(txid)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "txid can not be an array. Use POST for bulk upload."
|
||||
})
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Executing transaction.ts/detailsSingle with this txid: `,
|
||||
txid
|
||||
)
|
||||
|
||||
// Query the Insight API.
|
||||
const retData = await transactionsFromInsight(txid)
|
||||
//console.log(`retData: ${JSON.stringify(retData,null,2)}`)
|
||||
|
||||
// Return the array of retrieved address information.
|
||||
res.status(200)
|
||||
return res.json(retData)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
// Write out error to error log.
|
||||
//logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error(`Error in transactions.ts/detailsSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
transactionsFromInsight,
|
||||
testableComponents: {
|
||||
root,
|
||||
detailsBulk,
|
||||
detailsSingle
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const express = require("express")
|
||||
const router = express.Router()
|
||||
const axios = require("axios")
|
||||
|
||||
const routeUtils = require("./route-utils")
|
||||
const logger = require("./logging.js")
|
||||
const wlogger = require("../../util/winston-logging")
|
||||
|
||||
const BITBOXJS = require("@chris.troutner/bch-js")
|
||||
const BITBOX = new BITBOXJS()
|
||||
|
||||
// Used to convert error messages to strings, to safely pass to users.
|
||||
const util = require("util")
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const requestConfig = {
|
||||
method: "post",
|
||||
auth: {
|
||||
username: username,
|
||||
password: password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/", root)
|
||||
router.get("/validateAddress/:address", validateAddressSingle)
|
||||
router.post("/validateAddress", validateAddressBulk)
|
||||
|
||||
function root(req, res, next) {
|
||||
return res.json({ status: "util" })
|
||||
}
|
||||
|
||||
async function validateAddressSingle(req, res, next) {
|
||||
try {
|
||||
const address = req.params.address
|
||||
if (!address || address === "") {
|
||||
res.status(400)
|
||||
return res.json({ error: "address can not be empty" })
|
||||
}
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
requestConfig.data.id = "validateaddress"
|
||||
requestConfig.data.method = "validateaddress"
|
||||
requestConfig.data.params = [address]
|
||||
|
||||
const response = await BitboxHTTP(requestConfig)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in util.ts/validateAddressSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
async function validateAddressBulk(req, res, next) {
|
||||
try {
|
||||
const addresses = req.body.addresses
|
||||
|
||||
// Reject if addresses is not an array.
|
||||
if (!Array.isArray(addresses)) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: "addresses needs to be an array. Use GET for single address."
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, addresses)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
return res.json({
|
||||
error: `Array too large.`
|
||||
})
|
||||
}
|
||||
|
||||
// Validate each element in the array.
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
const address = addresses[i]
|
||||
|
||||
// Ensure the input is a valid BCH address.
|
||||
try {
|
||||
var legacyAddr = BITBOX.Address.toLegacyAddress(address)
|
||||
} catch (err) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid BCH address. Double check your address is valid: ${address}`
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent a common user error. Ensure they are using the correct network address.
|
||||
const networkIsValid = routeUtils.validateNetwork(address)
|
||||
if (!networkIsValid) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `Invalid network. Trying to use a testnet address on mainnet, or vice versa.`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Executing util/validate with these addresses: `, addresses)
|
||||
|
||||
const {
|
||||
BitboxHTTP,
|
||||
username,
|
||||
password,
|
||||
requestConfig
|
||||
} = routeUtils.setEnvVars()
|
||||
|
||||
// Loop through each address and creates an array of requests to call in parallel
|
||||
const promises = addresses.map(async address => {
|
||||
requestConfig.data.id = "validateaddress"
|
||||
requestConfig.data.method = "validateaddress"
|
||||
requestConfig.data.params = [address]
|
||||
|
||||
return await BitboxHTTP(requestConfig)
|
||||
})
|
||||
|
||||
// Wait for all parallel Insight requests to return.
|
||||
const axiosResult = await axios.all(promises)
|
||||
|
||||
// Retrieve the data part of the result.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
// Attempt to decode the error message.
|
||||
const { msg, status } = routeUtils.decodeError(err)
|
||||
if (msg) {
|
||||
res.status(status)
|
||||
return res.json({ error: msg })
|
||||
}
|
||||
|
||||
wlogger.error(`Error in util.ts/validateAddressSingle().`, err)
|
||||
|
||||
res.status(500)
|
||||
return res.json({ error: util.inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
testableComponents: {
|
||||
root,
|
||||
validateAddressSingle,
|
||||
validateAddressBulk
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const axios = require("axios")
|
||||
|
||||
const BitboxHTTP = axios.create({
|
||||
baseURL: process.env.RPC_BASEURL
|
||||
})
|
||||
|
||||
const getInstance = () => BitboxHTTP
|
||||
|
||||
module.exports = getInstance
|
||||
@@ -1,20 +0,0 @@
|
||||
"use strict"
|
||||
|
||||
const username = process.env.RPC_USERNAME
|
||||
const password = process.env.RPC_PASSWORD
|
||||
|
||||
const getRequestConfig = (method, params) => ({
|
||||
method: "post",
|
||||
auth: {
|
||||
username,
|
||||
password
|
||||
},
|
||||
data: {
|
||||
jsonrpc: "1.0",
|
||||
id: method,
|
||||
method,
|
||||
params
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = getRequestConfig
|
||||
+15
-22
@@ -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 = {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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"
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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?"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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"])
|
||||
})
|
||||
*/
|
||||
})
|
||||
})
|
||||
@@ -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
@@ -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"
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user